Since you're here I'm assuming you know what NT functions are already, however for the sake of a comprehensive post here are the basic Windows internals concepts required to understand what and why we have NT functions:
When programming for Windows in C you're likely used to "importing" the Windows libraries with #include <windows.h>
. This header file contains the most common Windows API function definitions, an example of a common function used by Windows applications would be VirtualAlloc()
which is used to allocate regions of memory. If we attach a debugger to a program and watch what happens when we call this function, stepping down the line far enough you'll see a call to NtAllocateVirtualMemory()
. Functions starting with "Nt" or "Rtl" belong to what is known as the Native API (stored in ntdll.dll
) and is the lowest level user-callable API before the kernel and was likely designed for use by Windows developers to build more complex and lower level Windows components as the original API before creating "safer" wrapper functions like the ones found in kernel32.dll
.
See below a simplified example of the Windows API hierarchy:
_________________________________________ | | | VirtualAlloc (kernel32.dll) | |_________________________________________| | [User Land] | V _________________________________________ | | | NtAllocateVirtualMemory (ntdll.dll) | |_________________________________________| | | | Syscall Executed ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | [Kernel Space] | V _________________________________________ | | | System call Interface | |_________________________________________|
"Ok so normal functions from Windows userland dlls call a lower level couterpart which then forwards the call to the kernel using a syscall. Why would I ever want to manually call an NT function instead of using the intended higher level function?" one might ask. Well here are some reasons why we may want to use Native API functions directly:
// CreateThread Definition typedef HANDLE(WINAPI* _CreateThread)( OUT LPSECURITY_ATTRIBUTES lpThreadAttributes OPTIONAL, IN SIZE_T dwStackSize, IN LPTHREAD_START_ROUTINE lpStartAddress, IN LPVOID lpParameter OPTIONAL, IN DWORD dwCreationFlags, OUT LPDWORD lpThreadId OPTIONAL ); // NtCreateThreadEx Definition typedef NTSTATUS(NTAPI* _NtCreateThreadEx)( OUT PHANDLE ThreadHandle, IN ACCESS_MASK DesiredAccess, IN PVOID ObjectAttributes OPTIONAL, IN HANDLE ProcessHandle, IN PVOID StartRoutine, IN PVOID Argument OPTIONAL, IN ULONG CreateFlags, IN SIZE_T ZeroBits, IN SIZE_T StackSize, IN SIZE_T MaximumStackSize, IN PVOID AttributeList OPTIONAL );
Since the Native API is not supposed to be actively used by developers it's not as simple as including ntdll.dll
and then calling the functions we want. So how do we go from zero to calling an NT function? Well here are the steps we need to perform:
Reverse engineering function definitions yourself may not be the best use of time, instead see my favourite resources:
For this example I'll be allocating a small section of memory using NtAllocateVirtualMemory instead of VirtualAlloc:
#include <stdlib.h> #include <windows.h> // Define the function structure typedef NTSTATUS(NTAPI* _NtAllocateVirtualMemory)( IN HANDLE ProcessHandle, IN OUT PVOID* BaseAddress, IN ULONG ZeroBits, IN OUT PSIZE_T RegionSize, IN ULONG AllocationType, IN ULONG Protect ); // Declare a function pointer named NtAllocateVirtualMemory of type _NtAllocateVirtualMemory _NtAllocateVirtualMemory NtAllocateVirtualMemory; int main() { char MyData[] = "felixm.pw"; PVOID pMyDataAddress = NULL; SIZE_T sMyDataSize = sizeof(MyData); // Resolve the function pointer for NtAllocateVirtualMemory directly from ntdll.dll NtAllocateVirtualMemory = (_NtAllocateVirtualMemory)GetProcAddress(GetModuleHandleA("ntdll"), "NtAllocateVirtualMemory"); // Call function then check if it succeeded NTSTATUS ntStatusAllocate = NtAllocateVirtualMemory(GetCurrentProcess(), &pMyDataAddress, 0, &sMyDataSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READ); if (!NT_SUCCESS(ntStatusAllocate)) { printf("NtAllocateVirtualMemory Failed!"); return 0; } return 1; }
This example should be enough for you to hack your way through implementing your own NT use cases. As per usual any questions, shoot me a direct message on twitter!