Простой сканер портов
By exs. Saturday, 14. April 2007, 07:39:13
Пример использования библиотеки сокетов и немного бакфорт-кода с подробным описанием.
Задание : получить от пользователя адрес машины для исследования (в виде домена или IP-адреса) и номера портов которые надо просканировать. Требуется отобразить номера открытых на целевой машине портов из указанного диапазона. Сканирование порта - это просто попытка установить соединение. Если на целевой машине запущен какой-то сервис который слушает этот порт (и не применяются техники типа port knocking, etc), то мы сможем законнектиться.
REQUIRE ConnectHost ~ac/lib/win/winsock/sockets.f
REQUIRE PRO ~profit/lib/bac4th.f
REQUIRE NEXT-PARAM ~day/common/clparam.f
\ REQUIRE NUMBER ~ygrek/lib/parse.f
\ Преобразовать строку a u в число (только символы текущей системы счисления)
: NUMBER ( a u -- n -1 | 0 )
0 0 2SWAP >NUMBER NIP IF 2DROP FALSE ELSE D>S TRUE THEN ;
: tryConnect ( a u port -- ? )
ConnectHost 0= \ если удаётся установить соединение - ior ноль
IF
CloseSocket DROP \ тогда корректно закрываем сокет
TRUE
ELSE
DROP FALSE \ иначе возвращаем признак неудачи
THEN ;
\ сканируем последовательность портов
: tryConnectRange=> ( a u port1 port2 --> port \ <-- )
PRO
2DUP < IF SWAP THEN \ гарантируем корректность параметров цикла
?DO
2DUP I tryConnect
IF
I CONT \ если удача - генерируем вызов с номером порта
THEN
LOOP 2DROP ;
\ получаем параметры из коммандной строки
: getParameters ( -- a u port1 port2 )
NEXT-PARAM 2DROP \ первый параметр - путь к исполняемому файлу - игнорируем
\ если параметров не хватает - выкидываем исключение
NEXT-PARAM DUP 0= THROW
NEXT-PARAM DUP 0= THROW NUMBER 0= THROW
NEXT-PARAM DUP 0= IF 2DROP DUP 1+ ELSE NUMBER 0= THROW THEN ;
: printUsage ( -- )
CR S" Usage: portscan.exe host port_from [port_to]" TYPE
CR S" host - IP address or domain to scan ports" TYPE
CR S" port_from port_to - port range to scan" TYPE
S" , if port_to is omitted only port_from is checked" TYPE
;
\ для каждой удавшейся попытки соединения просто печатаем номер порта
: performScan ( a u port1 port2 -- )
tryConnectRange=> CR ." Port is open : " . ;
: main
['] getParameters CATCH \ ловим исключения от обработки комстроки
IF
printUsage BYE \ если ошибка в параметрах то пишем информацию по использованию
THEN
\ !!! не забываем проинициализировать систему сокетов !!!
SocketsStartup THROW
CR ." Performing scan on : "
2OVER TYPE
CR ." Port range : " 2DUP SWAP . ." - " .
performScan
CR ." Finished"
KEY DROP BYE ;
: save
0 TO SPF-INIT? \ выключаем интерпретацию комстроки spf-ом
['] main MAINX ! \ устанавливаем нашу точку входа
S" portscan.exe" SAVE ; \ сохраняем программу в exe-файл
save BYE
Главное слово - tryConnect ( a u port -- ? ) которое проверяет соединение с хостом a u на указанный порт. Внимание на tryConnectRange=> ( a u port1 port2 --> port \ <-- ) - это бакфорт слово, что и отражено в стековой нотации. Ничего сложного :
- слово берёт четыре параметра со стека - строку в виде a u и два числа
- стрелка --> port обозначает что при успехе генерируется вызов с одним параметром на стеке
- пустое место за обратной стрелкой <-- ) значит, что сгенерированный вызов ничего не должен возвратить, т.е. условно стековая диаграмма для бакфорт-вызова выглядит так : ( port -- )
- после завершения работы слово tryConnectRange=> ничего не возвращает - это следует из пустого места перед обратной стрелкой \ <--.
Остался один неясный момент - что значит генерируется вызов? Это ключевой момент для понимания бакфорта.. На самом деле происходит просто передача управления - но несколько непривычным образом. Посмотрим на слово :
: performScan ( a u port1 port2 -- ) tryConnectRange=> CR ." Port is open : " . ;По сути, бакфорт вызов передаёт управление на условный выполнимый токен соответствующий последовательности CR ." Port is open : " .
Проверим стековый эффект для нашего "условного" xt :
слово CR ( -- )
слово ." ( -- )
слово . ( n -- )
Итого общий эффект : ( n -- ), как раз то что требует слово tryConnectRange=> для своих бакфорт вызовов - ( port -- ).
Так выглядит использование бакфорт-итераторов снаружи. Изнутри же это выглядит так : слово-итератор подготавливает почву для бэктрекинга словом PRO в начале своего определения. А вызовы генерируются словом CONT. По сути вызов CONT просто передаёт управление на условный xt который передаётся неявным образом. Подставьте мысленно вместо CONT последовательность CR ." Port is open : " . и это будет точным эквивалентом того что происходит в момент исполнения этого слова.
Использование бакфорта в этой ситуации позволило отделить мух от котлет - проверка события "открытый порт" и реакция на него (напечатать номер порта) разделены. Этого можно было достичь и явной передачей вышеописанного "условного xt" в слово tryConnectRange=>.
Слово NEXT-PARAM ( -- a u ) даёт параметры коммандной строки.
Также обращаю внимание на обязательную инициализацию библиотеки winsock помощью SocketsStartup THROW иначе слова для работы с сокетами не будут работать!
После выполнения получим portscan.exe, который запускаем например так :
portscan.exe forth.org.ru 25 81
Performing scan on : forth.org.ru Port range : 25 - 81 Port is open : 25 Port is open : 53 Port is open : 80 Finished
Отметим, что сканирование идёт очень медленно. Причина в том что в каждый момент времени активно только одно соединение. Если мы это дело распараллелим в несколько потоков, то очевидно получим прирост в скорости. Что мы и проделаем в следующий раз








How to use Quote function: