Информационные технологииStfw.Ru 🔍

Быстрая проверка состояния портов на Delphi

TCP View
🕛 28.08.2008, 16:05
Мы уже не раз описывали утилиту TCP View от великого Марка Руссиновича. Наверняка, многим интересно устройство этой незаменимой в хакерском деле программы, поэтому сейчас, проведя буквально пять минут за клавиатурой, мы напишем ее простенький аналог.

Набор функций

Существует два набора функций для получения состояния TCP/UDP-портов. Первый поддерживается всеми ОС, начиная с Windows 95, второй же появился, если я не ошибаюсь, в Windows 2000 или даже в XP. Новые функции намного лучше и позволяют получить больше информации (в том числе и имя процесса, который открыл порт), но и работать они будут, как можно предположить, только в Windows XP (за окна 2000-го размера не ручаюсь). Оба варианта реализованы в dll-файле iphlpapi.dll. Так как мы хотим, чтобы наша программа работала в любой версии форточек, то мы рассмотрим оба набора и создадим универсальную утилиту.

В стандартной библиотеке Delphi ни один из наборов функций не описан, поэтому нам придется сделать свой собственный заголовочный файл, причем функции будут подключены не статически, а динамически (библиотека iphlpapi будет загружаться с помощью LoadLibrary, а потом мы будем получать адреса необходимых функций). Это очень важно при использовании нового, расширенного набора функций, ведь если связать заголовочный файл статически, то при старте программы она автоматически будет искать связь с библиотекой. И если пользователь запустит прогу на Windows 95 (наличие хардкорных фриков на старых тачках среди наших продвинутых читателей нельзя исключить полностью :) - примечание Лозовского), то произойдет ошибка, так как новые функции будут не найдены.

Сегодня мы рассмотрим только старый набор функций, а в следующий раз двинемся дальше. Итак, усаживайся поудобнее, мы начинаем великое погружение в код.

Набор функций

Для получения таблицы состояния TCP-портов необходимо использовать функцию GetTcpTable. Она выглядит следующим образом:

GetTcpTable: function(

pTcpTable: PMIB_TCPTABLE;

var pdwSize: DWORD;

bOrder: BOOL

): DWORD; stdcall;

Здесь мы объявляем переменную типа «функция» с именем GetTcpTable. Чуть позже, когда мы создадим код загрузки библиотеки, в эту переменную будет записан адрес системной функции. GetTcpTable получает 3 параметра:

1. Указатель на структуру MIB_TCPTABLE. Это и есть таблица состояний, которую мы получим на выходе.

2. Размер выделенной памяти для хранения таблицы. О том, сколько памяти выделять, мы поговорим, когда будем рассматривать реальный пример.

3. Булево значение, которое определяет, нужно ли сортировать таблицу.

Самое интересное здесь - это, конечно же, первый параметр, где мы видим структуру MIB_TCPTABLE. Она выглядит следующим образом:

MIB_TCPTABLE = record

dwNumEntries: DWORD;

table: array [0..0] of MIB_TCPROW;

end;

Уже по именам можно понять, для чего нужны поля структуры. Первый параметр - это количество записей в таблицы состояний, а второй - это массив структур типа MIB_TCPROW, где содержатся сами записи.

Структура MIB_TCPROW выглядит вот так:

MIB_TCPROW = record

dwState: DWORD;

dwLocalAddr: DWORD;

dwLocalPort: DWORD;

dwRemoteAddr: DWORD;

dwRemotePort: DWORD;

end;

Вот тут и содержится вся необходимая нам информация, которую отображает TCP View:

- dwState - состояние;

- dwLocalAddr - локальный адрес;

- dwLocalPort - локальный порт;

- dwRemoteAddr - адрес удаленной машины, которая запросила соединение;

- dwRemotePort - удаленный порт.

Состояние UDP

Идентичная функция есть и для получения состояния UDP-портов - GetUdpTable. Разница заключается только в том, что структура состояния содержит исключительно локальный порт и локальный адрес. У UDP нет возможности создавать соединения, а значит удаленного адреса и порта просто не может быть. Полный код описания процедур GetTcpTable, GetUdpTable и необходимых структур можешь увидеть в листинге 1. Тебе остается только создать модуль и добавить в него этот код.

Загрузка библиотеки

Настало время написать код загрузки библиотеки. Создадим для этого процедуру:

LoadAPIHelpAPI:

procedure LoadAPIHelpAPI;

begin

if HIphlpapi = 0 then

HIpHlpApi := LoadLibrary('iphlpapi.dll');

if HIpHlpApi > HINSTANCE_ERROR then

begin

@GetTcpTable := GetProcAddress(HIpHlpApi, 'GetTcpTable');

@GetUdpTable := GetProcAddress(HIpHlpApi, 'GetUdpTable');

end;

end;

