使用 Python 的 Socket 模塊構建一個 UDP 掃描工具

mx64 9年前發布 | 23K 次閱讀 Python Python開發

當涉及到對一些目標網絡的偵察時,出發點無疑是首先發現宿主主機。這個任務還可能包含嗅探和解析網絡中數據包的能力。

幾周前,我曾經談到了如何使用Wireshark來進行數據包嗅探,但如果你沒有wireshark,你如何去監控網絡流量呢?

這一次,Python提供了幾種解決方案,今天我將一步步演示如何建立一個UDP主機發現工具。首先,我們要看我們如何處理原始套接字來編寫一個簡單的嗅探器,它能夠查看和解析網絡數據包。然后,我們將在子網內多線程運行該進程,結果將在我們的掃描儀上。

原始套接字酷之所在是它能夠訪問底層網絡的信息。比如,我們可以用它來檢查IPICMP報頭,這是在OSI模型的第三層(網絡層)。

使用UDP數據報最酷的事情是:當發送信息穿越子網時,不同于TCP,它不太多的開銷(還記得TCP握手吧)。我們需要做的就是等待ICMP回應,對方主機是否可用或關閉(不可訪問)。記住,ICMP協議本質上是一個特殊的控制協議,它指示錯誤報告和控制機器數據傳輸的的行為。

編寫網絡包嗅探器

我們從一個小功能開始:用 Python 的 socket 庫來編寫一個簡單的網絡包嗅探器。

在這個嗅探器中,我們創建一個原始 socket 并將它綁定到一個外部網卡。這個網卡要啟用混淆模式(promiscuous mode),也就是說獲經過這個網卡的所有數據包都會被捕獲,包括那些目標地址不是它的數據包。

使用 Windows 時要注意一點:我們需要發送一個 IOCTL 包才能將網卡設置為混淆模式。另外,雖然 linux 需要使用 ICMP,Windows 卻可以以一種獨立于協議的方式來嗅探收到的數據包。

import socket
import os

 host to listen

HOST = '192.168.1.114'

def sniffing(host, win, socket_prot):     while 1:         sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_prot)         sniffer.bind((host, 0))

        # include the IP headers in the captured packets         sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

        if win == 1:             sniffer.ioctl(socket.SIO_RCVALL, socket_RCVALL_ON)

        # read in a single packet         print sniffer.recvfrom(65565)

def main(host):     if os.name == 'nt':         sniffing(host, 1, socket.IPPROTO_IP)     else:         sniffing(host, 0, socket.IPPROTO_ICMP)

if name == 'main':     main(HOST)</pre>

在終端中運行如下命令進行測試:

$ sudo python sniffer.py

在另一個終端中 ping 或 traceroute 某些地址,如 www.google.com 會得到如下結果:

$ sudo python raw_socket.py
('E\x00\x00T\xb3\xec\x00\x005\x01\xe4\x13J}\xe1\x11\xc0\xa8\x01r\x00\x00v\xdfx\xa2\x00\x01sr\x98T\x00\x00\x00\x008\xe3\r\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567', ('74.125.225.17', 0))
('E\x00\x00T\xb4\x1b\x00\x005\x01\xe3\xe4J}\xe1\x11\xc0\xa8\x01r\x00\x00~\xd7x\xa2\x00\x02tr\x98T\x00\x00\x00\x00/\xea\r\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567', ('74.125.225.17', 0))

很明顯需要對這些頭信息進行解碼。

IP ICMP層解碼

IP 頭

典型的 IP 頭有如下結構,每個字段都對應一個變量 (這個頭最初是用 C 編寫的):

使用 Python 的 Socket 模塊構建一個 UDP 掃描工具

ICMP頭

同樣,ICMP 由于內容的不同其消息類型也不同,但每個消息都包括三個一致的元素:type,code (告知接收主機這個 ICMP 消息的解碼類型)和 checksum。

使用 Python 的 Socket 模塊構建一個 UDP 掃描工具

對于我們的掃描器,如果得到的 type 和 code 值是3,這意味著 Destination Unreachable(目標不可達)和 Port Unreachable (端口不可達) ICMP 消息錯誤

