来自 Black Hat Python 书的 Python 嗅探
Posted
技术标签:
【中文标题】来自 Black Hat Python 书的 Python 嗅探【英文标题】:Python Sniffing from Black Hat Python book 【发布时间】:2015-06-01 03:20:18 【问题描述】:import socket
import os
import struct
import sys
from ctypes import *
# host to listen on
host = sys.argv[1]
class IP(Structure):
_fields_ = [
("ihl", c_ubyte, 4),
("version", c_ubyte, 4),
("tos", c_ubyte),
("len", c_ushort),
("id", c_ushort),
("offset", c_ushort),
("ttl", c_ubyte),
("protocol_num", c_ubyte),
("sum", c_ushort),
("src", c_ulong),
("dst", c_ulong)
]
def __new__(self, socket_buffer=None):
return self.from_buffer_copy(socket_buffer)
def __init__(self, socket_buffer=None):
# map protocol constants to their names
self.protocol_map = 1:"ICMP", 6:"TCP", 17:"UDP"
# human readable IP addresses
self.src_address = socket.inet_ntoa(struct.pack("<L",self.src))
self.dst_address = socket.inet_ntoa(struct.pack("<L",self.dst))
# human readable protocol
try:
self.protocol = self.protocol_map[self.protocol_num]
except:
self.protocol = str(self.protocol_num)
# create a raw socket and bind it to the public interface
if os.name == "nt":
socket_protocol = socket.IPPROTO_IP
else:
socket_protocol = socket.IPPROTO_ICMP
sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
sniffer.bind((host, 0))
# we want the IP headers included in the capture
sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
# if we're on Windows we need to send some ioctls
# to setup promiscuous mode
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
try:
while True:
# read in a single packet
raw_buffer = sniffer.recvfrom(65565)[0]
# create an IP header from the first 20 bytes of the buffer
ip_header = IP(raw_buffer[0:20])
print "Protocol: %s %s -> %s" % (ip_header.protocol, ip_header.src_address, ip_header.dst_address)
except KeyboardInterrupt:
# if we're on Windows turn off promiscuous mode
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
这是来自 Black Hat Python 一书的代码。此代码应该嗅探原始套接字并显示来自 IP 标头的信息。它在 Windows 上运行良好(使用 Windows 8.1 64 位)。当我尝试在 linux (Kali linux 1.1.0-amd64) 上运行它时,我收到以下错误
ValueError: Buffer size too small (20 instead of at least 32 bytes)
为了解决这个问题,我像这样在缓冲区中添加了 12 个空格
ip_header = IP(raw_buffer[0:20]+' '*12)
当我这样做时,我得到以下错误
struct.error: 'L' format requires 0 <= number <= 4294967295
这样就行了
self.src_address = socket.inet_ntoa(struct.pack("<L",self.src))
我尝试将 L 之前的符号更改为 > 和 !我只用 L 试了一下,他们都给了我同样的问题。我也尝试像这样将 self.src 包装在 ntohs 中
self.src_address = socket.inet_ntoa(struct.pack("<L",socket.ntohs(self.src)))
我认为这与字节序有关,但我不确定。任何帮助将不胜感激。
注意:在 Windows 上,您必须以管理员身份运行,而在 linux 上,由于原始套接字,您必须以超级用户身份运行。如果您在 linux 上运行它,请打开另一个终端并 ping www.google.com,这样您就可以生成一些 ICMP 数据包以供其捕获。
编辑:我也尝试过使用
反转缓冲区ip_header = IP(raw_buffer[0:20][::-1]+' '*12)
编辑 2:在执行此处列出的任何其他项目之前,我确实在下一行尝试了 65535 和 65534。
raw_buffer = sniffer.recvfrom(65565)[0]
编辑 3:这在运行 python 2.7.6 的 ubuntu 机器上工作,我的 kali 发行版是 2.7.3,所以我决定在我的 kali 盒子上获取最新版本的 python,恰好是 2.7.9。还是没有运气。
我将以下代码放入结构中的 new 函数中以查看缓冲区大小
print sizeof(self)
在我的 Ubuntu 和 windows 机器上是 20,但在我的 kali 机器上是 32
【问题讨论】:
您的代码正在运行。 sys.version '2.7.6(默认,2014 年 3 月 22 日,22:59:38)\n[GCC 4.8.2]'。uname -a
'Linux 笔记本电脑 3.16.0-25-generic #33-Ubuntu SMP Tue Nov 4 12:05:25 UTC 2014 i686 i686 i686 GNU/Linux'
尝试传递整个缓冲区 IP(raw_buffer)
或 IP(raw_buffer[:32])
有趣.. 看看我的 linux 机器上的 python 版本是 2.7.3,我的 windows 是 2.7.8... 我会尝试升级看看会发生什么。
我改变了这个。 host = 'localhost'
并运行 ping localhost
hmmmm 我在 kali 机器上升级到 python 2.7.9 仍然遇到同样的错误。我尝试在我的 Ubuntu VM 上使用 2.7.6 运行相同的代码,它可以工作......一定是我的 kali 盒子上的东西正在这样做......
【参考方案1】:
#raw_buffer = sniffer.recvfrom(65565)[0]
raw_buffer = sniffer.recvfrom(65535)[0]
IP 包大小为 (2^16) - 1
问题在于 32 位和 64 位系统。ip_header = IP(raw_buffer[:20])
适用于 x86 Ubuntu。ip_header = IP(raw_buffer[:32])
适用于 amd64 CentOS 6.6 Python 2.6.6ip_header = IP(raw_buffer)
适用于两者。
你必须改变这些,
("src", c_ulong),
("dst", c_ulong)
self.src_address = socket.inet_ntoa(struct.pack("<L",self.src))
self.dst_address = socket.inet_ntoa(struct.pack("<L",self.dst))
进入
("src", c_uint32),
("dst", c_uint32)
self.src_address = socket.inet_ntoa(struct.pack("@I",self.src))
self.dst_address = socket.inet_ntoa(struct.pack("@I",self.dst))
'@I' 在本机顺序中是无符号整数。
因为c_ulong
在 i386 中是 4 个字节,在 amd64 中是 8 个字节。检查以下,
struct.calcsize('@BBHHHBBHLL')
在 i386 中为 20,在 amd64 中为 32,大小为 _fields_
。实际上它是 amd64 中的 28 个字节加上 4 个字节用于字对齐。
ip_header = IP(raw_buffer[:20])
现在可以独立于平台正常工作。
【讨论】:
我也注意到了。我确实尝试了 65535 和 65534,然后其他任何值都不起作用 只需阅读编辑内容即可。谢谢尼扎姆!我将在多个平台上测试你和 MadMan2064 的答案,并在我点击答案检查之前让其他人有机会参与进来。 对我来说,在 Lubuntu 64 位上,我所做的只是将两个 c_ulong 更改为 c_uint32,并且它有效。只是这两个 c_types。不需要更改包格式字符串('所以这是一个 64/32 位的问题。它需要 32 个字节而不是 20 个字节的事实意味着该结构没有正确打包。 “c_ulong”在 64 位 linux 中是 64 位,并且在“IP”类中以这种方式映射。
IP 标头为 20 字节 + 可选字段。源 IP 地址和目标 IP 地址以字节 20 结尾,这是当前 IP 结构所采用的。 (如果你想要这些选项,你将不得不手动解析它们)。
我查找了 UDP 位字段并直接将它们设置为“IP”类。查看 ctypes 文档,可以映射整数类型以限制位数。
class IP(Structure):
_fields_ = [
("ihl", c_ubyte, 4),
("version", c_ubyte, 4),
("tos", c_ubyte, 8),
("len", c_ushort, 16),
("id", c_ushort, 16),
("offset", c_ushort, 16),
("ttl", c_ubyte, 8),
("protocol_num", c_ubyte, 8),
("sum", c_ushort, 16),
("src", c_uint, 32),
("dst", c_uint, 32),
]
如果将位偏移量相加,则它们的总和为 160。160/8 = 20 字节,这就是 ctypes 将此结构打包到的内容。
在 ping 上运行它会产生看起来可以接受的结果。
Protocol: ICMP 127.0.0.1 -> 127.0.0.1
Protocol: ICMP 127.0.0.1 -> 127.0.0.1
Protocol: ICMP 127.0.0.1 -> 127.0.0.1
Protocol: ICMP 127.0.0.1 -> 127.0.0.1
此外,数据包大小是 MTU(或最大传输单元)的函数,因此如果您计划在以太网上运行它,限制因素是帧的 MTU。较大的数据包在被推出以太网端口之前会在 tcp/ip 堆栈中分片。
$ ifconfig eth0
eth0 Link encap:Ethernet HWaddr 00:00:00:ff:ff:ff
UP BROADCAST MULTICAST MTU:1500 Metric:1
此外,这个问题应该有助于澄清为什么某些平台具有不同大小的整数和长整数的问题:
What is the bit size of long on 64-bit Windows?
作为替代方案,我发现 dpkt 是一个相当不错的用于解码/编码 ip 数据包的库,除非您特别需要使用或想要 ctypes。
https://code.google.com/p/dpkt/
【讨论】:
以上是关于来自 Black Hat Python 书的 Python 嗅探的主要内容,如果未能解决你的问题,请参考以下文章
python 为VGOC Premiershit 2016排序帽子。保存为hat.py并使用“python hat.py”运行。
Black Hat USA 2021:通过无线基带-针对5G智能手机的RCE白皮书