1. 前言
如果只需要研究Linux的tcp協議棧行為,只需要使用packetdrill就能夠滿足我的所有需求。packetdrill才是讓我隨心所欲地撩tcp協議棧。packetdrill的簡單使用手冊。
然而悲劇的是,除了要研究Linux的TCP協議棧行為,還需要研究Windows的tcp協議棧的行為,Windows不開源,感覺裡面應該有挺多未知的坑。
為了能夠重現Windows的tcp協議棧的一些網路行為,這裡使用python的scapy進行包構造撩撩Windows的tcp協議棧。scapy在tcp資料報文注入會有一點的時延,這個工具在要求時延嚴格的情境無法使用(還是packetdrill好用,囧)。針對目前遇到的情境,勉強能用,再則已經擼慣了python,上手起來比較容易。 2. 基本文法 安裝scapy
在Centos 7.2中直接使用yum install 來安裝。
yum install scapy.noarch
help 能解決大部分問題
[root@localhost ~]# scapyINFO: Can't import python gnuplot wrapper . Won't be able to plot.INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().WARNING: No route found for IPv6 destination :: (no default route?)Welcome to Scapy (2.2.0)>>> help(send)
在大部分時候,如果看到不明白的地方,請用help。其次是官方的參考手冊
基本文法 ip/tcp/http資料包操縱
>>> IP()<IP |>>>>> IP()/TCP()<IP frag=0 proto=tcp |<TCP |>>>>>> IP(proto=55)/TCP()<IP frag=0 proto=55 |<TCP >> >>>> Ether()/IP()/TCP()<Ether type=IPv4 |<IP frag=0 proto=tcp |<TCP |>>>>>>> IP()/TCP()/"GET /HTTP/1.0\r\n\r\n" 資料部分可以直接使用字串<IP frag=0 proto=tcp |<TCP |<Raw load='GET /HTTP/1.0\r\n\r\n' |>>> >>>> Ether()/IP()/UDP()<Ether type=IPv4 |<IP frag=0 proto=udp |<UDP |>>>>>>> Ether()/IP()/IP()/UDP()<Ether type=IPv4 |<IP frag=0 proto=ipencap |<IP frag=0 proto=udp |<UDP |>>>>>>> str(IP())'E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01'>>> IP(_)<IP version=4L ihl=5L tos=0x0 len=20 id=1 flags= frag=0L ttl=64 proto=hopopt chksum=0x7ce7 src=127.0.0.1 dst=127.0.0.1 |>>>> a=Ether()/IP(dst="www.baidu.com")/TCP()/"GET /index.html HTTP/1.0 \n\n">>> hexdump(a)0000 00 03 0F 19 6A 49 08 00 27 FE D8 12 08 00 45 00 ....jI..'.....E.0010 00 43 00 01 00 00 40 06 70 78 C0 A8 73 C6 B4 61 .C....@.px..s..a0020 21 6C 00 14 00 50 00 00 00 00 00 00 00 00 50 02 !l...P........P.0030 20 00 B3 75 00 00 47 45 54 20 2F 69 6E 64 65 78 ..u..GET /index0040 2E 68 74 6D 6C 20 48 54 54 50 2F 31 2E 30 20 0A .html HTTP/1.0 .0050 0A .>>> b=str(a)>>> b"\x00\x03\x0f\x19jI\x08\x00'\xfe\xd8\x12\x08\x00E\x00\x00C\x00\x01\x00\x00@\x06px\xc0\xa8s\xc6\xb4a!l\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\xb3u\x00\x00GET /index.html HTTP/1.0 \n\n"
資料包發送
資料包的發送主要包括以下函數send/sendp/sr/sr1/srp 主要區別是:
send函數工作在第三層
send(IP(dst="192.168.115.188")/ICMP())
sendp函數工作在第二層,你可以選擇網卡和協議
sendp(Ether()/IP(dst="192.168.115.188",ttl=(1,4)),iface="eth0")
fuzz函數的作用:可以更改一些預設的不可以被計算的值(比如校正和checksums),更改的值是隨機的,但是類型是符合欄位的值的。
send(IP(dst="www.baidu.com")/UDP()/NTP(version=4),loop=2) #未使用fuzz() send(IP(dst=" www.baidu.com")/fuzz(UDP()/NTP(version=4)),loop=2) #使用fuzz()
SR()函數用來來發送資料包和接收響應。該函數返回有回應的資料包和沒有回應的資料包;該函數也算得上是scapy的核心了,他會返回兩個列表資料,一個是answer list 另一個是unanswered
>>> sr(IP(dst="192.168.115.1")/TCP(dport=[21,22,23]))Begin emission:Finished to send 3 packets.***Received 3 packets, got 3 answers, remaining 0 packetsResults: TCP:3 UDP:0 ICMP:0 Other:0>, Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>>> ans,unans=_ 這也是scapy的核心了>>> ans.show()0000 IP / TCP 192.168.115.198:ftp_data > 192.168.115.1:ftp S ==> IP / TCP 192.168.115.1:ftp > 192.168.115.198:ftp_data RA / Padding0001 IP / TCP 192.168.115.198:ftp_data > 192.168.115.1:ssh S ==> IP / TCP 192.168.115.1:ssh > 192.168.115.198:ftp_data RA / Padding0002 IP / TCP 192.168.115.198:ftp_data > 192.168.115.1:telnet S ==> IP / TCP 192.168.115.1:telnet > 192.168.115.198:ftp_data SA / Padding >>>sr(IP(dst="192.168.115.1")/TCP(dport=[21,22,23]),inter=0.5,retry=-2,timeout=1) 網路環境不好時,也可以追加inter retry timeout等附加資訊,
函數sr1()是sr()一個變種,只返回應答發送的分組(或分組集)。這兩個函數發送的資料包必須是第3層資料包(IP,ARP等)。而函數SRP()位於第2層(乙太網路,802.3,等)。
>>> p=sr1(IP(dst="192.168.115.188")/ICMP()/"test")Begin emission:.....Finished to send 1 packets..*Received 7 packets, got 1 answers, remaining 0 packets>>> p<IP version=4L ihl=5L tos=0x0 len=32 id=26000 flags= frag=0L ttl=128 proto=icmp chksum=0x6c79 src=192.168.115.188 dst=192.168.115.198 options=[] |<ICMP type=echo-reply code=0 chksum=0x1826 id=0x0 seq=0x0 |<Raw load='test' |<Padding load='\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |>>>>>>> p.show()###[ IP ]### version= 4L ihl= 5L tos= 0x0 len= 32 id= 26000 flags= frag= 0L ttl= 128 proto= icmp chksum= 0x6c79 src= 192.168.115.188 dst= 192.168.115.198 \options\###[ ICMP ]### type= echo-reply code= 0 chksum= 0x1826 id= 0x0 seq= 0x0###[ Raw ]### load= 'test'###[ Padding ]### load= '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
資料包sniff
a=sniff(count=1,filter="tcp and host 192.168.1.1 and port 80")
使用sniff主要是用於資料包的接收,根據filter設定的條件,將合格資料包接收回來。 3. 情境構造
scapy的缺點是,他只負責構造包,是單向的。不像packetdrill這麼完美,packetdrill 不但可以構造包,還能實現系統調用構造不同的情境,還能幫你檢查協議棧發出的資料包是否符合預期。撩協tcp協議棧的過程不外乎兩端,一端使用系統調用類比協議棧行為,另外一端則是我們構造的包。常見情境主要是:伺服器陣列景、用戶端情境。 伺服器陣列景:
伺服器陣列景使用系統調用(即使用者態程式),而用戶端則是scapy構造的包。
在這裡構造一個簡單的三向交握後向伺服器端發送資料。為了防止Linux用戶端rst。
iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 192.168.56.1 -j DROP
#!/usr/local/bin/pythonfrom scapy.all import *# VARIABLESsrc = sys.argv[1]dst = sys.argv[2]sport = random.randint(1024,65535)dport = int(sys.argv[3])# SYNip=IP(src=src,dst=dst)SYN=TCP(sport=sport,dport=dport,flags='S',seq=1000)SYNACK=sr1(ip/SYN)# ACKACK=TCP(sport=sport, dport=dport, flags='A', seq=SYNACK.ack, ack=SYNACK.seq + 1)send(ip/ACK)
在這裡可以安裝一個nginx來驗證。 用戶端情境:
用戶端情境使用系統調用(即使用者態程式),而伺服器端則是scapy構造包。
在這裡使用scapy構造一個簡單的http伺服器。為了防止協議棧發送RST,需要對iptables進行設定。
iptables -A OUTPUT -p tcp --tcp-flags RST RST --sport 80 -j DROP
#!/usr/bin/pythonfrom scapy.all import *# Interacts with a client by going through the three-way handshake.# Shuts down the connection immediately after the connection has been established.# Akaljed Dec 2010, http://www.akaljed.wordpress.com# Wait for client to connect.a=sniff(count=1,filter="tcp and host 192.168.1.1 and port 80")# some variables for later use.ValueOfPort=a[0].sportSeqNr=a[0].seqAckNr=a[0].seq+1# Generating the IP layer:ip=IP(src="192.168.1.1", dst="192.168.1.2")# Generating TCP layer:TCP_SYNACK=TCP(sport=80, dport=ValueOfPort, flags="SA", seq=SeqNr, ack=AckNr, options=[('MSS', 1460)])#send SYNACK to remote host AND receive ACK.ANSWER=sr1(ip/TCP_SYNACK)# Capture next TCP packets with dport 80. (contains http GET request)GEThttp = sniff(filter="tcp and port 80",count=1,prn=lambda x:x.sprintf("{IP:%IP.src%: %TCP.dport%}"))AckNr=AckNr+len(GEThttp[0].load)SeqNr=a[0].seq+1# Print the GET request# (Sanity check: size of data should be greater than 1.)if len(GEThttp[0].load)>1: print GEThttp[0].load# Generate custom http file content.html1="HTTP/1.1 200 OK\x0d\x0aDate: Wed, 29 Sep 2010 20:19:05 GMT\x0d\x0aServer: Testserver\x0d\x0aConnection: Keep-Alive\x0d\x0aContent-Type: text/html; charset=UTF-8\x0d\x0aContent-Length: 291\x0d\x0a\x0d\x0a<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\"><html><head><title>Testserver</title></head><body bgcolor=\"black\" text=\"white\" link=\"blue\" vlink=\"purple\" alink=\"red\"><p><font face=\"Courier\" color=\"blue\">-Welcome to test server-------------------------------</font></p></body></html>"# Generate TCP datadata1=TCP(sport=80, dport=ValueOfPort, flags="PA", seq=SeqNr, ack=AckNr, options=[('MSS', 1460)])# Construct whole network packet, send it and fetch the returning ack.ackdata1=sr1(ip/data1/html1)# Store new sequence number.SeqNr=ackdata1.ack# Generate RST-ACK packetBye=TCP(sport=80, dport=ValueOfPort, flags="FA", seq=SeqNr, ack=AckNr, options=[('MSS', 1460)])send(ip/Bye)# The End
這個伺服器只需要使用wget或者curl就可以實現驗證了。 4. 參考資料
http://www.secdev.org/projects/scapy/doc/usage.html#starting-scapy
https://akaljed.wordpress.com/2010/12/12/scapy-as-webserver/
http://lost-and-found-narihiro.blogspot.com/2012/12/scapy-simple-web-server-with-scapy.html