你好,游客 登录
背景:
阅读新闻

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

[日期:2017-09-07] 来源:百家号  作者:python学习开发 [字体: ]

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

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

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

原始套接字酷之所在是它能够访问底层网络的信息。比如,我们可以用它来检查IP和ICMP报头,这是在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 )

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

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

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

编写消息头解码器

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

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

import struct

from ICMPHeader import ICMP

# host to listen on

def main () :

socket_protocol = socket . IPPROTO_ICMP

sniffer = socket . socket ( socket . AF_INET , socket . SOCK_RAW , socket_protocol )

sniffer . bind (( HOST , 0 ))

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'

main ()

测试解码器

在一个终端中运行该脚本,然后在另一个终端运行一个 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

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 -& gt ; 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 -& gt ; Version : 4 , Header Length : 5 , TTL : 56 , Protocol : 1 , Source : 74.125.226.50 , Destination : 192.168.1.114

编写扫描器

安装 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!' )

深入扫描器

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

import threading

import time

from netaddr import IPNetwork , IPAddress

# 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 :

t = threading . Thread ( target = udp_sender , args = ( SUBNET , MESSAGE ))

t . start ()

# continually read in packets and parse their information

src_addr = socket . inet_ntoa ( iph [ 8 ]);

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

运行后得到的结果如下:

$ sudo python scanner . py

Host up : 192.168.1.114 (...)

非常棒!

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

收藏 推荐 打印 | 阅读:
相关新闻       python网络开发  pyton udp