Персональный блог Игоря Антонова aka "spider_net"

Создание сниффера на Delphi


Рубрика: Delphi -> Программирование -> Журнал Хакер -> Статьи
Метки: | |
Просмотров: 14910
Создание сниффера на Delphi

Снифер – тулза номер один для хакера. Поснифать и проанализировать трафик, выудить из него пароли к почтовому ящику своего соседа Васи Пупкина – типичные задачи, которые возлагают на подобные программы. 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

    Универсальные компоненты

    Сегодня мы создадим снифер, который будет поддерживать работу с помощью двух технологий: используя драйвер WinPCap и RAW Socket. Для воплощения этой безумной идеи я решил найти какую-нибудь универсальную библиотеку компонентов. Признаюсь честно, вначале я даже не думал, что это возможно. Но немного погуглив, я наткнулся на очень интересный наборчик - Magenta Systems Internet Packet Monitoring Components.

    Эта полезная библиотека состоит из двух главных невизуальных компонентов: TMonitorPCap (взаимодействует с WinPCap) и TMonitorSocket (использует сырые сокеты). Оба компонента написаны достаточно хорошо, и глюков при их использовании замечено не было. Давай рассмотрим их возможности.

    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;

    Разработка сниффера на Delphi

    Теперь их необходимо как-то разобрать. Сначала я пропускаю данные через функцию 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

    Исходники сниффера на Delphi

    Оставьте комментарий!
    comments powered by HyperComments