Снифер на C#

Перевод статьи: http://www.codeproject.com/Articles/17031/A-Network-Sniffer-in-C

Скачать: Исходный код с сайта codeproject

MJsniffer

В этом переводе будет показано как создать простой снифер, с парсингом IP, TCP, UDP и DNS пакетов на c#. Без использования сторонних библиотек типа SharpCap.

//Для захвата пакетов сокетом
//он должен иметь тип raw
//с протоколом IP
mainSocket = newSocket(AddressFamily.InterNetwork, SocketType.Raw,
                       ProtocolType.IP);

// Привязываем сокет к выбранному IP
mainSocket.Bind(newIPEndPoint(IPAddress.Parse(cmbInterfaces.Text),0));

//Устанавливаем опции у сокета
mainSocket.SetSocketOption(SocketOptionLevel.IP,  //Принимать только IP пакеты
                           SocketOptionName.HeaderIncluded, //Включать заголовок
                           true);                           

byte[] byTrue = newbyte[4]{1, 0, 0, 0};
byte[] byOut = newbyte[4];

//Socket.IOControl это аналог метода WSAIoctl в Winsock 2
mainSocket.IOControl(IOControlCode.ReceiveAll,  //SIO_RCVALL of Winsock
                     byTrue, byOut);

//Начинаем приём асинхронный приём пакетов
mainSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None,
                        newAsyncCallback(OnReceive), null);

Для захвата пакетов, мы используем raw(сырой) сокет и привязываем его к IP-адресу. После установки некоторых параметров для сокета, вызываем метод IOControl. Обратите внимание, что IOControl аналогичен методу Winsock2WSAIoctl. IOControlCode.ReceiveAll означает, что захватываться будут абсолютно все пакеты как входящие так и исходящие.

Второй параметр, передаваемый в IOControl с IOControlCode.ReceiveAll должен содержать именно такое значение массива byTrue (спасибо Леониду Молочному за это). Далее мы начинаем получать все пакеты асинхронно.

Анализ пакетов

IP датаграммы инкапсулируются TCP и UDP пакетами. Они также содержатся в  данных, передаваемыъ по протоколам прикладного уровня, таких как DNS, HTTP, FTP, SMTP, SIP и т.д. Таким образом, пакет TCP содержит в себе дейтаграммы IP, например:

+--------------+---------------+----------------------+
| IP заголовок | TCP заголовок |        Данные        |  
+--------------+---------------+----------------------+

Итак, первое, что мы должны сделать, это проанализировать IP-заголовок. Для этого создан урезанный класс classIPHeader. Комментарии описывают что и как происходит.

public classIPHeader 
{ 
  //Поля IP заголовка
  private byte byVersionAndHeaderLength; // Восемь бит для версии 
                                         // и длины 
  private byte byDifferentiatedServices; // Восемь бит для дифференцированного 
                                         // сервиса
  private ushort usTotalLength;          // 16 бит для общей длины 
  private ushort usIdentification;       // 16 бит для идентификатора
  private ushort usFlagsAndOffset;       // 16 бит для флагов, фрагментов 
                                         // смещения 
  private byte byTTL;                    // 8 бит для TTL (Time To Live) 
  private byte byProtocol;               // 8 бит для базового протокола
  private short sChecksum;               // 16 бит для контрольной суммы 
                                         //  заголовка 
  private uint uiSourceIPAddress;        // 32 бита для адреса источника IP 
  private uint uiDestinationIPAddress;   // 32 бита для IP назначения 

  //Конец полей IP заголовка   
  private byte byHeaderLength;             //Длина заголовка
  private byte[] byIPData = new byte[4096]; //Данные в дейтаграмме
  public IPHeader(byte[] byBuffer, int nReceived)
  {
    try
    {
    //Создаём MemoryStream для принимаемых данных
    MemoryStream memoryStream = newMemoryStream(byBuffer, 0, nReceived);

    //Далее создаем BinaryReader для чтения MemoryStream
    BinaryReader binaryReader = newBinaryReader(memoryStream);

    //Первые 8 бит содержат верисю и длину заголовка
    //считываем 8 бит = 1 байт
    byVersionAndHeaderLength = binaryReader.ReadByte();

    //Следующие 8 бит содержат дифф. сервис
    byDifferentiatedServices = binaryReader.ReadByte();

    //Следующие 8 бит содержат общую длину дейтаграммы
    usTotalLength = 
             (ushort) IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

    //16 байт для идентификатора
    usIdentification = 
              (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

    //8 бит для флагов, фрагментов, смещений
    usFlagsAndOffset = 
              (ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

    //8 бит для TTL
    byTTL = binaryReader.ReadByte();

    //8 бит для базового протокола
    byProtocol = binaryReader.ReadByte();

    //16 бит для контрольной суммы
    sChecksum = IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());

    //32 бита для IP источника
    uiSourceIPAddress = (uint)(binaryReader.ReadInt32());

    //32 бита IP назначения
    uiDestinationIPAddress = (uint)(binaryReader.ReadInt32());

    //Высчитываем длину заголовка
    byHeaderLength = byVersionAndHeaderLength;

    //Последние 4 бита в версии и длине заголовка содержат длину заголовка
    //выполняем простые арифметические операции для их извлечения
    byHeaderLength <<= 4;
    byHeaderLength >>= 4;

    //Умножаем на 4 чтобы получить точную длину заголовка
    byHeaderLength *= 4;

    //Копируем данные (которые содержат информацию в соответствии с типом 
    //основного протокола) в другой массив
    Array.Copy(byBuffer, 
               byHeaderLength, //копируем с конца заголовка
               byIPData, 0, usTotalLength - byHeaderLength);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "MJsniff", MessageBoxButtons.OK, 
                        MessageBoxIcon.Error);
    }
 }

}

Класс содержит элементы данных, соответствующих полям заголовка IP. Ознакомтесь с RFC 791 для подробного изучения  IP заголовка и его полей. Конструктор класса берет полученные байты и создает MemoryStream для полученной информации. Затем создает BinaryReader, чтобы считать данные байт за  байтом из MemoryStream. Также отметим, что данные, полученные от сети, находятся в форме с обратным порядком байтов, в связи с этим мы используем IPAddress. NetworkToHostOrder, чтобы исправить порядок байтов. Это должно быть сделано для всех элементов данных.

P.S. На основе этого примера буду писать программку для мониторинга сети. До этого писал с использованием SharpPcap, но необходимость установки WinPcap заставила попробовать изобрести велосипед 8)

5 Replies to “Снифер на C#”

  1. Не работает эта штука…флаги наверно не верны…она кажет только исходящие пакеты((

    1. Нет, нет.. у меня всё заработало прямо из коробки. На её основе пишу свое приложение. Одно из условий запуск от имени администратора, а также использование IPv4 с IPv6 работать не будет с этими флагами. На днях выложу свою программку, вдруг она только у меня работает 8)

    2. Программу нужно запускать только с правами администратора, иначе будет ошибка.

Добавить комментарий для Дмитрий Отменить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *