- PVSM.RU - https://www.pvsm.ru -
Сразу скажу: то, чем мы с вами будем здесь заниматься, не претендует, скажем, на какую-то промышленную применимость. Более того, я признаю, что мой код в данном примере может быть ужасен, страшен и ненужен. И тем не менее — почему бы не поперехватывать пакеты посреди недели? Так, слегка.
Итак, сегодня мы вот что наколдуем:
1. Реализуем простейший пассивный перехватчик пакетов для TCP и UDP
2. Засунем его в C-библиотеку в виде расширения для Python
3. Приделаем ко всему этому интерфейс итератора, дабы байты сыпались, как из рога изобилия
4.…
5. PROFIT!
Зачем нужны снифферы? Чтобы нюхать сетевой трафик. Нет, я не шучу, с английского глагол «to sniff» так и переводится — «нюхать». Ладно-ладно, будем более научны — с их помощью можно и нужно анализировать сетевые пакеты, проходящие через сетевую карту компьютера.
Снифферы бывают пассивные и активные. Пассивный сниффер делает именно и только то, что от него требуется — перехватывает для анализа трафик, проходящий сквозь сетевую карту компьютера, на котором он установлен. Казалось бы, куда уж круче? Однако же, активный сниффер не только мониторит сетевую карту, но и всячески пытается добраться до трафика, который гуляет по локальной сети и не предназначен для чужих глаз. Делает он это, например, с использованием ARP-Spoofing [1] и всяких прочих грязных трюков. Кстати, в некоммутируемых сетях (основанных на репитерах и хабах) пассивный сниффер может внезапно обнаружить в себе суперсилы, ибо при отсутствии коммутации все пакеты в таких сетях рассылаются по всем хостам. Принимать их, конечно, должны только адресаты, но… :)
А вот чтобы их принимать — надо перво-наперво заставить сетевую карту уволить ее личного секретаря, который фильтрует корреспонденцию, и начать читать все и сразу. Научно это называется "Promiscuous mode [2]", то есть «Неразборчивый режим». И тут есть засада:
Да, для перевода карты в неразборчивый режим требуются права root. Поэтому и наш скрипт попросит их для запуска. Такие дела.
Как говорил один известный киношный персонаж, «We need to go deeper». То есть, нырнуть поглубже в уровни OSI, на канальный уровень. Для этого при создании сокета необходимо указать константу PF_PACKET вместо PF_INET:
int s_sock;
struct ifreq ifr;
strcpy(ifr.ifr_name, IFACE);
if ( (s_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
perror("Error creating socket");
exit(-1);
}
ifr.ifr_flags |= IFF_PROMISC;
if (ioctl(s_sock, SIOCGIFFLAGS, &ifr) < 0) {
perror("Unable to set promiscious mode for device");
close(s_sock);
exit(-1);
}
Обычно при открытии подобного сокета указывается конкретный протокол, по которому идет перехват. Если же указать константу ETH_P_ALL — будут перехватываться все. Структура ifreq используется в Linux для руления сетевым интерфейсом на низком уровне через ioctl() [3], а IFACE в данном случае — просто строка с именем смотрящего в сеть интерфейса, например, «eth0».
Собственно, теперь все, что нам остается — читать данные из сокета в цикле и смотреть, что из этого получится:
int n = 0;
char buf[MTU];
n = recvfrom(s_sock, buf, sizeof(buf), 0, 0, 0);
MTU здесь проще всего поставить равным 1500 — это определенный стандартом максимальный размер пакета в сетях Ethernet. Для сетей, построенных по другому стандарту, например, FDDI, значение может быть другим.
Поскольку мы работаем с Ethernet, данные о заголовках полученных пакетов проще всего записывать в специально предназначенную для этого структуру ядра [4] — ethhdr. Для разных базовых протоколов вплоть до транспортного уровня существуют похожие структуры, которые, как ни сложно в это поверить, носят названия наподобие iphdr, tcphdr или даже udphdr (как говорится, «знаю отличную шутку про UDP, но не факт, что она до вас дойдет»). Как и полагается в старом добром C, это делается как-то так:
struct ethhdr eth;
memcpy((char *) ð, data, sizeof(struct ethhdr));
Всем известно, что в питоне у нас все есть объект. В случае с генератором/итератором исключения так же не будет — мы должны создать объект, имеющий метод __iter__() и умеющий next(). У объекта есть куча полей, большинство из которых нам не нужны, поэтому осторожно — впереди куча нулей.
PyTypeObject PyPacketGenerator_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"packgen", /* tp_name */
sizeof(PacketGeneratorState), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)packgen_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
(iternextfunc)packgen_next, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
PyType_GenericAlloc, /* tp_alloc */
packgen_new, /* tp_new */
};
Из этого объявления видно, что рабочие методы нашего будущего объекта — это packgen_new(), packgen_next() и packgen_dealloc(). Последний является деструктором и в общем и целом без него можно обойтись, если очень хочется и нет лишних данных в памяти. Кроме того, нам потребуется структура, в которой мы будем хранить данные о состоянии объекта на текущей итерации. Поскольку единственное, что нам необходимо хранить — это сокет, его и объявим:
typedef struct {
PyObject_HEAD
int s_sock;
} PacketGeneratorState;
Как я сказал выше, нужно открыть сокет и настроить сетевую карту в режим шпиёнства. Проще всего это сделать прямо в методе инициализации объекта.
static PyObject *
packgen_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
int s_sock;
struct ifreq ifr;
strcpy(ifr.ifr_name, IFACE);
if ( (s_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
perror("Error creating socket");
exit(-1);
}
ifr.ifr_flags |= IFF_PROMISC;
if (ioctl(s_sock, SIOCGIFFLAGS, &ifr) < 0) {
perror("Unable to set promiscious mode for device");
close(s_sock);
exit(-1);
}
PacketGeneratorState *pkstate = (PacketGeneratorState *)type->tp_alloc(type, 0);
if (!pkstate)
return NULL;
pkstate->s_sock = s_sock;
return (PyObject *)pkstate;
}
В принципе, вполне реально было бы сделать возвращение пакетов из модуля в виде классов, но мне было ленья решил сделать проще и возвращать словарь (а если кто переделает по-своему — будет молодец). Все, что остается сделать — это определить, что у нас IP на сетевом уровне, а по IP-заголовку, в свою очередь, определить вышележащий протокол (если кто не помнит, протоколы обозначаются циферками и прописаны в /etc/protocols). Это все работает потому, что пакеты у нас похожи на творения поваров Burger King — каждый вышестоящий уровень дописывает свой заголовок к нижестоящему. Возьмем старых добрых братьев — TCP и UDP.
PyObject *packet;
packet = PyDict_New();
if (!packet)
return NULL;
if (ntohs(eth.h_proto) == ETH_P_IP) {
ip = (struct iphdr *)(data + sizeof(struct ethhdr));
PyDict_SetItemString(packet, "ip_source", PyString_FromFormat("%s", inet_ntoa(ip->saddr)));
...
if ((ip->protocol) == IPPROTO_TCP) {
tcp = (struct tcphdr *)(data + sizeof(struct ethhdr) + sizeof(struct iphdr));
PyDict_SetItemString(packet, "tcp_source_port", PyString_FromFormat("%d", ntohs(tcp->source)));
...
}
if ((ip->protocol) == IPPROTO_UDP) {
udp = (struct udphdr *)(data + sizeof(struct ethhdr) + sizeof(struct iphdr));
PyDict_SetItemString(packet, "udp_source_port", PyString_FromFormat("%d", ntohs(udp->source)));
...
}
}
return packet;
Выглядит все в итоге примерно вот так:
Python 2.7.3 (default, Apr 20 2012, 22:44:07)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pysniff
>>> for i in pysniff.packgen():
... print i
...
{'ip_destination': '192.168.1.111', 'tcp_seq': '10972', 'ip_source': '173.194.32.53', 'tcp_offset': '8', 'tcp_source_port': '443', 'tcp_dest_port': '44021'}
{'ip_destination': '173.194.32.53', 'tcp_seq': '47475', 'ip_source': '192.168.1.111', 'tcp_offset': '8', 'tcp_source_port': '44021', 'tcp_dest_port': '443'}
{'ip_destination': '192.168.1.111', 'tcp_seq': '10972', 'ip_source': '173.194.32.53', 'tcp_offset': '8', 'tcp_source_port': '443', 'tcp_dest_port': '44021'}
{'ip_destination': '173.194.32.53', 'tcp_seq': '47475', 'ip_source': '192.168.1.111', 'tcp_offset': '8', 'tcp_source_port': '44021', 'tcp_dest_port': '443'}
Ради смеха создал репозиторий на github [5], вдруг захочется развить идею? Удачного вам сниффинга, не попадайтесь!
Автор: enchantner
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/6301
Ссылки в тексте:
[1] ARP-Spoofing: http://ru.wikipedia.org/wiki/ARP-spoofing
[2] Promiscuous mode: http://ru.wikipedia.org/wiki/Promiscuous_mode
[3] руления сетевым интерфейсом на низком уровне через ioctl(): http://h30097.www3.hp.com/docs/dev_doc/DOCUMENTATION/HTML/DDK_R2/DOCS/HTML/MAN/MAN9/0143___S.HTM
[4] структуру ядра: http://lxr.oss.org.cn/source/include/linux/if_ether.h#L126
[5] репозиторий на github: https://github.com/enchantner/pysniff
Нажмите здесь для печати.