В этом коде нетрудно заметить переменную HIpHlpApi. Что это? А это всего-навсего хэндл загруженной библиотеки. Ее нужно объявить где-нибудь в модуле, чуть ранее:

var

HIpHlpApi: THandle = 0;

Теперь вернемся к LoadAPIHelpAPI. Сначала проверяем переменную HIpHlpApi. Если она равна нулю, то загружаем сетевую библиотеку. Кстати, имя этой библиотеки - iphlpapi.dll. Ну что, проверим результат? В случае положительного ответа, мы получим адреса всех необходимых функций с помощью GetProcAddress.

Чтобы функция выполнялась автоматически при использовании модуля, вызовем ее в разделе initialization:

Initialization

LoadAPIHelpAPI;

Finalization

Как известно, настоящий программист никогда не забывает соблюдать правила хорошего тона, освобождая хэндл загруженной библиотеки. Для этой цели мы напишем отдельную функцию:

procedure FreeAPIHelpAPI;

begin

if HIpHlpApi <> 0 then FreeLibrary(HIpHlpApi);

HIpHlpApi := 0;

end;

А чтобы она выполнялась автоматически, вызов ее нужно поместить в разделе finalization. Полный код модуля с описанием функций, загрузки и выгрузки можно найти на компакт-диске в файле HorrificHelpAPI.pas.

Пример использования

Перейдем непосредственно к примеру получения таблицы состояний TCP-портов. Создаем новый проект и добавляем созданный ранее заголовочный файл. На форме нам понадобится только компонент типа TListView, которому мы установим следующие свойства:

Name - установим в lwTCP;

ViewStyle - будет vsReport, чтобы видеть сетку.

Дважды щелкни по созданному ListView и добавь 7 колонок: протокол, процесс, локальный адрес, локальный порт, удаленный адрес, удаленный порт, состояние. Мы будем заполнять все колонки, кроме колонки «процесс» (старыми функциями, которые мы сегодня рассматриваем, процесс получить нельзя).

Теперь взгляни на листинг 2, где показан пример кода, получающего TCP-таблицу. После очистки компонента ListView вызываем функцию GetTcpTable. Обрати внимание, что первый параметр равен nil. Получается, что вместо того чтобы указать переменную, куда функция запишет результат, передается нулевое значение. Почему? Дело в том, что мы не знаем, сколько записей вернет функция и сколько памяти нужно выделить под хранение результата.

Так как мы указали нулевое значение в первом параметре и нулевой размер во втором, функция должна завершиться ошибкой ERROR_INSUFFICIENT_BUFFER, вернув во втором параметре количество записей в системе. Выделяем необходимую память с помощью ReallocMem.

Теперь уже мы в состоянии по-человечески вызвать GetTcpTable и получить нормальную таблицу. Если результат не равен нулю, то все закончилось успешно. Далее все банально - количество записей находится в tcpTable.dwNumEntries, а доступ к каждой отдельной структуре, описывающей состояние TCP-порта, можно получить так:

tcpTable^.table[номер записи]

Дальнейшие комментарии, как мне кажется, излишни. Все и так ясно из кода. Хотя нет, нужно еще сказать о состоянии. Если с адресом и портом все понятно, то состояние - это вопрос. Судя по справке MSDN, состояние может принимать значения от 1 до 12 и для этого заведены следующие константы:

MIB_TCP_STATE_CLOSED, MIB_TCP_STATE_LISTEN, MIB_TCP_STATE_SYN_SENT, MIB_TCP_STATE_SYN_RCVD, MIB_TCP_STATE_ESTAB, MIB_TCP_STATE_FIN_WAIT1, MIB_TCP_STATE_FIN_WAIT2, MIB_TCP_STATE_CLOSE_WAIT, MIB_TCP_STATE_CLOSING, MIB_TCP_STATE_LAST_ACK, MIB_TCP_STATE_TIME_WAIT, MIB_TCP_STATE_DELETE_TCB.

Уже по названию легко понять, для чего нужны эти константы. Исходя из личного опыта, могу сказать, что иногда состояние может быть равным 0, хотя соответствующей константы нет. Видимо, данный факт нужно воспринимать как ошибку, а может быть, как нечто неопознанное (возможно, даже инопланетное - примечание Лозовского). Чтобы было удобнее превращать числовое значение состояния в строку, можно создать константу в виде массива строк, например, вот так:

TcpState: array [0..12] of String = (

'???', 'CLOSED', 'LISTENING', 'SYN_SENT',

'SYN_RCVD', 'ESTABLISHED', 'FIN_WAIT1',

'FIN_WAIT2', 'CLOSE_WAIT', 'CLOSING',

'LAST_ACK', 'TIME_WAIT', 'DELETE_TCB');

UDP

