Снифер – тулза номер один для хакера. Поснифать и проанализировать трафик, выудить из него пароли к почтовому ящику своего соседа Васи Пупкина – типичные задачи, которые возлагают на подобные программы. Etherscan Analyzer, EtherSnoop, Ettercap, ZxSniffer – хорошо зарекомендовавшие себя сниферы. Об их правильном использовании не раз говорилось в нашем журнале. Юзать эти проги, несомненно, круто, но намного круче написать свой 31337-й снифер, который будет обладать тем функционалом, который нужен именно тебе и только тебе!
Теория сниффера
Итак, начнем с определения. Снифер – это программа или аппаратное устройство для перехвата сетевого трафика. Аппаратные сниферы нас сегодня интересовать не будут, а программные мы рассмотрим подробнее.
Сниферы условно можно разделить по способу прослушивания сети. Прослушивание бывает активным и пассивным. Сниферы, использующие этот пассивное прослушивание, просто переводят сетевую карту в режим promiscuous (неразборчивый). В этом случае становится возможным получить все пакеты в сегменте Ethernet. Сразу хочу развеять возможно возникшие иллюзии. Все пакеты ты сможешь увидеть только в том случае, если сеть построена на так называемых хабах (hub). В сетях, где используются устройства типа свитча (switch), такие фокусы не пройдут. Почему? Секрет кроется в работе сетевого устройства. Хабы – довольно безграмотные жестянки, одним словом, двоечники. Все пришедшие данные с одного порта они передают на все остальные, то есть даже не задумываясь о том, на каком порту находится получатель, для которого эти данные, собственно, и были предназначены. Свитчи – это своего рода отличники. Они с немецкой дотошностью проверяют заголовки пришедших кадров данных и пересылают их в другой сегмент только в том случае, если в нем есть истинный получатель. Сниферы, применяющие активное прослушивания сети, более продвинуты. Для перехвата тяжелого трафика они используют разные способы. Наиболее известные из них:
- MAC Flooding. Актуален для устаревших свитчей. Суть этого метода состоит в следующем: многие старые свитчи имели лимит памяти, в которой хранилась адресная таблица. Если умудриться ее зафлудить, свитч переставал проверять данные перед их отправкой и просто слал их на все порты, подобно обычному хабу. Способ достаточно прост в реализации, но, к сожалению, таких свитчей найти сейчас уже практически нереально.
- MAC Duplicating. Этот способ активного прослушивания заключается в изменении своего MAC на MAC жертвы. В результате все отправляемые жертве пакеты будут дублироваться и тебе.
Routing attacks. Этот способ основан на отправке жертве фальшивых сообщений ICMP Redirect. Они используются роутерами для сообщения узлу адреса нового роутера, но с более коротким путем для запрашиваемого узла. Таким образом, можно в качестве адреса нового роутера подставить адрес своей тачки, и все пакеты будут проходить через твой комп. Применение этого способа осложняется игнорированием современными ОС сообщений ICMP Redirect.
Зачем нужен хакеру пассивный снифинг? А вот зачем! Многие злодеи наживляют своим врагам тайные и неопределяемые антивирусами (ввиду самописности) трояны, которые собирают логи и отсылают информацию своему хозяину.
Снифер с точки зрения кодера
С позиции программирования наиболее простыми в реализации являются сниферы, использующие пассивное прослушивание. Сейчас можно найти кучу готовых драйверов, которые занимаются перехватом трафика. Все, что остается, – изучить хелп по работе с ними и написать свою программу-интерфейс. Подобным образом и работают многие известные сниферы. Если ты думаешь, что это несерьезно, то спешу огорчить тебя. Хороший снифер должен уметь не только захватывать данные, но и разбирать их, а это уже достаточно серьезная задача.
Из драйверов для перехвата трафика чаще всего используется **WinPCap**. Этот драйвер один из немногих поддерживает все NT версии Windows, поэтому его можно юзать, не опасаясь, что твое творение где-то вылетит с ошибкой.
Если тебе не хочется заморачиваться с драйверами, можно пойти более простым путем – использовать raw sockets (сырые сокеты). Такой тип снифера реализовать тоже достаточно просто, а если учесть, что в рубрике «Кодинг» я уже неоднократно приводил примеры сетевых приложений с использованием WinSock API, то задача становится и вовсе нетривиальной.
Универсальные компоненты
Сегодня мы создадим снифер, который будет поддерживать работу с помощью двух технологий: используя драйвер WinPCap и RAW Socket. Для воплощения этой безумной идеи я решил найти какую-нибудь универсальную библиотеку компонентов. Признаюсь честно, вначале я даже не думал, что это возможно. Но немного погуглив, я наткнулся на очень интересный наборчик - Magenta Systems Internet Packet Monitoring Components.
Эта полезная библиотека состоит из двух главных невизуальных компонентов: TMonitorPCap (взаимодействует с WinPCap) и TMonitorSocket (использует сырые сокеты). Оба компонента написаны достаточно хорошо, и глюков при их использовании замечено не было. Давай рассмотрим их возможности.
TMonitorSocket
Все свойства и методы обоих компонентов перечислены в соответствующих таблицах. В них не попало описание только одного-единственного обработчика события - TPacketEvent. Событие описано следующим образом:
TPacketEvent = procedure (Sender: TObject; PacketInfo: TPacketInfo) of object;
Первый параметр пояснять, я думаю, не нужно, поскольку если ты программируешь на Delphi, то знать это просто обязан, а вот второй рассмотрим подробнее. Во втором параметре передается структура типа TPacketInfo, в которой и хранятся все захваченные данные. Структура описана так:
TPacketInfo = record
PacketLen: integer ;
EtherProto: word ;
EtherSrc: TMacAddr ;
EtherDest: TMacAddr ;
AddrSrc: TInAddr ;
AddrDest: TInAddr ;
PortSrc: integer ;
PortDest: integer ;
ProtoType: byte ;
TcpFlags: word ;
SendFlag: boolean ;
IcmpType: byte;
DataLen: integer;
DataBuf: string ;
PacketDT: TDateTime ;
end;
Разберем поля структуры более подробно:
- packetLen – длина полученного пакета.
- EtherProto – тип Ethernet-протокола. Может быть: PUP, XNS, IP, ARP, RARP, SCA, IPv6, LOOP, XIMT, IPX и т.д. Полное описание можешь посмотреть в packhdrs.pas.
- EtherSrc – MAC-адрес отправителя.
- EtherDest – MAC-адрес получателя.
- AddrSrc – IP отправителя.
- AddrDest – IP получателя.
- PortSrc – порт отправителя.
- PortDest – порт получателя.
- ProtoType – тип транспортного протокола. Может быть: IPPROTO_TCP (TCP), IPPROTO_UDP (UDP), IPPROTO_ICMP).
- TcpFlags – флаги TCP/IP-пакетов.
- SendFlags – истина, если пакет отправлен с локального IP.
- IcmpType – тип ICMP-пакета. Возможные значения: ECHO_REPLAY, DEST_UNREA, SRC_Q, REDIR, ECHO, TTLX, BADPAR, TIME, TIME_REPLY, INFO, INFO_REPLY.
- DataLen – длина захваченных данных.
- DataBuf - сами данные.
- PacketDT – время, в которое был захвачен пакет.
От теории к практике
Пришло время закончить с теорией и перейти к реальной работе. Сейчас мы с тобой напишем самый настоящий снифер в, казалось бы, неприспособленной для этого среде. Запускай Delphi и создавай новый проект. Как создашь, сразу же добавь в Uses имена модулей компонентов от MSIPMC. В своем проекте я добавил monsock, monpcap, WSocket, packet32, pcap, Winsock, magsubs1, Packhdrs.
Сохрани проект и попробуй его откомпилировать. Если компиляция пройдет успешно, значит Delphi смог найти указанные модули, если нет - ты забыл прописать пути, по которым расположены модули.
Делаем форму
Для сегодняшнего примера я сделал простенькую форму, которая представлена на одном из рисунков. По всей форме я растянул TPageControll и создал две закладки: «Настройки» и «Лог».
Закрой новоиспеченную форму и перейди в раздел private. Объяви в нем следующие переменные и процедуры:
_monWinPCap: TMonitorPCap;
_monRawSocket: TMonitorSocket;
_nado : boolean;
_adapterIpList: TStringList;
_adapterMaskList: TStringList;
_adapterbroadcastList: TStringList;
procedure Initialize();
procedure RefreshInfo();
procedure GetPacket(Sender: TObject; PacketInfo: TPacketInfo);
Рассмотрим сначала процедуру Initialize(). Ее вызов необходимо повесить на OnCreate формы. Сама начинка процедуры описана в соответствующей врезке.
Процедура Initialize()
//Мониторинг сырых сокетов
_monRawSocket := TMonitorSocket.Create(self);
_monRawSocket.OnPacketEvent := GetPacket;
cbxIpForControll.Items.Clear;
cbxIpForControll.Items := LocalIpList;
if (cbxIpForControll.Items.Count<0) then
cbxIpForControll.ItemIndex := 0;
//Если доступен WinPCap
if (loadPacketDll) then
_monWinPCap := TMonitorPCap.Create(self);
_monWinPCap.OnPacketEvent := GetPacket;
//Получаем список доступных сетевых адаптеров
cbxAdapters.Items.Clear;
cbxAdapters.Items.Assign(_monWinPCap.AdapterDescList);
//Если в системе присутствует хоть один адаптер,
// значит нам есть чем заняться :)
If (cbxAdapters.Items.Count<0) then
begin
cbxAdapters.ItemIndex := 0;
cbUseWinPCap.Checked := true;
cbPromiscuous.Checked := true;
cbIgnoreNonIp.Checked := true;
_adapterIpList := TStringList.Create;
_adapterMaskList := TStringList.Create;
_adapterBroadCastList := TStringList.Create;
end
else
begin
ShowMessage('Не обнаружен ни один сетевой адаптер!');
Application.Terminate;
End;
_nado := false; //Мониторинг сырых сокетов
_monRawSocket := TMonitorSocket.Create(self);
_monRawSocket.OnPacketEvent := GetPacket;
cbxIpForControll.Items.Clear;
cbxIpForControll.Items := LocalIpList;
if (cbxIpForControll.Items.Count<0) then
cbxIpForControll.ItemIndex := 0;
//Если доступен WinPCap
if (loadPacketDll) then
_monWinPCap := TMonitorPCap.Create(self);
_monWinPCap.OnPacketEvent := GetPacket;
//Получаем список доступных сетевых адаптеров
cbxAdapters.Items.Clear;
cbxAdapters.Items.Assign(_monWinPCap.AdapterDescList);
//Если в системе присутствует хоть один адаптер,
//значит нам есть чем заняться :)
If (cbxAdapters.Items.Count<0) then
begin
cbxAdapters.ItemIndex := 0;
cbUseWinPCap.Checked := true;
cbPromiscuous.Checked := true;
cbIgnoreNonIp.Checked := true;
_adapterIpList := TStringList.Create;
_adapterMaskList := TStringList.Create;
_adapterBroadCastList := TStringList.Create;
end
else
begin
ShowMessage('Не обнаружен ни один сетевой адаптер!');
Application.Terminate;
End;
_nado := false;
Пока ты переписываешь листинг, я прокомментирую происходящее. Итак, как я уже сказал, эта процедура должна вызываться во время создания формы и ее единственная цель - инициализация всех необходимых объектов. Первое, что я делаю в процедуре, – создаю компоненты типа TMonitorSocket и TMonitorPCap. Обоим компонентам я устанавливаю процедуру, которая будет выполняться всякий раз, когда возникнет событие OnPacketEvent. Основной код этой процедуры приведен в соответствующем листинге. Как только TMonitorPCap инициализировался, нам сразу становится доступно свойство AdapterDescList, в котором нас уже дожидается список доступных сетевых адаптеров. Все найденные адаптеры я переношу в соответствующий ComboBox на форме:
cbxAdapters.Items.Assign(_monWinPCap.AdapterDescList);
Кстати, попробуй прямо сейчас протестировать наше приложение. Скомпиль и попробуй запустить - ComboBox’ы для хранения списка сетевых адаптеров и IP-адресов должны заполниться. Отрывок процедуры вывода пакета в окно лога
if (not cbFullData.Checked) and (PacketInfo.DataLen<96) then
SetLength(PacketInfo.DataBuf, 96);
_b := PacketInfo.DataBuf;
StringRemCntls (_b) ;
if PacketInfo.EtherProto = PROTO_IP then
begin
_srcIp := IPToStr (PacketInfo.AddrSrc) + ':' + IntToStr (PacketInfo.PortSrc);
_distip := IPToStr (PacketInfo.AddrDest) + ':' + IntToStr (PacketInfo.PortDest) ;
if PacketInfo.ProtoType = IPPROTO_ICMP then
_a := Format (sPL, [TimeToZStr(PacketInfo.PacketDT), GetIPProtoName (PacketInfo.ProtoType),
sPL, _srcIp, _distIp, lowercase (GetICMPType (PacketInfo.IcmpType)), PacketInfo.DataLen, _b])
else
begin
if (PacketInfo.DataLen = 0) then
_b := GetFlags(PacketInfo.TcpFlags);
_a := Format (sPL, [TimeToZStr (PacketInfo.PacketDT), GetIPProtoName (PacketInfo.ProtoType),
PacketInfo.PacketLen, _srcIp, _distIp, Lowercase (GetServiceNameEx (PacketInfo.PortSrc, PacketInfo.PortDest)),
PacketInfo.DataLen, _b]) ;
end;
end
else
begin
_a := Format (sPL, [TimeToZStr (PacketInfo.PacketDT), GetEtherProtoName (PacketInfo.EtherProto),
PacketInfo.PacketLen, MacToStr (PacketInfo.EtherSrc), MacToStr (PacketInfo.EtherDest),
'', PacketInfo.DataLen, _b]);
end;
reLog.Lines.Add(_a);
end;
После получения очередной порции данных управление будет передаваться процедуре GetPacket(). Код процедуры приведен во врезке. Перед выводом данных в лог нужно проверить состояние флажка cbFullData («Выводить полученные данные целиком»). Если он тру, тогда будем добавлять в лог все захваченные пакеты. Получаем сами данные:
_b := PacketInfo.DataBuf;
Теперь их необходимо как-то разобрать. Сначала я пропускаю данные через функцию StringRemCntls() из модуля magsubs1.pas. Эта функция заменяет управляющие коды пробелами. Это делать не обязательно, но желательно, поскольку читабельность полученной информации заметно возрастет.
Когда с управляющими кодами будет покончено, переходим к более детальному разбору. В зависимости от текущего типа данных я вызываю всем известную функцию format, которая форматирует строку в соответствии с шаблоном. Шаблон у меня определен в константе sPL:
sPl = '%-12s %-4s %4d %-20s < %-20s %-12s %4d %s';
Отформатированные данные добавляются в RichEdit. Наверняка ты заметил имена неизвестных тебе функций вроде IPToStr(), GetICMPType() и т.п. Все они описаны в модулях packhdrs.pas и magsubs1.pas и призваны облегчить процесс разработки. Например, функция IpToStr() позволяет выудить из структуры TInAddr IP-адрес в виде строки. Заглянув в модули, нашел интересные функции, которые могут пригодиться не только при программировании сниферов, но и при разработке других программ. Советую и тебе хорошенько покопаться в них.
Финальный тест
Возьми пример с нашего диска и посмотри код запуска монитора. Перепиши/скопируй его в свой проект и запусти, пора тестировать снифер в боевых условиях. Во время тестирования программы у меня на всю катушку работал torrent-клиент и Opera. Если хорошенько присмотреться к логу, то можно увидеть трафик браузера, который был поснифан. Можно считать тест оконченным и успешно пройденным.
Статья опубликована в журнале “Хакер” (http://xakep.ru). Март 2008 г.
Ссылка на журнал: http://goo.gl/8gOhDK