Работа с процессами в С/С++. Основные приемы
Тимур Хабибуллин
Данная статья рассказывает о работе с процессами,
модулями, кучами и потоками при помощи билиотеки TOOLHELP
Работа с процессами - основа, без которой заниматься
системным программированием так же бессмысленно, как без знания структуры
PE-файлов или организации памяти. Поэтому я поднимаю эту тему вновь и расскажу
о работе с процессами посредством функций TOOLHELP.
Язык программирования: я выбрал C (без плюсиков, т.к.
работы с классами в этой статье не будет - после прочтения вы сможете их без
труда составить сами) по многим причинам и в первую очередь из-за его
низкоуровнего взаимодействия с памятью...записал-считал, все просто и понятно.
Перечислить запущенные в системе процессы можно
по-разному, я привык пользоваться функциями TOOLHELP. Общая последовательность
действий при работе с этой библиотекой: делаем "снимок" (Snapshot)
системной информации, которая нам необходима, потом бегаем по процессам (а
также модулям и кучам). Поэтому начнем с простого - перечислим все процессы.
//Перечисление процессов
int EnumerateProcs(void)
{
//создаем "снимок" информации о процессах
//первый параметр функции - константа, определяющая,
//какую информацию нам нужно "снять", а второй
-
//идентификатор процесса, к которому относится эта
//информация. В данном случае это 0 т.к. мы делаем
//снимок всех процессов
HANDLE pSnap =
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
bool bIsok = false;
//Структура, в которую будут записаны данные процесса
PROCESSENTRY32 ProcEntry;
//установим ее размер, это необходимое действие
ProcEntry.dwSize = sizeof(ProcEntry);
//теперь определим первый процесс
//первый параметр функции - хэндл "снимка"
информации
//второй - адрес структуры PROCESSENTRY32
//true - в случае удачи, false - в случае неудачи
bIsok = Process32First(pSnap, &ProcEntry);
//здесь можно было вставить роскошный цикл for(....) но
это
//не совсем удобочитаемо
//так что цикл while
while(bIsok)
{
//печатаем имя процесса, его идентификатор
//теперь, когда у нас есть структура ProcEntry
//То, какую информацию вы из нее возьмете, зависит
//только от задачи ))
printf("%s
%un", ProcEntry.szExeFile, ProcEntry.th32ProcessID);
bIsok = Process32Next(pSnap, &ProcEntry);
}
//чистим память!
CloseHandle(pSnap);
return 1;
}
Вуаля, список всех процессов, аки в диспетчере задач.
Теперь мы сделаем кое-что, чего в диспетчере нет! В адресном пространстве
каждого процесса (в области памяти, выделенной ему системой) находятся
различные библиотеки, которые, собственно, состовляют ПРИЛОЖЕНИЕ. Это и
Kernel32 и GDI и еще множество различных. Наша задача - их все пересчитать и
переписать! Для этого действа напишем небольшую функцию.
//Перечисление модулей процесса
int EnumerateModules(DWORD PID)
{
//Входной параметр - идентификатор процесса, чьи модули
мы собираемся
//перечислять. Во первых создадим snapshot информации о
модулях
//теперь нам нужна информация о конкретном процессе -
процессе
//с идентификатором PID
HANDLE pMdlSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
PID);
bool bIsok = false;
//структура с информацией о модуле
MODULEENTRY32 MdlEntry;
//зададим размер
MdlEntry.dwSize = sizeof(MODULEENTRY32);
//и найдем первый модуль
bIsok = Module32First(pMdlSnap, &MdlEntry);
//и далее, как и с процессами
while(bIsok)
{
//печатаем имя модуля
printf(" %s n", MdlEntry.szModule);
//и переходим к следующему
bIsok = Module32Next(pMdlSnap, &MdlEntry);
}
//чистим память!
CloseHandle(pMdlSnap);
return 1;
}
А теперь немного притормозим и посмотрим, какую еще
информацию о процессах и модулях мы получаем:
typedef struct tagPROCESSENTRY32 {
DWORD dwSize;
//Рамер структуры
DWORD cntUsage; //Число ссылк на процесс. Процесс
уничтожается, //когда число ссылок становится 0
DWORD th32ProcessID; //Идентификатор процесса - необходим
//во многих
функциях
DWORD th32DefaultHeapID; //Идентификатор основной кучи -
имеет
//смысл только в
функциях toolhelp
DWORD th32ModuleID; //идентификатор модуля - имеет
//смысл только в
функциях toolhelp
DWORD cntThreads; //Число
потоков
DWORD th32ParentProcessID; //Идентификатор родителя -
возвращается
//Даже если
родителя уже нет
LONG pcPriClassBase; //приоритет по умолчанию всех
//создаваемых процессом потоков
DWORD dwFlags; //Зарезервировано
CHAR
szExeFile[MAX_PATH]; //Собственно имя процесса
} PROCESSENTRY32,*PPROCESSENTRY32,*LPPROCESSENTRY32;
typedef struct tagMODULEENTRY32 {
DWORD dwSize;
//размер структуры
DWORD th32ModuleID; //идентификатор
модуля
DWORD th32ProcessID; //идентификатор процесса, к которому
относится
//модуль
DWORD GlblcntUsage;
//общее число ссылок на этот модуль
DWORD ProccntUsage;
//число ссылко в контексте процесса,
//по
идентификатору которого был создан
//снэпшот. Если
равен 65535 - модуль подгружен
//неявно
BYTE *modBaseAddr;
//адрес модуля в контексте процесса
DWORD modBaseSize;
//размер проекции
HMODULE hModule; //ссылка
на модуль
char szModule[MAX_MODULE_NAME32 + 1]; //Имя модуля
char szExePath[MAX_PATH]; //Полный путь к модулю
} MODULEENTRY32,*PMODULEENTRY32,*LPMODULEENTRY32;
Обратите внмание: ссылка на модуль (параметр hModule) - это первый байт ДОС-заголовка! Таким образом, мы получаем возможность работать с проекцией при некотором
знании структуры PE-файлов. В частности мы можем прочиатать таблицу импорта, и,
как правило, - даже переписать ее (это используется при перехвате АПИ).
Параметр szExePath имеет свой "заскок" - иногда полный путь к модулю
возвращается со странными вставками и, например, всесто
"c:windowssystem32advapi32.dll" я иногда получаю
"c:x86_proc_winsyspathadvapi32.dll". Как правило для системных задач
средней сложности (перехват апи, или, наоборот, перехват стелсов) всего
вышеописанного хватает. Но на этом возможности toolhelp не исчерпываются и
теперь мы побегаем по потокам! Работа с потоками несколько отличается от работы
с модулями - даже если мы сделаем снимок, задав идентификатор какого-либо
процесса, функция Thread32Next не остановится, пока не пробежится по ВСЕМ
потокам в системе. Поэтому мы должны проверять, к какому процессу принадлежит
поток - благо, в структуре THREADENTRY32 есть член th32OwnerProcessID -
идентификатор породившего поток процесса. Таким образом:
int EnumerateThreads(DWORD PID)
{
//Начнем с создания снимка
HANDLE pThreadSnap =
CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, PID);
bool bIsok = false;
//Структура, описывающая поток
THREADENTRY32 ThrdEntry;
//ставим размер
ThrdEntry.dwSize = sizeof(THREADENTRY32);
//Берем первый поток
bIsok = Thread32First(pThreadSnap, &ThrdEntry);
//и бегаем по всем потокам...
while (bIsok)
{
//проверяем, тому ли процессу принадлежит поток
if (ThrdEntry.th32OwnerProcessID == PID)
{
//Если да, то выводим некотурую информацию...
//Хоть она никому нафиг не нужна :о)
printf("%u
%un", ThrdEntry.th32OwnerProcessID, ThrdEntry.th32ThreadID);
}
bIsok = Thread32Next(pThreadSnap, &ThrdEntry);
}
//не забываем чистить память
CloseHandle(pThreadSnap);
return 1;
}
Ну вот, у нас есть потоки. Что еще осталось? Правильно,
остались кучи. Здесь тоже все очень просто:
int EnumerateHeaps(DWORD PID)
{
//Первый параметр - идентификатор процесса
//а второй - основная куча
//Теперь делаем снимок, чтоб перечислить кучки...
HANDLE pSnapHeaps =
CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST, PID);
bool bIsok = false;
bool bIsokHeap = false;
//Структура, в которую будут записываться данные списка
кучи
HEAPLIST32 HpLst;
//Структура, в которую будут записываться данные
//непосредствнно БЛОКОВ КУЧИ
HEAPENTRY32 HpEntry;
//Ставим размеры...
HpLst.dwSize = sizeof(HEAPLIST32);
HpEntry.dwSize = sizeof(HEAPENTRY32);
bIsok = Heap32ListFirst(pSnapHeaps, &HpLst);
while (bIsok)
{
//Теперь перечисляем блоки кучи
//этот код я привел, чтобы стало ясно
//как получить данные по блокам
//но он жрет много времени
//так что я его закомментирую - если вам интересно
//можете погонять...
/*bIsokHeap = Heap32First(&HpEntry, PID,
HpLst.th32HeapID);
while(bIsokHeap)
{
//Выводим немного информации
printf("%u n", HpEntry.dwBlockSize);
//Шагаем дальше
bIsokHeap = Heap32Next(&HpEntry);
}*/
//выводим инфу о куче в общем
printf("%u n", HpLst.dwSize);
//шагаем дальше
bIsok = Heap32ListNext(pSnapHeaps, &HpLst);
}
CloseHandle(pSnapHeaps);
return 1;
}
Ну вот, теперь тока осталось написать о структурах THREADENTRY32, HEAPENTRY32 и HEAPLIST32:
typedef struct tagTHREADENTRY32{
DWORD dwSize; //размер структуры
DWORD cntUsage; //число ссылок
DWORD th32ThreadID; //идентификатор
DWORD th32OwnerProcessID; //родительский процесс
LONG tpBasePri; //основной
приоритет (при инициализации)
LONG tpDeltaPri; //изменение приоритета
DWORD dwFlags; //зарезервировано
} THREADENTRY32;
typedef THREADENTRY32 * PTHREADENTRY32;
typedef THREADENTRY32 * LPTHREADENTRY32;
typedef struct tagHEAPENTRY32
{
DWORD dwSize;
//размер структуры
HANDLE hHandle;
// хэндл этого блока
DWORD
dwAddress; // линейный адрес начала блока
DWORD dwBlockSize;
// размер блока в байтах
DWORD dwFlags; //флаги
/*
LF32_FIXED Блок памяти имеет фиксированную позицию
LF32_FREE Блок
памяти не используется
LF32_MOVEABLE Блок
памяти может перемещаться
*/
DWORD dwLockCount;
число "замков"
DWORD dwResvd; // зарезервировано
DWORD
th32ProcessID; // родительский процесс
DWORD
th32HeapID; // идентификатор кучи
} HEAPENTRY32;
typedef HEAPENTRY32 *
PHEAPENTRY32;
typedef HEAPENTRY32 *
LPHEAPENTRY32;
typedef struct tagHEAPLIST32
{
DWORD dwSize;
//размер структуры
DWORD
th32ProcessID; // родительский
процесс
DWORD
th32HeapID; //куча в
контексте процесса
DWORD dwFlags; //флаг.
Значение всегда одно:
// HF32_DEFAULT - основная куча процесса
} HEAPLIST32;
вызовы функций EnumerateHeaps, EnumerateThreads и
EnumerateModules можно проводить из EnumerateProcs. Все скомпилино в Visual C++
6.0. В тексте использована информация из MSDN и книги Джеффри Рихтера
"Создание эффективных win32 приложений" (имхо эта книга - настольная
для системного программиста).
Список литературы
Для подготовки данной работы были использованы материалы
с сайта http://www.realcoding.net