MASM32: Часть 3 — Брутальная бессердечность
Суббота, 11. Сентябрь 2010
Раздел: Assembler, Windows, автор: dx
Давно не писал новых статей по MASM, решил наконец-то продолжить цикл. Думаю, вы уже прочитали первые две статьи (эту и эту тоже) и разобрали их содержание. Пришло время поговорить о других возможностях MASM32. Возьмите исходный код из самой первой статьи, из той, в которой мы делали самое первое GUI-приложение, будем его использовать как базу для нового проекта.
В общем, сначала идея была проста... [click]
Но потом...
Программка будет сканировать все порты из указанного диапазона на заданном ip-адресе и выводить информацию в лог. Многопоточность делать не будем, но один поток всё равно создадим, чтобы при сканировании форма не висела.
Сначала я приведу RC-файл, который получился у меня. Можете его использовать:
#define MANIFEST 24 #define PortScanner 1000 #define IDC_IPADDR 1005 #define IDC_PORTLOW 1007 #define IDC_PORTHIGH 1009 #define TEST_BTN 1001 #define IDC_STOP 1003 #define IDC_CLEANLOG 1012 #define IDC_LOG 1010 #define EXIT_BTN 1002 #define IDC_STC1 1004 #define IDC_STC2 1006 #define IDC_STC3 1008 #define IDC_STC4 1011 #define IDC_EDT1 1013 #define IDC_STC5 1014 #define IDC_STC6 1015 #define IDC_OPENONLY 1016 #define IDR_XPMANIFEST1 1 #include "C:/masm32/include/RESOURCE.H" PortScanner DIALOGEX 6,7,272,91 CAPTION "Port Scanner" FONT 8,"MS Sans Serif",0,0,0 STYLE WS_VISIBLE|WS_CAPTION|WS_SYSMENU BEGIN CONTROL "",IDC_IPADDR,"SysIPAddress32",WS_CHILD|WS_VISIBLE|WS_TABSTOP,52,3,74,11 CONTROL "1",IDC_PORTLOW,"Edit",WS_CHILD|WS_VISIBLE|WS_TABSTOP|ES_NUMBER,52,16,38,11,WS_EX_CLIENTEDGE CONTROL "65535",IDC_PORTHIGH,"Edit",WS_CHILD|WS_VISIBLE|WS_TABSTOP|ES_NUMBER,112,16,38,11,WS_EX_CLIENTEDGE CONTROL "Начать!",TEST_BTN,"Button",WS_CHILD|WS_VISIBLE|WS_TABSTOP,6,60,54,13 CONTROL "Стоп",IDC_STOP,"Button",WS_CHILD|WS_VISIBLE|WS_DISABLED|WS_TABSTOP,66,60,54,13 CONTROL "Очистить",IDC_CLEANLOG,"Button",WS_CHILD|WS_VISIBLE|WS_TABSTOP,232,1,36,13 CONTROL "",IDC_LOG,"Edit",WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_TABSTOP|ES_READONLY|ES_MULTILINE,162,16,106,70,WS_EX_CLIENTEDGE CONTROL "Выход",EXIT_BTN,"Button",WS_CHILD|WS_VISIBLE|WS_TABSTOP,6,75,54,13 CONTROL "IP-адрес:",IDC_STC1,"Static",WS_CHILD|WS_VISIBLE,8,5,36,9 CONTROL "Порты от:",IDC_STC2,"Static",WS_CHILD|WS_VISIBLE,8,18,38,9 CONTROL "до",IDC_STC3,"Static",WS_CHILD|WS_VISIBLE,96,18,10,9 CONTROL "Лог сканирования:",IDC_STC4,"Static",WS_CHILD|WS_VISIBLE,162,3,70,9 CONTROL "500",IDC_EDT1,"Edit",WS_CHILD|WS_VISIBLE|WS_TABSTOP|ES_NUMBER,82,31,38,11,WS_EX_CLIENTEDGE CONTROL "Таймаут коннекта:",IDC_STC5,"Static",WS_CHILD|WS_VISIBLE,8,33,70,9 CONTROL "ms",IDC_STC6,"Static",WS_CHILD|WS_VISIBLE,124,33,10,9 CONTROL "Логировать только открытые порты",IDC_OPENONLY,"Button",WS_CHILD|WS_VISIBLE|WS_TABSTOP|BS_AUTOCHECKBOX,6,48,140,9 END IDR_XPMANIFEST1 MANIFEST "xpmanifest.xml"
А теперь, как обычно, я приведу полный исходный код проекта, а потом его по частям прокомментирую.
.386 .model flat, stdcall option casemap :none include \masm32\include\windows.inc include \masm32\macros\macros.asm uselib kernel32, user32, masm32, comctl32, ws2_32 WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD AddLog PROTO :DWORD,:DWORD AddTextLog PROTO :DWORD Check PROTO :DWORD EnableControls PROTO :DWORD PortScanner = 1000 TEST_BTN = 1001 EXIT_BTN = 1002 IDC_STOP = 1003 IDC_IPADDR = 1005 IDC_PORTLOW = 1007 IDC_PORTHIGH = 1009 IDC_LOG = 1010 IDC_CLEANLOG = 1012 IDC_TIMEOUT = 1013 IDC_OPENONLY = 1016 fd_struct STRUCT fd_count dd ? ssock dd ? fd_struct ENDS .data checking db 0 err db "Ошибка", 0 .data? hInstance dd ? hWnd dd ? icce INITCOMMONCONTROLSEX <> ip dd ? porthigh dd ? portlow dd ? stopper db ? timeout dd ? openonly db ? .code start PROC LOCAL WSAStruct :WSADATA invoke WSAStartup, 0101h, addr WSAStruct .if eax != 0 invoke MessageBox, 0, chr$("WSAStartup - ошибка"), offset err, MB_ICONERROR invoke ExitProcess,eax ret .endif mov icce.dwSize, SIZEOF INITCOMMONCONTROLSEX mov icce.dwICC, ICC_DATE_CLASSES or \ ICC_INTERNET_CLASSES or \ ICC_PAGESCROLLER_CLASS or \ ICC_COOL_CLASSES invoke InitCommonControlsEx, offset icce invoke GetModuleHandle, NULL mov hInstance, eax invoke DialogBoxParam, hInstance, PortScanner, 0, offset WndProc, 0 call WSACleanup invoke ExitProcess,eax ret start ENDP WndProc proc hWin :DWORD, uMsg :DWORD, wParam :DWORD, lParam :DWORD switch uMsg case WM_INITDIALOG m2m hWnd, hWin invoke SendMessage, hWin, WM_SETICON, 1, FUNC(LoadIcon, NULL, IDI_ASTERISK) invoke SendDlgItemMessage, hWin, IDC_LOG, EM_SETLIMITTEXT, -1, 0 invoke SendDlgItemMessage, hWin, IDC_PORTLOW, EM_SETLIMITTEXT, 5, 0 invoke SendDlgItemMessage, hWin, IDC_PORTHIGH, EM_SETLIMITTEXT, 5, 0 invoke SendDlgItemMessage, hWin, IDC_TIMEOUT, EM_SETLIMITTEXT, 7, 0 case WM_COMMAND switch wParam case TEST_BTN invoke SendDlgItemMessage, hWin, IDC_IPADDR, IPM_GETADDRESS, 0, offset ip .if eax != 4 invoke MessageBox, hWin, chr$("Введите IP-адрес"), offset err, MB_ICONEXCLAMATION xor eax, eax ret .endif invoke GetDlgItemInt, hWin, IDC_PORTLOW, 0, FALSE .if eax < 1 || eax > 65535 invoke MessageBox, hWin, chr$("Неверно введен начальный номер порта."), offset err, MB_ICONEXCLAMATION xor eax, eax ret .endif mov portlow, eax invoke GetDlgItemInt, hWin, IDC_PORTHIGH, 0, FALSE .if eax < 1 || eax > 65535 || portlow > eax invoke MessageBox, hWin, chr$("Неверно введен конечный номер порта"), offset err, MB_ICONEXCLAMATION xor eax, eax ret .endif mov porthigh, eax invoke GetDlgItemInt, hWin, IDC_TIMEOUT, 0, FALSE .if eax < 10 invoke MessageBox, hWin, chr$("Слишком маленький таймаут"), offset err, MB_ICONEXCLAMATION xor eax, eax ret .endif mov timeout, eax invoke SendDlgItemMessage, hWin, IDC_OPENONLY, BM_GETCHECK, 0, 0 .if eax == BST_CHECKED mov openonly, 1 .else mov openonly, 0 .endif invoke EnableControls, 0 mov stopper, 0 invoke CreateThread, 0, 10240, offset Check, 0, 0, 0 .if eax>0 mov checking, 1 invoke CloseHandle, eax .else invoke MessageBox, hWin, chr$("Не удалось создать поток"), offset err, MB_ICONERROR invoke EnableControls, 1 .endif case IDC_CLEANLOG invoke SetDlgItemText, hWin, IDC_LOG, chr$(0) case IDC_STOP mov stopper, 1 case EXIT_BTN jmp exit_program endsw case WM_CLOSE .if checking == 1 invoke MessageBox, hWin, chr$("Завершите проверку портов перед выходом."), offset err, MB_ICONEXCLAMATION xor eax, eax ret .endif exit_program: invoke EndDialog, hWin, 0 endsw xor eax, eax ret WndProc ENDP Check PROC param :DWORD LOCAL SocketAddress :sockaddr_in LOCAL sock :DWORD LOCAL iMode :DWORD LOCAL fds_r :fd_struct LOCAL fds_w :fd_struct LOCAL fds_e :fd_struct LOCAL tv :timeval mov tv.tv_sec, 0 mov tv.tv_usec, 0 mov iMode, 1 invoke AddTextLog, chr$("Сканирование начато", 13, 10) invoke htonl, ip mov SocketAddress.sin_addr, eax mov SocketAddress.sin_family, AF_INET _testnext: cmp stopper, 1 je _end invoke socket, AF_INET, SOCK_STREAM, IPPROTO_TCP .if eax == INVALID_SOCKET invoke AddTextLog, chr$("ошибка создания сокета", 13, 10) jmp _end .endif mov sock, eax invoke ioctlsocket, sock, FIONBIO, addr iMode .if eax == SOCKET_ERROR invoke closesocket, sock invoke AddTextLog, chr$("Не удалось перевести сокет в неблокирующий режим", 13, 10) jmp _end .endif invoke htons, portlow mov SocketAddress.sin_port, ax invoke connect, sock, addr SocketAddress, sizeof sockaddr_in invoke Sleep, timeout mov fds_r.fd_count, 1 mov fds_w.fd_count, 1 mov fds_e.fd_count, 1 m2m fds_r.ssock, sock m2m fds_w.ssock, sock m2m fds_e.ssock, sock invoke select, 0, addr fds_r, addr fds_w, addr fds_e, addr tv invoke __WSAFDIsSet, sock, addr fds_r push eax invoke __WSAFDIsSet, sock, addr fds_w pop ebx .if eax == 0 && ebx == 0 .if openonly == 0 invoke AddLog, portlow, 0 .endif jmp __next .endif invoke __WSAFDIsSet, sock, addr fds_e .if eax != 0 .if openonly == 0 invoke AddLog, portlow, 0 .endif .else invoke AddLog, portlow, 1 .endif __next: inc portlow invoke closesocket, sock mov eax, porthigh cmp portlow, eax jbe _testnext _end: invoke AddTextLog, chr$("Сканирование завершено", 13, 10) invoke EnableControls, 1 mov checking, 0 invoke ExitThread,0 ret Check ENDP AddLog PROC port :DWORD, opened :DWORD LOCAL buf [20] :BYTE .if opened == 1 invoke wsprintf, addr buf, chr$("%u открыт", 13, 10), port .else invoke wsprintf, addr buf, chr$("%u закрыт", 13, 10), port .endif invoke AddTextLog, addr buf ret AddLog ENDP AddTextLog PROC xstr :DWORD invoke SendDlgItemMessage, hWnd, IDC_LOG, EM_SETSEL, -2, -2 invoke SendDlgItemMessage, hWnd, IDC_LOG, EM_REPLACESEL, 0, xstr ret AddTextLog ENDP EnableControls PROC en :DWORD invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_IPADDR), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_PORTLOW), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_PORTHIGH), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_CLEANLOG), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, TEST_BTN), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, EXIT_BTN), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_TIMEOUT), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_OPENONLY), en xor en, 1 invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_STOP), en ret EnableControls ENDP end start
Самое начало пояснять не буду - вы все уже и так знаете: объявление необходимых директив, подключение файлов (среди которых добавился ws2_32 для работы с сокетами), объявление прототипов процедур, которые я опишу дальше, и объявление констант из rc-файла. Далее идет объявление структуры fd_struct, про нее я тоже расскажу дальше. В любом случае, теперь вы знаете, как объявляются структуры в MASM32.
Дальше начинается секция данных и неинициализированных данных:
.data checking db 0 ;эта переменная будет указывать, ;происходит ли проверка портов в данный ;момент времени, или нет err db "Ошибка", 0 ;заголовок сообщений об ошибках .data? hInstance dd ? ;указатель на модуль ;нашего исполняемого файла hWnd dd ? ;хендл нашего окна icce INITCOMMONCONTROLSEX <> ip dd ? ;ip-адрес, на котором будем ;производить сканирование портов porthigh dd ? ;максимальный порт, до которого ;будем сканировать portlow dd ? ;минимальный порт, от которого начнем ;сканирование stopper db ? ;эта переменная будет индикатором ;для остановки потока сканирования, про нее ;я еще немного расскажу дальше timeout dd ? ;здесь будем хранить таймаут коннекта ;к порту openonly db ? ;логировать только открытые порты или все
Собственно, а как же мы будем производить сканирование? Пользователь вводит ip-адрес, диапазон портов для сканирования и жмет старт, после чего программа создает поток, который по очереди ко всем портам пытается подключиться (соответственно, сканируем только TCP-порты). Если подключение удалось - порт открыт, если нет - закрыт.
Теперь дальше к коду. Инициализацию я обернул в процедуру, и здесь приходит время познакомиться с локальными переменными. Вы, наверное, уже знаете, что во многих языках программирования в функциях можно создавать локальные переменные, которые после выхода из функции удаляются, так как хранятся в стеке. MASM не является исключением.
.code start PROC ;процедура инициализации (точка входа) LOCAL WSAStruct :WSADATA ;вот она - локальная ;переменная типа WSADATA с именем WSAStruct ;структура WSADATA объявлена в файле ws2_32.inc, ;который лежит в папке masm32/include, и который мы ;с вами подключили в самом начале. Можете ;его открыть и посмотреть, что в этой структуре ;В любом случае, инициализировать нам ее не надо, ;она лишь используется для инициализации системы ;winsock в программе: invoke WSAStartup, 0101h, addr WSAStruct ;вот так ;0101h - это версия winsock. Мы используем winsock 1.1 ;можно использовать и 2.2 (1010h), это не столь важно ;в этом примере. Подробнее - читайте msdn .if eax != 0 ;если какая-то ошибка invoke MessageBox, 0, chr$("WSAStartup - ошибка"), offset err, MB_ICONERROR invoke ExitProcess, eax ;то выйдем ret .endif ;инициализация контролов mov icce.dwSize, SIZEOF INITCOMMONCONTROLSEX mov icce.dwICC, ICC_DATE_CLASSES or \ ICC_INTERNET_CLASSES or \ ICC_PAGESCROLLER_CLASS or \ ICC_COOL_CLASSES invoke InitCommonControlsEx, offset icce invoke GetModuleHandle, NULL mov hInstance, eax ;создаем окно invoke DialogBoxParam, hInstance, PortScanner, 0, offset WndProc, 0 ;перед выходом освободим ресурсы winsock call WSACleanup invoke ExitProcess, eax ret start ENDP
Далее идет процедура обработки оконных сообщений:
WndProc proc hWin :DWORD, uMsg :DWORD, wParam :DWORD, lParam :DWORD switch uMsg case WM_INITDIALOG m2m hWnd, hWin invoke SendMessage, hWin, WM_SETICON, 1, FUNC(LoadIcon, NULL, IDI_ASTERISK) invoke SendDlgItemMessage, hWin, IDC_LOG, EM_SETLIMITTEXT, -1, 0 invoke SendDlgItemMessage, hWin, IDC_PORTLOW, EM_SETLIMITTEXT, 5, 0 invoke SendDlgItemMessage, hWin, IDC_PORTHIGH, EM_SETLIMITTEXT, 5, 0 invoke SendDlgItemMessage, hWin, IDC_TIMEOUT, EM_SETLIMITTEXT, 7, 0
При инициализации окна мы теперь не только устанавливаем его иконку, но и ограничиваем длины полей. Для поля лога снимаем ограничение на максимальную длину (передаем в оконном сообщении EM_SETLIMITTEXT число -1), а у других полей ставим разумные ограничения.
Теперь - код, который выполняется при нажатии на кнопку "Начать!":
case WM_COMMAND
switch wParam
case TEST_BTN
invoke SendDlgItemMessage, hWin, IDC_IPADDR, IPM_GETADDRESS, 0, offset ip ;получаем
;ip-адрес из поля ввода IP-адресов с помощью специального
;оконного сообщения
.if eax != 4 ;если введено меньше, чем четыре октета
invoke MessageBox, hWin, chr$("Введите IP-адрес"), offset err, MB_ICONEXCLAMATION
xor eax, eax
ret ;выходим из процедуры
.endif
;получаем начальный номер порта
invoke GetDlgItemInt, hWin, IDC_PORTLOW, 0, FALSE
.if eax < 1 || eax > 65535 ;и проверяем его
invoke MessageBox, hWin, chr$("Неверно введен начальный номер порта."), offset err, MB_ICONEXCLAMATION
xor eax, eax
ret
.endif
mov portlow, eax ;запоминаем
;получаем конечный номер порта
invoke GetDlgItemInt, hWin, IDC_PORTHIGH, 0, FALSE
.if eax < 1 || eax > 65535 || portlow > eax ;и снова проверяем
invoke MessageBox, hWin, chr$("Неверно введен конечный номер порта"), offset err, MB_ICONEXCLAMATION
xor eax, eax
ret
.endif
mov porthigh, eax ;и запоминаем
;наконец, получаем значение таймаута подключения
invoke GetDlgItemInt, hWin, IDC_TIMEOUT, 0, FALSE
.if eax < 10 ;и небольшая проверка
invoke MessageBox, hWin, chr$("Слишком маленький таймаут"), offset err, MB_ICONEXCLAMATION
xor eax, eax
ret
.endif
mov timeout, eax ;запоминаем его
;смотрим, не установлена ли опция "Логировать только открытые порты"
;с помощью оконного сообщения BM_GETCHECK
invoke SendDlgItemMessage, hWin, IDC_OPENONLY, BM_GETCHECK, 0, 0
.if eax == BST_CHECKED ;и если галочка установлена
mov openonly, 1 ;то запоминаем 1
.else
mov openonly, 0 ;если нет, то 0
.endif
invoke EnableControls, 0 ;блокируем некоторые элементы управления
;эта процедура определена в самом конце кода, я ее потом опишу
mov stopper, 0 ;сбрасываем флаг остановки потока
;создаем поток, который будет выполнять процедуру Check
;советую заглянуть в msdn, чтобы понять параметры
;функции CreateThread
invoke CreateThread, 0, 10240, offset Check, 0, 0, 0
.if eax>0 ;если все ОК
mov checking, 1 ;устанавливаем флаг, говорящий о том,
;что идет проверка
;ну и закрываем хендл потока - он нам больше не нужен,
;только память занимает
invoke CloseHandle, eax
.else ;если ошибка
invoke MessageBox, hWin, chr$("Не удалось создать поток"), offset err, MB_ICONERROR
invoke EnableControls, 1
.endifТеперь - несколько обработчиков нажатий других кнопок:
case IDC_CLEANLOG ;очистка поля лога ;с помощью функции SetDlgItemtext устанавливаем ;пустой текст в поле лога (chr$(0)). invoke SetDlgItemText, hWin, IDC_LOG, chr$(0) case IDC_STOP ;если пользователь нажал Стоп при проверке ;то устанавливаем флаг остановки потока, чтобы поток ;узнал, что его хотят остановить mov stopper, 1 case EXIT_BTN ;выход, тут все как в первом уроке jmp exit_program endsw
Здесь я должен сказать немного больше о переменной типа байт stopper, которую мы устанавливаем в 1, когда пользователь хочет остановить проверку портов раньше времени. Дело в том, что мы могли бы просто убить поток с помощью WinAPI-функции TerminateThread (для этого нам не следовало закрывать хендл потока с помощью CloseHandle), но это не совсем корректный, хоть и быстрый, путь. На MSDN перечислены проблемы, которые могут возникнуть при таком убийстве потока. Мы будем использовать переменную stopper, чтобы сообщить потоку, что мы хотим его завершить, и он сам корректно завершит своё выполнение, увидем, что stopper выставлена в единицу.
Теперь еще некоторые изменения в процедуре обработки оконных сообщений:
case WM_CLOSE
.if checking == 1 ;если проверка в данный момент времени идет
;скажем пользователю, чтобы завершил ее, и только потом
;завершал работу программы
invoke MessageBox, hWin, chr$("Завершите проверку портов перед выходом."), offset err, MB_ICONEXCLAMATION
xor eax, eax
ret
.endif
exit_program:
invoke EndDialog, hWin, 0
endsw
xor eax, eax
ret
WndProc ENDPА теперь перейдем к разбору процедуры, которая будет выполняться в отдельном потоке. Это - самая сложная часть, и вы сможете действительно гордиться собой, если разберетесь, как она работает.
Check PROC param :DWORD ;начало процедуры ;объявление локальных переменных LOCAL SocketAddress :sockaddr_in LOCAL sock :DWORD LOCAL iMode :DWORD LOCAL fds_r :fd_struct LOCAL fds_w :fd_struct LOCAL fds_e :fd_struct LOCAL tv :timeval mov tv.tv_sec, 0 ;инициализация некоторых переменных mov tv.tv_usec, 0 mov iMode, 1 invoke AddTextLog, chr$("Сканирование начато", 13, 10) ;говорим о начале сканирования
Что же произошло в этом кусочке кода? Во-первых, мы объявили начало процедуры. У нее есть один входной параметр (я назвал его param). Этот параметр может пригодиться, если мы из главного потока, который создает вторичный поток, этому самому вторичному потоку хотим передать какую-то информацию. Например, его номер. Мы это использовать не будем, потому что поток у нас всего один, и передавать нам в него ничего не требуется. Если вы уже заходили на msdn и читали описание функции CreateThread, то вы уже в курсе, откуда берется этот параметр.
Далее мы объявили несколько локальных переменных - первая socketAddress типа sockaddr_in (эта структура опять-таки объявлена в masm32/include/ws2_32.inc), используется для подключения сокета. Далее sock - в ней мы будем хранить хендл сокета. Переменная iMode, в которую мы записали 1, будет использоваться для перевода сокета в неблокирующий режим (потом поясню, что это такое и зачем). Далее идут три переменные одинакового типа fd_struct (помните, это та структура, которую мы определили в самом начале исходного кода?). Они будут использоваться для определения, открыт ли порт. Далее идет переменная tv типа timeval, в поля которой мы записываем нули. Эта переменная служит для хранения времени в секундах и миллисекундах для задания таймаута функции select (опять-таки, поясню дальше, зачем).
Идем дальше.
;здесь мы приводим ip-адрес в вид, ;необходимый для функции connect invoke htonl, ip mov SocketAddress.sin_addr, eax ;и записываем ;его в структуру ;здесь мы пишем AF_INET (других значений не бывает) mov SocketAddress.sin_family, AF_INET ;начало цикла проверки портов _testnext: cmp stopper, 1 ;вот тут мы смотрим, а не ;хотят ли остановить поток? ;то есть сравниваем stopper с единицей je _end ;и если хотят, то выходим из цикла ;je - "jump equal" - перейти, если равно ;создаем сокет TCP invoke socket, AF_INET, SOCK_STREAM, IPPROTO_TCP .if eax == INVALID_SOCKET ;если ошибка - завершаем проверку invoke AddTextLog, chr$("ошибка создания сокета", 13, 10) jmp _end .endif mov sock, eax ;запоминаем хендл сокета
В этом кусочке должно быть все понятно, а вот дальше идет более сложный момент:
invoke ioctlsocket, sock, FIONBIO, addr iMode .if eax == SOCKET_ERROR invoke closesocket, sock invoke AddTextLog, chr$("Не удалось перевести сокет в неблокирующий режим", 13, 10) jmp _end .endif
Здесь мы переводим сокет в неблокирующий режим. Зачем это надо? По умолчанию созданный сокет будет работать в блокирующем режиме. То есть, все функции вроде connect, send, recv будут блокировать выполнение нашей программы до тех пор, пока не завершатся. Нас такое положение дел не устраивает, ведь мы хотим возможность задавать таймаут для connect! Поэтому нам необходим неблокирующий режим, в котором функции выполняются и не блокируют нашу программу, сразу возвращая управление.
invoke htons, portlow mov SocketAddress.sin_port, ax invoke connect, sock, addr SocketAddress, sizeof sockaddr_in invoke Sleep, timeout
Здесь мы переводим в нужный вид порт, который содержится в переменной portlow (т.е. начальное значение) и тоже записываем его в структуру SocketAddress. После чего делаем попытку подключить сокет, используя только что заполненную структуру. Потом идет вызов функции Sleep, которая приостанавливает выполнение потока на заданное число миллисекунд (а задает его пользователь). Функция connect выполнение потока не блокирует, потому что у нас активирован неблокирующий режим, а тихо работает себе в фоне. Но вот как же узнать, подключился ли сокет спустя заданное время, или нет? Пожалуй, это самый хитрый момент программы.
mov fds_r.fd_count, 1 mov fds_w.fd_count, 1 mov fds_e.fd_count, 1 m2m fds_r.ssock, sock m2m fds_w.ssock, sock m2m fds_e.ssock, sock invoke select, 0, addr fds_r, addr fds_w, addr fds_e, addr tv invoke __WSAFDIsSet, sock, addr fds_r push eax invoke __WSAFDIsSet, sock, addr fds_w pop ebx .if eax == 0 && ebx == 0 .if openonly == 0 invoke AddLog, portlow, 0 .endif jmp __next .endif invoke __WSAFDIsSet, sock, addr fds_e .if eax != 0 .if openonly == 0 invoke AddLog, portlow, 0 .endif .else invoke AddLog, portlow, 1 .endif __next:
Пришло время поговорить о структуре fd_struct, которая была определена в начале кода. Эта структура, вообще говоря, имеет произвольный размер, и используется функцией select для определения, какие из сокетов, указанные в этой структуре, готовы к чтению/записи или вернули ошибку. Функция select используется как раз в неблокирующем режиме, потому что позволяет приостановить выполнение до тех пор, пока статус какого-либо из указанных в структурах сокетов не изменится. Время ожидания можно менять, его мы задали в переменной tv - по нулям. То есть, в нашем случае функция select проверит, какие из сокетов, заданные в структуре fds_r, готовы к чтению, какие из сокетов из fds_r готовы к записи, и какие из сокетов из fds_e вернули ошибку. В этих структурах всего по одному сокету (он у нас и есть один), поэтому я и сделал частный случай структуры fd_set (она описана на msdn) под единственный сокет, и в счетчик сокетов мы загружаем единицу. Блокироваться выполнение потока не будет, потому что, как я уже сказал, таймаут задан как 0. Конечно же, вам следует прочитать описание функции select и на msdn, чтобы все полностью встало на свои места.
Еще пара слов о макросе m2m (и не помню уже, вроде бы не пояснял его). Команда mov поддерживает формы
mov [регистр], [память]
mov [память], [регистр]
mov [регистр], [число]
mov [память], [число]
mov [регистр], [регистр]
Но вот формы mov [память], [память] не существует, поэтому, чтобы загрузить значение из памяти в память, следует воспользоваться либо регистром, либо стеком. То есть, писать либо "push memory1; pop memory2", либо "mov eax, memory1; mov memory2, eax". Макрос m2m реализует первую операцию (точнее, две операции) просто ради того, чтобы меньше писать. Макрос mrm реализует вторые две операции. Это уж на ваш вкус, что использовать для пересылки данных из памяти в память. Но нужно помнить, что макрос mrm не сохраняет значение регистра eax.
Так, ладно. Самая задница позади, и можно немножко расслабиться.
invoke __WSAFDIsSet, sock, addr fds_r push eax invoke __WSAFDIsSet, sock, addr fds_w pop ebx .if eax == 0 && ebx == 0 .if openonly == 0 invoke AddLog, portlow, 0 .endif jmp __next .endif invoke __WSAFDIsSet, sock, addr fds_e .if eax != 0 .if openonly == 0 invoke AddLog, portlow, 0 .endif .else invoke AddLog, portlow, 1 .endif __next:
После вызова select все три структуры (fds_r, fds_w, fds_e) будут содержать информацию о сокетах, пригодных для чтения, записи, и о возвративших ошибку соответственно. Далее идут проверки - если наш сокет не присутствует в структурах fds_r и fds_w, то есть не пригоден для записи и чтения, значит, он не подключен, т.е., порт закрыт. После этого идет проверка, не вернул ли сокет ошибку (не содержится ли он в структуре fds_e). Если вернул - опять-таки сокет не подключен. И только в том случае, если все эти условия остались невыполненными, сокет можно считать подключенным и порт, соответственно, открытым. Функция __WSAFDIsSet как раз проверяет наличие нужного сокета в заданной структуре.
inc portlow ;увеличиваем минимальное значение порта на единицу ;закрываем сокет и освобождаем его хендл invoke closesocket, sock mov eax, porthigh cmp portlow, eax ;сравниваем максимальное значение порта с минимальным jbe _testnext ;если оно меньше или равно - переходим на начало цикла ;(jbe = "jump below equal" = "переход, если ниже или равно") ;(сравнение беззнаковое. Если бы мы хотели знаковое, следовало бы использовать ;jle - "jump lower equal" = "переход, если меньше или равно"
Почему мы просто не написали cmp portlow, porthigh? Потому что инструкция cmp, как и mov, не поддерживает форму cmp [память], [память], пришлось воспользоваться регистром eax.
_end: ;конец проверки invoke AddTextLog, chr$("Сканирование завершено", 13, 10) invoke EnableControls, 1 ;разблокируем элементы управления mov checking, 0 ;говорим о том, что проверка завершена invoke ExitThread,0 ;завершаем поток ret Check ENDP
Следует отметить, что поток нужно завершать именно функцией ExitThread. Просто инструкция ret здесь прокатит, но это не слишком корректное решение, так как не все ресурсы будут освобождены.
Ну как, мозги уже кипят? Ничего, осталось разобрать всего несколько вспомогательных функций, которые используются в нашей программе.
;эта процедура добавляет в лог запись о том, открыт ли ;порт port или нет AddLog PROC port :DWORD, opened :DWORD LOCAL buf [20] :BYTE ;локальная переменная ;массив байт из 20 штук .if opened == 1 ;если порт открыт invoke wsprintf, addr buf, chr$("%u открыт", 13, 10), port .else ;если нет invoke wsprintf, addr buf, chr$("%u закрыт", 13, 10), port .endif invoke AddTextLog, addr buf ;вызываем процедуру записи в лог ret AddLog ENDP ;процедура записи текста в лог AddTextLog PROC xstr :DWORD ;она просто шлет текстовому полю лога два оконных сообщения ;первое ставит курсор на позицию "конец текста" ;в msdn есть запись ;"If the start is 0 and the end is –1, all the text in the edit control is selected." ;"If the start is –1, any current selection is deselected." ;поэтому, чтобы перейти в самый конец текста, ставим -2. invoke SendDlgItemMessage, hWnd, IDC_LOG, EM_SETSEL, -2, -2 ;второе сообщение - вставка текста на позицию "конец текста", ;на которую мы и перешли invoke SendDlgItemMessage, hWnd, IDC_LOG, EM_REPLACESEL, 0, xstr ret AddTextLog ENDP
EnableControls PROC en :DWORD ;процедура, блокирующая ;или разблокирующая некоторые контролы на нашей форме ;в зависимости от параметра "en" либо блокируем, либо разблокируем ;некоторые кнопки и поля invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_IPADDR), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_PORTLOW), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_PORTHIGH), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_CLEANLOG), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, TEST_BTN), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, EXIT_BTN), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_TIMEOUT), en invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_OPENONLY), en xor en, 1 ;делаем отрицание, т.е., если en было 0, то станет 1, и наоборот invoke EnableWindow, FUNC(GetDlgItem, hWnd, IDC_STOP), en ret EnableControls ENDP end start
Что такое xor? Это операция "исключающее или". Она несложная, подробнее можно прочитать ЗДЕСЬ.
Вот мы и разобрали весь исходный код по кусочкам. Надеюсь, что вы смогли дочитать до этого места и постарались все понять.
Если хочется как-то модифицировать сканер, улучшить его - попробуйте прикрутить многопоточность, и будете няшами :3
Все вопросы можно писать в комментариях, постараюсь на них ответить.





