Detecting Hooked Syscalls
It's possible to enumerate which Windows API calls are hooked by an EDR using inline patcihng technique, where a jmp instruction is inserted at the beginning of the syscall stub to be hooked.
Related Notes
Windows API HookingBypassing Cylance and other AVs/EDRs by Unhooking Windows APIsAPI Monitoring and Hooking for Offensive ToolingWalkthrough
Function before Hooking
Below shows the stub for for NtReadVirtualMemory on a system with no EDR present, meaning the syscall NtReadVirtualMemory is not hooked:

We can see the NtReadVirtualMemory syscall stub starts with instructions:
...which translates to the following 4 opcodes:
4c 8b d1 b8 - are important for this lab - we will come back to this in a moment in a section Checking for Hooks.
Function after Hooking
Below shows an example of how NtReadVirtualMemory syscall stub looks like when it's hooked by an EDR:

Note that in this case, the first instruction is a jmp instruction, redirecting the code execution somewhere else (another module in the process's memory):
...which translates to the following 5 opcodes:
Checking for Hooks
Knowing that interesting functions/syscalls (that are often used in malware), starting with Nt | Zw, before hooking, start with opcodes: 4c 8b d1 b8, we can determine if a given function is hooked or not by following this process:
Iterate through all the exported functions of the ntdll.dll
Read the first 4 bytes of the the syscall stub and check if they start with
4c 8b d1 b8If yes, the function is not hooked
If no, the function is most likely hooked (with a couple of exceptions mentioned in the False Positives callout).
Below is a simplified visual example attempting to further explaine the above process:
NtReadVirtualMemorystarts with opcodese9 0f 64 f8rather than4c 8b d1 b8, meaning it's most likely hookedNtWriteVirtualMemorystarts with opcodes4c 8b d1 b8, meaning it has not been hooked

False Positives Although highly effective at detecting functions hooked with inline patching, this method returns a few false positives when enumerating hooked functions inside ntdll.dll, such as:
`NtGetTickCount
NtQuerySystemTime
NtdllDefWindowProc_A
NtdllDefWindowProc_W
NtdllDialogWndProc_A
NtdllDialogWndProc_W ZwQuerySystemTime`
The above functions are not hooked.
Code
Below is the code that we can compile and run on an endpoint running an AV/EDR to see enumerate APIs that were most likely hooked:
Demo
Below is a snippet of the output of the program compiled from the above source code and run on a system with an EDR present. It shows some of the interesting functions (not all displayed) that are most likely hooked, with an exception of NtGetTickCount, which is a false positive, as mentioned earlier:

Updates
After I've posted this note on my twitter, I got the following reply from Derek Rynd:

Derek is suggesting to check if the syscall instruction itself is not hooked. The syscall handler routine (responsible for locating functions in the SSDT based on a syscall number) location can be found by reading the Model Specific Register (MSR) at location 0xc0000082 and confirming that the address stored there points to nt!KiSystemCall64Shadow.
Below shows how this could be done manually in WinBDG:

References
Last updated
Was this helpful?