Статья ASM для х86. (5.2.) WinSock – протокол WHOIS

В предыдущей части речь шла о портах..
Теперь поговорим о протоколах и начнём с самого простого WHOIS (nickName), который полностью развенчивает миф об анонимности в сети. Посредством сервисов Whois, начинающий разведчик сможет собрать информацию о любом домене, ..а если повезёт, и об его админе. Отмечу, что инфа о владельце является конфиденциальной и предоставляется регистратором только по запросу органов власти. Но есть и открытые данные, читать которые дозволено всем.

Суть в том, что если мы хотим купить домен в сети Интернет, то должны оставить свою/подноготную регистратору домена. Среди этих данных ФИО, адрес проживания, номер телефона, мэйл и т.д. По такой схеме "люди в чёрном" смогут отследить нас, если мы будем заниматься противозаконной деятельностью.

Основная проблема в том, что в глобальной сети доменов много, например: .ru, .com, .info, .net и собрать общую базу всех клиентов никак не получится. Поэтому у каждого домена свой регистратор, в геотермальный карман которого попадают только его клиенты. Значит если мы хотим собрать о ком-нибудь инфу, то должны запросить её не у кого-попало, а у конкретного регистратора, в поле зрения которого лежит этот домен. Например запрос mail.ru у регистратора .com – с треском провалится.

На серверах Whois в фоновом режиме работают "процессы-демоны", которые прослушивают сетевой порт 43 протокола TCP. Обнаружив входящий запрос на этом порту, сервер обрабатывает его, и возвращает клиенту текстовый пакет данных. Нам остаётся только вывести этот пакет на экран и всё. Протокол Whois описывает где говорится, что запрос к серверу с нашей стороны должен быть оформлен как всего одна строка с именем интересующего домена, и терминальной пары ,13,10 (конец строки и возврат каретки соответственно). Например на запрос вида "mail.ru",13,10 сервер Whois вернёт нам техническую информацию об этом домене. Как видим – всё предельно просто..

whs.png


Если мы хотим создать универсальное Whois-приложение, которое возвращало-бы инфу о доменах сразу всех регионов (а не о каком-то одном), то должны позаботится о том, чтобы по вводу юзера, на лету найти IP-адрес соответствующего регистратора, и только потом отправить ему запрос. Именно на этот программный модуль будет ложиться основная нагрузка. Значит должна быть база-адресов всех регистраторов, из которой мы вытащим один. Со-списком известных Whois-серверов для большинства региональных зон можно ознакомиться

Из огромного кол-ва вариантов решения этой проблемы, я выбрал самый простой..
Допустим юзер ввёл адрес mail.ru, а я беру только два последних символа из этой строки – в данном случае это "RU", которые определяют доменную зону адреса. У этого домена свой регистратор, имя которого заношу в свою базу. Если юзер введёт например google.com (где под домен выделяются уже три символа .com), то я всё-равно беру два последних и получаю маску "OM" для поиска имени сервера в своей базе. Базу оформляю так, что первые два символа в ней это маска, после которой идёт уже само имя Whois-сервера. Конечно-же способ "не фонтан" и вполне могут возникнуть коллизии (совпадения), но в большинстве случаях это работает.

Второе – мне нужна длинна одной записи в базе, чтобы переходить к следующей записи. Длину вычисляю по серверу, чьё имя самого длинное, а остальные имена в базе, дополняю до него нулями. Ниже представлена база, которая живёт в секции-данных программы. Здесь видно, что сервер регистратора доменов .com ожидает нас по адресу whois.verisign-grs.com, и это самое длинное имя из всех в моей базе. Если учесть 2-байтную маску и терминальный нуль в конце, то получаю длину одной записи =26 символов. Теперь, поставив указатель на начало базы сравниваю маску, и если она не совпадает, то к текущему указателю прибавляю 26 и перехожу на сл.строку и т.д. Счётчик(12) записей в базе предотвращает выход за её пределы:

