Создание сниффера на Delphi
Рубрика: Delphi -> Программирование -> Журнал Хакер -> Статьи
Метки: delphi | sockets | программирование
Просмотров: 15445
Снифер – тулза номер один для хакера. Поснифать и проанализировать трафик, выудить из него пароли к почтовому ящику своего соседа Васи Пупкина – типичные задачи, которые возлагают на подобные программы. Etherscan Analyzer, EtherSnoop, Ettercap, ZxSniffer – хорошо зарекомендовавшие себя сниферы. Об их правильном использовании не раз говорилось в нашем журнале. Юзать эти проги, несомненно, круто, но намного круче написать свой 31337-й снифер, который будет обладать тем функционалом, который нужен именно тебе и только тебе!
Теория сниффера
Итак, начнем с определения. Снифер – это программа или аппаратное устройство для перехвата сетевого трафика. Аппаратные сниферы нас сегодня интересовать не будут, а программные мы рассмотрим подробнее.
Сниферы условно можно разделить по способу прослушивания сети. Прослушивание бывает активным и пассивным. Сниферы, использующие этот пассивное прослушивание, просто переводят сетевую карту в режим promiscuous (неразборчивый). В этом случае становится возможным получить все пакеты в сегменте Ethernet. Сразу хочу развеять возможно возникшие иллюзии. Все пакеты ты сможешь увидеть только в том случае, если сеть построена на так называемых хабах (hub). В сетях, где используются устройства типа свитча (switch), такие фокусы не пройдут. Почему? Секрет кроется в работе сетевого устройства. Хабы – довольно безграмотные жестянки, одним словом, двоечники. Все пришедшие данные с одного порта они передают на все остальные, то есть даже не задумываясь о том, на каком порту находится получатель, для которого эти данные, собственно, и были предназначены. Свитчи – это своего рода отличники. Они с немецкой дотошностью проверяют заголовки пришедших кадров данных и пересылают их в другой сегмент только в том случае, если в нем есть истинный получатель. Сниферы, применяющие активное прослушивания сети, более продвинуты. Для перехвата тяжелого трафика они используют разные способы. Наиболее известные из них:
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;
Разберем поля структуры более подробно:
От теории к практике
Пришло время закончить с теорией и перейти к реальной работе. Сейчас мы с тобой напишем самый настоящий снифер в, казалось бы, неприспособленной для этого среде. Запускай 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