commit 5e90abba1b28163f62c025b40a0f7ee3110f45ec Author: klez.virus Date: Sun Dec 21 22:51:42 2025 +0100 First Public Release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25b0f3f --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Visual Studio Dir +x64 +.vs \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ee7c315 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2025, Alessandro Magnosi + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f761c38 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# ThreadPoolExecChain + +Simple POC to chain multiple callbacks via tail calls to artificially build a callstack + +## TL;DR + +ThreadPoolExecChain is a simple proof of concept demonstrating how multiple callbacks can be chained via tail calls to artificially construct a call stack, and how specific return-value–saving frames can be leveraged to recover the results of the last target call. + +**Read more in the Blog Post:** [Callback hell: abusing callbacks, tail-calls, and proxy frames to obfuscate the stack](https://klezvirus.github.io/posts/Callback-hell/). + +## Overview + +This repository demonstrates a PoC implementation to construct a semi-artificial (callback-dependent) call stack when calling arbitrary Windows APIs, while simultanously save the return value of the called Windows API. + +An extensive overview of the technique and why it was developed can be read [here](https://klezvirus.github.io/posts/Callback-hell/). + +This POC was made to work with a few callback-chains to test its flexibility but should be customized for additional results. Moreover, +the gadget chosen to save the return value cannot be used to execute functions with more than 4 arguments (technically 5, but you'll lose a volatile register in the process and that's not recommended). + +## Previous Work + +* [Hiding In PlainSight - Indirect Syscall is Dead! Long Live Custom Call Stacks](https://0xdarkvortex.dev/hiding-in-plainsight/) + +## Credits + +* [namazso](https://x.com/namazso): stack unwinding inner mechanics, retaddr/frame spoofing, and more +* [Chetan Nayak](https://x.com/NinjaParanoid): previous public work on implementing ThreadPool Worker Callback as a tailcall + diff --git a/ThreadPoolExecChain.sln b/ThreadPoolExecChain.sln new file mode 100644 index 0000000..44228b0 --- /dev/null +++ b/ThreadPoolExecChain.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36327.8 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ThreadPoolExecChain", "ThreadPoolExecChain\ThreadPoolExecChain.vcxproj", "{9E48F1CA-3B4E-442F-8191-8938EFA80A7B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9E48F1CA-3B4E-442F-8191-8938EFA80A7B}.Debug|x64.ActiveCfg = Debug|x64 + {9E48F1CA-3B4E-442F-8191-8938EFA80A7B}.Debug|x64.Build.0 = Debug|x64 + {9E48F1CA-3B4E-442F-8191-8938EFA80A7B}.Debug|x86.ActiveCfg = Debug|Win32 + {9E48F1CA-3B4E-442F-8191-8938EFA80A7B}.Debug|x86.Build.0 = Debug|Win32 + {9E48F1CA-3B4E-442F-8191-8938EFA80A7B}.Release|x64.ActiveCfg = Release|x64 + {9E48F1CA-3B4E-442F-8191-8938EFA80A7B}.Release|x64.Build.0 = Release|x64 + {9E48F1CA-3B4E-442F-8191-8938EFA80A7B}.Release|x86.ActiveCfg = Release|Win32 + {9E48F1CA-3B4E-442F-8191-8938EFA80A7B}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2463E8EB-CCAA-4A01-8A98-FBE08436B945} + EndGlobalSection +EndGlobal diff --git a/ThreadPoolExecChain/Callback.asm b/ThreadPoolExecChain/Callback.asm new file mode 100644 index 0000000..29f7810 --- /dev/null +++ b/ThreadPoolExecChain/Callback.asm @@ -0,0 +1,157 @@ +.data + +PUBLIC GCONTEXT +PUBLIC GINDEX +PUBLIC GCCTX +PUBLIC GLOCK + +GCONTEXT QWORD 0 +GINDEX QWORD 0 +GCCTX QWORD 0 +GLOCK QWORD 0 + +.code + +GetReturn PROC + +GetReturn ENDP + +FirstCallback PROC + mov GCONTEXT, rdx + mov GINDEX, 0 ; reset GINDEX + MOV GLOCK, 0 + jmp GenericCallback + +FirstCallback ENDP + +GenericCallback PROC + ; Load GINDEX + mov rax, GINDEX ; rax = index + mov rdx, 56 ; sizeof(WorkItemContext) + imul rax, rdx ; rax = index * 56 + + ; Load GCONTEXT (base pointer) + mov rdx, GCONTEXT ; rdx = base address of workItems + add rdx, rax ; rdx = &workItems[index] + + ; rdx now points to WorkItemContext[index] + mov GCCTX, rdx + mov r10, rdx + + ; Load func ptr + mov rax, [rdx] ; rax = workItems[index].func + + ; Load ARGC + mov r11, [rdx + 010h] ; argc (int) + + cmp r11, 4 + jg skip_call ; current implementation only supports up to 4 args + + ; Set RCX, RDX, R8, R9 + cmp r11, 0 + jle do_call ; no args, just call + + mov rcx, [r10 + 018h] + cmp r11, 1 + jle do_call + + mov rdx, [r10 + 020h] + cmp r11, 2 + jle do_call + + mov r8, [r10 + 028h] + cmp r11, 3 + jle do_call + + mov r9, [r10 + 030h] + cmp r11, 4 + jle do_call + +do_call: + mov r11, GINDEX + inc r11 + mov GINDEX, r11 + jmp rax + +skip_call: + mov r11, GINDEX + inc r11 + mov GINDEX, r11 + ret + +GenericCallback ENDP + +LastCallback PROC frame + ; Check if a lock is set, skip if true + MOV rax, GLOCK + cmp rax, 1 + je skip_call + + ; Set LOCK to avoid executing more than once + MOV GLOCK, 1 + + ; Load GINDEX + mov rax, GINDEX ; rax = index + mov rdx, 56 ; sizeof(WorkItemContext) + imul rax, rdx ; rax = index * 56 + + ; Load GCONTEXT (base pointer) + mov rdx, GCONTEXT ; rdx = base address of workItems + add rdx, rax ; rdx = &workItems[index] + + ; rdx now points to WorkItemContext[index] + mov GCCTX, rdx + mov r10, rdx + + ; Load func ptr + mov rax, [rdx] ; rax = workItems[index].func + mov rax, [rax] ; rax = workItems[index].func + + mov r11, [r10 + 10h] + + cmp r11, 4 + jg skip_call ; current implementation only supports up to 4 args + + ; Set RCX, RDX, R8, R9 + cmp r11, 0 + jle do_call ; no args, just call + + mov rcx, [r10 + 018h] + cmp r11, 1 + jle do_call + + mov rdx, [r10 + 020h] + cmp r11, 2 + jle do_call + + mov r8, [r10 + 028h] + cmp r11, 3 + jle do_call + + mov r9, [r10 + 030h] + cmp r11, 4 + jle do_call + + +do_call: + mov r10, [r10 + 08h] ; addressToPush + + .pushreg rbx + push rbx + .allocstack 20h + sub rsp, 20h ; this is for the spoofed/swapped frame + .endprolog + push r10 + mov rbx, GCONTEXT ; we need this structure in RBX + add rbx, 8 ; we use the address of the return value, unused for generic callbacks + + jmp rax + +skip_call: + mov rax, 0 + ret + + +LastCallback ENDP + +END \ No newline at end of file diff --git a/ThreadPoolExecChain/Callback.h b/ThreadPoolExecChain/Callback.h new file mode 100644 index 0000000..fe0afb5 --- /dev/null +++ b/ThreadPoolExecChain/Callback.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +#define WORK_ITEM_COUNT 4 +#define MAX_ARGC 4 + +typedef struct WorkItemContext { + FARPROC func; // Function pointer + void* retAddress; // Return address to simulate stack return + uint64_t argc; // Argument count + void* args[MAX_ARGC]; // Arguments (up to 4) +} WorkItemContext; + + +extern "C" void CALLBACK FirstCallback(PTP_CALLBACK_INSTANCE, PVOID, PTP_WORK); + +extern "C" void CALLBACK GenericCallback(...); +extern "C" void CALLBACK LastCallback(...); diff --git a/ThreadPoolExecChain/PatternSearch.cpp b/ThreadPoolExecChain/PatternSearch.cpp new file mode 100644 index 0000000..682594f --- /dev/null +++ b/ThreadPoolExecChain/PatternSearch.cpp @@ -0,0 +1,128 @@ +#include "PatternSearch.h" + +PVOID g_SaveRaxInRbx = NULL; + +void* find_pattern_masked(const void* haystack, size_t haystack_size, + const unsigned char* pattern, + const unsigned char* mask, + size_t pattern_len) { + const unsigned char* mem = (const unsigned char*)haystack; + + for (size_t i = 0; i <= haystack_size - pattern_len; i++) { + size_t j = 0; + for (; j < pattern_len; j++) { + if (mask[j] == 0x00 && mem[i + j] != pattern[j]) + break; + } + if (j == pattern_len) + return (void*)(mem + i); + } + + return nullptr; +} + + +void* find_pattern_in_module(HMODULE hmod, + const unsigned char* pattern, + const unsigned char* mask, + size_t pattern_len) { + if (!hmod) return NULL; + + // Get DOS and NT headers + IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)hmod; + IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)((BYTE*)hmod + dos->e_lfanew); + + // Locate .text section + IMAGE_SECTION_HEADER* sec = IMAGE_FIRST_SECTION(nt); + for (int i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) { + if (memcmp(sec->Name, ".text", 5) == 0) { + BYTE* start = (BYTE*)hmod + sec->VirtualAddress; + DWORD size = sec->Misc.VirtualSize; + + // Perform masked pattern scan + for (DWORD offset = 0; offset <= size - pattern_len; offset++) { + size_t j = 0; + for (; j < pattern_len; j++) { + if (mask[j] == 0x00 && start[offset + j] != pattern[j]) + break; + } + if (j == pattern_len) + return (void*)(start + offset); + } + + break; // only .text + } + } + + return NULL; +} + +void* find_address_to_push() { + if (g_SaveRaxInRbx != 0) { + return g_SaveRaxInRbx; + } + + /* + + 180152075 e8 ee af CALL FUN_18015d068 undefined FUN_18015d068() + 00 00 + 18015207a 48 89 03 MOV qword ptr [RBX],RAX + 18015207d b8 01 00 MOV EAX,0x1 + 00 00 + 180152082 eb 06 JMP LAB_18015208a + LAB_180152084 XREF[3]: 180152060(j), 180152066(j), + 18015206c(j) + 180152084 48 89 02 MOV qword ptr [RDX],RAX + 180152087 48 89 03 MOV qword ptr [RBX],RAX + LAB_18015208a XREF[1]: 180152082(j) + 18015208a 48 83 c4 20 ADD RSP,0x20 + 18015208e 5b POP RBX + 18015208f c3 RET + 180152090 cc ?? CCh + + */ + + // Assembly pattern to match in memory + + const unsigned char pattern[] = { + 0xE8, 0x00, 0x00, 0x00, 0x00, // CALL (wildcard offset) + 0x48, 0x89, 0x03, + 0xB8, 0x01, 0x00, 0x00, 0x00, + 0xEB, 0x06, + 0x48, 0x89, 0x02, + 0x48, 0x89, 0x03, + 0x48, 0x83, 0xC4, 0x20, + 0x5B, + 0xC3 + }; + + // MASK - 0x00 strict match, 0xFF match everything + const unsigned char mask[] = { + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, + 0x00 + }; + + HMODULE hMod = LoadLibraryA("wininet.dll"); + if (!hMod) { + printf("Failed to load wininet.dll: %lu\n", GetLastError()); + return nullptr; + } + + void* address = find_pattern_in_module(hMod, pattern, mask, sizeof(pattern)); + + if (!address) { + printf("Pattern not found in wininet.dll\n"); + return nullptr; + } + + // Adjust the address to point to the instruction after the CALL + g_SaveRaxInRbx = (void*)((UINT64)address + 5); + return g_SaveRaxInRbx; +} diff --git a/ThreadPoolExecChain/PatternSearch.h b/ThreadPoolExecChain/PatternSearch.h new file mode 100644 index 0000000..72fffeb --- /dev/null +++ b/ThreadPoolExecChain/PatternSearch.h @@ -0,0 +1,9 @@ +#pragma once +#pragma once +#include +#include + +void* find_pattern_masked(const void* haystack, size_t haystack_size, const unsigned char* pattern, const unsigned char* mask, size_t pattern_len); +void* find_pattern_in_module(HMODULE hmod, const unsigned char* pattern, const unsigned char* mask, size_t pattern_len); + +void* find_address_to_push(); \ No newline at end of file diff --git a/ThreadPoolExecChain/ProxyCallbacks.cpp b/ThreadPoolExecChain/ProxyCallbacks.cpp new file mode 100644 index 0000000..5efd7a9 --- /dev/null +++ b/ThreadPoolExecChain/ProxyCallbacks.cpp @@ -0,0 +1,193 @@ +#include "Callback.h" +#include "PatternSearch.h" +#include "ProxyCallbacks.h" + +#include + +uint64_t ProxyPooling(WorkItemContext* ctx, UINT chain) { + + // Alright, this is the address of the final backward proxy + void* addressToPush = find_address_to_push(); + + // This is the array of work items (1 x callback) + WorkItemContext* workItems = (WorkItemContext*)malloc(sizeof(WorkItemContext) * WORK_ITEM_COUNT); + if (!workItems) { + return ERROR_CODE; + } + + memset(workItems, 0, sizeof(WorkItemContext) * WORK_ITEM_COUNT); + + // This is stuff for the callback itself + // https://github.com/aahmad097/AlternativeShellcodeExec + + if (chain == 0) { + + HDC dc = GetDC(NULL); + + // Set up work item 0 - In this case EnumThreadWindows + workItems[0].func = (FARPROC)&EnumThreadWindows; + workItems[0].retAddress = NULL; + workItems[0].argc = 3; + workItems[0].args[0] = (void*)0; + workItems[0].args[1] = (void*)&GenericCallback; + workItems[0].args[2] = (void*)0; + + // Set up work item 1 - EnumObjects + workItems[1].func = (FARPROC)&EnumObjects; + workItems[1].retAddress = NULL; + workItems[1].argc = 4; + workItems[1].args[0] = (void*)dc; + workItems[1].args[1] = (void*)OBJ_BRUSH; + workItems[1].args[2] = (void*)&LastCallback; + workItems[1].args[3] = (void*)NULL; + } + else if (chain == 1) { + + HDC dc = GetDC(NULL); + + // Set up work item 0 - In this case K32EnumPageFilesW + workItems[0].func = (FARPROC)&K32EnumPageFilesW; + workItems[0].retAddress = NULL; + workItems[0].argc = 2; + workItems[0].args[0] = (void*)&GenericCallback; + workItems[0].args[1] = (void*)0; + + // Set up work item 1 - EnumFontFamiliesW + workItems[1].func = (FARPROC)&EnumFontFamiliesW; + workItems[1].retAddress = NULL; + workItems[1].argc = 4; + workItems[1].args[0] = (void*)dc; + workItems[1].args[1] = (void*)NULL; + workItems[1].args[2] = (void*)&LastCallback; + workItems[1].args[3] = (void*)NULL; + + } + else { + PVOID lpContext; + INIT_ONCE g_InitOnce = INIT_ONCE_STATIC_INIT; + + // Set up work item 0 - In this case InitOnceExecuteOnce + workItems[0].func = (FARPROC)&InitOnceExecuteOnce; + workItems[0].retAddress = NULL; + workItems[0].argc = 4; + workItems[0].args[0] = (void*)&g_InitOnce; + workItems[0].args[1] = (void*)&GenericCallback; + workItems[0].args[2] = (void*)0; + workItems[0].args[3] = (void*)&lpContext; + + // Set up work item 1 - EnumUILanguagesW + workItems[1].func = (FARPROC)&EnumUILanguagesW; + workItems[1].retAddress = NULL; + workItems[1].argc = 3; + workItems[1].args[0] = (void*)&LastCallback; + workItems[1].args[1] = (void*)MUI_LANGUAGE_ID; + workItems[1].args[2] = (void*)NULL; + + } + + // Set up work item 2 - Target function + proxy frame + workItems[2].func = (FARPROC)&ctx->func; + workItems[2].retAddress = addressToPush; + workItems[2].argc = ctx->argc; + memcpy(workItems[2].args, ctx->args, 8 * ctx->argc); + + // Create a work object + PTP_WORK work = CreateThreadpoolWork(FirstCallback, workItems, nullptr); + if (!work) { + printf("Failed CreateThreadpoolWork: %lu\n", GetLastError()); + free(workItems); + return ERROR_CODE; + } + + // Submit it + SubmitThreadpoolWork(work); + + // Wait for completion + WaitForThreadpoolWorkCallbacks(work, FALSE); + CloseThreadpoolWork(work); + + // Voilat, we got the return value where we wanted + uint64_t rax = (uint64_t)(workItems[0].retAddress); + + // Just free this + free(workItems); + + return rax; + +} + + +uint64_t ProxyLoadLibrary(const char* library, unsigned int chain) { + + // We wanna proxy LoadLibraryA + FARPROC targetFunction = reinterpret_cast(LoadLibraryA); + + if (!targetFunction) { + printf("Failed to load function: %lu\n", GetLastError()); + return ERROR_CODE; + } + + unsigned char* libName = (unsigned char*)malloc(256); + if (!libName) { + printf("Memory allocation failed\n"); + return ERROR_CODE; + } + snprintf(reinterpret_cast(libName), 256, library); + + WorkItemContext* ctx = (WorkItemContext*)malloc(sizeof(WorkItemContext)); + if (ctx == NULL) { + printf("[-] Failed to allocate context structure\n"); + return ERROR_CODE; + } + memset(ctx, 0, sizeof(WorkItemContext)); + + ctx->func = targetFunction; + ctx->argc = 1; + ctx->args[0] = (void*)libName; + + // We proxy LoadLibraryA via the chain + uint64_t ret = ProxyPooling(ctx, chain); + free(ctx); + + return ret; + +} + +uint64_t ProxyMessageBox(const char* titleText, const char* msgText, unsigned int chain) { + + // We wanna proxy MessageBoxA + FARPROC targetFunction = reinterpret_cast(MessageBoxA); + + if (!targetFunction) { + printf("Failed to load function: %lu\n", GetLastError()); + return ERROR_CODE; + } + + unsigned char* title = (unsigned char*)malloc(256); + unsigned char* msg = (unsigned char*)malloc(256); + if (!title || !msg) { + printf("Memory allocation failed\n"); + return ERROR_CODE; + } + snprintf(reinterpret_cast(title), 256, titleText); + snprintf(reinterpret_cast(msg), 256, msgText); + + WorkItemContext* ctx = (WorkItemContext*)malloc(sizeof(WorkItemContext)); + if (ctx == NULL) { + printf("[-] Failed to allocate context structure\n"); + return ERROR_CODE; + } + memset(ctx, 0, sizeof(WorkItemContext)); + + ctx->func = targetFunction; + ctx->argc = 4; + ctx->args[0] = (void*)NULL; + ctx->args[1] = (void*)msg; + ctx->args[2] = (void*)title; + ctx->args[3] = (void*)(MB_OK | MB_ICONINFORMATION); + + // We proxy MessageBoxA via the chain + uint64_t ret = ProxyPooling(ctx, chain); + free(ctx); + return ret; +} diff --git a/ThreadPoolExecChain/ProxyCallbacks.h b/ThreadPoolExecChain/ProxyCallbacks.h new file mode 100644 index 0000000..29366b5 --- /dev/null +++ b/ThreadPoolExecChain/ProxyCallbacks.h @@ -0,0 +1,7 @@ +#pragma once +#include + +#define ERROR_CODE 0xCAFEBABECAFEBABE + +uint64_t ProxyLoadLibrary(const char* dllName, unsigned int chain); +uint64_t ProxyMessageBox(const char* title, const char* message, unsigned int chain); diff --git a/ThreadPoolExecChain/Rand.cpp b/ThreadPoolExecChain/Rand.cpp new file mode 100644 index 0000000..3d3df58 --- /dev/null +++ b/ThreadPoolExecChain/Rand.cpp @@ -0,0 +1,14 @@ +#include +#include "Rand.h" + +unsigned int RandintMod3() +{ + unsigned __int64 t = __rdtsc(); + + // Mix bits a little + t ^= (t >> 17); + t ^= (t << 31); + t ^= (t >> 8); + + return (unsigned int)(t % 3); +} \ No newline at end of file diff --git a/ThreadPoolExecChain/Rand.h b/ThreadPoolExecChain/Rand.h new file mode 100644 index 0000000..02ccfb8 --- /dev/null +++ b/ThreadPoolExecChain/Rand.h @@ -0,0 +1,3 @@ +#pragma once + +unsigned int RandintMod3(); \ No newline at end of file diff --git a/ThreadPoolExecChain/ThreadPoolExecChain.cpp b/ThreadPoolExecChain/ThreadPoolExecChain.cpp new file mode 100644 index 0000000..b069af0 --- /dev/null +++ b/ThreadPoolExecChain/ThreadPoolExecChain.cpp @@ -0,0 +1,37 @@ +#include +#include "ProxyCallbacks.h" +#include "Rand.h" + +int main() { + + uint64_t returnValue; + unsigned int chain = RandintMod3(); + + returnValue = ProxyMessageBox("Trying to load something here!", "What? Well... urlmon.dll, of course!", chain++ % 3); + if (returnValue == ERROR_CODE) { + printf("[-] Error Proxying MessageBoxA"); + return 1; + } + + printf("[+] Returned from MessageBoxA: 0x%llx\n", returnValue); + + returnValue = ProxyLoadLibrary("urlmon.dll", chain++ % 3); + if (returnValue == ERROR_CODE) { + printf("[-] Error Proxying LoadLibraryA"); + return 1; + } + + printf("[+] Returned from LoadLibraryA: 0x%llx\n", returnValue); + + char message[64]; + sprintf_s(message, "Address is: 0x%llx", (unsigned long long)returnValue); + returnValue = ProxyMessageBox("Address of urlmon.dll ", message, chain++ % 3); + if (returnValue == ERROR_CODE) { + printf("[-] Error Proxying MessageBoxA"); + return 1; + } + + printf("[+] Returned from MessageBoxA: 0x%llx\n", returnValue); + + return 0; +} diff --git a/ThreadPoolExecChain/ThreadPoolExecChain.vcxproj b/ThreadPoolExecChain/ThreadPoolExecChain.vcxproj new file mode 100644 index 0000000..2e84825 --- /dev/null +++ b/ThreadPoolExecChain/ThreadPoolExecChain.vcxproj @@ -0,0 +1,150 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {9e48f1ca-3b4e-442f-8191-8938efa80a7b} + ThreadPoolExecChain + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + true + MultiThreaded + + + Console + true + + + + + + + + + + + + + + + + + Document + + + + + + + \ No newline at end of file diff --git a/ThreadPoolExecChain/ThreadPoolExecChain.vcxproj.filters b/ThreadPoolExecChain/ThreadPoolExecChain.vcxproj.filters new file mode 100644 index 0000000..57e6870 --- /dev/null +++ b/ThreadPoolExecChain/ThreadPoolExecChain.vcxproj.filters @@ -0,0 +1,50 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/ThreadPoolExecChain/ThreadPoolExecChain.vcxproj.user b/ThreadPoolExecChain/ThreadPoolExecChain.vcxproj.user new file mode 100644 index 0000000..0f14913 --- /dev/null +++ b/ThreadPoolExecChain/ThreadPoolExecChain.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ThreadPoolExecChain/cbN b/ThreadPoolExecChain/cbN new file mode 100644 index 0000000..ed70f88 --- /dev/null +++ b/ThreadPoolExecChain/cbN @@ -0,0 +1,53 @@ +.code +ThreadPoolCallback PROC +pop rbp +add rsp, 30h + +; Load context +mov rax, [rdx]; func ptr +mov r10, rdx +push r10; We save the address of the CTX structure +mov r11, [rdx + 010h]; argc(int) + +cmp r11, 4 +jg skip_call; current implementation only supports up to 4 args + +; Set RCX, RDX, R8, R9 +cmp r11, 0 +jle do_call; no args, just call + +mov rcx, [r10 + 018h] +cmp r11, 1 +jle do_call + +mov rdx, [r10 + 020h] +cmp r11, 2 +jle do_call + +mov r8, [r12 + 028h] +cmp r11, 3 +jle do_call + +mov r9, [r12 + 030h] +cmp r11, 4 +jle do_call + +do_call : +pop r11; We fetch the address of the CTX structure +sub rsp, 30h; We release the stack space +push rbp +mov r10, [r10 + 08h]; addressToPush + +sub rsp, 28h; this is for the spoofed / swapped frame +push r10 +mov rbx, r11; we need this structure in RBX + +jmp rax + +skip_call : +pop r11; We fetch the address of the CTX structure +sub rsp, 30h; We release the stack space +ret +ThreadPoolCallback ENDP + +END \ No newline at end of file