使用 Python 的 Socket 模块构建一个 UDP 扫描工具 已翻译 100%

oschina 投递于 2015/06/22 07:01 (共 8 段, 翻译完成于 06-23)
阅读 10118
收藏 85
10
加载中

当涉及到对一些目标网络的侦察时,出发点无疑是首先发现宿主主机。这个任务还可能包含嗅探和解析网络中数据包的能力。

几周前,我曾经谈到了如何使用Wireshark来进行数据包嗅探,但如果你没有wireshark,你如何去监控网络流量呢?

这一次,Python提供了几种解决方案,今天我将一步步演示如何建立一个UDP主机发现工具。首先,我们要看我们如何处理原始套接字来编写一个简单的嗅探器,它能够查看和解析网络数据包。然后,我们将在子网内多线程运行该进程,结果将在我们的扫描仪上。

Garfielt
Garfielt
翻译于 2015/06/22 09:10
2

原始套接字酷之所在是它能够访问底层网络的信息。比如,我们可以用它来检查IPICMP报头,这是在OSI模型的第三层(网络层)。

使用UDP数据报最酷的事情是:当发送信息穿越子网时,不同于TCP,它不太多的开销(还记得TCP握手吧)。我们需要做的就是等待ICMP回应,对方主机是否可用或关闭(不可访问)。记住,ICMP协议本质上是一个特殊的控制协议,它指示错误报告和控制机器数据传输的的行为。

Garfielt
Garfielt
翻译于 2015/06/22 09:17
2

编写网络包嗅探器

我们从一个小功能开始:用 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)

在终端中运行如下命令进行测试:

$ 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))

很明显需要对这些头信息进行解码。

pseudo
pseudo
翻译于 2015/06/23 10:17
1

IP ICMP层解码

IP 头

典型的 IP 头有如下结构,每个字段都对应一个变量 (这个头最初是用 C 编写的):

ICMP头

同样,ICMP 由于内容的不同其消息类型也不同,但每个消息都包括三个一致的元素:type,code (告知接收主机这个 ICMP 消息的解码类型)和 checksum。

对于我们的扫描器,如果得到的 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
pseudo
pseudo
翻译于 2015/06/23 09:08
1

编写消息头解码器

现在可以着手编写 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()
pseudo
pseudo
翻译于 2015/06/23 09:24
1

测试解码器

在一个终端中运行该脚本,然后在另一个终端运行一个 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
pseudo
pseudo
翻译于 2015/06/23 09:29
1

编写扫描器

安装 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!')
pseudo
pseudo
翻译于 2015/06/23 09:39
1

深入扫描器

我们会将上面所提到的组织在一起来完成我们的扫描器,然后添加一个循环来向目标子网内的所有地址发送 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()

运行后得到的结果如下:

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

非常棒!

另外,可以将扫描得到的结果与路由器 DHCP 表中的 IP 地址作对比,它们应该是相同的。

参考:

pseudo
pseudo
翻译于 2015/06/23 09:56
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(9)

长风破浪炫
长风破浪炫
mark一下,慢慢学习
justalittlenoob
justalittlenoob
写的好。很详细。
gengv2007
gengv2007
mark
cnnic17173
cnnic17173
这个是Linux的。window的呢?
想不到
想不到
代码好乱,python居然有分号结尾
从前是菜鸟
从前是菜鸟
不错,收藏先,慢慢学习。
evangelist
evangelist
不错,学习一下
梁复活
mark
Serval
Serval
Mark一下,最近打算了解一点Python知识,把它作为日常高级脚本语言。
返回顶部
顶部