Retrieving ntdll Syscall Stubs from Disk at Run-time

Overview

The purpose of this lab was to play with syscalls once more. More specifically, the goal was to be able to retrieve ntdll syscall stubs from the disk during run-time (before AVs/EDRs get a chance to hook them), rather than hardcoding them, since they may change between different Windows versions.

This lab was sparked by am0nsec's and RtlMateusz's https://github.com/am0nsec/HellsGate although is very different in implementation and execution.

See my previous post on syscalls too:

Calling Syscalls Directly from Visual Studio to Bypass AVs/EDRs

I will write some crude code that will do the following:

  1. Read ntdll.dll file bytes from the disk (before any AV/EDR has a chance to hook its functions) and write them to some memory location m1

  2. Parse out .rdata and .text sections of the ntdll.dll file

    1. .rdata contains ntdll exported function names

    2. .text contains code that gets executed by those functions

  3. Locate the specified function's code (syscall) in the memory location m1. In this lab I will find the location where the stub code for NtCreateFile resides

  4. Extract the stub (23 bytes) of the NtCreateFile and write it to some memory location m2

  5. Declare a function prototype for NtCreateFile

  6. Define a variable v1 of function type NtCreateFile and point it to the memory location m2, where the syscall stub for NtCreateFile is written, as mentioned in step 4.

  7. Invoke the NtCreateFile syscall by calling the syscall v1, which actually points to m2, where NtCreateFile syscall stub is stored

  8. NtCreate syscall gets executed - profit

Note, that the above process is just one way of achieving the same goal.

Reminder

As a reminder, we can easily see the syscall IDs for NT functions via WinDBG or any other debugger.

The syscall ID is 2 bytes in length and starts 4 bytes into the function, so for example, the syscall ID for NtCreateFile is 0x0055, NtQueryEvent is 0x0056, etc - see below image.

Also - in green are the bytes, that I refer to as syscall stub forNtCreateFile and these are the bytes that we want to be able to retrieve at run-time for any given NT function, and hence this lab.

orange - syscall function name and its id, green - syscall stub

Extracting the Syscall Stub

I wrote a function GetSyscallStub, that is responsible for steps 3 and 4 of the processes that I outlined in the Overview section.

It allows me to find any given function's code location inside the ntdll.dll and carve out its syscall stub (the first 23 bytes):

So, for example, if I wanted to retrieve the syscall stub for NtCreateFile, I would call GetSyscallStub like so:

Once GetSyscallStubis called, it will cycle through all the ntdll exported function names (they are resolved to functionNameResolved) as well as exported function addresses simulatenously, and look for the function we want to extract the syscall stub for, which in our case is the NtCreateFile (passed to GetSycallStub via functionName):

Once the needed function name is resolved, the given function's syscall stub is extracted and stored in the syscallStub variable.

In the below GIF, we can see the instruction mov eax, 0x55 when viewing the syscallStub variable in a disassembly view. Since we know that the NtCreateFile syscall ID is 0x0055, this suggests we have extracted the syscall stub successfully:

Calling Syscall Stub

In order to be able to invoke the syscall, we need to define a variable NtCreateFile of type myNtCreateFile (see code section for the function prototype), point it to the syscallStub and make syscallStub executable:

We can now call NtCreateFile:

Below shows how NtCreateFile gets called on a file c:\temp\pw.log and a handle to that file is opened, which confirms that NtCreateFile syscall stub was retrieved and called successfully:

Code

References

Last updated

Was this helpful?