⬆️ ⬇️

Unexpected behavior of the WinAPI function IsWow64Process ()

This note is written for those who will sometime google the name of the WinAPI function IsWow64Process () in an attempt to understand why it sometimes does not work in the way described in MSDN. It is possible that it will be me in a year or two. But it may be useful to someone else.



So what are we talking about? The Windows operating system, as you know, is 32-bit or 64-bit. On 32-bit Windows, you can run only 32-bit applications - which means the question “is this a 32-bit application or 64-bit?” There simply does not make sense, the answer is known in advance. Life on the 64-bit version of Windows is a bit more fun - here you can run both 64-bit applications (they are considered native) and 32-bit applications that are not native to the OS, and they run in a special WoW64 subsystem (Windows-on-Windows 64-bit) . This subsystem includes 32-bit code launchers, separate registry branches and system folders for 32-bit applications to work in a 64-bit environment.



Sometimes it is important to know whether a process running on 64-bit Windows is a truly native 64-bit process, or a WoW64 process (that is, a 32-bit application running on a WoW64 subsystem). For these purposes, Microsoft suggests using the IsWow64Process () function. The description in MSDN is quite detailed, there are a couple of warnings about how to call it, but in general everything is trivial. There is even a sample code. The only trouble is that in some cases this function lies and defines the architecture of the process incorrectly.



')

Let's write a test application that will ask the user for the PID of the process and determine its architecture. We take the code from MSDN as a basis. Add error handling to it - i.e. at the entrance we have a PID process, and at the output one of the three options - “could not be determined”, “this is a 64-bit process”, “this is a WoW64 process”.



#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; } 


Let's run our program, and next to it open the task manager (or Process Hacker, which I like more) to see the architecture of processes and their PIDs. We test our program.







At first glance, everything is OK: a non-existent PID was not defined, 32-bit and 64-bit applications were correctly identified.



Go ahead. Launch Chrome. When it starts, it starts a number of child processes responsible for rendering, processing the content of pages, etc. Let's try to determine the bit depth of one of these processes:







Everything is OK, this is a 32-bit application.



And now we are doing such a feint with our ears: type in our test application the same PID, kill the Chrome child process with this PID in Process Hacker, quickly return to our test application and hit Enter. And we see a beautiful picture:







The killed process is not defined as “not found” (like the PID 999999 from the example above). It is also not defined as 32-bit (which it was in life). It is defined clearly and clearly as a 64-bit process existing in the system. But how ?! Why?



But why.



When we kill a process, it doesn’t complete its work immediately. Its threads stop, the memory it occupies is freed, but whether the process completely leaves does not depend on it, but on whether any other process has open descriptors (HANDLE) to this process. Well, you know, maybe someone wanted to interact with him somehow. This could be, for example, an antivirus, a virus, a system utility like Process Hacker, a parent process, etc. ... If any of them have an open handle on the process left hanging, it will go into the zombie state and be in it, for the time being something will continue to keep him in this mortal world. In this state, it no longer executes code in any thread, but still exists as an entity in the operating system — for example, it occupies its “lifetime” PID and no process can get the same PID until the zombie dies completely . Here you can already guess why I proposed an example with a Chrome child process - the Chrome parent process holds the handle of the child process, and this is a direct path to it in the “zombie” processes.



Let us return to our problem - why does the IsWow64Process () function determine the architecture of this process incorrectly? And here everything is very simple - when the process switches to the zombie state, the WoW64 subsystem stops and unloads in it. It is no longer needed (there are no options to bring the zombies back to life) - so why borrow resources? As a result, inquiring about the architecture of some process at the wrong time, we can get the wrong result.



By the way, Chrome is a quality product, it quickly detects the fact of the death of its child process, releases its descriptor (which gives “zombies” a chance to rest in peace) and recreates the process of this type. In the end, by calling the same function for the same PID, after a few seconds you will see the following picture:







How to deal with it?



It's very simple - in addition to the IsWow64Process () call, you also need a call to the GetExitCodeProcess () function, which will always return STILL_ACTIVE for processes that are still alive (not “zombies”). On this basis, you can understand the "zombies" in front of you or not, and whether it is worth believing the result of IsWow64Process (). Here, of course, the question arises what to do when we realize that this is a “zombie”, which means its architecture is unknown to us by definition. The only answer to this may be a question, but what are you going to do with a “zombie”, with whom you can’t do anything clever? In the absolute majority of cases, you will be faced with the inverse problem of finding a “live” process, and to obtain information on it, the combination GetExitCodeProcess () + IsWow64Process () will work fine.



That's all that I wanted to tell you about the function IsWow64Process ().

Source: https://habr.com/ru/post/308490/



All Articles