C-подобный:
;//-------- База Whois-серверов ------------------------------
;//----------------------------------------------------------
count      dd  12                                       ; всего записей
servLen    dd  26                                       ; длина одной записи
whoisBase  db  'om whois.verisign-grs.com',0            ; .com
           db  'ro whois.registrypro.pro' ,0,01 dup(0)  ; .pro
           db  'iz whois.neulevel.biz'    ,0,04 dup(0)  ; .biz
           db  'fo whois.afilias.info'    ,0,04 dup(0)  ; .info
           db  'in whois.registry.in'     ,0,05 dup(0)  ; .in
           db  'ru whois.tcinet.ru'       ,0,07 dup(0)  ; .ru
           db  'by whois.cctld.by'        ,0,08 dup(0)  ; .by
           db  'uz whois.cctld.uz'        ,0,08 dup(0)  ; .uz
           db  'rg whois.pir.org'         ,0,09 dup(0)  ; .org
           db  'et whois.reg.ru'          ,0,10 dup(0)  ; .net
           db  'kz whois.nic.kz'          ,0,10 dup(0)  ; .kz
           db  'ua whois.ua'              ,0,14 dup(0)  ; .ua
;//------------------------------------------------------------

И последнее что нужно сделать коду, это получить IP-адрес Whois сервера, по его имени. Для этого имеется уже знакомая нам функция WinSock gethostbyname(), которая обращается к DNS-серверу сети Интернет и получает от него IP-адрес запрошенного сервера. Единственным аргументом этой функции является указатель на имя, который мы передадим ей из нашей базы. Если функция выполняется успешно, то в регистре EAX возвращает указатель на структуру hostent, где среди прочего есть и IP. В противном случае – получим от неё нуль. Пример ниже представляет собой утилиту Whois собственного приготовления:

C-подобный:
format   pe console
entry    start
include 'win32ax.inc'
;----------
.data
sAddr      sockaddr_in                ;
sData      WSADATA                    ;

mes0       db  13,10,' Whois Example v0.1 /com,net,org,ru,kz,ua/'
           db  13,10,' *****************************************'
           db  13,10,' Domain name : '              ,0
servName   db        ' Whois server: %s'            ,0
crlf       db  13,10,' *****************************************',13,10,10,0
pKey       db  13,10,' *****************************************'
           db  13,10,' Press any key for exit..'    ,0
error      db  13,10,' ERROR! Check network cable..',0
noServ     db  13,10,' ERROR! Bad domain..'         ,0

count      dd  12     ;// кол-во записей в базе ---------------
servLen    dd  26     ;// длина одной записи
whoisBase  db  'om whois.verisign-grs.com',0            ; .com
           db  'ro whois.registrypro.pro' ,0,01 dup(0)  ; .pro
           db  'iz whois.neulevel.biz'    ,0,04 dup(0)  ; .biz
           db  'fo whois.afilias.info'    ,0,04 dup(0)  ; .info
           db  'in whois.registry.in'     ,0,05 dup(0)  ; .in
           db  'ru whois.tcinet.ru'       ,0,07 dup(0)  ; .ru
           db  'by whois.cctld.by'        ,0,08 dup(0)  ; .by
           db  'uz whois.cctld.uz'        ,0,08 dup(0)  ; .uz
           db  'rg whois.pir.org'         ,0,09 dup(0)  ; .org
           db  'et whois.reg.ru'          ,0,10 dup(0)  ; .net
           db  'kz whois.nic.kz'          ,0,10 dup(0)  ; .kz
           db  'ua whois.ua'              ,0,14 dup(0)  ; .ua
;//------------------------------------------------------------
frmt       db  '%s',0                ; спецификатор для printf/scanf (строка)
s          dd  0                     ; под дескриптор сокета
server     dd  0                     ; под указатель на имя сервера из базы
whoisData  db  2048 dup(0)           ; буфер, для приёма от сервера Whois-данных
nameLen    dd  0                     ; длина запроса для передачи - требует recv()
name       db  128 dup(0)            ; сюда юзер введёт запрос типа: "mail.ru"

;//----------
.code
start:  cinvoke  printf,mes0           ; шапка и запрос на ввод
        cinvoke  scanf,frmt,name       ; принимаем юзерский ввод в буфер..

;// Оформляем ввод как запрос и вычисляем его длину
         mov     edi,name              ; указатель на имя жертвы (он-же запрос)
         mov     ecx,-1                ; счётчик на максимум
         xor     eax,eax               ; очистить EAX=0
         repne   scasb                 ; ищем AL в EDI..
         dec     edi                   ; нашли! Коррекция и он указывает на хвост
         mov     word[edi],0x0a0d      ; вставить в хвост CR/LF = 13,10 (оформить ввод как Whois-запрос)
         not     ecx                   ; инверсия битов в ECX
         inc     ecx                   ; теперь в ECX длина запроса для recv()
         mov     [nameLen],ecx         ; запомнить её в переменной..

