(Внимание!!! Пример написанный в Kylix прекрасно работает в Delphi6)
Ко мне почти каждый день идут письма с просьбой выслать исходники какой-нибудь сетевой проги. Оно и понятно, потому что сетевой хак интереснее и адреналинистее (любим же мы коверкать русский язык :)). Чаще всего народ просит порт-сканеры, поэтому сегодня я расскажу тебе, как самому создать это чудо природы.
Начнемсссс...:
Постоянные читатели уже должны знать, как самому написать порт-сканер. Год назад, в спец выпуске "Кодинг" я уже рассказывал об этом чуде. Тогда я написал порт сканер с использованием WinAPI. В этом ничего плохого нет, просто в Linux нет WinAPI, поэтому тот пример нельзя так просто перенести на Kylix.
Продвинутые должны знать, что работа с сетью в Linux и Win построена одинаково на использовании сокетов. Но в Linux - это нормальные сокеты, а в Win - это WinSocks. Они не совместимы между собой, хотя и имеют много общего в названиях функций.
Поэтому я решил сегодня написать порт сканер на основе компонентов. Такой сканер скомпилируется в Linux или Win без малейшего изменения исходника. А работать он будет даже быстрее чем синхронные сокеты.
Песни и пляски ...:
Запусти Kylix. Брось на форму одну кнопку (имя по умолчанию Button1), два компонента TLabel (с именами Label1 и Label 2) и два компонента TEdit (c именами Edit1 и Edit2). Теперь у кнопки поменяй свойство Caption на "Scan", у Label1 на "Start Port", а у Label2 на "End Port".
Рисунок 1. Форма будущего сканера
Если ты все сделал правильно, то у тебя должно получится нечто похожее на рисунок 1. По нажатию кнопки мы будем сканировать порты начиная от номера указанного в Edit1 по номер указанный в Edit2.
Теперь нужно бросить на форму самый важный компонент - TCPClient. В нем дядя Борман уже реализовал для нас все необходимые функции для работы с сокетами и конечно же сканирования.
Рисунок 2. TCPClient
Прежде чем приступить к кодингу, давай еще немного улучшим форму. Установи у Edit1 свойство Text в "1", а у Edit2 в "2". Этим мы задаем значения по умолчанию для начального и конечного порта. И наконец брось еще TMemo. Желательно растянуть ее на всю оставшуюся свободную часть формы. Здесь мы будет отображать состояние сканирования. В итоге, у тебя должно получится нечто похожее на рисунок 3.
Теперь с оформлением покончено, пора переходить к кодингу. В принципе, код достаточно легкий и помещается всего-то в 8 строчек. Так что скоро ты увидишь свой сканер в действии.
Рисунок 3. Окончательная форма будущего сканера
Шкодинг:
Для начала создадим событие OnClick для кнопки. Это событие отлавливает сообщение "нажатие на кнопку". Обработчик можно создать двумя способами:
1. Выделить кнопку, перейти в объектный инспектор и дважды щелкнуть по строке OnClick.
2. Просто дважды щелкнуть по кнопке. По умолчанию, двойной клик по компоненту создает для него обработчик события OnClick.
Выбирай то, что тебе по душе и двигаемся дальше.
Как я уже сказал, по нажатию этой пимпы мы будем сканировать порты. Вот и давай напишем сюда этот текст. Перепиши все, что написано во врезке "Обработчик события OnClick". Комментарии переписывать не обязательно, я их вставил для большей ясности происходящего. Как только перепишешь, можешь продолжить читать дальше, я объясню, что здесь происходит.
Теория:
Ну а теперь давай разберемся, как же работает наш сканер. В разделе var я объявил две переменные i типа целое число (intrger) и ipstr типа строка (String). После начала блока кода (после begin), в первой строке я присваиваю переменной ipst значение '127.0.0.1'. Это будет значение по умолчанию для адреса сканируемой машины.
Следующей строкой я запрашиваю у юзера ip адрес машины:
if not InputQuery('Atention', 'Enter IP Address', ipstr) then exit;
Здесь я использую функцию InputQuery. Она выводит стандартное окно ввода (ты можешь его увидеть на рисунке 4). Функции передается три параметра:
1. Текст заголовка окна.
2. Текст отображаемый над строкой ввода.
3. Переменная типа строки, куда запишется результат.
Если пользователь ввел значение и нажал ОК, то функция вернет true, иначе вернет false. Поэтому я использую конструкцию:
if not InputQuery(...) then exit;
Которая означает: "Если пользователь не нажал ОК, то выйти".
Рисунок 4. Результат работы сканера
После этого я запускаю цикл:
for i:=StrToInt(Edit1.Text) to StrToInt(Edit2.Text) do
Эта строка звучит так: "Для переменной i присвоить значение указанное в Edit1 и до значения указанного в Edit2 выполнять следующую строку". Вместо следующей строки у меня идет блок begin ... end, поэтому будет выполнятся код указанный в этом блоке. Чтобы было понятней, давай рассмотрим пример цикла:
for i:=1 to 10 do
begin
print('ОК');
end;
Строка for i:=1 to 10 do запускает цикл от 1 до 10. Это значит, что код указанный за последующими begin ... end будет выполнятся 10 раз. После каждого выполнения i будет увеличиваться на 1. После первого выполнения print('ОК') переменная i будет равна 2, после второго равна 3 и так держать.
И наконец IntToStr - функция, которой передается строка, а она превращает ее в число. Если ты передашь ей '1', то получишь число 1. Но если передашь 'a24gd', то получишь дулю с маком, потому что здесь не только числа, но и буквы.
Понеслась душа в рай :
Теперь посмотрим на само сканирование, которое находится между begin и end. Первая строка указывает, какой порт мы хотим открыть:
TcpClient1.RemotePort:=IntToStr(i);
Эта строка но русском звучит так: "у компонента TcpClient1 свойство RemotePort установить в значение указанное в переменной "i". Свойство RemotePort является строковым, поэтому я конвертирую число i в строку с помощью IntToStr.
Едем дальше. Следующей строкой я пытаюсь открыть порт: "TcpClient1.Open" После этого произвожу проверку:
if TcpClient1.Connected then
Если компонент TcpClient1 смог законнектиться, то вывожу об этом сообщение "Memo1.Lines.Add(IntToStr(i) + ' open')".
И самое последнее, что я делаю - закрываю порт. Если порт открылся и мы его не закрыли, то при следующей попытки открыть произойдет ошибка.
Понеслась душа в ад:
Вот и готов первый сканер. В Linux он у меня просканировал 1024 порта за несколько секунд. В Windows сканирование проходило около 5 минут, после чего я его вырубил. Так что под окнами сканируй не более десяти портов. Ты наверно спросишь, а как же тогда другие проги сканируют так быстро? Но это уже совсем другая история, о которой я расскажу в другой раз.
В принципе, сканер рабочий и его вполне реально юзать даже в боевых условиях. Тем более, что сканировать большое количество портов сразу очень опасно. Такие массовые сканы очень легко вычисляются. Желательно еще сканировать не все порты подряд, а в рассыпную. Например: 1, 150, 76, 1654, 756 и так далее. А это уже все в твоих руках.
На этом на сегодня все. Примеры, как всегда можно забрать с моего сайта www.x-c-r.com.
Обработчик события OnClick:
procedure TForm1.Button1Click(Sender: TObject);
var
i:Integer;
ipstr:String;
begin
ipstr:='127.0.0.1';
//Запрашиваю адрес компа.
if not InputQuery('Atention', 'Enter IP Address', ipstr) then exit;
//Запускаю цикл
for i:=StrToInt(Edit1.Text) to StrToInt(Edit2.Text) do
begin
//Устанавливаю порт
TcpClient1.RemotePort:=IntToStr(i);
//Пытаюсь его открыть
TcpClient1.Open;
//Если удалось, то сообщаю об этом
if TcpClient1.Connected then
Memo1.Lines.Add(IntToStr(i)+' open');
//Закрываю порт.
TcpClient1.Close;
end;
end;