feat: implement Warden API stub dispatch via Unicorn UC_HOOK_CODE

Previously hookAPI() allocated a stub address and registered a C++ handler
but never stored the handler or wrote any executable code to the stub
region, meaning any Warden module call to a Windows API would execute zeros
and crash or silently return garbage.

Changes:
- Store ApiHookEntry {argCount, handler} per stub address in apiHandlers_
- Write RET (0xC3) to stub memory as a safe fallback
- Register UC_HOOK_CODE over the API stub address range during initialize()
- hookCode() now detects stub addresses, reads args from the emulated stack,
  dispatches to the C++ handler, then simulates stdcall epilogue by setting
  EAX/ESP/EIP so Unicorn returns cleanly to the caller
- Convert static-local nextStubAddr to instance member nextApiStubAddr_
  so re-initialization resets the allocator correctly
- Known arg counts for all 7 registered Windows APIs (VirtualAlloc,
  VirtualFree, GetTickCount, Sleep, GetCurrentThreadId,
  GetCurrentProcessId, ReadProcessMemory)
This commit is contained in:
Kelsi 2026-03-17 21:22:41 -07:00
parent b29d76bbc8
commit 005b1fcb54
2 changed files with 92 additions and 16 deletions

View file

@ -147,9 +147,18 @@ private:
uint32_t heapSize_; // Heap size uint32_t heapSize_; // Heap size
uint32_t apiStubBase_; // API stub base address uint32_t apiStubBase_; // API stub base address
// API hooks: DLL name -> Function name -> Handler // API hooks: DLL name -> Function name -> stub address
std::map<std::string, std::map<std::string, uint32_t>> apiAddresses_; std::map<std::string, std::map<std::string, uint32_t>> apiAddresses_;
// API stub dispatch: stub address -> {argCount, handler}
struct ApiHookEntry {
int argCount;
std::function<uint32_t(WardenEmulator&, const std::vector<uint32_t>&)> handler;
};
std::map<uint32_t, ApiHookEntry> apiHandlers_;
uint32_t nextApiStubAddr_; // tracks next free stub slot (replaces static local)
bool apiCodeHookRegistered_; // true once UC_HOOK_CODE for stub range is added
// Memory allocation tracking // Memory allocation tracking
std::map<uint32_t, size_t> allocations_; std::map<uint32_t, size_t> allocations_;
std::map<uint32_t, size_t> freeBlocks_; // free-list keyed by base address std::map<uint32_t, size_t> freeBlocks_; // free-list keyed by base address

View file