;// Берём последние два символа ввода, и ищем их в своей базе
         mov     ax,[edi-2]            ; AX = маска (например "ru")
         mov     esi,whoisBase         ; ESI = указатель на начало базы
         mov     ecx,[count]           ; ECX = кол-во записей в ней
@find:   cmp     [esi],ax              ; сравнить первые 2-байта с АХ
         je      @ok                   ; если соппало..
         add     esi,[servLen]         ; иначе: указатель + длина одной записи
         loop    @find                 ; повторить ЕСХ-раз..

        cinvoke  printf,noServ         ; Облом! Сервера нет в нашей базе!
         jmp     @exit                 ; на выход с ошибкой.

@ok:     add     esi,3                 ; Нашли! Сместить указатель на начало имени
         mov     [server],esi          ; запомнить его в переменнной
        cinvoke  printf,servName,esi   ; покажем имя найденного Whois-сервера,
        cinvoke  printf,crlf           ;   ..и следом разделительную линию.

;// Полезный код!
;// Отправляем запрос конкретному Whois-серверу
         invoke  WSAStartup,0x0101,sData    ;
         invoke  gethostbyname,[server]     ; получить IP Whois-сервера, по его имени
         cmp     eax,0                      ; ошибка?
         jne     @ipok                      ; если нет..
        cinvoke  printf,error               ; иначе: ERROR - нет подключения к сети
         jmp     @exit                      ;

virtual  at      eax                        ; в EAX лежит указатель на структуру "hostent"
host     hostent                            ; определяем вирт.структуру с именем "host"
end      virtual                            ;

;// Здесь нужно заполнить адресный-пакет: IP, порт, протокол
;// и привязать этот пакет к сокету
@ipok:   mov     eax,[host.h_addr_list]        ; ищем поле IP в структуре
         mov     eax,[eax]                     ;   ^^^
         mov     eax,[eax]                     ;     ^^^
         mov     [sAddr.sin_addr],eax          ; отправляем его в пакет-адреса
         mov     [sAddr.sin_family],AF_INET    ; туда-же протокол
         invoke  htons,43                      ; Whois-порт в сетевой порядок
         mov     [sAddr.sin_port],ax           ; записать в пакет!

         invoke  socket,AF_INET,SOCK_STREAM,IPPROTO_TCP   ; создаём TCP-сокет
         mov     [s],eax                                  ; запомнить хэндл в переменной
         invoke  bind,eax,sAddr,16                        ; привязать адрес к сокету

         invoke  connect,[s],sAddr,16       ; пробуем соедениться с Whois-сервером..
         cmp     eax,-1                     ; прокол?
         jne     @send                      ; если всё ОК!
        cinvoke  printf,error               ; иначе: эррор и на выход..
         jmp     @exit                      ;

;// Всё готово! Отправляем запрос на выбранный сервер Whois,
;// и выводим все данные на консоль:
@send:   invoke  send,[s],name,[nameLen],0       ; --> отправить запрос
         invoke  recv,[s],whoisData,2048,0       ; <-- принять ответ в свой буфер
        cinvoke  printf,whoisData,frmt           ; вывести всю портянку на экран!

@exit:  cinvoke  printf,pKey             ;
        cinvoke  scanf,frmt,frmt+2       ; ждём клаву, чтобы осмотреться..
         invoke  ExitProcess,0           ;

;//--- Секция импорта  ----------------------
section '.idata' import data readable
library  kernel32,'kernel32.dll',\
         wsock32,'wsock32.dll',\
         msvcrt,'msvcrt.dll'
import   msvcrt,printf,'printf',scanf,'scanf'
include 'api\kernel32.inc'
include 'api\wsock32.inc'

whData.png


На скрине выше я не случайно выбрал Whois-сервер Казахстана – видимо регистратор этой зоны привык к порядку, т.к. позаботился о форматированном ответе. Большинство серверов плюют на такие мелочи, и вываливают инфу в виде зловонной кучи, что не способствует её зрительному восприятию от слова "никак". Один плюс ушёл в копилку kz..

