QueryFullProcessImageNameW Under-the-Hood - Reversing NtQueryInformationProcess

How a path across processes can be obtained from PEB's LDR linked list through QueryFullProcessImageNameW under the hood.

I'd like to preface this by saying that I am not a reverse engineer & I barely know what I'm doing most of the time. You learn from your struggles.

This whole journey began when I was asked how you'd obtain the full path across processes. I didn't think too hard and just answered that it could be obtained from PEB's LDR linked list and through QueryFullProcessImageNameW. This answer haunted me for a while because I didnt really know how this happens. So I decided to peel away the layers of abstraction and record the process.

First, I did a bit of googling and found out that this function is exported by kernel32.

As seen above this function starts in kernel32, typically functions like VirtualAlloc in the same DLL are forwarded to kernel base instead using a non-conditional jmp instruction. The best way to trace these, as microsoft does not document forwarded calls well, is to hop into the debugger and follow the execution. Which is what well do in this blog to jump into the rabbit hole. Typically they will use a non-conditional jmp instruction to move into another DLL. So with kernel32.

Drag kernel32 into IDA to see the disassembly. We'll find our function name QueryFullProcessImageNameW

When you jmp into another memory space, you can look in the memory map/library to see the DLL name

I found that the function exported by kernel32 is actually a forwarding function. It is exported by api-ms-win-core-psapi-l1-1-0.dll. If you've been in this situation before you'll know that many api-ms* dlls are all on disk. I couldn't find it at all, I found it and dragged it into IDA, and found that it was just a string. So let's drag api-ms-win-core-psapi-l1-1-0.dll into IDA and have a look at it.

In fact, Microsoft tries to separate the API architecture from the specific implementation, but often a dll contains a large number of implementations of different systems (such as kernelbase), so Microsoft has proposed a solution called (virtual dlls), which is established through virtual dlls. A mapping table is forwarded to the implementation dll, so that the API system can be separated from the implementation. For specific details, please refer to this article: Article-1 Article-2.

So IDA looks like it's showing you the "Virtual DLL" name and not the Logical name. Now that we know this we'll go ahead and get the real dll kernelbase.dll and drag it into IDA.

After a quick read of the code, it turns out that in reality QueryFullProcessImageNameW is realized realized by NtQueryInformationProcess, and the incoming query parameter is flag*16 27, According to the parameter detection in front of the micro, there are only two values 0 or 1 that can be passed in to view the document.

If we wanted we could check if its ring0 and ring3, knowing that the query parameters that will be passed in according to the logic seen above will be 27 or 43 respectively. But, let's use ReactOS to try to see if this option is available in the old NT kernel. The reason why ReactOS is used instead of the leaked windows source code is because ReactOS has maintained documentation, and I like my life. Therefore, it is more convenient to cross query check.

So as we can see in the image above, the answer to my friends question is a little more clear now. We better understand what going on under-the-hood when it comes to obtaining the full path across processes. So ProcessImageFileName of NtQueryInformationProcess is actually all being done under the hood from QueryFullProcessImageNameW.