
                          RAW-сокеты под Win2K/XP
                          -----------------------

     Тема  raw-сокетов  юзалась  многократно,  но то все были неправильные,
 юниксовые  сокеты. Эти же - наши, виндовые. В журнале 29A#6 уже писалось о
 виндовых  raw-сокетах  [1],  и как нахуярить снифак на оных. Поэтому тут я
 суммирую все, что узнал о спуфинге и сниффинге на raw-сокетах под виндами,
 только примеры будут не на асме, а на си++.
     Прежде всего скажу, что если у вас не 2K/XP, то дальше не читайте - не
 поможет.   Также   для   использования   raw-сокетов  требуются  админские
 привилегии, и если это вас останавливает, то тоже не надо мучаться.
     Затем  скажу,  что  надо  обзавестись  Borland  C++  5.5  -  это такой
 замечательный  коммандлайновый  компилер, что дистриб его занимает всего 8
 метров,  скачивается  откуда  угодно  и  ставится легко и быстро. С другой
 стороны, можно тестить raw-сокеты и на асме, но это не принесет счастья, и
 один  только  вселенский  сакс  ждет  вас,  когда вы будете делать на асме
 printf.

     Итак,  raw-сокеты  даны  нам  в  виде  набора апишек, импортируемых из
 WS2_32.DLL. Отнюдь не из WSOCK32.DLL. В чем же разница? Разница, как между
 хуем и пальцем. Если вы внимательно разберетесь в спуфинге на raw-сокетах,
 и  увидите то, что отличает их от обычных, а затем посмотрите в win2k'шный
 WSOCK32.DLL::setsockopt()  идой  -  то можно будет легко увидеть, в чем же
 фишка.

     Для  того, чтоб не иметь проблем с константами и описаниями функций, в
 начале сорца пишут так:

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>

     Затем,  как  вы  знаете,  надо  инициализировать  windows sockets api.
 Делается  это  WSAStartup'ом,  коему  впаривается  версия  именно  2.2 - в
 противном случае вас ожидает вселенский сакс.

  WSADATA WSAData;
  if ( WSAStartup(MAKEWORD(2,2), &WSAData) != 0 )
  {
    printf("ERROR:WSAStartup() error %i\n", WSAGetLastError());
    exit(0);
  }

     Теперь можно сделать raw-сокет. Вот он.

  SOCKET raw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
  if (raw_socket == INVALID_SOCKET)
  {
    printf("ERROR:socket(SOCK_RAW) error %i\n", WSAGetLastError());
    exit(0);
  }

     Далее  все  зависит  от  цели  - снифак мы пишем, спуфер, или даже все
 вместе.
                                   СПУФЕР
                                   ------

     Для  тех  кто  не  знает,  спуфер (spoofer) - это такая хрень, которая
 позволяет  вместо  своего  IP-шника  подставить  чужой.  А  в общем случае
 послать весь IP-пакет целиком. И это правильно.

     Для того, чтобы через созданный raw-сокет отправить raw ip-пакет, надо
 поставить  на этот сокет такую опцию, чтоб винда не создавала ip-заголовки
 у  посылаемых  пакетов, а попросту бы хуй забила на них и молча отправляла
 дальше в сеть. Вот как это делается:

  DWORD optval = 1;
  if ( setsockopt(raw_socket, IPPROTO_IP, IP_HDRINCL,
                  (char*)&optval, sizeof(optval)) == SOCKET_ERROR )
  {
    printf("ERROR:setsockopt(IP_HDRINCL) error %i\n", WSAGetLastError());
    exit(0);
  }

     А  дальше  можно  работать  как  с  обычным  UDP-пакетом,  посредством
 sendto(), только что в пакете будет еще и IP-заголовок.

     Пусть  готовый к отправке пакет, начинающийся с ip-header'а, находится
 в BYTE* buf, длиной int len.

  sockaddr_in addr;
  addr.sin_family      = AF_INET;
  addr.sin_port        = htons(0);           // пох
  addr.sin_addr.s_addr = *(DWORD*)&buf[16];  // packet.ip_header.dst_ip  (*)

  int res = sendto(raw_socket, buf, len, 0, (sockaddr*)&addr, sizeof(addr));
  if (res != len)
  {
    printf("ERROR:sendto()=%i, WSAGetLastError=%i\n", res,WSAGetLastError());
    exit(0);
  }

     (*)  И  тут  есть  одна  тонкость.  Винда пошлет пакет вовсе не на тот
 адрес, коий указан в поле dst ip-заголовка пакета. А пошлет винда пакет на
 тот  адрес,  который указан в структуре sockaddr, подаваемой в sendto(). В
 случае  одной  сетевухи  это  все одно и то же; в случае двух - может быть
 определенная разница.

                                  СНИФФЕР
                                  -------

     Для  тех  кто  не  знает, сниффер (sniffer) - это такая хрень, которая
 позволяет ловить все пакеты, хоторые проходят мимо машины.

     Для  того,  чтобы  ловить  пакеты,  надо  "висеть  на интерфейсе". Так
 говорят. Очень тяжело объяснить что это значит. На практике это выливается
 вот  во  что:  надо  выбрать один (а лучше все) из IP-адресов, присущих на
 данный  момент  машине,  где  работает  сниффер, потом взять этот IP-адрес
 вместе с созданным raw-сокетом, и запидарасить все это в функцию bind().

     Так что получим IP-адреса так, как это описано в [1]

  BYTE addrlist[1024];
  DWORD bytesret;
  int res = WSAIoctl(s, SIO_ADDRESS_LIST_QUERY, NULL, 0,
                     &addrlist, sizeof(addrlist), &bytesret, NULL, NULL);
  if (res == SOCKET_ERROR)
  {
    printf("WSAIoctl(SIO_ADDRESS_LIST_QUERY) error %i\n", WSAGetLastError());
    exit(0);
  }

     На всякий случай проверим, чтобы не вышло хуйни.

  DWORD addrcount = *(DWORD*)&addrlist[0];
  if (addrcount == 0)
  {
    printf("ERROR:no IP address found\n");
    exit(0);
  }

     Распечатаем все полученные IP-адреса.

  for(DWORD i=0; i<addrcount; i++)
    printf("IP=%s\n",
      inet_ntoa( ((sockaddr_in*)(*(DWORD*)&addrlist[4+i*8+0]))->sin_addr ));

     Теперь делаем bind() -- ассоциируем сокет с первым полученным IP-шником.

  sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port   = htons(0);
  addr.sin_addr   = ((sockaddr_in*)(*(DWORD*)&addrlist[4+0*8+0]))->sin_addr;

  if (bind(s, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR)
  {
    printf("ERROR:bind(IP=%s) error %i\n",
      inet_ntoa( ((sockaddr_in*)(*(DWORD*)&addrlist[4+0*8+0]))->sin_addr ),
      WSAGetLastError());
    exit(0);
  }

     Далее  сокет  переводится  в  режим принятия на себя всех пакетов. Это
 фича, начинающаяся с win2k, поэтому в инклюдниках ее может не быть.

#define SIO_RCVALL  0x98000001

  DWORD optval = 1;
  res = WSAIoctl(s, SIO_RCVALL,
                 &optval, sizeof(optval), 0,0,&bytesret,0,0);
  if (res == SOCKET_ERROR)
  {
    printf("ERROR:WSAIoctl(SIO_RCVALL) error %i\n", WSAGetLastError());
    exit(0);
  }

     После  этого  на  сокет  начинают приходить IP-пакеты со всей локалки.
 Получать их можно так же, как и обычные UDP-пакеты, командой recv(), с той
 лишь разницей, что что в них есть еще и IP-заголовок.

    static BYTE buf[10000];
    int len = recv(s, buf, sizeof(buf), 0);
    if ((len == SOCKET_ERROR) || (len == 0))
    {
      printf("ERROR:recv()=%i, WSAGetLastError=%i\n", len,WSAGetLastError());
      exit(0);
    }

     Далее идет разбор пакета, ну и все по плану.

                                 ПРИМЕНЕНИЯ
                                 ----------

     Самое  интересное заключается в том, что многие локальные файрволы при
 использовании  raw-сокетов  обламываются. По-видимому, их драйвера садятся
 на  какой-то  не  тот  источник данных - а значит, майкрософт их наебал. А
 может  быть,  в таких файрволах не предусмотрено то, что из машины выходит
 ip-пакет  с  совсем  неродным  src ip-шником. А может быть проблема в том,
 что  файрволы  портировали  с  NT 4, забыв о 2K-шных фичах. Хуй его знает,
 поди  разберись  во  всем.  Но  факт тот, что глупые файрволы выпускают из
 машины  любые  raw-пакеты. В сочетании со сниффингом, это дает возможность
 ремотного  управления  затрояненой тачкой, в том случае, если на нее после
 установки  трояна  поставили  файрвол. Естественно, TCP-соединение с такой
 машиной  уже  не  установишь,  и придется общаться по какому-нибудь левому
 протоколу, но это, я думаю, не проблема.

     Ну   и   широко   известные   применения   --   выгребание  email'ов и
 аккаунтов/пассвордов  посредством сниффинга, имперсонация других IP-шников
 в локалке, сканеры, флудеры, и все прочие счастья.

  See also:

  [1]  29a-6.004 -- Snorting coke (and packets) by GriYo / 29A

                                  (x) 2002
                           http://z0mbie.host.sk

                                   * * *
