Python Sniffing из книги Black Hat Python

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

Я думаю, что это имеет какое-то отношение к контенту, но я не уверен. Любая помощь будет принята с благодарностью.

ПРИМЕЧАНИЕ. В окнах вы должны запускаться как администратор, а на Linux вы должны запускаться как суперпользователь из-за сырых сокетов. Если вы запустите это на linux, откройте еще один терминал и выполните ping www.google.com, чтобы вы могли генерировать некоторые ICMP-пакеты для его захвата.

EDIT: Я также попытался перевернуть буфер с помощью

ip_header = IP(raw_buffer[0:20][::-1]+' '*12)

РЕДАКТИРОВАТЬ 2: Я попытался выполнить как 65535, так и 65534 в строке ниже, прежде чем выполнять какие-либо другие пункты, перечисленные здесь.

raw_buffer = sniffer.recvfrom(65565)[0]

EDIT 3: Это работало на машине ubuntu с запуском python 2.7.6, а мой дистрибутив kali был 2.7.3, поэтому я решил получить последнюю версию python на моем ящике kali, который, как оказалось, равен 2.7.9. Еще не повезло.

Я поместил следующий код в функцию new в моей структуре, чтобы просмотреть размер буфера

print sizeof(self)

На моих машинах Ubuntu и windows это было 20, однако на моей машине Kali это было 32

Ответ 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.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))

'@I' - это unisigned int в собственном порядке. потому что c_ulong составляет 4 байта в i386 и 8 в amd64. Проверьте следующее:

struct.calcsize('@BBHHHBBHLL')  

равно 20 в i386 и 32 в amd64, размер которого равен _fields_. В действительности это 28 байтов в amd64 плюс 4 байта, дополненных для выравнивания слов.

ip_header = IP(raw_buffer[:20]) теперь корректно работает независимо от платформ.

Ответ 2

Итак, это проблема 64/32 бит. Тот факт, что ему понадобилось 32 байта вместо 20, означает, что структура была неправильно упакована. "c_ulong" - 64 бита в 64-битном Linux, и он был сопоставлен таким образом в классе "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 для этой структуры.

Выполнение этого на пинге дает что-то, что выглядит приемлемым.

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 (или Maximum Transfer Unit), поэтому, если вы планируете запустить это в сети Ethernet, ограничивающим фактором является MTU кадра. Большие пакеты будут фрагментированы в стеке tcp/ip, прежде чем выталкиваться из порта Ethernet.

$ ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 00:00:00:ff:ff:ff  
          UP BROADCAST MULTICAST  MTU:1500  Metric:1

Кроме того, этот вопрос должен помочь прояснить вопрос о том, почему некоторые платформы имеют разные размеры и длинные размеры:

Каков размер бит в 64-битной Windows?

В качестве альтернативы я обнаружил, что dpkt является довольно хорошей библиотекой для декодирования/кодирования ip-пакетов, если только вам не нужно использовать или не хотите ctypes.

https://code.google.com/p/dpkt/