From 3a414da4f2d98523dfa53464da68029754288fab Mon Sep 17 00:00:00 2001 From: toneillcodes Date: Sun, 24 May 2026 17:04:43 -0400 Subject: [PATCH] Refactoring PEB parsing code to streamline processing for local and remote usage. --- includes/peb-eat-utils.cpp | 389 +++++++++++++++----------- includes/peb-eat-utils.h | 55 ++-- module-stomping/list-process-dlls.cpp | 4 + 3 files changed, 247 insertions(+), 201 deletions(-) diff --git a/includes/peb-eat-utils.cpp b/includes/peb-eat-utils.cpp index 15507f4..e76d383 100644 --- a/includes/peb-eat-utils.cpp +++ b/includes/peb-eat-utils.cpp @@ -1,10 +1,208 @@ #include "peb-eat-utils.h" #include "utils.h" +#include +#include -// Ensure NTSTATUS success is defined -#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define LOCAL_PROCESS_HANDLE ((HANDLE)(LONG_PTR)-1) + +typedef NTSTATUS(NTAPI* pNtQueryInformationProcess)( + HANDLE ProcessHandle, + ULONG ProcessInformationClass, + PVOID ProcessInformation, + ULONG ProcessInformationLength, + PULONG ReturnLength +); + +// --- Core Internal Abstraction Layer --- + +BOOL ReadMemoryInternal(HANDLE hProcess, PVOID baseAddress, PVOID localBuffer, SIZE_T size) { + if (hProcess == NULL || hProcess == LOCAL_PROCESS_HANDLE) { + __try { + memcpy(localBuffer, baseAddress, size); + return TRUE; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + return FALSE; + } + } else { + SIZE_T bytesRead = 0; + return ReadProcessMemory(hProcess, baseAddress, localBuffer, size, &bytesRead) && (bytesRead == size); + } +} + +BOOL GetPEHeaders(HANDLE hProcess, PVOID moduleBase, IMAGE_DOS_HEADER* outDos, IMAGE_NT_HEADERS* outNt) { + if (!ReadMemoryInternal(hProcess, moduleBase, outDos, sizeof(IMAGE_DOS_HEADER))) return FALSE; + if (outDos->e_magic != IMAGE_DOS_SIGNATURE) return FALSE; + + PVOID ntHeadersAddr = (BYTE*)moduleBase + outDos->e_lfanew; + if (!ReadMemoryInternal(hProcess, ntHeadersAddr, outNt, sizeof(IMAGE_NT_HEADERS))) return FALSE; + if (outNt->Signature != IMAGE_NT_SIGNATURE) return FALSE; + + return TRUE; +} + +PVOID GetPebAddress(HANDLE hProcess) { + if (hProcess == NULL || hProcess == LOCAL_PROCESS_HANDLE) { +#ifdef _WIN64 + return (PVOID)__readgsqword(0x60); +#else + return (PVOID)__readfsdword(0x30); +#endif + } + + PROCESS_BASIC_INFORMATION pbi; + ULONG returnLength; + + pNtQueryInformationProcess NtQueryInfo = (pNtQueryInformationProcess)GetProcAddress( + GetModuleHandleA("ntdll.dll"), + "NtQueryInformationProcess" + ); + + if (!NtQueryInfo) return NULL; + + NTSTATUS status = NtQueryInfo(hProcess, 0, &pbi, sizeof(pbi), &returnLength); + return (status == STATUS_SUCCESS) ? pbi.PebBaseAddress : NULL; +} + +PVOID GetModuleBaseManualGeneric(HANDLE hProcess, PVOID pebAddr, const char* targetModuleName) { + PEB localPeb = { 0 }; + if (!ReadMemoryInternal(hProcess, pebAddr, &localPeb, sizeof(PEB))) return NULL; + if (!localPeb.Ldr) return NULL; + + PEB_LDR_DATA localLdr = { 0 }; + if (!ReadMemoryInternal(hProcess, localPeb.Ldr, &localLdr, sizeof(PEB_LDR_DATA))) return NULL; + + PVOID remoteListHead = (BYTE*)localPeb.Ldr + offsetof(PEB_LDR_DATA, InMemoryOrderModuleList); + LIST_ENTRY currentEntry = localLdr.InMemoryOrderModuleList; + + WCHAR targetNameWide[MAX_PATH] = { 0 }; + MultiByteToWideChar(CP_ACP, 0, targetModuleName, -1, targetNameWide, MAX_PATH); + + while (currentEntry.Flink != remoteListHead) { + PVOID tableEntryAddr = CONTAINING_RECORD(currentEntry.Flink, LDR_DATA_TABLE_ENTRY_COMPAT, InMemoryOrderLinks); + LDR_DATA_TABLE_ENTRY_COMPAT moduleEntry = { 0 }; + + if (!ReadMemoryInternal(hProcess, tableEntryAddr, &moduleEntry, sizeof(LDR_DATA_TABLE_ENTRY_COMPAT))) break; + + if (moduleEntry.BaseDllName.Buffer && moduleEntry.BaseDllName.Length < (MAX_PATH * sizeof(WCHAR))) { + WCHAR localNameBuffer[MAX_PATH] = { 0 }; + + if (ReadMemoryInternal(hProcess, moduleEntry.BaseDllName.Buffer, localNameBuffer, moduleEntry.BaseDllName.Length)) { + localNameBuffer[moduleEntry.BaseDllName.Length / sizeof(WCHAR)] = L'\0'; + + if (_wcsicmp(localNameBuffer, targetNameWide) == 0) { + return moduleEntry.DllBase; + } + } + } + currentEntry = moduleEntry.InMemoryOrderLinks; + } + return NULL; +} + +PVOID GetProcAddressManualGeneric(HANDLE hProcess, PVOID moduleBase, const char* functionName, WORD ordinal) { + IMAGE_DOS_HEADER dosHeader = { 0 }; + IMAGE_NT_HEADERS ntHeaders = { 0 }; + if (!GetPEHeaders(hProcess, moduleBase, &dosHeader, &ntHeaders)) return NULL; + + IMAGE_DATA_DIRECTORY exportDataDir = ntHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (exportDataDir.VirtualAddress == 0) return NULL; + + IMAGE_EXPORT_DIRECTORY exportDir = { 0 }; + PVOID exportDirAddr = (BYTE*)moduleBase + exportDataDir.VirtualAddress; + if (!ReadMemoryInternal(hProcess, exportDirAddr, &exportDir, sizeof(IMAGE_EXPORT_DIRECTORY))) return NULL; + + if (functionName == NULL) { + // By Ordinal + DWORD functionIndex = ordinal - exportDir.Base; + if (functionIndex >= exportDir.NumberOfFunctions) return NULL; + + DWORD funcRVA = 0; + PVOID funcRVAAddr = (BYTE*)moduleBase + exportDir.AddressOfFunctions + (functionIndex * sizeof(DWORD)); + if (!ReadMemoryInternal(hProcess, funcRVAAddr, &funcRVA, sizeof(DWORD))) return NULL; + + return (BYTE*)moduleBase + funcRVA; + } + + // By Name (Binary Search) + DWORD* nameTable = (DWORD*)malloc(exportDir.NumberOfNames * sizeof(DWORD)); + WORD* ordinalTable = (WORD*)malloc(exportDir.NumberOfNames * sizeof(WORD)); + if (!nameTable || !ordinalTable) { + free(nameTable); free(ordinalTable); + return NULL; + } + + ReadMemoryInternal(hProcess, (BYTE*)moduleBase + exportDir.AddressOfNames, nameTable, exportDir.NumberOfNames * sizeof(DWORD)); + ReadMemoryInternal(hProcess, (BYTE*)moduleBase + exportDir.AddressOfNameOrdinals, ordinalTable, exportDir.NumberOfNames * sizeof(WORD)); + + int low = 0; + int high = exportDir.NumberOfNames - 1; + PVOID functionAddress = NULL; + + while (low <= high) { + int mid = low + (high - low) / 2; + char currentName[256] = { 0 }; + + PVOID nameAddress = (BYTE*)moduleBase + nameTable[mid]; + ReadMemoryInternal(hProcess, nameAddress, currentName, sizeof(currentName) - 1); + + int cmp = strcmp(functionName, currentName); + if (cmp == 0) { + WORD ordinalValue = ordinalTable[mid]; + DWORD funcRVA = 0; + PVOID funcRVAAddr = (BYTE*)moduleBase + exportDir.AddressOfFunctions + (ordinalValue * sizeof(DWORD)); + + if (ReadMemoryInternal(hProcess, funcRVAAddr, &funcRVA, sizeof(DWORD))) { + if (funcRVA >= exportDataDir.VirtualAddress && funcRVA < (exportDataDir.VirtualAddress + exportDataDir.Size)) { + printf("[!] Warning: Forwarded export detected.\n"); + break; + } + functionAddress = (BYTE*)moduleBase + funcRVA; + } + break; + } + if (cmp < 0) high = mid - 1; + else low = mid + 1; + } + + free(nameTable); + free(ordinalTable); + return functionAddress; +} + +BOOL GetModuleSectionGeneric(HANDLE hProcess, PVOID moduleBase, const char* sectionName, IMAGE_SECTION_INFO* outSectionInfo) { + IMAGE_DOS_HEADER dosHeader = { 0 }; + IMAGE_NT_HEADERS ntHeaders = { 0 }; + if (!GetPEHeaders(hProcess, moduleBase, &dosHeader, &ntHeaders)) return FALSE; + + PVOID sectionTableAddr = (BYTE*)moduleBase + dosHeader.e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER) + ntHeaders.FileHeader.SizeOfOptionalHeader; + WORD numberOfSections = ntHeaders.FileHeader.NumberOfSections; + + IMAGE_SECTION_HEADER* sectionHeaders = (IMAGE_SECTION_HEADER*)malloc(sizeof(IMAGE_SECTION_HEADER) * numberOfSections); + if (!sectionHeaders) return FALSE; + + if (!ReadMemoryInternal(hProcess, sectionTableAddr, sectionHeaders, sizeof(IMAGE_SECTION_HEADER) * numberOfSections)) { + free(sectionHeaders); + return FALSE; + } + + BOOL found = FALSE; + for (WORD i = 0; i < numberOfSections; i++) { + if (strncmp((char*)sectionHeaders[i].Name, sectionName, IMAGE_SIZEOF_SHORT_NAME) == 0) { + outSectionInfo->VirtualAddress = (BYTE*)moduleBase + sectionHeaders[i].VirtualAddress; + outSectionInfo->SizeOfRawData = sectionHeaders[i].SizeOfRawData; + outSectionInfo->VirtualSize = sectionHeaders[i].Misc.VirtualSize; + found = TRUE; + break; + } + } + + free(sectionHeaders); + return found; +} + +// --- Exported Public API Wrappers --- -// obtain the local process TEB void* GetLocalTebAddress(void) { #ifdef _WIN64 return (void*)__readgsqword(0x30); @@ -13,175 +211,30 @@ void* GetLocalTebAddress(void) { #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 -// depends on a name value being present in the array found at AddressOfNames -PVOID GPAManualByName(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 GPAManualByOrdinal(HMODULE hMod, WORD ordinal) { - PBYTE base = (PBYTE)hMod; - - // 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); - - // Adjust the ordinal - DWORD functionIndex = ordinal - exports->Base; - - // Bounds check - if (functionIndex >= exports->NumberOfFunctions) return NULL; - - // Get the RVA from the functions array - PDWORD functionsArray = (PDWORD)(base + exports->AddressOfFunctions); - DWORD funcRVA = functionsArray[functionIndex]; - - return (PVOID)(base + funcRVA); +PVOID GetRemotePebAddress(HANDLE hProcess) { + return GetPebAddress(hProcess); } 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; + return GetModuleBaseManualGeneric(LOCAL_PROCESS_HANDLE, (PVOID)pebObject, targetModuleName); +} - // 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; - } +PVOID GetModuleBaseManualRemote(HANDLE hProcess, PVOID remotePebAddr, const char* targetModuleName) { + return GetModuleBaseManualGeneric(hProcess, remotePebAddr, targetModuleName); +} - // 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; - } +PVOID GPAManualByName(HMODULE hMod, char* targetFunc) { + return GetProcAddressManualGeneric(LOCAL_PROCESS_HANDLE, (PVOID)hMod, targetFunc, 0); +} - return NULL; +PVOID GPAManualByOrdinal(HMODULE hMod, WORD ordinal) { + return GetProcAddressManualGeneric(LOCAL_PROCESS_HANDLE, (PVOID)hMod, NULL, ordinal); +} + +PVOID GetRemoteProcAddressManual(HANDLE hProcess, PVOID moduleBase, const char* functionName) { + return GetProcAddressManualGeneric(hProcess, moduleBase, functionName, 0); +} + +BOOL GetRemoteModuleSection(HANDLE hProcess, PVOID moduleBase, const char* sectionName, IMAGE_SECTION_INFO* outSectionInfo) { + return GetModuleSectionGeneric(hProcess, moduleBase, sectionName, outSectionInfo); } \ No newline at end of file diff --git a/includes/peb-eat-utils.h b/includes/peb-eat-utils.h index f3ec35a..8ad53ed 100644 --- a/includes/peb-eat-utils.h +++ b/includes/peb-eat-utils.h @@ -1,50 +1,39 @@ -#ifndef PEB_EAT_UTILS_H -#define PEB_EAT_UTILS_H +#pragma once +#include +#include -#include // For IO operations -#include // For data structures, types, functions - -#include // Required for __readgsqword / __readfsdword -#include // for peb data structure https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb - -// Define missing NTSTATUS codes +// --- Structures & Defines --- #ifndef STATUS_SUCCESS #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) #endif -// 1. Define the COMPLETE structure since winternl.h cuts out the fields we need -typedef struct _FULL_LDR_DATA_TABLE_ENTRY { +typedef struct _IMAGE_SECTION_INFO { + PVOID VirtualAddress; + DWORD SizeOfRawData; + DWORD VirtualSize; +} IMAGE_SECTION_INFO, *PIMAGE_SECTION_INFO; + +// Custom layout ensuring BaseDllName is present regardless of SDK versions +typedef struct _LDR_DATA_TABLE_ENTRY_COMPAT { LIST_ENTRY InLoadOrderLinks; - LIST_ENTRY InMemoryOrderLinks; // This is what we use to offset math! + LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; - UNICODE_STRING BaseDllName; // Now visible! - ULONG Flags; - WORD ObsoleteLoadCount; - WORD TlsIndex; - LIST_ENTRY HashLinks; - ULONG TimeDateStamp; -} FULL_LDR_DATA_TABLE_ENTRY, *PFULL_LDR_DATA_TABLE_ENTRY; + UNICODE_STRING BaseDllName; +} LDR_DATA_TABLE_ENTRY_COMPAT, *PLDR_DATA_TABLE_ENTRY_COMPAT; -// Returns the TEB address for the current thread +// --- Forward Declarations of Public API Functions --- 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 GPAManualByName(HMODULE hMod, char* targetFunc); - -// Manual implementation of GetProcAddress (by ordinal) -PVOID GPAManualByOrdinal(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); +PVOID GetModuleBaseManualRemote(HANDLE hProcess, PVOID remotePebAddr, const char* targetModuleName); -#endif // PEB_EAT_UTILS_H \ No newline at end of file +PVOID GPAManualByName(HMODULE hMod, char* targetFunc); +PVOID GPAManualByOrdinal(HMODULE hMod, WORD ordinal); +PVOID GetRemoteProcAddressManual(HANDLE hProcess, PVOID moduleBase, const char* functionName); + +BOOL GetRemoteModuleSection(HANDLE hProcess, PVOID moduleBase, const char* sectionName, IMAGE_SECTION_INFO* outSectionInfo); \ No newline at end of file diff --git a/module-stomping/list-process-dlls.cpp b/module-stomping/list-process-dlls.cpp index 8c6bd68..3ea4115 100644 --- a/module-stomping/list-process-dlls.cpp +++ b/module-stomping/list-process-dlls.cpp @@ -1,3 +1,7 @@ +/* +* Outputs the DLLs loaded by a process with optional flags to display names only and write to a file +* compile: cl.exe /D"UNICODE" /D"_UNICODE" list-process-dlls.cpp ..\includes\peb-eat-utils.cpp ..\includes\utils.cpp ..\includes\ps-utils.cpp +*/ #include #include // Ensure bool, true, and false are explicitly supported