Calling NT Functions in C


Contents

What Is An NT Function?


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          |
|_________________________________________|

Note

This diagram is very over simplified as many intermediary functions are called post NT execution however I would advise you to open a debugger and follow a kernel32 call down the rabbit hole yourself as a learning exercise!

"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:

  • 1. We get more granular control over the function as we gain access to more parameters. Using CreateThread as an example, we can see the difference in usable parameters by looking at their function definitions:
    // 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
    );
    
  • 2. We can call functions that are just not available via the higher level APIs. These functions are generally intended to be used by Windows developers who may need specific functions or features for native Windows programs and protocols. The Native API is not officially documented thus we have to resort to third parties who have reverse engineered the DLL to find information on some of the more obscure, lesser known functions.

How to Call NT Functions


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:

  • 1. Define the function structure
  • 2. Retrieve the function pointer from ntdll
  • 3. Call the NT function via the function pointer

Reverse engineering function definitions yourself may not be the best use of time, instead see my favourite resources:

Code Example (in C)


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!