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/