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