網路流量嗅探
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=644 來自 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主機不可達,呵呵。
折騰了半天,這個部分沒弄好。。。
留了一個坑。