為描述 ICMP 消息頭,我們用 python 的 ctypes 庫來創建一個類

import ctypes

class ICMP(ctypes.Structure):     fields = [     ('type',        ctypes.c_ubyte),     ('code',        ctypes.c_ubyte),     ('checksum',    ctypes.c_ushort),     ('unused',      ctypes.c_ushort),     ('next_hop_mtu',ctypes.c_ushort)     ]

    def new(self, socket_buffer):         return self.from_buffer_copy(socket_buffer)

    def init(self, socket_buffer):         pass</pre>

編寫消息頭解碼器

現在可以著手編寫 IP/ICMP 消息頭解碼器了。下面的腳本創建了一個 sniffer socket(正如前面做的那樣),然后在一個循環中持續讀取數據包并進行解碼。

注意代碼中將 IP 頭的前20個字節讀取到了緩存,然后再打印消息頭的變量。ICMP 頭數據如下:

import socket
import os
import struct
import ctypes
from ICMPHeader import ICMP

 host to listen on

HOST = '192.168.1.114'

def main():     socket_protocol = socket.IPPROTO_ICMP     sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)     sniffer.bind(( HOST, 0 ))     sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

    while 1:         raw_buffer = sniffer.recvfrom(65565)[0]         ip_header = raw_buffer[0:20]         iph = struct.unpack('!BBHHHBBH4s4s' , ip_header)

        # Create our IP structure         version_ihl = iph[0]         version = version_ihl >> 4         ihl = version_ihl & 0xF         iph_length = ihl * 4         ttl = iph[5]         protocol = iph[6]         s_addr = socket.inet_ntoa(iph[8]);         d_addr = socket.inet_ntoa(iph[9]);

        print 'IP -> Version:' + str(version) + ', Header Length:' + str(ihl) + \         ', TTL:' + str(ttl) + ', Protocol:' + str(protocol) + ', Source:'\          + str(s_addr) + ', Destination:' + str(d_addr)

        # Create our ICMP structure         buf = raw_buffer[iph_length:iph_length + ctypes.sizeof(ICMP)]         icmp_header = ICMP(buf)

        print "ICMP -> Type:%d, Code:%d" %(icmp_header.type, icmp_header.code) + '\n'

if name == 'main':     main()</pre>

測試解碼器

在一個終端中運行該腳本,然后在另一個終端運行一個 ping 命令會得到如下結果(注意 ICMP type 值為 0):

$ ping www.google.com
PING www.google.com (74.125.226.16) 56(84) bytes of data.
64 bytes from lga15s42-in-f16.1e100.net (74.125.226.16): icmp_seq=1 ttl=56 time=15.7 ms
64 bytes from lga15s42-in-f16.1e100.net (74.125.226.16): icmp_seq=2 ttl=56 time=15.0 ms(...)
$ sudo python ip_header_decode.py
IP -> Version:4, Header Length:5, TTL:56, Protocol:1, Source:74.125.226.16, Destination:192.168.1.114
ICMP -> Type:0, Code:0
IP -> Version:4, Header Length:5, TTL:56, Protocol:1, Source:74.125.226.16, Destination:192.168.1.114
ICMP -> Type:0, Code:0(...)

另外,如果我們運行 traceroute:

$ traceroute www.google.com
traceroute to www.google.com (74.125.226.50), 30 hops max, 60 byte packets
 1  * * *
 2  * * *
 3  67.59.255.137 (67.59.255.137)  17.183 ms 67.59.255.129 (67.59.255.129)  70.563 ms 67.59.255.137 (67.59.255.137)  21.480 ms
 4  451be075.cst.lightpath.net (65.19.99.117)  14.639 ms rtr102.wan.hcvlny.cv.net (65.19.99.205)  24.086 ms 451be075.cst.lightpath.net (65.19.107.117)  24.025 ms
 5  64.15.3.246 (64.15.3.246)  24.005 ms 64.15.0.218 (64.15.0.218)  23.961 ms 451be0c2.cst.lightpath.net (65.19.120.194)  23.935 ms
 6  72.14.215.203 (72.14.215.203)  23.872 ms  46.943 ms *
 7  216.239.50.141 (216.239.50.141)  48.906 ms  46.138 ms  46.122 ms
 8  209.85.245.179 (209.85.245.179)  46.108 ms  46.095 ms  46.074 ms
 9  lga15s43-in-f18.1e100.net (74.125.226.50)  45.997 ms  19.507 ms  16.607 ms

