уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Вообще заинтересоваться темой работой с процессами в Windows XP меня заставило не любопытство, а острая необходимость решения мелкой, но очень нехорошей проблемы.

Началось всё с того, что моя жена блуждая по просторам Рунета, приволокла в свой комп нехороший вирус. Не знаю какое уж название прилепил этому вирусу Касперский, н согласно его методам именования, но вирус, мягко говоря козлячий. Смысл его работы заключался в следующем: примерно через 1-2 минуты после запуска ОС на рабочем столе появлялось окно с сообщением, типа «Тестовый период использования программы …  закончился. Отошлите СМС на номер … » и до кучи предупреждение о том, что не стоит вмешиваться работы системы иначе будет мега-коллапс. В общем развод на денюжки  гордых пользователей нелегального софта честных граждан. Проблемка заключалась в том, что окно это висело аккурат по центру рабочего стола, поверх всех окон и напрочь отказывалось сменить свое положение, т.е. просто так взять и запустить диспетчер задач было нереально (кстати, он и не помог бы особо в решении проблемы). В безопасном режиме — то же самое.

Делать нечего — пошел Гуглить на тему как с этой гадостью бороться. Как оказалось вирь этот уже имел тучу модификаций и скрывался в разных файлах системы.  Как  назло у меня оказалась последняя модификация. Как вариант, на одном из форумов нашел совет по «ручному» обезвреживанию — как-нибудь убить процесс winlogon.exe (в нем скрывалось жирное, безобразное тело вируса), скачать скрипт (прилагался) и выполнить его, тем самым убив файлы вируса пока тот отключен.

Если кто не в курсе — winlogon.exe средствами Windows просто так не убить. Обычно убийство заканчивается сообщением:

Вот тут и началось мое ускоренное изучение работы с процессами в Windows XP. И первая задача, которая передо мной встала — узнать имена всех запущенных процессов в Windows. И тут нам как нельзя кстати пригодиться модуль PsAPI.pas.

Назначение PsAPI (Process status Application Programming Interface): вспомогательная библиотека, облегчающая доступ к информации о запущенных процессах и драйверах системы.

Открываем Delphi, создаем новый проект и подключаем в uses модуль psapi. Теперь открываем сам модуль и смотрим, что он нам может предложить:

function EnumProcesses(lpidProcess: LPDWORD; cb: DWORD; var cbNeeded: DWORD): BOOL;

Получает идентификаторы для каждого процесса, запущенного в системе.

lpidProcess — указатель на массив, в котором будут храниться идентификаторы.

cb — размер массива в байтах

cbNeeded — количество байт, записанных в массив.

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

function OpenProcess; external kernel32 name 'OpenProcess';

параметры функции следующие:

dwDesiredAccess: DWORDправа доступа к объекту. Далеко не углубляясь в дебри прав доступа, скажу, что нам необходимы права PROCESS_QUERY_INFORMATION (0x0400) и PROCESS_VM_READ (0x0010)

bInheritHandle: bool — Если это значение равно true, то процесс, созданный в результате выполнения функции будет наследовать Handle.

dwProcessId — идентификатор процесса. Тот самый, который мы получим, выполнив EnumProcesses.

Хэндл процесса получили. Что с ним делать? Первое, что пришло на ум — заглянуть опять в PsAPI.pas и узнать в каких функциях используется значение Handle процесса. Вот, что мы можем использовать, применительно к нашей задаче:

function EnumProcessModules(hProcess: THandle; lphModule: LPDWORD; cb: DWORD; var lpcbNeeded: DWORD): BOOL;

Возвращает хэндл для каждого модуля процесса.  Параметры функции:

hProcess — хэндл процесса.

lphModule — указатель на массив для записи хэндлов модулей

cb — размер массива в байтах

lpcbNeeded — число байт, записанных в массив.

Ну, а дальше можно получать и имя файла, используя функцию:

function GetModuleFileNameEx(hProcess: THandle; hModule: HMODULE;
lpFilename: PWideChar; nSize: DWORD): DWORD;

Возвращает полный путь к файлу, содержащему указанный модуль.

lpFilename —  указатель на буфер, который получает полный путь к модулю. Если размер файла больше, чем значение параметра nSize, функция завершается успешно, но имя файла обрезается.

nSize — размер lpFilename в символах.

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

