设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

使用Python的Socket模块构建一个UDP扫描工具

2015-6-24 22:47| 发布者: joejoe0332| 查看: 3213| 评论: 0|原作者: pseudo, Garfielt, lostTemple|来自: oschina

摘要: 几周前,我曾经谈到了如何使用Wireshark来进行数据包嗅探,但如果你没有wireshark,你如何去监控网络流量呢?这一次,Python提供了几种解决方案,今天我将一步步演示如何建立一个UDP主机发现工具。 ...

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


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


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


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


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


编写网络包嗅探器

  我们从一个小功能开始:用 Python 的 socket 库来编写一个简单的网络包嗅探器。


  在这个嗅探器中,我们创建一个原始 socket 并将它绑定到一个外部网卡。这个网卡要启用混淆模式(promiscuous mode),也就是说获经过这个网卡的所有数据包都会被捕获,包括那些目标地址不是它的数据包。


  使用 Windows 时要注意一点:我们需要发送一个 IOCTL 包才能将网卡设置为混淆模式。另外,虽然 linux 需要使用 ICMP,Windows 却可以以一种独立于协议的方式来嗅探收到的数据包。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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)


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

1
sudo python sniffer.py


  在另一个终端中 ping 或 traceroute 某些地址,如 www.google.com 会得到如下结果:

1
2
3
$ 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 编写的):

ICMP头

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

对于我们的扫描器,如果得到的 type 和 code 值是3,这意味着 Destination Unreachable(目标不可达)和 Port Unreachable (端口不可达) ICMP 消息错误

为描述 ICMP 消息头,我们用 python 的 ctypes 库来创建一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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


编写消息头解码器

现在可以着手编写 IP/ICMP 消息头解码器了。下面的脚本创建了一个 sniffer socket(正如前面做的那样),然后在一个循环中持续读取数据包并进行解码。

注意代码中将 IP 头的前20个字节读取到了缓存,然后再打印消息头的变量。ICMP 头数据如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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()

测试解码器

在一个终端中运行该脚本,然后在另一个终端运行一个 ping 命令会得到如下结果(注意 ICMP type 值为 0):

1
2
3
4
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(...)
1
2
3
4
5
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:

1
2
3
4
5
6
7
8
9
10
11
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 的响应类型):

1
2
3
4
5
6
7
8
9
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

1
sudo pip install netaddr

我们可以使用如下的代码段来测试这个库 (成功会打印“OK”):

1
2
3
4
5
import netaddr
 
ip = '192.168.1.114'
if ip in netaddr.IPNetwork('192.168.1.0/24'):
    print('OK!')


深入扫描器

我们会将上面所提到的组织在一起来完成我们的扫描器,然后添加一个循环来向目标子网内的所有地址发送 UDP 数据报。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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():
    = 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()

运行后得到的结果如下:

1
2
sudo python scanner.py
Host up: 192.168.1.114(...)

非常棒!

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

参考:


酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部