1. 程式人生 > 實用技巧 >網路流量嗅探

網路流量嗅探

2020-08-11  記錄

參考《python黑帽子 黑客滲透測試與程式設計之道》,內容大多都是書裡的,懺愧,本人戰五渣。主要就記錄自己怎麼學的吧。。。


  一般黑客發起攻擊前都得踩點,瞭解下有哪些主機開著,它們身上執行著哪些程式,它們都在傳輸著些什麼資訊等等。

  有一種踩點,就是嗅探,目的是為了解析目標主機傳輸流量的內容,這種屬於被動攻擊。說到嗅探,就想到了wireshark,實驗課也是用這個來學習協議的呢!雖然我學得不咋樣。

  在一個局域網裡,對於我的主機來講(我的主機不是路由節點),我和其它人連著同一個WiFi,就是在同一個WLAN下,就是一個衝突域(對於乙太網和無線區域網),一般別人的資料我的主機也是可以接收到的,不過網絡卡就是個硬體的"過濾器",它會識別MAC地址過濾掉和自己無關的資訊。不過我們還是可以選擇來接受這些無關的資訊,只要將網絡卡設定為"混雜模式"就可以了(一般情況下,主機都是非混雜模式的)。開啟混雜模式,在Linux下需要root許可權,Windows下需要管理員許可權。

  目前WLAN下連線的裝置有3臺(以IP地址表示):

  1.   192.168.0.101  -------------本機 SC-202002071824
  2. 192.168.0.102 TL-WR886N
  3.   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 IPv6

  socket.AF_UNIX 只能夠用於單一的Unix系統程序間通訊

引數二:型別

  socket.SOCK_STREAM  流式socket , for TCP (預設)
  socket.SOCK_DGRAM   資料報式socket , for UDP

  socket.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來的表:

TYPECODEDescriptionQueryError
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主機不可達,呵呵。

折騰了半天,這個部分沒弄好。。。

留了一個坑。