diff options
Diffstat (limited to 'src/ac.c')
-rw-r--r-- | src/ac.c | 257 |
1 files changed, 199 insertions, 58 deletions
@@ -20,14 +20,21 @@ #include <immintrin.h> #endif +#include "alias.h" #include "bind.h" +#include "chunklets/fastspin.h" +#include "chunklets/msg.h" #include "con_.h" +#include "crypto.h" +#include "democustom.h" +#include "demorec.h" #include "hook.h" #include "engineapi.h" #include "errmsg.h" #include "event.h" #include "feature.h" #include "gamedata.h" +#include "gametype.h" #include "intdefs.h" #include "mem.h" #include "os.h" @@ -37,33 +44,91 @@ #include "x86.h" #include "x86util.h" +#ifdef _WIN32 +#include <werapi.h> // must be after Windows.h (via os.h) +#endif + FEATURE() REQUIRE(bind) REQUIRE(democustom) REQUIRE_GAMEDATA(vtidx_GetDesktopResolution) REQUIRE_GAMEDATA(vtidx_DispatchAllStoredGameMessages) +REQUIRE_GLOBAL(pluginhandler) + +static bool enabled = false; -static bool lockdown = false; +// mild overkill: 1 page of memory that won't be coredumped or swapped to disk +static struct keybox { + union { uchar prv[32], shr[32]; }; + uchar tmp[32], pub[32], lbpub[32]; // NOTE: these 3 must be kept contiguous! + union { u64 nonce; uchar nonce_bytes[8]; }; + crypto_rng_ctx rng; // NOTE: keep this at the end, for wipesessionkeys() +} *keybox; + +enum { + LBPK_L4D +}; +const uchar lbpubkeys[1][32] = { + // L4D series (PLACEHOLDERS for now; this whole thing is unfinished!) + 0x4A, 0xF3, 0xE2, 0xFC, 0x9C, 0x4E, 0xCB, 0xF9, + 0xBD, 0xB8, 0xA9, 0xFC, 0x0E, 0xF7, 0x93, 0x9C, + 0xC3, 0x09, 0x43, 0xB2, 0x6E, 0x7B, 0x1F, 0x19, + 0x40, 0x05, 0xE9, 0x60, 0x43, 0xE8, 0xE2, 0x03 +}; + +static void newsessionkeys(void) { + crypto_rng_read(&keybox->rng, keybox->prv, sizeof(keybox->prv)); + crypto_x25519_public_key(keybox->pub, keybox->prv); + crypto_x25519(keybox->tmp, keybox->prv, keybox->lbpub); + // dumbest, safest possible key derivation, because I'm not a cryptographer. + // future versions of the custom demo protocol COULD get something faster + // (like something with hchacha20, if only I could find enough info on that) + crypto_blake2b(keybox->shr, sizeof(keybox->tmp), keybox->tmp, 96); + crypto_wipe(keybox->tmp, sizeof(keybox->tmp)); + keybox->nonce = 0; +} + +static void wipesessionkeys(void) { + crypto_wipe(keybox->prv, offsetof(struct keybox, rng)); +} + +HANDLE_EVENT(DemoRecordStarting, void) { if (enabled) newsessionkeys(); } +HANDLE_EVENT(DemoRecordStopped, int ndemos) { if (enabled) wipesessionkeys(); } #ifdef _WIN32 static void *gamewin, *inhookwin, *inhookthr; static ulong inhooktid; -// UINT_PTR is a **stupid** typedef, but whatever. -static ssize __stdcall kproc(int code, UINT_PTR wp, ssize lp) { +static ssize __stdcall kproc(int code, usize wp, ssize lp) { KBDLLHOOKSTRUCT *data = (KBDLLHOOKSTRUCT *)lp; - if (lockdown && data->flags & LLKHF_INJECTED && + if (enabled && data->flags & LLKHF_INJECTED && GetForegroundWindow() == gamewin) { - return 1; + // maybe this input is reasonable, but log it for closer inspection + // TODO(rta): figure out what to do with this stuff + // something like the following, but with a proper abstraction... + //uchar buf[28 + 16], *p = buf; + //msg_putasz4(p, 2); p += 1; + // msg_putssz5(p, 8); memcpy(p + 1, "FakeKey", 7); p += 8; + // msg_putmsz4(p, 2); p += 1; + // msg_putssz5(p, 3); memcpy(p + 1, "vk", 2); p += 3; + // p += msg_putu32(p, data->vkCode); + // msg_putssz5(p, 3); memcpy(p + 1, "scan", 4); p += 5; + // p += msg_putu32(p, data->scanCode); + //++keybox->nonce; + //// append mac at end of message + //crypto_aead_lock_djb(buf, p, keybox->shr, keybox->nonce_bytes, 0, 0, + // buf, p - buf); + //democustom_write(buf, p - buf + 16); } return CallNextHookEx(0, code, wp, lp); } -static ssize __stdcall mproc(int code, UINT_PTR wp, ssize lp) { +static ssize __stdcall mproc(int code, usize wp, ssize lp) { MSLLHOOKSTRUCT *data = (MSLLHOOKSTRUCT *)lp; - if (lockdown && data->flags & LLMHF_INJECTED && + if (enabled && data->flags & LLMHF_INJECTED && GetForegroundWindow() == gamewin) { + // no way this input would ever be reasonable. just discard it return 1; } return CallNextHookEx(0, code, wp, lp); @@ -72,46 +137,48 @@ static ssize __stdcall mproc(int code, UINT_PTR wp, ssize lp) { // this is its own thread to meet the strict timing deadline, otherwise the // hook gets silently removed. plus, we don't wanna incur latency anyway. static ulong __stdcall inhookthrmain(void *param) { - volatile u32 *sig = param; - if (!SetWindowsHookExW(WH_KEYBOARD_LL, &kproc, 0, 0) || - !SetWindowsHookExW(WH_MOUSE_LL, &mproc, 0, 0)) { - *sig = 2; + volatile int *sig = param; + if (!SetWindowsHookExW(WH_KEYBOARD_LL, (HOOKPROC)&kproc, 0, 0) || + !SetWindowsHookExW(WH_MOUSE_LL, (HOOKPROC)&mproc, 0, 0)) { + fastspin_raise(sig, 2); return -1; } - *sig = 1; + fastspin_raise(sig, 1); MSG m; int ret; while ((ret = GetMessageW(&m, inhookwin, 0, 0)) > 0) DispatchMessage(&m); return ret; } -static WNDPROC orig_wndproc; -static ssize __stdcall hook_wndproc(void *wnd, uint msg, UINT_PTR wp, ssize lp) { - if (msg == WM_COPYDATA && lockdown) return DefWindowProcW(wnd, msg, wp, lp); - return orig_wndproc(wnd, msg, wp, lp); +static ssize orig_wndproc; +static ssize __stdcall hook_wndproc(void *wnd, uint msg, usize wp, ssize lp) { + if (msg == WM_COPYDATA && enabled) return DefWindowProcW(wnd, msg, wp, lp); + return CallWindowProcA((WNDPROC)orig_wndproc, wnd, msg, wp, lp); } static bool win32_init(void) { - gamewin = FindWindowW(L"Valve001", 0); + // note: using A instead of W to avoid some weirdness with handles... + gamewin = FindWindowA("Valve001", 0); // note: error messages here are a bit cryptic on purpose, but easy to find // in the code. in other words, we're hiding in plain sight :-) if (!gamewin) { errmsg_errorsys("failed to find window"); return false; } - orig_wndproc = (WNDPROC)SetWindowLongPtrW(gamewin, GWLP_WNDPROC, - (ssize)hook_wndproc); - if (!orig_wndproc) { + orig_wndproc = SetWindowLongPtrA(gamewin, GWLP_WNDPROC, + (ssize)&hook_wndproc); + if (!orig_wndproc) { // XXX: assuming 0 won't be legitimately returned errmsg_errorsys("failed to attach message handler"); + return false; } return true; } static void win32_end(void) { // no error handling here because we'd crash either way. good luck! - SetWindowLongW(gamewin, GWLP_WNDPROC, (ssize)orig_wndproc); + SetWindowLongPtrA(gamewin, GWLP_WNDPROC, orig_wndproc); } -static void inhook_start(volatile u32 *sig) { +static void inhook_start(volatile int *sig) { inhookwin = CreateWindowW(L"sst-eventloop", L"sst-eventloop", WS_DISABLED, 0, 0, 0, 0, HWND_MESSAGE, 0, 0, 0); inhookthr = CreateThread(0, 0, &inhookthrmain, (u32 *)sig, 0, &inhooktid); @@ -127,7 +194,7 @@ static void inhook_check(void) { // won't matter in practice but... this kind of sucks. con_warn("** sst: ERROR in message loop, abandoning RTA mode! **"); // TODO(rta): stop demos, and stuff. - lockdown = false; + enabled = false; } } } @@ -138,12 +205,14 @@ static void inhook_stop(void) { errmsg_warnsys("couldn't wait for thread, status unknown"); // XXX: now what!? } - // assume WAIT_OBJECT_0 - ulong status; - GetExitCodeThread(inhookthr, &status); - if (status) { - // not much else we can do now! - con_warn("warning: RTA mode message loop had an error during shutdown"); + else { + // assume WAIT_OBJECT_0 + ulong status; + GetExitCodeThread(inhookthr, &status); + if (status) { + // not much else we can do now! + errmsg_errorx("message loop didn't shut down cleanly\n"); + } } CloseHandle(inhookthr); } @@ -155,22 +224,18 @@ static void inhook_stop(void) { #endif bool ac_enable(void) { - if (lockdown) return true; + if (enabled) return true; #ifdef _WIN32 - // and now for some frivolously microoptimised spinlocking nonsense - volatile u32 sig = 0; // paranoid volatile to ensure no loop misopt... + volatile int sig = 0; inhook_start(&sig); - register u32 x; // avoid double-reading the volatile - // pausing in the middle here seems to produce shorter asm with gcc -O2 and - // clang -O3 in godbolt (avoids unrolling of head which is unlikely to help) - while (x = sig, _mm_pause(), !x); - if (x == 2) { // else 1 for success + fastspin_wait(&sig); + if (sig == 2) { // else 1 for success con_warn("** sst: ERROR starting message loop, can't continue! **"); CloseHandle(inhookthr); return false; } #endif - lockdown = true; + enabled = true; return true; } @@ -178,16 +243,16 @@ HANDLE_EVENT(Tick, bool simulating) { #ifdef _WIN32 static uint fewticks = 0; // just check this every so often (roughly 0.1-0.3s depending on game) - if (lockdown && !(++fewticks & 7)) inhook_check(); + if (enabled && !(++fewticks & 7)) inhook_check(); #endif } void ac_disable(void) { - if (!lockdown) return; + if (!enabled) return; #ifdef _WIN32 inhook_stop(); #endif - lockdown = false; + enabled = false; } enum /* from InputEventType_t - terser names used here */ { @@ -216,15 +281,22 @@ typedef void (*VCALLCONV DispatchInputEvent_func)(void *, struct inputevent *); static DispatchInputEvent_func orig_DispatchInputEvent; static void VCALLCONV hook_DispatchInputEvent(void *this, struct inputevent *ev) { - // TODO(rta): do something here! (here's a quick reference/example) - //switch (ev->type) { - // CASES(BTNDOWN, BTNUP, BTNDOUBLECLICK): - // const char *desc[] = {"DOWN", "UP", "DOUBLE"}; - // const char *binding = bind_get(ev->data); - // if (!binding) binding = "[unbound]"; - // con_msg("key %d %s => %s\n", ev->data, desc[ev->type - BTNDOWN], - // binding); - //} + //const char *desc[] = {"DOWN", "UP", "DBL"}; + //const char desclen[] = {4, 2, 3}; + switch (ev->type) { + CASES(BTNDOWN, BTNUP, BTNDOUBLECLICK):; + // TODO(rta): do something interesting with button data + //uchar buf[28], *p = buf; + //msg_putasz4(p, 2); p += 1; + // msg_putssz5(p, 8); memcpy(p + 1, "KeyInput", 8); p += 9; + // msg_putmsz4(p, 2); p += 1; + // msg_putssz5(p, 3); memcpy(p + 1, "key", 3); p += 4; + // p += msg_puts32(p, ev->data); + // msg_putssz5(p, 3); memcpy(p + 1, "btn", 3); p += 4; + // int idx = ev->type - BTNDOWN; + // msg_putssz5(p++, desclen[idx]); + // memcpy(p, desc[idx], desclen[idx]); p += desclen[idx]; + } orig_DispatchInputEvent(this, ev); } @@ -242,7 +314,7 @@ static bool find_DispatchInputEvent(void) { return false; } void *cgame; - const uchar *insns = (const uchar*)VFUNC(gameuifuncs, GetDesktopResolution); + const uchar *insns = (const uchar *)VFUNC(gameuifuncs, GetDesktopResolution); for (const uchar *p = insns; p - insns < 16;) { if (p[0] == X86_MOVRMW && p[1] == X86_MODRM(0, 1, 5)) { void **indirect = mem_loadptr(p + 2); @@ -273,12 +345,27 @@ ok: insns = (const uchar *)VFUNC(cgame, DispatchAllStoredGameMessages); return false; } +HANDLE_EVENT(AllowPluginLoading, bool loading) { + if (enabled && demorec_demonum() != -1) { + con_warn("sst: plugins cannot be %s while recording a run\n", + loading ? "loaded" : "unloaded"); + return false; + } + return true; +} + +HANDLE_EVENT(PluginLoaded, void) { + // TODO(rta): do something with plugin list here +} +HANDLE_EVENT(PluginUnloaded, void) { + // TODO(rta): do something with plugin list here +} + +PREINIT { + return GAMETYPE_MATCHES(L4D); // TODO(compat): add more here obviously +} + INIT { -#if defined(_WIN32) - if (!win32_init()) return false; -#elif defined(__linux__) - // TODO(linux): call init things -#endif if (!find_DispatchInputEvent()) return false; orig_DispatchInputEvent = (DispatchInputEvent_func)hook_inline( (void *)orig_DispatchInputEvent, (void *)&hook_DispatchInputEvent); @@ -286,16 +373,70 @@ INIT { errmsg_errorsys("couldn't hook DispatchInputEvent function"); return false; } + +#ifdef _WIN32 + keybox = VirtualAlloc(0, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (!keybox) { + errmsg_errorsys("couldn't allocate memory for session state"); + return false; + } + if (!VirtualLock(keybox, 4096)) { + errmsg_errorsys("couldn't secure session state"); + goto e2; + } + if (WerRegisterExcludedMemoryBlock(keybox, 4096) != S_OK) { + // FIXME: stringify errors properly here + errmsg_errorx("couldn't secure session state"); + goto e2; + } + if (!win32_init()) goto e; +#else + keybox = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); + if (keybox == MAP_FAILED) { + errmsg_errorstd("couldn't allocate memory for session state"); + return false; + } + // linux-specific madvise stuff (there are some equivalents in OpenBSD and + // FreeBSD, if anyone's wondering, but we don't need to worry about those) + if (madvise(keybox, 4096, MADV_DONTFORK) == -1 || + madvise(keybox, 4096, MADV_DONTDUMP) == - 1 || + mlock(keybox, 4096) == -1) { + errormsg_errorstd("couldn't secure session state"); + goto e; + } + // TODO(linux): call other init things +#endif + + uchar seed[32]; + os_randombytes(seed, sizeof(seed)); + crypto_rng_init(&keybox->rng, seed); + if (GAMETYPE_MATCHES(L4D)) { + // copy into the keybox so key derivation blake2 gets a nice contiguous + // run of bytes + memcpy(keybox->lbpub, lbpubkeys[LBPK_L4D], 32); + } return true; + +#ifdef _WIN32 +e: WerUnregisterExcludedMemoryBlock(keybox); // this'd better not fail! +e2: VirtualFree(keybox, 4096, MEM_RELEASE); +#elif +e: munmap(keybox, 4096); +#endif + unhook_inline((void *)orig_DispatchInputEvent); + return false; } END { + ac_disable(); #if defined(_WIN32) + WerUnregisterExcludedMemoryBlock(keybox); // this'd better not fail! + VirtualFree(keybox, 4096, MEM_RELEASE); win32_end(); #elif defined(__linux__) - // TODO(linux): call cleanup things + munmap(keybox, 4096); + // TODO(linux): call other cleanup things #endif - ac_disable(); unhook_inline((void *)orig_DispatchInputEvent); } |