Indirect Syscall in CSharp
19 September 2022
Introduction
While doing CSharp tradecraft development, I was wondering if there is any SysWhispers like implementation in CSharp and I found an excellent project from SECFORCE called SharpWhispers. This makes my life so much easier by providing functions (SharpASM) to execute assembly and re-implement the "Sorting by System Call Address" way to look for syscall number (SSN) like what SysWhispers2 did.
With the increasing use of direct syscall as an evasion technique against EDR API hooking, some detection strategies such as "Mark of the Syscall" signature and execution of syscall instruction originating from outside of NTDLL were developed to identify abnormal syscall usage in both static and dynamic perspective.
Therefore, KlezVirus implemented another solution called SysWhispers3 to demonstrate indirect syscall technique, which can be used to bypass detection strategies mentioned above.
By implementing indirect syscall, you could enjoy the following benefits:
Avoid having syscall instruction in your payload
Ensure the syscall execution is always originated from legitimate NTDLL
In order to provide better evasion capability to my loader, I started implementing indirect syscall in CSharp based on the SharpWhispers and SysWhispers3 implementation and this blog will document the key steps that I did to achieve it.
Implementing Indirect Syscall in CSharp
Indirect syscall technique aims to replace the original syscall instruction with a jump instruction pointing to a memory address of NTDLL where it stores the syscall instruction.
For instance, the offset 0x12 of each NTDLL API (i.e., NtAllocateVirtualMemory) will generally be the syscall instruction as shown below:
To obtain the syscall address of each NTDLL API, we could walk through the loaded NTDLL in the current process to obtain the address of each NTDLL exported functions and calculate the offset 0x12 and 0x0f respectively to obtain address pointing to the syscall/sysenter (syscall equivalent in 32-bit OS) instruction.
The original SharpWhispers had already did the hard part to locate the export table directory and the relative virtual address of each NTDLL API function. My part will be trying to re-implement the similar function as SysWhispers3 did in CSharp to obtain the address of syscall instruction for each NTDLL APIs.
The original SysWhisper3 implementation used a fixed offset to calculate the syscall. However, it could be possible to fail to find the syscall instruction if EDR hooking in installed and it was mentioned in his blog post.
To ensure I always locate the syscall instruction, I included additional search in case the static offset failed to find the syscall instruction by searching byte by byte for syscall instruction that is next to the NTDLL API address.
Then, I edited in the original SYSCALL_ENTRY object to have additional attribute to store the address of the syscall instruction for each NTDLL API.
In addition, additional check (iswow64()) is added to determine if it is Windows 32-bit on Windows 64-bit (WoW64) and skip the syscall instruction search to minimize overhead to the payload.
Once the syscall list is populated, the GetSyscallAddress function will be used to randomly select a syscall address from the syscall entry list every time I want to execute a syscall.
Apart from the function to populate list of syscall address for indirect syscall. Updating the syscall stub assembly is also required.
Syscall Stub For Indirect Syscall in x64
Unlike the SysWhispers2/3 syscall stub implementation, the CSharp version of syscall stub will not call the getSyscallNumber and getSyscallAddress functions in the assembly code. Instead, these functions will be executed separately and update the stub template afterward. Therefore, there is no need to handle the CPU registers since the stack is not changed.
The updated syscall stub will now assign a randomly generated NTDLL syscall address to R11 followed by a jump instruction to achieve indirect syscall. The x64 syscall stub will be as simple as below:
Syscall Stub For Indirect Syscall in x86
For x86 syscall stub, it will be a little bit more complicated than x64 since the syscall stub needs to be changed to support running syscall on both 32-bit OS and 64-bit OS (wow64).
The original syscall stub from SharpWhispers as shown below supports only x86 execution in 64-bit OS by calling fs:[C0] (KiFastSystemCall).
However, the existing stub does not support 32-bit OS since 32-bit OS Windows executes syscall differently. Thus another instruction called "sysenter" will be used, which is an instruction similar to "syscall" for x86 syscall execution. The instruction can be easier inspected from the WinDBG in 32 bit OS.
In order to support both 32/64 bit OS execution, the syscall stub will be re-structured to first determine if it is wow64 and redirect to proper instructions to execute syscall.
The x86 syscall stub will be separated in few parts. Firstly, the syscall number will be assigned to EAX register.
Referring to SysWhispers2, a test will be conducted to validate if fs:[C0] exists to determine the architecture of the operating system (32/64bit).
The TEB 0xC0 offset will show different result depending on the architecture of the OS.
If the address is zero, it means the system is running 32-bit OS and it will jump to the instructions that calls sysenter instructions in NTDLL to achieve indirect syscall.
In sysenter instruction, EDX will be the user mode return address. Therefore, It is important to assign proper return value (i.e., ESP) to EDX in order to avoid returning execution to unexpected address.
At the same time, if it is a x64 system running x86 program, the jump will happen and the next instruction will be calling the KiFastSystemCall function to execute the API call.
The above steps will result in the following syscall stub to support x86 syscall execution for both 32/64bit OS.
By including above codes to the SharpWispers, you should be able to execute indirect syscall for both x64/x86 process.
With the syscall number and the syscall address obtained previously, we are now ready to replace them in the corresponding offset of the modified syscall stub template in the DynamicSyscallInvoke function.
Combining all above codes together with SharpWhispers implementation, we are now ready to have a CSharp template that could execute NT API using indirect syscall.
Credit
All the above implementations cannot be done without the help from their research:
Reference
Last updated