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.
I will write some crude code that will do the following:
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
Parse out .rdata and .text sections of the ntdll.dll file
.rdata contains ntdll exported function names
.text contains code that gets executed by those functions
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
Extract the stub (23 bytes) of the NtCreateFile and write it to some memory location m2
Declare a function prototype for NtCreateFile
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.
Invoke the NtCreateFile syscall by calling the syscall v1, which actually points to m2, where NtCreateFile syscall stub is stored
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.
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:
GetSyscallStub( // function name for which the syscall stub is to be retrieved"NtCreateFile", // ntdll export directory exportDirectory, // ntdll file bytes fileData, // ntdll .text section descriptor - contains code of ntdll exported functions. Required for locating NtCreateFile syscall stub textSection, // ntdll .rdata section descriptor - contains name of ntdll exported functions. rdataSection, // NtCreateFile stub will be written here syscallStub);
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:
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: