Python嗅探黑帽Python书籍

时间:2015-03-27 17:33:41

标签: python linux sockets networking

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之前的符号更改为&gt;而且!我只用L试了一下它们都给了我同样的问题。我也试过在ntohs中包装self.src,如此

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

2 个答案:

答案 0 :(得分:25)

#raw_buffer = sniffer.recvfrom(65565)[0]
raw_buffer = sniffer.recvfrom(65535)[0]

IP paket大小为(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.6
ip_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))

&#39; @我&#39;是本机顺序的unisigned int。 因为c_ulong在i386中是4个字节,在amd64中是8个。请检查以下内容,

struct.calcsize('@BBHHHBBHLL')  

在i386中为20,在amd64中为32,其大小为_fields_。实际上,amd64中的28个字节加上4个字节用于字对齐。

ip_header = IP(raw_buffer[:20])现在可以独立于平台正常工作。

答案 1 :(得分:4)

所以这是64/32位问题。它需要32个字节而不是20个字节的事实意味着结构未正确打包。 “c_ulong”在64位linux中是64位,并且正在“IP”类中进行映射。

IP标头是20个字节+可选字段。源和目标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/