While cross-referencing notes against old blog posts, I realized that I never actually published the majority of my work on system calls and user mode hooking.
System calls are the standard way to transition from user mode to kernel mode.
On Windows, the kernel has a table of functions that are allowed to be called from user mode.
To call a system service from user mode a system call must be performed, which is done via the syscall instruction.
The System Service ID, is the index of the function's entry within the SSDT. So, setting eax to 0 will call the first function in the SSDT, 1 will call the second, 2 will call the third, and so on.
The ntdll versions of these functions simply perform syscalls to call their kernel mode counterparts, which is why they're often referred to as system call stubs.
This is due to the fact that Nt functions are designed to be called from user mode, therefore do more extensive validation of function parameters.
Previously, security products monitored user mode calls from inside the kernel by hooking the SSDT. Since all Nt/Zw functions are implemented in the kernel, all user mode calls must go through the SSDT, and are therefore subject to SSDT hooks.
Once the EDR is done, it can resume the ntdll call by executing the overwritten instructions, then jumping to the location in ntdll right after the hook.
Whenever the EDR wants to call the real NtWriteFile, it executes the 3 overwritten instructions, then jumps to the 4th instruction of the hooked function to complete the syscall.
If the SSN for the function we want to call is 0x18, then the one directly before it will likely be 0x17 and the one directly after, 0x19. Since the EDR doesn't hook every Nt function, we can simply grab the SSN from the nearest non-hooked function, then calculate the one we want by adding or subtracting how many functions are between it and our target function.
The function before it is system call number 0x17, and the function after it is 0x19. We can easily assume that the SSN we want is 0x18. This method does have one flaw though: we can't 100% guarantee system call numbers will remain sequential forever, or the DLL won't skip a few.
If we perform a manual syscall, and somewhere along the way the kernel function we call hits any of the above, the EDR could take the opportunity to inspect the callstack of our thread. By unwinding the call stack and inspecting return addresses, the EDR can see the entire chain of function calls that led to this syscall.
This tells us that the executable called VirtualAlloc(), which called NtAllocateVirtualMemory(), which then performed a system call to transition into kernel mode.
Because direct system calls are such a strong indicator of malicious activity, more sophisticated EDRs will log detections for system call that originated outside ntdll.
One issue we could run into is if the EDR hooks or overwrites the syscall instruction part of the Nt call.
Not just where the syscall came from, but who called the function that executed the syscall.
Since the function is hooked by the EDR, the EDR's hook would be expected to appear in the call stack.
As you can see here it's clear from the call stack that we bypassed the EDR hook.
The EDR's hook is visible in the call stack from a regular call but not from an indirect syscall.
This Cyber News was published on malwaretech.com. Publication date: Mon, 25 Dec 2023 08:13:05 +0000