From 5f5bc41fd532a41fe3be9f67c551d24f70a74df8 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 7 Sep 2026 17:22:15 -0400 Subject: [PATCH] m0: binkw32 proxy shim, minhook, trace hook on Application::Initialize; build/deploy tooling --- CMakeLists.txt | 42 ++ CMakePresets.json | 27 + cmake/toolchain-mingw-i686.cmake | 17 + docs/M0.md | 72 ++ src/shim/binkw32.def | 71 ++ src/shim/main.cpp | 184 +++++ src/shim/shim.cfg | 7 + tests/addr_smoke.cpp | 11 + third_party/minhook/LICENSE.txt | 81 +++ third_party/minhook/VENDOR.txt | 3 + third_party/minhook/include/MinHook.h | 185 +++++ third_party/minhook/src/buffer.c | 311 +++++++++ third_party/minhook/src/buffer.h | 42 ++ third_party/minhook/src/hde/hde32.c | 462 +++++++++++++ third_party/minhook/src/hde/hde32.h | 105 +++ third_party/minhook/src/hde/hde64.c | 470 +++++++++++++ third_party/minhook/src/hde/hde64.h | 112 +++ third_party/minhook/src/hde/pstdint.h | 39 ++ third_party/minhook/src/hde/table32.h | 73 ++ third_party/minhook/src/hde/table64.h | 74 ++ third_party/minhook/src/hook.c | 939 ++++++++++++++++++++++++++ third_party/minhook/src/trampoline.c | 320 +++++++++ third_party/minhook/src/trampoline.h | 105 +++ tools/build-shim.sh | 38 ++ tools/deploy.ps1 | 52 ++ tools/sync-build.sh | 17 + tools/undeploy.ps1 | 27 + 27 files changed, 3886 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 CMakePresets.json create mode 100644 cmake/toolchain-mingw-i686.cmake create mode 100644 docs/M0.md create mode 100644 src/shim/binkw32.def create mode 100644 src/shim/main.cpp create mode 100644 src/shim/shim.cfg create mode 100644 tests/addr_smoke.cpp create mode 100644 third_party/minhook/LICENSE.txt create mode 100644 third_party/minhook/VENDOR.txt create mode 100644 third_party/minhook/include/MinHook.h create mode 100644 third_party/minhook/src/buffer.c create mode 100644 third_party/minhook/src/buffer.h create mode 100644 third_party/minhook/src/hde/hde32.c create mode 100644 third_party/minhook/src/hde/hde32.h create mode 100644 third_party/minhook/src/hde/hde64.c create mode 100644 third_party/minhook/src/hde/hde64.h create mode 100644 third_party/minhook/src/hde/pstdint.h create mode 100644 third_party/minhook/src/hde/table32.h create mode 100644 third_party/minhook/src/hde/table64.h create mode 100644 third_party/minhook/src/hook.c create mode 100644 third_party/minhook/src/trampoline.c create mode 100644 third_party/minhook/src/trampoline.h create mode 100755 tools/build-shim.sh create mode 100644 tools/deploy.ps1 create mode 100755 tools/sync-build.sh create mode 100644 tools/undeploy.ps1 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..04c313b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.25) +project(sots-engine LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Build id stamped into the shim log banner (tools/build-shim.sh passes git describe + UTC time). +set(SHIM_BUILD_ID "dev" CACHE STRING "Build identifier logged by the shim") + +# Provenance of the binary facts: the generated header's own "Source:" line, forwarded verbatim +# so every shim.log records which RE snapshot the RVAs came from. +file(STRINGS include/generated/sots_addresses.h _addr_provenance REGEX "^// Source:") +list(GET _addr_provenance 0 _addr_provenance) +string(REGEX REPLACE "^// " "" SOTS_ADDR_PROVENANCE "${_addr_provenance}") + +add_library(sots_addresses INTERFACE) +target_include_directories(sots_addresses INTERFACE ${CMAKE_SOURCE_DIR}/include) + +if(WIN32) + # ---- shim: proxy binkw32.dll that the original game loads (Phase 2 frontend) ---- + add_library(minhook STATIC + third_party/minhook/src/buffer.c + third_party/minhook/src/hook.c + third_party/minhook/src/trampoline.c + third_party/minhook/src/hde/hde32.c) + target_include_directories(minhook PUBLIC third_party/minhook/include) + + add_library(binkw32 SHARED src/shim/main.cpp src/shim/binkw32.def) + target_link_libraries(binkw32 PRIVATE minhook sots_addresses) + target_compile_definitions(binkw32 PRIVATE + SHIM_BUILD_ID="${SHIM_BUILD_ID}" + SOTS_ADDR_PROVENANCE="${SOTS_ADDR_PROVENANCE}") + # Must be exactly binkw32.dll (no "lib" prefix); export names come solely from the .def. + set_target_properties(binkw32 PROPERTIES PREFIX "" OUTPUT_NAME "binkw32") +else() + # ---- host: nothing to build yet; keep the preset alive with a header smoke test ---- + enable_testing() + add_executable(addr_smoke tests/addr_smoke.cpp) + target_link_libraries(addr_smoke PRIVATE sots_addresses) + add_test(NAME addr_smoke COMMAND addr_smoke) +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..261bef2 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,27 @@ +{ + "version": 6, + "configurePresets": [ + { + "name": "shim", + "displayName": "shim (i686 MinGW cross, binkw32.dll)", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build-shim", + "toolchainFile": "${sourceDir}/cmake/toolchain-mingw-i686.cmake", + "cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" } + }, + { + "name": "host", + "displayName": "host (Linux, tests)", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build-host", + "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } + } + ], + "buildPresets": [ + { "name": "shim", "configurePreset": "shim" }, + { "name": "host", "configurePreset": "host" } + ], + "testPresets": [ + { "name": "host", "configurePreset": "host", "output": { "outputOnFailure": true } } + ] +} diff --git a/cmake/toolchain-mingw-i686.cmake b/cmake/toolchain-mingw-i686.cmake new file mode 100644 index 0000000..f9b66a0 --- /dev/null +++ b/cmake/toolchain-mingw-i686.cmake @@ -0,0 +1,17 @@ +# Cross toolchain: Linux host -> 32-bit Windows (the game is a 32-bit MSVC 2010 exe). +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR i686) + +set(_triple i686-w64-mingw32) +set(CMAKE_C_COMPILER ${_triple}-gcc) +set(CMAKE_CXX_COMPILER ${_triple}-g++) +set(CMAKE_RC_COMPILER ${_triple}-windres) + +set(CMAKE_FIND_ROOT_PATH /usr/${_triple}) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +# No libgcc/libstdc++ DLLs next to the game: link the runtimes in. +set(CMAKE_EXE_LINKER_FLAGS_INIT "-static-libgcc -static-libstdc++") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "-static-libgcc -static-libstdc++") diff --git a/docs/M0.md b/docs/M0.md new file mode 100644 index 0000000..2b02ca1 --- /dev/null +++ b/docs/M0.md @@ -0,0 +1,72 @@ +# M0 — shim bootstrap (proxy `binkw32.dll` + one hook) + +**Result (2026-09-07): proven end to end on the real game.** The proxy DLL loads, forwards all +Bink calls, relocates the target RVA against the ASLR'd exe base, hooks `Mars::Application::Initialize`, +logs the call, and the game plays its intro and reaches the main menu. + +## What was built +- `src/shim/binkw32.def` — 66 forwarders (`_BinkOpen@8 = binkw32_real._BinkOpen@8 @41` style), + names and ordinals mirrored from the real DLL. Note: the real DLL has **66** exports including + `_RADTimerRead@0`, which earlier lists missed. The exe imports 12 of them **by name** (no ordinals). +- `src/shim/main.cpp` — `DllMain` → `Shim_Init`: opens `\shim.log`, logs banner (build id, + exe path, exe base + ASLR delta, provenance line lifted from `sots_addresses.h`), reads + `shim.cfg` (`hooks=trace|off`), `MH_Initialize`, one MinHook hook on + `exeBase + sots::addr::Mars_Application_Initialize`, `MH_EnableHook`. Detach → `MH_Uninitialize`. +- `third_party/minhook/` — MinHook vendored (BSD-2, see `VENDOR.txt` for commit). +- `CMakeLists.txt`, `cmake/toolchain-mingw-i686.cmake`, `CMakePresets.json` (`shim` = i686 MinGW + cross, `host` = Linux placeholder test `tests/addr_smoke.cpp`). +- `tools/build-shim.sh` (build box), `tools/sync-build.sh` (dev box → build box), `tools/deploy.ps1`, + `tools/undeploy.ps1` (game VM). + +## Build & deploy +``` +# dev box: push the tree to spicy:/bulk-storage/re-lab/build/sots-engine (== CT111 /srv/re-lab/...) +# and run tools/build-shim.sh inside CT111 (needs g++-mingw-w64-i686 binutils-mingw-w64-i686 cmake ninja-build) +tools/sync-build.sh +# -> builds build-shim/binkw32.dll, diffs its export-name table against the real DLL (fails on mismatch), +# stages binkw32.dll + shim.cfg + deploy.ps1 + undeploy.ps1 + BUILD_ID in /srv/re-lab/shim/dist (= Z:\shim\dist) + +# game VM (re@192.168.10.139): Z: is per-logon, so pass share creds (or set RELAB_USER/RELAB_PASS) +powershell -ExecutionPolicy Bypass -File deploy.ps1 -ShareUser re -SharePass +# stops the game, keeps the original as C:\SOTS\binkw32_real.dll (only if not already a proxy), +# copies the proxy, writes shim.cfg only if absent, relaunches via scheduled task SOTS +powershell -ExecutionPolicy Bypass -File undeploy.ps1 # put the original back +``` +Logs: `C:\SOTS\shim.log` (append mode, one banner per run). Screenshots: `qm monitor 140` screendump. + +## Evidence (`/bulk-storage/re-lab/shim/logs/`) +- `m0.log` — the shim log. Key lines from the passing run (build `4a15301-20260907T2118Z`): + ``` + ==== sots-engine shim (binkw32 proxy) build 4a15301-20260907T2118Z ==== + exe base=0x00e80000 (link-time image base 0x00400000, ASLR delta +11010048) pid=3808 shim=730c0000 + addresses: Source: sots-re ghidra/addresses.json @ daea98f, generated 2026-09-07 by tools/gen_addresses.py + hook: Mars_Application_Initialize rva=0x004a0e50 -> va=01320e50 + hook: MH_Initialize -> MH_OK / MH_CreateHook -> MH_OK (trampoline=00e50fe0) / MH_EnableHook -> MH_OK + Application::Initialize called (this=035c8128) + ``` +- `m0-main-menu.png` — main menu with the hook live (intro skipped with 3× Esc, ~35 s after launch). +- Export table: `build-shim/exports.txt`; `tools/build-shim.sh` printed + `exports: 66 names, identical to binkw32.dll`. +- `hooks=off` run (first banner in `m0.log`, pid 6396): forwarders alone play the Bink intro — proxy proven + separately from hooking. +- `tools/clean_room_check.sh` → OK. + +## Gotchas +1. **Do not call the original from a C++ `thiscall` detour.** v1 did exactly that + (`void __thiscall Detour(void* self) { log; orig(self); }`) and the game died inside Initialize with + "Mars: Application error encountered" (`m0-crash-hookv1.{log,png}`). The prototype in + `sots_addresses.h` is `[unverified]`; if Initialize takes stack args, returns via EAX or relies on + EDX (fastcall-like), a calling wrapper corrupts the stack/registers on return. The shipped detour is + an asm stub: `pushfl/pushal`, call a C logger with ECX, `popal/popfl`, **`jmp` to the trampoline**. + It's correct for any convention but cannot log the return. Until a prototype is verified, use this + pattern for every trace hook; only replace-hooks (that never call the original) may be plain C++. +2. Hooking from `DllMain` worked (process is single-threaded at that point; MinHook only touches + kernel32). The fallback (install from the first forwarded Bink call) was not needed. +3. `--kill-at` / `--enable-stdcall-fixup` must NOT be passed to ld — they would strip the `@N` + suffixes the exe imports by name. With a plain `.def` MinGW exports the names verbatim. +4. `objdump -p` lists `+base[` rows in both the address table and the name table; compare the + `[Ordinal/Name Pointer] Table` section only (forwarder rows print `Forwarder RVA -- target`). +5. rsync `--exclude 'build-*'` also matches `build-shim.sh`; use `/build-*/`. +6. The CT is unprivileged: files written on the host must be `--chown=100000:100000` or the CT can't + write its build dir. `Z:` mappings are per logon on the VM; SSH sessions start without it. +7. `shim.log` is held open by the running game; move/rotate it only after `Stop-Process`. diff --git a/src/shim/binkw32.def b/src/shim/binkw32.def new file mode 100644 index 0000000..e704c66 --- /dev/null +++ b/src/shim/binkw32.def @@ -0,0 +1,71 @@ +; binkw32.dll proxy: every export of the real Bink DLL is forwarded to binkw32_real.dll +; (the original, renamed by tools/deploy.ps1). Names and ordinals mirror the real DLL exactly; +; the exe imports 12 of these by decorated name, so they must match byte-for-byte. +LIBRARY binkw32.dll +EXPORTS + _BinkBufferBlit@12 = binkw32_real._BinkBufferBlit@12 @1 + _BinkBufferCheckWinPos@12 = binkw32_real._BinkBufferCheckWinPos@12 @2 + _BinkBufferClear@8 = binkw32_real._BinkBufferClear@8 @3 + _BinkBufferClose@4 = binkw32_real._BinkBufferClose@4 @4 + _BinkBufferGetDescription@4 = binkw32_real._BinkBufferGetDescription@4 @5 + _BinkBufferGetError@0 = binkw32_real._BinkBufferGetError@0 @6 + _BinkBufferLock@4 = binkw32_real._BinkBufferLock@4 @7 + _BinkBufferOpen@16 = binkw32_real._BinkBufferOpen@16 @8 + _BinkBufferSetDirectDraw@8 = binkw32_real._BinkBufferSetDirectDraw@8 @9 + _BinkBufferSetHWND@8 = binkw32_real._BinkBufferSetHWND@8 @10 + _BinkBufferSetOffset@12 = binkw32_real._BinkBufferSetOffset@12 @11 + _BinkBufferSetResolution@12 = binkw32_real._BinkBufferSetResolution@12 @12 + _BinkBufferSetScale@12 = binkw32_real._BinkBufferSetScale@12 @13 + _BinkBufferUnlock@4 = binkw32_real._BinkBufferUnlock@4 @14 + _BinkCheckCursor@20 = binkw32_real._BinkCheckCursor@20 @15 + _BinkClose@4 = binkw32_real._BinkClose@4 @16 + _BinkCloseTrack@4 = binkw32_real._BinkCloseTrack@4 @17 + _BinkControlBackgroundIO@8 = binkw32_real._BinkControlBackgroundIO@8 @18 + _BinkControlPlatformFeatures@8 = binkw32_real._BinkControlPlatformFeatures@8 @19 + _BinkCopyToBuffer@28 = binkw32_real._BinkCopyToBuffer@28 @20 + _BinkCopyToBufferRect@44 = binkw32_real._BinkCopyToBufferRect@44 @21 + _BinkDDSurfaceType@4 = binkw32_real._BinkDDSurfaceType@4 @22 + _BinkDX8SurfaceType@4 = binkw32_real._BinkDX8SurfaceType@4 @23 + _BinkDX9SurfaceType@4 = binkw32_real._BinkDX9SurfaceType@4 @24 + _BinkDoFrame@4 = binkw32_real._BinkDoFrame@4 @25 + _BinkGetError@0 = binkw32_real._BinkGetError@0 @26 + _BinkGetFrameBuffersInfo@8 = binkw32_real._BinkGetFrameBuffersInfo@8 @27 + _BinkGetKeyFrame@12 = binkw32_real._BinkGetKeyFrame@12 @28 + _BinkGetPalette@4 = binkw32_real._BinkGetPalette@4 @29 + _BinkGetRealtime@12 = binkw32_real._BinkGetRealtime@12 @30 + _BinkGetRects@8 = binkw32_real._BinkGetRects@8 @31 + _BinkGetSummary@8 = binkw32_real._BinkGetSummary@8 @32 + _BinkGetTrackData@8 = binkw32_real._BinkGetTrackData@8 @33 + _BinkGetTrackID@8 = binkw32_real._BinkGetTrackID@8 @34 + _BinkGetTrackMaxSize@8 = binkw32_real._BinkGetTrackMaxSize@8 @35 + _BinkGetTrackType@8 = binkw32_real._BinkGetTrackType@8 @36 + _BinkGoto@12 = binkw32_real._BinkGoto@12 @37 + _BinkIsSoftwareCursor@8 = binkw32_real._BinkIsSoftwareCursor@8 @38 + _BinkLogoAddress@0 = binkw32_real._BinkLogoAddress@0 @39 + _BinkNextFrame@4 = binkw32_real._BinkNextFrame@4 @40 + _BinkOpen@8 = binkw32_real._BinkOpen@8 @41 + _BinkOpenDirectSound@4 = binkw32_real._BinkOpenDirectSound@4 @42 + _BinkOpenMiles@4 = binkw32_real._BinkOpenMiles@4 @43 + _BinkOpenTrack@8 = binkw32_real._BinkOpenTrack@8 @44 + _BinkOpenWaveOut@4 = binkw32_real._BinkOpenWaveOut@4 @45 + _BinkPause@8 = binkw32_real._BinkPause@8 @46 + _BinkRegisterFrameBuffers@8 = binkw32_real._BinkRegisterFrameBuffers@8 @47 + _BinkRestoreCursor@4 = binkw32_real._BinkRestoreCursor@4 @48 + _BinkService@4 = binkw32_real._BinkService@4 @49 + _BinkSetError@4 = binkw32_real._BinkSetError@4 @50 + _BinkSetFrameRate@8 = binkw32_real._BinkSetFrameRate@8 @51 + _BinkSetIO@4 = binkw32_real._BinkSetIO@4 @52 + _BinkSetIOSize@4 = binkw32_real._BinkSetIOSize@4 @53 + _BinkSetMemory@8 = binkw32_real._BinkSetMemory@8 @54 + _BinkSetMixBinVolumes@20 = binkw32_real._BinkSetMixBinVolumes@20 @55 + _BinkSetMixBins@16 = binkw32_real._BinkSetMixBins@16 @56 + _BinkSetPan@12 = binkw32_real._BinkSetPan@12 @57 + _BinkSetSimulate@4 = binkw32_real._BinkSetSimulate@4 @58 + _BinkSetSoundOnOff@8 = binkw32_real._BinkSetSoundOnOff@8 @59 + _BinkSetSoundSystem@8 = binkw32_real._BinkSetSoundSystem@8 @60 + _BinkSetSoundTrack@8 = binkw32_real._BinkSetSoundTrack@8 @61 + _BinkSetVideoOnOff@8 = binkw32_real._BinkSetVideoOnOff@8 @62 + _BinkSetVolume@12 = binkw32_real._BinkSetVolume@12 @63 + _BinkShouldSkip@4 = binkw32_real._BinkShouldSkip@4 @64 + _BinkWait@4 = binkw32_real._BinkWait@4 @65 + _RADTimerRead@0 = binkw32_real._RADTimerRead@0 @66 diff --git a/src/shim/main.cpp b/src/shim/main.cpp new file mode 100644 index 0000000..5b67d8a --- /dev/null +++ b/src/shim/main.cpp @@ -0,0 +1,184 @@ +// sots-engine shim: a proxy binkw32.dll the original game loads. +// +// Every Bink export is forwarded to the real DLL by the linker (see binkw32.def); this file only +// exists to get code running inside the game process. M0 scope: log a banner, install one hook on +// Mars::Application::Initialize, and prove injection + ASLR relocation + hooking end to end. +// +// Binary facts (RVAs, calling conventions) come exclusively from the generated header. + +#include + +#include +#include +#include + +#include "MinHook.h" +#include "generated/sots_addresses.h" + +namespace { + +// ---- logging ------------------------------------------------------------------------------ + +FILE* g_log = nullptr; +char g_dir[MAX_PATH] = {}; // directory the shim DLL lives in (== game dir) + +void Log(const char* fmt, ...) { + if (!g_log) return; + SYSTEMTIME st; + GetLocalTime(&st); + std::fprintf(g_log, "%02u:%02u:%02u.%03u [tid %5lu] ", st.wHour, st.wMinute, st.wSecond, + st.wMilliseconds, GetCurrentThreadId()); + va_list ap; + va_start(ap, fmt); + std::vfprintf(g_log, fmt, ap); + va_end(ap); + std::fputc('\n', g_log); + std::fflush(g_log); +} + +// ---- config (shim.cfg next to the DLL; "key=value" per line) ------------------------------ + +struct Config { + bool hooks = true; // hooks=trace (default) | off +}; + +Config ReadConfig() { + Config cfg; + char path[MAX_PATH]; + std::snprintf(path, sizeof path, "%s\\shim.cfg", g_dir); + FILE* f = std::fopen(path, "r"); + if (!f) { + Log("config: %s not found, using defaults", path); + return cfg; + } + char line[256]; + while (std::fgets(line, sizeof line, f)) { + char* p = line; + while (*p == ' ' || *p == '\t') ++p; + if (*p == '#' || *p == ';' || *p == '\n' || *p == '\0') continue; + char* eq = std::strchr(p, '='); + if (!eq) continue; + *eq = '\0'; + char* val = eq + 1; + val[std::strcspn(val, "\r\n")] = '\0'; + if (std::strcmp(p, "hooks") == 0) { + cfg.hooks = std::strcmp(val, "off") != 0; + Log("config: hooks=%s", val); + } else { + Log("config: ignoring unknown key '%s'", p); + } + } + std::fclose(f); + return cfg; +} + +// ---- the one M0 hook ---------------------------------------------------------------------- + +bool g_minhookUp = false; + +} // namespace + +// The detour is a register-transparent asm stub rather than a C++ thiscall function: the +// prototype in sots_addresses.h is [unverified], and a C++ detour that *calls* the original +// bakes in assumptions about stack args / EDX / return value. Calling out for the log with +// everything saved and then tail-jumping to the trampoline leaves the original's own `ret` +// in charge of cleanup, so the hook is correct for any calling convention. (v1 of this hook +// was a thiscall wrapper and crashed the game inside Initialize.) +extern "C" void* g_origInitialize; // trampoline, filled in by MH_CreateHook +void* g_origInitialize = nullptr; +extern "C" void InitializeDetour(); + +extern "C" void LogInitializeCalled(void* self) { + Log("Application::Initialize called (this=%p)", self); +} + +asm(R"( + .text + .globl _InitializeDetour +_InitializeDetour: + pushfl + pushal + pushl %ecx + call _LogInitializeCalled + addl $4, %esp + popal + popfl + jmp *_g_origInitialize +)"); + +namespace { + +void InstallHooks() { + const uintptr_t exeBase = reinterpret_cast(GetModuleHandleA(nullptr)); + void* target = reinterpret_cast(exeBase + sots::addr::Mars_Application_Initialize); + Log("hook: Mars_Application_Initialize rva=0x%08x -> va=%p", sots::addr::Mars_Application_Initialize, + target); + + MH_STATUS st = MH_Initialize(); + Log("hook: MH_Initialize -> %s", MH_StatusToString(st)); + if (st != MH_OK) return; + g_minhookUp = true; + + st = MH_CreateHook(target, reinterpret_cast(&InitializeDetour), &g_origInitialize); + Log("hook: MH_CreateHook -> %s (trampoline=%p)", MH_StatusToString(st), g_origInitialize); + if (st != MH_OK) return; + + st = MH_EnableHook(target); + Log("hook: MH_EnableHook -> %s", MH_StatusToString(st)); +} + +// ---- lifecycle ----------------------------------------------------------------------------- + +void Shim_Init(HMODULE self) { + GetModuleFileNameA(self, g_dir, sizeof g_dir); + if (char* slash = std::strrchr(g_dir, '\\')) *slash = '\0'; + + char logPath[MAX_PATH]; + std::snprintf(logPath, sizeof logPath, "%s\\shim.log", g_dir); + g_log = std::fopen(logPath, "a"); + if (!g_log) return; // nothing we can do; the forwarders still work without us + + char exePath[MAX_PATH] = {}; + GetModuleFileNameA(nullptr, exePath, sizeof exePath); + const uintptr_t exeBase = reinterpret_cast(GetModuleHandleA(nullptr)); + + Log("==== sots-engine shim (binkw32 proxy) build %s ====", SHIM_BUILD_ID); + Log("exe: %s", exePath); + Log("exe base=0x%08lx (link-time image base 0x%08lx, ASLR delta %+ld) pid=%lu shim=%p", + static_cast(exeBase), static_cast(sots::addr::IMAGE_BASE), + static_cast(exeBase - sots::addr::IMAGE_BASE), GetCurrentProcessId(), + static_cast(self)); + Log("addresses: %s", SOTS_ADDR_PROVENANCE); + + const Config cfg = ReadConfig(); + if (cfg.hooks) { + InstallHooks(); + } else { + Log("hook: disabled by config"); + } +} + +void Shim_Shutdown() { + if (g_minhookUp) { + MH_STATUS st = MH_Uninitialize(); + Log("shutdown: MH_Uninitialize -> %s", MH_StatusToString(st)); + } + Log("==== shim detach ===="); + if (g_log) std::fclose(g_log); + g_log = nullptr; +} + +} // namespace + +extern "C" BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID) { + switch (reason) { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hinst); + Shim_Init(hinst); + break; + case DLL_PROCESS_DETACH: + Shim_Shutdown(); + break; + } + return TRUE; +} diff --git a/src/shim/shim.cfg b/src/shim/shim.cfg new file mode 100644 index 0000000..c6df8d1 --- /dev/null +++ b/src/shim/shim.cfg @@ -0,0 +1,7 @@ +# sots-engine shim configuration. Read once when binkw32.dll (the proxy) loads. +# One key=value per line; '#' starts a comment. +# +# hooks = trace | off +# trace install the hooks and log them to shim.log (default) +# off forward Bink calls only; touch nothing in the exe +hooks=trace diff --git a/tests/addr_smoke.cpp b/tests/addr_smoke.cpp new file mode 100644 index 0000000..25b1a10 --- /dev/null +++ b/tests/addr_smoke.cpp @@ -0,0 +1,11 @@ +// Host-side placeholder: the generated address header must compile and stay in-image. +#include "generated/sots_addresses.h" +#include + +int main() { + static_assert(sots::addr::Mars_Application_Initialize > 0 && + sots::addr::Mars_Application_Initialize < 0x01000000, + "RVA out of range"); + std::printf("Mars_Application_Initialize rva=0x%08x\n", sots::addr::Mars_Application_Initialize); + return 0; +} diff --git a/third_party/minhook/LICENSE.txt b/third_party/minhook/LICENSE.txt new file mode 100644 index 0000000..74dea27 --- /dev/null +++ b/third_party/minhook/LICENSE.txt @@ -0,0 +1,81 @@ +MinHook - The Minimalistic API Hooking Library for x64/x86 +Copyright (C) 2009-2017 Tsuda Kageyu. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +================================================================================ +Portions of this software are Copyright (c) 2008-2009, Vyacheslav Patkov. +================================================================================ +Hacker Disassembler Engine 32 C +Copyright (c) 2008-2009, Vyacheslav Patkov. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +Hacker Disassembler Engine 64 C +Copyright (c) 2008-2009, Vyacheslav Patkov. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/minhook/VENDOR.txt b/third_party/minhook/VENDOR.txt new file mode 100644 index 0000000..ea78d8a --- /dev/null +++ b/third_party/minhook/VENDOR.txt @@ -0,0 +1,3 @@ +MinHook vendored from https://github.com/TsudaKageyu/minhook +commit 7586d1a64ad87864e336560327f6b4bd720a6917 (2026-09-07), copied src/ include/ LICENSE.txt unmodified. +License: BSD 2-Clause (see LICENSE.txt). diff --git a/third_party/minhook/include/MinHook.h b/third_party/minhook/include/MinHook.h new file mode 100644 index 0000000..ae39350 --- /dev/null +++ b/third_party/minhook/include/MinHook.h @@ -0,0 +1,185 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__) + #error MinHook supports only x86 and x64 systems. +#endif + +#include + +// MinHook Error Codes. +typedef enum MH_STATUS +{ + // Unknown error. Should not be returned. + MH_UNKNOWN = -1, + + // Successful. + MH_OK = 0, + + // MinHook is already initialized. + MH_ERROR_ALREADY_INITIALIZED, + + // MinHook is not initialized yet, or already uninitialized. + MH_ERROR_NOT_INITIALIZED, + + // The hook for the specified target function is already created. + MH_ERROR_ALREADY_CREATED, + + // The hook for the specified target function is not created yet. + MH_ERROR_NOT_CREATED, + + // The hook for the specified target function is already enabled. + MH_ERROR_ENABLED, + + // The hook for the specified target function is not enabled yet, or already + // disabled. + MH_ERROR_DISABLED, + + // The specified pointer is invalid. It points the address of non-allocated + // and/or non-executable region. + MH_ERROR_NOT_EXECUTABLE, + + // The specified target function cannot be hooked. + MH_ERROR_UNSUPPORTED_FUNCTION, + + // Failed to allocate memory. + MH_ERROR_MEMORY_ALLOC, + + // Failed to change the memory protection. + MH_ERROR_MEMORY_PROTECT, + + // The specified module is not loaded. + MH_ERROR_MODULE_NOT_FOUND, + + // The specified function is not found. + MH_ERROR_FUNCTION_NOT_FOUND +} +MH_STATUS; + +// Can be passed as a parameter to MH_EnableHook, MH_DisableHook, +// MH_QueueEnableHook or MH_QueueDisableHook. +#define MH_ALL_HOOKS NULL + +#ifdef __cplusplus +extern "C" { +#endif + + // Initialize the MinHook library. You must call this function EXACTLY ONCE + // at the beginning of your program. + MH_STATUS WINAPI MH_Initialize(VOID); + + // Uninitialize the MinHook library. You must call this function EXACTLY + // ONCE at the end of your program. + MH_STATUS WINAPI MH_Uninitialize(VOID); + + // Creates a hook for the specified target function, in disabled state. + // Parameters: + // pTarget [in] A pointer to the target function, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal); + + // Creates a hook for the specified API function, in disabled state. + // Parameters: + // pszModule [in] A pointer to the loaded module name which contains the + // target function. + // pszProcName [in] A pointer to the target function name, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHookApi( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal); + + // Creates a hook for the specified API function, in disabled state. + // Parameters: + // pszModule [in] A pointer to the loaded module name which contains the + // target function. + // pszProcName [in] A pointer to the target function name, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + // ppTarget [out] A pointer to the target function, which will be used + // with other functions. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHookApiEx( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget); + + // Removes an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget); + + // Enables an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // enabled in one go. + MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget); + + // Disables an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // disabled in one go. + MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget); + + // Queues to enable an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // queued to be enabled. + MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget); + + // Queues to disable an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // queued to be disabled. + MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget); + + // Applies all queued changes in one go. + MH_STATUS WINAPI MH_ApplyQueued(VOID); + + // Translates the MH_STATUS to its name as a string. + const char *WINAPI MH_StatusToString(MH_STATUS status); + +#ifdef __cplusplus +} +#endif diff --git a/third_party/minhook/src/buffer.c b/third_party/minhook/src/buffer.c new file mode 100644 index 0000000..d2850bf --- /dev/null +++ b/third_party/minhook/src/buffer.c @@ -0,0 +1,311 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "buffer.h" + +// Size of each memory block. (= page size of VirtualAlloc) +#define MEMORY_BLOCK_SIZE 0x1000 + +// Max range for seeking a memory block. (= 1024MB) +#define MAX_MEMORY_RANGE 0x40000000 + +// Memory protection flags to check the executable address. +#define PAGE_EXECUTE_FLAGS \ + (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY) + +// Memory slot. +typedef struct _MEMORY_SLOT +{ + union + { + struct _MEMORY_SLOT *pNext; + UINT8 buffer[MEMORY_SLOT_SIZE]; + }; +} MEMORY_SLOT, *PMEMORY_SLOT; + +// Memory block info. Placed at the head of each block. +typedef struct _MEMORY_BLOCK +{ + struct _MEMORY_BLOCK *pNext; + PMEMORY_SLOT pFree; // First element of the free slot list. + UINT usedCount; +} MEMORY_BLOCK, *PMEMORY_BLOCK; + +//------------------------------------------------------------------------- +// Global Variables: +//------------------------------------------------------------------------- + +// First element of the memory block list. +static PMEMORY_BLOCK g_pMemoryBlocks; + +//------------------------------------------------------------------------- +VOID InitializeBuffer(VOID) +{ + // Nothing to do for now. +} + +//------------------------------------------------------------------------- +VOID UninitializeBuffer(VOID) +{ + PMEMORY_BLOCK pBlock = g_pMemoryBlocks; + g_pMemoryBlocks = NULL; + + while (pBlock) + { + PMEMORY_BLOCK pNext = pBlock->pNext; + VirtualFree(pBlock, 0, MEM_RELEASE); + pBlock = pNext; + } +} + +//------------------------------------------------------------------------- +#if defined(_M_X64) || defined(__x86_64__) +static LPVOID FindPrevFreeRegion(LPVOID pAddress, LPVOID pMinAddr, DWORD dwAllocationGranularity) +{ + ULONG_PTR tryAddr = (ULONG_PTR)pAddress; + + // Round down to the allocation granularity. + tryAddr -= tryAddr % dwAllocationGranularity; + + // Start from the previous allocation granularity multiply. + tryAddr -= dwAllocationGranularity; + + while (tryAddr >= (ULONG_PTR)pMinAddr) + { + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0) + break; + + if (mbi.State == MEM_FREE) + return (LPVOID)tryAddr; + + if ((ULONG_PTR)mbi.AllocationBase < dwAllocationGranularity) + break; + + tryAddr = (ULONG_PTR)mbi.AllocationBase - dwAllocationGranularity; + } + + return NULL; +} +#endif + +//------------------------------------------------------------------------- +#if defined(_M_X64) || defined(__x86_64__) +static LPVOID FindNextFreeRegion(LPVOID pAddress, LPVOID pMaxAddr, DWORD dwAllocationGranularity) +{ + ULONG_PTR tryAddr = (ULONG_PTR)pAddress; + + // Round down to the allocation granularity. + tryAddr -= tryAddr % dwAllocationGranularity; + + // Start from the next allocation granularity multiply. + tryAddr += dwAllocationGranularity; + + while (tryAddr <= (ULONG_PTR)pMaxAddr) + { + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0) + break; + + if (mbi.State == MEM_FREE) + return (LPVOID)tryAddr; + + tryAddr = (ULONG_PTR)mbi.BaseAddress + mbi.RegionSize; + + // Round up to the next allocation granularity. + tryAddr += dwAllocationGranularity - 1; + tryAddr -= tryAddr % dwAllocationGranularity; + } + + return NULL; +} +#endif + +//------------------------------------------------------------------------- +static PMEMORY_BLOCK GetMemoryBlock(LPVOID pOrigin) +{ + PMEMORY_BLOCK pBlock; +#if defined(_M_X64) || defined(__x86_64__) + ULONG_PTR minAddr; + ULONG_PTR maxAddr; + + SYSTEM_INFO si; + GetSystemInfo(&si); + minAddr = (ULONG_PTR)si.lpMinimumApplicationAddress; + maxAddr = (ULONG_PTR)si.lpMaximumApplicationAddress; + + // pOrigin ± 512MB + if ((ULONG_PTR)pOrigin > MAX_MEMORY_RANGE && minAddr < (ULONG_PTR)pOrigin - MAX_MEMORY_RANGE) + minAddr = (ULONG_PTR)pOrigin - MAX_MEMORY_RANGE; + + if (maxAddr > (ULONG_PTR)pOrigin + MAX_MEMORY_RANGE) + maxAddr = (ULONG_PTR)pOrigin + MAX_MEMORY_RANGE; + + // Make room for MEMORY_BLOCK_SIZE bytes. + maxAddr -= MEMORY_BLOCK_SIZE - 1; +#endif + + // Look the registered blocks for a reachable one. + for (pBlock = g_pMemoryBlocks; pBlock != NULL; pBlock = pBlock->pNext) + { +#if defined(_M_X64) || defined(__x86_64__) + // Ignore the blocks too far. + if ((ULONG_PTR)pBlock < minAddr || (ULONG_PTR)pBlock >= maxAddr) + continue; +#endif + // The block has at least one unused slot. + if (pBlock->pFree != NULL) + return pBlock; + } + +#if defined(_M_X64) || defined(__x86_64__) + // Alloc a new block above if not found. + { + LPVOID pAlloc = pOrigin; + while ((ULONG_PTR)pAlloc >= minAddr) + { + pAlloc = FindPrevFreeRegion(pAlloc, (LPVOID)minAddr, si.dwAllocationGranularity); + if (pAlloc == NULL) + break; + + pBlock = (PMEMORY_BLOCK)VirtualAlloc( + pAlloc, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (pBlock != NULL) + break; + } + } + + // Alloc a new block below if not found. + if (pBlock == NULL) + { + LPVOID pAlloc = pOrigin; + while ((ULONG_PTR)pAlloc <= maxAddr) + { + pAlloc = FindNextFreeRegion(pAlloc, (LPVOID)maxAddr, si.dwAllocationGranularity); + if (pAlloc == NULL) + break; + + pBlock = (PMEMORY_BLOCK)VirtualAlloc( + pAlloc, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (pBlock != NULL) + break; + } + } +#else + // In x86 mode, a memory block can be placed anywhere. + pBlock = (PMEMORY_BLOCK)VirtualAlloc( + NULL, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); +#endif + + if (pBlock != NULL) + { + // Build a linked list of all the slots. + PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBlock + 1; + pBlock->pFree = NULL; + pBlock->usedCount = 0; + do + { + pSlot->pNext = pBlock->pFree; + pBlock->pFree = pSlot; + pSlot++; + } while ((ULONG_PTR)pSlot - (ULONG_PTR)pBlock <= MEMORY_BLOCK_SIZE - MEMORY_SLOT_SIZE); + + pBlock->pNext = g_pMemoryBlocks; + g_pMemoryBlocks = pBlock; + } + + return pBlock; +} + +//------------------------------------------------------------------------- +LPVOID AllocateBuffer(LPVOID pOrigin) +{ + PMEMORY_SLOT pSlot; + PMEMORY_BLOCK pBlock = GetMemoryBlock(pOrigin); + if (pBlock == NULL) + return NULL; + + // Remove an unused slot from the list. + pSlot = pBlock->pFree; + pBlock->pFree = pSlot->pNext; + pBlock->usedCount++; +#ifdef _DEBUG + // Fill the slot with INT3 for debugging. + memset(pSlot, 0xCC, sizeof(MEMORY_SLOT)); +#endif + return pSlot; +} + +//------------------------------------------------------------------------- +VOID FreeBuffer(LPVOID pBuffer) +{ + PMEMORY_BLOCK pBlock = g_pMemoryBlocks; + PMEMORY_BLOCK pPrev = NULL; + ULONG_PTR pTargetBlock = ((ULONG_PTR)pBuffer / MEMORY_BLOCK_SIZE) * MEMORY_BLOCK_SIZE; + + while (pBlock != NULL) + { + if ((ULONG_PTR)pBlock == pTargetBlock) + { + PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBuffer; +#ifdef _DEBUG + // Clear the released slot for debugging. + memset(pSlot, 0x00, sizeof(MEMORY_SLOT)); +#endif + // Restore the released slot to the list. + pSlot->pNext = pBlock->pFree; + pBlock->pFree = pSlot; + pBlock->usedCount--; + + // Free if unused. + if (pBlock->usedCount == 0) + { + if (pPrev) + pPrev->pNext = pBlock->pNext; + else + g_pMemoryBlocks = pBlock->pNext; + + VirtualFree(pBlock, 0, MEM_RELEASE); + } + + break; + } + + pPrev = pBlock; + pBlock = pBlock->pNext; + } +} + +//------------------------------------------------------------------------- +BOOL IsExecutableAddress(LPVOID pAddress) +{ + MEMORY_BASIC_INFORMATION mi; + return VirtualQuery(pAddress, &mi, sizeof(mi)) && mi.State == MEM_COMMIT && + (mi.Protect & PAGE_EXECUTE_FLAGS); +} diff --git a/third_party/minhook/src/buffer.h b/third_party/minhook/src/buffer.h new file mode 100644 index 0000000..204d551 --- /dev/null +++ b/third_party/minhook/src/buffer.h @@ -0,0 +1,42 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +// Size of each memory slot. +#if defined(_M_X64) || defined(__x86_64__) + #define MEMORY_SLOT_SIZE 64 +#else + #define MEMORY_SLOT_SIZE 32 +#endif + +VOID InitializeBuffer(VOID); +VOID UninitializeBuffer(VOID); +LPVOID AllocateBuffer(LPVOID pOrigin); +VOID FreeBuffer(LPVOID pBuffer); +BOOL IsExecutableAddress(LPVOID pAddress); diff --git a/third_party/minhook/src/hde/hde32.c b/third_party/minhook/src/hde/hde32.c new file mode 100644 index 0000000..3d679a9 --- /dev/null +++ b/third_party/minhook/src/hde/hde32.c @@ -0,0 +1,462 @@ +/* + * Hacker Disassembler Engine 32 C + * Copyright (c) 2008-2009, Vyacheslav Patkov. + * All rights reserved. + * + */ + +#if defined(_M_IX86) || defined(__i386__) + +#include +#include "hde32.h" +#include "table32.h" + +/* An instruction is at most 15 bytes long, so the bytes past that may not be + * mapped: a decode that would run over the limit stops instead of reading. */ +#define NEED(n) do { if (limit - p < (n)) goto error_length; } while (0) + +unsigned int hde32_disasm(const void *code, hde32s *hs) +{ + uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0; + uint8_t *ht = hde32_table, m_mod, m_reg, m_rm, disp_size = 0; + uint8_t ext = 0; + uint8_t *limit = (uint8_t *)code + 15; + + memset(hs, 0, sizeof(hde32s)); + + for (x = 15; x; x--) + switch (c = *p++) { + case 0xf3: + hs->p_rep = c; + pref |= PRE_F3; + break; + case 0xf2: + hs->p_rep = c; + pref |= PRE_F2; + break; + case 0xf0: + hs->p_lock = c; + pref |= PRE_LOCK; + break; + case 0x26: case 0x2e: case 0x36: + case 0x3e: case 0x64: case 0x65: + hs->p_seg = c; + pref |= PRE_SEG; + break; + case 0x66: + hs->p_66 = c; + pref |= PRE_66; + break; + case 0x67: + hs->p_67 = c; + pref |= PRE_67; + break; + default: + goto pref_done; + } + goto error_length; /* 15 prefixes leave no room for an opcode */ + + pref_done: + + hs->flags = (uint32_t)pref << 23; + + if (!pref) + pref |= PRE_NONE; + + /* VEX, EVEX and XOP. C4, C5 and 62 keep their legacy meaning unless the + * byte after them has mod = 11, which les, lds and bound cannot encode; + * 8F is an escape only from map 8 upwards, which leaves 8F /0 as pop. The + * header names the opcode map, the map decides the immediate, and every + * instruction reached this way has a ModR/M byte. */ + if (p < limit && + (((c == 0xc4 || c == 0xc5 || c == 0x62) && (*p & 0xc0) == 0xc0) || + (c == 0x8f && (*p & 0x1f) >= 8))) { + uint8_t map; + + hs->opcode = c; + if (c == 0xc5) { + NEED(3); + map = 1; + p++; + } else if (c == 0x62) { + NEED(5); + map = *p & 0x0f; + p += 3; + } else { + NEED(4); + map = *p & 0x1f; + p += 2; + } + hs->opcode2 = opcode = *p++; + + /* The escape carries the operand size itself, so a 66, F2, F3 or LOCK + * in front of it is invalid. A segment or 67 prefix still applies. */ + if (pref & (PRE_66 | PRE_F2 | PRE_F3 | PRE_LOCK)) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + pref &= ~PRE_66; + + cflags = C_MODRM; + if (c == 0x8f) { + switch (map) { + case 8: /* every XOP8 opcode takes an imm8 */ + cflags |= C_IMM8; + break; + case 9: /* no XOP9 opcode takes one */ + break; + case 10: /* bextr and the lwp forms: imm32 */ + cflags |= C_IMM_P66; + break; + default: + hs->flags |= F_ERROR | F_ERROR_OPCODE; + break; + } + } else switch (map) { + case 1: /* 0F map */ + if (opcode == 0x70 || (opcode >= 0x71 && opcode <= 0x73) || + opcode == 0xc2 || (opcode >= 0xc4 && opcode <= 0xc6)) + cflags |= C_IMM8; + else if (c != 0x62 && opcode == 0x77) + cflags = C_NONE; /* vzeroupper and vzeroall: no ModR/M */ + break; + case 2: /* 0F 38 map: never an immediate */ + break; + case 3: /* 0F 3A map: always an imm8 */ + cflags |= C_IMM8; + break; + case 5: case 6: /* half-precision maps, EVEX only */ + if (c != 0x62) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + else if (map == 5 && (opcode == 0x08 || opcode == 0x0a || + opcode == 0x26 || opcode == 0x27 || + opcode == 0x56 || opcode == 0x57 || + opcode == 0x66 || opcode == 0x67 || + opcode == 0xc2)) + cflags |= C_IMM8; /* the 0F 3A forms of that map */ + break; + default: + hs->flags |= F_ERROR | F_ERROR_OPCODE; + break; + } + ext = 1; + x = 0; + goto modrm; + } + + if ((hs->opcode = c) == 0x0f) { + NEED(1); + hs->opcode2 = c = *p++; + if (c == 0x38 || c == 0x3a) { + /* Three-byte maps: the opcode follows and always has a ModR/M + * byte. Every 0F 3A opcode takes an imm8, no 0F 38 one does, and + * none of them is lockable. */ + NEED(2); + opcode = *p++; + cflags = (c == 0x3a) ? (C_MODRM | C_IMM8) : C_MODRM; + if (pref & PRE_LOCK) + hs->flags |= F_ERROR | F_ERROR_LOCK; + ext = 1; + x = 0; + goto modrm; + } + ht += DELTA_OPCODES; + } else if (c >= 0xa0 && c <= 0xa3) { + if (pref & PRE_67) + pref |= PRE_66; + else + pref &= ~PRE_66; + } + + opcode = c; + cflags = ht[ht[opcode / 4] + (opcode % 4)]; + + if (cflags == C_ERROR) { + hs->flags |= F_ERROR | F_ERROR_OPCODE; + /* An unknown opcode is still measured when its shape is known: 0F 24 + * and 0F 26 (test registers), 0F A6 and 0F A7 (PadLock), 0F B9 (ud1) + * and 0F FF (ud0) all take a ModR/M byte. */ + cflags = 0; + if ((opcode & -3) == 0x24 || opcode == 0xa6 || opcode == 0xa7 || + opcode == 0xb9 || opcode == 0xff) + cflags = C_MODRM; + } + + x = 0; + if (cflags & C_GROUP) { + uint16_t t; + t = *(uint16_t *)(ht + (cflags & 0x7f)); + cflags = (uint8_t)t; + x = (uint8_t)(t >> 8); + } + + if (hs->opcode2) { + ht = hde32_table + DELTA_PREFIXES; + if (ht[ht[opcode / 4] + (opcode % 4)] & pref) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + /* vmread, vmwrite and popcnt take a ModR/M byte that the table cannot + * give them: their entries share a row with opcodes that take none. */ + if (opcode == 0x78 || opcode == 0x79 || opcode == 0xb8) + cflags |= C_MODRM; + } + + modrm: + if (cflags & C_MODRM) { + hs->flags |= F_MODRM; + NEED(1); + hs->modrm = c = *p++; + hs->modrm_mod = m_mod = c >> 6; + hs->modrm_rm = m_rm = c & 7; + hs->modrm_reg = m_reg = (c & 0x3f) >> 3; + + if (x && ((x << m_reg) & 0x80)) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + + if (ext) + goto no_error_operand; /* no legacy operand rule applies */ + + if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) { + uint8_t t = opcode - 0xd9; + if (m_mod == 3) { + ht = hde32_table + DELTA_FPU_MODRM + t*8; + t = ht[m_reg] << m_rm; + } else { + ht = hde32_table + DELTA_FPU_REG; + t = ht[t] << m_reg; + } + if (t & 0x80) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + } + + if (pref & PRE_LOCK) { + if (m_mod == 3) { + hs->flags |= F_ERROR | F_ERROR_LOCK; + } else { + uint8_t *table_end, op = opcode; + if (hs->opcode2) { + ht = hde32_table + DELTA_OP2_LOCK_OK; + table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK; + } else { + ht = hde32_table + DELTA_OP_LOCK_OK; + table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK; + op &= -2; + } + for (; ht != table_end; ht++) + if (*ht++ == op) { + if (!((*ht << m_reg) & 0x80)) + goto no_lock_error; + else + break; + } + hs->flags |= F_ERROR | F_ERROR_LOCK; + no_lock_error: + ; + } + } + + if (hs->opcode2) { + switch (opcode) { + case 0x20: case 0x22: + m_mod = 3; + if (m_reg > 4 || m_reg == 1) + goto error_operand; + else + goto no_error_operand; + case 0x21: case 0x23: + m_mod = 3; + if (m_reg == 4 || m_reg == 5) + goto error_operand; + else + goto no_error_operand; + } + } else { + switch (opcode) { + case 0x8c: + if (m_reg > 5) + goto error_operand; + else + goto no_error_operand; + case 0x8e: + if (m_reg == 1 || m_reg > 5) + goto error_operand; + else + goto no_error_operand; + } + } + + if (m_mod == 3) { + uint8_t *table_end; + if (hs->opcode2) { + ht = hde32_table + DELTA_OP2_ONLY_MEM; + table_end = ht + sizeof(hde32_table) - DELTA_OP2_ONLY_MEM; + } else { + ht = hde32_table + DELTA_OP_ONLY_MEM; + table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM; + } + for (; ht != table_end; ht += 2) + if (*ht++ == opcode) { + if ((*ht++ & pref) && !((*ht << m_reg) & 0x80)) + goto error_operand; + else + break; + } + goto no_error_operand; + } else if (hs->opcode2) { + switch (opcode) { + case 0x50: case 0xd7: case 0xf7: + if (pref & (PRE_NONE | PRE_66)) + goto error_operand; + break; + case 0xd6: + if (pref & (PRE_F2 | PRE_F3)) + goto error_operand; + break; + case 0xc5: + goto error_operand; + } + goto no_error_operand; + } else + goto no_error_operand; + + error_operand: + hs->flags |= F_ERROR | F_ERROR_OPERAND; + no_error_operand: + + /* Group 3: F6 /0 and /1 take an imm8, F7 /0 and /1 an imm32 or imm16. + * The rule belongs to the one-byte map; 0F F6 and 0F F7 are psadbw + * and maskmovq, which take neither. */ + if (!ext && !hs->opcode2 && m_reg <= 1) { + if (opcode == 0xf6) + cflags |= C_IMM8; + else if (opcode == 0xf7) + cflags |= C_IMM_P66; + } + /* C7 /7 is xbegin, whose immediate is a displacement. */ + if (!ext && !hs->opcode2 && opcode == 0xc7 && m_reg == 7) + hs->flags |= F_RELATIVE; + /* extrq and insertq take two imm8 operands where vmread takes none. */ + if (!ext && hs->opcode2 == 0x78 && (pref & (PRE_66 | PRE_F2))) + cflags |= C_IMM16; + + switch (m_mod) { + case 0: + if (pref & PRE_67) { + if (m_rm == 6) + disp_size = 2; + } else + if (m_rm == 5) + disp_size = 4; + break; + case 1: + disp_size = 1; + break; + case 2: + disp_size = 2; + if (!(pref & PRE_67)) + disp_size <<= 1; + break; + } + + if (m_mod != 3 && m_rm == 4 && !(pref & PRE_67)) { + hs->flags |= F_SIB; + NEED(1); + hs->sib = c = *p++; + hs->sib_scale = c >> 6; + hs->sib_index = (c & 0x3f) >> 3; + if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1)) + disp_size = 4; + } + + if (disp_size) { + NEED(disp_size); + switch (disp_size) { + case 1: + hs->flags |= F_DISP8; + hs->disp.disp8 = *p; + break; + case 2: + hs->flags |= F_DISP16; + hs->disp.disp16 = *(uint16_t *)p; + break; + case 4: + hs->flags |= F_DISP32; + hs->disp.disp32 = *(uint32_t *)p; + break; + } + p += disp_size; + } + } else if (pref & PRE_LOCK) + hs->flags |= F_ERROR | F_ERROR_LOCK; + + if (cflags & C_IMM_P66) { + if (cflags & C_REL32) { + if (pref & PRE_66) { + NEED(2); + hs->flags |= F_IMM16 | F_RELATIVE; + hs->imm.imm16 = *(uint16_t *)p; + p += 2; + goto disasm_done; + } + goto rel32_ok; + } + if (pref & PRE_66) { + NEED(2); + hs->flags |= F_IMM16; + hs->imm.imm16 = *(uint16_t *)p; + p += 2; + } else { + NEED(4); + hs->flags |= F_IMM32; + hs->imm.imm32 = *(uint32_t *)p; + p += 4; + } + } + + if (cflags & C_IMM16) { + NEED(2); + if (hs->flags & F_IMM32) { + hs->flags |= F_IMM16; + hs->disp.disp16 = *(uint16_t *)p; + } else if (hs->flags & F_IMM16) { + hs->flags |= F_2IMM16; + hs->disp.disp16 = *(uint16_t *)p; + } else { + hs->flags |= F_IMM16; + hs->imm.imm16 = *(uint16_t *)p; + } + p += 2; + } + if (cflags & C_IMM8) { + NEED(1); + hs->flags |= F_IMM8; + if (hs->flags & F_IMM16) + hs->disp.disp8 = *p++; /* enter: the union holds the imm16 */ + else + hs->imm.imm8 = *p++; + } + + if (cflags & C_REL32) { + rel32_ok: + NEED(4); + hs->flags |= F_IMM32 | F_RELATIVE; + hs->imm.imm32 = *(uint32_t *)p; + p += 4; + } else if (cflags & C_REL8) { + NEED(1); + hs->flags |= F_IMM8 | F_RELATIVE; + hs->imm.imm8 = *p++; + } + + disasm_done: + + hs->len = (uint8_t)(p - (uint8_t *)code); + return (unsigned int)hs->len; + + error_length: + + hs->flags |= F_ERROR | F_ERROR_LENGTH; + hs->len = 15; + return 15; +} + +#undef NEED + +#endif // defined(_M_IX86) || defined(__i386__) diff --git a/third_party/minhook/src/hde/hde32.h b/third_party/minhook/src/hde/hde32.h new file mode 100644 index 0000000..1112450 --- /dev/null +++ b/third_party/minhook/src/hde/hde32.h @@ -0,0 +1,105 @@ +/* + * Hacker Disassembler Engine 32 + * Copyright (c) 2006-2009, Vyacheslav Patkov. + * All rights reserved. + * + * hde32.h: C/C++ header file + * + */ + +#ifndef _HDE32_H_ +#define _HDE32_H_ + +/* stdint.h - C99 standard header + * http://en.wikipedia.org/wiki/stdint.h + * + * if your compiler doesn't contain "stdint.h" header (for + * example, Microsoft Visual C++), you can download file: + * http://www.azillionmonkeys.com/qed/pstdint.h + * and change next line to: + * #include "pstdint.h" + */ +#include "pstdint.h" + +#define F_MODRM 0x00000001 +#define F_SIB 0x00000002 +#define F_IMM8 0x00000004 +#define F_IMM16 0x00000008 +#define F_IMM32 0x00000010 +#define F_DISP8 0x00000020 +#define F_DISP16 0x00000040 +#define F_DISP32 0x00000080 +#define F_RELATIVE 0x00000100 +#define F_2IMM16 0x00000800 +#define F_ERROR 0x00001000 +#define F_ERROR_OPCODE 0x00002000 +#define F_ERROR_LENGTH 0x00004000 +#define F_ERROR_LOCK 0x00008000 +#define F_ERROR_OPERAND 0x00010000 +#define F_PREFIX_REPNZ 0x01000000 +#define F_PREFIX_REPX 0x02000000 +#define F_PREFIX_REP 0x03000000 +#define F_PREFIX_66 0x04000000 +#define F_PREFIX_67 0x08000000 +#define F_PREFIX_LOCK 0x10000000 +#define F_PREFIX_SEG 0x20000000 +#define F_PREFIX_ANY 0x3f000000 + +#define PREFIX_SEGMENT_CS 0x2e +#define PREFIX_SEGMENT_SS 0x36 +#define PREFIX_SEGMENT_DS 0x3e +#define PREFIX_SEGMENT_ES 0x26 +#define PREFIX_SEGMENT_FS 0x64 +#define PREFIX_SEGMENT_GS 0x65 +#define PREFIX_LOCK 0xf0 +#define PREFIX_REPNZ 0xf2 +#define PREFIX_REPX 0xf3 +#define PREFIX_OPERAND_SIZE 0x66 +#define PREFIX_ADDRESS_SIZE 0x67 + +#pragma pack(push,1) + +typedef struct { + uint8_t len; + uint8_t p_rep; + uint8_t p_lock; + uint8_t p_seg; + uint8_t p_66; + uint8_t p_67; + uint8_t opcode; + uint8_t opcode2; + uint8_t modrm; + uint8_t modrm_mod; + uint8_t modrm_reg; + uint8_t modrm_rm; + uint8_t sib; + uint8_t sib_scale; + uint8_t sib_index; + uint8_t sib_base; + union { + uint8_t imm8; + uint16_t imm16; + uint32_t imm32; + } imm; + union { + uint8_t disp8; + uint16_t disp16; + uint32_t disp32; + } disp; + uint32_t flags; +} hde32s; + +#pragma pack(pop) + +#ifdef __cplusplus +extern "C" { +#endif + +/* __cdecl */ +unsigned int hde32_disasm(const void *code, hde32s *hs); + +#ifdef __cplusplus +} +#endif + +#endif /* _HDE32_H_ */ diff --git a/third_party/minhook/src/hde/hde64.c b/third_party/minhook/src/hde/hde64.c new file mode 100644 index 0000000..1251609 --- /dev/null +++ b/third_party/minhook/src/hde/hde64.c @@ -0,0 +1,470 @@ +/* + * Hacker Disassembler Engine 64 C + * Copyright (c) 2008-2009, Vyacheslav Patkov. + * All rights reserved. + * + */ + +#if defined(_M_X64) || defined(__x86_64__) + +#include +#include "hde64.h" +#include "table64.h" + +/* An instruction is at most 15 bytes long, so the bytes past that may not be + * mapped: a decode that would run over the limit stops instead of reading. */ +#define NEED(n) do { if (limit - p < (n)) goto error_length; } while (0) + +unsigned int hde64_disasm(const void *code, hde64s *hs) +{ + uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0; + uint8_t *ht = hde64_table, m_mod, m_reg, m_rm, disp_size = 0; + uint8_t op64 = 0, rex = 0, ext = 0; + uint8_t *limit = (uint8_t *)code + 15; + + memset(hs, 0, sizeof(hde64s)); + + for (x = 15; x; x--) + switch (c = *p++) { + case 0xf3: + hs->p_rep = c; + pref |= PRE_F3; + rex = 0; + break; + case 0xf2: + hs->p_rep = c; + pref |= PRE_F2; + rex = 0; + break; + case 0xf0: + hs->p_lock = c; + pref |= PRE_LOCK; + rex = 0; + break; + case 0x26: case 0x2e: case 0x36: + case 0x3e: case 0x64: case 0x65: + hs->p_seg = c; + pref |= PRE_SEG; + rex = 0; + break; + case 0x66: + hs->p_66 = c; + pref |= PRE_66; + rex = 0; + break; + case 0x67: + hs->p_67 = c; + pref |= PRE_67; + rex = 0; + break; + default: + if ((c & 0xf0) != 0x40) + goto pref_done; + /* REX counts only as the last prefix before the opcode: a + * legacy prefix after it, or a further REX, discards it. */ + rex = c; + break; + } + goto error_length; /* 15 prefixes leave no room for an opcode */ + + pref_done: + + hs->flags = (uint32_t)pref << 23; + + if (!pref) + pref |= PRE_NONE; + + if (rex) { + hs->flags |= F_PREFIX_REX; + hs->rex = rex; + hs->rex_w = (rex & 0xf) >> 3; + hs->rex_r = (rex & 7) >> 2; + hs->rex_x = (rex & 3) >> 1; + hs->rex_b = rex & 1; + if (hs->rex_w && (c & 0xf8) == 0xb8) + op64++; + } + + /* VEX, EVEX and XOP. C4, C5 and 62 are always escapes in long mode; 8F is + * one only from map 8 upwards, which leaves 8F /0 as POP. The header names + * the opcode map, the map decides the immediate, and every instruction + * reached this way has a ModR/M byte. */ + if (c == 0xc4 || c == 0xc5 || c == 0x62 || + (c == 0x8f && p < limit && (*p & 0x1f) >= 8)) { + uint8_t map; + + hs->opcode = c; + if (c == 0xc5) { + NEED(3); + map = 1; + p++; + } else if (c == 0x62) { + NEED(5); + map = *p & 0x07; /* the bit above the map is APX's B4 */ + p += 3; + } else { + NEED(4); + map = *p & 0x1f; + p += 2; + } + hs->opcode2 = opcode = *p++; + + /* The escape carries the operand size and the register extensions + * itself, so a 66, F2, F3, LOCK or REX in front of it is invalid. A + * segment or 67 prefix still applies. */ + if (rex || (pref & (PRE_66 | PRE_F2 | PRE_F3 | PRE_LOCK))) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + pref &= ~PRE_66; + + cflags = C_MODRM; + if (c == 0x8f) { + switch (map) { + case 8: /* every XOP8 opcode takes an imm8 */ + cflags |= C_IMM8; + break; + case 9: /* no XOP9 opcode takes one */ + break; + case 10: /* bextr and the lwp forms: imm32 */ + cflags |= C_IMM_P66; + break; + default: + hs->flags |= F_ERROR | F_ERROR_OPCODE; + break; + } + } else switch (map) { + case 1: /* 0F map */ + if (opcode == 0x70 || (opcode >= 0x71 && opcode <= 0x73) || + opcode == 0xc2 || (opcode >= 0xc4 && opcode <= 0xc6)) + cflags |= C_IMM8; + else if (c != 0x62 && opcode == 0x77) + cflags = C_NONE; /* vzeroupper and vzeroall: no ModR/M */ + else if (c != 0x62 && (opcode == 0x84 || opcode == 0x85)) + cflags = C_REL32; /* jkzd and jknzd: rel32, no ModR/M */ + break; + case 2: /* 0F 38 map: never an immediate */ + break; + case 3: /* 0F 3A map: always an imm8 */ + cflags |= C_IMM8; + break; + case 5: case 6: /* half-precision maps, EVEX only */ + if (c != 0x62) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + else if (map == 5 && (opcode == 0x08 || opcode == 0x0a || + opcode == 0x26 || opcode == 0x27 || + opcode == 0x56 || opcode == 0x57 || + opcode == 0x66 || opcode == 0x67 || + opcode == 0xc2)) + cflags |= C_IMM8; /* the 0F 3A forms of that map */ + break; + default: + hs->flags |= F_ERROR | F_ERROR_OPCODE; + break; + } + ext = 1; + x = 0; + goto modrm; + } + + if ((hs->opcode = c) == 0x0f) { + NEED(1); + hs->opcode2 = c = *p++; + if (c == 0x38 || c == 0x3a) { + /* Three-byte maps: the opcode follows and always has a ModR/M + * byte. Every 0F 3A opcode takes an imm8, no 0F 38 one does, and + * none of them is lockable. */ + NEED(2); + opcode = *p++; + cflags = (c == 0x3a) ? (C_MODRM | C_IMM8) : C_MODRM; + if (pref & PRE_LOCK) + hs->flags |= F_ERROR | F_ERROR_LOCK; + ext = 1; + x = 0; + goto modrm; + } + ht += DELTA_OPCODES; + } else if (c >= 0xa0 && c <= 0xa3) { + /* moffs: the absolute address is as wide as the address size, so + * eight bytes unless 67 selects 32-bit addressing. */ + if (!(pref & PRE_67)) + op64++; + pref &= ~PRE_66; + } + + opcode = c; + cflags = ht[ht[opcode / 4] + (opcode % 4)]; + + if (cflags == C_ERROR) { + hs->flags |= F_ERROR | F_ERROR_OPCODE; + /* An unknown opcode is still measured when its shape is known: 0F 24 + * and 0F 26 (test registers), 0F A6 and 0F A7 (PadLock), 0F B9 (ud1) + * and 0F FF (ud0) all take a ModR/M byte. */ + cflags = 0; + if ((opcode & -3) == 0x24 || opcode == 0xa6 || opcode == 0xa7 || + opcode == 0xb9 || opcode == 0xff) + cflags = C_MODRM; + } + + x = 0; + if (cflags & C_GROUP) { + uint16_t t; + t = *(uint16_t *)(ht + (cflags & 0x7f)); + cflags = (uint8_t)t; + x = (uint8_t)(t >> 8); + } + + if (hs->opcode2) { + ht = hde64_table + DELTA_PREFIXES; + if (ht[ht[opcode / 4] + (opcode % 4)] & pref) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + /* vmread, vmwrite and popcnt take a ModR/M byte that the table cannot + * give them: their entries share a row with opcodes that take none. */ + if (opcode == 0x78 || opcode == 0x79 || opcode == 0xb8) + cflags |= C_MODRM; + } + + modrm: + if (cflags & C_MODRM) { + hs->flags |= F_MODRM; + NEED(1); + hs->modrm = c = *p++; + hs->modrm_mod = m_mod = c >> 6; + hs->modrm_rm = m_rm = c & 7; + hs->modrm_reg = m_reg = (c & 0x3f) >> 3; + + if (x && ((x << m_reg) & 0x80)) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + + if (ext) + goto no_error_operand; /* no legacy operand rule applies */ + + if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) { + uint8_t t = opcode - 0xd9; + if (m_mod == 3) { + ht = hde64_table + DELTA_FPU_MODRM + t*8; + t = ht[m_reg] << m_rm; + } else { + ht = hde64_table + DELTA_FPU_REG; + t = ht[t] << m_reg; + } + if (t & 0x80) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + } + + if (pref & PRE_LOCK) { + if (m_mod == 3) { + hs->flags |= F_ERROR | F_ERROR_LOCK; + } else { + uint8_t *table_end, op = opcode; + if (hs->opcode2) { + ht = hde64_table + DELTA_OP2_LOCK_OK; + table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK; + } else { + ht = hde64_table + DELTA_OP_LOCK_OK; + table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK; + op &= -2; + } + for (; ht != table_end; ht++) + if (*ht++ == op) { + if (!((*ht << m_reg) & 0x80)) + goto no_lock_error; + else + break; + } + hs->flags |= F_ERROR | F_ERROR_LOCK; + no_lock_error: + ; + } + } + + if (hs->opcode2) { + switch (opcode) { + case 0x20: case 0x22: + m_mod = 3; + if (m_reg > 4 || m_reg == 1) + goto error_operand; + else + goto no_error_operand; + case 0x21: case 0x23: + m_mod = 3; + if (m_reg == 4 || m_reg == 5) + goto error_operand; + else + goto no_error_operand; + } + } else { + switch (opcode) { + case 0x8c: + if (m_reg > 5) + goto error_operand; + else + goto no_error_operand; + case 0x8e: + if (m_reg == 1 || m_reg > 5) + goto error_operand; + else + goto no_error_operand; + } + } + + if (m_mod == 3) { + uint8_t *table_end; + if (hs->opcode2) { + ht = hde64_table + DELTA_OP2_ONLY_MEM; + table_end = ht + sizeof(hde64_table) - DELTA_OP2_ONLY_MEM; + } else { + ht = hde64_table + DELTA_OP_ONLY_MEM; + table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM; + } + for (; ht != table_end; ht += 2) + if (*ht++ == opcode) { + if ((*ht++ & pref) && !((*ht << m_reg) & 0x80)) + goto error_operand; + else + break; + } + goto no_error_operand; + } else if (hs->opcode2) { + switch (opcode) { + case 0x50: case 0xd7: case 0xf7: + if (pref & (PRE_NONE | PRE_66)) + goto error_operand; + break; + case 0xd6: + if (pref & (PRE_F2 | PRE_F3)) + goto error_operand; + break; + case 0xc5: + goto error_operand; + } + goto no_error_operand; + } else + goto no_error_operand; + + error_operand: + hs->flags |= F_ERROR | F_ERROR_OPERAND; + no_error_operand: + + /* Group 3: F6 /0 and /1 take an imm8, F7 /0 and /1 an imm32 or imm16. + * The rule belongs to the one-byte map; 0F F6 and 0F F7 are psadbw + * and maskmovq, which take neither. */ + if (!ext && !hs->opcode2 && m_reg <= 1) { + if (opcode == 0xf6) + cflags |= C_IMM8; + else if (opcode == 0xf7) + cflags |= C_IMM_P66; + } + /* C7 /7 is xbegin, whose immediate is a displacement. */ + if (!ext && !hs->opcode2 && opcode == 0xc7 && m_reg == 7) + hs->flags |= F_RELATIVE; + /* extrq and insertq take two imm8 operands where vmread takes none. */ + if (!ext && hs->opcode2 == 0x78 && (pref & (PRE_66 | PRE_F2))) + cflags |= C_IMM16; + + switch (m_mod) { + case 0: + if (m_rm == 5) + disp_size = 4; + break; + case 1: + disp_size = 1; + break; + case 2: + disp_size = 4; + break; + } + + if (m_mod != 3 && m_rm == 4) { + hs->flags |= F_SIB; + NEED(1); + hs->sib = c = *p++; + hs->sib_scale = c >> 6; + hs->sib_index = (c & 0x3f) >> 3; + if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1)) + disp_size = 4; + } + + if (disp_size) { + NEED(disp_size); + if (disp_size == 1) { + hs->flags |= F_DISP8; + hs->disp.disp8 = *p; + } else { + hs->flags |= F_DISP32; + hs->disp.disp32 = *(uint32_t *)p; + } + p += disp_size; + } + } else if (pref & PRE_LOCK) + hs->flags |= F_ERROR | F_ERROR_LOCK; + + /* An operand-size dependent immediate is 32 bits wide, 16 under a 66 + * prefix, and 32 again when a REX.W overrides that prefix. */ + if (cflags & C_IMM_P66) { + if (cflags & C_REL32) { + if ((pref & PRE_66) && !hs->rex_w) { + NEED(2); + hs->flags |= F_IMM16 | F_RELATIVE; + hs->imm.imm16 = *(uint16_t *)p; + p += 2; + goto disasm_done; + } + goto rel32_ok; + } + if (op64) { + NEED(8); + hs->flags |= F_IMM64; + hs->imm.imm64 = *(uint64_t *)p; + p += 8; + } else if (!(pref & PRE_66) || hs->rex_w) { + NEED(4); + hs->flags |= F_IMM32; + hs->imm.imm32 = *(uint32_t *)p; + p += 4; + } else + goto imm16_ok; + } + + + if (cflags & C_IMM16) { + imm16_ok: + NEED(2); + hs->flags |= F_IMM16; + hs->imm.imm16 = *(uint16_t *)p; + p += 2; + } + if (cflags & C_IMM8) { + NEED(1); + hs->flags |= F_IMM8; + if (hs->flags & F_IMM16) + hs->disp.disp8 = *p++; /* enter: the union holds the imm16 */ + else + hs->imm.imm8 = *p++; + } + + if (cflags & C_REL32) { + rel32_ok: + NEED(4); + hs->flags |= F_IMM32 | F_RELATIVE; + hs->imm.imm32 = *(uint32_t *)p; + p += 4; + } else if (cflags & C_REL8) { + NEED(1); + hs->flags |= F_IMM8 | F_RELATIVE; + hs->imm.imm8 = *p++; + } + + disasm_done: + + hs->len = (uint8_t)(p - (uint8_t *)code); + return (unsigned int)hs->len; + + error_length: + + hs->flags |= F_ERROR | F_ERROR_LENGTH; + hs->len = 15; + return 15; +} + +#undef NEED + +#endif // defined(_M_X64) || defined(__x86_64__) diff --git a/third_party/minhook/src/hde/hde64.h b/third_party/minhook/src/hde/hde64.h new file mode 100644 index 0000000..ecbf4df --- /dev/null +++ b/third_party/minhook/src/hde/hde64.h @@ -0,0 +1,112 @@ +/* + * Hacker Disassembler Engine 64 + * Copyright (c) 2008-2009, Vyacheslav Patkov. + * All rights reserved. + * + * hde64.h: C/C++ header file + * + */ + +#ifndef _HDE64_H_ +#define _HDE64_H_ + +/* stdint.h - C99 standard header + * http://en.wikipedia.org/wiki/stdint.h + * + * if your compiler doesn't contain "stdint.h" header (for + * example, Microsoft Visual C++), you can download file: + * http://www.azillionmonkeys.com/qed/pstdint.h + * and change next line to: + * #include "pstdint.h" + */ +#include "pstdint.h" + +#define F_MODRM 0x00000001 +#define F_SIB 0x00000002 +#define F_IMM8 0x00000004 +#define F_IMM16 0x00000008 +#define F_IMM32 0x00000010 +#define F_IMM64 0x00000020 +#define F_DISP8 0x00000040 +#define F_DISP16 0x00000080 +#define F_DISP32 0x00000100 +#define F_RELATIVE 0x00000200 +#define F_ERROR 0x00001000 +#define F_ERROR_OPCODE 0x00002000 +#define F_ERROR_LENGTH 0x00004000 +#define F_ERROR_LOCK 0x00008000 +#define F_ERROR_OPERAND 0x00010000 +#define F_PREFIX_REPNZ 0x01000000 +#define F_PREFIX_REPX 0x02000000 +#define F_PREFIX_REP 0x03000000 +#define F_PREFIX_66 0x04000000 +#define F_PREFIX_67 0x08000000 +#define F_PREFIX_LOCK 0x10000000 +#define F_PREFIX_SEG 0x20000000 +#define F_PREFIX_REX 0x40000000 +#define F_PREFIX_ANY 0x7f000000 + +#define PREFIX_SEGMENT_CS 0x2e +#define PREFIX_SEGMENT_SS 0x36 +#define PREFIX_SEGMENT_DS 0x3e +#define PREFIX_SEGMENT_ES 0x26 +#define PREFIX_SEGMENT_FS 0x64 +#define PREFIX_SEGMENT_GS 0x65 +#define PREFIX_LOCK 0xf0 +#define PREFIX_REPNZ 0xf2 +#define PREFIX_REPX 0xf3 +#define PREFIX_OPERAND_SIZE 0x66 +#define PREFIX_ADDRESS_SIZE 0x67 + +#pragma pack(push,1) + +typedef struct { + uint8_t len; + uint8_t p_rep; + uint8_t p_lock; + uint8_t p_seg; + uint8_t p_66; + uint8_t p_67; + uint8_t rex; + uint8_t rex_w; + uint8_t rex_r; + uint8_t rex_x; + uint8_t rex_b; + uint8_t opcode; + uint8_t opcode2; + uint8_t modrm; + uint8_t modrm_mod; + uint8_t modrm_reg; + uint8_t modrm_rm; + uint8_t sib; + uint8_t sib_scale; + uint8_t sib_index; + uint8_t sib_base; + union { + uint8_t imm8; + uint16_t imm16; + uint32_t imm32; + uint64_t imm64; + } imm; + union { + uint8_t disp8; + uint16_t disp16; + uint32_t disp32; + } disp; + uint32_t flags; +} hde64s; + +#pragma pack(pop) + +#ifdef __cplusplus +extern "C" { +#endif + +/* __cdecl */ +unsigned int hde64_disasm(const void *code, hde64s *hs); + +#ifdef __cplusplus +} +#endif + +#endif /* _HDE64_H_ */ diff --git a/third_party/minhook/src/hde/pstdint.h b/third_party/minhook/src/hde/pstdint.h new file mode 100644 index 0000000..84d82a0 --- /dev/null +++ b/third_party/minhook/src/hde/pstdint.h @@ -0,0 +1,39 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +// Integer types for HDE. +typedef INT8 int8_t; +typedef INT16 int16_t; +typedef INT32 int32_t; +typedef INT64 int64_t; +typedef UINT8 uint8_t; +typedef UINT16 uint16_t; +typedef UINT32 uint32_t; +typedef UINT64 uint64_t; diff --git a/third_party/minhook/src/hde/table32.h b/third_party/minhook/src/hde/table32.h new file mode 100644 index 0000000..7b3e12e --- /dev/null +++ b/third_party/minhook/src/hde/table32.h @@ -0,0 +1,73 @@ +/* + * Hacker Disassembler Engine 32 C + * Copyright (c) 2008-2009, Vyacheslav Patkov. + * All rights reserved. + * + */ + +#define C_NONE 0x00 +#define C_MODRM 0x01 +#define C_IMM8 0x02 +#define C_IMM16 0x04 +#define C_IMM_P66 0x10 +#define C_REL8 0x20 +#define C_REL32 0x40 +#define C_GROUP 0x80 +#define C_ERROR 0xff + +#define PRE_ANY 0x00 +#define PRE_NONE 0x01 +#define PRE_F2 0x02 +#define PRE_F3 0x04 +#define PRE_66 0x08 +#define PRE_67 0x10 +#define PRE_LOCK 0x20 +#define PRE_SEG 0x40 +#define PRE_ALL 0xff + +#define DELTA_OPCODES 0x4a +#define DELTA_FPU_REG 0xf1 +#define DELTA_FPU_MODRM 0xf8 +#define DELTA_PREFIXES 0x130 +#define DELTA_OP_LOCK_OK 0x1a1 +#define DELTA_OP2_LOCK_OK 0x1b9 +#define DELTA_OP_ONLY_MEM 0x1cb +#define DELTA_OP2_ONLY_MEM 0x1da + +unsigned char hde32_table[] = { + 0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3, + 0xa8,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xac,0xaa,0xb2,0xaa,0x9f,0x9f, + 0x9f,0x9f,0xb5,0xa3,0xa3,0xa4,0xaa,0xaa,0xba,0xaa,0x96,0xaa,0xa8,0xaa,0xc3, + 0xc3,0x96,0x96,0xb7,0xae,0xd6,0xbd,0xa3,0xc5,0xa3,0xa3,0x9f,0xc3,0x9c,0xaa, + 0xaa,0xac,0xaa,0xbf,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0x90, + 0x82,0x7d,0x97,0x59,0x59,0x59,0x59,0x59,0x7f,0x59,0x59,0x60,0x7d,0x7f,0x7f, + 0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x9a,0x88,0x7d, + 0x59,0x50,0x50,0x50,0x50,0x59,0x59,0x59,0x59,0x61,0x94,0x61,0x9e,0x59,0x59, + 0x85,0x59,0x92,0xa3,0x60,0x60,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59, + 0x59,0x59,0x9f,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xcc,0x01,0xbc,0x03,0xf0, + 0x10,0x10,0x10,0x10,0x50,0x50,0x50,0x50,0x14,0x20,0x20,0x20,0x20,0x01,0x01, + 0x01,0x01,0xc4,0x02,0x10,0x00,0x00,0x00,0x00,0x01,0x01,0xc0,0xc2,0x10,0x11, + 0x02,0x03,0x11,0x03,0x03,0x04,0x00,0x00,0x14,0x00,0x02,0x00,0x00,0xc6,0xc8, + 0x02,0x02,0x02,0x02,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xca, + 0x01,0x01,0x01,0x00,0x06,0x00,0x04,0x00,0xc0,0xc2,0x01,0x01,0x03,0x01,0xff, + 0xff,0x01,0x00,0x03,0xc4,0xc4,0xc6,0x03,0x01,0x01,0x01,0xff,0x03,0x03,0x03, + 0xc8,0x40,0x00,0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00, + 0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xff,0xff,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x7f,0x00,0x00,0xff,0x4a,0x4a,0x4a,0x4a,0x4b,0x52,0x4a,0x4a,0x4a,0x4a,0x4f, + 0x4c,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x55,0x45,0x40,0x4a,0x4a,0x4a, + 0x45,0x59,0x4d,0x46,0x4a,0x5d,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a, + 0x4a,0x4a,0x4a,0x4a,0x4a,0x61,0x63,0x67,0x4e,0x4a,0x4a,0x6b,0x6d,0x4a,0x4a, + 0x45,0x6d,0x4a,0x4a,0x44,0x45,0x4a,0x4a,0x00,0x00,0x00,0x02,0x0d,0x06,0x06, + 0x06,0x06,0x0e,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x00,0x06,0x06,0x02,0x06, + 0x00,0x0a,0x0a,0x07,0x07,0x06,0x02,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, + 0x04,0x04,0x00,0x00,0x00,0x0e,0x05,0x06,0x06,0x06,0x01,0x06,0x00,0x00,0x08, + 0x00,0x10,0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01, + 0x86,0x00,0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba, + 0xf8,0xbb,0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00, + 0xc4,0xff,0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00, + 0x13,0x09,0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07, + 0xb2,0xff,0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf, + 0xe7,0x08,0x00,0xf0,0x02,0x00 +}; diff --git a/third_party/minhook/src/hde/table64.h b/third_party/minhook/src/hde/table64.h new file mode 100644 index 0000000..01d4541 --- /dev/null +++ b/third_party/minhook/src/hde/table64.h @@ -0,0 +1,74 @@ +/* + * Hacker Disassembler Engine 64 C + * Copyright (c) 2008-2009, Vyacheslav Patkov. + * All rights reserved. + * + */ + +#define C_NONE 0x00 +#define C_MODRM 0x01 +#define C_IMM8 0x02 +#define C_IMM16 0x04 +#define C_IMM_P66 0x10 +#define C_REL8 0x20 +#define C_REL32 0x40 +#define C_GROUP 0x80 +#define C_ERROR 0xff + +#define PRE_ANY 0x00 +#define PRE_NONE 0x01 +#define PRE_F2 0x02 +#define PRE_F3 0x04 +#define PRE_66 0x08 +#define PRE_67 0x10 +#define PRE_LOCK 0x20 +#define PRE_SEG 0x40 +#define PRE_ALL 0xff + +#define DELTA_OPCODES 0x4a +#define DELTA_FPU_REG 0xfd +#define DELTA_FPU_MODRM 0x104 +#define DELTA_PREFIXES 0x13c +#define DELTA_OP_LOCK_OK 0x1ae +#define DELTA_OP2_LOCK_OK 0x1c6 +#define DELTA_OP_ONLY_MEM 0x1d8 +#define DELTA_OP2_ONLY_MEM 0x1e7 + +unsigned char hde64_table[] = { + 0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5, + 0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1, + 0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea, + 0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0, + 0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab, + 0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92, + 0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90, + 0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b, + 0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b, + 0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc, + 0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20, + 0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff, + 0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00, + 0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01, + 0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10, + 0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00, + 0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00, + 0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00, + 0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40, + 0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43, + 0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, + 0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40, + 0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06, + 0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07, + 0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, + 0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10, + 0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00, + 0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb, + 0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff, + 0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09, + 0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff, + 0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08, + 0x00,0xf0,0x02,0x00 +}; diff --git a/third_party/minhook/src/hook.c b/third_party/minhook/src/hook.c new file mode 100644 index 0000000..06f5696 --- /dev/null +++ b/third_party/minhook/src/hook.c @@ -0,0 +1,939 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "../include/MinHook.h" +#include "buffer.h" +#include "trampoline.h" + +#ifndef ARRAYSIZE + #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +// Initial capacity of the HOOK_ENTRY buffer. +#define INITIAL_HOOK_CAPACITY 32 + +// Initial capacity of the thread IDs buffer. +#define INITIAL_THREAD_CAPACITY 128 + +// Special hook position values. +#define INVALID_HOOK_POS UINT_MAX +#define ALL_HOOKS_POS UINT_MAX + +// Freeze() action argument defines. +#define ACTION_DISABLE 0 +#define ACTION_ENABLE 1 +#define ACTION_APPLY_QUEUED 2 + +// Thread access rights for suspending/resuming threads. +#define THREAD_ACCESS \ + (THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SET_CONTEXT) + +// Hook information. +typedef struct _HOOK_ENTRY +{ + LPVOID pTarget; // Address of the target function. + LPVOID pDetour; // Address of the detour or relay function. + LPVOID pTrampoline; // Address of the trampoline function. + UINT8 backup[8]; // Original prologue of the target function. + + UINT8 patchAbove : 1; // Uses the hot patch area. + UINT8 isEnabled : 1; // Enabled. + UINT8 queueEnable : 1; // Queued for enabling/disabling when != isEnabled. + + UINT nIP : 4; // Count of the instruction boundaries. + UINT8 oldIPs[8]; // Instruction boundaries of the target function. + UINT8 newIPs[8]; // Instruction boundaries of the trampoline function. +} HOOK_ENTRY, *PHOOK_ENTRY; + +// Suspended threads for Freeze()/Unfreeze(). +typedef struct _FROZEN_THREADS +{ + LPDWORD pItems; // Data heap + UINT capacity; // Size of allocated data heap, items + UINT size; // Actual number of data items +} FROZEN_THREADS, *PFROZEN_THREADS; + +//------------------------------------------------------------------------- +// Global Variables: +//------------------------------------------------------------------------- + +// Spin lock flag for EnterSpinLock()/LeaveSpinLock(). +static volatile LONG g_isLocked = FALSE; + +// Private heap handle. If not NULL, this library is initialized. +static HANDLE g_hHeap = NULL; + +// Hook entries. +static struct +{ + PHOOK_ENTRY pItems; // Data heap + UINT capacity; // Size of allocated data heap, items + UINT size; // Actual number of data items +} g_hooks; + +//------------------------------------------------------------------------- +// Returns INVALID_HOOK_POS if not found. +static UINT FindHookEntry(LPVOID pTarget) +{ + UINT i; + for (i = 0; i < g_hooks.size; ++i) + { + if ((ULONG_PTR)pTarget == (ULONG_PTR)g_hooks.pItems[i].pTarget) + return i; + } + + return INVALID_HOOK_POS; +} + +//------------------------------------------------------------------------- +static PHOOK_ENTRY AddHookEntry() +{ + if (g_hooks.pItems == NULL) + { + g_hooks.capacity = INITIAL_HOOK_CAPACITY; + g_hooks.pItems = (PHOOK_ENTRY)HeapAlloc( + g_hHeap, 0, g_hooks.capacity * sizeof(HOOK_ENTRY)); + if (g_hooks.pItems == NULL) + return NULL; + } + else if (g_hooks.size >= g_hooks.capacity) + { + PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc( + g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity * 2) * sizeof(HOOK_ENTRY)); + if (p == NULL) + return NULL; + + g_hooks.capacity *= 2; + g_hooks.pItems = p; + } + + return &g_hooks.pItems[g_hooks.size++]; +} + +//------------------------------------------------------------------------- +static VOID DeleteHookEntry(UINT pos) +{ + if (pos < g_hooks.size - 1) + g_hooks.pItems[pos] = g_hooks.pItems[g_hooks.size - 1]; + + g_hooks.size--; + + if (g_hooks.capacity / 2 >= INITIAL_HOOK_CAPACITY && g_hooks.capacity / 2 >= g_hooks.size) + { + PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc( + g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity / 2) * sizeof(HOOK_ENTRY)); + if (p == NULL) + return; + + g_hooks.capacity /= 2; + g_hooks.pItems = p; + } +} + +//------------------------------------------------------------------------- +static DWORD_PTR FindOldIP(PHOOK_ENTRY pHook, DWORD_PTR ip) +{ + UINT i; + + if (pHook->patchAbove && ip == ((DWORD_PTR)pHook->pTarget - sizeof(JMP_REL))) + return (DWORD_PTR)pHook->pTarget; + + for (i = 0; i < pHook->nIP; ++i) + { + if (ip == ((DWORD_PTR)pHook->pTrampoline + pHook->newIPs[i])) + return (DWORD_PTR)pHook->pTarget + pHook->oldIPs[i]; + } + +#if defined(_M_X64) || defined(__x86_64__) + // Check relay function. + if (ip == (DWORD_PTR)pHook->pDetour) + return (DWORD_PTR)pHook->pTarget; +#endif + + return 0; +} + +//------------------------------------------------------------------------- +static DWORD_PTR FindNewIP(PHOOK_ENTRY pHook, DWORD_PTR ip) +{ + UINT i; + for (i = 0; i < pHook->nIP; ++i) + { + if (ip == ((DWORD_PTR)pHook->pTarget + pHook->oldIPs[i])) + return (DWORD_PTR)pHook->pTrampoline + pHook->newIPs[i]; + } + + return 0; +} + +//------------------------------------------------------------------------- +static VOID ProcessThreadIPs(HANDLE hThread, UINT pos, UINT action) +{ + // If the thread suspended in the overwritten area, + // move IP to the proper address. + + CONTEXT c; +#if defined(_M_X64) || defined(__x86_64__) + DWORD64 *pIP = &c.Rip; +#else + DWORD *pIP = &c.Eip; +#endif + UINT count; + + c.ContextFlags = CONTEXT_CONTROL; + if (!GetThreadContext(hThread, &c)) + return; + + if (pos == ALL_HOOKS_POS) + { + pos = 0; + count = g_hooks.size; + } + else + { + count = pos + 1; + } + + for (; pos < count; ++pos) + { + PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; + BOOL enable; + DWORD_PTR ip; + + switch (action) + { + case ACTION_DISABLE: + enable = FALSE; + break; + + case ACTION_ENABLE: + enable = TRUE; + break; + + default: // ACTION_APPLY_QUEUED + enable = pHook->queueEnable; + break; + } + if (pHook->isEnabled == enable) + continue; + + if (enable) + ip = FindNewIP(pHook, *pIP); + else + ip = FindOldIP(pHook, *pIP); + + if (ip != 0) + { + *pIP = ip; + SetThreadContext(hThread, &c); + } + } +} + +//------------------------------------------------------------------------- +static BOOL EnumerateThreads(PFROZEN_THREADS pThreads) +{ + BOOL succeeded = FALSE; + + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (hSnapshot != INVALID_HANDLE_VALUE) + { + THREADENTRY32 te; + te.dwSize = sizeof(THREADENTRY32); + if (Thread32First(hSnapshot, &te)) + { + succeeded = TRUE; + do + { + if (te.dwSize >= (FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(DWORD)) + && te.th32OwnerProcessID == GetCurrentProcessId() + && te.th32ThreadID != GetCurrentThreadId()) + { + if (pThreads->pItems == NULL) + { + pThreads->capacity = INITIAL_THREAD_CAPACITY; + pThreads->pItems + = (LPDWORD)HeapAlloc(g_hHeap, 0, pThreads->capacity * sizeof(DWORD)); + if (pThreads->pItems == NULL) + { + succeeded = FALSE; + break; + } + } + else if (pThreads->size >= pThreads->capacity) + { + LPDWORD p; + pThreads->capacity *= 2; + p = (LPDWORD)HeapReAlloc( + g_hHeap, 0, pThreads->pItems, pThreads->capacity * sizeof(DWORD)); + if (p == NULL) + { + succeeded = FALSE; + break; + } + + pThreads->pItems = p; + } + pThreads->pItems[pThreads->size++] = te.th32ThreadID; + } + + te.dwSize = sizeof(THREADENTRY32); + } while (Thread32Next(hSnapshot, &te)); + + if (succeeded && GetLastError() != ERROR_NO_MORE_FILES) + succeeded = FALSE; + + if (!succeeded && pThreads->pItems != NULL) + { + HeapFree(g_hHeap, 0, pThreads->pItems); + pThreads->pItems = NULL; + } + } + CloseHandle(hSnapshot); + } + + return succeeded; +} + +//------------------------------------------------------------------------- +static MH_STATUS Freeze(PFROZEN_THREADS pThreads, UINT pos, UINT action) +{ + MH_STATUS status = MH_OK; + + pThreads->pItems = NULL; + pThreads->capacity = 0; + pThreads->size = 0; + if (!EnumerateThreads(pThreads)) + { + status = MH_ERROR_MEMORY_ALLOC; + } + else if (pThreads->pItems != NULL) + { + UINT i; + for (i = 0; i < pThreads->size; ++i) + { + HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, pThreads->pItems[i]); + BOOL suspended = FALSE; + if (hThread != NULL) + { + DWORD result = SuspendThread(hThread); + if (result != 0xFFFFFFFF) + { + suspended = TRUE; + ProcessThreadIPs(hThread, pos, action); + } + CloseHandle(hThread); + } + + if (!suspended) + { + // Mark thread as not suspended, so it's not resumed later on. + pThreads->pItems[i] = 0; + } + } + } + + return status; +} + +//------------------------------------------------------------------------- +static VOID Unfreeze(PFROZEN_THREADS pThreads) +{ + if (pThreads->pItems != NULL) + { + UINT i; + for (i = 0; i < pThreads->size; ++i) + { + DWORD threadId = pThreads->pItems[i]; + if (threadId != 0) + { + HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, threadId); + if (hThread != NULL) + { + ResumeThread(hThread); + CloseHandle(hThread); + } + } + } + + HeapFree(g_hHeap, 0, pThreads->pItems); + } +} + +//------------------------------------------------------------------------- +static MH_STATUS EnableHookLL(UINT pos, BOOL enable) +{ + PHOOK_ENTRY pHook = &g_hooks.pItems[pos]; + DWORD oldProtect; + SIZE_T patchSize = sizeof(JMP_REL); + LPBYTE pPatchTarget = (LPBYTE)pHook->pTarget; + + if (pHook->patchAbove) + { + pPatchTarget -= sizeof(JMP_REL); + patchSize += sizeof(JMP_REL_SHORT); + } + + if (!VirtualProtect(pPatchTarget, patchSize, PAGE_EXECUTE_READWRITE, &oldProtect)) + return MH_ERROR_MEMORY_PROTECT; + + if (enable) + { + PJMP_REL pJmp = (PJMP_REL)pPatchTarget; + pJmp->opcode = 0xE9; + pJmp->operand = (INT32)((LPBYTE)pHook->pDetour - (pPatchTarget + sizeof(JMP_REL))); + + if (pHook->patchAbove) + { + PJMP_REL_SHORT pShortJmp = (PJMP_REL_SHORT)pHook->pTarget; + pShortJmp->opcode = 0xEB; + pShortJmp->operand = (INT8)(0 - (sizeof(JMP_REL_SHORT) + sizeof(JMP_REL))); + } + } + else + { + if (pHook->patchAbove) + memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL) + sizeof(JMP_REL_SHORT)); + else + memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL)); + } + + VirtualProtect(pPatchTarget, patchSize, oldProtect, &oldProtect); + + // Just-in-case measure. + FlushInstructionCache(GetCurrentProcess(), pPatchTarget, patchSize); + + pHook->isEnabled = enable; + pHook->queueEnable = enable; + + return MH_OK; +} + +//------------------------------------------------------------------------- +static MH_STATUS EnableAllHooksLL(BOOL enable) +{ + MH_STATUS status = MH_OK; + UINT i, first = INVALID_HOOK_POS; + + for (i = 0; i < g_hooks.size; ++i) + { + if (g_hooks.pItems[i].isEnabled != enable) + { + first = i; + break; + } + } + + if (first != INVALID_HOOK_POS) + { + FROZEN_THREADS threads; + status = Freeze(&threads, ALL_HOOKS_POS, enable ? ACTION_ENABLE : ACTION_DISABLE); + if (status == MH_OK) + { + for (i = first; i < g_hooks.size; ++i) + { + if (g_hooks.pItems[i].isEnabled != enable) + { + status = EnableHookLL(i, enable); + if (status != MH_OK) + break; + } + } + + Unfreeze(&threads); + } + } + + return status; +} + +//------------------------------------------------------------------------- +static VOID EnterSpinLock(VOID) +{ + SIZE_T spinCount = 0; + + // Wait until the flag is FALSE. + while (InterlockedCompareExchange(&g_isLocked, TRUE, FALSE) != FALSE) + { + // No need to generate a memory barrier here, since InterlockedCompareExchange() + // generates a full memory barrier itself. + + // Prevent the loop from being too busy. + if (spinCount < 32) + Sleep(0); + else + Sleep(1); + + spinCount++; + } +} + +//------------------------------------------------------------------------- +static VOID LeaveSpinLock(VOID) +{ + // No need to generate a memory barrier here, since InterlockedExchange() + // generates a full memory barrier itself. + + InterlockedExchange(&g_isLocked, FALSE); +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_Initialize(VOID) +{ + MH_STATUS status = MH_OK; + + EnterSpinLock(); + + if (g_hHeap == NULL) + { + g_hHeap = HeapCreate(0, 0, 0); + if (g_hHeap != NULL) + { + // Initialize the internal function buffer. + InitializeBuffer(); + } + else + { + status = MH_ERROR_MEMORY_ALLOC; + } + } + else + { + status = MH_ERROR_ALREADY_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_Uninitialize(VOID) +{ + MH_STATUS status = MH_OK; + + EnterSpinLock(); + + if (g_hHeap != NULL) + { + status = EnableAllHooksLL(FALSE); + if (status == MH_OK) + { + // Free the internal function buffer. + + // HeapFree is actually not required, but some tools detect a false + // memory leak without HeapFree. + + UninitializeBuffer(); + + HeapFree(g_hHeap, 0, g_hooks.pItems); + HeapDestroy(g_hHeap); + + g_hHeap = NULL; + + g_hooks.pItems = NULL; + g_hooks.capacity = 0; + g_hooks.size = 0; + } + } + else + { + status = MH_ERROR_NOT_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal) +{ + MH_STATUS status = MH_OK; + + EnterSpinLock(); + + if (g_hHeap != NULL) + { + if (IsExecutableAddress(pTarget) && IsExecutableAddress(pDetour)) + { + UINT pos = FindHookEntry(pTarget); + if (pos == INVALID_HOOK_POS) + { + LPVOID pBuffer = AllocateBuffer(pTarget); + if (pBuffer != NULL) + { + TRAMPOLINE ct; + + ct.pTarget = pTarget; + ct.pDetour = pDetour; + ct.pTrampoline = pBuffer; + if (CreateTrampolineFunction(&ct)) + { + PHOOK_ENTRY pHook = AddHookEntry(); + if (pHook != NULL) + { + pHook->pTarget = ct.pTarget; +#if defined(_M_X64) || defined(__x86_64__) + pHook->pDetour = ct.pRelay; +#else + pHook->pDetour = ct.pDetour; +#endif + pHook->pTrampoline = ct.pTrampoline; + pHook->patchAbove = ct.patchAbove; + pHook->isEnabled = FALSE; + pHook->queueEnable = FALSE; + pHook->nIP = ct.nIP; + memcpy(pHook->oldIPs, ct.oldIPs, ARRAYSIZE(ct.oldIPs)); + memcpy(pHook->newIPs, ct.newIPs, ARRAYSIZE(ct.newIPs)); + + // Back up the target function. + + if (ct.patchAbove) + { + memcpy( + pHook->backup, + (LPBYTE)pTarget - sizeof(JMP_REL), + sizeof(JMP_REL) + sizeof(JMP_REL_SHORT)); + } + else + { + memcpy(pHook->backup, pTarget, sizeof(JMP_REL)); + } + + if (ppOriginal != NULL) + *ppOriginal = pHook->pTrampoline; + } + else + { + status = MH_ERROR_MEMORY_ALLOC; + } + } + else + { + status = MH_ERROR_UNSUPPORTED_FUNCTION; + } + + if (status != MH_OK) + { + FreeBuffer(pBuffer); + } + } + else + { + status = MH_ERROR_MEMORY_ALLOC; + } + } + else + { + status = MH_ERROR_ALREADY_CREATED; + } + } + else + { + status = MH_ERROR_NOT_EXECUTABLE; + } + } + else + { + status = MH_ERROR_NOT_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget) +{ + MH_STATUS status = MH_OK; + + EnterSpinLock(); + + if (g_hHeap != NULL) + { + UINT pos = FindHookEntry(pTarget); + if (pos != INVALID_HOOK_POS) + { + if (g_hooks.pItems[pos].isEnabled) + { + FROZEN_THREADS threads; + status = Freeze(&threads, pos, ACTION_DISABLE); + if (status == MH_OK) + { + status = EnableHookLL(pos, FALSE); + + Unfreeze(&threads); + } + } + + if (status == MH_OK) + { + FreeBuffer(g_hooks.pItems[pos].pTrampoline); + DeleteHookEntry(pos); + } + } + else + { + status = MH_ERROR_NOT_CREATED; + } + } + else + { + status = MH_ERROR_NOT_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +static MH_STATUS EnableHook(LPVOID pTarget, BOOL enable) +{ + MH_STATUS status = MH_OK; + + EnterSpinLock(); + + if (g_hHeap != NULL) + { + if (pTarget == MH_ALL_HOOKS) + { + status = EnableAllHooksLL(enable); + } + else + { + UINT pos = FindHookEntry(pTarget); + if (pos != INVALID_HOOK_POS) + { + if (g_hooks.pItems[pos].isEnabled != enable) + { + FROZEN_THREADS threads; + status = Freeze(&threads, pos, ACTION_ENABLE); + if (status == MH_OK) + { + status = EnableHookLL(pos, enable); + + Unfreeze(&threads); + } + } + else + { + status = enable ? MH_ERROR_ENABLED : MH_ERROR_DISABLED; + } + } + else + { + status = MH_ERROR_NOT_CREATED; + } + } + } + else + { + status = MH_ERROR_NOT_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget) +{ + return EnableHook(pTarget, TRUE); +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget) +{ + return EnableHook(pTarget, FALSE); +} + +//------------------------------------------------------------------------- +static MH_STATUS QueueHook(LPVOID pTarget, BOOL queueEnable) +{ + MH_STATUS status = MH_OK; + + EnterSpinLock(); + + if (g_hHeap != NULL) + { + if (pTarget == MH_ALL_HOOKS) + { + UINT i; + for (i = 0; i < g_hooks.size; ++i) + g_hooks.pItems[i].queueEnable = queueEnable; + } + else + { + UINT pos = FindHookEntry(pTarget); + if (pos != INVALID_HOOK_POS) + { + g_hooks.pItems[pos].queueEnable = queueEnable; + } + else + { + status = MH_ERROR_NOT_CREATED; + } + } + } + else + { + status = MH_ERROR_NOT_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget) +{ + return QueueHook(pTarget, TRUE); +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget) +{ + return QueueHook(pTarget, FALSE); +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_ApplyQueued(VOID) +{ + MH_STATUS status = MH_OK; + UINT i, first = INVALID_HOOK_POS; + + EnterSpinLock(); + + if (g_hHeap != NULL) + { + for (i = 0; i < g_hooks.size; ++i) + { + if (g_hooks.pItems[i].isEnabled != g_hooks.pItems[i].queueEnable) + { + first = i; + break; + } + } + + if (first != INVALID_HOOK_POS) + { + FROZEN_THREADS threads; + status = Freeze(&threads, ALL_HOOKS_POS, ACTION_APPLY_QUEUED); + if (status == MH_OK) + { + for (i = first; i < g_hooks.size; ++i) + { + PHOOK_ENTRY pHook = &g_hooks.pItems[i]; + if (pHook->isEnabled != pHook->queueEnable) + { + status = EnableHookLL(i, pHook->queueEnable); + if (status != MH_OK) + break; + } + } + + Unfreeze(&threads); + } + } + } + else + { + status = MH_ERROR_NOT_INITIALIZED; + } + + LeaveSpinLock(); + + return status; +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_CreateHookApiEx( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, + LPVOID *ppOriginal, LPVOID *ppTarget) +{ + HMODULE hModule; + LPVOID pTarget; + + hModule = GetModuleHandleW(pszModule); + if (hModule == NULL) + return MH_ERROR_MODULE_NOT_FOUND; + + pTarget = (LPVOID)GetProcAddress(hModule, pszProcName); + if (pTarget == NULL) + return MH_ERROR_FUNCTION_NOT_FOUND; + + if (ppTarget != NULL) + *ppTarget = pTarget; + + return MH_CreateHook(pTarget, pDetour, ppOriginal); +} + +//------------------------------------------------------------------------- +MH_STATUS WINAPI MH_CreateHookApi( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal) +{ + return MH_CreateHookApiEx(pszModule, pszProcName, pDetour, ppOriginal, NULL); +} + +//------------------------------------------------------------------------- +const char *WINAPI MH_StatusToString(MH_STATUS status) +{ +#define MH_ST2STR(x) \ + case x: \ + return #x; + + switch (status) { + MH_ST2STR(MH_UNKNOWN) + MH_ST2STR(MH_OK) + MH_ST2STR(MH_ERROR_ALREADY_INITIALIZED) + MH_ST2STR(MH_ERROR_NOT_INITIALIZED) + MH_ST2STR(MH_ERROR_ALREADY_CREATED) + MH_ST2STR(MH_ERROR_NOT_CREATED) + MH_ST2STR(MH_ERROR_ENABLED) + MH_ST2STR(MH_ERROR_DISABLED) + MH_ST2STR(MH_ERROR_NOT_EXECUTABLE) + MH_ST2STR(MH_ERROR_UNSUPPORTED_FUNCTION) + MH_ST2STR(MH_ERROR_MEMORY_ALLOC) + MH_ST2STR(MH_ERROR_MEMORY_PROTECT) + MH_ST2STR(MH_ERROR_MODULE_NOT_FOUND) + MH_ST2STR(MH_ERROR_FUNCTION_NOT_FOUND) + } + +#undef MH_ST2STR + + return "(unknown)"; +} diff --git a/third_party/minhook/src/trampoline.c b/third_party/minhook/src/trampoline.c new file mode 100644 index 0000000..cf8ce05 --- /dev/null +++ b/third_party/minhook/src/trampoline.c @@ -0,0 +1,320 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#if defined(_MSC_VER) && !defined(MINHOOK_DISABLE_INTRINSICS) + #define ALLOW_INTRINSICS + #include +#endif + +#ifndef ARRAYSIZE + #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +#if defined(_M_X64) || defined(__x86_64__) + #include "./hde/hde64.h" + typedef hde64s HDE; + #define HDE_DISASM(code, hs) hde64_disasm(code, hs) +#else + #include "./hde/hde32.h" + typedef hde32s HDE; + #define HDE_DISASM(code, hs) hde32_disasm(code, hs) +#endif + +#include "trampoline.h" +#include "buffer.h" + +// Maximum size of a trampoline function. +#if defined(_M_X64) || defined(__x86_64__) + #define TRAMPOLINE_MAX_SIZE (MEMORY_SLOT_SIZE - sizeof(JMP_ABS)) +#else + #define TRAMPOLINE_MAX_SIZE MEMORY_SLOT_SIZE +#endif + +//------------------------------------------------------------------------- +static BOOL IsCodePadding(LPBYTE pInst, UINT size) +{ + UINT i; + + if (pInst[0] != 0x00 && pInst[0] != 0x90 && pInst[0] != 0xCC) + return FALSE; + + for (i = 1; i < size; ++i) + { + if (pInst[i] != pInst[0]) + return FALSE; + } + return TRUE; +} + +//------------------------------------------------------------------------- +BOOL CreateTrampolineFunction(PTRAMPOLINE ct) +{ +#if defined(_M_X64) || defined(__x86_64__) + CALL_ABS call = { + 0xFF, 0x15, 0x00000002, // FF15 00000002: CALL [RIP+8] + 0xEB, 0x08, // EB 08: JMP +10 + 0x0000000000000000ULL // Absolute destination address + }; + JMP_ABS jmp = { + 0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6] + 0x0000000000000000ULL // Absolute destination address + }; + JCC_ABS jcc = { + 0x70, 0x0E, // 7* 0E: J** +16 + 0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6] + 0x0000000000000000ULL // Absolute destination address + }; +#else + CALL_REL call = { + 0xE8, // E8 xxxxxxxx: CALL +5+xxxxxxxx + 0x00000000 // Relative destination address + }; + JMP_REL jmp = { + 0xE9, // E9 xxxxxxxx: JMP +5+xxxxxxxx + 0x00000000 // Relative destination address + }; + JCC_REL jcc = { + 0x0F, 0x80, // 0F8* xxxxxxxx: J** +6+xxxxxxxx + 0x00000000 // Relative destination address + }; +#endif + + UINT8 oldPos = 0; + UINT8 newPos = 0; + ULONG_PTR jmpDest = 0; // Destination address of an internal jump. + BOOL finished = FALSE; // Is the function completed? +#if defined(_M_X64) || defined(__x86_64__) + UINT8 instBuf[16]; +#endif + + ct->patchAbove = FALSE; + ct->nIP = 0; + + do + { + HDE hs; + UINT copySize; + LPVOID pCopySrc; + ULONG_PTR pOldInst = (ULONG_PTR)ct->pTarget + oldPos; + ULONG_PTR pNewInst = (ULONG_PTR)ct->pTrampoline + newPos; + + copySize = HDE_DISASM((LPVOID)pOldInst, &hs); + if (hs.flags & F_ERROR) + return FALSE; + + pCopySrc = (LPVOID)pOldInst; + if (oldPos >= sizeof(JMP_REL)) + { + // The trampoline function is long enough. + // Complete the function with the jump to the target function. +#if defined(_M_X64) || defined(__x86_64__) + jmp.address = pOldInst; +#else + jmp.operand = (INT32)(pOldInst - (pNewInst + sizeof(jmp))); +#endif + pCopySrc = &jmp; + copySize = sizeof(jmp); + + finished = TRUE; + } +#if defined(_M_X64) || defined(__x86_64__) + else if ((hs.modrm & 0xC7) == 0x05) + { + // Instructions using RIP relative addressing. (ModR/M = 00???101B) + + // Modify the RIP relative address. + PUINT32 pRelAddr; + + // Avoid using memcpy to reduce the footprint. +#ifndef ALLOW_INTRINSICS + memcpy(instBuf, (LPBYTE)pOldInst, copySize); +#else + __movsb(instBuf, (LPBYTE)pOldInst, copySize); +#endif + pCopySrc = instBuf; + + // Relative address is stored at (instruction length - immediate value length - 4). + pRelAddr = (PUINT32)(instBuf + hs.len - ((hs.flags & 0x3C) >> 2) - 4); + *pRelAddr + = (UINT32)((pOldInst + hs.len + (INT32)hs.disp.disp32) - (pNewInst + hs.len)); + + // Complete the function if JMP (FF /4). + if (hs.opcode == 0xFF && hs.modrm_reg == 4) + finished = TRUE; + } +#endif + else if (hs.opcode == 0xE8) + { + // Direct relative CALL + ULONG_PTR dest = pOldInst + hs.len + (INT32)hs.imm.imm32; +#if defined(_M_X64) || defined(__x86_64__) + call.address = dest; +#else + call.operand = (INT32)(dest - (pNewInst + sizeof(call))); +#endif + pCopySrc = &call; + copySize = sizeof(call); + } + else if ((hs.opcode & 0xFD) == 0xE9) + { + // Direct relative JMP (EB or E9) + ULONG_PTR dest = pOldInst + hs.len; + + if (hs.opcode == 0xEB) // isShort jmp + dest += (INT8)hs.imm.imm8; + else + dest += (INT32)hs.imm.imm32; + + // Simply copy an internal jump. + if ((ULONG_PTR)ct->pTarget <= dest + && dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL))) + { + if (jmpDest < dest) + jmpDest = dest; + } + else + { +#if defined(_M_X64) || defined(__x86_64__) + jmp.address = dest; +#else + jmp.operand = (INT32)(dest - (pNewInst + sizeof(jmp))); +#endif + pCopySrc = &jmp; + copySize = sizeof(jmp); + + // Exit the function if it is not in the branch. + finished = (pOldInst >= jmpDest); + } + } + else if ((hs.opcode & 0xF0) == 0x70 + || (hs.opcode & 0xFC) == 0xE0 + || (hs.opcode2 & 0xF0) == 0x80) + { + // Direct relative Jcc + ULONG_PTR dest = pOldInst + hs.len; + + if ((hs.opcode & 0xF0) == 0x70 // Jcc + || (hs.opcode & 0xFC) == 0xE0) // LOOPNZ/LOOPZ/LOOP/JECXZ + dest += (INT8)hs.imm.imm8; + else + dest += (INT32)hs.imm.imm32; + + // Simply copy an internal jump. + if ((ULONG_PTR)ct->pTarget <= dest + && dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL))) + { + if (jmpDest < dest) + jmpDest = dest; + } + else if ((hs.opcode & 0xFC) == 0xE0) + { + // LOOPNZ/LOOPZ/LOOP/JCXZ/JECXZ to the outside are not supported. + return FALSE; + } + else + { + UINT8 cond = ((hs.opcode != 0x0F ? hs.opcode : hs.opcode2) & 0x0F); +#if defined(_M_X64) || defined(__x86_64__) + // Invert the condition in x64 mode to simplify the conditional jump logic. + jcc.opcode = 0x71 ^ cond; + jcc.address = dest; +#else + jcc.opcode1 = 0x80 | cond; + jcc.operand = (INT32)(dest - (pNewInst + sizeof(jcc))); +#endif + pCopySrc = &jcc; + copySize = sizeof(jcc); + } + } + else if ((hs.opcode & 0xFE) == 0xC2) + { + // RET (C2 or C3) + + // Complete the function if not in a branch. + finished = (pOldInst >= jmpDest); + } + + // Can't alter the instruction length in a branch. + if (pOldInst < jmpDest && copySize != hs.len) + return FALSE; + + // Trampoline function is too large. + if ((newPos + copySize) > TRAMPOLINE_MAX_SIZE) + return FALSE; + + // Trampoline function has too many instructions. + if (ct->nIP >= ARRAYSIZE(ct->oldIPs)) + return FALSE; + + ct->oldIPs[ct->nIP] = oldPos; + ct->newIPs[ct->nIP] = newPos; + ct->nIP++; + + // Avoid using memcpy to reduce the footprint. +#ifndef ALLOW_INTRINSICS + memcpy((LPBYTE)ct->pTrampoline + newPos, pCopySrc, copySize); +#else + __movsb((LPBYTE)ct->pTrampoline + newPos, (LPBYTE)pCopySrc, copySize); +#endif + newPos += copySize; + oldPos += hs.len; + } while (!finished); + + // Is there enough place for a long jump? + if (oldPos < sizeof(JMP_REL) + && !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL) - oldPos)) + { + // Is there enough place for a short jump? + if (oldPos < sizeof(JMP_REL_SHORT) + && !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL_SHORT) - oldPos)) + { + return FALSE; + } + + // Can we place the long jump above the function? + if (!IsExecutableAddress((LPBYTE)ct->pTarget - sizeof(JMP_REL))) + return FALSE; + + if (!IsCodePadding((LPBYTE)ct->pTarget - sizeof(JMP_REL), sizeof(JMP_REL))) + return FALSE; + + ct->patchAbove = TRUE; + } + +#if defined(_M_X64) || defined(__x86_64__) + // Create a relay function. + jmp.address = (ULONG_PTR)ct->pDetour; + + ct->pRelay = (LPBYTE)ct->pTrampoline + newPos; + memcpy(ct->pRelay, &jmp, sizeof(jmp)); +#endif + + return TRUE; +} diff --git a/third_party/minhook/src/trampoline.h b/third_party/minhook/src/trampoline.h new file mode 100644 index 0000000..f7efb36 --- /dev/null +++ b/third_party/minhook/src/trampoline.h @@ -0,0 +1,105 @@ +/* + * MinHook - The Minimalistic API Hooking Library for x64/x86 + * Copyright (C) 2009-2017 Tsuda Kageyu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#pragma pack(push, 1) + +// Structs for writing x86/x64 instructions. + +// 8-bit relative jump. +typedef struct _JMP_REL_SHORT +{ + UINT8 opcode; // EB xx: JMP +2+xx + INT8 operand; // Relative destination address +} JMP_REL_SHORT, *PJMP_REL_SHORT; + +// 32-bit direct relative jump/call. +typedef struct _JMP_REL +{ + UINT8 opcode; // E9/E8 xxxxxxxx: JMP/CALL +5+xxxxxxxx + INT32 operand; // Relative destination address +} JMP_REL, *PJMP_REL, CALL_REL; + +// 64-bit indirect absolute jump. +typedef struct _JMP_ABS +{ + UINT8 opcode0; // FF25 00000000: JMP [+6] + UINT8 opcode1; + UINT32 dummy; + UINT64 address; // Absolute destination address +} JMP_ABS, *PJMP_ABS; + +// 64-bit indirect absolute call. +typedef struct _CALL_ABS +{ + UINT8 opcode0; // FF15 00000002: CALL [+6] + UINT8 opcode1; + UINT32 dummy0; + UINT8 dummy1; // EB 08: JMP +10 + UINT8 dummy2; + UINT64 address; // Absolute destination address +} CALL_ABS; + +// 32-bit direct relative conditional jumps. +typedef struct _JCC_REL +{ + UINT8 opcode0; // 0F8* xxxxxxxx: J** +6+xxxxxxxx + UINT8 opcode1; + INT32 operand; // Relative destination address +} JCC_REL; + +// 64bit indirect absolute conditional jumps that x64 lacks. +typedef struct _JCC_ABS +{ + UINT8 opcode; // 7* 0E: J** +16 + UINT8 dummy0; + UINT8 dummy1; // FF25 00000000: JMP [+6] + UINT8 dummy2; + UINT32 dummy3; + UINT64 address; // Absolute destination address +} JCC_ABS; + +#pragma pack(pop) + +typedef struct _TRAMPOLINE +{ + LPVOID pTarget; // [In] Address of the target function. + LPVOID pDetour; // [In] Address of the detour function. + LPVOID pTrampoline; // [In] Buffer address for the trampoline and relay function. + +#if defined(_M_X64) || defined(__x86_64__) + LPVOID pRelay; // [Out] Address of the relay function. +#endif + BOOL patchAbove; // [Out] Should use the hot patch area? + UINT nIP; // [Out] Number of the instruction boundaries. + UINT8 oldIPs[8]; // [Out] Instruction boundaries of the target function. + UINT8 newIPs[8]; // [Out] Instruction boundaries of the trampoline function. +} TRAMPOLINE, *PTRAMPOLINE; + +BOOL CreateTrampolineFunction(PTRAMPOLINE ct); diff --git a/tools/build-shim.sh b/tools/build-shim.sh new file mode 100755 index 0000000..3a8d269 --- /dev/null +++ b/tools/build-shim.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Build the shim (binkw32.dll) with the MinGW i686 cross toolchain and stage a deployable set. +# Runs on the build box (CT111). Env: +# BUILD_ID stamp for the log banner (default: git describe or "nogit", plus UTC time) +# DIST staging dir (default /srv/re-lab/shim/dist, == Z:\shim\dist on the game VM) +# REAL_DLL original binkw32.dll to compare export tables against (skipped if absent) +set -euo pipefail +cd "$(dirname "$0")/.." + +BUILD_ID=${BUILD_ID:-$(git describe --always --dirty 2>/dev/null || echo nogit)-$(date -u +%Y%m%dT%H%MZ)} +DIST=${DIST:-/srv/re-lab/shim/dist} +REAL_DLL=${REAL_DLL:-/srv/re-lab/sots-game/binkw32.dll} +OBJDUMP=${OBJDUMP:-i686-w64-mingw32-objdump} + +cmake --preset shim -DSHIM_BUILD_ID="$BUILD_ID" +cmake --build --preset shim + +dll=build-shim/binkw32.dll +"$OBJDUMP" -p "$dll" > build-shim/exports.txt + +# Export-table check: names must be byte-identical to the real DLL's (the exe imports by name). +names() { # the "[Ordinal/Name Pointer] Table" section only (the address table also lists +base[ rows) + "$OBJDUMP" -p "$1" | sed -n '/Ordinal\/Name Pointer\] Table/,/^$/p' | awk '/\+base\[/ { print $6 }' | sort +} +if [ -f "$REAL_DLL" ]; then + if diff <(names "$REAL_DLL") <(names "$dll") > build-shim/exports.diff; then + echo "exports: $(names "$dll" | wc -l) names, identical to $(basename "$REAL_DLL")" + else + echo "exports: MISMATCH vs $REAL_DLL:"; cat build-shim/exports.diff; exit 1 + fi +else + echo "exports: $(names "$dll" | wc -l) names (no REAL_DLL to compare against)" +fi + +mkdir -p "$DIST" +cp "$dll" src/shim/shim.cfg tools/deploy.ps1 tools/undeploy.ps1 "$DIST/" +echo "$BUILD_ID" > "$DIST/BUILD_ID" +echo "staged $BUILD_ID in $DIST" diff --git a/tools/deploy.ps1 b/tools/deploy.ps1 new file mode 100644 index 0000000..4b796df --- /dev/null +++ b/tools/deploy.ps1 @@ -0,0 +1,52 @@ +# Deploy the shim onto the game VM: stop the game, keep the original binkw32.dll as +# binkw32_real.dll, drop in the proxy + config, relaunch. +# powershell -ExecutionPolicy Bypass -File deploy.ps1 [-Dist Z:\shim\dist] [-NoLaunch] +# If -Dist is on an unmapped drive, the share is mapped using -ShareUser/-SharePass +# (or env RELAB_USER / RELAB_PASS). Credentials are never stored here. +param( + [string]$Dist = 'Z:\shim\dist', + [string]$GameDir = 'C:\SOTS', + [string]$Share = '\\192.168.10.138\re-lab', + [string]$ShareUser = $env:RELAB_USER, + [string]$SharePass = $env:RELAB_PASS, + [string]$Task = 'SOTS', + [switch]$NoLaunch +) +$ProgressPreference = 'SilentlyContinue' +$ErrorActionPreference = 'Stop' + +if (-not (Test-Path $Dist)) { + $drive = $Dist.Substring(0, 2) + if ($SharePass) { net use $drive $Share /user:$ShareUser $SharePass | Out-Null } + if (-not (Test-Path $Dist)) { throw "cannot reach $Dist (share not mapped; pass -ShareUser/-SharePass)" } +} +$src = Join-Path $Dist 'binkw32.dll' +if (-not (Test-Path $src)) { throw "no binkw32.dll in $Dist" } + +$exe = 'Sword of the Stars' +$cur = Join-Path $GameDir 'binkw32.dll' +$real = Join-Path $GameDir 'binkw32_real.dll' + +$p = Get-Process -Name $exe -ErrorAction SilentlyContinue +if ($p) { Write-Output "stopping $exe (pid $($p.Id))"; $p | Stop-Process -Force; Start-Sleep -Seconds 3 } + +# First deploy: the file in place is the original. A proxy is recognisable by its forwarder strings. +$isProxy = (Get-Content -Path $cur -Raw) -match 'binkw32_real\.' +if (-not (Test-Path $real)) { + if ($isProxy) { throw "$cur is already a proxy and $real is missing - restore the original first" } + Copy-Item $cur $real + Write-Output "backed up original -> $real" +} + +Copy-Item $src $cur -Force +$cfg = Join-Path $GameDir 'shim.cfg' +if (-not (Test-Path $cfg)) { Copy-Item (Join-Path $Dist 'shim.cfg') $cfg } # keep operator edits +$id = Join-Path $Dist 'BUILD_ID' +if (Test-Path $id) { Write-Output "deployed build $(Get-Content $id)" } + +if (-not $NoLaunch) { + schtasks /Run /TN $Task | Out-Null + Start-Sleep -Seconds 5 + $p = Get-Process -Name $exe -ErrorAction SilentlyContinue + if ($p) { Write-Output "launched $exe (pid $($p.Id))" } else { Write-Warning "$exe not running 5 s after launch" } +} diff --git a/tools/sync-build.sh b/tools/sync-build.sh new file mode 100755 index 0000000..e94c439 --- /dev/null +++ b/tools/sync-build.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# From the dev box: push this tree to the build box's share and run tools/build-shim.sh there. +# The share (/bulk-storage/re-lab on host "spicy") is /srv/re-lab inside CT111. +set -euo pipefail +cd "$(dirname "$0")/.." + +HOST=${HOST:-spicy} +CT=${CT:-111} +REMOTE_TREE=${REMOTE_TREE:-/bulk-storage/re-lab/build/sots-engine} # host path +CT_TREE=${CT_TREE:-/srv/re-lab/build/sots-engine} # same dir, seen from the CT +BUILD_ID=$(git describe --always --dirty 2>/dev/null || echo nogit)-$(date -u +%Y%m%dT%H%MZ) + +# unprivileged CT: uid 0 in the container is 100000 on the host +rsync -a --delete --chown=100000:100000 \ + --exclude .git --exclude '/build-*/' --exclude '/build/' \ + ./ "$HOST:$REMOTE_TREE/" +ssh "$HOST" pct exec "$CT" -- env BUILD_ID="$BUILD_ID" bash "$CT_TREE/tools/build-shim.sh" diff --git a/tools/undeploy.ps1 b/tools/undeploy.ps1 new file mode 100644 index 0000000..759319a --- /dev/null +++ b/tools/undeploy.ps1 @@ -0,0 +1,27 @@ +# Put the original binkw32.dll back and relaunch the game. +# powershell -ExecutionPolicy Bypass -File undeploy.ps1 [-NoLaunch] +param( + [string]$GameDir = 'C:\SOTS', + [string]$Task = 'SOTS', + [switch]$NoLaunch +) +$ProgressPreference = 'SilentlyContinue' +$ErrorActionPreference = 'Stop' + +$exe = 'Sword of the Stars' +$cur = Join-Path $GameDir 'binkw32.dll' +$real = Join-Path $GameDir 'binkw32_real.dll' +if (-not (Test-Path $real)) { throw "$real not found - nothing to restore" } + +$p = Get-Process -Name $exe -ErrorAction SilentlyContinue +if ($p) { Write-Output "stopping $exe (pid $($p.Id))"; $p | Stop-Process -Force; Start-Sleep -Seconds 3 } + +Move-Item $real $cur -Force +Write-Output "restored original binkw32.dll" + +if (-not $NoLaunch) { + schtasks /Run /TN $Task | Out-Null + Start-Sleep -Seconds 5 + $p = Get-Process -Name $exe -ErrorAction SilentlyContinue + if ($p) { Write-Output "launched $exe (pid $($p.Id))" } else { Write-Warning "$exe not running 5 s after launch" } +}