Базы данных Whois-серверов, бывают централизованными и распределёнными. В первом случае один сервер содержит полную БД и отвечает на запросы, касающиеся всех регистраторов. По такой схеме построен Whois-сервер, например доменов .org и .ru. Во втором случае – центральный сервер не содержит полной БД и рекурсивно перенаправляет юзера на сервер соответствующего регистратора. По такой схеме работает домен .com.

Когда утилита умеет распознавать такое перенаправление, она сама запрашивает нужный сервер (как в примере выше), в противном случае юзеру приходится делать это вручную. В протоколе "nickName" (whois, хуиз) не предусмотрено различия между централизованной и распределённой моделей – конкретная реализация хранилища зависит только от регистратора.

Внимательный читатель мог заметить, что функция WinSock gethostbyname() каким-то образом вычисляет IP-адрес узла, приняв на грудь лишь его имя. На самом деле она обращается к DNS-серверу "Domain-Name-System", у которого и запрашивает IP. DNS-серверы имеют такие-же базы данных как и Whois, только в них хранится не подноготная владельца домена, а только IP-адреса.

Так, немного изменив алгоритм данного кода, мы можем создать себе базу IP-адресов всех интересущих нас узлов. Обычно такие базы требуются локальным серверам, которые мы устанавливаем на своей машине. Например, мой "Small Http" имеет встроенный DNS-сервер, для работы которому необходима подобная база. Только натравив DNS-сервер на его базу я смогу обращаться к своим клиентам по их именам. Иначе он будет требовать исключительно IP-адрес, что не всегда удобно.

В следующий раз рассмотрим более продвинутые приёмы, где нужно будет отправлять уже не тупые запросы, а оформленные специальным образом сообщения. По такому алго работает протоколы ICMP и ARP. Здесь уже взводятся определённые биты в заголовках пакетов, по которым принимающая стороны понимает, что от неё хотят. Другими словами, это уже работа на логическом уровне.
 

Hardreversengineer

Green Team
20.08.2019
49
28
BIT
14
У меня компилятор ругается на IPPROTO_TCP, что якобы это неизвестный символ. Я приравнял к нулю (ибо в функции сокет последняя переменная, вроде как, означает протокол, если выставить ноль, то система сама выберет нужный), но в итоге программа выдаёт ошибку и просит проверить интернет-кабель. Как исправить ?
 

Marylin

Mod.Assembler
Red Team
05.06.2019
326
1 451
BIT
696
чтобы целый файл не инклудить
Компилятор берёт с инклуд только то, что нужно текущему коду, а остальное не подключается в исходник и остаётся в инклуде. В примерах, которые я привожу, код начинается с инклуда win32ax.inc, и если посмотреть на его содержимое, то оказывается что он сам импортирует все имеющиеся у FASM'a инклуды, а компилятор потом выбирает из них только нужные ему константы и структуры.

Если пишешь код чисто для себя, то аргументы можно сразу определять в виде констант, как это сделали вы. Но в демонстрационных примерах лучше указывать имена, чтобы потом не было вопросов типа "А что означает 6" ??? Но согласен - работать будет в обоих случаях, только и ресурсов будет потреблять одинаково - как был этот "Whois.exe" 5К-байт, так и останется.
 
  • Нравится
Реакции: Hardreversengineer

Hardreversengineer

Green Team
20.08.2019
49
28
BIT
14
Компилятор берёт с инклуд только то, что нужно текущему коду, а остальное не подключается в исходник и остаётся в инклуде. В примерах, которые я привожу, код начинается с инклуда win32ax.inc, и если посмотреть на его содержимое, то оказывается что он сам импортирует все имеющиеся у FASM'a инклуды, а компилятор потом выбирает из них только нужные ему константы и структуры.

Если пишешь код чисто для себя, то аргументы можно сразу определять в виде констант, как это сделали вы. Но в демонстрационных примерах лучше указывать имена, чтобы потом не было вопросов типа "А что означает 6" ??? Но согласен - работать будет в обоих случаях, только и ресурсов будет потреблять одинаково - как был этот "Whois.exe" 5К-байт, так и останется.
Интересно. Я даже не знал, что берёт только то что нужно, боялся что будет "много лишнего".
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!