會得到這種輸出 (注意 ICMP 的響應類型):

sudo python ip_header_decode.py
IP -> Version:4, Header Length:5, TTL:252, Protocol:1, Source:65.19.99.117, Destination:192.168.1.114
ICMP -> Type:11, Code:0(...)IP -> Version:4, Header Length:5, TTL:250, Protocol:1, Source:72.14.215.203, Destination:192.168.1.114
ICMP -> Type:11, Code:0
IP -> Version:4, Header Length:5, TTL:56, Protocol:1, Source:74.125.226.50, Destination:192.168.1.114
ICMP -> Type:3, Code:3
IP -> Version:4, Header Length:5, TTL:249, Protocol:1, Source:216.239.50.141, Destination:192.168.1.114
ICMP -> Type:11, Code:0(...)IP -> Version:4, Header Length:5, TTL:56, Protocol:1, Source:74.125.226.50, Destination:192.168.1.114
ICMP -> Type:3, Code:3

編寫掃描器

安裝 netaddr

在編寫完整的掃描器前首先要安裝 netaddr,它是一個用于表示和處理網絡地址的 python 庫。

Netaddr 提供了操作 IPv4,IPv6 和子網 Mac 等地址的能力。它非常有用,因為我們會用到子網掩碼,如192.168.1.0/24

$ sudo pip install netaddr

我們可以使用如下的代碼段來測試這個庫 (成功會打印“OK”):

import netaddr

ip = '192.168.1.114' if ip in netaddr.IPNetwork('192.168.1.0/24'):     print('OK!')</pre>

深入掃描器

我們會將上面所提到的組織在一起來完成我們的掃描器,然后添加一個循環來向目標子網內的所有地址發送 UDP 數據報。

import threading
import time
import socket
import os
import struct
from netaddr import IPNetwork, IPAddress
from ICMPHeader import ICMP
import ctypes

 host to listen on

HOST = '192.168.1.114'

 subnet to target (iterates through all IP address in this subnet)

SUBNET = '192.168.1.0/24'

 string signature

MESSAGE = 'hellooooo'

 sprays out the udp datagram

def udp_sender(SUBNET, MESSAGE):     time.sleep(5)     sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)     for ip in IPNetwork(SUBNET):         try:             sender.sendto(MESSAGE, ("%s" % ip, 65212))         except:             pass

def main():     t = threading.Thread(target=udp_sender, args=(SUBNET, MESSAGE))     t.start()

    socket_protocol = socket.IPPROTO_ICMP     sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)     sniffer.bind(( HOST, 0 ))     sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

    # continually read in packets and parse their information     while 1:         raw_buffer = sniffer.recvfrom(65565)[0]         ip_header = raw_buffer[0:20]         iph = struct.unpack('!BBHHHBBH4s4s' , ip_header)

        # Create our IP structure         version_ihl = iph[0]         ihl = version_ihl & 0xF         iph_length = ihl * 4         src_addr = socket.inet_ntoa(iph[8]);

        # Create our ICMP structure         buf = raw_buffer[iph_length:iph_length + ctypes.sizeof(ICMP)]         icmp_header = ICMP(buf)

        # check for the type 3 and code and within our target subnet         if icmp_header.code == 3 and icmp_header.type == 3:             if IPAddress(src_addr) in IPNetwork(SUBNET):                 if raw_buffer[len(raw_buffer) - len(MESSAGE):] == MESSAGE:                     print("Host up: %s" % src_addr)

if name == 'main':     main()</pre>

運行后得到的結果如下:

$ sudo python scanner.py
Host up: 192.168.1.114(...)

非常棒!

另外,可以將掃描得到的結果與路由器 DHCP 表中的 IP 地址作對比,它們應該是相同的。

參考:

 本文由用戶 mx64 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!