Ради экономии места я не буду рассматривать код получения UDP-таблицы. Ты можешь написать его сам, поскольку он идентичен работе с TCP. Просто поменяем функцию GetTCPTable на GetUDPTable, а переменную типа PMIB_TCPTABLE - на PMIB_UDPTABLE. Попробуй реализовать код самостоятельно, не заглядывая в листинг 3.

Complete

Как видишь, ничего сверхъестественного мы не написали. Никаких великих алгоритмов мы не использовали, обойдясь всего лишь двумя функциями, о которых просто нужно знать. Вот и все, пиши письма. Если их количество превысит некую критическую отметку, то через месяц ты узнаешь про расширенные функции работы с TCP- и UDP-таблицами и про то, как с ними работать.

На этом мы завершаем наш сегодняшний рассказ. Спокойной ночи, дорогие друзья. Тьфу, насмотрелся с детьми «Спокойной ночи». Все-таки Оксана Федорова рулит! Показывать Мисс мира в детской передаче - гениальное решение, мой сын уже с детства засматривается на красивых женщин в самом расцвете сил.

Листинг 1

type

// Описание отдельной записи TCP

PMIB_TCPROW = ^MIB_TCPROW;

MIB_TCPROW = record

dwState: DWORD;

dwLocalAddr: DWORD;

dwLocalPort: DWORD;

dwRemoteAddr: DWORD;

dwRemotePort: DWORD;

end;

// Структура для хранения массива записей TCP

PMIB_TCPTABLE = ^MIB_TCPTABLE;

MIB_TCPTABLE = record

dwNumEntries: DWORD;

table: array [0..0] of MIB_TCPROW;

end;

// Описание отдельной UDP записи

PMIB_UDPROW = ^MIB_UDPROW;

MIB_UDPROW = record

dwLocalAddr: DWORD;

dwLocalPort: DWORD;

end;

// Структура для хранения массива записей UDP

PMIB_UDPTABLE = ^MIB_UDPTABLE;

MIB_UDPTABLE = record

dwNumEntries: DWORD;

table: array [0..0] of MIB_UDPROW;

end;

var

// Функция получения таблицы TCP

GetTcpTable: function (pTcpTable: PMIB_TCPTABLE;

var pdwSize: DWORD; bOrder: BOOL): DWORD; stdcall;

{$EXTEALSYM GetTcpTable}

// Функция получения таблицы UDP

GetUdpTable: function (pUdpTable: PMIB_UDPTABLE;

var pdwSize: DWORD; bOrder: BOOL): DWORD; stdcall;

{$EXTEALSYM GetUdpTable}

Листинг 2

var

error, dwSize:DWORD;

tcpTable:PMIB_TCPTABLE;

i:Integer;

begin

lwTCP.Items.Clear;

dwSize:=0;

// Первый вызов для определения количества записей

error := GetTcpTable(nil, &dwSize, TRUE);

if (error <> ERROR_INSUFFICIENT_BUFFER) then

exit;

// Выделяем необходимую память и получаем таблицу TCP

try

ReallocMem(tcpTable, dwSize);

error := GetTcpTable(tcpTable, &dwSize, TRUE);

if (error>0) then

exit;

// перебираем все записи и добавляем их в ListView

for i := 0 to tcpTable.dwNumEntries - 1 do

begin

with lwTCP.Items.Add do

begin

Caption:='TCP';

SubItems.Add(inet_ntoa(TInAddr(tcpTable^.table.dwLocalAddr)));

SubItems.Add(IntToStr(tcpTable^.table.dwLocalPort));

SubItems.Add(inet_ntoa(TInAddr(tcpTable^.table.dwRemoteAddr)));

SubItems.Add(IntToStr(tcpTable^.table.dwRemotePort));

SubItems.Add(TCPState[tcpTable^.Table.dwState]);

SubItems.Add('');

end;

end;

finally

FreeMem(tcpTable);

end;

end;

Листинг 3

var

error, dwSize:DWORD;

udpTable:PMIB_UDPTABLE;

i:Integer;

begin

// Определяем количество UDP записей

dwSize:=0;

error := GetUDPTable(nil, &dwSize, TRUE);

if (error <> ERROR_INSUFFICIENT_BUFFER) then

exit;

// Выделяем память и получаем UDP-таблицу

ReallocMem(udpTable, dwSize);

error := GetUdpTable(udpTable, &dwSize, TRUE);

if (error>0) then

exit;

// Просматриваем таблицу и добавляем записи в ListView

for i := 0 to udpTable.dwNumEntries - 1 do

begin

with lwTCP.Items.Add do

begin

Caption:='TCP';

SubItems.Add('');

SubItems.Add(inet_ntoa(TInAddr(udpTable^.table.dwLocalAddr)));

SubItems.Add(IntToStr(udpTable^.table.dwLocalPort));

end;

end;

FreeMem(udpTable);

end;

Pascal и Delphi   Теги:

Читать IT-новости в Telegram
Информационные технологии
Мы в соцсетях ✉