2020-08-11 記錄
參考《python黑帽子 黑客滲透測試與編程之道》,內容大多都是書里的,懺愧,本人戰五渣。主要就記錄自己怎么學的吧。。。
一般黑客發起攻擊前都得踩點,了解下有哪些主機開着,它們身上運行着哪些程序,它們都在傳輸着些什么信息等等。
有一種踩點,就是嗅探,目的是為了解析目標主機傳輸流量的內容,這種屬於被動攻擊。說到嗅探,就想到了wireshark,實驗課也是用這個來學習協議的呢!雖然我學得不咋樣。
在一個局域網里,對於我的主機來講(我的主機不是路由節點),我和其它人連着同一個WiFi,就是在同一個WLAN下,就是一個沖突域(對於以太網和無線局域網),一般別人的數據我的主機也是可以接收到的,不過網卡就是個硬件的"過濾器",它會識別MAC地址過濾掉和自己無關的信息。不過我們還是可以選擇來接受這些無關的信息,只要將網卡設置為"混雜模式"就可以了(一般情況下,主機都是非混雜模式的)。開啟混雜模式,在Linux下需要root權限,Windows下需要管理員權限。
目前WLAN下連接的設備有3台(以IP地址表示):
- 192.168.0.101 -------------本機 SC-202002071824
- 192.168.0.102 TL-WR886N
- 192.168.0.100 vivo-Y67
直接ping設備vivo-Y67,可以ping通:
1 正在 Ping 192.168.0.100 具有 32 字節的數據: 2 來自 192.168.0.100 的回復: 字節=32 時間=4ms TTL=64 3 來自 192.168.0.100 的回復: 字節=32 時間=323ms TTL=64 4 來自 192.168.0.100 的回復: 字節=32 時間=328ms TTL=64 5 來自 192.168.0.100 的回復: 字節=32 時間=647ms TTL=64
可以ping通,說明目標主機是存活的;不過一般為了安全,主機會禁止ping功能,如果我們去ping這種主機,一般返回的是目標端口不可達的信息,這也說明了目標主機是存活的。
一般網絡主機掃描的話用UDP協議比較好,它是面向非連接的,相比於TCP,它的消耗要小。
開胃菜-先試試能不能接收到數據包
測試環境:Windows10,python3.7
Linux下開啟混雜模式的命令:
1 # ifconfig eth1 promisc #設置混雜模式 2 # ifconfig eth1 -promisc #取消混雜模式
promisc adj.混雜的
Windows下開啟混雜模式:
使用套接字輸入/輸出控制(IOCTL)來設置標志,向網卡驅動發送IOCTL信號來啟用混雜模式。
代碼清單1
1 # -*- coding=utf-8 -*- 2 import socket 3 import os 4 5 host = '192.168.0.101' 6 7 # 判斷是否處於windows下 8 if os.name == 'nt': 9 socket_protocol = socket.IPPROTO_IP 10 else: 11 socket_protocol = socket.IPPROTO_ICMP 12 13 # 創建套接字 14 sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) 15 16 sniffer.bind((host, 0)) 17 18 # 設置捕獲的數據包中包含IP頭 19 sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 20 21 # Windows下開啟混雜模式 22 if os.name == 'nt': 23 sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 24 25 print(sniffer.recvfrom(65565)) 26 27 # Windows下關閉混雜模式 28 if os.name == 'nt': 29 sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
8-11行:為啥Windows下要用socket.IPPROTO_IP參數,Linux下用socket.IPPROTO_ICMP參數呢?
Windows和Linux下的套接字是有區別的,Windows允許我們嗅探所有協議的所有數據包,而Linux只能嗅探到ICMP數據。
14行:socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
直接看下面的參數解釋:(copy別人博客的)
參數一:地址簇
socket.AF_INET IPv4(默認)
socket.AF_INET6 IPv6socket.AF_UNIX 只能夠用於單一的Unix系統進程間通信
參數二:類型
socket.SOCK_STREAM 流式socket , for TCP (默認)
socket.SOCK_DGRAM 數據報式socket , for UDPsocket.SOCK_RAW 原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。
socket.SOCK_RDM 是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RAM用來提供對原始協議的低級訪問,在需要執行某些特殊操作時使用,如發送ICMP報文SOCK_RDM通常僅限於高級用戶或管理員運行的程序使用。socket.SOCK_SEQPACKET 可靠的連續數據包服務
參數三:協議
0 (默認)與特定的地址家族相關的協議,如果是 0 ,則系統就會根據地址格式和套接類別,自動選擇一個合適的協議
IPPROTO_ICMP = 1
IPPROTO_IP = 0
第2個參數選擇socket.SOCK_RAW就是要監聽各種協議的數據包的。。。
16行:sniffer.bind((host, 0));將套接字綁定在一個地址上,這個地址是IP+端口構成的一個元組。可是端口為啥要綁定到0上呢?
綁定到0,就是告訴系統自己選一個合適的端口給套接字。
平常我們寫服務端程序時,總是要綁定到確定的IP地址和端口上,為啥?服務器的地址就是得周知的,客戶端才知道向誰請求。那么客戶端程序怎么沒有寫bind語句呢?呵呵,不寫,都是讓系統自動分配一個合適的IP地址和端口號的,我們省略了這一步。
19行: sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
這是自己來設置套接字的選項,設置在捕獲的數據包中包含IP頭部。
23行: sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
很明顯,看英文意思就是開啟接受所有數據的開關。這里打開網卡的混雜模式。
接下來運行看看能不能收到什么東西。
(b'E\x00\x00\xcaG\x13\x00\x00\x02\x11\xbf\xe8\xc0\xa8\x00\x85\xef\xff\xff\xfa9U\x07l\x00\xb6^\xffM-SEARCH * HTTP/1.1\r\nMX: 1\r\nST: upnp:rootdevice\r\nMAN: "ssdp:discover"\r\nUser-Agent: UPnP/1.0 IQIYIDLNA/iqiyidlna/NewDLNA/1.0\r\nConnection: close\r\nHost: 239.255.255.250:1900\r\n\r\n', ('192.168.0.133', 0))
抓到了192.168.0.133這台主機的數據包(我的是192.168.0.101),不過我看不懂這是啥,好像是啥協議。。。反正現在可以接收到數據包了,這只是個測試。
對原始數據進行探索
學過計算機網絡的都知道,數據從應用層-->表示層-->會話層-->傳輸層-->網絡層-->數據鏈路層-->物理層下去,一層層地將數據封裝,到達目標主機后按相反的順序再拆封,往上傳。(現實中不一定都要經過7層,那個只是標准化模型)。數據的封裝是按照協議來的,上面抓的那個數據包的內容也看不出啥有用的內容。我們需要對數據包進行解碼才行,就像主機各個層的協議會對數據解碼一樣。
現在我們只解碼數據包里的IP頭數據。先看看IP協議包的構成:
圖1.IPV4協議的內容
解碼是啥,就是我知道這個數據包的0-3字節表示版本號,4-7字節表示頭長度,那么我就每次讀取0-3字節時,用一個unsigned char數組變量來接受0-3字節的數據,這個變量表示的就是版本號,以此類推。
看看IP頭在C語言中如何表示:
1 struct ip{ 2 u_char ip_hl:4; // 頭長度 1Bytes :4是比特位標志,說明字段按比特位計算,長度是4比特。就是1個字節里只用到4比特。 3 u_char ip_v:4; // 版本號 1Bytes 4 u_char ip_tos; // 服務類型 1Byte 5 u_short ip_len; // IP數據包總長度 2Bytes 6 u_short ip_id; // 標記 2Bytes 7 u_short ip_off; // 片偏移 2Bytes 8 u_char ip_ttl; // 生存時間 1Byte 9 u_char ip_p; // 協議類型 1Byte 10 u_short ip_sum; // 頭部校驗 2Byte 11 u_long ip_src; // 源IP地址 32bit系統下u_long是4Bytes,64bit系統下u_long是8Bytes 12 u_long ip_dst; // 目的IP地址 32bit系統下是u_long4Bytes,64bit系統下u_long是8Bytes
13 };
對照着協議標准里的內容和真正實現的代碼,發現不是所有字節都有用到的。
以C語言的結構體為參照,然后使用python的ctypes模塊來創建類似於C的結構體,直接上代碼:
代碼清單2:
1 # -*- coding=utf-8 -*- 2 import socket 3 import os 4 import struct 5 from ctypes import * 6 7 # 監聽的主機 8 host = '192.168.0.101' 9 10 11 class IP(Structure): 12 _fields_ = [ 13 ('ihl', c_ubyte, 4), 14 ('version', c_ubyte, 4), 15 ('tos', c_ubyte), 16 ('len', c_ushort), 17 ('id', c_ushort), 18 ('offset', c_ushort), 19 ('ttl', c_ubyte), 20 ('protocol_num', c_ubyte), 21 ('sum', c_ushort), 22 ('src', c_ulong), 23 ('dst', c_ulong) 24 ] 25 26 def __new__(self, socket_buffer=None): 27 return self.from_buffer_copy(socket_buffer) 28 29 def __init__(self, socket_buffer=None): 30 self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"} 31 self.src_address = socket.inet_ntoa(struct.pack("<L", self.src)) 32 self.dst_address = socket.inet_ntoa(struct.pack("<L", self.dst)) 33 34 try: 35 self.protocol = self.protocol_map[self.protocol_num] 36 except: 37 self.protocol = str(self.protocol_num) 38 39 40 if os.name == 'nt': 41 socket_protocol = socket.IPPROTO_IP 42 else: 43 socket_protocol = socket.IPPROTO_ICMP 44 45 sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) 46 47 sniffer.bind((host, 0)) 48 sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 49 50 # 打開混雜端口 51 if os.name == 'nt': 52 sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 53 54 # 循環偵聽 55 try: 56 while True: 57 # 讀取數據包 58 raw_buffer = sniffer.recvfrom(65565)[0] 59 # 將緩沖區的前20字節按IP頭進行解析 60 ip_header = IP(raw_buffer[0:20]) 61 # 輸出雙方IP地址 62 print("Protocol:{0} {1} -> {2}".format(ip_header.protocol, ip_header.src_address, ip_header.dst_address)) 63 # 處理CTRL-C 64 except KeyboardInterrupt: 65 # 關閉混雜模式 66 if os.name == 'nt': 67 sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
IP類繼承自Structure類,這個類就是用來構造類似C語言的結構體的。
使用ctypes模塊就可以調用C語言的函數,變量的類型定義等(short, 指針,結構體之類的)。
IP類中出現了__new__和__init__函數,16行:IP(raw_buffer[0:20]),當我們要創建一個類的實例時,會先調用這個類的__new__()函數來生成一個實例,然后通過這個實例調用__init__()方法來初始化該實例的變量和參數。(想到C++里先new一個對象,然后再調用構造函數了。。。)
流程:
1.判斷當前操作系統,來確定要使用的協議類型socket_protocol;
2.創建可以接受原始數據流的套接字;
3.綁定套接字到"192.168.0.101"上,端口由系統分配;
4.設置套接字選項,接受數據中的IP頭部;
5.打開網卡的混雜端口;
6.開始偵聽,將接受到的數據作為參數傳給IP類,由IP類解析數據包,返回IP實例,這個實例里有解碼后的信息;
7.打印IP頭部信息;
8.如果遇到中斷信號CTRL-C,關閉網卡的混雜模式。
IP類中的_fields_是一個列表,列表里的元素是元組。這就是IP類的內置變量了(以單下划線開頭,單下划線結尾),IP類的每個函數都可以使用它。
傳進IP類的數據流的前20個字節會按在__new__()執行時填充到__fields__結構當中,相當於解碼了。。。然后__init__()方法會把這些結構進一步轉為更具可讀性的數據。
圖2.數據類型對照
畢竟是要和C語言的數據類型對照的,ctypes充當着一個映射的作用。
('ihl', c_ubyte, 4),ihl就是變量名,c_ubyte是變量的類型,對應C語言的unsigned char,python的int/long;4是比特位標志。
u_char ip_hl:4;這個是C語言的格式。
不太懂ctypes到底如何映射這些數據類型,用就完了。。。
30行:self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"}
protocol_map變量是一個字典,里面是數字和對應協議類型的對照關系。這個在RFC791(好像是這個)有提到,哪個數字代表哪種協議(自己找吧)。
35行:self.protocol = self.protocol_map[self.protocol_num]
這個就是根據數據包里的協議號來判斷是哪種協議。
31行:self.src_address = socket.inet_ntoa(struct.pack("<L", self.src))
struct.pack將c_long類型的src(源地址)轉為小端的long類型數據,返回源地址的bytes格式。(<號的意思看下表)
socket.inet_ntoa()函數則把網絡地址,即src,轉為以"."分割的字符串,其實就是IP地址的點分十進制形式啦。
好玩的來了,運行看看:
1 Protocol:ICMP 192.168.0.101 -> 192.168.0.102 2 Protocol:ICMP 192.168.0.102 -> 192.168.0.101 3 Protocol:UDP 192.168.0.101 -> 230.0.0.1 4 Protocol:UDP 192.168.0.101 -> 230.0.0.1 5 Protocol:TCP 13.107.42.12 -> 192.168.0.101 6 Protocol:TCP 118.212.233.223 -> 192.168.0.101 7 Protocol:TCP 118.212.233.223 -> 192.168.0.101
就截取了一部分,看到的是傳輸層和網絡層的協議類型,看不出來應用層是DNS還是HTTP,FTP啥的。Linux下的沒有試,按書上的話只能看到ICMP協議的數據包。
不知道怎么分析數據包,這我也看不出啥。。。
接着繼續解碼ICMP
代碼清單3
1 # -*- coding=utf-8 -*- 2 import socket 3 import os 4 import struct 5 from ctypes import * 6 7 # 監聽的主機 8 host = '192.168.0.101' 9 10 11 class IP(Structure): 12 _fields_ = [ 13 ('ihl', c_ubyte, 4), 14 ('version', c_ubyte, 4), 15 ('tos', c_ubyte), 16 ('len', c_ushort), 17 ('id', c_ushort), 18 ('offset', c_ushort), 19 ('ttl', c_ubyte), 20 ('protocol_num', c_ubyte), 21 ('sum', c_ushort), 22 ('src', c_ulong), 23 ('dst', c_ulong) 24 ] 25 26 def __new__(self, socket_buffer=None): 27 return self.from_buffer_copy(socket_buffer) 28 29 def __init__(self, socket_buffer=None): 30 self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"} 31 self.src_address = socket.inet_ntoa(struct.pack("<L", self.src)) 32 self.dst_address = socket.inet_ntoa(struct.pack("<L", self.dst)) 33 34 try: 35 self.protocol = self.protocol_map[self.protocol_num] 36 except: 37 self.protocol = str(self.protocol_num) 38 39 40 class ICMP(Structure): 41 _fields_ = [ 42 ('type', c_ubyte), 43 ('code', c_ubyte), 44 ('checksum', c_ushort), 45 ('unused', c_ushort), 46 ('next_hop_mtu', c_ushort) 47 ] 48 49 def __new__(self, socket_buffer): 50 return self.from_buffer_copy(socket_buffer) 51 52 def __init__(self, socket_buffer): 53 pass 54 55 56 if os.name == 'nt': 57 socket_protocol = socket.IPPROTO_IP 58 else: 59 socket_protocol = socket.IPPROTO_ICMP 60 61 sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) 62 63 sniffer.bind((host, 0)) 64 sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 65 66 # 打開混雜端口 67 if os.name == 'nt': 68 sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 69 70 # 循環偵聽 71 try: 72 while True: 73 # 讀取數據包 74 raw_buffer = sniffer.recvfrom(65565)[0] 75 # 將緩沖區的前20字節按IP頭進行解析 76 ip_header = IP(raw_buffer[0:20]) 77 # 輸出雙方IP地址 78 if ip_header.protocol == 'ICMP': 79 # 計算ICMP包的起始位置 80 offset = ip_header.ihl * 4 81 buf = raw_buffer[offset:offset + sizeof(ICMP)] 82 83 # 解析ICMP數據 84 icmp_header = ICMP(buf) 85 86 print("ICMP:{0}->{1}".format(ip_header.src_address, ip_header.dst_address)) 87 print("type:{0} code:{1}".format(icmp_header.type, icmp_header.code)) 88 89 # 處理CTRL-C 90 except KeyboardInterrupt: 91 # 關閉混雜模式 92 if os.name == 'nt': 93 sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
相比於代碼清單2,多了一個ICMP類,用來解析ICMP的數據。
IP和ICMP協議都是網絡層的協議,ICMP屬於IP的一部分。看圖。
要對ICMP協議部分的數據進行解碼,就得知道ICMP部分的數據從哪開始,到哪結束。
從哪開始呢?
ICMP報文位於IP數據包的數據部分,所以得知道IP數據報的首部長度。在IP頭部中,有個頭部長度,C語言表示為u_char ip_hl:4;它表示IP首部中占32BIT的數目,如果有選項的話,也會包含選項的長度。
那么只要ip_hl * 4(4是4Bytes=32比特)就可以知道IP首部的長度了,也就是ICMP報文的起始位置了。
到哪結束呢?
類型1字節,代碼1字節,校驗和2字節,首部其它部分(取決於ICMP的類型)4字節。所以ICMP頭部為8字節。
參考代碼41-47行。
代碼81行:buf = raw_buffer[offset:offset + sizeof(ICMP)]
取得ICMP報文頭部的數據,sizeof(ICMP)其實就是_fields_結構的大小(理解成結構體就好了)。
然后為了簡潔點,只打印ICMP相關的數據,測試結果:
ICMP:192.168.0.101->192.168.0.102 type:8 code:0 ICMP:192.168.0.102->192.168.0.101 type:0 code:0 ICMP:192.168.0.101->192.168.0.102 type:8 code:0 ICMP:192.168.0.102->192.168.0.101 type:0 code:0 ICMP:192.168.0.101->192.168.0.102 type:8 code:0 ICMP:192.168.0.102->192.168.0.101 type:0 code:0 ICMP:192.168.0.101->192.168.0.102 type:8 code:0 ICMP:192.168.0.102->192.168.0.101 type:0 code:0
從百科copy來的表:
TYPE | CODE | Description | Query | Error |
---|---|---|---|---|
0 | 0 | Echo Reply——回顯應答(Ping應答) | x | |
3 | 0 | Network Unreachable——網絡不可達 | x | |
3 | 1 | Host Unreachable——主機不可達 | x | |
3 | 2 | Protocol Unreachable——協議不可達 | x | |
3 | 3 | Port Unreachable——端口不可達 | x | |
3 | 4 | Fragmentation needed but no frag. bit set——需要進行分片但設置不分片比特 | x | |
3 | 5 | Source routing failed——源站選路失敗 | x | |
3 | 6 | Destination network unknown——目的網絡未知 | x | |
3 | 7 | Destination host unknown——目的主機未知 | x | |
3 | 8 | Source host isolated (obsolete)——源主機被隔離(作廢不用) | x | |
3 | 9 | Destination network administratively prohibited——目的網絡被強制禁止 | x | |
3 | 10 | Destination host administratively prohibited——目的主機被強制禁止 | x | |
3 | 11 | Network unreachable for TOS——由於服務類型TOS,網絡不可達 | x | |
3 | 12 | Host unreachable for TOS——由於服務類型TOS,主機不可達 | x | |
3 | 13 | Communication administratively prohibited by filtering——由於過濾,通信被強制禁止 | x | |
3 | 14 | Host precedence violation——主機越權 | x | |
3 | 15 | Precedence cutoff in effect——優先中止生效 | x | |
4 | 0 | Source quench——源端被關閉(基本流控制) | ||
5 | 0 | Redirect for network——對網絡重定向 | ||
5 | 1 | Redirect for host——對主機重定向 | ||
5 | 2 | Redirect for TOS and network——對服務類型和網絡重定向 | ||
5 | 3 | Redirect for TOS and host——對服務類型和主機重定向 | ||
8 | 0 | Echo request——回顯請求(Ping請求) | x | |
9 | 0 | Router advertisement——路由器通告 | ||
10 | 0 | Route solicitation——路由器請求 | ||
11 | 0 | TTL equals 0 during transit——傳輸期間生存時間為0 | x | |
11 | 1 | TTL equals 0 during reassembly——在數據報組裝期間生存時間為0 | x | |
12 | 0 | IP header bad (catchall error)——壞的IP首部(包括各種差錯) | x | |
12 | 1 | Required options missing——缺少必需的選項 | x | |
13 | 0 | Timestamp request (obsolete)——時間戳請求(作廢不用) | x | |
14 | Timestamp reply (obsolete)——時間戳應答(作廢不用) | x | ||
15 | 0 | Information request (obsolete)——信息請求(作廢不用) | x | |
16 | 0 | Information reply (obsolete)——信息應答(作廢不用) | x | |
17 | 0 | Address mask request——地址掩碼請求 | x | |
18 | 0 | Address mask reply——地址掩碼應答 |
看code和type字段就知道這個數據包在干嘛了。
掃描網段啦
之前都只是在抓包,得要自己手動去ping別人,現在要一次性掃描多個主機了。
書上是只打印端口不可達的情況,不過我這邊的其它主機都可以ping通呢!所以,我打算把所有類型的ICMP消息都打印下,試試吧。
代碼清單4:
呵呵,不知道是不是我寫錯了,按書上那個發包函數,都收不到ICMP消息呢!網上找了一大堆都是python2寫的,還都可以用,真的是我這的問題嗎,也沒什么太大差別吧?
1 # -*- coding=utf-8 -*- 2 import socket 3 import os 4 import struct 5 from ctypes import * 6 import threading 7 import time 8 from netaddr import IPNetwork, IPAddress 9 10 # 監聽的主機 11 host = '192.168.0.101' 12 13 # 要掃描的目標子網 14 subnet = '192.168.0.0/24' 15 16 # 待會要發送的消息 17 my_message = b"WALL" 18 19 20 class IP(Structure): 21 _fields_ = [ 22 ('ihl', c_ubyte, 4), 23 ('version', c_ubyte, 4), 24 ('tos', c_ubyte), 25 ('len', c_ushort), 26 ('id', c_ushort), 27 ('offset', c_ushort), 28 ('ttl', c_ubyte), 29 ('protocol_num', c_ubyte), 30 ('sum', c_ushort), 31 ('src', c_ulong), 32 ('dst', c_ulong) 33 ] 34 35 def __new__(self, socket_buffer=None): 36 return self.from_buffer_copy(socket_buffer) 37 38 def __init__(self, socket_buffer=None): 39 self.protocol_map = {1: "ICMP", 2: "IGMP", 6: "TCP", 17: "UDP"} 40 self.src_address = socket.inet_ntoa(struct.pack("<L", self.src)) 41 self.dst_address = socket.inet_ntoa(struct.pack("<L", self.dst)) 42 43 try: 44 self.protocol = self.protocol_map[self.protocol_num] 45 except: 46 self.protocol = str(self.protocol_num) 47 48 49 class ICMP(Structure): 50 _fields_ = [ 51 ('type', c_ubyte), 52 ('code', c_ubyte), 53 ('checksum', c_ushort), 54 ('unused', c_ushort), 55 ('next_hop_mtu', c_ushort) 56 ] 57 58 def __new__(self, socket_buffer): 59 return self.from_buffer_copy(socket_buffer) 60 61 def __init__(self, socket_buffer): 62 pass 63 64 65 #批量發送UDP數據包 66 def udp_sender(subnet, my_message): 67 time.sleep(2) 68 sender = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 69 70 for ip in IPNetwork(subnet): 71 try: 72 print("#Scan {0}".format(ip)) 73 sender.sendto(my_message, ("%s" % ip,0)) 74 except: 75 pass 76 77 78 if os.name == 'nt': 79 socket_protocol = socket.IPPROTO_IP 80 else: 81 socket_protocol = socket.IPPROTO_ICMP 82 83 sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) 84 85 sniffer.bind((host, 0)) 86 sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 87 88 # 打開混雜端口 89 if os.name == 'nt': 90 sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) 91 92 # 啟動發送數據包的線程 93 t = threading.Thread(target=udp_sender, args=(subnet, my_message)) 94 t.start() 95 96 # 循環偵聽 97 try: 98 while True: 99 # 讀取數據包 100 raw_buffer, address = sniffer.recvfrom(65565) 101 # 將緩沖區的前20字節按IP頭進行解析 102 ip_header = IP(raw_buffer[0:20]) 103 if ip_header.protocol == 'ICMP': 104 print('#From {0}:{1}'.format(address[0], address[1])) 105 # 計算ICMP包的起始位置 106 offset = ip_header.ihl * 4 107 buf = raw_buffer[offset:offset + sizeof(ICMP)] 108 109 # 解析ICMP數據 110 icmp_header = ICMP(buf) 111 112 print("ICMP:{0}->{1}".format(ip_header.src_address, ip_header.dst_address)) 113 print("type:{0} code:{1}".format(icmp_header.type, icmp_header.code)) 114 115 # 判斷發送來的數據包的源地址位於我們掃描的子網內 116 if icmp_header.type == 3 and icmp_header.code == 3: 117 if IPAddress(ip_header.src_address) in IPNetwork(subnet): 118 # 確認ICMP中包含我們發的自定義消息 119 if raw_buffer[len(raw_buffer) - len(my_message):] == my_message: 120 print('Host up:{0}'.format(ip_header.src_address)) 121 # 處理CTRL-C 122 except KeyboardInterrupt: 123 # 關閉混雜模式 124 if os.name == 'nt': 125 sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
這個子網掃描的沒成功,ICMP數據包就是沒成功發送給目標主機過,type:3,code:1主機不可達,呵呵。
折騰了半天,這個部分沒弄好。。。
留了一個坑。