2020-08-11 记录
参考《python黑帽子 黑客渗透测试与编程之道》,内容大多都是书里的,忏愧,本人战五渣。主要就记录自己怎么学的吧。。。
一般黑客发起攻击前都得踩点,了解下有哪些主机开着,它们身上运行着哪些程序,它们都在传输着些什么信息等等。
有一种踩点,就是嗅探,目的是为了解析目标主机传输流量的内容,这种属于被动攻击。说到嗅探,就想到了wireshark,实验课也是用这个来学习协议的呢!虽然我学得不咋样。
在一个局域网里,对于我的主机来讲(我的主机不是路由节点),我和其它人连着同一个WiFi,就是在同一个WLAN下,就是一个冲突域(对于以太网和无线局域网),一般别人的数据我的主机也是可以接收到的,不过网卡就是个硬件的"过滤器",它会识别MAC地址过滤掉和自己无关的信息。不过我们还是可以选择来接受这些无关的信息,只要将网卡设置为"混杂模式"就可以了(一般情况下,主机都是非混杂模式的)。开启混杂模式,在Linux下需要root权限,Windows下需要管理员权限。
目前WLAN下连接的设备有3台(以IP地址表示):
- 192.168.0.101 -------------本机 SC-202002071824
- 192.168.0.102 TL-WR886N
- 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 IPv6socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
参数二:类型
socket.SOCK_STREAM 流式socket , for TCP (默认)
socket.SOCK_DGRAM 数据报式socket , for UDPsocket.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来的表:
TYPE | CODE | Description | Query | Error |
---|---|---|---|---|
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主机不可达,呵呵。
折腾了半天,这个部分没弄好。。。
留了一个坑。