mirror of
https://github.com/toneillcodes/windows-process-injection
synced 2026-06-21 14:11:25 +00:00
PEB walking and EAT crawling
This commit is contained in:
@@ -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]
|
||||
@@ -0,0 +1,2 @@
|
||||
# Dyanmic Function Resolution
|
||||
Intended to hide functions from static analysis that evaluates strings and the IAT.
|
||||
@@ -0,0 +1,2 @@
|
||||
# EDR Notes
|
||||
Collecting notes about different EDR platforms.
|
||||
@@ -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
|
||||
@@ -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?
|
||||
@@ -0,0 +1,191 @@
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <intrin.h> // Required for __readgsqword / __readfsdword
|
||||
#include <winternl.h> // 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;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef PEB_EAT_UTILS_H
|
||||
#define PEB_EAT_UTILS_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
#include <intrin.h>
|
||||
|
||||
// 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
|
||||
@@ -0,0 +1,248 @@
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <intrin.h> // Required for __readgsqword / __readfsdword
|
||||
#include <winternl.h> // 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;
|
||||
}
|
||||
@@ -4,201 +4,13 @@
|
||||
#include <intrin.h> // Required for __readgsqword / __readfsdword
|
||||
#include <winternl.h> // 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
|
||||
|
||||
Reference in New Issue
Block a user