Несподівана поведінка WinAPI-функції IsWow64Process()

Ця замітка пишеться для тих, хто коли-небудь буде гуглити назва WinAPI-функції IsWow64Process() у спробах зрозуміти, чому ж вона іноді працює не так, як це описано в MSDN. Цілком можливо, що це буду я сам через рік-другий. Але, можливо, знадобитися комусь ще.

Отже, про що ж йде мова? Операційна система Windows, як відомо, буває 32-бітної або 64-бітної. На 32-бітної Windows можна запустити тільки 32-бітові додатки — а значить питання «це 32-бітове додаток або 64-бітове?» там просто не має сенсу, відповідь відома заздалегідь. Життя на 64-бітному варіанті Windows трохи веселіше — тут можна запускати як 64-бітні додатки (вони вважаються нативними), так і 32-бітні, які не є рідними для ОС, і виконуються вони в спеціальній підсистемі WoW64 (Windows-on-Windows 64-bit). Ця підсистема включає в себе засоби запуску 32-бітного коду, окремі гілки реєстру та системні папки для роботи 32-бітних додатків в 64-бітної середовищі.

Іноді буває важливо знати, чи є деякий процес, що працює в 64-бітної Windows, дійсно нативним 64-бітним процесом, або WoW64-процесом (тобто 32-бітним додатком, що працює в WoW64-підсистемі). Для цих цілей Microsoft пропонує використовувати функцію IsWow64Process(). Опис в MSDN досить детально, є пара попереджень на рахунок способу її виклику, але в загальному-то все тривіально. Приклад коду навіть є. Біда тільки в тому, що в деяких випадках ця функція бреше і визначає архітектуру процесу невірно.


Давайте напишемо тестове додаток, яке буде запитувати у користувача PID процесу і визначати його архітектуру. За основу візьмемо код з MSDN. Додамо в нього обробку помилок — тобто на вході у нас PID процесу, а на виході один з трьох варіантів — «не вдалося визначити», «це 64-бітний процес», «це WoW64-процес».

#include <windows.h>
#include < iostream>


bool IsWow64(DWORD pid, BOOL &isWow64)
{
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (hProcess == NULL)
return false;

typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
LPFN_ISWOW64PROCESS fnIsWow64Process;
fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");

bool res = fnIsWow64Process != NULL && fnIsWow64Process(hProcess, &isWow64);
CloseHandle(hProcess);
return res;
}

int main(void)
{
for (;;)
{
std::cout << "Please enter PID: ";

DWORD pid;
std::cin >> pid;

BOOL isWow64 = false;
BOOL resultKnown = IsWow64(pid, isWow64);

if (resultKnown == false)
std::cout << "Process type is unknown";
else
std::cout << "Process type is " << (isWow64 ? "x86 (wow64)" : "x64");

std::cout << std::endl << std::endl;
}

return 0;
}


Давайте запустимо нашу програму, а поруч з нею відкриємо диспетчер завдань (або Process Hacker, який я люблю більше) щоб бачити архітектуру процесів та їх PIDы. Потестируем нашу програму.


На перший погляд все ок: неіснуючий PID не визначився, 32-біт і 64-бітні програми були визначені вірно.

Йдемо далі. Запускаємо Chrome. При запуску він стартує деяку кількість дочірніх процесів, що відповідають за візуалізацію, обробку контенту сторінок і т. д. Давайте спробуємо визначити бітність одного з таких процесів:


Все ок, це 32-бітове додаток.

А тепер робимо ось такий фінт вухами: набираємо в нашому тестовому додатку той же PID, вбиваємо дочірній процес Chrome з цим PID в Process Hacker, швидко повертаємося в наш тестове додаток і тиснемо Enter. І бачимо прекрасну картину:


Вбитий тільки що процес не визначається як «не знайдений» (на кшталт того PID 999999 з прикладу вище). Вона також не визначається як 32-бітний (яким він був за життя). Він визначається чітко і ясно як існуючий у системі 64-бітний процес. Але як ?! Чому?

А ось чому.

Коли ми вбиваємо деякий процес — він завершує свою роботу не відразу. Його потоки зупиняються, зайнята їм пам'ять звільняється, але піде процес повністю залежить не від нього, а від того, є чи якого-небудь іншого процесу відкриті дескриптори (HANDLE) на цей процес. Ну, знаєте, можливо хтось хотів з ним як-небудь взаємодіяти. Це може бути, наприклад, антивірус, вірус, системна утиліта начебто Process Hacker, батьківський процес і т. д… Якщо у кого-небудь з них залишився висіти відкритий дескриптор на процес — він перейде в стан «зомбі» і буде перебувати в ньому, поки щось буде продовжувати тримати його в цьому тлінному світі. У цьому стані він вже не виконує код ні в одному потоці, але все ще існує як сутність операційної системи — наприклад, він займає свій «прижиттєвий» PID і жоден процес не може отримати такий же PID, поки «зомбі» не помре повністю. Тут ви вже можете здогадатися, чому я запропонував приклад з дочірнім процесом Chrome — батьківський процес Chrome тримає дескриптор дочірнього процесу, а це прямий йому шлях у «зомбі»-процеси.

Повернемося до нашої проблеми — чому ж функція IsWow64Process() визначає архітектуру цього процесу невірно? А тут все дуже просто — при переході процесу в стан «зомбі» підсистема WoW64 в ньому зупиняється і вивантажується. Вона вже не потрібна (немає ніяких варіантів знову повернути «зомбі» до життя) — так навіщо позичати ресурси? У підсумку, поцікавившись архітектурою деякого процесу не вчасно, ми можемо отримати невірний результат.

До речі, Chrome — якісний продукт, він швидко визначає факт смерті свого дочірнього процесу, відпускає його дескриптор (що дає «зомбі» шанс упокоїтися з миром) і пересотворює процес даного типу. У підсумку, викликавши ту ж функцію для тієї ж PID через кілька секунд ви побачите таку картину:


Як з цим боротися?

Та дуже просто — крім виклику IsWow64Process() вам необхідний ще і виклик функції GetExitCodeProcess(), яка для ще живих (не «зомбі») процесів завжди буде повертати STILL_ACTIVE. За цією ознакою можна зрозуміти «зомбі» перед вами чи ні і чи варто вірити результату IsWow64Process(). Тут, звичайно, виникає питання, що ж робити, коли зрозуміє, що це «зомбі», а значить його архітектура нам невідома по-визначенням. Єдиною відповіддю на це може бути питання, а що ж ви взагалі збираєтеся робити з «зомбі», з яким вдіяти вже нічого розумного не можна. В абсолютній більшості випадків перед вами буде стояти зворотна задача — знайти «живий» процес, а для отримання інформації з нього комбінація GetExitCodeProcess() + IsWow64Process() чудово спрацює.

Ось і все, що я хотів розповісти про функції IsWow64Process().
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.