這兩天在做一個實驗需要自己構造IP首部,遇到諸多問題,搞了一天終於搞定。
關於socket的介紹網上一大堆,我只記錄構造IP頭時我遇到的問題。由於沒玩過socket構造IP首部,網上找了段代碼研究下,無奈代碼跑不動,各種問題,網上搜集資料無果,從基礎學起,加上自己的腦洞總算解決了。
我想自己構造一個自定義IP頭的ICMP回送請求,網上找了段代碼,自己改了改,現在長這個樣子:
1 import socket 2 import struct 3 def checksum(source_string): 4 sum = 0 5 countTo = (len(source_string)/2)*2 6 count = 0 7 while count<countTo: 8 thisVal = ord(source_string[count + 1])*256 + ord(source_string[count]) 9 sum = sum + thisVal 10 sum = sum & 0xffffffff 11 count = count + 2 12 if countTo<len(source_string): 13 sum = sum + ord(source_string[len(source_string) - 1]) 14 sum = sum & 0xffffffff 15 sum = (sum >> 16) + (sum & 0xffff) 16 sum = sum + (sum >> 16) 17 answer = ~sum 18 answer = answer & 0xffff 19 answer = answer >> 8 | (answer << 8 & 0xff00) 20 return answer 21 def ping(ip): 22 s=socket.socket(socket.AF_INET,socket.SOCK_RAW,255) 24 s.setsockopt(0, socket.IP_HDRINCL, 1) 25 # now start constructing the packet 26 27 source_ip = '172.16.12.1' 28 dest_ip = ip 29 30 # ip header fields 31 ihl = 5 32 version = 4 33 tos = 0 34 tot_len = 28 35 id = 0 36 frag_off = 0 37 ttl = 255 38 protocol = 1 39 check = 0 40 saddr =socket.inet_aton ( source_ip ) #Spoof the source ip address if you want to 41 daddr = socket.inet_aton ( dest_ip ) 44 ihl_version = (version << 4) + ihl 45 # the ! in the pack format string means network order 46 ip_header = struct.pack('!BBHHHBBH4s4s', ihl_version, tos, tot_len, id, frag_off, ttl, protocol, check, saddr, daddr) 47 packet = struct.pack( 48 "!BBHHH", 8, 0, 0, 0, 0 49 ) 50 chksum=checksum(packet) 51 packet = struct.pack( 52 "!BBHHH", 8, 0, chksum, 0, 0 53 ) 54 packet=ip_header+packet 55 s.sendto(packet,(ip,1)) 56 print "done" 57 58 if __name__=='__main__': 59 ping('172.31.0.1')
步驟很簡單,就是自己創建個套接字,然后把頭構造好再發送就行了,但是一般的套接字是無法自己更改IP頭的,只能從IP數據報的數據部分開始構造,想要構造IP首部就要用到原始套接字,用原始套接字可以從IP首部開始構造,但是如果用原始套接字需要root權限,開始我在OS X下用IDE,程序總是報錯socket.error: [Errno 1] Operation not permitted,就是因為權限的問題,在終端里sudo運行就沒有權限問題了(Ps:如果想用root權限打開IDE,又不想切換賬戶的話,終端里sudo ./IDE就行了)現在有了權限,開始報別的錯了,提示socket.error: [Errno 22]Invalid argument。
這是創建原始套接字的代碼,第一行第三個值255是IPPROTO_RAW的值,如果要構造IP頭,就要加上第二行代碼設置IP_HDRINCL,第一個值0是IPPROTO_IP的值
s=socket.socket(socket.AF_INET,socket.SOCK_RAW,255)
s.setsockopt(0, socket.IP_HDRINCL, 1)
如果這樣設置在OS X下就會在調用sendto()的位置報Invalid argument錯誤,后來發現問題出在第一行的第三個參數255上,經測試發現
在OS X下,這個參數置成0或255都會報錯
在WINDOWS下 ,這個參數置成0或255都不會報錯
在LINUX下,這個參數置成0會報錯,置成255不會報錯
現在可以構造任意的源IP和目的IP的ICMP回送請求了,IP首部字段的ID,長度,校驗和置成0就可以,內核協議棧會修正。
在linux上抓包發現目的IP為同子網下不存在的主機IP時,是抓不到ICMP包的,這是因為主機先發送ARP包請求目的IP的MAC地址得不到回應,而不能進一步發送ICMP回送請求,也就是說PING命令中提示的Request timeout for icmp_seq是因為ARP請求得不到應答而產生的。