procedure TForm4.Button3Click(Sender: TObject);
var procesess: array [0..$FFF] of DWORD;
i,count, cm: cardinal;
ph: THandle; //дескриптор процесса
ModName:array[0..max_path] of char;//имя модуля
mh:hmodule;//дескрипrтор модуля
List: TStringList;
begin
  if Not EnumProcesses(@procesess,SizeOf(procesess),count) then
    Exit;
  else
    begin
      List:=TStringList.Create;
      for i:=0 to count div 4-1 do
        begin
          ph:=OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, procesess[i]);
           if ph>0 then
             begin
               EnumProcessModules(ph, @mh, 4, cm);
               GetModuleFileNameEx(ph, mh, ModName, sizeof(ModName));
               List.Add(string(ModName));
               CloseHandle(ph);
             end;
        end;
  end;
end;

Здесь, в числе прочего, видимо следует обратить внимание на строку

for i:=0 to count div 4-1 do

и, соответственно на переменную

procesess: array [0..$FFF] of DWORD;

т.к. может показаться, что деление на 4 и сам размер массива взяты «с потолка». Дело в том, что согласно рекомендациям с msdn необходимо изначально задавать большой размер массива, т.к. неизвестно сколько процессов запущено на данный момент в системе.

Далее, чтобы узнать количество запущенных процессов нам необходимо разделить cbNeeded на sizeof(DWORD), а это как раз и есть 4. Далее, если значение cb оказывается равным cbNeeded, то размер массива увеличивается и выполнение функции повторяется. Поэтому я изначально задал большой размер массива, чтобы не повторять выполнение  EnumProcesses. Конечно, в идеале стоило бы провести проверку всё ли мы записали в массив, но для примера, процедуры, рассмотренной выше, думаю, будет вполне достаточно.

Итак, в результате выполнения функций из модуля PsAPI.pas мы можем получить:

  • идентификаторы процессов, запущенных в системе
  • дескрипторы каждого модуля для процесса
  • пути к файлам, содержащим модули

Остается решить вторую задачу — убить критический процесс. Здесь можно, опять же воспользоваться функцией из kernel32.dll — TerminateProcess, например вот так:

[...]
var Ret: BOOL;
[...]
Ret := TerminateProcess(Ph, 0);
if Integer(Ret) = 0 then
  begin
    //процесс по каким-либо причинам не был убит
  end;
[...]

По крайней мере с использованием функции мне удалось завершить начатой — завершить процесс winlogon.exe.

В целом могу сказать, что работа с PsAPI мне показалась более удобной, нежели с TLHelp32, который, как оказалось, рекомендуется использовать для Win98-Me, хотя функции из этого модуля возвращают такие же результаты в XP.

5 1 голос
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
10 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
33-й
33-й
05/01/2010 22:39

Список создали
[code]List:=TStringList.Create;[/code]
А освобождать?
[code]List.Free[/code]
И вообще[code] try finally[/code] надо использовать

Владимир
Владимир
26/01/2010 09:18

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

Сергей
Сергей
15/02/2010 21:13

Если взять этот код, и в каждой итерации выводить ph (дескриптор процесса), то на выходе одно и тоже значение?! Но при этом я по этому дескриптору получаю различные имена модулей.
А вообще я не могу остановить текущее приложение WaitForSingleObject(ph, 5000); Не работает..

Игорь
Игорь
25/03/2010 05:09

GetModuleFileNameEx(hProcess: THandle; hModule: HMODULE;
lpFilename: PWideChar; nSize: DWORD): DWORD; — не возвращает полный путь к exe…. ну только к текущему процессу….

bne
bne
18/06/2010 15:37

Я (после экспериментов) просто убил зараженного пользователя и потом создал нового

Володько
Володько
27/01/2011 10:37

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

Саня
Саня
10/01/2012 21:48

«if Not EnumProcesses(@procesess,SizeOf(procesess),count) then
    Exit(здесь двоеточие на хрена, с ним не запустится!)
  else
    begin»

Hobit
30/08/2012 00:18

Влад, столкнулся с такой проблемой, в Delphi 7 (х32)собранное приложение не определяет EXE запущенного приложения по его хендлу в Win7 x64, используя вышеуказанную методику. Как это можно побороть?

Буду признателен за помощь.