Windows DLL user space calling process

178 views Asked by At

I'm trying to understand how Windows recursively loads DLLs in user space.

Tracing kernel32.ReadProcessMemory as an example:

The first step for ReadProcessMemory is the IAT of kernel32:

00007FF901F6AFA0 | 48:FF25 21D20500 | jmp qword ptr ds:[<&ReadProcessMemory>] |

Which jmp's to kernelbase.ReadProcessMemory:

00007FF9002D22F0 | 48:83EC 48               | sub rsp,48                                   |
00007FF9002D22F4 | 48:8D4424 30             | lea rax,qword ptr ss:[rsp+30]                |
00007FF9002D22F9 | 48:894424 20             | mov qword ptr ss:[rsp+20],rax                |
00007FF9002D22FE | 48:FF15 C3521400         | call qword ptr ds:[<&ZwReadVirtualMemory>]   |
<snip>

Which call's ntdll.ZwReadVirtualMemory:

00007FF902F5C840 | 4C:8BD1                  | mov r10,rcx                                  |
00007FF902F5C843 | B8 3F000000              | mov eax,3F                                   | 3F:'?'
00007FF902F5C848 | F60425 0803FE7F 01       | test byte ptr ds:[7FFE0308],1                |
00007FF902F5C850 | 75 03                    | jne ntdll.7FF902F5C855                       |
00007FF902F5C852 | 0F05                     | syscall                                      |
00007FF902F5C854 | C3                       | ret                                          |
00007FF902F5C855 | CD 2E                    | int 2E                                       |
00007FF902F5C857 | C3                       | ret                                          |

So the flow from user mode in this example is:

  1. kernel32.ReadProcessMemory
  2. kernelbase.ReadProcessMemory
  3. ntdll.ZwReadVirtualMemory

The expectation would be that each of the above DLLs can 'find' the appropriate function based on their IAT/imported functions from other DLLs when they are loaded.

Using dumpbin and tracing the IMPORTS this is true for kernel32.ReadProcessMemory (where api-ms-win-core-memory-l1-1-0.dll is an ApiSet for kernelbase.dll):

    api-ms-win-core-memory-l1-1-0.dll
             180078178 Import Address Table
             18009E120 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                          35 VirtualQueryEx
                          <snip>
                          1C ReadProcessMemory

However, this is not true for kernelbase.dll - NtReadVirtualMemory is imported, however ZwReadVirtualMemory is not imported:

    ntdll.dll
             1801A67C8 Import Address Table
             180262A48 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                         893 __C_specific_handler
                         <snip>
                         205 NtReadVirtualMemory

So, my question is: during the DLL load process, how does kernelbase.dll identify the 'location' of ZwReadVirtualMemory if it isn't imported?

The ZwReadVirtualMemory function is being called by kernelbase.dll, so it must have been resolved/stored in the IAT at some point, but how does this happen technically?

Is there some indirection where the loader maps NtReadVirtualMemory to ZwReadVirtualMemory as these functions resolve to the same address?

0

There are 0 answers