@ -32,6 +32,8 @@ WardenEmulator::WardenEmulator()
, heapBase_(HEAP_BASE) , heapBase_(HEAP_BASE)
, heapSize_(HEAP_SIZE) , heapSize_(HEAP_SIZE)
, apiStubBase_(API_STUB_BASE) , apiStubBase_(API_STUB_BASE)
, nextApiStubAddr_(API_STUB_BASE)
, apiCodeHookRegistered_(false)
, nextHeapAddr_(HEAP_BASE) , nextHeapAddr_(HEAP_BASE)
{ {
} }
@ -51,8 +53,11 @@ bool WardenEmulator::initialize(const void* moduleCode, size_t moduleSize, uint3
allocations_.clear(); allocations_.clear();
freeBlocks_.clear(); freeBlocks_.clear();
apiAddresses_.clear(); apiAddresses_.clear();
apiHandlers_.clear();
hooks_.clear(); hooks_.clear();
nextHeapAddr_ = heapBase_; nextHeapAddr_ = heapBase_;
nextApiStubAddr_ = apiStubBase_;
apiCodeHookRegistered_ = false;
{ {
char addrBuf[32]; char addrBuf[32];
@ -149,6 +154,13 @@ bool WardenEmulator::initialize(const void* moduleCode, size_t moduleSize, uint3
uc_hook_add(uc_, &hh, UC_HOOK_MEM_INVALID, (void*)hookMemInvalid, this, 1, 0); uc_hook_add(uc_, &hh, UC_HOOK_MEM_INVALID, (void*)hookMemInvalid, this, 1, 0);
hooks_.push_back(hh); hooks_.push_back(hh);
// Add code hook over the API stub area so Windows API calls are intercepted
uc_hook apiHook;
uc_hook_add(uc_, &apiHook, UC_HOOK_CODE, (void*)hookCode, this,
API_STUB_BASE, API_STUB_BASE + 0x10000 - 1);
hooks_.push_back(apiHook);
apiCodeHookRegistered_ = true;
{ {
char sBuf[128]; char sBuf[128];
std::snprintf(sBuf, sizeof(sBuf), "WardenEmulator: Emulator initialized Stack: 0x%X-0x%X Heap: 0x%X-0x%X", std::snprintf(sBuf, sizeof(sBuf), "WardenEmulator: Emulator initialized Stack: 0x%X-0x%X Heap: 0x%X-0x%X",
@ -161,23 +173,45 @@ bool WardenEmulator::initialize(const void* moduleCode, size_t moduleSize, uint3
uint32_t WardenEmulator::hookAPI(const std::string& dllName, uint32_t WardenEmulator::hookAPI(const std::string& dllName,
const std::string& functionName, const std::string& functionName,
[[maybe_unused]] std::function<uint32_t(WardenEmulator&, const std::vector<uint32_t>&)> handler) { std::function<uint32_t(WardenEmulator&, const std::vector<uint32_t>&)> handler) {
// Allocate address for this API stub // Allocate address for this API stub (16 bytes each)
static uint32_t nextStubAddr = API_STUB_BASE; uint32_t stubAddr = nextApiStubAddr_;
uint32_t stubAddr = nextStubAddr; nextApiStubAddr_ += 16;
nextStubAddr += 16; // Space for stub code
// Store mapping // Store address mapping for IAT patching
apiAddresses_[dllName][functionName] = stubAddr; apiAddresses_[dllName][functionName] = stubAddr;
{ // Determine stdcall arg count from known Windows APIs so the hook can
char hBuf[32]; // clean up the stack correctly (RETN N convention).
std::snprintf(hBuf, sizeof(hBuf), "0x%X", stubAddr); static const std::pair<const char*, int> knownArgCounts[] = {
LOG_DEBUG("WardenEmulator: Hooked ", dllName, "!", functionName, " at ", hBuf); {"VirtualAlloc", 4},
{"VirtualFree", 3},
{"GetTickCount", 0},
{"Sleep", 1},
{"GetCurrentThreadId", 0},
{"GetCurrentProcessId", 0},
{"ReadProcessMemory", 5},
};
int argCount = 0;
for (const auto& [name, cnt] : knownArgCounts) {
if (functionName == name) { argCount = cnt; break; }
} }
// TODO: Write stub code that triggers a hook callback // Store the handler so hookCode() can dispatch to it
// For now, just return the address for IAT patching apiHandlers_[stubAddr] = { argCount, std::move(handler) };
// Write a RET (0xC3) at the stub address as a safe fallback in case
// the code hook fires after EIP has already advanced past our intercept.
if (uc_) {
static const uint8_t retInstr = 0xC3;
uc_mem_write(uc_, stubAddr, &retInstr, 1);
}
{
char hBuf[64];
std::snprintf(hBuf, sizeof(hBuf), "0x%X (argCount=%d)", stubAddr, argCount);
LOG_DEBUG("WardenEmulator: Hooked ", dllName, "!", functionName, " at ", hBuf);
}
return stubAddr; return stubAddr;
} }
@ -503,8 +537,40 @@ uint32_t WardenEmulator::apiReadProcessMemory(WardenEmulator& emu, const std::ve
// Unicorn Callbacks // Unicorn Callbacks
// ============================================================================ // ============================================================================
void WardenEmulator::hookCode([[maybe_unused]] uc_engine* uc, uint64_t address, [[maybe_unused]] uint32_t size, [[maybe_unused]] void* userData) { void WardenEmulator::hookCode(uc_engine* uc, uint64_t address, [[maybe_unused]] uint32_t size, void* userData) {
(void)address; // Trace disabled by default to avoid log spam auto* self = static_cast<WardenEmulator*>(userData);
if (!self) return;
auto it = self->apiHandlers_.find(static_cast<uint32_t>(address));
if (it == self->apiHandlers_.end()) return; // not an API stub — trace disabled to avoid spam
const ApiHookEntry& entry = it->second;
// Read stack: [ESP+0] = return address, [ESP+4..] = stdcall args
uint32_t esp = 0;
uc_reg_read(uc, UC_X86_REG_ESP, &esp);
uint32_t retAddr = 0;
uc_mem_read(uc, esp, &retAddr, 4);
std::vector<uint32_t> args(static_cast<size_t>(entry.argCount));
for (int i = 0; i < entry.argCount; ++i) {
uint32_t val = 0;
uc_mem_read(uc, esp + 4 + static_cast<uint32_t>(i) * 4, &val, 4);
args[static_cast<size_t>(i)] = val;
}
// Dispatch to the C++ handler
uint32_t retVal = 0;
if (entry.handler) {
retVal = entry.handler(*self, args);
}
// Simulate stdcall epilogue: pop return address + args
uint32_t newEsp = esp + 4 + static_cast<uint32_t>(entry.argCount) * 4;
uc_reg_write(uc, UC_X86_REG_EAX, &retVal);
uc_reg_write(uc, UC_X86_REG_ESP, &newEsp);
uc_reg_write(uc, UC_X86_REG_EIP, &retAddr);
} }
void WardenEmulator::hookMemInvalid([[maybe_unused]] uc_engine* uc, int type, uint64_t address, int size, [[maybe_unused]] int64_t value, [[maybe_unused]] void* userData) { void WardenEmulator::hookMemInvalid([[maybe_unused]] uc_engine* uc, int type, uint64_t address, int size, [[maybe_unused]] int64_t value, [[maybe_unused]] void* userData) {
@ -533,7 +599,8 @@ WardenEmulator::WardenEmulator()
: uc_(nullptr), moduleBase_(0), moduleSize_(0) : uc_(nullptr), moduleBase_(0), moduleSize_(0)
, stackBase_(0), stackSize_(0) , stackBase_(0), stackSize_(0)
, heapBase_(0), heapSize_(0) , heapBase_(0), heapSize_(0)
, apiStubBase_(0), nextHeapAddr_(0) {} , apiStubBase_(0), nextApiStubAddr_(0), apiCodeHookRegistered_(false)
, nextHeapAddr_(0) {}
WardenEmulator::~WardenEmulator() {} WardenEmulator::~WardenEmulator() {}
bool WardenEmulator::initialize(const void*, size_t, uint32_t) { return false; } bool WardenEmulator::initialize(const void*, size_t, uint32_t) { return false; }
uint32_t WardenEmulator::hookAPI(const std::string&, const std::string&, uint32_t WardenEmulator::hookAPI(const std::string&, const std::string&,