Bemanitools v5.26 release
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
libs += hook
|
||||
|
||||
src_hook := \
|
||||
com-proxy.c \
|
||||
iohook.c \
|
||||
pe.c \
|
||||
peb.c \
|
||||
table.c \
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#include <windows.h>
|
||||
#include <unknwn.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "hook/com-proxy.h"
|
||||
|
||||
#include "util/defs.h"
|
||||
#include "util/mem.h"
|
||||
|
||||
#ifdef _WIN64
|
||||
|
||||
/***** 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 rax, [rcx] ; Get real->vtbl */
|
||||
0x48, 0x8B, 0x01,
|
||||
|
||||
/* mov rax, [rax+XX] ; Get vtbl->slot_XX */
|
||||
0x48, 0x8B, 0x80, -1, -1, -1, -1,
|
||||
|
||||
/* jmp rax ; Continue to slot_XX */
|
||||
0xFF, 0xE0,
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
/***** 32-BIT TRAMPOLINE *****/
|
||||
|
||||
#define SLOT_OFFSET 0x0F
|
||||
static const uint8_t com_proxy_tramp[] = {
|
||||
/* mov eax, [esp+4] ; Get this */
|
||||
0x8B, 0x44, 0x24, 0x04,
|
||||
|
||||
/* mov eax, [eax+4] ; Get this->real */
|
||||
0x8B, 0x40, 0x04,
|
||||
|
||||
/* mov [esp+4], eax ; Replace this with this->real on stack */
|
||||
0x89, 0x44, 0x24, 0x04,
|
||||
|
||||
/* mov ecx, [eax] ; Get real->vtbl */
|
||||
0x8B, 0x08,
|
||||
|
||||
/* mov ecx, [ecx+XX] ; Get vtbl->slot_XX */
|
||||
0x8B, 0x89, -1, -1, -1, -1,
|
||||
|
||||
/* jmp ecx ; Continue to slot_XX */
|
||||
0xFF, 0xE1
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
static HRESULT STDCALL com_proxy_query_interface(IUnknown *ptr, REFIID iid,
|
||||
void **iface)
|
||||
{
|
||||
struct com_proxy *self = (struct com_proxy *) ptr;
|
||||
IUnknown *obj = self->real; /* Not necessarily the real IUnknown* */
|
||||
|
||||
/* To some extent, COM is designed to support shennanigans like these.
|
||||
We can safely pass the call straight through to the underlying
|
||||
interface pointer because of the following:
|
||||
|
||||
"It is specifically not the case that queries for interfaces other
|
||||
than IUnknown (even the same interface through the same pointer)
|
||||
must return the same pointer value."
|
||||
|
||||
- MSDN documentation for IUnknown::QueryInterface()
|
||||
|
||||
Of course, pretty much everyone screws up COM's conventions (probably
|
||||
including me in this very module to be honest), so if someone ends up
|
||||
relying on broken assumptions then this could well get a lot more
|
||||
complicated. */
|
||||
|
||||
return IUnknown_QueryInterface(obj, iid, iface);
|
||||
}
|
||||
|
||||
static ULONG STDCALL com_proxy_addref(IUnknown *ptr)
|
||||
{
|
||||
struct com_proxy *self = (struct com_proxy *) ptr;
|
||||
IUnknown *obj = self->real;
|
||||
|
||||
return IUnknown_AddRef(obj);
|
||||
}
|
||||
|
||||
static ULONG STDCALL com_proxy_release(IUnknown *ptr)
|
||||
{
|
||||
struct com_proxy *self = (struct com_proxy *) ptr;
|
||||
IUnknown *obj = self->real;
|
||||
ULONG result;
|
||||
|
||||
result = IUnknown_Release(obj);
|
||||
|
||||
if (!result) {
|
||||
/* Last ref to underlying object released */
|
||||
VirtualFree(self->tramps, 0, MEM_RELEASE);
|
||||
free(self->vptr);
|
||||
free(self);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef HOOK_COM_PROXY_H
|
||||
#define HOOK_COM_PROXY_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* 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)
|
||||
|
||||
struct com_proxy {
|
||||
/* Pointer to vtable filled with trampolines. Edit these as you please.
|
||||
Each com_proxy has its own independent vtable. */
|
||||
void *vptr;
|
||||
|
||||
/* Interface pointer wrapped by this proxy. */
|
||||
void *real;
|
||||
|
||||
/* 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
|
||||
can modify in order to intercept a subset of the interface's method calls.
|
||||
|
||||
By default, all the vtable slots contain dynamically generated trampolines
|
||||
which pass method calls onwards to the corresponding methods in the
|
||||
underlying object's vtable.
|
||||
|
||||
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:
|
||||
|
||||
1. Do not wrap IUnknown pointers with this function. This will break the
|
||||
IUnknown::QueryInterface contract. This refers to _the_ unique
|
||||
IUnknown* for the object, not for any other interface (which necessarily
|
||||
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.
|
||||
|
||||
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
|
||||
@@ -0,0 +1,816 @@
|
||||
#define LOG_MODULE "iohook"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.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 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);
|
||||
|
||||
/* API hooks */
|
||||
|
||||
static BOOL STDCALL my_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 STDCALL my_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 STDCALL my_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 BOOL STDCALL my_FlushFileBuffers(HANDLE hFile);
|
||||
|
||||
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);
|
||||
|
||||
/* Links */
|
||||
|
||||
static BOOL (STDCALL *real_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 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 BOOL (STDCALL *real_ReadFile)(
|
||||
HANDLE fd,
|
||||
void *buf,
|
||||
uint32_t nbytes,
|
||||
uint32_t *nread,
|
||||
OVERLAPPED *ovl);
|
||||
|
||||
static BOOL (STDCALL *real_WriteFile)(
|
||||
HANDLE fd,
|
||||
const void *buf,
|
||||
uint32_t nbytes,
|
||||
uint32_t *nwrit,
|
||||
OVERLAPPED *ovl);
|
||||
|
||||
static DWORD (STDCALL *real_SetFilePointer)(
|
||||
HANDLE hFile,
|
||||
int32_t lDistanceToMove,
|
||||
int32_t *lpDistanceToMoveHigh,
|
||||
uint32_t dwMoveMethod);
|
||||
|
||||
static BOOL (STDCALL *real_FlushFileBuffers)(HANDLE fd);
|
||||
|
||||
/* Hook table */
|
||||
|
||||
static struct hook_symbol iohook_kernel32_syms[] = {
|
||||
/* Basic IO */
|
||||
|
||||
{
|
||||
.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,
|
||||
},
|
||||
};
|
||||
|
||||
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 irp_handler_t *iohook_handlers;
|
||||
static size_t iohook_nhandlers;
|
||||
|
||||
void iohook_init(const irp_handler_t *handlers, size_t nhandlers)
|
||||
{
|
||||
log_assert(handlers != NULL);
|
||||
log_assert(iohook_handlers == NULL);
|
||||
|
||||
iohook_handlers = handlers;
|
||||
iohook_nhandlers = nhandlers;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
log_info("IO Hook subsystem initialized");
|
||||
}
|
||||
|
||||
HANDLE iohook_open_dummy_fd(void)
|
||||
{
|
||||
HANDLE fd;
|
||||
|
||||
fd = real_CreateFileW(
|
||||
L"NUL",
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED,
|
||||
NULL);
|
||||
|
||||
if (fd == INVALID_HANDLE_VALUE) {
|
||||
log_fatal("Failed to open dummy fd: %08x", (uint32_t) GetLastError());
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static BOOL iohook_overlapped_result(
|
||||
uint32_t *syncout,
|
||||
OVERLAPPED *ovl,
|
||||
uint32_t value)
|
||||
{
|
||||
if (ovl != NULL) {
|
||||
ovl->Internal = 0; // (NTSTATUS) STATUS_SUCCESS
|
||||
ovl->InternalHigh = value;
|
||||
|
||||
if (ovl->hEvent != NULL) {
|
||||
SetEvent(ovl->hEvent);
|
||||
}
|
||||
}
|
||||
|
||||
if (syncout != NULL) {
|
||||
*syncout = value;
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
|
||||
return TRUE;
|
||||
} else {
|
||||
SetLastError(ERROR_IO_PENDING);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT irp_invoke_next(struct irp *irp)
|
||||
{
|
||||
irp_handler_t handler;
|
||||
HRESULT hr;
|
||||
|
||||
log_assert(irp != NULL);
|
||||
log_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;
|
||||
}
|
||||
} else {
|
||||
irp->next_handler = (size_t) -1;
|
||||
hr = irp_invoke_real(irp);
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT irp_invoke_real(struct irp *irp)
|
||||
{
|
||||
irp_handler_t handler;
|
||||
|
||||
log_assert(irp != NULL);
|
||||
log_assert(irp->op < lengthof(irp_real_handlers));
|
||||
|
||||
handler = irp_real_handlers[irp->op];
|
||||
|
||||
log_assert(handler != NULL);
|
||||
|
||||
return handler(irp);
|
||||
}
|
||||
|
||||
static HRESULT irp_invoke_real_open(struct irp *irp)
|
||||
{
|
||||
HANDLE fd;
|
||||
|
||||
log_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);
|
||||
|
||||
if (fd == INVALID_HANDLE_VALUE) {
|
||||
return hr_from_win32();
|
||||
}
|
||||
|
||||
irp->fd = fd;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT irp_invoke_real_close(struct irp *irp)
|
||||
{
|
||||
BOOL ok;
|
||||
|
||||
log_assert(irp != NULL);
|
||||
|
||||
ok = real_CloseHandle(irp->fd);
|
||||
|
||||
if (!ok) {
|
||||
return hr_from_win32();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT irp_invoke_real_read(struct irp *irp)
|
||||
{
|
||||
uint32_t nread;
|
||||
BOOL ok;
|
||||
|
||||
log_assert(irp != NULL);
|
||||
|
||||
ok = real_ReadFile(
|
||||
irp->fd,
|
||||
&irp->read.bytes[irp->read.pos],
|
||||
irp->read.nbytes - irp->read.pos,
|
||||
&nread,
|
||||
irp->ovl);
|
||||
|
||||
if (!ok) {
|
||||
return hr_from_win32();
|
||||
}
|
||||
|
||||
irp->read.pos += nread;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT irp_invoke_real_write(struct irp *irp)
|
||||
{
|
||||
uint32_t nwrit;
|
||||
BOOL ok;
|
||||
|
||||
log_assert(irp != NULL);
|
||||
|
||||
ok = real_WriteFile(
|
||||
irp->fd,
|
||||
&irp->write.bytes[irp->write.pos],
|
||||
irp->write.nbytes - irp->write.pos,
|
||||
&nwrit,
|
||||
irp->ovl);
|
||||
|
||||
if (!ok) {
|
||||
return hr_from_win32();
|
||||
}
|
||||
|
||||
irp->write.pos += nwrit;
|
||||
|
||||
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)
|
||||
{
|
||||
BOOL ok;
|
||||
|
||||
log_assert(irp != NULL);
|
||||
|
||||
ok = real_FlushFileBuffers(irp->fd);
|
||||
|
||||
if (!ok) {
|
||||
return hr_from_win32();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT irp_invoke_real_ioctl(struct irp *irp)
|
||||
{
|
||||
uint32_t nread;
|
||||
BOOL ok;
|
||||
|
||||
log_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);
|
||||
|
||||
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);
|
||||
|
||||
if (!ok) {
|
||||
return hr_from_win32();
|
||||
}
|
||||
|
||||
irp->read.pos = nread;
|
||||
|
||||
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)
|
||||
{
|
||||
wchar_t *wfilename;
|
||||
HANDLE fd;
|
||||
|
||||
if (lpFileName == NULL) {
|
||||
log_warning("%s: lpFileName == NULL", __func__);
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
wfilename = str_widen(lpFileName);
|
||||
fd = my_CreateFileW(
|
||||
wfilename,
|
||||
dwDesiredAccess,
|
||||
dwShareMode,
|
||||
lpSecurityAttributes,
|
||||
dwCreationDisposition, dwFlagsAndAttributes,
|
||||
hTemplateFile);
|
||||
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)
|
||||
{
|
||||
struct irp irp;
|
||||
HRESULT hr;
|
||||
|
||||
if (lpFileName == NULL) {
|
||||
log_warning("%s: lpFileName == NULL", __func__);
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
memset(&irp, 0, sizeof(irp));
|
||||
irp.op = IRP_OP_OPEN;
|
||||
irp.fd = INVALID_HANDLE_VALUE;
|
||||
irp.open_filename = lpFileName;
|
||||
irp.open_access = dwDesiredAccess;
|
||||
irp.open_share = dwShareMode;
|
||||
irp.open_sa = lpSecurityAttributes;
|
||||
irp.open_creation = dwCreationDisposition;
|
||||
irp.open_flags = dwFlagsAndAttributes;
|
||||
irp.open_tmpl = hTemplateFile;
|
||||
|
||||
hr = irp_invoke_next(&irp);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr_propagate_win32(hr, INVALID_HANDLE_VALUE);
|
||||
}
|
||||
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
|
||||
return irp.fd;
|
||||
}
|
||||
|
||||
static BOOL STDCALL my_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;
|
||||
}
|
||||
|
||||
memset(&irp, 0, sizeof(irp));
|
||||
irp.op = IRP_OP_CLOSE;
|
||||
irp.fd = hFile;
|
||||
|
||||
hr = irp_invoke_next(&irp);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr_propagate_win32(hr, FALSE);
|
||||
}
|
||||
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL STDCALL my_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__);
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (lpOverlapped == NULL) {
|
||||
if (lpNumberOfBytesRead == NULL) {
|
||||
log_warning( "%s: lpNumberOfBytesRead must be supplied in "
|
||||
"synchronous mode",
|
||||
__func__);
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*lpNumberOfBytesRead = 0;
|
||||
}
|
||||
|
||||
memset(&irp, 0, sizeof(irp));
|
||||
irp.op = IRP_OP_READ;
|
||||
irp.fd = hFile;
|
||||
irp.ovl = lpOverlapped;
|
||||
irp.read.bytes = lpBuffer;
|
||||
irp.read.nbytes = nNumberOfBytesToRead;
|
||||
irp.read.pos = 0;
|
||||
|
||||
hr = irp_invoke_next(&irp);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr_propagate_win32(hr, FALSE);
|
||||
}
|
||||
|
||||
log_assert(irp.read.pos <= irp.read.nbytes);
|
||||
|
||||
return iohook_overlapped_result(
|
||||
lpNumberOfBytesRead,
|
||||
lpOverlapped,
|
||||
irp.read.pos);
|
||||
}
|
||||
|
||||
static BOOL STDCALL my_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__);
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (lpOverlapped == NULL) {
|
||||
if (lpNumberOfBytesWritten == NULL) {
|
||||
log_warning( "%s: lpNumberOfBytesWritten must be supplied in "
|
||||
"synchronous mode",
|
||||
__func__);
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*lpNumberOfBytesWritten = 0;
|
||||
}
|
||||
|
||||
memset(&irp, 0, sizeof(irp));
|
||||
irp.op = IRP_OP_WRITE;
|
||||
irp.fd = hFile;
|
||||
irp.ovl = lpOverlapped;
|
||||
irp.write.bytes = lpBuffer;
|
||||
irp.write.nbytes = nNumberOfBytesToWrite;
|
||||
irp.write.pos = 0;
|
||||
|
||||
hr = irp_invoke_next(&irp);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr_propagate_win32(hr, FALSE);
|
||||
}
|
||||
|
||||
log_assert(irp.write.pos <= irp.write.nbytes);
|
||||
|
||||
return iohook_overlapped_result(
|
||||
lpNumberOfBytesWritten,
|
||||
lpOverlapped,
|
||||
irp.write.pos);
|
||||
}
|
||||
|
||||
static DWORD STDCALL my_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;
|
||||
}
|
||||
|
||||
memset(&irp, 0, sizeof(irp));
|
||||
irp.op = IRP_OP_SEEK;
|
||||
irp.fd = hFile;
|
||||
irp.seek_origin = dwMoveMethod;
|
||||
|
||||
/* This is a clumsy API. In 32-bit mode lDistanceToMove is a signed 32-bit
|
||||
int, but in 64-bit mode it is a 32-bit UNsigned int. Care must be taken
|
||||
with sign-extension vs zero-extension here. */
|
||||
|
||||
if (lpDistanceToMoveHigh != NULL) {
|
||||
irp.seek_offset = ((( int64_t) *lpDistanceToMoveHigh) << 32)
|
||||
| ((uint64_t) lDistanceToMove);
|
||||
} else {
|
||||
irp.seek_offset = (int64_t) lDistanceToMove;
|
||||
}
|
||||
|
||||
hr = irp_invoke_next(&irp);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr_propagate_win32(hr, INVALID_SET_FILE_POINTER);
|
||||
}
|
||||
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
|
||||
if (lpDistanceToMoveHigh != NULL) {
|
||||
*lpDistanceToMoveHigh = irp.seek_pos >> 32;
|
||||
}
|
||||
|
||||
return (DWORD) irp.seek_pos;
|
||||
}
|
||||
|
||||
static BOOL STDCALL my_FlushFileBuffers(HANDLE hFile)
|
||||
{
|
||||
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) {
|
||||
log_warning("%s: Invalid file descriptor %p", __func__, hFile);
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
memset(&irp, 0, sizeof(irp));
|
||||
irp.op = IRP_OP_FSYNC;
|
||||
irp.fd = hFile;
|
||||
|
||||
hr = irp_invoke_next(&irp);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr_propagate_win32(hr, FALSE);
|
||||
}
|
||||
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
|
||||
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)
|
||||
{
|
||||
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 (lpOverlapped == NULL) {
|
||||
if (lpBytesReturned == NULL) {
|
||||
log_warning(
|
||||
"%s: lpBytesReturned must be supplied in synchronous mode",
|
||||
__func__);
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*lpBytesReturned = 0;
|
||||
}
|
||||
|
||||
memset(&irp, 0, sizeof(irp));
|
||||
irp.op = IRP_OP_IOCTL;
|
||||
irp.fd = hFile;
|
||||
irp.ovl = lpOverlapped;
|
||||
irp.ioctl = dwIoControlCode;
|
||||
|
||||
if (lpInBuffer != NULL) {
|
||||
irp.write.bytes = lpInBuffer;
|
||||
irp.write.nbytes = nInBufferSize;
|
||||
}
|
||||
|
||||
if (lpOutBuffer != NULL) {
|
||||
irp.read.bytes = lpOutBuffer;
|
||||
irp.read.nbytes = nOutBufferSize;
|
||||
}
|
||||
|
||||
hr = irp_invoke_next(&irp);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef HOOK_IOHOOK_H
|
||||
#define HOOK_IOHOOK_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/iobuf.h"
|
||||
|
||||
enum irp_op {
|
||||
IRP_OP_OPEN,
|
||||
IRP_OP_CLOSE,
|
||||
IRP_OP_READ,
|
||||
IRP_OP_WRITE,
|
||||
IRP_OP_IOCTL,
|
||||
IRP_OP_FSYNC,
|
||||
IRP_OP_SEEK,
|
||||
};
|
||||
|
||||
struct irp {
|
||||
enum irp_op op;
|
||||
size_t next_handler;
|
||||
HANDLE fd;
|
||||
OVERLAPPED *ovl;
|
||||
struct const_iobuf write;
|
||||
struct iobuf read;
|
||||
uint32_t ioctl;
|
||||
const wchar_t *open_filename;
|
||||
uint32_t open_access;
|
||||
uint32_t open_share;
|
||||
SECURITY_ATTRIBUTES *open_sa;
|
||||
uint32_t open_creation;
|
||||
uint32_t open_flags;
|
||||
HANDLE *open_tmpl;
|
||||
uint32_t seek_origin;
|
||||
int64_t seek_offset;
|
||||
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);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,286 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 void *pe_offset(void *ptr, size_t off)
|
||||
{
|
||||
uint8_t *base;
|
||||
|
||||
if (off == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
base = ptr;
|
||||
|
||||
return base + off;
|
||||
}
|
||||
|
||||
static const void *pe_offsetc(const void *ptr, size_t off)
|
||||
{
|
||||
const uint8_t *base;
|
||||
|
||||
if (off == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
base = ptr;
|
||||
|
||||
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;
|
||||
const IMAGE_NT_HEADERS *nth;
|
||||
|
||||
dh = (IMAGE_DOS_HEADER *) pe;
|
||||
nth = pe_offsetc(pe, dh->e_lfanew);
|
||||
|
||||
return nth;
|
||||
}
|
||||
|
||||
const pe_iid_t *pe_iid_get_first(HMODULE pe)
|
||||
{
|
||||
const IMAGE_NT_HEADERS *nth;
|
||||
const IMAGE_IMPORT_DESCRIPTOR *iid;
|
||||
|
||||
nth = pe_get_nt_header(pe);
|
||||
iid = pe_offsetc(pe, nth->OptionalHeader
|
||||
.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
|
||||
|
||||
if (iid == NULL || iid->Name == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return iid;
|
||||
}
|
||||
|
||||
const char *pe_iid_get_name(HMODULE pe, const pe_iid_t *iid)
|
||||
{
|
||||
return pe_offsetc(pe, iid->Name);
|
||||
}
|
||||
|
||||
const pe_iid_t *pe_iid_get_next(HMODULE pe, const pe_iid_t *iid)
|
||||
{
|
||||
const IMAGE_IMPORT_DESCRIPTOR *iid_next;
|
||||
|
||||
iid_next = iid + 1;
|
||||
|
||||
if (iid_next->Name != 0) {
|
||||
return iid_next;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool 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;
|
||||
void **pointers;
|
||||
|
||||
if (iid->OriginalFirstThunk == 0) {
|
||||
log_warning("OriginalFirstThunk == 0");
|
||||
}
|
||||
|
||||
import_rvas = pe_offset(pe, iid->OriginalFirstThunk);
|
||||
|
||||
if (import_rvas[n] == 0) {
|
||||
/* End of imports */
|
||||
entry->name = NULL;
|
||||
entry->ordinal = 0;
|
||||
entry->ppointer = NULL;
|
||||
|
||||
return false;
|
||||
} else if (import_rvas[n] & INTPTR_MIN) {
|
||||
/* Ordinal import */
|
||||
entry->name = NULL;
|
||||
entry->ordinal = (uint16_t) import_rvas[n];
|
||||
} else {
|
||||
/* Named import */
|
||||
import = pe_offsetc(pe, import_rvas[n]);
|
||||
entry->name = (const char *) import->Name; /* Not an RVA */
|
||||
entry->ordinal = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void *pe_get_export(HMODULE pe, const char *name, uint16_t ord)
|
||||
{
|
||||
const IMAGE_NT_HEADERS *nth;
|
||||
const IMAGE_EXPORT_DIRECTORY *ied;
|
||||
const uint32_t *name_rvas;
|
||||
const uint32_t *target_rvas;
|
||||
DWORD i;
|
||||
|
||||
nth = pe_get_nt_header(pe);
|
||||
ied = pe_offsetc(pe, nth->OptionalHeader
|
||||
.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].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]);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
} else if (ord - ied->Base < ied->NumberOfFunctions) {
|
||||
return pe_offset(pe, target_rvas[ord - ied->Base]);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL pe_call_dll_main(HMODULE pe, uint32_t reason, void *ctx)
|
||||
{
|
||||
const IMAGE_NT_HEADERS *nth;
|
||||
dll_main_t dll_main;
|
||||
|
||||
nth = pe_get_nt_header(pe);
|
||||
dll_main = pe_offset(pe, nth->OptionalHeader.AddressOfEntryPoint);
|
||||
|
||||
return dll_main(pe, reason, ctx);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef HOOK_PE_H
|
||||
#define HOOK_PE_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef IMAGE_IMPORT_DESCRIPTOR pe_iid_t;
|
||||
|
||||
struct pe_iat_entry {
|
||||
const char *name;
|
||||
uint16_t ordinal;
|
||||
void **ppointer;
|
||||
};
|
||||
|
||||
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);
|
||||
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
|
||||
@@ -0,0 +1,68 @@
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
|
||||
#include "hook/peb.h"
|
||||
|
||||
#include "util/defs.h"
|
||||
#include "util/str.h"
|
||||
|
||||
static const PEB *peb_get(void)
|
||||
{
|
||||
#ifdef __amd64
|
||||
return (const PEB *) __readgsqword(0x60);
|
||||
#else
|
||||
return (const PEB *) __readfsdword(0x30);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
const peb_dll_t *peb_dll_get_first(void)
|
||||
{
|
||||
const PEB *peb;
|
||||
const LIST_ENTRY *node;
|
||||
|
||||
peb = peb_get();
|
||||
node = peb->Ldr->InMemoryOrderModuleList.Flink;
|
||||
|
||||
return containerof(node, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
|
||||
}
|
||||
|
||||
const peb_dll_t *peb_dll_get_next(const peb_dll_t *dll)
|
||||
{
|
||||
const PEB *peb;
|
||||
const LIST_ENTRY *node;
|
||||
|
||||
peb = peb_get();
|
||||
node = dll->InMemoryOrderLinks.Flink;
|
||||
|
||||
if (node == peb->Ldr->InMemoryOrderModuleList.Flink) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return containerof(node, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
|
||||
}
|
||||
|
||||
HMODULE peb_dll_get_base(const peb_dll_t *dll)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef HOOK_PEB_H
|
||||
#define HOOK_PEB_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
|
||||
typedef LDR_DATA_TABLE_ENTRY peb_dll_t;
|
||||
|
||||
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
|
||||
@@ -0,0 +1,121 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hook/pe.h"
|
||||
#include "hook/peb.h"
|
||||
#include "hook/table.h"
|
||||
|
||||
#include "util/mem.h"
|
||||
|
||||
static void hook_table_apply_to_all(
|
||||
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);
|
||||
|
||||
static bool hook_table_match_proc(
|
||||
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 peb_dll_t *dll;
|
||||
HMODULE pe;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
hook_table_apply(pe, depname, syms, nsyms);
|
||||
}
|
||||
}
|
||||
|
||||
void hook_table_apply(
|
||||
HMODULE target,
|
||||
const char *depname,
|
||||
const struct hook_symbol *syms,
|
||||
size_t nsyms)
|
||||
{
|
||||
const pe_iid_t *iid;
|
||||
const char *iid_name;
|
||||
|
||||
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)) {
|
||||
iid_name = pe_iid_get_name(target, iid);
|
||||
|
||||
if (_stricmp(iid_name, depname) == 0) {
|
||||
hook_table_apply_to_iid(target, iid, syms, nsyms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void hook_table_apply_to_iid(
|
||||
HMODULE target,
|
||||
const pe_iid_t *iid,
|
||||
const struct hook_symbol *syms,
|
||||
size_t nsyms)
|
||||
{
|
||||
struct pe_iat_entry iate;
|
||||
size_t i;
|
||||
size_t j;
|
||||
const struct hook_symbol *sym;
|
||||
|
||||
i = 0;
|
||||
|
||||
while (pe_iid_get_iat_entry(target, iid, i++, &iate)) {
|
||||
for (j = 0 ; j < nsyms ; j++) {
|
||||
sym = &syms[j];
|
||||
|
||||
if (hook_table_match_proc(&iate, sym)) {
|
||||
if (sym->link != NULL && *sym->link == NULL) {
|
||||
*sym->link = *iate.ppointer;
|
||||
}
|
||||
|
||||
pe_patch_pointer(iate.ppointer, sym->patch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (sym->ordinal != 0 && sym->ordinal == iate->ordinal) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef HOOK_TABLE_H
|
||||
#define HOOK_TABLE_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct hook_symbol {
|
||||
const char *name;
|
||||
uint16_t ordinal;
|
||||
void *patch;
|
||||
void **link;
|
||||
};
|
||||
|
||||
void hook_table_apply(
|
||||
HMODULE target,
|
||||
const char *depname,
|
||||
const struct hook_symbol *syms,
|
||||
size_t nsyms);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user