From a016f27652e46eb73aa8e90d1296aa769fe1b46b Mon Sep 17 00:00:00 2001 From: icex2 Date: Fri, 21 Aug 2020 15:04:13 +0200 Subject: [PATCH] hook: Update capnhook source files from latest master https://github.com/decafcode/capnhook --- src/main/hook/Module.mk | 4 + src/main/hook/args.c | 137 ++++++ src/main/hook/args.h | 6 + src/main/hook/com-proxy.c | 256 ++++++----- src/main/hook/com-proxy.h | 37 +- src/main/hook/hr.c | 33 ++ src/main/hook/hr.h | 10 + src/main/hook/iobuf.c | 343 ++++++++++++++ src/main/hook/iobuf.h | 40 ++ src/main/hook/iohook.c | 927 ++++++++++++++++++++++---------------- src/main/hook/iohook.h | 19 +- src/main/hook/pe.c | 252 ++++------- src/main/hook/pe.h | 21 +- src/main/hook/peb.c | 37 +- src/main/hook/peb.h | 5 +- src/main/hook/process.c | 196 ++++++++ src/main/hook/process.h | 9 + src/main/hook/table.c | 128 ++++-- src/main/hook/table.h | 17 +- 19 files changed, 1720 insertions(+), 757 deletions(-) create mode 100644 src/main/hook/args.c create mode 100644 src/main/hook/args.h create mode 100644 src/main/hook/hr.c create mode 100644 src/main/hook/hr.h create mode 100644 src/main/hook/iobuf.c create mode 100644 src/main/hook/iobuf.h create mode 100644 src/main/hook/process.c create mode 100644 src/main/hook/process.h diff --git a/src/main/hook/Module.mk b/src/main/hook/Module.mk index 0f9a3ff..c6c5ab0 100644 --- a/src/main/hook/Module.mk +++ b/src/main/hook/Module.mk @@ -1,10 +1,14 @@ libs += hook src_hook := \ + args.c \ com-proxy.c \ d3d9.c \ + hr.c \ + iobuf.c \ iohook.c \ pe.c \ peb.c \ + process.c \ table.c \ diff --git a/src/main/hook/args.c b/src/main/hook/args.c new file mode 100644 index 0000000..5211bd3 --- /dev/null +++ b/src/main/hook/args.c @@ -0,0 +1,137 @@ +#include + +#include +#include +#include +#include +#include + +#include "hook/args.h" + +/* This does not handle escaped double quotes inside args correctly yet */ + +static HRESULT args_push( + int *argc, + char ***argv, + const char *begin, + const char *end) +{ + int tmp_argc; + char **tmp_argv; + size_t nchars; + char *str; + + nchars = end - begin; + str = malloc(nchars + 1); + + if (str == NULL) { + goto fail; + } + + memcpy(str, begin, nchars); + str[nchars] = '\0'; + + tmp_argc = *argc + 1; + tmp_argv = realloc(*argv, tmp_argc * sizeof(char **)); + + if (tmp_argv == NULL) { + goto fail; + } + + tmp_argv[tmp_argc - 1] = str; + + *argv = tmp_argv; + *argc = tmp_argc; + + return S_OK; + +fail: + free(str); + + return E_OUTOFMEMORY; +} + +HRESULT args_recover(int *argc_out, char ***argv_out) +{ + int argc; + char **argv; + const char *begin; + const char *pos; + bool quote; + HRESULT hr; + + assert(argc_out != NULL); + assert(argv_out != NULL); + + *argc_out = 0; + *argv_out = NULL; + + argc = 0; + argv = NULL; + quote = false; + + for (begin = pos = GetCommandLine() ; *pos ; pos++) { + switch (*pos) { + case '"': + if (!quote) { + quote = true; + begin = pos + 1; + } else { + hr = args_push(&argc, &argv, begin, pos); + + if (FAILED(hr)) { + goto fail; + } + + quote = false; + begin = NULL; + } + + break; + + case ' ': + if (!quote && begin != NULL) { + args_push(&argc, &argv, begin, pos); + begin = NULL; + } + + break; + + default: + if (begin == NULL) { + begin = pos; + } + + break; + } + } + + if (begin != NULL && !quote) { + hr = args_push(&argc, &argv, begin, pos); + + if (FAILED(hr)) { + goto fail; + } + } + + *argc_out = argc; + *argv_out = argv; + + return S_OK; + +fail: + args_free(argc, argv); + + return hr; +} + +void args_free(int argc, char **argv) +{ + int i; + + for (i = 0 ; i < argc ; i++) { + free(argv[i]); + } + + free(argv); +} diff --git a/src/main/hook/args.h b/src/main/hook/args.h new file mode 100644 index 0000000..2f4d586 --- /dev/null +++ b/src/main/hook/args.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +HRESULT args_recover(int *argc, char ***argv); +void args_free(int argc, char **argv); diff --git a/src/main/hook/com-proxy.c b/src/main/hook/com-proxy.c index c67d123..b51084c 100644 --- a/src/main/hook/com-proxy.c +++ b/src/main/hook/com-proxy.c @@ -1,90 +1,177 @@ -#include #include +#include +#include +#include #include #include "hook/com-proxy.h" -#include "util/defs.h" -#include "util/mem.h" +static void com_proxy_free(struct com_proxy *proxy); +static HRESULT STDMETHODCALLTYPE com_proxy_query_interface( + IUnknown *unk, + REFIID iid, + void **iface); +static ULONG STDMETHODCALLTYPE com_proxy_addref(IUnknown *unk); +static ULONG STDMETHODCALLTYPE com_proxy_release(IUnknown *unk); -#ifdef _WIN64 +#ifdef __amd64 -/***** 64-BIT TRAMPOLINE *****/ + /***** 64-BIT TRAMPOLINE *****/ #define SLOT_OFFSET 0x0A static const uint8_t com_proxy_tramp[] = { - /* mov rcx, [rcx+8] ; Replace this with this->real */ - 0x48, - 0x8B, - 0x49, - 0x08, + /* mov rcx, [rcx+8] ; Get this->real */ + 0x48, 0x8B, 0x49, 0x08, - /* mov rax, [rcx] ; Get real->vtbl */ - 0x48, - 0x8B, - 0x01, + /* mov rax, [rcx] ; Get this->vtbl */ + 0x48, 0x8B, 0x01, /* mov rax, [rax+XX] ; Get vtbl->slot_XX */ - 0x48, - 0x8B, - 0x80, - -1, - -1, - -1, - -1, + 0x48, 0x8B, 0x80, -1, -1, -1, -1, /* jmp rax ; Continue to slot_XX */ - 0xFF, - 0xE0, + 0xFF, 0xE0, }; #else -/***** 32-BIT TRAMPOLINE *****/ + /***** 32-BIT TRAMPOLINE *****/ #define SLOT_OFFSET 0x0F static const uint8_t com_proxy_tramp[] = { /* mov eax, [esp+4] ; Get this */ - 0x8B, - 0x44, - 0x24, - 0x04, + 0x8B, 0x44, 0x24, 0x04, /* mov eax, [eax+4] ; Get this->real */ - 0x8B, - 0x40, - 0x04, + 0x8B, 0x40, 0x04, /* mov [esp+4], eax ; Replace this with this->real on stack */ - 0x89, - 0x44, - 0x24, - 0x04, + 0x89, 0x44, 0x24, 0x04, - /* mov ecx, [eax] ; Get real->vtbl */ - 0x8B, - 0x08, + /* mov ecx, [eax] ; Get this->vtbl */ + 0x8B, 0x08, /* mov ecx, [ecx+XX] ; Get vtbl->slot_XX */ - 0x8B, - 0x89, - -1, - -1, - -1, - -1, + 0x8B, 0x89, -1, -1, -1, -1, /* jmp ecx ; Continue to slot_XX */ - 0xFF, - 0xE1}; + 0xFF, 0xE1 +}; #endif -static HRESULT STDCALL -com_proxy_query_interface(IUnknown *ptr, REFIID iid, void **iface) +HRESULT com_proxy_wrap( + struct com_proxy **out, + void *real, + size_t vtbl_size) { - struct com_proxy *self = (struct com_proxy *) ptr; - IUnknown *obj = self->real; /* Not necessarily the real IUnknown* */ + struct com_proxy *proxy; + void **vtbl; + uint8_t *cur_tramp; + size_t nslots; + size_t i; + HRESULT hr; + + assert(out != NULL); + assert(real != NULL); + + *out = NULL; + + proxy = calloc(1, sizeof(*proxy)); + + if (proxy == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + proxy->vptr = malloc(vtbl_size); + + if (proxy->vptr == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + nslots = vtbl_size / sizeof(void *); + + proxy->tramps = VirtualAlloc( + NULL, + sizeof(com_proxy_tramp) * nslots, + MEM_RESERVE | MEM_COMMIT, + PAGE_EXECUTE_READWRITE); + + if (proxy->tramps == NULL) { + hr = E_OUTOFMEMORY; + + goto end; + } + + proxy->real = real; + + /* Set up proxied IUnknown impl */ + + vtbl = proxy->vptr; + vtbl[0] = com_proxy_query_interface; + vtbl[1] = com_proxy_addref; + vtbl[2] = com_proxy_release; + + /* Populate trampoline code for remaining vtbl entries */ + + for (i = 3 /* Skip IUnknown */ ; i < nslots ; i++) { + cur_tramp = proxy->tramps + i * sizeof(com_proxy_tramp); + + /* Copy template */ + memcpy(cur_tramp, com_proxy_tramp, sizeof(com_proxy_tramp)); + + /* Patch XX into vtbl lookup (see definition of tramp) */ + *((uint32_t *) (cur_tramp + SLOT_OFFSET)) = i * sizeof(void *); + + /* Set vtable entry */ + vtbl[i] = cur_tramp; + } + + *out = proxy; + proxy = NULL; + hr = S_OK; + +end: + com_proxy_free(proxy); + + return hr; +} + +static void com_proxy_free(struct com_proxy *proxy) +{ + if (proxy == NULL) { + return; + } + + if (proxy->cleanup_ctx != NULL) { + proxy->cleanup_ctx(proxy->ctx); + } + + if (proxy->tramps != NULL) { + VirtualFree(proxy->tramps, 0, MEM_RELEASE); + } + + free(proxy->vptr); + free(proxy); +} + +static HRESULT STDMETHODCALLTYPE com_proxy_query_interface( + IUnknown *unk, + REFIID iid, + void **iface) +{ + struct com_proxy *proxy; + IUnknown *obj; + + assert(unk != NULL); + + proxy = (struct com_proxy *) unk; + obj = proxy->real; /* Not necessarily this object's canonical IUnknown */ /* To some extent, COM is designed to support shennanigans like these. We can safely pass the call straight through to the underlying @@ -104,70 +191,35 @@ com_proxy_query_interface(IUnknown *ptr, REFIID iid, void **iface) return IUnknown_QueryInterface(obj, iid, iface); } -static ULONG STDCALL com_proxy_addref(IUnknown *ptr) +static ULONG STDMETHODCALLTYPE com_proxy_addref(IUnknown *unk) { - struct com_proxy *self = (struct com_proxy *) ptr; - IUnknown *obj = self->real; + struct com_proxy *proxy; + IUnknown *obj; + + assert(unk != NULL); + + proxy = (struct com_proxy *) unk; + obj = proxy->real; return IUnknown_AddRef(obj); } -static ULONG STDCALL com_proxy_release(IUnknown *ptr) +static ULONG STDMETHODCALLTYPE com_proxy_release(IUnknown *unk) { - struct com_proxy *self = (struct com_proxy *) ptr; - IUnknown *obj = self->real; + struct com_proxy *proxy; + IUnknown *real; ULONG result; - result = IUnknown_Release(obj); + assert(unk != NULL); + + proxy = (struct com_proxy *) unk; + real = proxy->real; + result = IUnknown_Release(real); if (!result) { /* Last ref to underlying object released */ - VirtualFree(self->tramps, 0, MEM_RELEASE); - free(self->vptr); - free(self); + com_proxy_free(proxy); } return result; } - -struct com_proxy *com_proxy_wrap(void *iface, size_t vtbl_size) -{ - struct com_proxy *self; - void **vtbl; - uint8_t *cur_tramp; - uint32_t nslots; - uint32_t i; - - nslots = vtbl_size / sizeof(void *); - - self = xmalloc(sizeof(*self)); - self->vptr = xmalloc(vtbl_size); - self->real = iface; - self->tramps = VirtualAlloc( - NULL, - sizeof(com_proxy_tramp) * nslots, - MEM_RESERVE | MEM_COMMIT, - PAGE_EXECUTE_READWRITE); - - /* Set up proxied IUnknown impl */ - vtbl = self->vptr; - vtbl[0] = com_proxy_query_interface; - vtbl[1] = com_proxy_addref; - vtbl[2] = com_proxy_release; - - /* Populate trampoline code for remaining vtbl entries */ - for (i = 3 /* Skip IUnknown */; i < nslots; i++) { - cur_tramp = self->tramps + i * sizeof(com_proxy_tramp); - - /* Copy template */ - memcpy(cur_tramp, com_proxy_tramp, sizeof(com_proxy_tramp)); - - /* Patch XX into vtbl lookup (see definition of tramp) */ - *((uint32_t *) (cur_tramp + SLOT_OFFSET)) = i * sizeof(void *); - - /* Set vtable entry */ - vtbl[i] = cur_tramp; - } - - return self; -} diff --git a/src/main/hook/com-proxy.h b/src/main/hook/com-proxy.h index 49c0a74..b678b17 100644 --- a/src/main/hook/com-proxy.h +++ b/src/main/hook/com-proxy.h @@ -1,14 +1,9 @@ -#ifndef HOOK_COM_PROXY_H -#define HOOK_COM_PROXY_H +#pragma once #include #include -/* N.B. Here be dragons. You really ought to be fairly familiar with COM - before using this, otherwise you risk ignoring the subtler issues at your - peril. */ - -#define COM_PROXY_UNWRAP(self) (((struct com_proxy *) self)->real) +#define com_proxy_downcast(self) ((struct com_proxy *) self) struct com_proxy { /* Pointer to vtable filled with trampolines. Edit these as you please. @@ -18,13 +13,20 @@ struct com_proxy { /* Interface pointer wrapped by this proxy. */ void *real; + /* Context pointer for use by hook code. */ + void *ctx; + + /* Optional cleanup callback, will be called during com_proxy deallocation + to clean up *ctx. */ + void (*cleanup_ctx)(void *ctx); + /* Dynamically generated x86 trampoline code. The initial vtable entries all point into code located here. */ uint8_t *tramps; }; /* Wrap a COM interface pointer in a proxy. This is an object that acts just - like the object that it wraps, but has a freely editable vtable, which you + like the object that it wraps but has a freely editable vtable, which you can modify in order to intercept a subset of the interface's method calls. By default, all the vtable slots contain dynamically generated trampolines @@ -34,9 +36,8 @@ struct com_proxy { NOTE! This does not AddRef the underlying interface. NOTE! This function wraps COM POINTERS, not COM OBJECTS (since the latter - is, in general, impossible). If you're insufficiently versed in COM to - understand the difference... well, you really should be, but the following - observations are a start: + is, in general, impossible). Consequences of this distinction include the + following: 1. Do not wrap IUnknown pointers with this function. This will break the IUnknown::QueryInterface contract. This refers to _the_ unique @@ -44,11 +45,11 @@ struct com_proxy { extends IUnknown). Wrapping the unique IUnknown* for an object will cause it to no longer be unique. - 2. Callers can "jailbreak" your wrapper using IUnknown::QueryInterface. + 2. Callers can "jailbreak" your wrapper using IUnknown::QueryInterface + unless you provide a custom QueryInterface implementation to prevent them + from doing so. */ - Generally this isn't an issue for DirectX objects, since nobody ever seems - to use QueryInterface with them. */ - -struct com_proxy *com_proxy_wrap(void *iface, size_t vtbl_size); - -#endif +HRESULT com_proxy_wrap( + struct com_proxy **out, + void *real, + size_t vtbl_size); diff --git a/src/main/hook/hr.c b/src/main/hook/hr.c new file mode 100644 index 0000000..02a8642 --- /dev/null +++ b/src/main/hook/hr.c @@ -0,0 +1,33 @@ +#include + +#include + +#include "hook/hr.h" + +uint32_t hr_to_win32_error(HRESULT hr) +{ + if (SUCCEEDED(hr)) { + return ERROR_SUCCESS; + } else if (HRESULT_FACILITY(hr) == FACILITY_WIN32) { + return HRESULT_CODE(hr); + } else { + switch (hr) { + case E_ABORT: return ERROR_OPERATION_ABORTED; + case E_ACCESSDENIED: return ERROR_ACCESS_DENIED; + case E_FAIL: return ERROR_GEN_FAILURE; + case E_HANDLE: return ERROR_INVALID_HANDLE; + case E_INVALIDARG: return ERROR_INVALID_PARAMETER; + case E_NOINTERFACE: return ERROR_INVALID_FUNCTION; + case E_NOTIMPL: return ERROR_NOT_SUPPORTED; + case E_OUTOFMEMORY: return ERROR_OUTOFMEMORY; + case E_POINTER: return ERROR_INVALID_ADDRESS; + case E_UNEXPECTED: return ERROR_INTERNAL_ERROR; + default: return ERROR_INTERNAL_ERROR; + } + } +} + +void hr_propagate_win32_(HRESULT hr) +{ + SetLastError(hr_to_win32_error(hr)); +} diff --git a/src/main/hook/hr.h b/src/main/hook/hr.h new file mode 100644 index 0000000..29615cc --- /dev/null +++ b/src/main/hook/hr.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include + +#define hr_propagate_win32(hr, r) (hr_propagate_win32_(hr), r) + +uint32_t hr_to_win32_error(HRESULT hr); +void hr_propagate_win32_(HRESULT hr); diff --git a/src/main/hook/iobuf.c b/src/main/hook/iobuf.c new file mode 100644 index 0000000..d100274 --- /dev/null +++ b/src/main/hook/iobuf.c @@ -0,0 +1,343 @@ +#include +#include + +#include "hook/iobuf.h" + +void iobuf_flip(struct const_iobuf *child, struct iobuf *parent) +{ + assert(child != NULL); + assert(parent != NULL); + + child->bytes = parent->bytes; + child->pos = 0; + child->nbytes = parent->pos; +} + +size_t iobuf_move(struct iobuf *dest, struct const_iobuf *src) +{ + size_t dest_avail; + size_t src_avail; + size_t chunksz; + + assert(dest != NULL); + assert(dest->bytes != NULL || dest->nbytes == 0); + assert(dest->pos <= dest->nbytes); + + assert(src != NULL); + assert(src->bytes != NULL || src->nbytes == 0); + assert(src->pos <= src->nbytes); + + dest_avail = dest->nbytes - dest->pos; + src_avail = src->nbytes - src->pos; + chunksz = dest_avail < src_avail ? dest_avail : src_avail; + + memcpy(&dest->bytes[dest->pos], &src->bytes[src->pos], chunksz); + + dest->pos += chunksz; + src->pos += chunksz; + + return chunksz; +} + +size_t iobuf_shift(struct iobuf *dest, struct iobuf *src) +{ + struct const_iobuf span; + + assert(dest != NULL); + assert(src != NULL); + + iobuf_flip(&span, src); + iobuf_move(dest, &span); + + memmove(src->bytes, &src->bytes[span.pos], span.nbytes - span.pos); + src->pos -= span.pos; + + return span.pos; +} + +HRESULT iobuf_read(struct const_iobuf *src, void *bytes, size_t nbytes) +{ + assert(src != NULL); + assert(bytes != NULL || nbytes == 0); + + if (src->pos + nbytes > src->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + memcpy(bytes, &src->bytes[src->pos], nbytes); + src->pos += nbytes; + + return S_OK; +} + +HRESULT iobuf_read_8(struct const_iobuf *src, uint8_t *out) +{ + assert(src != NULL); + assert(out != NULL); + + if (src->pos + sizeof(uint8_t) > src->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + *out = src->bytes[src->pos++]; + + return S_OK; +} + +HRESULT iobuf_read_be16(struct const_iobuf *src, uint16_t *out) +{ + uint16_t value; + + assert(src != NULL); + assert(out != NULL); + + if (src->pos + sizeof(uint16_t) > src->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + value = src->bytes[src->pos++] << 8; + value |= src->bytes[src->pos++]; + + *out = value; + + return S_OK; +} + +HRESULT iobuf_read_be32(struct const_iobuf *src, uint32_t *out) +{ + uint32_t value; + + assert(src != NULL); + assert(out != NULL); + + if (src->pos + sizeof(uint32_t) > src->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + value = src->bytes[src->pos++] << 24; + value |= src->bytes[src->pos++] << 16; + value |= src->bytes[src->pos++] << 8; + value |= src->bytes[src->pos++]; + + *out = value; + + return S_OK; +} + +HRESULT iobuf_read_be64(struct const_iobuf *src, uint64_t *out) +{ + uint64_t value; + + assert(src != NULL); + assert(out != NULL); + + if (src->pos + sizeof(uint64_t) > src->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + value = ((uint64_t) src->bytes[src->pos++]) << 56; + value |= ((uint64_t) src->bytes[src->pos++]) << 48; + value |= ((uint64_t) src->bytes[src->pos++]) << 40; + value |= ((uint64_t) src->bytes[src->pos++]) << 32; + value |= ((uint64_t) src->bytes[src->pos++]) << 24; + value |= ((uint64_t) src->bytes[src->pos++]) << 16; + value |= ((uint64_t) src->bytes[src->pos++]) << 8; + value |= ((uint64_t) src->bytes[src->pos++]); + + *out = value; + + return S_OK; +} + +HRESULT iobuf_read_le16(struct const_iobuf *src, uint16_t *out) +{ + uint16_t value; + + assert(src != NULL); + assert(out != NULL); + + if (src->pos + sizeof(uint16_t) > src->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + value = src->bytes[src->pos++]; + value |= src->bytes[src->pos++] << 8; + + *out = value; + + return S_OK; +} + +HRESULT iobuf_read_le32(struct const_iobuf *src, uint32_t *out) +{ + uint32_t value; + + assert(src != NULL); + assert(out != NULL); + + if (src->pos + sizeof(uint32_t) > src->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + value = src->bytes[src->pos++]; + value |= src->bytes[src->pos++] << 8; + value |= src->bytes[src->pos++] << 16; + value |= src->bytes[src->pos++] << 24; + + *out = value; + + return S_OK; +} + +HRESULT iobuf_read_le64(struct const_iobuf *src, uint64_t *out) +{ + uint64_t value; + + assert(src != NULL); + assert(out != NULL); + + if (src->pos + sizeof(uint64_t) > src->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + value = ((uint64_t) src->bytes[src->pos++]); + value |= ((uint64_t) src->bytes[src->pos++]) << 8; + value |= ((uint64_t) src->bytes[src->pos++]) << 16; + value |= ((uint64_t) src->bytes[src->pos++]) << 24; + value |= ((uint64_t) src->bytes[src->pos++]) << 32; + value |= ((uint64_t) src->bytes[src->pos++]) << 40; + value |= ((uint64_t) src->bytes[src->pos++]) << 48; + value |= ((uint64_t) src->bytes[src->pos++]) << 56; + + *out = value; + + return S_OK; +} + +HRESULT iobuf_write(struct iobuf *dest, const void *bytes, size_t nbytes) +{ + assert(dest != NULL); + assert(bytes != NULL || nbytes == 0); + + if (dest->pos + nbytes > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + memcpy(&dest->bytes[dest->pos], bytes, nbytes); + dest->pos += nbytes; + + return S_OK; +} + +HRESULT iobuf_write_8(struct iobuf *dest, uint8_t value) +{ + assert(dest != NULL); + + if (dest->pos + sizeof(uint8_t) > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = value; + + return S_OK; +} + +HRESULT iobuf_write_be16(struct iobuf *dest, uint16_t value) +{ + assert(dest != NULL); + + if (dest->pos + sizeof(uint16_t) > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = value >> 8; + dest->bytes[dest->pos++] = value; + + return S_OK; +} + +HRESULT iobuf_write_be32(struct iobuf *dest, uint32_t value) +{ + assert(dest != NULL); + + if (dest->pos + sizeof(uint32_t) > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = value >> 24; + dest->bytes[dest->pos++] = value >> 16; + dest->bytes[dest->pos++] = value >> 8; + dest->bytes[dest->pos++] = value; + + return S_OK; +} + +HRESULT iobuf_write_be64(struct iobuf *dest, uint64_t value) +{ + assert(dest != NULL); + + if (dest->pos + sizeof(uint64_t) > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = value >> 56; + dest->bytes[dest->pos++] = value >> 48; + dest->bytes[dest->pos++] = value >> 40; + dest->bytes[dest->pos++] = value >> 32; + dest->bytes[dest->pos++] = value >> 24; + dest->bytes[dest->pos++] = value >> 16; + dest->bytes[dest->pos++] = value >> 8; + dest->bytes[dest->pos++] = value; + + return S_OK; +} + +HRESULT iobuf_write_le16(struct iobuf *dest, uint16_t value) +{ + assert(dest != NULL); + + if (dest->pos + sizeof(uint16_t) > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = value; + dest->bytes[dest->pos++] = value >> 8; + + return S_OK; +} + +HRESULT iobuf_write_le32(struct iobuf *dest, uint32_t value) +{ + assert(dest != NULL); + + if (dest->pos + sizeof(uint32_t) > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = value; + dest->bytes[dest->pos++] = value >> 8; + dest->bytes[dest->pos++] = value >> 16; + dest->bytes[dest->pos++] = value >> 24; + + return S_OK; +} + +HRESULT iobuf_write_le64(struct iobuf *dest, uint64_t value) +{ + assert(dest != NULL); + + if (dest->pos + sizeof(uint64_t) > dest->nbytes) { + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + + dest->bytes[dest->pos++] = value; + dest->bytes[dest->pos++] = value >> 8; + dest->bytes[dest->pos++] = value >> 16; + dest->bytes[dest->pos++] = value >> 24; + dest->bytes[dest->pos++] = value >> 32; + dest->bytes[dest->pos++] = value >> 40; + dest->bytes[dest->pos++] = value >> 48; + dest->bytes[dest->pos++] = value >> 56; + + return S_OK; +} diff --git a/src/main/hook/iobuf.h b/src/main/hook/iobuf.h new file mode 100644 index 0000000..02b726c --- /dev/null +++ b/src/main/hook/iobuf.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include +#include + +struct iobuf { + uint8_t *bytes; + size_t nbytes; + size_t pos; +}; + +struct const_iobuf { + const uint8_t *bytes; + size_t nbytes; + size_t pos; +}; + +void iobuf_flip(struct const_iobuf *child, struct iobuf *parent); +size_t iobuf_move(struct iobuf *dest, struct const_iobuf *src); +size_t iobuf_shift(struct iobuf *dest, struct iobuf *src); + +HRESULT iobuf_read(struct const_iobuf *src, void *bytes, size_t nbytes); +HRESULT iobuf_read_8(struct const_iobuf *src, uint8_t *value); +HRESULT iobuf_read_be16(struct const_iobuf *src, uint16_t *value); +HRESULT iobuf_read_be32(struct const_iobuf *src, uint32_t *value); +HRESULT iobuf_read_be64(struct const_iobuf *src, uint64_t *value); +HRESULT iobuf_read_le16(struct const_iobuf *src, uint16_t *value); +HRESULT iobuf_read_le32(struct const_iobuf *src, uint32_t *value); +HRESULT iobuf_read_le64(struct const_iobuf *src, uint64_t *value); + +HRESULT iobuf_write(struct iobuf *dest, const void *bytes, size_t nbytes); +HRESULT iobuf_write_8(struct iobuf *dest, uint8_t value); +HRESULT iobuf_write_be16(struct iobuf *dest, uint16_t value); +HRESULT iobuf_write_be32(struct iobuf *dest, uint32_t value); +HRESULT iobuf_write_be64(struct iobuf *dest, uint64_t value); +HRESULT iobuf_write_le16(struct iobuf *dest, uint16_t value); +HRESULT iobuf_write_le32(struct iobuf *dest, uint32_t value); +HRESULT iobuf_write_le64(struct iobuf *dest, uint64_t value); diff --git a/src/main/hook/iohook.c b/src/main/hook/iohook.c index 9a76e34..f28f52d 100644 --- a/src/main/hook/iohook.c +++ b/src/main/hook/iohook.c @@ -1,234 +1,358 @@ -#define LOG_MODULE "iohook" - +#define WIN32_NO_STATUS +/* See precompiled.h for more information */ #include +#undef WIN32_NO_STATUS +#include +#include +#include +#include + +#include +#include +#include #include #include +#include "hook/hr.h" #include "hook/iohook.h" #include "hook/table.h" -#include "util/hr.h" -#include "util/log.h" -#include "util/str.h" - /* Helpers */ -static BOOL -iohook_overlapped_result(uint32_t *syncout, OVERLAPPED *ovl, uint32_t value); +static void iohook_init(void); +static BOOL iohook_overlapped_result( + uint32_t *syncout, + OVERLAPPED *ovl, + uint32_t value); -static HRESULT irp_invoke_real(struct irp *irp); -static HRESULT irp_invoke_real_open(struct irp *irp); -static HRESULT irp_invoke_real_close(struct irp *irp); -static HRESULT irp_invoke_real_read(struct irp *irp); -static HRESULT irp_invoke_real_write(struct irp *irp); -static HRESULT irp_invoke_real_seek(struct irp *irp); -static HRESULT irp_invoke_real_fsync(struct irp *irp); -static HRESULT irp_invoke_real_ioctl(struct irp *irp); +static HRESULT iohook_invoke_real(struct irp *irp); +static HRESULT iohook_invoke_real_open(struct irp *irp); +static HRESULT iohook_invoke_real_close(struct irp *irp); +static HRESULT iohook_invoke_real_read(struct irp *irp); +static HRESULT iohook_invoke_real_write(struct irp *irp); +static HRESULT iohook_invoke_real_seek(struct irp *irp); +static HRESULT iohook_invoke_real_fsync(struct irp *irp); +static HRESULT iohook_invoke_real_ioctl(struct irp *irp); -/* API hooks */ +/* API hooks. We take some liberties with function signatures here (e.g. + stdint.h types instead of DWORD and LARGE_INTEGER et al). */ -static BOOL STDCALL my_CloseHandle(HANDLE fd); +static BOOL WINAPI iohook_CloseHandle(HANDLE fd); -static HANDLE STDCALL my_CreateFileW( - const wchar_t *lpFileName, - uint32_t dwDesiredAccess, - uint32_t dwShareMode, - SECURITY_ATTRIBUTES *lpSecurityAttributes, - uint32_t dwCreationDisposition, - uint32_t dwFlagsAndAttributes, - HANDLE hTemplateFile); +static HANDLE WINAPI iohook_CreateFileW( + const wchar_t *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); -static HANDLE STDCALL my_CreateFileA( - const char *lpFileName, - uint32_t dwDesiredAccess, - uint32_t dwShareMode, - SECURITY_ATTRIBUTES *lpSecurityAttributes, - uint32_t dwCreationDisposition, - uint32_t dwFlagsAndAttributes, - HANDLE hTemplateFile); +static HANDLE WINAPI iohook_CreateFileA( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); -static BOOL STDCALL my_ReadFile( - HANDLE hFile, - void *lpBuffer, - uint32_t nNumberOfBytesToRead, - uint32_t *lpNumberOfBytesRead, - OVERLAPPED *lpOverlapped); +static BOOL WINAPI iohook_ReadFile( + HANDLE hFile, + void *lpBuffer, + uint32_t nNumberOfBytesToRead, + uint32_t *lpNumberOfBytesRead, + OVERLAPPED *lpOverlapped); -static BOOL STDCALL my_WriteFile( - HANDLE hFile, - const void *lpBuffer, - uint32_t nNumberOfBytesToWrite, - uint32_t *lpNumberOfBytesWritten, - OVERLAPPED *lpOverlapped); +static BOOL WINAPI iohook_WriteFile( + HANDLE hFile, + const void *lpBuffer, + uint32_t nNumberOfBytesToWrite, + uint32_t *lpNumberOfBytesWritten, + OVERLAPPED *lpOverlapped); -static DWORD STDCALL my_SetFilePointer( - HANDLE hFile, - int32_t lDistanceToMove, - int32_t *lpDistanceToMoveHigh, - uint32_t dwMoveMethod); +static DWORD WINAPI iohook_SetFilePointer( + HANDLE hFile, + int32_t lDistanceToMove, + int32_t *lpDistanceToMoveHigh, + uint32_t dwMoveMethod); -static BOOL STDCALL my_FlushFileBuffers(HANDLE hFile); +static BOOL WINAPI iohook_SetFilePointerEx( + HANDLE hFile, + int64_t liDistanceToMove, + uint64_t *lpNewFilePointer, + uint32_t dwMoveMethod); -static BOOL STDCALL my_DeviceIoControl( - HANDLE hFile, - uint32_t dwIoControlCode, - void *lpInBuffer, - uint32_t nInBufferSize, - void *lpOutBuffer, - uint32_t nOutBufferSize, - uint32_t *lpBytesReturned, - OVERLAPPED *lpOverlapped); +static BOOL WINAPI iohook_FlushFileBuffers(HANDLE hFile); + +static BOOL WINAPI iohook_DeviceIoControl( + HANDLE hFile, + uint32_t dwIoControlCode, + void *lpInBuffer, + uint32_t nInBufferSize, + void *lpOutBuffer, + uint32_t nOutBufferSize, + uint32_t *lpBytesReturned, + OVERLAPPED *lpOverlapped); /* Links */ -static BOOL(STDCALL *real_CloseHandle)(HANDLE fd); +static BOOL (WINAPI *next_CloseHandle)(HANDLE fd); -static HANDLE(STDCALL *real_CreateFileW)( - const wchar_t *filename, - uint32_t access, - uint32_t share, - SECURITY_ATTRIBUTES *sa, - uint32_t creation, - uint32_t flags, - HANDLE tmpl); +static HANDLE (WINAPI *next_CreateFileA)( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile); -static BOOL(STDCALL *real_DeviceIoControl)( - HANDLE fd, - uint32_t code, - void *in_bytes, - uint32_t in_nbytes, - void *out_bytes, - uint32_t out_nbytes, - uint32_t *out_returned, - OVERLAPPED *ovl); +static HANDLE (WINAPI *next_CreateFileW)( + const wchar_t *filename, + uint32_t access, + uint32_t share, + SECURITY_ATTRIBUTES *sa, + uint32_t creation, + uint32_t flags, + HANDLE tmpl); -static BOOL(STDCALL *real_ReadFile)( - HANDLE fd, void *buf, uint32_t nbytes, uint32_t *nread, OVERLAPPED *ovl); +static BOOL (WINAPI *next_DeviceIoControl)( + HANDLE fd, + uint32_t code, + void *in_bytes, + uint32_t in_nbytes, + void *out_bytes, + uint32_t out_nbytes, + uint32_t *out_returned, + OVERLAPPED *ovl); -static BOOL(STDCALL *real_WriteFile)( - HANDLE fd, - const void *buf, - uint32_t nbytes, - uint32_t *nwrit, - OVERLAPPED *ovl); +static BOOL (WINAPI *next_ReadFile)( + HANDLE fd, + void *buf, + uint32_t nbytes, + uint32_t *nread, + OVERLAPPED *ovl); -static DWORD(STDCALL *real_SetFilePointer)( - HANDLE hFile, - int32_t lDistanceToMove, - int32_t *lpDistanceToMoveHigh, - uint32_t dwMoveMethod); +static BOOL (WINAPI *next_WriteFile)( + HANDLE fd, + const void *buf, + uint32_t nbytes, + uint32_t *nwrit, + OVERLAPPED *ovl); -static BOOL(STDCALL *real_FlushFileBuffers)(HANDLE fd); +static DWORD (WINAPI *next_SetFilePointer)( + HANDLE hFile, + int32_t lDistanceToMove, + int32_t *lpDistanceToMoveHigh, + uint32_t dwMoveMethod); -/* Hook table */ +static BOOL (WINAPI *next_SetFilePointerEx)( + HANDLE hFile, + int64_t liDistanceToMove, + uint64_t *lpNewFilePointer, + uint32_t dwMoveMethod); -static struct hook_symbol iohook_kernel32_syms[] = { - /* Basic IO */ +static BOOL (WINAPI *next_FlushFileBuffers)(HANDLE fd); +/* Hook symbol table */ + +static const struct hook_symbol iohook_kernel32_syms[] = { { - .name = "CloseHandle", - .patch = my_CloseHandle, - .link = (void *) &real_CloseHandle, - }, - { - .name = "CreateFileA", - .patch = my_CreateFileA, - }, - { - .name = "CreateFileW", - .patch = my_CreateFileW, - .link = (void *) &real_CreateFileW, - }, - { - .name = "DeviceIoControl", - .patch = my_DeviceIoControl, - .link = (void *) &real_DeviceIoControl, - }, - { - .name = "ReadFile", - .patch = my_ReadFile, - .link = (void *) &real_ReadFile, - }, - { - .name = "WriteFile", - .patch = my_WriteFile, - .link = (void *) &real_WriteFile, - }, - { - .name = "SetFilePointer", - .patch = my_SetFilePointer, - .link = (void *) &real_SetFilePointer, - }, - { - .name = "FlushFileBuffers", - .patch = my_FlushFileBuffers, - .link = (void *) &real_FlushFileBuffers, + .name = "CloseHandle", + .patch = iohook_CloseHandle, + .link = (void *) &next_CloseHandle, + }, { + .name = "CreateFileA", + .patch = iohook_CreateFileA, + .link = (void *) &next_CreateFileA, + }, { + .name = "CreateFileW", + .patch = iohook_CreateFileW, + .link = (void *) &next_CreateFileW, + }, { + .name = "DeviceIoControl", + .patch = iohook_DeviceIoControl, + .link = (void *) &next_DeviceIoControl, + }, { + .name = "ReadFile", + .patch = iohook_ReadFile, + .link = (void *) &next_ReadFile, + }, { + .name = "WriteFile", + .patch = iohook_WriteFile, + .link = (void *) &next_WriteFile, + }, { + .name = "SetFilePointer", + .patch = iohook_SetFilePointer, + .link = (void *) &next_SetFilePointer, + }, { + .name = "SetFilePointerEx", + .patch = iohook_SetFilePointerEx, + .link = (void *) &next_SetFilePointerEx, + }, { + .name = "FlushFileBuffers", + .patch = iohook_FlushFileBuffers, + .link = (void *) &next_FlushFileBuffers, }, }; -static const irp_handler_t irp_real_handlers[] = { - [IRP_OP_OPEN] = irp_invoke_real_open, - [IRP_OP_CLOSE] = irp_invoke_real_close, - [IRP_OP_READ] = irp_invoke_real_read, - [IRP_OP_WRITE] = irp_invoke_real_write, - [IRP_OP_SEEK] = irp_invoke_real_seek, - [IRP_OP_FSYNC] = irp_invoke_real_fsync, - [IRP_OP_IOCTL] = irp_invoke_real_ioctl, +static const iohook_fn_t iohook_real_handlers[] = { + [IRP_OP_OPEN] = iohook_invoke_real_open, + [IRP_OP_CLOSE] = iohook_invoke_real_close, + [IRP_OP_READ] = iohook_invoke_real_read, + [IRP_OP_WRITE] = iohook_invoke_real_write, + [IRP_OP_SEEK] = iohook_invoke_real_seek, + [IRP_OP_FSYNC] = iohook_invoke_real_fsync, + [IRP_OP_IOCTL] = iohook_invoke_real_ioctl, }; -static const irp_handler_t *iohook_handlers; +static bool iohook_initted; +static CRITICAL_SECTION iohook_lock; +static iohook_fn_t *iohook_handlers; static size_t iohook_nhandlers; -void iohook_init(const irp_handler_t *handlers, size_t nhandlers) +static void iohook_init(void) { - log_assert(handlers != NULL); - log_assert(iohook_handlers == NULL); + HMODULE kernel32; - iohook_handlers = handlers; - iohook_nhandlers = nhandlers; + /* Permit repeated initializations. This isn't atomic because the whole IAT + insertion dance is extremely non-atomic to begin with. */ - hook_table_apply( - NULL, - "kernel32.dll", - iohook_kernel32_syms, - lengthof(iohook_kernel32_syms)); - - if (real_CreateFileW == NULL) { - /* my_CreateFileA requires this to be present */ - real_CreateFileW = (void *) GetProcAddress( - GetModuleHandleA("kernel32.dll"), "CreateFileW"); + if (iohook_initted) { + return; } - log_info("IO Hook subsystem initialized"); + /* Splice iohook into IAT entries referencing Win32 I/O APIs */ + + hook_table_apply( + NULL, + "kernel32.dll", + iohook_kernel32_syms, + _countof(iohook_kernel32_syms)); + + /* Here be dragons: + + The hook table mechanism will populate our next_ function pointers with + the prior values of these IAT entries, and we invoke these next_ + function pointers to propagate IO requests onwards to the OS (or to + whoever hooked everybody's IATs before us). We don't statically import + these functions using the compiler because those static imports would + also be realized as IAT entries somewhere, so we'd overwrite our own + imports for the Win32 file I/O APIs and send ourselves into an infinite + recursion. + + That being said, not all pass-through operations necessarily end up + leaving through the same function that they entered through, owing to + the existence of multiple and possibly-obsolete ways of doing various + I/O things through the Win32 API. For instance, a call to CreateFileA + intercepted by iohook will emerge as a call to CreateFileW. Similarly, + a call to the clunky SetFilePointer API will emerge as a call to the + safer and simpler SetFilePointerEx. This presents a problem if the + desired API exit path isn't actually imported through an IAT that we + touch with hook_table_apply. In this edge case, we have to explicitly + call GetProcAddress to ensure that these function pointers are populated. + + Oh, and as with all other uses of hook_table_apply(), subtle problems + may arise if a function is referenced from multiple IAT entries across + the process and those IAT entries somehow end up not all pointing to the + same destination. */ + + kernel32 = GetModuleHandleW(L"kernel32.dll"); + + if (next_CreateFileW == NULL) { + next_CreateFileW = (void *) GetProcAddress( + kernel32, + "CreateFileW"); + } + + if (next_SetFilePointerEx == NULL) { + next_SetFilePointerEx = (void *) GetProcAddress( + kernel32, + "SetFilePointerEx"); + } + + InitializeCriticalSection(&iohook_lock); + iohook_initted = true; } -HANDLE -iohook_open_dummy_fd(void) +// Deprecated +HANDLE iohook_open_dummy_fd(void) +{ + iohook_init(); + + return next_CreateFileW( + L"NUL", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL); +} + +HRESULT iohook_open_nul_fd(HANDLE *out) { HANDLE fd; - fd = real_CreateFileW( - L"NUL", - GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, - OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, - NULL); + assert(out != NULL); - if (fd == INVALID_HANDLE_VALUE) { - log_fatal("Failed to open dummy fd: %08x", (uint32_t) GetLastError()); + *out = NULL; + iohook_init(); + + fd = next_CreateFileW( + L"NUL", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL); + + if (fd == NULL) { + return HRESULT_FROM_WIN32(GetLastError()); } - return fd; + *out = fd; + + return S_OK; } -static BOOL -iohook_overlapped_result(uint32_t *syncout, OVERLAPPED *ovl, uint32_t value) +HRESULT iohook_push_handler(iohook_fn_t fn) +{ + iohook_fn_t *new_array; + size_t new_size; + HRESULT hr; + + assert(fn != NULL); + + iohook_init(); + EnterCriticalSection(&iohook_lock); + + new_size = iohook_nhandlers + 1; + new_array = realloc(iohook_handlers, new_size * sizeof(iohook_fn_t)); + + if (new_array != NULL) { + iohook_handlers = new_array; + iohook_handlers[iohook_nhandlers++] = fn; + hr = S_OK; + } else { + hr = E_OUTOFMEMORY; + } + + LeaveCriticalSection(&iohook_lock); + + return hr; +} + +static BOOL iohook_overlapped_result( + uint32_t *syncout, + OVERLAPPED *ovl, + uint32_t value) { if (ovl != NULL) { - ovl->Internal = 0; // (NTSTATUS) STATUS_SUCCESS + ovl->Internal = STATUS_SUCCESS; ovl->InternalHigh = value; if (ovl->hEvent != NULL) { @@ -248,61 +372,68 @@ iohook_overlapped_result(uint32_t *syncout, OVERLAPPED *ovl, uint32_t value) } } -HRESULT -irp_invoke_next(struct irp *irp) +HRESULT iohook_invoke_next(struct irp *irp) { - irp_handler_t handler; + iohook_fn_t handler; HRESULT hr; - log_assert(irp != NULL); - log_assert(irp->next_handler <= iohook_nhandlers); + assert(irp != NULL); + assert(iohook_initted); + + EnterCriticalSection(&iohook_lock); + + assert(irp->next_handler <= iohook_nhandlers); if (irp->next_handler < iohook_nhandlers) { - handler = iohook_handlers[irp->next_handler++]; - hr = handler(irp); - - if (FAILED(hr)) { - irp->next_handler = (size_t) -1; - } + handler = iohook_handlers[irp->next_handler]; + irp->next_handler++; } else { + handler = iohook_invoke_real; + irp->next_handler = (size_t) -1; + } + + LeaveCriticalSection(&iohook_lock); + + hr = handler(irp); + + if (FAILED(hr)) { irp->next_handler = (size_t) -1; - hr = irp_invoke_real(irp); } return hr; } -static HRESULT irp_invoke_real(struct irp *irp) +static HRESULT iohook_invoke_real(struct irp *irp) { - irp_handler_t handler; + iohook_fn_t handler; - log_assert(irp != NULL); - log_assert(irp->op < lengthof(irp_real_handlers)); + assert(irp != NULL); + assert(irp->op < _countof(iohook_real_handlers)); - handler = irp_real_handlers[irp->op]; + handler = iohook_real_handlers[irp->op]; - log_assert(handler != NULL); + assert(handler != NULL); return handler(irp); } -static HRESULT irp_invoke_real_open(struct irp *irp) +static HRESULT iohook_invoke_real_open(struct irp *irp) { HANDLE fd; - log_assert(irp != NULL); + assert(irp != NULL); - fd = real_CreateFileW( - irp->open_filename, - irp->open_access, - irp->open_share, - irp->open_sa, - irp->open_creation, - irp->open_flags, - irp->open_tmpl); + fd = next_CreateFileW( + irp->open_filename, + irp->open_access, + irp->open_share, + irp->open_sa, + irp->open_creation, + irp->open_flags, + irp->open_tmpl); if (fd == INVALID_HANDLE_VALUE) { - return hr_from_win32(); + return HRESULT_FROM_WIN32(GetLastError()); } irp->fd = fd; @@ -310,37 +441,37 @@ static HRESULT irp_invoke_real_open(struct irp *irp) return S_OK; } -static HRESULT irp_invoke_real_close(struct irp *irp) +static HRESULT iohook_invoke_real_close(struct irp *irp) { BOOL ok; - log_assert(irp != NULL); + assert(irp != NULL); - ok = real_CloseHandle(irp->fd); + ok = next_CloseHandle(irp->fd); if (!ok) { - return hr_from_win32(); + return HRESULT_FROM_WIN32(GetLastError()); } return S_OK; } -static HRESULT irp_invoke_real_read(struct irp *irp) +static HRESULT iohook_invoke_real_read(struct irp *irp) { uint32_t nread; BOOL ok; - log_assert(irp != NULL); + assert(irp != NULL); - ok = real_ReadFile( - irp->fd, - &irp->read.bytes[irp->read.pos], - irp->read.nbytes - irp->read.pos, - &nread, - irp->ovl); + ok = next_ReadFile( + irp->fd, + &irp->read.bytes[irp->read.pos], + irp->read.nbytes - irp->read.pos, + &nread, + irp->ovl); if (!ok) { - return hr_from_win32(); + return HRESULT_FROM_WIN32(GetLastError()); } irp->read.pos += nread; @@ -348,22 +479,22 @@ static HRESULT irp_invoke_real_read(struct irp *irp) return S_OK; } -static HRESULT irp_invoke_real_write(struct irp *irp) +static HRESULT iohook_invoke_real_write(struct irp *irp) { uint32_t nwrit; BOOL ok; - log_assert(irp != NULL); + assert(irp != NULL); - ok = real_WriteFile( - irp->fd, - &irp->write.bytes[irp->write.pos], - irp->write.nbytes - irp->write.pos, - &nwrit, - irp->ovl); + ok = next_WriteFile( + irp->fd, + &irp->write.bytes[irp->write.pos], + irp->write.nbytes - irp->write.pos, + &nwrit, + irp->ovl); if (!ok) { - return hr_from_win32(); + return HRESULT_FROM_WIN32(GetLastError()); } irp->write.pos += nwrit; @@ -371,128 +502,141 @@ static HRESULT irp_invoke_real_write(struct irp *irp) return S_OK; } -static HRESULT irp_invoke_real_seek(struct irp *irp) -{ - int32_t hi; - int32_t lo; - HRESULT hr; - - log_assert(irp != NULL); - - hi = (uint32_t)(irp->seek_offset >> 32); - lo = (uint32_t)(irp->seek_offset); - - lo = real_SetFilePointer( - irp->fd, (int32_t) lo, hi == 0 ? NULL : &hi, irp->seek_origin); - - if (lo == INVALID_SET_FILE_POINTER) { - hr = hr_from_win32(); - - if (FAILED(hr)) { - return hr; - } - } - - irp->seek_pos = (((uint64_t) hi) << 32) | ((uint32_t) lo); - - return S_OK; -} - -static HRESULT irp_invoke_real_fsync(struct irp *irp) +static HRESULT iohook_invoke_real_seek(struct irp *irp) { BOOL ok; - log_assert(irp != NULL); + assert(irp != NULL); - ok = real_FlushFileBuffers(irp->fd); + ok = next_SetFilePointerEx( + irp->fd, + irp->seek_offset, + &irp->seek_pos, + irp->seek_origin); if (!ok) { - return hr_from_win32(); + return HRESULT_FROM_WIN32(GetLastError()); } return S_OK; } -static HRESULT irp_invoke_real_ioctl(struct irp *irp) +static HRESULT iohook_invoke_real_fsync(struct irp *irp) +{ + BOOL ok; + + assert(irp != NULL); + + ok = next_FlushFileBuffers(irp->fd); + + if (!ok) { + return HRESULT_FROM_WIN32(GetLastError()); + } + + return S_OK; +} + +static HRESULT iohook_invoke_real_ioctl(struct irp *irp) { uint32_t nread; BOOL ok; - log_assert(irp != NULL); + assert(irp != NULL); /* ioctl in/out params tend to be structs, so it probably does not make sense to concatenate a synthetic result with a pass-through result in the same way as one might do with read/write. */ - log_assert(irp->write.pos == 0); - log_assert(irp->read.pos == 0); + assert(irp->write.pos == 0); + assert(irp->read.pos == 0); - ok = real_DeviceIoControl( - irp->fd, - irp->ioctl, - (void *) irp->write.bytes, // Cast off const - irp->write.nbytes, - irp->read.bytes, - irp->read.nbytes, - &nread, - irp->ovl); + ok = next_DeviceIoControl( + irp->fd, + irp->ioctl, + (void *) irp->write.bytes, // Cast off const + irp->write.nbytes, + irp->read.bytes, + irp->read.nbytes, + &nread, + irp->ovl); - if (!ok) { - return hr_from_win32(); - } + /* Must be propagated even if there is an error, see + iohook_DeviceIoControl. */ irp->read.pos = nread; + if (!ok) { + return HRESULT_FROM_WIN32(GetLastError()); + } + return S_OK; } -static HANDLE STDCALL my_CreateFileA( - const char *lpFileName, - uint32_t dwDesiredAccess, - uint32_t dwShareMode, - SECURITY_ATTRIBUTES *lpSecurityAttributes, - uint32_t dwCreationDisposition, - uint32_t dwFlagsAndAttributes, - HANDLE hTemplateFile) +static HANDLE WINAPI iohook_CreateFileA( + const char *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile) { wchar_t *wfilename; + int nchars; + int result; HANDLE fd; + fd = INVALID_HANDLE_VALUE; + wfilename = NULL; + if (lpFileName == NULL) { - log_warning("%s: lpFileName == NULL", __func__); SetLastError(ERROR_INVALID_PARAMETER); - return INVALID_HANDLE_VALUE; + goto end; } - wfilename = str_widen(lpFileName); - fd = my_CreateFileW( - wfilename, - dwDesiredAccess, - dwShareMode, - lpSecurityAttributes, - dwCreationDisposition, - dwFlagsAndAttributes, - hTemplateFile); + nchars = MultiByteToWideChar(CP_ACP, 0, lpFileName, -1, NULL, 0); + wfilename = malloc(nchars * sizeof(wchar_t)); + + if (wfilename == NULL) { + SetLastError(ERROR_OUTOFMEMORY); + + goto end; + } + + result = MultiByteToWideChar(CP_ACP, 0, lpFileName, -1, wfilename, nchars); + + if (result == 0) { + goto end; + } + + fd = iohook_CreateFileW( + wfilename, + dwDesiredAccess, + dwShareMode, + lpSecurityAttributes, + dwCreationDisposition, dwFlagsAndAttributes, + hTemplateFile); + +end: free(wfilename); return fd; } -static HANDLE STDCALL my_CreateFileW( - const wchar_t *lpFileName, - uint32_t dwDesiredAccess, - uint32_t dwShareMode, - SECURITY_ATTRIBUTES *lpSecurityAttributes, - uint32_t dwCreationDisposition, - uint32_t dwFlagsAndAttributes, - HANDLE hTemplateFile) +static HANDLE WINAPI iohook_CreateFileW( + const wchar_t *lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + SECURITY_ATTRIBUTES *lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes, + HANDLE hTemplateFile) { struct irp irp; HRESULT hr; if (lpFileName == NULL) { - log_warning("%s: lpFileName == NULL", __func__); SetLastError(ERROR_INVALID_PARAMETER); return INVALID_HANDLE_VALUE; @@ -509,7 +653,7 @@ static HANDLE STDCALL my_CreateFileW( irp.open_flags = dwFlagsAndAttributes; irp.open_tmpl = hTemplateFile; - hr = irp_invoke_next(&irp); + hr = iohook_invoke_next(&irp); if (FAILED(hr)) { return hr_propagate_win32(hr, INVALID_HANDLE_VALUE); @@ -520,13 +664,12 @@ static HANDLE STDCALL my_CreateFileW( return irp.fd; } -static BOOL STDCALL my_CloseHandle(HANDLE hFile) +static BOOL WINAPI iohook_CloseHandle(HANDLE hFile) { struct irp irp; HRESULT hr; if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { - log_warning("%s: Invalid file descriptor %p", __func__, hFile); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; @@ -536,36 +679,28 @@ static BOOL STDCALL my_CloseHandle(HANDLE hFile) irp.op = IRP_OP_CLOSE; irp.fd = hFile; - hr = irp_invoke_next(&irp); + hr = iohook_invoke_next(&irp); if (FAILED(hr)) { return hr_propagate_win32(hr, FALSE); } - SetLastError(ERROR_SUCCESS); + /* Don't call SetLastError on success! */ return TRUE; } -static BOOL STDCALL my_ReadFile( - HANDLE hFile, - void *lpBuffer, - uint32_t nNumberOfBytesToRead, - uint32_t *lpNumberOfBytesRead, - OVERLAPPED *lpOverlapped) +static BOOL WINAPI iohook_ReadFile( + HANDLE hFile, + void *lpBuffer, + uint32_t nNumberOfBytesToRead, + uint32_t *lpNumberOfBytesRead, + OVERLAPPED *lpOverlapped) { struct irp irp; HRESULT hr; - if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { - log_warning("%s: Invalid file descriptor %p", __func__, hFile); - SetLastError(ERROR_INVALID_PARAMETER); - - return FALSE; - } - - if (lpBuffer == NULL) { - log_warning("%s: lpBuffer == NULL", __func__); + if (hFile == NULL || hFile == INVALID_HANDLE_VALUE || lpBuffer == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; @@ -573,10 +708,6 @@ static BOOL STDCALL my_ReadFile( if (lpOverlapped == NULL) { if (lpNumberOfBytesRead == NULL) { - log_warning( - "%s: lpNumberOfBytesRead must be supplied in " - "synchronous mode", - __func__); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; @@ -593,38 +724,31 @@ static BOOL STDCALL my_ReadFile( irp.read.nbytes = nNumberOfBytesToRead; irp.read.pos = 0; - hr = irp_invoke_next(&irp); + hr = iohook_invoke_next(&irp); if (FAILED(hr)) { return hr_propagate_win32(hr, FALSE); } - log_assert(irp.read.pos <= irp.read.nbytes); + assert(irp.read.pos <= irp.read.nbytes); return iohook_overlapped_result( - lpNumberOfBytesRead, lpOverlapped, irp.read.pos); + lpNumberOfBytesRead, + lpOverlapped, + irp.read.pos); } -static BOOL STDCALL my_WriteFile( - HANDLE hFile, - const void *lpBuffer, - uint32_t nNumberOfBytesToWrite, - uint32_t *lpNumberOfBytesWritten, - OVERLAPPED *lpOverlapped) +static BOOL WINAPI iohook_WriteFile( + HANDLE hFile, + const void *lpBuffer, + uint32_t nNumberOfBytesToWrite, + uint32_t *lpNumberOfBytesWritten, + OVERLAPPED *lpOverlapped) { struct irp irp; HRESULT hr; - if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { - /* Don't log this because iidx14 calls WriteFile with a NULL handle */ - // log_warning("%s: Invalid file descriptor %p", __func__, hFile); - SetLastError(ERROR_INVALID_PARAMETER); - - return FALSE; - } - - if (lpBuffer == NULL) { - log_warning("%s: lpBuffer == NULL", __func__); + if (hFile == NULL || hFile == INVALID_HANDLE_VALUE || lpBuffer == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; @@ -632,10 +756,6 @@ static BOOL STDCALL my_WriteFile( if (lpOverlapped == NULL) { if (lpNumberOfBytesWritten == NULL) { - log_warning( - "%s: lpNumberOfBytesWritten must be supplied in " - "synchronous mode", - __func__); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; @@ -652,29 +772,30 @@ static BOOL STDCALL my_WriteFile( irp.write.nbytes = nNumberOfBytesToWrite; irp.write.pos = 0; - hr = irp_invoke_next(&irp); + hr = iohook_invoke_next(&irp); if (FAILED(hr)) { return hr_propagate_win32(hr, FALSE); } - log_assert(irp.write.pos <= irp.write.nbytes); + assert(irp.write.pos <= irp.write.nbytes); return iohook_overlapped_result( - lpNumberOfBytesWritten, lpOverlapped, irp.write.pos); + lpNumberOfBytesWritten, + lpOverlapped, + irp.write.pos); } -static DWORD STDCALL my_SetFilePointer( - HANDLE hFile, - int32_t lDistanceToMove, - int32_t *lpDistanceToMoveHigh, - uint32_t dwMoveMethod) +static DWORD WINAPI iohook_SetFilePointer( + HANDLE hFile, + int32_t lDistanceToMove, + int32_t *lpDistanceToMoveHigh, + uint32_t dwMoveMethod) { struct irp irp; HRESULT hr; if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { - log_warning("%s: Invalid file descriptor %p", __func__, hFile); SetLastError(ERROR_INVALID_PARAMETER); return INVALID_SET_FILE_POINTER; @@ -690,13 +811,13 @@ static DWORD STDCALL my_SetFilePointer( with sign-extension vs zero-extension here. */ if (lpDistanceToMoveHigh != NULL) { - irp.seek_offset = (((int64_t) *lpDistanceToMoveHigh) << 32) | - ((uint64_t) lDistanceToMove); + irp.seek_offset = ((( int64_t) *lpDistanceToMoveHigh) << 32) | + ((uint64_t) lDistanceToMove ) ; } else { - irp.seek_offset = (int64_t) lDistanceToMove; + irp.seek_offset = ( int64_t) lDistanceToMove; } - hr = irp_invoke_next(&irp); + hr = iohook_invoke_next(&irp); if (FAILED(hr)) { return hr_propagate_win32(hr, INVALID_SET_FILE_POINTER); @@ -711,17 +832,50 @@ static DWORD STDCALL my_SetFilePointer( return (DWORD) irp.seek_pos; } -static BOOL STDCALL my_FlushFileBuffers(HANDLE hFile) +static BOOL WINAPI iohook_SetFilePointerEx( + HANDLE hFile, + int64_t liDistanceToMove, + uint64_t *lpNewFilePointer, + uint32_t dwMoveMethod) { struct irp irp; HRESULT hr; - /* Some of the old games using acio (e.g. DistorteD) are calling - FlushFileBuffers. If that call is not hooked, the game will write a bunch - of 0 data to the device and stop doing any read/write calls after that */ + if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { + SetLastError(ERROR_INVALID_PARAMETER); + + return FALSE; + } + + /* Much nicer than SetFilePointer */ + + memset(&irp, 0, sizeof(irp)); + irp.op = IRP_OP_SEEK; + irp.fd = hFile; + irp.seek_offset = liDistanceToMove; + irp.seek_origin = dwMoveMethod; + + hr = iohook_invoke_next(&irp); + + if (FAILED(hr)) { + return hr_propagate_win32(hr, FALSE); + } + + if (lpNewFilePointer != NULL) { + *lpNewFilePointer = irp.seek_pos; + } + + SetLastError(ERROR_SUCCESS); + + return TRUE; +} + +static BOOL WINAPI iohook_FlushFileBuffers(HANDLE hFile) +{ + struct irp irp; + HRESULT hr; if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { - log_warning("%s: Invalid file descriptor %p", __func__, hFile); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; @@ -731,7 +885,7 @@ static BOOL STDCALL my_FlushFileBuffers(HANDLE hFile) irp.op = IRP_OP_FSYNC; irp.fd = hFile; - hr = irp_invoke_next(&irp); + hr = iohook_invoke_next(&irp); if (FAILED(hr)) { return hr_propagate_win32(hr, FALSE); @@ -742,21 +896,20 @@ static BOOL STDCALL my_FlushFileBuffers(HANDLE hFile) return TRUE; } -static BOOL STDCALL my_DeviceIoControl( - HANDLE hFile, - uint32_t dwIoControlCode, - void *lpInBuffer, - uint32_t nInBufferSize, - void *lpOutBuffer, - uint32_t nOutBufferSize, - uint32_t *lpBytesReturned, - OVERLAPPED *lpOverlapped) +static BOOL WINAPI iohook_DeviceIoControl( + HANDLE hFile, + uint32_t dwIoControlCode, + void *lpInBuffer, + uint32_t nInBufferSize, + void *lpOutBuffer, + uint32_t nOutBufferSize, + uint32_t *lpBytesReturned, + OVERLAPPED *lpOverlapped) { struct irp irp; HRESULT hr; if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) { - log_warning("%s: Invalid file descriptor %p", __func__, hFile); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; @@ -764,9 +917,6 @@ static BOOL STDCALL my_DeviceIoControl( if (lpOverlapped == NULL) { if (lpBytesReturned == NULL) { - log_warning( - "%s: lpBytesReturned must be supplied in synchronous mode", - __func__); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; @@ -791,15 +941,24 @@ static BOOL STDCALL my_DeviceIoControl( irp.read.nbytes = nOutBufferSize; } - hr = irp_invoke_next(&irp); + hr = iohook_invoke_next(&irp); if (FAILED(hr)) { + /* Special case: ERROR_MORE_DATA requires this out parameter to be + propagated, per MSDN. All ioctls in the entire process that go via + the win32 API (as opposed to the NTDLL API) get redirected through + iohook, and the Windows XP version of DirectSound is known to rely + on this behavior. */ + + if (lpBytesReturned != NULL) { + *lpBytesReturned = (DWORD) irp.read.pos; + } + return hr_propagate_win32(hr, FALSE); } - log_assert(irp.write.pos <= irp.write.nbytes); - log_assert(irp.read.pos <= irp.read.nbytes); - return iohook_overlapped_result( - lpBytesReturned, lpOverlapped, irp.read.pos); + lpBytesReturned, + lpOverlapped, + irp.read.pos); } diff --git a/src/main/hook/iohook.h b/src/main/hook/iohook.h index a0ba10b..58bf642 100644 --- a/src/main/hook/iohook.h +++ b/src/main/hook/iohook.h @@ -1,12 +1,11 @@ -#ifndef HOOK_IOHOOK_H -#define HOOK_IOHOOK_H +#pragma once #include #include #include -#include "util/iobuf.h" +#include "hook/iobuf.h" enum irp_op { IRP_OP_OPEN, @@ -38,10 +37,14 @@ struct irp { uint64_t seek_pos; }; -typedef HRESULT (*irp_handler_t)(struct irp *irp); - -void iohook_init(const irp_handler_t *handlers, size_t nhandlers); -HANDLE iohook_open_dummy_fd(void); -HRESULT irp_invoke_next(struct irp *irp); +typedef HRESULT (*iohook_fn_t)(struct irp *irp); +HANDLE iohook_open_dummy_fd(void) +#ifdef __GNUC__ +__attribute__((deprecated("Use iohook_open_nul_fd instead"))) #endif +; + +HRESULT iohook_open_nul_fd(HANDLE *fd); +HRESULT iohook_push_handler(iohook_fn_t fn); +HRESULT iohook_invoke_next(struct irp *irp); diff --git a/src/main/hook/pe.c b/src/main/hook/pe.c index 91d6ca2..49b7e94 100644 --- a/src/main/hook/pe.c +++ b/src/main/hook/pe.c @@ -1,20 +1,17 @@ #include +#include #include #include #include +#include +#include #include "hook/pe.h" -#include "util/log.h" - -typedef BOOL(WINAPI *dll_main_t)(HMODULE, uint32_t, void *); - -static const IMAGE_NT_HEADERS *pe_get_nt_header(HMODULE pe); -static uint32_t -pe_get_virtual_size(const IMAGE_SECTION_HEADER *sh, int nsections); static void *pe_offset(void *ptr, size_t off); static const void *pe_offsetc(const void *ptr, size_t off); +static const IMAGE_NT_HEADERS *pe_get_nt_header(HMODULE pe); static void *pe_offset(void *ptr, size_t off) { @@ -42,26 +39,6 @@ static const void *pe_offsetc(const void *ptr, size_t off) return base + off; } -static uint32_t -pe_get_virtual_size(const IMAGE_SECTION_HEADER *sh, int nsections) -{ - uint32_t sec_end; - uint32_t size; - int i; - - size = 0; - - for (i = 0; i < nsections; i++) { - sec_end = sh[i].VirtualAddress + sh[i].Misc.VirtualSize; - - if (size < sec_end) { - size = sec_end; - } - } - - return size; -} - static const IMAGE_NT_HEADERS *pe_get_nt_header(HMODULE pe) { const IMAGE_DOS_HEADER *dh; @@ -76,13 +53,14 @@ static const IMAGE_NT_HEADERS *pe_get_nt_header(HMODULE pe) const pe_iid_t *pe_iid_get_first(HMODULE pe) { const IMAGE_NT_HEADERS *nth; + const IMAGE_DATA_DIRECTORY *idd; const IMAGE_IMPORT_DESCRIPTOR *iid; + assert(pe != NULL); + nth = pe_get_nt_header(pe); - iid = pe_offsetc( - pe, - nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] - .VirtualAddress); + idd = &nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + iid = pe_offsetc(pe, idd->VirtualAddress); if (iid == NULL || iid->Name == 0) { return NULL; @@ -93,6 +71,9 @@ const pe_iid_t *pe_iid_get_first(HMODULE pe) const char *pe_iid_get_name(HMODULE pe, const pe_iid_t *iid) { + assert(pe != NULL); + assert(iid != NULL); + return pe_offsetc(pe, iid->Name); } @@ -100,6 +81,9 @@ const pe_iid_t *pe_iid_get_next(HMODULE pe, const pe_iid_t *iid) { const IMAGE_IMPORT_DESCRIPTOR *iid_next; + assert(pe != NULL); + assert(iid != NULL); + iid_next = iid + 1; if (iid_next->Name != 0) { @@ -109,27 +93,30 @@ const pe_iid_t *pe_iid_get_next(HMODULE pe, const pe_iid_t *iid) } } -bool pe_iid_get_iat_entry( - HMODULE pe, const pe_iid_t *iid, size_t n, struct pe_iat_entry *entry) +HRESULT pe_iid_get_iat_entry( + HMODULE pe, + const pe_iid_t *iid, + size_t n, + struct pe_iat_entry *entry) { const IMAGE_IMPORT_BY_NAME *import; - uintptr_t *import_rvas; + intptr_t *import_rvas; void **pointers; - if (iid->OriginalFirstThunk == 0) { - log_warning("OriginalFirstThunk == 0"); - } + assert(pe != NULL); + assert(iid != NULL); + assert(entry != NULL); import_rvas = pe_offset(pe, iid->OriginalFirstThunk); if (import_rvas[n] == 0) { /* End of imports */ - entry->name = NULL; - entry->ordinal = 0; - entry->ppointer = NULL; + memset(entry, 0, sizeof(*entry)); - return false; - } else if (import_rvas[n] & INTPTR_MIN) { + return S_FALSE; + } + + if (import_rvas[n] & INTPTR_MIN) { /* Ordinal import */ entry->name = NULL; entry->ordinal = (uint16_t) import_rvas[n]; @@ -143,143 +130,43 @@ bool pe_iid_get_iat_entry( pointers = pe_offset(pe, iid->FirstThunk); entry->ppointer = &pointers[n]; - return true; -} - -void pe_patch_pointer(void **ppointer, void *new_value) -{ - DWORD old_protect; - - VirtualProtect( - ppointer, sizeof(void *), PAGE_EXECUTE_READWRITE, &old_protect); - *ppointer = new_value; - VirtualProtect(ppointer, sizeof(void *), old_protect, &old_protect); -} - -HMODULE -pe_explode(const uint8_t *bytes, uint32_t nbytes) -{ - HMODULE base; - const IMAGE_DOS_HEADER *dh; - const IMAGE_NT_HEADERS *nth; - const IMAGE_SECTION_HEADER *sh; - uint32_t virtual_size; - uint32_t vflags; - int i; - - dh = (IMAGE_DOS_HEADER *) bytes; - nth = pe_offsetc(bytes, dh->e_lfanew); - sh = pe_offsetc(bytes, dh->e_lfanew + sizeof(*nth)); - - virtual_size = pe_get_virtual_size(sh, nth->FileHeader.NumberOfSections); - base = (HMODULE) VirtualAlloc( - (void *) nth->OptionalHeader.ImageBase, - virtual_size, - MEM_RESERVE, - PAGE_NOACCESS); - - if (base == NULL) { - /* Try again, allowing any base address */ - base = (HMODULE) VirtualAlloc( - NULL, virtual_size, MEM_RESERVE, PAGE_NOACCESS); - - if (base == NULL) { - /* Aargh */ - log_fatal( - "Failed to VirtualAlloc %#x bytes of address space", - virtual_size); - } - } - - log_misc( - "Exploding PE, base %p actual %p", - (void *) nth->OptionalHeader.ImageBase, - base); - - /* Commit header region */ - VirtualAlloc( - (void *) base, - nth->OptionalHeader.SizeOfHeaders, - MEM_COMMIT, - PAGE_READWRITE); - - memcpy(base, dh, sizeof(*dh)); - memcpy(pe_offset(base, dh->e_lfanew), nth, sizeof(*nth)); - memcpy( - pe_offset(base, dh->e_lfanew + sizeof(*nth)), - sh, - sizeof(*sh) * nth->FileHeader.NumberOfSections); - - for (i = 0; i < nth->FileHeader.NumberOfSections; i++) { - vflags = sh[i].Characteristics & 0x20000000 ? PAGE_EXECUTE_READWRITE : - PAGE_READWRITE; - - VirtualAlloc( - pe_offset(base, sh[i].VirtualAddress), - sh[i].Misc.VirtualSize, - MEM_COMMIT, - vflags); - - memcpy( - pe_offset(base, sh[i].VirtualAddress), - pe_offsetc(bytes, sh[i].PointerToRawData), - sh[i].SizeOfRawData); - } - - return base; -} - -void pe_relocate(HMODULE pe) -{ - const IMAGE_NT_HEADERS *nth; - const IMAGE_DATA_DIRECTORY *dde; - const IMAGE_BASE_RELOCATION *chunk; - intptr_t delta_va; - const uint16_t *reloc; - uintptr_t *addr_ptr; - - nth = pe_get_nt_header(pe); - delta_va = (intptr_t) pe - nth->OptionalHeader.ImageBase; - dde = nth->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_BASERELOC; - - for (chunk = pe_offsetc(pe, dde->VirtualAddress); - (void *) chunk < pe_offsetc(pe, dde->VirtualAddress + dde->Size); - chunk = pe_offsetc(chunk, chunk->SizeOfBlock)) { - for (reloc = (uint16_t *) (chunk + 1); - (void *) reloc < pe_offsetc(chunk, chunk->SizeOfBlock); - reloc++) { - if (*reloc >> 12 == IMAGE_REL_BASED_HIGHLOW) { - addr_ptr = - pe_offset(pe, chunk->VirtualAddress + (*reloc & 0x0FFF)); - *addr_ptr += delta_va; - } - } - } + return S_OK; } void *pe_get_export(HMODULE pe, const char *name, uint16_t ord) { const IMAGE_NT_HEADERS *nth; + const IMAGE_DATA_DIRECTORY *idd; const IMAGE_EXPORT_DIRECTORY *ied; const uint32_t *name_rvas; const uint32_t *target_rvas; + const char *name_va; DWORD i; + assert(pe != NULL); + nth = pe_get_nt_header(pe); - ied = pe_offsetc( - pe, - nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] - .VirtualAddress); + idd = &nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + ied = pe_offsetc(pe, idd->VirtualAddress); name_rvas = pe_offsetc(pe, ied->AddressOfNames); target_rvas = pe_offsetc(pe, ied->AddressOfFunctions); if (name != NULL) { - for (i = 0; i < ied->NumberOfNames; i++) { - if (name_rvas[i] != 0 && - strcmp(pe_offsetc(pe, name_rvas[i]), name) == 0) { - return pe_offset(pe, target_rvas[i]); + for (i = 0 ; i < ied->NumberOfNames ; i++) { + if (name_rvas[i] == 0) { + /* Ordinal-only export, cannot match against this */ + continue; } + + name_va = pe_offsetc(pe, name_rvas[i]); + + if (strcmp(name_va, name) != 0) { + /* Name did not match */ + continue; + } + + return pe_offset(pe, target_rvas[i]); } return NULL; @@ -290,13 +177,46 @@ void *pe_get_export(HMODULE pe, const char *name, uint16_t ord) } } -BOOL pe_call_dll_main(HMODULE pe, uint32_t reason, void *ctx) +void *pe_get_entry_point(HMODULE pe) { const IMAGE_NT_HEADERS *nth; - dll_main_t dll_main; + + assert(pe != NULL); nth = pe_get_nt_header(pe); - dll_main = pe_offset(pe, nth->OptionalHeader.AddressOfEntryPoint); - return dll_main(pe, reason, ctx); + return pe_offset(pe, nth->OptionalHeader.AddressOfEntryPoint); +} + +HRESULT pe_patch(void *dest, const void *src, size_t nbytes) +{ + DWORD old_protect; + BOOL ok; + + assert(dest != NULL); + assert(src != NULL); + + ok = VirtualProtect( + dest, + nbytes, + PAGE_EXECUTE_READWRITE, + &old_protect); + + if (!ok) { + return HRESULT_FROM_WIN32(GetLastError()); + } + + memcpy(dest, src, nbytes); + + ok = VirtualProtect( + dest, + nbytes, + old_protect, + &old_protect); + + if (!ok) { + return HRESULT_FROM_WIN32(GetLastError()); + } + + return S_OK; } diff --git a/src/main/hook/pe.h b/src/main/hook/pe.h index 5fa27bf..1a33f12 100644 --- a/src/main/hook/pe.h +++ b/src/main/hook/pe.h @@ -1,9 +1,9 @@ -#ifndef HOOK_PE_H -#define HOOK_PE_H +#pragma once #include #include +#include #include typedef IMAGE_IMPORT_DESCRIPTOR pe_iid_t; @@ -17,14 +17,11 @@ struct pe_iat_entry { const pe_iid_t *pe_iid_get_first(HMODULE pe); const char *pe_iid_get_name(HMODULE pe, const pe_iid_t *iid); const pe_iid_t *pe_iid_get_next(HMODULE pe, const pe_iid_t *iid); -bool pe_iid_get_iat_entry( - HMODULE pe, const pe_iid_t *iid, size_t n, struct pe_iat_entry *entry); +HRESULT pe_iid_get_iat_entry( + HMODULE pe, + const pe_iid_t *iid, + size_t n, + struct pe_iat_entry *entry); void *pe_get_export(HMODULE pe, const char *name, uint16_t ord); -BOOL pe_call_dll_main(HMODULE pe, uint32_t reason, void *ctx); - -void pe_patch_pointer(void **ppointer, void *new_value); - -HMODULE pe_explode(const uint8_t *bytes, uint32_t nbytes); -void pe_relocate(HMODULE pe); - -#endif +void *pe_get_entry_point(HMODULE pe); +HRESULT pe_patch(void *dest, const void *src, size_t nbytes); diff --git a/src/main/hook/peb.c b/src/main/hook/peb.c index 8da6477..7fa9a9c 100644 --- a/src/main/hook/peb.c +++ b/src/main/hook/peb.c @@ -1,14 +1,13 @@ #include #include -#include "hook/peb.h" +#include -#include "util/defs.h" -#include "util/str.h" +#include "hook/peb.h" static const PEB *peb_get(void) { -#ifdef __amd64 +#ifdef _M_AMD64 return (const PEB *) __readgsqword(0x60); #else return (const PEB *) __readfsdword(0x30); @@ -23,7 +22,7 @@ const peb_dll_t *peb_dll_get_first(void) peb = peb_get(); node = peb->Ldr->InMemoryOrderModuleList.Flink; - return containerof(node, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + return CONTAINING_RECORD(node, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); } const peb_dll_t *peb_dll_get_next(const peb_dll_t *dll) @@ -31,6 +30,8 @@ const peb_dll_t *peb_dll_get_next(const peb_dll_t *dll) const PEB *peb; const LIST_ENTRY *node; + assert(dll != NULL); + peb = peb_get(); node = dll->InMemoryOrderLinks.Flink; @@ -38,30 +39,12 @@ const peb_dll_t *peb_dll_get_next(const peb_dll_t *dll) return NULL; } - return containerof(node, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + return CONTAINING_RECORD(node, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); } -HMODULE -peb_dll_get_base(const peb_dll_t *dll) +HMODULE peb_dll_get_base(const peb_dll_t *dll) { + assert(dll != NULL); + return dll->DllBase; } - -char *peb_dll_dup_name(const peb_dll_t *dll) -{ - const UNICODE_STRING *wstr; - char *name; - size_t i; - - wstr = &dll->FullDllName; - - for (i = wstr->Length / 2 - 1; i > 0; i--) { - if (wstr->Buffer[i] == L'\\') { - wstr_narrow(&wstr->Buffer[i + 1], &name); - - return name; - } - } - - return NULL; -} diff --git a/src/main/hook/peb.h b/src/main/hook/peb.h index f12b45d..6c10cf8 100644 --- a/src/main/hook/peb.h +++ b/src/main/hook/peb.h @@ -1,5 +1,4 @@ -#ifndef HOOK_PEB_H -#define HOOK_PEB_H +#pragma once #include #include @@ -10,5 +9,3 @@ const peb_dll_t *peb_dll_get_first(void); const peb_dll_t *peb_dll_get_next(const peb_dll_t *dll); HMODULE peb_dll_get_base(const peb_dll_t *dll); char *peb_dll_dup_name(const peb_dll_t *dll); - -#endif diff --git a/src/main/hook/process.c b/src/main/hook/process.c new file mode 100644 index 0000000..7d98cf2 --- /dev/null +++ b/src/main/hook/process.c @@ -0,0 +1,196 @@ +#include +#include + +#include +#include +#include + +#include "hook/hr.h" +#include "hook/pe.h" +#include "hook/process.h" + +static bool thread_match_startup( + const CONTEXT *ctx, + void *ntstart, + void *exe_entry) +{ +#ifdef _M_AMD64 + return ctx->Rip == (DWORD64) ntstart && + ctx->Rcx == (DWORD64) exe_entry; +#else + return ctx->Eip == (DWORD) ntstart && + ctx->Eax == (DWORD) exe_entry; +#endif +} + +static void thread_patch_startup( + process_entry_t new_entry, + process_entry_t *orig_entry, + CONTEXT *ctx) +{ +#ifdef _M_AMD64 + *orig_entry = (void *) ctx->Rcx; + ctx->Rcx = (DWORD64) new_entry; +#else + *orig_entry = (void *) ctx->Eax; + ctx->Eax = (DWORD) new_entry; +#endif +} + +static HRESULT process_hijack_try_thread( + process_entry_t new_entry, + process_entry_t *orig_entry, + DWORD thread_id) +{ + CONTEXT ctx; + HMODULE exe; + HMODULE ntdll; + void *exe_entry; + void *ntstart; + HANDLE thread; + HRESULT hr; + BOOL ok; + + thread = NULL; + + exe = GetModuleHandleW(NULL); + + if (exe == NULL) { + /* uhhhh... */ + hr = E_UNEXPECTED; + + goto end; + } + + ntdll = GetModuleHandleW(L"ntdll.dll"); + + if (ntdll == NULL) { + /* Another 2 + 2 = 5 situation */ + hr = E_UNEXPECTED; + + goto end; + } + + exe_entry = pe_get_entry_point(exe); + ntstart = GetProcAddress(ntdll, "RtlUserThreadStart"); + + if (ntstart == NULL) { + /* TODO Deal with WinXP, for the poor souls still stuck on that OS. + XP starts threads at LdrInitializeThunk instead (I think) */ + hr = E_NOTIMPL; + + goto end; + } + + thread = OpenThread( + THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, + FALSE, + thread_id); + + if (thread == NULL) { + hr = HRESULT_FROM_WIN32(GetLastError()); + + goto end; + } + + memset(&ctx, 0, sizeof(ctx)); +#ifdef _M_AMD64 + ctx.ContextFlags = CONTEXT_AMD64 | CONTEXT_FULL; +#else + ctx.ContextFlags = CONTEXT_i386 | CONTEXT_FULL; +#endif + + ok = GetThreadContext(thread, &ctx); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + + goto end; + } + + if (thread_match_startup(&ctx, ntstart, exe_entry)) { + thread_patch_startup(new_entry, orig_entry, &ctx); + ok = SetThreadContext(thread, &ctx); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + + goto end; + } + + return S_OK; + } else { + return S_FALSE; + } + +end: + if (thread != NULL) { + CloseHandle(thread); + } + + return hr; +} + +HRESULT process_hijack_startup( + process_entry_t new_entry, + process_entry_t *orig_entry) +{ + THREADENTRY32 thread; + HANDLE snap; + DWORD pid; + HRESULT fault; + HRESULT hr; + BOOL ok; + + assert(new_entry != NULL); + assert(orig_entry != NULL); + + pid = GetCurrentProcessId(); + snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + + if (snap == INVALID_HANDLE_VALUE) { + hr = HRESULT_FROM_WIN32(GetLastError()); + + goto end; + } + + thread.dwSize = sizeof(thread); + ok = Thread32First(snap, &thread); + + if (!ok) { + hr = HRESULT_FROM_WIN32(GetLastError()); + + goto end; + } + + /* Return this if we don't find anything suitable */ + fault = E_FAIL; + + do { + if (thread.th32OwnerProcessID != pid) { + continue; + } + + hr = process_hijack_try_thread( + new_entry, + orig_entry, + thread.th32ThreadID); + + if (hr == S_OK) { + /* Main thread successfully hijacked, finish up */ + goto end; + } else if (FAILED(hr)) { + /* Latch this error code, but don't abort, keep trying. */ + fault = hr; + } + } while (Thread32Next(snap, &thread)); + + hr = fault; + +end: + if (snap != INVALID_HANDLE_VALUE) { + CloseHandle(snap); + } + + return hr; +} diff --git a/src/main/hook/process.h b/src/main/hook/process.h new file mode 100644 index 0000000..85937c4 --- /dev/null +++ b/src/main/hook/process.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +typedef DWORD (CALLBACK *process_entry_t)(void); + +HRESULT process_hijack_startup( + process_entry_t new_entry, + process_entry_t *orig_entry); diff --git a/src/main/hook/table.c b/src/main/hook/table.c index d5d9e69..57ff0c0 100644 --- a/src/main/hook/table.c +++ b/src/main/hook/table.c @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -8,32 +9,44 @@ #include "hook/peb.h" #include "hook/table.h" -#include "util/mem.h" +static const char apiset_prefix[] = "api-ms-win-core-"; +static const size_t apiset_prefix_len = sizeof(apiset_prefix) - 1; static void hook_table_apply_to_all( - const char *depname, const struct hook_symbol *syms, size_t nsyms); + const char *depname, + const struct hook_symbol *syms, + size_t nsyms); static void hook_table_apply_to_iid( - HMODULE target, - const pe_iid_t *iid, - const struct hook_symbol *syms, - size_t nsyms); + HMODULE target, + const pe_iid_t *iid, + const struct hook_symbol *syms, + size_t nsyms); + +static bool hook_table_match_module( + HMODULE target, + const char *iid_name, + const char *depname); static bool hook_table_match_proc( - const struct pe_iat_entry *iate, const struct hook_symbol *sym); + const struct pe_iat_entry *iate, + const struct hook_symbol *sym); static void hook_table_apply_to_all( - const char *depname, const struct hook_symbol *syms, size_t nsyms) + const char *depname, + const struct hook_symbol *syms, + size_t nsyms) { const peb_dll_t *dll; HMODULE pe; - for (dll = peb_dll_get_first(); dll != NULL; dll = peb_dll_get_next(dll)) { + for ( dll = peb_dll_get_first() ; + dll != NULL ; + dll = peb_dll_get_next(dll)) { pe = peb_dll_get_base(dll); if (pe == NULL) { - /* wtf? */ - continue; + continue; /* ?? Happens sometimes. */ } hook_table_apply(pe, depname, syms, nsyms); @@ -41,25 +54,29 @@ static void hook_table_apply_to_all( } void hook_table_apply( - HMODULE target, - const char *depname, - const struct hook_symbol *syms, - size_t nsyms) + HMODULE target, + const char *depname, + const struct hook_symbol *syms, + size_t nsyms) { const pe_iid_t *iid; const char *iid_name; + assert(depname != NULL); + assert(syms != NULL || nsyms == 0); + if (target == NULL) { /* Call out, which will then call us back repeatedly. Awkward, but viewed from the outside it's good for usability. */ hook_table_apply_to_all(depname, syms, nsyms); } else { - for (iid = pe_iid_get_first(target); iid != NULL; - iid = pe_iid_get_next(target, iid)) { + for ( iid = pe_iid_get_first(target) ; + iid != NULL ; + iid = pe_iid_get_next(target, iid)) { iid_name = pe_iid_get_name(target, iid); - if (_stricmp(iid_name, depname) == 0) { + if (hook_table_match_module(target, iid_name, depname)) { hook_table_apply_to_iid(target, iid, syms, nsyms); } } @@ -67,10 +84,10 @@ void hook_table_apply( } static void hook_table_apply_to_iid( - HMODULE target, - const pe_iid_t *iid, - const struct hook_symbol *syms, - size_t nsyms) + HMODULE target, + const pe_iid_t *iid, + const struct hook_symbol *syms, + size_t nsyms) { struct pe_iat_entry iate; size_t i; @@ -79,8 +96,8 @@ static void hook_table_apply_to_iid( i = 0; - while (pe_iid_get_iat_entry(target, iid, i++, &iate)) { - for (j = 0; j < nsyms; j++) { + while (pe_iid_get_iat_entry(target, iid, i++, &iate) == S_OK) { + for (j = 0 ; j < nsyms ; j++) { sym = &syms[j]; if (hook_table_match_proc(&iate, sym)) { @@ -88,17 +105,70 @@ static void hook_table_apply_to_iid( *sym->link = *iate.ppointer; } - pe_patch_pointer(iate.ppointer, sym->patch); + pe_patch(iate.ppointer, &sym->patch, sizeof(sym->patch)); } } } } -static bool hook_table_match_proc( - const struct pe_iat_entry *iate, const struct hook_symbol *sym) +static bool hook_table_match_module( + HMODULE target, + const char *iid_name, + const char *depname) { - if (sym->name != NULL && iate->name != NULL && - strcmp(sym->name, iate->name) == 0) { + HMODULE kernel32; + int result; + + /* OK, first do a straightforward match on the imported DLL name versus + the hook table DLL name. If it succeeds then we're done. */ + + result = _stricmp(iid_name, depname); + + if (result == 0) { + return true; + } + + /* If it failed then we have to check if this hook table targets kernel32. + We have to do some special processing around API sets in that case, so + stop here if kernel32 is not the subject of this hook table. */ + + if (_stricmp(depname, "kernel32.dll") != 0) { + return false; + } + + /* There isn't really any good test for whether a DLL import wants a + concrete DLL or an abstract DLL providing a particular Windows API-set, + so we use a hacky check against the prefix. If the imported DLL name + looks like an apiset then we'll allow kernel32 hook tables to apply. */ + + result = _strnicmp(iid_name, apiset_prefix, apiset_prefix_len); + + if (result != 0) { + return false; + } + + /* ... EXCEPT for the case where we're hooking all DLLs loaded into the + process and we are currently examining kernel32 itself. In that case + there's some weird reference loops issues I don't entirely understand + right now. To avoid those, we just don't apply kernel32 hook tables to + kernel32 itself. */ + + kernel32 = GetModuleHandleW(L"kernel32.dll"); + + if (target == kernel32) { + return false; + } + + return true; +} + +static bool hook_table_match_proc( + const struct pe_iat_entry *iate, + const struct hook_symbol *sym) +{ + if ( sym->name != NULL && + iate->name != NULL && + strcmp(sym->name, iate->name) == 0) { return true; } diff --git a/src/main/hook/table.h b/src/main/hook/table.h index 6b377de..f4028cc 100644 --- a/src/main/hook/table.h +++ b/src/main/hook/table.h @@ -1,5 +1,4 @@ -#ifndef HOOK_TABLE_H -#define HOOK_TABLE_H +#pragma once #include @@ -14,9 +13,13 @@ struct hook_symbol { }; void hook_table_apply( - HMODULE target, - const char *depname, - const struct hook_symbol *syms, - size_t nsyms); + HMODULE target, + const char *depname, + const struct hook_symbol *syms, + size_t nsyms); -#endif +void hook_table_revert( + HMODULE target, + const char *depname, + const struct hook_symbol *syms, + size_t nsyms);