From ef0fea33c980e2dda0820116a6df7981a2268f5b Mon Sep 17 00:00:00 2001 From: toneillcodes Date: Thu, 22 Jan 2026 22:11:08 -0500 Subject: [PATCH] PEB walking and EAT crawling --- README.md | 8 + dynamic-function-resolution/README.md | 2 + edr-notes/README.md | 2 + edr-notes/crowdstrike.md | 10 ++ edr-notes/sophos.md | 10 ++ header-files/peb-eat-utils.cpp | 191 ++++++++++++++++++++ header-files/peb-eat-utils.h | 31 ++++ walking-peb-eat/eat-crawler.cpp | 248 ++++++++++++++++++++++++++ walking-peb-eat/peb-walk.cpp | 194 +------------------- 9 files changed, 506 insertions(+), 190 deletions(-) create mode 100644 dynamic-function-resolution/README.md create mode 100644 edr-notes/README.md create mode 100644 edr-notes/crowdstrike.md create mode 100644 edr-notes/sophos.md create mode 100644 header-files/peb-eat-utils.cpp create mode 100644 header-files/peb-eat-utils.h create mode 100644 walking-peb-eat/eat-crawler.cpp diff --git a/README.md b/README.md index 4ff7502..531992e 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,11 @@ A collection of examples intended to demonstrate the fundamentals and provide a * [injection-example-2.cpp](https://github.com/toneillcodes/windows-process-injection/blob/main/fundamentals/injection-example-2.cpp): injecting calc.exe msfvenom shellcode into the current process, toggling the memory protection between RW and RWX * [injection-example-3.cpp](https://github.com/toneillcodes/windows-process-injection/blob/main/fundamentals/injection-example-3.cpp): injecting calc.exe msfvenom shellcode into a remote process, with memory protection toggling * [injection-example-4.cpp](https://github.com/toneillcodes/windows-process-injection/blob/main/fundamentals/injection-example-4.cpp): injecting calc.exe msfvenom shellcode into a remote process, with memory protection toggling and using dynamic function resolution + +## Techniques +* [Dynamic Function Resolution]() +* [Module Stomping] +* [Walking the PEB and EAT] +* [Direct Syscalls] +* [Indirect Syscalls] +* [Thread Pool Injection] \ No newline at end of file diff --git a/dynamic-function-resolution/README.md b/dynamic-function-resolution/README.md new file mode 100644 index 0000000..09c6acb --- /dev/null +++ b/dynamic-function-resolution/README.md @@ -0,0 +1,2 @@ +# Dyanmic Function Resolution +Intended to hide functions from static analysis that evaluates strings and the IAT. \ No newline at end of file diff --git a/edr-notes/README.md b/edr-notes/README.md new file mode 100644 index 0000000..a0b393b --- /dev/null +++ b/edr-notes/README.md @@ -0,0 +1,2 @@ +# EDR Notes +Collecting notes about different EDR platforms. \ No newline at end of file diff --git a/edr-notes/crowdstrike.md b/edr-notes/crowdstrike.md new file mode 100644 index 0000000..29318b6 --- /dev/null +++ b/edr-notes/crowdstrike.md @@ -0,0 +1,10 @@ +# CrowdStrike EDR Notes +NOTE: Everything here is likely out of date by the time it is here. +This information may still be interesting or helpful for people who are interested in AV/EDR evasion. + +DLL umppc.dll + +DLL Injection? Yes +DLL Injection on Debug? No +Usermode Hooking? Yes +Kernel Hooking? Yes \ No newline at end of file diff --git a/edr-notes/sophos.md b/edr-notes/sophos.md new file mode 100644 index 0000000..6517b87 --- /dev/null +++ b/edr-notes/sophos.md @@ -0,0 +1,10 @@ +# Sophos EDR Notes +NOTE: Everything here is likely out of date by the time it is here. +This information may still be interesting or helpful for people who are interested in AV/EDR evasion. + +DLL SophosED.dll + +DLL Injection? +DLL Injection on Debug? +Usermode Hooking? +Kernel Hooking? \ No newline at end of file diff --git a/header-files/peb-eat-utils.cpp b/header-files/peb-eat-utils.cpp new file mode 100644 index 0000000..6626321 --- /dev/null +++ b/header-files/peb-eat-utils.cpp @@ -0,0 +1,191 @@ +#include +#include +#include // Required for __readgsqword / __readfsdword +#include // for peb data structure https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb + +#include "peb-eat-utils.h" +#include "utils.h" + +// obtain the local process TEB +void* GetLocalTebAddress(void) { +#ifdef _WIN64 + return (void*)__readgsqword(0x30); +#else + return (void*)__readfsdword(0x18); +#endif +} + +// todo: test and validation for remote PEB +typedef NTSTATUS (NTAPI *pNtQueryInformationProcess)( + HANDLE ProcessHandle, + PROCESSINFOCLASS ProcessInformationClass, + PVOID ProcessInformation, + ULONG ProcessInformationLength, + PULONG ReturnLength +); + +// todo: test and validation for remote PEB +PVOID GetRemotePebAddress(HANDLE hProcess) { + PROCESS_BASIC_INFORMATION pbi; + ULONG returnLength; + + // get the address of NtQueryInformationProcess + // this could be replaced with a manual lookup through the local PEB to avoid GetProcAddress + pNtQueryInformationProcess NtQueryInfo = (pNtQueryInformationProcess)GetProcAddress( + GetModuleHandleA("ntdll.dll"), + "NtQueryInformationProcess" + ); + + // Query the process for the PEB address + NTSTATUS status = NtQueryInfo( + hProcess, + ProcessBasicInformation, // Value 0 + &pbi, + sizeof(pbi), + &returnLength + ); + + if (status == STATUS_SUCCESS) { + return pbi.PebBaseAddress; + } + + return NULL; +} + +// find the address of an exported function within a given module +// obviously depends on a name value being present in the array found at AddressOfNames +PVOID GetProcAddressManualByName(HMODULE hMod, char* targetFunc) { + PBYTE base = (PBYTE)hMod; + + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base; + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew); + + IMAGE_DATA_DIRECTORY exportDataDir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (exportDataDir.VirtualAddress == 0) return NULL; + + PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)(base + exportDataDir.VirtualAddress); + + PDWORD names = (PDWORD)(base + exports->AddressOfNames); + PWORD ordinals = (PWORD)(base + exports->AddressOfNameOrdinals); + PDWORD functions = (PDWORD)(base + exports->AddressOfFunctions); + + // --- Binary Search Logic Start --- + int low = 0; + int high = exports->NumberOfNames - 1; + + while (low <= high) { + int mid = low + (high - low) / 2; + char* currentName = (char*)(base + names[mid]); + + int cmp = my_strcmp(targetFunc, currentName); + + if (cmp == 0) { + // Match found! + WORD ordinalValue = ordinals[mid]; + DWORD funcRVA = functions[ordinalValue]; + + // Forwarder Check + if (funcRVA >= exportDataDir.VirtualAddress && + funcRVA < (exportDataDir.VirtualAddress + exportDataDir.Size)) { + // Add more forwarder logic here + return NULL; + } + + return (PVOID)(base + funcRVA); + } + + if (cmp < 0) { + high = mid - 1; // Target is in the lower half + } + else { + low = mid + 1; // Target is in the upper half + } + } + // --- Binary Search Logic End --- + + return NULL; +} + +// find the address of an exported function within a given module +PVOID GetProcAddressManualByOrdinal(HMODULE hMod, WORD ordinal) { + PBYTE base = (PBYTE)hMod; + + // 1. Navigate to the Export Directory (standard PE parsing) + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base; + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew); + PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)(base + + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + + // 2. Adjust the ordinal + // Most DLLs start their ordinals at a "Base" (usually 1). + // If the DLL says export #12 and the Base is 1, the actual index is 11. + DWORD functionIndex = ordinal - exports->Base; + + // 3. Bounds check + if (functionIndex >= exports->NumberOfFunctions) return NULL; + + // 4. Get the RVA from the functions array + PDWORD functionsArray = (PDWORD)(base + exports->AddressOfFunctions); + DWORD funcRVA = functionsArray[functionIndex]; + + return (PVOID)(base + funcRVA); +} + +PVOID GetModuleBaseManual(PPEB pebObject, const char* targetModuleName) { + // we want the ldr data + PPEB_LDR_DATA ldr = pebObject->Ldr; + PLIST_ENTRY listHead = &ldr->InMemoryOrderModuleList; + PLIST_ENTRY currentEntry = listHead->Flink; + + // traverse the doubly-linked list, if we ouroboros we're done + while (currentEntry != listHead) { + // InMemoryOrderLinks is the second field in LDR_DATA_TABLE_ENTRY + // use CONTAINING_RECORD to snap back to the start of the structure + LDR_DATA_TABLE_ENTRY* moduleEntry = (LDR_DATA_TABLE_ENTRY*)CONTAINING_RECORD( + currentEntry, + LDR_DATA_TABLE_ENTRY, + InMemoryOrderLinks + ); + + UNICODE_STRING fileName = moduleEntry->FullDllName; + //printf("fileName = %wZ\n", fileName); + PVOID moduleBase = moduleEntry->DllBase; + // get DOS Header + PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)moduleBase; + if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { + printf("Invalid DOS Signature\n"); + return NULL; + } + + // get NT Headers using the offset from DOS Header + PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)moduleBase + dosHeader->e_lfanew); + if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) { + printf("Invalid NT Signature\n"); + return NULL; + } + + // Locate the Export Directory in the Data Directory + IMAGE_DATA_DIRECTORY exportDataDir = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (exportDataDir.VirtualAddress == 0) { + // does this case matter? maybe with debug enabled + //printf("No Export Table found for entry %wZ\n", &fileName); + } + else { + PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)moduleBase + exportDataDir.VirtualAddress); + char* moduleName = NULL; + moduleName = (char*)((BYTE*)moduleBase + exportDir->Name); + + if (moduleName != NULL) { + // todo: case insensitive would be better, the names are not consistent + // examples: ntdll.dll, USER32.dll, KERNEL32.DLL + //if (my_strcmp(moduleName, targetModuleName) == 0) { + if (my_stricmp(moduleName, targetModuleName) == 0) { + return moduleBase; + } + } + } + currentEntry = currentEntry->Flink; + } + + return NULL; +} \ No newline at end of file diff --git a/header-files/peb-eat-utils.h b/header-files/peb-eat-utils.h new file mode 100644 index 0000000..a0e5b51 --- /dev/null +++ b/header-files/peb-eat-utils.h @@ -0,0 +1,31 @@ +#ifndef PEB_EAT_UTILS_H +#define PEB_EAT_UTILS_H + +#include +#include +#include + +// Define missing NTSTATUS codes +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif + +// Returns the TEB address for the current thread +void* GetLocalTebAddress(void); + +// Returns the address of the PEB for a remote process +PVOID GetRemotePebAddress(HANDLE hProcess); + +// Basic utility to calculate string length +int my_strlen(const char* inputString); + +// Manual implementation of GetProcAddress (by name) +PVOID GetProcAddressManualByName(HMODULE hMod, char* targetFunc); + +// Manual implementation of GetProcAddress (by ordinal) +PVOID GetProcAddressManualByOrdinal(HMODULE hMod, WORD ordinal); + +// Manually finds the base address of a module using the PEB's Ldr list +PVOID GetModuleBaseManual(PPEB pebObject, const char* targetModuleName); + +#endif // PEB_EAT_UTILS_H \ No newline at end of file diff --git a/walking-peb-eat/eat-crawler.cpp b/walking-peb-eat/eat-crawler.cpp new file mode 100644 index 0000000..691baec --- /dev/null +++ b/walking-peb-eat/eat-crawler.cpp @@ -0,0 +1,248 @@ +#include +#include +#include +#include // Required for __readgsqword / __readfsdword +#include // for peb data structure https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb + +// for testing with MessageBoxA +#pragma comment(lib, "user32.lib") + +#include "../header-files/utils.h" +#include "../header-files/peb-eat-utils.h" + +#define MAX_EXPORTED_FUNCS 25 + +typedef int (WINAPI* myMessageBoxA)(HWND hWnd, LPCSTR lptext, LPCSTR lpCaption, UINT uType); + +int main(int argc, char* argv[]) { + int debugOutput = 1; + uintptr_t functionAddresses[MAX_EXPORTED_FUNCS]; + + void* teb = GetLocalTebAddress(); + if (debugOutput) { + printf("Current process TEB address: %p\n", teb); + } + + // maybe move this to a GetLocalPebAddress function? + // this is good for demonstration but we can really skip straight to the PEB + void* peb = NULL; +#ifdef _WIN64 + // On x64, PEB is at TEB + 0x60 + peb = *(void**)((unsigned char*)teb + 0x60); +#else + // On x86, PEB is at TEB + 0x30 + // not currently used, but good to have around + peb = *(void**)((unsigned char*)teb + 0x30); +#endif + if (debugOutput) { + printf("Current process PEB address (TEB + offset): %p\n", peb); + } + + // https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb#remarks + // Assuming peb_address holds the valid memory location of the PEB + PPEB peb_ptr = (PPEB)peb; + + // Accessing the 'BeingDebugged' flag (a documented field) + if (peb_ptr->BeingDebugged) { + printf("Debugger is attached\n"); + } + else { + printf("No debugger detected\n"); + } + + // init counter to zero + int foundCount = 0; + + char targetFunc[] = "MessageBoxA"; + uintptr_t finalFunctionAddr = NULL; + + // Accessing the 'Ldr' member, which points to PEB_LDR_DATA + PPEB_LDR_DATA ldr_data = peb_ptr->Ldr; + + // The list head for modules in memory order + PLIST_ENTRY list_head = &ldr_data->InMemoryOrderModuleList; + // current entry + PLIST_ENTRY current_entry = list_head->Flink; + + while (current_entry != list_head) { // Loop until we return to the list head + // Use CONTAINING_RECORD to get the base address of the LDR_DATA_TABLE_ENTRY + LDR_DATA_TABLE_ENTRY* module_entry = (LDR_DATA_TABLE_ENTRY*)CONTAINING_RECORD( + current_entry, + LDR_DATA_TABLE_ENTRY, + InMemoryOrderLinks + ); + + // Access module information, e.g., the DLL base address + PVOID moduleBaseAddress = module_entry->DllBase; + UNICODE_STRING dllName = module_entry->FullDllName; + ULONG sessionId = module_entry->TimeDateStamp; + ULONG checksum = module_entry->CheckSum; + if (debugOutput) { + printf("--------\n"); + printf("DLL Base Address = 0x%p\n", &moduleBaseAddress); + printf("DLL Name = %wZ\n", &dllName); + printf("session id = %lu\n", sessionId); + printf("checksum = %lu\n", checksum); + } + //ParseDll(moduleBaseAddress); + PVOID moduleBase = moduleBaseAddress; + // 1. Get DOS Header + PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)moduleBase; + if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { + printf("Invalid DOS Signature\n"); + return -1; + } + + // Get NT Headers using the offset from DOS Header + PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)moduleBase + dosHeader->e_lfanew); + if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) { + printf("Invalid NT Signature\n"); + return -1; + } + + // Locate the Export Directory in the Data Directory + IMAGE_DATA_DIRECTORY exportDataDir = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (exportDataDir.VirtualAddress == 0) { + if (debugOutput) { + printf("No Export Table found for entry %wZ\n", &dllName); + } + } + else { + if (debugOutput) { + printf("Export Table Detected.\n"); + } + // Get the Export Directory Structure + PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)moduleBase + exportDataDir.VirtualAddress); + + char* dllName = (char*)((BYTE*)moduleBase + exportDir->Name); + if (debugOutput) { + printf("DLL Name: %s\n", dllName); + } + uintptr_t Base = exportDir->Base; + uintptr_t AddressOfFunctions = exportDir->AddressOfFunctions; + uintptr_t AddressOfNameOrdinals = exportDir->AddressOfNameOrdinals; + uintptr_t AddressOfNames = exportDir->AddressOfNames; + DWORD Characteristics = exportDir->Characteristics; + WORD MajorVersion = exportDir->MajorVersion; + WORD MinorVersion = exportDir->MinorVersion; + DWORD NumberOfNames = exportDir->NumberOfNames; + DWORD NumberOfFunctions = exportDir->NumberOfFunctions; + DWORD TimeDateStamp = exportDir->TimeDateStamp; + + // initialize EAT arrays + PDWORD nameArray = (PDWORD)((BYTE*)moduleBase + exportDir->AddressOfNames); + PWORD ordinalsArray = (PWORD)((BYTE*)moduleBase + exportDir->AddressOfNameOrdinals); + PDWORD functionsArray = (PDWORD)((BYTE*)moduleBase + exportDir->AddressOfFunctions); + + DWORD forwardedCount = 0; + + if (debugOutput) { + printf("AddressOfFunctions (RVA): 0x%llx\n", AddressOfFunctions); + printf("AddressOfNameOrdinals (RVA): 0x%llx\n", AddressOfNameOrdinals); + printf("AddressOfNames (RVA): 0x%llx\n", AddressOfNames); + printf("Absolute AddressOfFunctions: 0x%llx\n", (uintptr_t)moduleBase + AddressOfFunctions); + printf("Absolute AddressOfNameOrdinals: 0x%llx\n", (uintptr_t)moduleBase + AddressOfNameOrdinals); + printf("Absolute AddressOfNames: 0x%llx\n", (uintptr_t)moduleBase + AddressOfNames); + //printf("Base: 0x%llx\n", Base); + printf("Major Version: %d\n", MajorVersion); + printf("Minor Version: %d\n", MinorVersion); + printf("Number of Names: %d\n", NumberOfNames); + printf("Number of Functions: %d\n", NumberOfFunctions); + } + + if (_stricmp(dllName, "umppc.dll") == 0) { + printf("CrowdStrike DLL detected! Skipping parsing.\n"); + } else if(_stricmp(dllName, "SophosED.dll") == 0) { + printf("Sophos DLL detected! Skipping parsing.\n"); + } + else { + // Iterate through every function exported by the DLL + for (DWORD i = 0; i < NumberOfFunctions; i++) { + + // 1. Get the Function RVA directly using the index 'i' + DWORD functionRVA = functionsArray[i]; + if (functionRVA == 0) continue; // Skip entries with no address + + char* funcName = NULL; + + // Search the NameOrdinals array for a value that matches 'i' + for (DWORD j = 0; j < NumberOfNames; j++) { + if (ordinalsArray[j] == i) { + // the name at nameArray[j] belongs to function index i + funcName = (char*)((BYTE*)moduleBase + nameArray[j]); + break; + } + } + + // check if it matches our target function + if (funcName != NULL) { + if (debugOutput) { + // this takes debug output to another level... + //printf("Function Index %d has Name: %s\n", i, funcName); + } + + if (strcmp(funcName, targetFunc) == 0) { + // Check if it's a Forwarded Export before saving the address + DWORD exportStart = exportDataDir.VirtualAddress; + DWORD exportEnd = exportDataDir.VirtualAddress + exportDataDir.Size; + + if (functionRVA >= exportStart && functionRVA < exportEnd) { + char* forwarderString = (char*)((BYTE*)moduleBase + functionRVA); + printf("[!] Found %s, but it's a forwarder to: %s\n", targetFunc, forwarderString); + // For now, we don't resolve the other DLL, just skip it + continue; + } + + finalFunctionAddr = (uintptr_t)((BYTE*)moduleBase + functionRVA); + if (foundCount < MAX_EXPORTED_FUNCS) { + functionAddresses[foundCount] = finalFunctionAddr; + foundCount++; + } + printf("[%s] found at: 0x%llx in: %wZ\n", targetFunc, finalFunctionAddr, &module_entry->FullDllName); + } + } + } + } + } + // Move to the next entry in the list + current_entry = current_entry->Flink; + } + + /*printf("Last entry located @ 0x%llx\n", finalFunctionAddr); + myMessageBoxA dynamicMsgBox = (myMessageBoxA)finalFunctionAddr; + dynamicMsgBox(NULL, "Executed via manual address resolution!", "Success", MB_OK);*/ + + // make sure we find an entry by adding a call + if (argc > 999) MessageBoxA(NULL, NULL, NULL, 0); + myMessageBoxA dynamicMsgBox = NULL; + if (foundCount > 1) { + printf("Multiple results found. Which would you like to invoke?\n"); + for (int i = 0; i < foundCount; i++) { + printf("[%d] to invoke @ 0x%llx\n", i, functionAddresses[i]); + } + int addressSelection = -1; + scanf_s("%d", &addressSelection); + if (addressSelection == -1) { + printf("No address selected, exiting."); + return -1; + } + else if (addressSelection >= 0 && addressSelection < foundCount) { + dynamicMsgBox = (myMessageBoxA)functionAddresses[addressSelection]; + } + } + else if (foundCount == 1) { + printf("Only one entry found - invoking from 0x%llx\n", finalFunctionAddr); + dynamicMsgBox = (myMessageBoxA)finalFunctionAddr; + } + else { + printf("No entries found.\n"); + } + + // did we find an entry? if so, let's invoke it + if(dynamicMsgBox != NULL) { + dynamicMsgBox(NULL, "Executed via manual address resolution!", "Success", MB_OK); + } + + printf("Done."); + return 0; +} \ No newline at end of file diff --git a/walking-peb-eat/peb-walk.cpp b/walking-peb-eat/peb-walk.cpp index 5f755ec..1e6cacb 100644 --- a/walking-peb-eat/peb-walk.cpp +++ b/walking-peb-eat/peb-walk.cpp @@ -4,201 +4,13 @@ #include // Required for __readgsqword / __readfsdword #include // for peb data structure https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb -#include "../header-files/utils.h" - -// Define missing NTSTATUS codes -#ifndef STATUS_SUCCESS -#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) -#endif +#include "../header-files/utils.h" // crt replacements +#include "../header-files/peb-eat-utils.h" // custom peb/eat walking functions #define MAX_EXPORTED_FUNCS 25 #pragma comment(lib, "user32.lib") -// obtain the local process TEB -void* GetLocalTebAddress(void) { -#ifdef _WIN64 - return (void*)__readgsqword(0x30); -#else - return (void*)__readfsdword(0x18); -#endif -} - -// todo: test and validation for remote PEB -typedef NTSTATUS (NTAPI *pNtQueryInformationProcess)( - HANDLE ProcessHandle, - PROCESSINFOCLASS ProcessInformationClass, - PVOID ProcessInformation, - ULONG ProcessInformationLength, - PULONG ReturnLength -); - -// todo: test and validation for remote PEB -PVOID GetRemotePebAddress(HANDLE hProcess) { - PROCESS_BASIC_INFORMATION pbi; - ULONG returnLength; - - // get the address of NtQueryInformationProcess - // this could be replaced with a manual lookup through the local PEB to avoid GetProcAddress - pNtQueryInformationProcess NtQueryInfo = (pNtQueryInformationProcess)GetProcAddress( - GetModuleHandleA("ntdll.dll"), - "NtQueryInformationProcess" - ); - - // Query the process for the PEB address - NTSTATUS status = NtQueryInfo( - hProcess, - ProcessBasicInformation, // Value 0 - &pbi, - sizeof(pbi), - &returnLength - ); - - if (status == STATUS_SUCCESS) { - return pbi.PebBaseAddress; - } - - return NULL; -} - -// find the address of an exported function within a given module -// obviously depends on a name value being present in the array found at AddressOfNames -PVOID GetProcAddressManualByName(HMODULE hMod, char* targetFunc) { - PBYTE base = (PBYTE)hMod; - - PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base; - PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew); - - IMAGE_DATA_DIRECTORY exportDataDir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; - if (exportDataDir.VirtualAddress == 0) return NULL; - - PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)(base + exportDataDir.VirtualAddress); - - PDWORD names = (PDWORD)(base + exports->AddressOfNames); - PWORD ordinals = (PWORD)(base + exports->AddressOfNameOrdinals); - PDWORD functions = (PDWORD)(base + exports->AddressOfFunctions); - - // --- Binary Search Logic Start --- - int low = 0; - int high = exports->NumberOfNames - 1; - - while (low <= high) { - int mid = low + (high - low) / 2; - char* currentName = (char*)(base + names[mid]); - - int cmp = my_strcmp(targetFunc, currentName); - - if (cmp == 0) { - // Match found! - WORD ordinalValue = ordinals[mid]; - DWORD funcRVA = functions[ordinalValue]; - - // Forwarder Check - if (funcRVA >= exportDataDir.VirtualAddress && - funcRVA < (exportDataDir.VirtualAddress + exportDataDir.Size)) { - // Add more forwarder logic here - return NULL; - } - - return (PVOID)(base + funcRVA); - } - - if (cmp < 0) { - high = mid - 1; // Target is in the lower half - } - else { - low = mid + 1; // Target is in the upper half - } - } - // --- Binary Search Logic End --- - - return NULL; -} - -// find the address of an exported function within a given module -PVOID GetProcAddressManualByOrdinal(HMODULE hMod, WORD ordinal) { - PBYTE base = (PBYTE)hMod; - - // 1. Navigate to the Export Directory (standard PE parsing) - PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base; - PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew); - PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)(base + - nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); - - // 2. Adjust the ordinal - // Most DLLs start their ordinals at a "Base" (usually 1). - // If the DLL says export #12 and the Base is 1, the actual index is 11. - DWORD functionIndex = ordinal - exports->Base; - - // 3. Bounds check - if (functionIndex >= exports->NumberOfFunctions) return NULL; - - // 4. Get the RVA from the functions array - PDWORD functionsArray = (PDWORD)(base + exports->AddressOfFunctions); - DWORD funcRVA = functionsArray[functionIndex]; - - return (PVOID)(base + funcRVA); -} - -PVOID GetModuleBaseManual(PPEB pebObject, const char* targetModuleName) { - // we want the ldr data - PPEB_LDR_DATA ldr = pebObject->Ldr; - PLIST_ENTRY listHead = &ldr->InMemoryOrderModuleList; - PLIST_ENTRY currentEntry = listHead->Flink; - - // traverse the doubly-linked list, if we ouroboros we're done - while (currentEntry != listHead) { - // InMemoryOrderLinks is the second field in LDR_DATA_TABLE_ENTRY - // use CONTAINING_RECORD to snap back to the start of the structure - LDR_DATA_TABLE_ENTRY* moduleEntry = (LDR_DATA_TABLE_ENTRY*)CONTAINING_RECORD( - currentEntry, - LDR_DATA_TABLE_ENTRY, - InMemoryOrderLinks - ); - - UNICODE_STRING fileName = moduleEntry->FullDllName; - //printf("fileName = %wZ\n", fileName); - PVOID moduleBase = moduleEntry->DllBase; - // get DOS Header - PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)moduleBase; - if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { - printf("Invalid DOS Signature\n"); - return NULL; - } - - // get NT Headers using the offset from DOS Header - PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)moduleBase + dosHeader->e_lfanew); - if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) { - printf("Invalid NT Signature\n"); - return NULL; - } - - // Locate the Export Directory in the Data Directory - IMAGE_DATA_DIRECTORY exportDataDir = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; - if (exportDataDir.VirtualAddress == 0) { - // does this case matter? maybe with debug enabled - //printf("No Export Table found for entry %wZ\n", &fileName); - } - else { - PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)moduleBase + exportDataDir.VirtualAddress); - char* moduleName = NULL; - moduleName = (char*)((BYTE*)moduleBase + exportDir->Name); - - if (moduleName != NULL) { - // todo: case insensitive would be better, the names are not consistent - // examples: ntdll.dll, USER32.dll, KERNEL32.DLL - //if (my_strcmp(moduleName, targetModuleName) == 0) { - if (my_stricmp(moduleName, targetModuleName) == 0) { - return moduleBase; - } - } - } - currentEntry = currentEntry->Flink; - } - - return NULL; -} - // custom type defs for function invocation typedef int (WINAPI* myMessageBoxA)(HWND hWnd, LPCSTR lptext, LPCSTR lpCaption, UINT uType); @@ -213,6 +25,8 @@ int main(int argc, char* argv[]) { printf("Current process TEB address: %p\n", teb); } + // maybe move this to a GetLocalPebAddress function? + // this is good for demonstration but we can really skip straight to the PEB void* peb = NULL; #ifdef _WIN64 // On x64, PEB is at TEB + 0x60