Сам себе Руссинович Михаил Фленов Хакер, номер #098, стр. 122 Современный метод определения состояния портовВ прошлый раз мы познакомились со старыми и надежными функциями определения состояния портов. Сегодня мы двинемся дальше и познакомимся с новыми функциями Windows XP, которые способны совершать еще немало интересного. Новый вариант программы будет отображать не только открытые порты, но и процессы, которые их открыли. В этом нам помогут функции, не описанные в заголовочных файлах Delphi и даже VC++, поэтому мы будем их загружать динамически. ФункцииИтак, поскольку необходимых нам функций, как я уже говорил, в Delphi нет и не предвидится, работу придется начать с заголовочного файла. Нам понадобятся следующие функции: AllocateAndGetUdpExTableFromStack, AllocateAndGetTcpExTableFromStack, CreateToolhelp32Snapshot, Process32First и Process32Next. Первые две из них реализованы в библиотеке iphlpapi.dll и необходимы для получения из стека таблицы открытых TCP- и UDP-портов соответственно. Какая из функций какую таблицу возвращает, нетрудно догадаться исходя из их имени. Остальные три функции реализованы в kernel32.dll и пригодятся нам для определения процесса, который открыл порт. Напомню, что в прошлый раз мы писали программу TCPView с запасом на будущее, а в главном окне даже подготовили отдельную колонку для отображения имени процесса. Сегодня с помощью нескольких волшебных движений тазом мы ее заполним. Если ты читал предыдущую статью (а если не читал – вставляй DVD в дисковод и бери ее оттуда), то открывай свой заголовочный файл, который уже должен быть создан и начинай добавлять в него описания функций. Как и в прошлый раз, мы будем объявлять функции в виде переменных, чтобы загружать их динамически. Состояния TCPДвинемся по порядку, а значит, начнем с рассмотрения функции:AllocateAndGetTcpExTableFromStack: AllocateAndGetTcpExTableFromStack: function ( pTCPTable: PMIB_TCPEXTABLE; bOrder: BOOL; heap: THandle; zero: DWORD; flags: DWORD ): DWORD; stdcall; Здесь мы объявляем переменную AllocateAndGetTcpExTableFromStack, по сути, представляющую собой функцию, которая принимает, если я правильно посчитал, пять параметров. До пяти я вроде бы считать умею, а если что-то не так, то простите старика-ветерана клавиатурного труда. Итак, функция получает следующие параметры:
Запусти поиск в рунете по названию функции AllocateAndGetTcpExTableFromStack и в большинстве случаев ты узнаешь, что функция не документирована. Кем не документирована? В MSDN есть подробное описание, просто искать его нужно умеючи :). Свежий msdn всегда можно найти по адресу msdn.microsoft.com. Да, он обновляется с задержкой и уже после выхода ОС, и чтобы быть впереди всей планеты, просто нужно купить подписку за немалое количество портретов американских лидеров. В общем, к чему я клоню: если новой функции нет в старой версии справки, то это не значит, что описание отсутствует вовсе ;). Кстати, если верить MSDN, эта функция устарела и больше не поддерживается в новоиспеченной Windows Vista! Я Висту пока еще не ставил и не проверял, но если это так, наш универсальный пример будет как раз кстати. Если посмотреть в SDK для Висты, можно заметить интересный факт: функция там объявлена, но только для совместимости. Так что не пытайся вызвать ее напрямую, иначе тебя ждет крах программы. Что будет в качестве замены – еще неизвестно, а Майкрософт пока молчит. Состояния UDPТаблицу состояний UDP-портов можно узнать с помощью функции AllocateAndGetUdpExTableFromStack, которую необходимо объявить следующим образом: AllocateAndGetUdpExTableFromStack: function ( pUDPTable: PMIB_UDPEXTABLE; bOrder: BOOL; heap: THandle; zero: DWORD; flags: DWORD ): DWORD; stdcall; Ее параметры идентичны параметрам функции работы с TCP-портами, за исключением первого, который имеет тип PMIB_UDPEXTABLE. Порты UDP не имеют соединений, поэтому их таблица состояний немного отличается. Структуры данныхТеперь поговорим о структурах данных, через которые мы будем получать результирующие таблицы. Начнем с TCP-портов. Функция принимает в качестве первого параметра тип данных PMIB_TCPEXTABLE, а, на самом деле, это структура следующего вида: PMIB_TCPEXTABLE = ^TMIB_TCPEXTABLE; TMIB_TCPEXTABLE = packed record dwNumEntries: DWORD; Table: array[0..0] of TMIB_TCPEXROW; end; В ней содержатся всего два параметра: количество элементов в таблице и массив элементов таблицы состояний портов. Каждый элемент массива - это тоже структура типа TMIB_TCPEXROW, представляющая собой вот что: PMIB_TCPEXROW = ^TMIB_TCPEXROW; TMIB_TCPEXROW = packed record dwState: DWORD; dwLocalAddr: DWORD; dwLocalPort: DWORD; dwRemoteAddr: DWORD; dwRemotePort: DWORD; dwProcessID: DWORD; end; Если ты не пропустил прошлый номер, то должен знать, что функция GetTcpTable возвращает примерно такую же структуру. Здесь также присутствует локальный адрес, локальный порт, удаленный адрес и удаленный порт. Самое последнее поле является новым и определяет идентификатор процесса, который открыл порт. Теперь посмотрим на структуру PMIB_UDPEXTABLE, которая передается в качестве первого параметра функции получения состояний UDP-портов: PMIB_UDPEXTABLE = ^TMIB_UDPEXTABLE; TMIB_UDPEXTABLE = packed record dwNumEntries: DWORD; Table: array[0..0] of TMIB_UDPEXROW; end; Тут снова нас ожидает количество элементов в таблице состояний и массив из структур типа TMIB_UDPEXROW. Эта структура выглядит так: PMIB_UDPEXROW = ^TMIB_UDPEXROW; TMIB_UDPEXROW = packed record dwLocalAddr: DWORD; dwLocalPort: DWORD; dwProcessID: DWORD; end; В ней мы видим локальный адрес, локальный порт и идентификатор процесса. Информации об удаленной машине нет и быть не может. Вспомогательные функцииДля реализации примера нам понадобятся еще три системные функции из оконного ядра kernel32.dll: CreateToolhelp32Snapshot: function (dwFlags, th32ProcessID: DWORD): THandle; stdcall; {$EXTERNALSYM CreateToolhelp32Snapshot} Process32First: function (hSnapshot: THandle; var lppe: TProcessEntry32): BOOL; stdcall; {$EXTERNALSYM Process32First} Process32Next: function (hSnapshot: THandle; var lppe: TProcessEntry32): BOOL; stdcall; {$EXTERNALSYM Process32Next} Давай кратко пробежимся по этим функциям:
За более подробной информацией по этим функциям обращайся к MSDN. У них достаточно много возможностей и различных флагов, поэтому описать их в одной статье будет сложно. Загрузка функцийМы объявили переменные, через которые будем обращаться к системным функциям, но все они являются указателями и на данном этапе указывают в никуда. Теперь в них необходимо записать соответствующие адреса. В прошлый раз для этого мы создавали функцию LoadAPIHelpAPI. Давай расширим ее и добавим следующие строки: @AllocateAndGetTcpExTableFromStack:=GetProcAddress(HIpHlpApi, 'AllocateAndGetTcpExTableFromStack'); @AllocateAndGetUdpExTableFromStack:=GetProcAddress(HIpHlpApi, 'AllocateAndGetUdpExTableFromStack'); @CreateToolhelp32Snapshot := GetProcAddress(GetModuleHandle('kernel32.dll'), 'CreateToolhelp32Snapshot'); @Process32First := GetProcAddress(GetModuleHandle('kernel32.dll'), 'Process32First'); @Process32Next := GetProcAddress(GetModuleHandle('kernel32.dll'), 'Process32Next'); Теперь, после загрузки, каждая переменная будет указывать на соответствующую функцию в системе. Если какая-то функция не будет найдена, то соответствующая переменная будет равна нулю. Эту особенность мы будем использовать для того, чтобы определить, поддерживает ли ОС новые функции или необходимо использовать универсальный код, который мы рассматривали в прошлый раз. РеализацияВот мы и подошли к самому интересному - реализации универсального примера. В нашем старом коде по событию OnShow вызывалась функция GetConnections, в которой и происходило определение состояний портов. Улучшим пример, поставив условие вместо безусловного вызова: procedure TForm1.FormShow(Sender: TObject); begin if @AllocateAndGetTcpExTableFromStack=nil then GetConnections else GetExConnections; end; Если указатель AllocateAndGetTcpExTableFromStack равен нулю, значит, соответствующей функции нет в системе, и нужно вызывать GetConnections. Если он не равен нулю, то функция найдена в системе и можно использовать расширенные функции, которые мы рассмотрели сегодня. Полный код содержится в функции GetExConnections, которую мы поместили в листинге 1. Логика GetExConnections практически не изменилась по сравнению с ранее написанной GetConnections. Мы точно так же получаем таблицу состояний и выводим ее содержимое, просто пользуясь при этом другими API функциями. Единственное, что заслуживает отдельного внимания - это вызов функции ProcessPidToName, которая должна переводить идентификатор процесса в удобочитаемое имя. Эту функцию с подробнейшими комментариями ты можешь увидеть в листинге 2. ИтогПрограмма готова. На DVD ты найдешь полноценную программку, которая определяет порты и делает это универсально. Если система поддерживает расширенные функции, то она использует их и отображает имена процессов. Если нет, то ничего страшного, никакой ошибки не произойдет - прога просто воспользуется старыми функциями и отобразит все то же самое, исключая имя процесса. Как видишь, все гениальное - в простоте и умении искать нужные функции. Можешь улучшить этот пример, чтобы он обновлял таблицу по таймеру и подсвечивал записи, состояние портов которых изменилось, или новые записи в таблице. Можно добавить возможность уничтожения выделенного процесса, ведь соответствующий идентификатор мы научились определять. Только не забывай, что при использовании старых функций процесс не определен. Кстати, написанный пример получает только состояние IP-портов старой версии, а реализацию состояния IPv6 легко организовать простым изменением последнего флага при вызове функций AllocateAndGetTcpExTableFromStack и AllocateAndGetUdpExTableFromStack. На этом спешу откланяться. До новых встреч! Листинг 1procedure TForm1.GetExConnections; var TCPExTable: PMIB_TCPEXTABLE; UDPExTable: PMIB_UDPEXTABLE; hSnapshot: THandle; i:Integer; local_name:array[0..255]of char; ExeName:String; begin lwTCP.Items.BeginUpdate; lwTCP.Items.Clear; // Получаем снимок процессов hSnapshot := CreateToolhelp32Snapshot($2, 0); try // Определяем таблицу состояний TCP-портов if AllocateAndGetTcpExTableFromStack(@TCPExTable, False, GetProcessHeap, 2, 2) = NO_ERROR then begin for i := 0 to TCPExTable.dwNumEntries - 1 do begin with lwTCP.Items.Add do begin Caption:='TCP'; if (hSnapshot = INVALID_HANDLE_VALUE) then // Если не удалось получить снимок, то имя процесса оставить пустым SubItems.Add('') else begin // Снимок процессов был получен удачно, поэтому переводим ID в человеческое имя ExeName:=ProcessPidToName(hSnapshot, tcpExTable.Table[i].dwProcessId); SubItems.Add(ExeName); end; SubItems.Add(inet_ntoa(TInAddr(TCPExTable^.Table[I].dwLocalAddr))); SubItems.Add(IntToStr(TCPExTable^.Table[I].dwLocalPort)); SubItems.Add(inet_ntoa(TInAddr(TCPExTable^.Table[I].dwRemoteAddr))); SubItems.Add(IntToStr(TCPExTable^.Table[I].dwRemotePort)); SubItems.Add(TCPState[TCPExTable^.Table[I].dwState]); end; end; end; // Определяем таблицу состояний UPX-портов if AllocateAndGetUdpExTableFromStack(@UdpExTable, False, GetProcessHeap, 2, 2) = NO_ERROR then begin for i := 0 to UDPExTable.dwNumEntries - 1 do begin with lwTCP.Items.Add do begin Caption:='UDP'; if (hSnapshot = INVALID_HANDLE_VALUE) then // Если не удалось получить снимок, то имя процесса оставить пустым SubItems.Add('') else // Снимок процессов был получен удачно, поэтому переводим ID в человеческое имя SubItems.Add(ProcessPidToName(hSnapshot, UDPExTable.Table[i].dwProcessId)); gethostname(local_name, 255); SubItems.Add(inet_ntoa(TInAddr(UDPExTable^.Table[I].dwLocalAddr))); SubItems.Add(IntToStr(UDPExTable^.Table[I].dwLocalPort)); end; end; end; finally lwTCP.Items.EndUpdate; end; end; Листинг 2function TForm1.ProcessPidToName(hProcess: THandle; ProcID: DWORD): String; var procEntry:TPROCESSENTRY32; ProcessName:String; begin procEntry.dwSize := sizeof(procEntry); ProcessName:='???'; // Получаем первый процесс из снимка, который мы сделали в функции GetExConnections if (Process32First(hProcess, procEntry)) then begin // Запускаем цикл перебора всех процессов repeat // Если текущий процесс равен искомому, то возвращаем его имя if (procEntry.th32ProcessID = ProcId) then begin ProcessName:=procEntry.szExeFile; Result:=ProcessName; exit; end; // Цикл выполнять, пока функция Process32Next не вернет nil, то есть пока мы не достигнем конца снимка until (not Process32Next(hProcess, &procEntry)); end; Result:=ProcessName; end; |