Chrome~ :
Но все таки, почему мы делаем Sleep(Timeout), а не задаем таймаут в функции select?
Опечатка в начале статьи: первый две ссылки указывают на одну и ту самую страницу.
А так статья норм, хотя не прочитал еще полностью. Спасибо большое!
[Ответить]
dx:
Сентябрь 12th, 2010 at 20:23
Как вариант - можно указать и таймаут у select (tv_usec, в миллисекундах). В этом случае управление возвратится в программу либо по окончанию этого таймаута, либо, если возникнут какие-либо изменения в сокетах в переданных функции структурах (fds_r/fds_w/fds_e). Да, такой путь даже выгоднее, потому что в некоторых случаях функция select может отдать управление быстрее, чем заданный таймаут.
Опечатку сейчас поправлю, спасибо.
[Ответить]
Игорь:
Апрель 21st, 2012 at 17:11
Слушай скинь мне пожалуйста компилятор ресурсов, для компиляции файла rc в res, заранее спасибо
[Ответить]
Игорь:
Апрель 23rd, 2012 at 20:45
Вот что за сообщение выдают батник при запуски из командной строки
http://s019.radikal.ru/i630/1204/b9/9ed13e3005af.jpg
С чем это может быть связано-?
[Ответить]
Алекс :
Здравствуйте! А вы с Tube Toolbox больше не работаете?
[Ответить]
Kaimi:
Сентябрь 18th, 2010 at 22:16
Нет, а что?
[Ответить]
Алекс:
Сентябрь 19th, 2010 at 18:39
Помнится, Вы нас обеспечивали dll-ками к нему, а теперь, с выходом новых версий, не знаем, что делать :(
[Ответить]
Kaimi:
Сентябрь 19th, 2010 at 21:51
В новых версиях необходимо ещё экзешник править, а делать это с каждым обновлением надоедает
Игорь:
Апрель 21st, 2012 at 18:44
Resed это редактор ресурсов, они ничего не компилирует а используется для подключения ресурсов, которые сохраняются в файле ресурсов, то есть в rc-файле а код сохраняется в текстовым редакторе с расширением asm. Компилируется и линкуется все bat-файлом, но для это rc-файл нужно преобразовать в res-файл.
Можете пожалуйста дать ссылку на рабочий компилятор ресурсов, ваш использовал, он не пашет
Chrome~ :
xor eax, eax
Всегда помещает в регистр eax 0? Почему тогда не используете
mov eax, 0
Есть какая то разница принципиальная? Просто для себя интересно.
[Ответить]
dx:
Сентябрь 20th, 2010 at 22:08
XOR - операция "исключающее или" между двумя операндами, я говорил о ней в своих статьях. XOR, примененный к двум одинаковым числам, всегда даст ноль, это несложно понять, узнав, что же из себя представляет "исключающее или".
В сравнении с mov eax, 0:
xor eax, eax занимает в ассемблированном виде занимает на 3 байта меньше, чем mov eax, 0, и, насколько помню, выполняется на 1 такт процессора быстрее.
[Ответить]
Chrome~:
Сентябрь 23rd, 2010 at 18:20
Все понятно, спасибо большое!
[Ответить]
Игорь:
Апрель 18th, 2012 at 23:18
Поэтому в результате выполнения операции XOR будут разблокированы элементы панели управления, которые были заблакированы во время выполнения программы,они разблокируются после завершения выполнения потока вернув управления пользователю.
[Ответить]
Игорь:
Апрель 20th, 2012 at 22:48
Я в бешенстве!! Все сделал, как в статье! Подключил файл с описанием всех констант и переменных, xp mainifest, использовал готовый rc-файл с описанием всех подключенных ресурсов.
Код сохранил как new.asm и закинул в одну папку с xp mainifest, new.rc, батником и скопировал в папку с проектом файл 64stub.exe для компиляции bat-файлом new.rc и new.asm, откомпилировать не получилось.
Выдает - http://s019.radikal.ru/i641/1204/74/2ed822139950.jpg
не может открыть asm. Просмотрел new.asm и new.rc они полностью идентичны. Что должно быть в new.asm-?
[Ответить]
genezis :
интересно, в наше время действительно кому-то важен 1 такт процессора и 2 байта инфы? Спрашиваю на полном серьезе. Или вы пишите чисто по привычке 90ых годов?
[Ответить]
dx:
Март 17th, 2011 at 18:24
Вообще, понимание того, как все происходит на низком уровне (возврат значений, передача значений в функции, стековые фреймы, куча и т.д.) весьма помогает в программировании на языках высокого уровня.
[Ответить]
Игорь:
Апрель 24th, 2012 at 11:59
Может ошибка здесь вед тут передается второй параметр-?
LINK /nologo %1.obj %1.res
rem этой первый параметри,компилирует все в exe а линкование, преоабразование в объеткный код второй параметр, который предается
LINK /nologo %2.obj %2.res
[Ответить]
Игорь :
dx извини если достал тебя, но я почти закончил твой код, использовал готовый rc - файл ресурсов,если не получится отлинковать и откомпилировать, поможешь а то ты не многословный-?
[Ответить]