Skip to content

Finding Unexported ZwXx Functions Reliably

Bill Demirkapi edited this page Aug 5, 2020 · 1 revision

One of the issues I faced during development was that certain functions such as ZwCreateUserProcess are not reliably exported by the Windows kernel. At the end of the day, this was still a ZwXx function and I thought there had to be a way to find it without running into stability issues. 0xNemi pointed out that the ZwCreateUserProcess function sets the same system call number seen in NtCreateUserProcess, the user-mode equivalent of the function, and that we can use the number inside of NtCreateUserProcess to find ZwCreateUserProcess. How do we go about doing this?

Well it's actually pretty simple. First of all, the system call number is going to change across different Windows versions; hard-coding the system call is not an option. Instead I opted to extract the system call number by parsing ntdll.dll myself.

  1. The rootkit loads ntdll.dll into member.
  2. We find the NtCreateUserProcess function by enumerating the export address table (EAT) of the ntdll.dll module.
  3. We can extract the system call number easily since NtXx functions follow the same assembly format starting with a mov r10, rcx and then a mov eax, [system call number].

With the system call number in hand, finding the ZwXx equivalent is simple as well. ZwXx functions use the following format:

push rax
mov eax, [system call number]
jmp KiServiceInternal

To find the ZwXx function, all we need to do is search the executable sections of ntoskrnl.exe for the above format except replacing the [system call number] with the number we found previously. That's it! Here is the NtFunctionResolver.cpp source in case you'd like to see the code.