DDOS常見的手法 SYN Flood
簡單來說就是利用Client傳送大量的SYN(Synchronize同步)
然後Server會回傳SYN+ACK(Acknowledgement確認)
接著Client會再回傳ACK完成連線。
可是如果Client遲遲不傳ACK,Server端就會重傳
然後會產生大量的半連接列表(listen_sock queue)
如過queue被塞爆了,正常的連結就無法進入
原本linux預設的作法,如果queue滿了,
新進來的request就drop掉,
解決辦法就是改寫kernel讓queue滿了
隨機找一個queue或待最久的request丟掉
如此新(善良)進來的request就有機會進入listen queue
然後變成accept queue完成連線
—————————————————————————————-
Client如何實踐DDOS ? 利用防火牆iptable 讓Client把Server的IP送過來的封包都丟掉就好~ $ iptables -F //clean up iptable rule $ iptables -L //check iptable $ iptables -A INPUT -s ServerIP -j REJECT 或者是Server把ACK的封包(第三步)丟掉 (現實中不太可能自找麻煩...) $ iptables -A OUTPUT -d ClientIP -p tcp --tcp-flags SYN,ACK ACK -j DROP
找一個簡單的client程式,寫一隻script讓他跑n次
#!/usr/bin/python import os for i in range(20): os.system('./client 192.168.163.139 5566 &')
—————————————————————————————-
Trace include/net/request_sock.h
static inline int reqsk_queue_removed(struct request_sock_queue *queue, struct request_sock *req) { struct listen_sock *lopt = queue->listen_opt; if (req->retrans == 0) --lopt->qlen_young; return --lopt->qlen; } Trace net/ipv4/inet_connection_sock.c void inet_csk_reqsk_queue_prune(struct sock *parent, const unsigned long interval, const unsigned long timeout, const unsigned long max_rto) { struct inet_connection_sock *icsk = inet_csk(parent); struct request_sock_queue *queue = &icsk->icsk_accept_queue; struct listen_sock *lopt = queue->listen_opt; int max_retries = icsk->icsk_syn_retries ? : sysctl_tcp_synack_retries; int thresh = max_retries; unsigned long now = jiffies; struct request_sock **reqp, *req; .... reqp=&lopt->syn_table[i]; req = *reqp; /* Drop this request */ inet_csk_reqsk_queue_unlink(parent, req, reqp); reqsk_queue_removed(queue, req); reqsk_free(req); continue; }
——————————————————————————–
從sock的結構下來 inet_csk(parent)拿到 inet_connection_sock inet_connection_sock->icsk_accept_queue拿到request_sock_queue request_sock_queue是 accept queue request_sock_queue是->listen_opt 來得到 listen_sock listen_sock可以指向syn_table[0]來取得queue的成員(request_sock) request_sock reqp=&lopt->syn_table[i]; lopt->dl_next可以指向下一個request_sock 基本上listen queue的最大值在 net/core/request_sock.c. line 44~46 nr_table_entries變數 syn_table[i] 基本上是存成一個 hash list table (好多掛) 所以同一掛(碰撞)的可以用lopt->dl_next 來指 EX: nr_table_entries等於8就有8個hash值 碰撞的用dl_next去指,然後用syn_table跳hash值
——————————————————————————–
然後開始改寫kernel
改寫如果inet_csk_reqsk_queue_is_full的判斷
net/ipv4/tcp_ipv4.c
tcp_v4_conn_request( ) if (inet_csk_reqsk_queue_is_full(sk) && !isn) { printk("The listen queue full.\n"); struct inet_connection_sock *icsk = inet_csk(sk); struct request_sock_queue *queue = &icsk->icsk_accept_queue; struct listen_sock *lopt = queue->listen_opt; struct request_sock **reqp,*req2; int i; /* //Random remove while(1){ i = get_random_int() % 8; reqp = &lopt->syn_table[i]; req2 = *reqp; printk("i = %d\n",i); if(req2 != NULL){ printk("Connection %d dropped.\n",i); inet_csk_reqsk_queue_unlink(sk,req2,reqp); reqsk_queue_removed(queue,req2); reqsk_free(req2); break; } } */ //Remove smallest timestamp int j = 0; int small_j = 0; struct request_sock **small_reqp,*small_req2; unsigned long small_timestamp = 0; for(j=0;j<nr_table_entries;j++){ reqp = &lopt->syn_table[j]; if(j == 0){ small_timestamp = req2->timestamp; small_reqp = reqp; small_req2 = *reqp; } while((req2 = *reqp) != NULL){ printk(" j = %d , timestamp = %lu\n",j,req2->timestamp); if(req2->timestamp < small_timestamp){ small_timestamp = req2->timestamp; small_j = j; small_reqp = reqp; small_req2 = req2; } reqp = &req2->dl_next; } } printk("Connection smallest_timestamp j = %d dropped.\n",j); inet_csk_reqsk_queue_unlink(sk,small_req2,small_reqp); reqsk_queue_removed(queue,small_req2); reqsk_free(small_req2); //goto drop; }
——————————————————————–
timestamp 是我們在 request_sock自己加上去的變數
struct request_sock {
…
unsigned long timestamp;
}
然後再ret初始化的地方加上時間
jiffies是開幾以來,已經過多少的tick
void tcp_openreq_init(struct request_sock *req, …)
req->timestamp = jiffies;
——————————————————————–
其他
netstat -tno 可以看連線多少時間