From 4e07e943a4c71bf29d97e6ac4c615f827410ec45 Mon Sep 17 00:00:00 2001 From: Guus Waals <_@guusw.nl> Date: Fri, 13 Sep 2024 23:01:27 +0800 Subject: [PATCH] Initial commit --- .gitignore | 3 + 3rdparty/LICENSE | 21 + 3rdparty/hooker.c | 1462 +++++++++++++++++++++++++++++++++++++++++++ 3rdparty/hooker.h | 162 +++++ 3rdparty/hooker.hpp | 354 +++++++++++ CMakeLists.txt | 31 + exports.def | 14 + stub.cpp | 90 +++ vsdbg.txt | 13 + 9 files changed, 2150 insertions(+) create mode 100644 .gitignore create mode 100644 3rdparty/LICENSE create mode 100644 3rdparty/hooker.c create mode 100644 3rdparty/hooker.h create mode 100644 3rdparty/hooker.hpp create mode 100644 CMakeLists.txt create mode 100644 exports.def create mode 100644 stub.cpp create mode 100644 vsdbg.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e11405f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build +*.txt +*.zip \ No newline at end of file diff --git a/3rdparty/LICENSE b/3rdparty/LICENSE new file mode 100644 index 0000000..7ffcaf4 --- /dev/null +++ b/3rdparty/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Rokas Kupstys + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/3rdparty/hooker.c b/3rdparty/hooker.c new file mode 100644 index 0000000..2576ba3 --- /dev/null +++ b/3rdparty/hooker.c @@ -0,0 +1,1462 @@ +/* + * License agreement + * + * Hacker Disassembler Engine 32 C + * Copyright (c) 2008-2009, Vyacheslav Patkov. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#if _WIN32 +# undef NOMINMAX +# include +# define MIN min +# define MAX max +#elif defined(__linux__) || defined(__APPLE__) +# include +# include +# include +# include +# include +#else +typedef char unsupported_platform[-1]; +#endif +#if __amd64__ || _M_X64 +# define HOOKER_X64 1 +#endif + +#include "hooker.h" +#include + +#if __amd64__ || _M_X64 + +#pragma pack(push,1) + +typedef struct { + uint8_t len; + uint8_t p_rep; + uint8_t p_lock; + uint8_t p_seg; + uint8_t p_66; + uint8_t p_67; + uint8_t rex; + uint8_t rex_w; + uint8_t rex_r; + uint8_t rex_x; + uint8_t rex_b; + uint8_t opcode; + uint8_t opcode2; + uint8_t modrm; + uint8_t modrm_mod; + uint8_t modrm_reg; + uint8_t modrm_rm; + uint8_t sib; + uint8_t sib_scale; + uint8_t sib_index; + uint8_t sib_base; + union { + uint8_t imm8; + uint16_t imm16; + uint32_t imm32; + uint64_t imm64; + } imm; + union { + uint8_t disp8; + uint16_t disp16; + uint32_t disp32; + } disp; + uint32_t flags; +} hde64s; + +#define C_NONE 0x00 +#define C_MODRM 0x01 +#define C_IMM8 0x02 +#define C_IMM16 0x04 +#define C_IMM_P66 0x10 +#define C_REL8 0x20 +#define C_REL32 0x40 +#define C_GROUP 0x80 +#define C_ERROR 0xff + +#define PRE_ANY 0x00 +#define PRE_NONE 0x01 +#define PRE_F2 0x02 +#define PRE_F3 0x04 +#define PRE_66 0x08 +#define PRE_67 0x10 +#define PRE_LOCK 0x20 +#define PRE_SEG 0x40 +#define PRE_ALL 0xff + +#define DELTA_OPCODES 0x4a +#define DELTA_FPU_REG 0xfd +#define DELTA_FPU_MODRM 0x104 +#define DELTA_PREFIXES 0x13c +#define DELTA_OP_LOCK_OK 0x1ae +#define DELTA_OP2_LOCK_OK 0x1c6 +#define DELTA_OP_ONLY_MEM 0x1d8 +#define DELTA_OP2_ONLY_MEM 0x1e7 + +unsigned char hde64_table[] = { + 0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5, + 0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1, + 0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea, + 0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0, + 0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab, + 0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92, + 0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90, + 0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b, + 0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b, + 0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc, + 0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20, + 0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff, + 0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00, + 0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01, + 0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10, + 0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00, + 0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00, + 0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00, + 0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40, + 0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43, + 0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, + 0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40, + 0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06, + 0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07, + 0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, + 0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10, + 0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00, + 0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb, + 0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff, + 0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09, + 0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff, + 0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08, + 0x00,0xf0,0x02,0x00 +}; + +#define F_MODRM 0x00000001 +#define F_SIB 0x00000002 +#define F_IMM8 0x00000004 +#define F_IMM16 0x00000008 +#define F_IMM32 0x00000010 +#define F_IMM64 0x00000020 +#define F_DISP8 0x00000040 +#define F_DISP16 0x00000080 +#define F_DISP32 0x00000100 +#define F_RELATIVE 0x00000200 +#define F_ERROR 0x00001000 +#define F_ERROR_OPCODE 0x00002000 +#define F_ERROR_LENGTH 0x00004000 +#define F_ERROR_LOCK 0x00008000 +#define F_ERROR_OPERAND 0x00010000 +#define F_PREFIX_REPNZ 0x01000000 +#define F_PREFIX_REPX 0x02000000 +#define F_PREFIX_REP 0x03000000 +#define F_PREFIX_66 0x04000000 +#define F_PREFIX_67 0x08000000 +#define F_PREFIX_LOCK 0x10000000 +#define F_PREFIX_SEG 0x20000000 +#define F_PREFIX_REX 0x40000000 +#define F_PREFIX_ANY 0x7f000000 + +#define PREFIX_SEGMENT_CS 0x2e +#define PREFIX_SEGMENT_SS 0x36 +#define PREFIX_SEGMENT_DS 0x3e +#define PREFIX_SEGMENT_ES 0x26 +#define PREFIX_SEGMENT_FS 0x64 +#define PREFIX_SEGMENT_GS 0x65 +#define PREFIX_LOCK 0xf0 +#define PREFIX_REPNZ 0xf2 +#define PREFIX_REPX 0xf3 +#define PREFIX_OPERAND_SIZE 0x66 +#define PREFIX_ADDRESS_SIZE 0x67 + +unsigned int hde64_disasm(const void *code, hde64s *hs) +{ + uint8_t x, c, *p = (uint8_t*)code, cflags, opcode, pref = 0; + uint8_t *ht = hde64_table, m_mod, m_reg, m_rm, disp_size = 0; + uint8_t op64 = 0; + + memset(hs,0,sizeof(hde64s)); + + for (x = 16; x; x--) + switch (c = *p++) { + case 0xf3: + hs->p_rep = c; + pref |= PRE_F3; + break; + case 0xf2: + hs->p_rep = c; + pref |= PRE_F2; + break; + case 0xf0: + hs->p_lock = c; + pref |= PRE_LOCK; + break; + case 0x26: case 0x2e: case 0x36: + case 0x3e: case 0x64: case 0x65: + hs->p_seg = c; + pref |= PRE_SEG; + break; + case 0x66: + hs->p_66 = c; + pref |= PRE_66; + break; + case 0x67: + hs->p_67 = c; + pref |= PRE_67; + break; + default: + goto pref_done; + } +pref_done: + + hs->flags = (uint32_t)pref << 23; + + if (!pref) + pref |= PRE_NONE; + + if ((c & 0xf0) == 0x40) { + hs->flags |= F_PREFIX_REX; + if ((hs->rex_w = (c & 0xf) >> 3) && (*p & 0xf8) == 0xb8) + op64++; + hs->rex_r = (c & 7) >> 2; + hs->rex_x = (c & 3) >> 1; + hs->rex_b = c & 1; + if (((c = *p++) & 0xf0) == 0x40) { + opcode = c; + goto error_opcode; + } + } + + if ((hs->opcode = c) == 0x0f) { + hs->opcode2 = c = *p++; + ht += DELTA_OPCODES; + } else if (c >= 0xa0 && c <= 0xa3) { + op64++; + if (pref & PRE_67) + pref |= PRE_66; + else + pref &= ~PRE_66; + } + + opcode = c; + cflags = ht[ht[opcode / 4] + (opcode % 4)]; + + if (cflags == C_ERROR) { +error_opcode: + hs->flags |= F_ERROR | F_ERROR_OPCODE; + cflags = 0; + if ((opcode & -3) == 0x24) + cflags++; + } + + x = 0; + if (cflags & C_GROUP) { + uint16_t t; + t = *(uint16_t*)(ht + (cflags & 0x7f)); + cflags = (uint8_t)t; + x = (uint8_t)(t >> 8); + } + + if (hs->opcode2) { + ht = hde64_table + DELTA_PREFIXES; + if (ht[ht[opcode / 4] + (opcode % 4)] & pref) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + } + + if (cflags & C_MODRM) { + hs->flags |= F_MODRM; + hs->modrm = c = *p++; + hs->modrm_mod = m_mod = c >> 6; + hs->modrm_rm = m_rm = c & 7; + hs->modrm_reg = m_reg = (c & 0x3f) >> 3; + + if (x && ((x << m_reg) & 0x80)) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + + if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) { + uint8_t t = opcode - 0xd9; + if (m_mod == 3) { + ht = hde64_table + DELTA_FPU_MODRM + t*8; + t = ht[m_reg] << m_rm; + } else { + ht = hde64_table + DELTA_FPU_REG; + t = ht[t] << m_reg; + } + if (t & 0x80) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + } + + if (pref & PRE_LOCK) { + if (m_mod == 3) { + hs->flags |= F_ERROR | F_ERROR_LOCK; + } else { + uint8_t* table_end, op = opcode; + if (hs->opcode2) { + ht = hde64_table + DELTA_OP2_LOCK_OK; + table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK; + } else { + ht = hde64_table + DELTA_OP_LOCK_OK; + table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK; + op &= -2; + } + for (; ht != table_end; ht++) + if (*ht++ == op) { + if (!((*ht << m_reg) & 0x80)) + goto no_lock_error; + else + break; + } + hs->flags |= F_ERROR | F_ERROR_LOCK; +no_lock_error: + ; + } + } + + if (hs->opcode2) { + switch (opcode) { + case 0x20: case 0x22: + m_mod = 3; + if (m_reg > 4 || m_reg == 1) + goto error_operand; + else + goto no_error_operand; + case 0x21: case 0x23: + m_mod = 3; + if (m_reg == 4 || m_reg == 5) + goto error_operand; + else + goto no_error_operand; + } + } else { + switch (opcode) { + case 0x8c: + if (m_reg > 5) + goto error_operand; + else + goto no_error_operand; + case 0x8e: + if (m_reg == 1 || m_reg > 5) + goto error_operand; + else + goto no_error_operand; + } + } + + if (m_mod == 3) { + uint8_t* table_end; + if (hs->opcode2) { + ht = hde64_table + DELTA_OP2_ONLY_MEM; + table_end = ht + sizeof(hde64_table) - DELTA_OP2_ONLY_MEM; + } else { + ht = hde64_table + DELTA_OP_ONLY_MEM; + table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM; + } + for (; ht != table_end; ht += 2) + if (*ht++ == opcode) { + if (*ht++ & pref && !((*ht << m_reg) & 0x80)) + goto error_operand; + else + break; + } + goto no_error_operand; + } else if (hs->opcode2) { + switch (opcode) { + case 0x50: case 0xd7: case 0xf7: + if (pref & (PRE_NONE | PRE_66)) + goto error_operand; + break; + case 0xd6: + if (pref & (PRE_F2 | PRE_F3)) + goto error_operand; + break; + case 0xc5: + goto error_operand; + } + goto no_error_operand; + } else + goto no_error_operand; + +error_operand: + hs->flags |= F_ERROR | F_ERROR_OPERAND; +no_error_operand: + + c = *p++; + if (m_reg <= 1) { + if (opcode == 0xf6) + cflags |= C_IMM8; + else if (opcode == 0xf7) + cflags |= C_IMM_P66; + } + + switch (m_mod) { + case 0: + if (pref & PRE_67) { + if (m_rm == 6) + disp_size = 2; + } else + if (m_rm == 5) + disp_size = 4; + break; + case 1: + disp_size = 1; + break; + case 2: + disp_size = 2; + if (!(pref & PRE_67)) + disp_size <<= 1; + } + + if (m_mod != 3 && m_rm == 4) { + hs->flags |= F_SIB; + p++; + hs->sib = c; + hs->sib_scale = c >> 6; + hs->sib_index = (c & 0x3f) >> 3; + if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1)) + disp_size = 4; + } + + p--; + switch (disp_size) { + case 1: + hs->flags |= F_DISP8; + hs->disp.disp8 = *p; + break; + case 2: + hs->flags |= F_DISP16; + hs->disp.disp16 = *(uint16_t*)p; + break; + case 4: + hs->flags |= F_DISP32; + hs->disp.disp32 = *(uint32_t*)p; + } + p += disp_size; + } else if (pref & PRE_LOCK) + hs->flags |= F_ERROR | F_ERROR_LOCK; + + if (cflags & C_IMM_P66) { + if (cflags & C_REL32) { + if (pref & PRE_66) { + hs->flags |= F_IMM16 | F_RELATIVE; + hs->imm.imm16 = *(uint16_t*)p; + p += 2; + goto disasm_done; + } + goto rel32_ok; + } + if (op64) { + hs->flags |= F_IMM64; + hs->imm.imm64 = *(uint64_t*)p; + p += 8; + } else if (!(pref & PRE_66)) { + hs->flags |= F_IMM32; + hs->imm.imm32 = *(uint32_t*)p; + p += 4; + } else + goto imm16_ok; + } + + + if (cflags & C_IMM16) { +imm16_ok: + hs->flags |= F_IMM16; + hs->imm.imm16 = *(uint16_t*)p; + p += 2; + } + if (cflags & C_IMM8) { + hs->flags |= F_IMM8; + hs->imm.imm8 = *p++; + } + + if (cflags & C_REL32) { +rel32_ok: + hs->flags |= F_IMM32 | F_RELATIVE; + hs->imm.imm32 = *(uint32_t*)p; + p += 4; + } else if (cflags & C_REL8) { + hs->flags |= F_IMM8 | F_RELATIVE; + hs->imm.imm8 = *p++; + } + +disasm_done: + + if ((hs->len = (uint8_t)(p-(uint8_t*)code)) > 15) { + hs->flags |= F_ERROR | F_ERROR_LENGTH; + hs->len = 15; + } + + return (unsigned int)hs->len; +} + +#elif __i386__ || _M_IX86 + +#pragma pack(push,1) + +typedef struct { + uint8_t len; + uint8_t p_rep; + uint8_t p_lock; + uint8_t p_seg; + uint8_t p_66; + uint8_t p_67; + uint8_t opcode; + uint8_t opcode2; + uint8_t modrm; + uint8_t modrm_mod; + uint8_t modrm_reg; + uint8_t modrm_rm; + uint8_t sib; + uint8_t sib_scale; + uint8_t sib_index; + uint8_t sib_base; + union { + uint8_t imm8; + uint16_t imm16; + uint32_t imm32; + } imm; + union { + uint8_t disp8; + uint16_t disp16; + uint32_t disp32; + } disp; + uint32_t flags; +} hde32s; + +#pragma pack(pop) + +#define C_NONE 0x00 +#define C_MODRM 0x01 +#define C_IMM8 0x02 +#define C_IMM16 0x04 +#define C_IMM_P66 0x10 +#define C_REL8 0x20 +#define C_REL32 0x40 +#define C_GROUP 0x80 +#define C_ERROR 0xff + +#define PRE_ANY 0x00 +#define PRE_NONE 0x01 +#define PRE_F2 0x02 +#define PRE_F3 0x04 +#define PRE_66 0x08 +#define PRE_67 0x10 +#define PRE_LOCK 0x20 +#define PRE_SEG 0x40 +#define PRE_ALL 0xff + +#define DELTA_OPCODES 0x4a +#define DELTA_FPU_REG 0xf1 +#define DELTA_FPU_MODRM 0xf8 +#define DELTA_PREFIXES 0x130 +#define DELTA_OP_LOCK_OK 0x1a1 +#define DELTA_OP2_LOCK_OK 0x1b9 +#define DELTA_OP_ONLY_MEM 0x1cb +#define DELTA_OP2_ONLY_MEM 0x1da + +unsigned char hde32_table[] = { + 0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3, + 0xa8,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xac,0xaa,0xb2,0xaa,0x9f,0x9f, + 0x9f,0x9f,0xb5,0xa3,0xa3,0xa4,0xaa,0xaa,0xba,0xaa,0x96,0xaa,0xa8,0xaa,0xc3, + 0xc3,0x96,0x96,0xb7,0xae,0xd6,0xbd,0xa3,0xc5,0xa3,0xa3,0x9f,0xc3,0x9c,0xaa, + 0xaa,0xac,0xaa,0xbf,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0x90, + 0x82,0x7d,0x97,0x59,0x59,0x59,0x59,0x59,0x7f,0x59,0x59,0x60,0x7d,0x7f,0x7f, + 0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x9a,0x88,0x7d, + 0x59,0x50,0x50,0x50,0x50,0x59,0x59,0x59,0x59,0x61,0x94,0x61,0x9e,0x59,0x59, + 0x85,0x59,0x92,0xa3,0x60,0x60,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59, + 0x59,0x59,0x9f,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xcc,0x01,0xbc,0x03,0xf0, + 0x10,0x10,0x10,0x10,0x50,0x50,0x50,0x50,0x14,0x20,0x20,0x20,0x20,0x01,0x01, + 0x01,0x01,0xc4,0x02,0x10,0x00,0x00,0x00,0x00,0x01,0x01,0xc0,0xc2,0x10,0x11, + 0x02,0x03,0x11,0x03,0x03,0x04,0x00,0x00,0x14,0x00,0x02,0x00,0x00,0xc6,0xc8, + 0x02,0x02,0x02,0x02,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xca, + 0x01,0x01,0x01,0x00,0x06,0x00,0x04,0x00,0xc0,0xc2,0x01,0x01,0x03,0x01,0xff, + 0xff,0x01,0x00,0x03,0xc4,0xc4,0xc6,0x03,0x01,0x01,0x01,0xff,0x03,0x03,0x03, + 0xc8,0x40,0x00,0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00, + 0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xff,0xff,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x7f,0x00,0x00,0xff,0x4a,0x4a,0x4a,0x4a,0x4b,0x52,0x4a,0x4a,0x4a,0x4a,0x4f, + 0x4c,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x55,0x45,0x40,0x4a,0x4a,0x4a, + 0x45,0x59,0x4d,0x46,0x4a,0x5d,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a, + 0x4a,0x4a,0x4a,0x4a,0x4a,0x61,0x63,0x67,0x4e,0x4a,0x4a,0x6b,0x6d,0x4a,0x4a, + 0x45,0x6d,0x4a,0x4a,0x44,0x45,0x4a,0x4a,0x00,0x00,0x00,0x02,0x0d,0x06,0x06, + 0x06,0x06,0x0e,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x00,0x06,0x06,0x02,0x06, + 0x00,0x0a,0x0a,0x07,0x07,0x06,0x02,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, + 0x04,0x04,0x00,0x00,0x00,0x0e,0x05,0x06,0x06,0x06,0x01,0x06,0x00,0x00,0x08, + 0x00,0x10,0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01, + 0x86,0x00,0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba, + 0xf8,0xbb,0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00, + 0xc4,0xff,0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00, + 0x13,0x09,0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07, + 0xb2,0xff,0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf, + 0xe7,0x08,0x00,0xf0,0x02,0x00 +}; + +#ifdef _MSC_VER +#pragma warning(disable:4701) +#endif + +#define F_MODRM 0x00000001 +#define F_SIB 0x00000002 +#define F_IMM8 0x00000004 +#define F_IMM16 0x00000008 +#define F_IMM32 0x00000010 +#define F_DISP8 0x00000020 +#define F_DISP16 0x00000040 +#define F_DISP32 0x00000080 +#define F_RELATIVE 0x00000100 +#define F_2IMM16 0x00000800 +#define F_ERROR 0x00001000 +#define F_ERROR_OPCODE 0x00002000 +#define F_ERROR_LENGTH 0x00004000 +#define F_ERROR_LOCK 0x00008000 +#define F_ERROR_OPERAND 0x00010000 +#define F_PREFIX_REPNZ 0x01000000 +#define F_PREFIX_REPX 0x02000000 +#define F_PREFIX_REP 0x03000000 +#define F_PREFIX_66 0x04000000 +#define F_PREFIX_67 0x08000000 +#define F_PREFIX_LOCK 0x10000000 +#define F_PREFIX_SEG 0x20000000 +#define F_PREFIX_ANY 0x3f000000 + +#define PREFIX_SEGMENT_CS 0x2e +#define PREFIX_SEGMENT_SS 0x36 +#define PREFIX_SEGMENT_DS 0x3e +#define PREFIX_SEGMENT_ES 0x26 +#define PREFIX_SEGMENT_FS 0x64 +#define PREFIX_SEGMENT_GS 0x65 +#define PREFIX_LOCK 0xf0 +#define PREFIX_REPNZ 0xf2 +#define PREFIX_REPX 0xf3 +#define PREFIX_OPERAND_SIZE 0x66 +#define PREFIX_ADDRESS_SIZE 0x67 + +unsigned int hde32_disasm(const void *code, hde32s *hs) +{ + uint8_t x, c, *p = (uint8_t*)code, cflags, opcode, pref = 0; + uint8_t* ht = hde32_table, m_mod, m_reg, m_rm, disp_size = 0; + + memset(hs,0,sizeof(hde32s)); + + for (x = 16; x; x--) + switch (c = *p++) { + case 0xf3: + hs->p_rep = c; + pref |= PRE_F3; + break; + case 0xf2: + hs->p_rep = c; + pref |= PRE_F2; + break; + case 0xf0: + hs->p_lock = c; + pref |= PRE_LOCK; + break; + case 0x26: case 0x2e: case 0x36: + case 0x3e: case 0x64: case 0x65: + hs->p_seg = c; + pref |= PRE_SEG; + break; + case 0x66: + hs->p_66 = c; + pref |= PRE_66; + break; + case 0x67: + hs->p_67 = c; + pref |= PRE_67; + break; + default: + goto pref_done; + } +pref_done: + + hs->flags = (uint32_t)pref << 23; + + if (!pref) + pref |= PRE_NONE; + + if ((hs->opcode = c) == 0x0f) { + hs->opcode2 = c = *p++; + ht += DELTA_OPCODES; + } else if (c >= 0xa0 && c <= 0xa3) { + if (pref & PRE_67) + pref |= PRE_66; + else + pref &= ~PRE_66; + } + + opcode = c; + cflags = ht[ht[opcode / 4] + (opcode % 4)]; + + if (cflags == C_ERROR) { + hs->flags |= F_ERROR | F_ERROR_OPCODE; + cflags = 0; + if ((opcode & -3) == 0x24) + cflags++; + } + + x = 0; + if (cflags & C_GROUP) { + uint16_t t; + t = *(uint16_t*)(ht + (cflags & 0x7f)); + cflags = (uint8_t)t; + x = (uint8_t)(t >> 8); + } + + if (hs->opcode2) { + ht = hde32_table + DELTA_PREFIXES; + if (ht[ht[opcode / 4] + (opcode % 4)] & pref) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + } + + if (cflags & C_MODRM) { + hs->flags |= F_MODRM; + hs->modrm = c = *p++; + hs->modrm_mod = m_mod = c >> 6; + hs->modrm_rm = m_rm = c & 7; + hs->modrm_reg = m_reg = (c & 0x3f) >> 3; + + if (x && ((x << m_reg) & 0x80)) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + + if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) { + uint8_t t = opcode - 0xd9; + if (m_mod == 3) { + ht = hde32_table + DELTA_FPU_MODRM + t*8; + t = ht[m_reg] << m_rm; + } else { + ht = hde32_table + DELTA_FPU_REG; + t = ht[t] << m_reg; + } + if (t & 0x80) + hs->flags |= F_ERROR | F_ERROR_OPCODE; + } + + if (pref & PRE_LOCK) { + if (m_mod == 3) { + hs->flags |= F_ERROR | F_ERROR_LOCK; + } else { + uint8_t* table_end, op = opcode; + if (hs->opcode2) { + ht = hde32_table + DELTA_OP2_LOCK_OK; + table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK; + } else { + ht = hde32_table + DELTA_OP_LOCK_OK; + table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK; + op &= -2; + } + for (; ht != table_end; ht++) + if (*ht++ == op) { + if (!((*ht << m_reg) & 0x80)) + goto no_lock_error; + else + break; + } + hs->flags |= F_ERROR | F_ERROR_LOCK; +no_lock_error: + ; + } + } + + if (hs->opcode2) { + switch (opcode) { + case 0x20: case 0x22: + m_mod = 3; + if (m_reg > 4 || m_reg == 1) + goto error_operand; + else + goto no_error_operand; + case 0x21: case 0x23: + m_mod = 3; + if (m_reg == 4 || m_reg == 5) + goto error_operand; + else + goto no_error_operand; + } + } else { + switch (opcode) { + case 0x8c: + if (m_reg > 5) + goto error_operand; + else + goto no_error_operand; + case 0x8e: + if (m_reg == 1 || m_reg > 5) + goto error_operand; + else + goto no_error_operand; + } + } + + if (m_mod == 3) { + uint8_t* table_end; + if (hs->opcode2) { + ht = hde32_table + DELTA_OP2_ONLY_MEM; + table_end = ht + sizeof(hde32_table) - DELTA_OP2_ONLY_MEM; + } else { + ht = hde32_table + DELTA_OP_ONLY_MEM; + table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM; + } + for (; ht != table_end; ht += 2) + if (*ht++ == opcode) { + if ((*ht++ & pref) && !((*ht << m_reg) & 0x80)) + goto error_operand; + else + break; + } + goto no_error_operand; + } else if (hs->opcode2) { + switch (opcode) { + case 0x50: case 0xd7: case 0xf7: + if (pref & (PRE_NONE | PRE_66)) + goto error_operand; + break; + case 0xd6: + if (pref & (PRE_F2 | PRE_F3)) + goto error_operand; + break; + case 0xc5: + goto error_operand; + } + goto no_error_operand; + } else + goto no_error_operand; + +error_operand: + hs->flags |= F_ERROR | F_ERROR_OPERAND; +no_error_operand: + + c = *p++; + if (m_reg <= 1) { + if (opcode == 0xf6) + cflags |= C_IMM8; + else if (opcode == 0xf7) + cflags |= C_IMM_P66; + } + + switch (m_mod) { + case 0: + if (pref & PRE_67) { + if (m_rm == 6) + disp_size = 2; + } else + if (m_rm == 5) + disp_size = 4; + break; + case 1: + disp_size = 1; + break; + case 2: + disp_size = 2; + if (!(pref & PRE_67)) + disp_size <<= 1; + break; + } + + if (m_mod != 3 && m_rm == 4 && !(pref & PRE_67)) { + hs->flags |= F_SIB; + p++; + hs->sib = c; + hs->sib_scale = c >> 6; + hs->sib_index = (c & 0x3f) >> 3; + if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1)) + disp_size = 4; + } + + p--; + switch (disp_size) { + case 1: + hs->flags |= F_DISP8; + hs->disp.disp8 = *p; + break; + case 2: + hs->flags |= F_DISP16; + hs->disp.disp16 = *(uint16_t*)p; + break; + case 4: + hs->flags |= F_DISP32; + hs->disp.disp32 = *(uint32_t*)p; + break; + } + p += disp_size; + } else if (pref & PRE_LOCK) + hs->flags |= F_ERROR | F_ERROR_LOCK; + + if (cflags & C_IMM_P66) { + if (cflags & C_REL32) { + if (pref & PRE_66) { + hs->flags |= F_IMM16 | F_RELATIVE; + hs->imm.imm16 = *(uint16_t*)p; + p += 2; + goto disasm_done; + } + goto rel32_ok; + } + if (pref & PRE_66) { + hs->flags |= F_IMM16; + hs->imm.imm16 = *(uint16_t*)p; + p += 2; + } else { + hs->flags |= F_IMM32; + hs->imm.imm32 = *(uint32_t*)p; + p += 4; + } + } + + if (cflags & C_IMM16) { + if (hs->flags & F_IMM32) { + hs->flags |= F_IMM16; + hs->disp.disp16 = *(uint16_t*)p; + } else if (hs->flags & F_IMM16) { + hs->flags |= F_2IMM16; + hs->disp.disp16 = *(uint16_t*)p; + } else { + hs->flags |= F_IMM16; + hs->imm.imm16 = *(uint16_t*)p; + } + p += 2; + } + if (cflags & C_IMM8) { + hs->flags |= F_IMM8; + hs->imm.imm8 = *p++; + } + + if (cflags & C_REL32) { +rel32_ok: + hs->flags |= F_IMM32 | F_RELATIVE; + hs->imm.imm32 = *(uint32_t*)p; + p += 4; + } else if (cflags & C_REL8) { + hs->flags |= F_IMM8 | F_RELATIVE; + hs->imm.imm8 = *p++; + } + +disasm_done: + + if ((hs->len = (uint8_t)(p-(uint8_t*)code)) > 15) { + hs->flags |= F_ERROR | F_ERROR_LENGTH; + hs->len = 15; + } + + return (unsigned int)hs->len; +} +#endif + +/****************************************** End of Hacker Disassembler code *******************************************/ + +void* hooker_alloc(size_t size) +{ +#if _WIN32 + return VirtualAlloc(0, size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); +#elif defined(__linux__) || defined(__APPLE__) + return mmap(0, size, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); +#endif +} + +void hooker_free(void* memory) +{ +#if _WIN32 + VirtualFree(memory, 0, MEM_RELEASE | MEM_DECOMMIT); +#elif defined(__linux__) || defined(__APPLE__) + munmap(memory, 0); // TODO: test +#endif +} + +void hooker_flush_instruction_cache(void* address, size_t size) +{ +#if defined(__linux__) || defined(__APPLE__) + const size_t cache_line = 64; + const char *cp = (const char *)address; + size_t i = 0; + + if (address == NULL || size == 0) + return; + + for (i = 0; i < size; i += cache_line) + { + asm volatile("clflush (%0)\n\t" + : + : "r"(&cp[i]) + : "memory"); + } + asm volatile("sfence\n\t" + : + : + : "memory"); +#elif _WIN32 + FlushInstructionCache(GetCurrentProcess(), address, size); +#endif +} + +bool hooker_mem_protect(void* p, size_t size, size_t protection, size_t* original_protection) +{ +#if _WIN32 + DWORD old = 0; + DWORD flags = PAGE_NOACCESS; + if (protection & HOOKER_MEM_PLATFORM) + flags = (DWORD)(protection & ~HOOKER_MEM_PLATFORM); + else + { + if (protection & HOOKER_MEM_R && protection & HOOKER_MEM_W && protection & HOOKER_MEM_X) + flags = PAGE_EXECUTE_READWRITE; + else if (protection & HOOKER_MEM_R && protection & HOOKER_MEM_W) + flags = PAGE_READWRITE; + else if (protection & HOOKER_MEM_R && protection & HOOKER_MEM_X) + flags = PAGE_EXECUTE_READ; + else if (protection & HOOKER_MEM_R) + flags = PAGE_READONLY; + else if (protection & HOOKER_MEM_X) + flags = PAGE_EXECUTE; + } + if (VirtualProtect(p, size, flags, &old)) + { + if (original_protection) + *original_protection = (size_t)old | HOOKER_MEM_PLATFORM; + return HOOKER_SUCCESS; + } +#elif defined(__linux__) || defined(__APPLE__) + size_t page_size = (size_t)sysconf(_SC_PAGE_SIZE); + void* page = (void*)((size_t)p & ~(page_size - 1)); + int flags = PROT_NONE; + if (protection & HOOKER_MEM_PLATFORM) + flags = (int)(protection & ~HOOKER_MEM_PLATFORM); + else + { + if (protection & HOOKER_MEM_R) + flags |= PROT_READ; + if (protection & HOOKER_MEM_W) + flags |= PROT_WRITE; + if (protection & HOOKER_MEM_X) + flags |= PROT_EXEC; + } + if (mprotect(page, ((size / page_size) + 1) * page_size, flags) == 0) + { + // Is there an easy way to get original protection flags without parsing sysfs? + // old_protect should be set by caller for now. + return HOOKER_SUCCESS; + } +#endif + return HOOKER_ERROR; +} + +size_t hooker_get_mnemonic_size(void* address, size_t min_size) +{ + size_t size = 0; + uint8_t* address_t = (uint8_t*)address; +#if HOOKER_X64 + hde64s hd; + while (size < min_size) + size += hde64_disasm(&address_t[size], &hd); +#else + hde32s hd; + while (size < min_size) + size += hde32_disasm(&address_t[size], &hd); +#endif + return size; +} + +void* hooker_hotpatch(void* location, void* new_proc) +{ + if (*(uint16_t*)location != 0xFF8B) // Verify if location is hot-patchable. + return (void*)HOOKER_ERROR; + hooker_write_instruction((uint8_t*)location - 5, new_proc, HOOKER_HOOK_JMP, 0); + size_t original_protection = HOOKER_MEM_RX; + hooker_mem_protect(location, 2, HOOKER_MEM_RWX, &original_protection); + *(uint16_t*)location = 0xF9EB; // jump back to hotpatch + hooker_mem_protect(location, 2, original_protection, 0); + hooker_flush_instruction_cache((uint8_t*)location - 5, 7); + return (uint8_t*)location + 2; +} + +bool hooker_unhotpatch(void* location) +{ + if (*(uint16_t*)location != 0xF9EB) // Verify that location was hotpatched. + return (size_t)HOOKER_ERROR; + size_t original_protection = HOOKER_MEM_RX; + hooker_mem_protect(location, 2, HOOKER_MEM_RWX, &original_protection); + *(uint16_t*)location = 0xFF8B; // mov edi, edi = nop + hooker_mem_protect(location, 2, original_protection, 0); + hooker_flush_instruction_cache((uint8_t*)location, 2); + return HOOKER_SUCCESS; +} + +void hooker_nop_tail(void* address, size_t len, size_t nops) +{ + if (nops == -1) + nops = hooker_get_mnemonic_size(address, len) - len; + if (nops > 0) + hooker_nop((uint8_t*)address + len, nops); +} + +size_t hooker_instruction_length(size_t flags) +{ +#if HOOKER_X64 + if (flags & HOOKER_HOOK_FAT) + { + if (flags & HOOKER_HOOK_CALL) + return 16; + else + return 14; + } +#endif + return 5; +} + +bool hooker_write_instruction(void* address_, void* new_proc, size_t flags, size_t nops) +{ + bool result = HOOKER_SUCCESS; + uint8_t* address = (uint8_t*)address_; + size_t instr_len = hooker_instruction_length(flags); + size_t original_protection = HOOKER_MEM_RX; + hooker_nop_tail(address, instr_len, nops); + hooker_mem_protect(address, instr_len, HOOKER_MEM_RWX, &original_protection); + +#if HOOKER_X64 + if (flags & HOOKER_HOOK_FAT) + { + // Fat call/jump to 64 bit address + if (flags & HOOKER_HOOK_CALL) + { + *(uint16_t*)&address[0] = 0x15FF; // call qword ptr [rip+0] + *(uint32_t*)&address[2] = 0; + *(uint16_t*)&address[6] = 0x08EB; // jmp +8 + *(uint64_t*)&address[8] = (uint64_t)new_proc; // address + } + else if (flags & HOOKER_HOOK_JMP) + { + *(uint16_t*)&address[0] = 0x25FF; // jmp qword ptr [rip+0] + *(uint32_t*)&address[2] = 0; + *(uint64_t*)&address[6] = (uint64_t)new_proc; // address + } + else + result = HOOKER_ERROR; + } + else +#endif + { + uint8_t opcode = 0; + if (flags & HOOKER_HOOK_CALL) + opcode = 0xE8; + else if (flags & HOOKER_HOOK_JMP) + opcode = 0xE9; + + if (opcode) + { +#if HOOKER_X64 + // On x64 addresses may be further away than fits into 32bits. HOOKER_HOOK_FAT should be used in these cases. + if ((size_t)(MAX(address, (uint8_t*)new_proc) - MIN(address, (uint8_t*)new_proc)) > 0x80000000) + result = HOOKER_ERROR; + else +#endif + { + uint8_t* location_t = address; + *location_t = opcode; + *(uint32_t*)(++location_t) = (uint32_t)((size_t)new_proc - (size_t)address) - 5; + } + } + else + result = HOOKER_ERROR; + } + + hooker_mem_protect(address, instr_len, original_protection, 0); + hooker_flush_instruction_cache(address, instr_len); + + return result; +} + +bool hooker_write_jmp(void* address, void* new_proc) +{ + return hooker_write_instruction(address, new_proc, HOOKER_HOOK_JMP, -1); +} + +bool hooker_write_call(void* address, void* new_proc) +{ + return hooker_write_instruction(address, new_proc, HOOKER_HOOK_CALL, -1); +} + +void* hooker_hook(void* address_, void* new_proc, size_t flags) +{ + uint8_t* address = (uint8_t*)address_; +#if HOOKER_X64 + flags |= HOOKER_HOOK_FAT; +#endif + size_t jmp_len = hooker_instruction_length((flags & ~HOOKER_HOOK_CALL) | HOOKER_HOOK_JMP); + size_t save_bytes = hooker_get_mnemonic_size(address, jmp_len); + size_t bridge_size = save_bytes + jmp_len + 1; + if (flags & HOOKER_HOOK_CALL) + bridge_size += jmp_len; + + // JUMP Bridge: [len(original bytes)] [original bytes] [jmp address+len(original bytes)] + // CALL Bridge: [len(original bytes)] [call hook] [original bytes] [jmp address+len(original bytes)] + uint8_t* bridge = (uint8_t*)hooker_alloc(bridge_size); + // Save number of saved bytes at the start of bridge and skip a byte. + *bridge = (uint8_t)save_bytes; bridge++; + uint8_t* bridge_start = bridge; + // Write a call to our hook. + if (flags & HOOKER_HOOK_CALL) + { + hooker_write_instruction(bridge, new_proc, flags, 0); + bridge += hooker_instruction_length((flags & ~HOOKER_HOOK_JMP) | HOOKER_HOOK_CALL); + } + // Write overwritten instructions + memcpy(bridge, address, save_bytes); + bridge += save_bytes; + // Write jump to original function + hooker_write_instruction(bridge, address + save_bytes, HOOKER_HOOK_JMP | HOOKER_HOOK_FAT, 0); + // Write jump to the new proc + if (flags & HOOKER_HOOK_CALL) + hooker_write_instruction(address, bridge_start, HOOKER_HOOK_JMP | HOOKER_HOOK_FAT, -1); + else + hooker_write_instruction(address, new_proc, HOOKER_HOOK_JMP | HOOKER_HOOK_FAT, -1); + // Bridge is call to original proc + return bridge_start; +} + +void hooker_unhook(void* address, void* original) +{ + // Possible call with HOOKER_SUCCESS or HOOKER_ERROR parameter. + if (original < (void*)2) + return; + + uint8_t restore_len = *((uint8_t*)original - 1); + size_t original_protection = HOOKER_MEM_RX; + hooker_mem_protect(address, restore_len, HOOKER_MEM_RWX, &original_protection); + memcpy(address, original, restore_len); + hooker_mem_protect(address, restore_len, original_protection, 0); + hooker_free((uint8_t*)original - 1); + hooker_flush_instruction_cache(address, restore_len); +} + +size_t* hooker_get_vmt_address(void* object, void* method) +{ + size_t* vmt = *(size_t**)object; + while (*vmt != (size_t)method) + vmt++; + return vmt; +} + +void* hooker_find_pattern(void* start, int size, const uint8_t* pattern, size_t pattern_len, uint8_t wildcard) +{ + if (start == 0 || pattern == 0 || pattern_len == 0) + return 0; + + uint8_t* p = (uint8_t*)start; + uint8_t* end = (uint8_t*)~0; + int step = 1; + if (size > 0) + end = &p[size - pattern_len]; + else if (size < 0) + { + step = -1; + p -= pattern_len; + end = &p[size]; + } + + while (step > 0 ? p < end : p >= end) + { + pattern_search: + for (size_t i = 0; i < pattern_len; i++) + { + if (p[i] != pattern[i] && pattern[i] != wildcard) + { + p += step; + goto pattern_search; + } + } + return p; + } + + return 0; +} + +void* hooker_find_pattern_ex(void* start, int size, const uint8_t* pattern, size_t pattern_len, const uint8_t* wildcard) +{ + if (start == 0 || pattern == 0 || pattern_len == 0) + return 0; + + uint8_t* p = (uint8_t*)start; + uint8_t* end = (uint8_t*)~0; + int step = 1; + if (size > 0) + end = &p[size - pattern_len]; + else if (size < 0) + { + step = -1; + p -= pattern_len; + end = &p[size]; + } + + while (step > 0 ? p < end : p >= end) + { + pattern_search: + for (size_t i = 0; i < pattern_len; i++) + { + uint8_t byte = p[i]; + uint8_t value = pattern[i]; + switch (wildcard[i]) + { + case 1: + // First half of byte to ignore, keep only second half. + byte &= 0xF0; + value &= 0xF0; + break; + case 2: + // Second half of byte to ignore, keep only first half. + byte &= 0x0F; + value &= 0x0F; + break; + case 3: + // Ignore entire byte. + byte = 0; + value = 0; + break; + default: + // Not a wildcard. + break; + } + + if (byte != value) + { + p += step; + goto pattern_search; + } + } + return p; + } + + return 0; +} + +bool hooker_nop(void* start, size_t size) +{ + if (start == 0) + return HOOKER_ERROR; + + if (size == -1) + size = hooker_get_mnemonic_size(start, 1); + + size_t original = HOOKER_MEM_RX; + if (hooker_mem_protect(start, size, HOOKER_MEM_RWX, &original) != HOOKER_SUCCESS) + return HOOKER_ERROR; + memset(start, 0x90, size); + hooker_mem_protect(start, size, original, 0); + hooker_flush_instruction_cache(start, size); + return HOOKER_SUCCESS; +} + +bool hooker_write(void* start, void* data, size_t size) +{ + if (start == 0 || size == 0) + return HOOKER_ERROR; + + size_t original = HOOKER_MEM_RX; + if (hooker_mem_protect(start, size, HOOKER_MEM_RWX, &original) == HOOKER_SUCCESS) + { + memcpy(start, data, size); + hooker_mem_protect(start, size, original, 0); + hooker_flush_instruction_cache(start, size); + return HOOKER_SUCCESS; + } + return HOOKER_ERROR; +} + +void* hooker_dlsym(const char* lib_name, const char* sym_name) +{ +#if _WIN32 + HMODULE mod = GetModuleHandleA(lib_name); + if (mod == 0) + { + mod = LoadLibraryA(lib_name); + if (mod == 0) + return 0; + } + + return GetProcAddress(mod, sym_name); +#elif defined(__linux__) || defined(__APPLE__) + void* mod = dlopen(lib_name, RTLD_GLOBAL | RTLD_NODELETE); + if (mod == 0) + return 0; + void* sym = dlsym(mod, sym_name); + dlclose(mod); + return sym; +#endif +} + +#if _WIN32 +void* hooker_hook_iat(const char* mod_name, const char* imp_mod_name, const char* imp_proc_name, void* new_proc) +{ + if (mod_name == 0 || imp_mod_name == 0 || imp_proc_name == 0 || new_proc == 0) + return HOOKER_ERROR; + + void* result = 0; + uintptr_t module_base = (uintptr_t)GetModuleHandleA(mod_name); + if (module_base == 0) + module_base = (uintptr_t)LoadLibraryA(mod_name); + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)module_base; + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(module_base + dos->e_lfanew); + PIMAGE_IMPORT_DESCRIPTOR import_dir; + + if (nt->Signature != IMAGE_NT_SIGNATURE) + return HOOKER_ERROR; + + import_dir = (PIMAGE_IMPORT_DESCRIPTOR)(module_base + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + for (unsigned i = 0; import_dir[i].Characteristics != 0; i++) + { + const char* import_name = (const char*)(module_base + import_dir[i].Name); + if (_strcmpi(import_name, imp_mod_name) != 0) + continue; + + PIMAGE_THUNK_DATA thunk; + PIMAGE_THUNK_DATA thunk_orig; + + if (!import_dir[i].FirstThunk || !import_dir[i].OriginalFirstThunk) + return HOOKER_ERROR; + + thunk = (PIMAGE_THUNK_DATA)(module_base + import_dir[i].FirstThunk); + thunk_orig = (PIMAGE_THUNK_DATA)(module_base + import_dir[i].OriginalFirstThunk); + + for (; thunk_orig->u1.Function != 0; thunk_orig++, thunk++) + { + if (thunk_orig->u1.Ordinal & IMAGE_ORDINAL_FLAG) + // Ordinals unsupported + continue; + + PIMAGE_IMPORT_BY_NAME import = (PIMAGE_IMPORT_BY_NAME)(module_base + thunk_orig->u1.AddressOfData); + if (strcmp(imp_proc_name, (const char*)import->Name) != 0) + continue; + + result = (PVOID)(uintptr_t)thunk->u1.Function; + if (hooker_write(&thunk->u1.Function, &new_proc, sizeof(new_proc)) == HOOKER_SUCCESS) + return result; + } + } + + return HOOKER_ERROR; +} +#endif diff --git a/3rdparty/hooker.h b/3rdparty/hooker.h new file mode 100644 index 0000000..495e240 --- /dev/null +++ b/3rdparty/hooker.h @@ -0,0 +1,162 @@ +/* + * MIT License + * + * Copyright (c) 2017 Rokas Kupstys + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include +#include + +#define HOOKER_ERROR (false) +#define HOOKER_SUCCESS (true) +#define HOOKER_MEM_R (1 << 0) +#define HOOKER_MEM_W (1 << 1) +#define HOOKER_MEM_X (1 << 2) +#define HOOKER_MEM_RW (HOOKER_MEM_R|HOOKER_MEM_W) +#define HOOKER_MEM_RX (HOOKER_MEM_R|HOOKER_MEM_X) +#define HOOKER_MEM_RWX (HOOKER_MEM_R|HOOKER_MEM_W|HOOKER_MEM_X) +/// Memory protection flags are platform specific (not a combination of above flags) and should not be converted. +#define HOOKER_MEM_PLATFORM (1 << 31) + +/// Write a call instruction (5 bytes on x86/64). +#define HOOKER_HOOK_CALL (1 << 0) +/// Write a jump instruction (5 bytes on x86/64). +#define HOOKER_HOOK_JMP (1 << 1) +/// Use fat jump (16 for call and 14 bytes for jmp on x64). Has no effect on x86. +#define HOOKER_HOOK_FAT (1 << 2) + +#if __cplusplus +extern "C" { +#endif + +/// Call any address with arbitrary amount of arguments. Returns value of specified type. +/// \param returnType of return value. +/// \param address of a call. +#define hooker_call(returnType, address, ...) (returnType(*)(...))(address)(__VA_ARGS__); +/// Call any address with arbitrary amount of arguments. Does not return anything. +/// \param address of a call. +#define hooker_callv(address, ...) (void(*)(...))(address)(__VA_ARGS__); + +/// Change protection of memory range. +/// \param p memory address. +/// \param size of memory at address p. +/// \param protection a combination of HOOKER_MEM_* flags. +/// \param original_protection on supported platforms will be set to current memory protection mode. May be null. If not null - always initialize to a best-guess current protection flags value, because on some platforms (like linux) this variable will not be set. +bool hooker_mem_protect(void* p, size_t size, size_t protection, size_t* original_protection); +/// Get mnemonic size of current platform. +size_t hooker_get_mnemonic_size(void* address, size_t min_size); + +/// Hotpatch a call. +void* hooker_hotpatch(void* location, void* new_proc); +/// Unhotpatch a call. +bool hooker_unhotpatch(void* location); + +/// Writes a jump or call hook from `address` to `new_proc`. +/// \param address a pointer where hook should be written +/// \param new_proc a pointer where hook should point to. +/// \param flags any of HOOKER_HOOK_* flags. They may not be combined. +/// \param nops of bytes to nop after hook instruction. Specify -1 to autocalculate. +/// \returns null on failure or non-null on success. +bool hooker_write_instruction(void* address, void* new_proc, size_t flags, size_t nops); + +/// Writes a 5 byte jump with E9 opcode. Difference between pointers is limited to 32bit. +/// \param address a pointer where hook should be written +/// \param new_proc a pointer where hook should point to. +/// \returns null on failure or non-null on success. +bool hooker_write_jmp(void* address, void* new_proc); +/// Writes a 5 byte call with E8 opcode. Difference between pointers is limited to 32bit. +/// \param address a pointer where hook should be written +/// \param new_proc a pointer where hook should point to. +/// \returns null on failure or non-null on success. +bool hooker_write_call(void* address, void* new_proc); + +/// Redirect call to custom proc. +/// \param address a start of original call. Warning: It should not contain any relatively-addressed instructions like calls or jumps. +/// \param new_proc a proc that will be called instead of original one. +/// \returns pointer, calling which will invoke original proc. It is user's responsibility to call original code when necessary. +void* hooker_hook(void* address, void* new_proc, size_t flags); + +/// Unhook a hook created by hooker_hook(.., .., HOOKER_HOOK_REDIRECT, ..). +/// \param address where hook was written to. +/// \param original result of hooker_hook() call. +void hooker_unhook(void* address, void* original); + +/// Return address in object's vmt which is pointing to specified method. +/// \param object is a pointer to a c++ object. +/// \param method is a pointer to a c++ object method. +size_t* hooker_get_vmt_address(void* object, void* method); + +/// Find a first occurrence of memory pattern. +/// \param start a pointer to beginning of memory range. +/// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards. +/// \param pattern a array of bytes to search for. +/// \param pattern_len a length of pattern array. +/// \param wildcard byte in the pattern array. It must be of same size as indicated by `pattern_len`. +void* hooker_find_pattern(void* start, int size, const uint8_t* pattern, size_t pattern_len, uint8_t wildcard); + +/// Find a first occurrence of memory pattern. +/// \param start a pointer to beginning of memory range. +/// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards. +/// \param pattern a array of bytes to search for. +/// \param pattern_len a length of pattern array. +/// \param wildcard array where values may be one of: 0? = 1, ?0 = 2, ?? = 3. +void* hooker_find_pattern_ex(void* start, int size, const uint8_t* pattern, size_t pattern_len, const uint8_t* wildcard); + +/// Fill memory with nops (0x90 opcode). +/// \param start of the memory address. +/// \param size of the memory that will be filled. +/// \returns HOOKER_SUCCESS or HOOKER_ERROR. +bool hooker_nop(void* start, size_t size); + +/// Write bytes to specified memory address. +/// \param start of the memory address. +/// \param data to be written. +/// \param size of data. +bool hooker_write(void* start, void* data, size_t size); + +/// Searches for symbol in specified library. On Windows LoadLibrary() will be called if its not loaded yet, otherwise GetModuleHandle() will be used. +/// On linux dlopen(RTLD_NODELETE) and dlclose() will always be called. +/// \param lib_name string with dynamic library name. +/// \param sym_name string with exported symbol name. +/// \returns pointer to resolved dynamic symbol. +void* hooker_dlsym(const char* lib_name, const char* sym_name); + +#if _WIN32 +/// Replaces entry in import address table of specified module. +/// \param mod_name string with name of module whose import table is to be modified. +/// \param imp_mod_name string with name of module which module specified in `mod_name` imports. +/// \param imp_proc_name string with name of symbol imported from module specified in `imp_mod_name`. +/// \param new_proc a pointer that should replace old entry in import address table. +/// \returns original value that was in import address table or null pointer on failure. +void* hooker_hook_iat(const char* mod_name, const char* imp_mod_name, const char* imp_proc_name, void* new_proc); +#endif + +// Define following macro in a single translation unit in order to use library without building it. +#ifdef HOOKER_IMPLEMENTATION +# include "hooker.c" +#endif // HOOKER_IMPLEMENTATION + +#if __cplusplus +}; +#endif diff --git a/3rdparty/hooker.hpp b/3rdparty/hooker.hpp new file mode 100644 index 0000000..9f5f351 --- /dev/null +++ b/3rdparty/hooker.hpp @@ -0,0 +1,354 @@ +/* + * MIT License + * + * Copyright (c) 2017 Rokas Kupstys + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include +#include + +namespace hooker +{ + namespace detail + { + extern "C" + { + #include "hooker.h" + } + struct Helper { }; + } +#if __cplusplus >= 201402L + /// Pattern for find_pattern() function. + template + struct pattern + { + /// Bytes to find. Value of wildcard byte or byte half can be anything. + uint8_t pattern[N]; + /// Wildcard pattern. Byte value may be one of: 0? = 1, ?0 = 2, ?? = 3. + uint8_t wildcard[N]; + }; + +# define CPP14(x) x + namespace detail + { + // Convert hex character to a number. + constexpr uint8_t char_to_byte(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return 0x0A + c - 'a'; + else if (c >= 'A' && c <= 'F') + return 0x0A + c - 'A'; + else if (c == '?') + return 0; + else + throw std::runtime_error("Not a hex character."); + } + + // Convert text hex byte at `idx` to binary. + template + constexpr uint8_t get_pattern_byte(const char(&s)[N], size_t idx) + { + if (s[idx * 3 + 2] != ' ' && s[idx * 3 + 2] != '\0') + throw std::runtime_error("Improperly formatted pattern."); + else + return (char_to_byte(s[idx * 3]) << 4) | char_to_byte(s[idx * 3 + 1]); + } + + // Convert text wildcard to binary mask. + template + constexpr uint8_t get_wildcard_byte(const char(&s)[N], size_t idx) + { + return (s[idx * 3] == '?' ? 2 : 0) | (s[idx * 3 + 1] == '?' ? 1 : 0); + } + + // Convert a character array to binary version and wildcard array. + template + constexpr pattern decode_string_pattern(const char(&s)[N], std::index_sequence) + { + return { + { get_pattern_byte(s, Is)... }, + { get_wildcard_byte(s, Is)... }, + }; + } + } + + // Create a binary pattern from raw string litteral in format: "AB C? ?D ??". + template + constexpr const pattern mkpat(const char(&s)[N]) + { + if ((N % 3) == 0) + return detail::decode_string_pattern(s, std::make_index_sequence()); + else + throw std::runtime_error("Improperly formatted pattern."); + } + +#else +# define CPP14(x) +#endif + +#if __cplusplus > 201703L + using bit_cast = std::bit_cast; +#else + template +#if __cplusplus >= 201703L + [[nodiscard]] +#endif + To bit_cast(From &&from) +#if __cplusplus >= 201402L + noexcept(std::is_nothrow_constructible::value) +#endif + { + static_assert(std::is_trivially_copyable::type>::type>::value, "From type must be trivially copable."); + static_assert(std::is_trivially_copyable::type>::type>::value, "To type must be trivially copiable."); + static_assert(sizeof(From) == sizeof(To), "Sizes of From and To types must be the same."); + static_assert(std::is_default_constructible::value, "To type must be default constructible."); +#if __cplusplus >= 201402L + auto result = (typename std::aligned_storage_t){}; +#else + To result{}; +#endif + return *static_cast(memcpy(&result, &from, sizeof(To))); + } +#endif + + namespace detail + { + template + typename std::enable_if::value, void*>::type + any_to_voidp(Addr addr) { return reinterpret_cast(addr); } + + template + typename std::enable_if::value, void*>::type + any_to_voidp(Addr addr) { return bit_cast(addr); } + } +#if _WIN32 + /// Calls specified address as __stdcall function passing any amount of arguments. Return type is specified as first template argument. Available only on windows. + /// \param address of function to call. + /// \param ... any amount of arguments with any types. + /// \returns value of type specified as first template argument, or none if no type is specified. + template + Result stdcall(Addr address, Args... arguments) + { + typedef Result(__stdcall*UniversalCall)(Args...); + return UniversalCall(address)(arguments...); + } +# define HOOKER_CDECL __cdecl +#else +# define HOOKER_CDECL +#endif + /// Calls specified address as __cdecl function passing any amount of arguments. Return type is specified as first template argument. + /// \param address of function to call. + /// \param ... any amount of arguments with any types. + /// \returns value of type specified as first template argument, or none if no type is specified. + template + Result ccall(Addr address, Args... arguments) + { + typedef Result(HOOKER_CDECL*UniversalCall)(Args...); + return reinterpret_cast(address)(arguments...); + } +#undef HOOKER_CDECL + /// Calls specified address as __thiscall function with provided address as 'this' pointer, passing any amount of arguments. Return type is specified as first template argument. + /// \param address of function to call. + /// \param ... any amount of arguments with any types. + /// \returns value of type specified as first template argument, or none if no type is specified. + template + Result thiscall(Addr address, This thisPtr, Args... arguments) + { + detail::Helper* thisHelper = bit_cast(thisPtr); + typedef Result(detail::Helper::*TUniversalCall)(Args...); + TUniversalCall UniversalCall = bit_cast(address); + return (thisHelper->*UniversalCall)(arguments...); + } + + /// Return object of specified `Type` which is located at `base + offset`. + template + Type at_offset(Base base, unsigned offset) + { + return bit_cast(bit_cast(base) + offset); + } + /// Return object of specified `Type` which is located at `base + offset`. + template + Type from_offset(Base base, unsigned offset) + { + return *at_offset(base, offset); + } + /// Change protection of memory range. + /// \param p memory address. + /// \param size of memory at address p. + /// \param protection a combination of HOOKER_MEM_* flags. + /// \param original_protection on supported platforms will be set to current memory protection mode. May be null. If not null - always initialize to a best-guess current protection flags value, because on some platforms (like linux) this variable will not be set. + template + bool mem_protect(Addr p, size_t size, size_t protection, size_t* original_protection=nullptr) { return detail::hooker_mem_protect(detail::any_to_voidp(p), size, protection, original_protection) == HOOKER_SUCCESS; } + /// Get mnemonic size of current platform. + template + size_t get_mnemonic_size(Addr address, size_t min_size) { return detail::hooker_get_mnemonic_size(detail::any_to_voidp(address), min_size); } + + /// Hotpatch a call. + template + OriginalProc hotpatch(Addr location, ProcAddr new_proc) { return bit_cast(detail::hooker_hotpatch(detail::any_to_voidp(location), detail::any_to_voidp(new_proc))); } + /// Unhotpatch a call. + template + bool unhotpatch(Addr location) { return detail::hooker_unhotpatch(detail::any_to_voidp(location)) == HOOKER_SUCCESS; } + + /// Writes a jump or call hook from `address` to `new_proc`. + /// \param address a pointer where hook should be written + /// \param new_proc a pointer where hook should point to. + /// \param flags any of HOOKER_HOOK_* flags. They may not be combined. + /// \param nops of bytes to nop after hook instruction. Specify -1 to autocalculate. + /// \returns null on failure or non-null on success. + template + bool write_instruction(Addr address, ProcAddr new_proc, size_t flags, size_t nops=-1) { return detail::hooker_write_instruction(detail::any_to_voidp(address), detail::any_to_voidp(new_proc), flags, nops) == HOOKER_SUCCESS; } + + /// Writes a 5 byte jump with E9 opcode. Difference between pointers is limited to 32bit. + /// \param address a pointer where hook should be written + /// \param new_proc a pointer where hook should point to. + /// \returns null on failure or non-null on success. + template + bool write_jmp(Addr address, ProcAddr new_proc) { return detail::hooker_write_jmp(detail::any_to_voidp(address), detail::any_to_voidp(new_proc)) == HOOKER_SUCCESS; } + /// Writes a 5 byte call with E8 opcode. Difference between pointers is limited to 32bit. + /// \param address a pointer where hook should be written + /// \param new_proc a pointer where hook should point to. + /// \returns null on failure or non-null on success. + template + bool write_call(Addr address, ProcAddr new_proc) { return detail::hooker_write_call(detail::any_to_voidp(address), detail::any_to_voidp(new_proc)) == HOOKER_SUCCESS; } + + /// Redirect call to custom proc. + /// \param address a start of original call. Warning: It should not contain any relatively-addressed instructions like calls or jumps. + /// \param new_proc a proc that will be called instead of original one. + /// \returns pointer, calling which will invoke original proc. It is user's responsibility to call original code when necessary. + template + OriginalProc hook(Addr address, ProcAddr new_proc, size_t flags=0) { return bit_cast(detail::hooker_hook(detail::any_to_voidp(address), detail::any_to_voidp(new_proc), flags)); } + + /// Unhook a hook created by hooker::hook(.., .., HOOKER_HOOK_REDIRECT, ..). + /// \param address where hook was written to. + /// \param original result of hooker::redirect() call. + template + void unhook(Addr address, OriginalProc original) { detail::hooker_unhook(detail::any_to_voidp(address), detail::any_to_voidp(original)); } + + /// Return address in object's vmt which is pointing to specified method. + /// \param object is a pointer to a c++ object. + /// \param method is a pointer to a c++ object method. + template + Proc& get_vmt_address(Addr object, ProcAddr method) { return *bit_cast(detail::hooker_get_vmt_address(reinterpret_cast(object), detail::any_to_voidp(method))); } + + /// Find a first occurrence of memory pattern. + /// \param start a pointer to beginning of memory range. + /// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards. + /// \param pattern a array of bytes to search for. + /// \param pattern_len a length of pattern array. + /// \param a wildcard byte in the pattern array. + template + Type find_pattern(Addr start, int size, const Pattern* pattern, size_t pattern_len, uint8_t wildcard) { return bit_cast(detail::hooker_find_pattern(detail::any_to_voidp(start), size, reinterpret_cast(pattern), pattern_len, wildcard)); } + +#if __cplusplus >= 201402L + /// Find a first occurrence of memory pattern. + /// \param start a pointer to beginning of memory range. + /// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards. + /// \param pattern and wildcard mask. + template + Type find_pattern(Addr start, int size, const pattern& pattern) { return bit_cast(detail::hooker_find_pattern_ex(detail::any_to_voidp(start), size, pattern.pattern, N, pattern.wildcard)); } +#endif + + /// Find a first occurrence of string pattern. + /// \param start a pointer to beginning of memory range. + /// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards. + /// \param pattern a string to search. + /// \param wildcard a wildcard character. + template + Type find_pattern(Addr start, int size, const char* pattern, char wildcard='?') { return bit_cast(detail::hooker_find_pattern(detail::any_to_voidp(start), size, reinterpret_cast(pattern), strlen(pattern), static_cast(wildcard))); } + + /// Fill memory with nops (0x90 opcode). + /// \param start of the memory address. + /// \param size of the memory that will be filled. Nops a single instruction when not specified. + /// \returns true on success or false on failure. + template + bool nop(Addr start, size_t size=-1) { return detail::hooker_nop(detail::any_to_voidp(start), size) == HOOKER_SUCCESS; } + + /// Write a value to specified memory address. + /// \param start of the memory address. + /// \param value to be written. + /// \returns true on success or false on failure. + template + bool write(Addr address, const Type value) { return detail::hooker_write(detail::any_to_voidp(address), (void*)&value, sizeof(value)) == HOOKER_SUCCESS; } + + /// Write an array to specified memory address. + /// \param start of the memory address. + /// \param value to be written. + /// \param count of elements in the array. + template + bool write(Addr address, const Type* value, size_t count) { return detail::hooker_write(detail::any_to_voidp(address), (void*)value, sizeof(Type) * count) == HOOKER_SUCCESS; } + + /// Write bytes to specified memory address. + /// \param start of the memory address. + /// \param data to be written. + /// \param size of data. + /// \returns true on success or false on failure. + template + bool write(Addr address, const void* data, size_t size) { return detail::hooker_write(detail::any_to_voidp(address), data, size) == HOOKER_SUCCESS; } + + /// Searches for symbol in specified library. On Windows LoadLibrary() will be called if its not loaded yet, otherwise GetModuleHandle() will be used. + /// On linux dlopen(RTLD_NODELETE) and dlclose() will always be called. + /// \param lib_name string with dynamic library name. + /// \param sym_name string with exported symbol name. + /// \returns pointer to resolved dynamic symbol. + template + Proc dlsym(const char* lib_name, const char* sym_name) { return bit_cast(detail::hooker_dlsym(lib_name, sym_name)); } + +#if _WIN32 + /// Replaces entry in import address table of specified module. + /// \param mod_name string with name of module whose import table is to be modified. + /// \param imp_mod_name string with name of module which module specified in `mod_name` imports. + /// \param imp_proc_name string with name of symbol imported from module specified in `imp_mod_name`. + /// \param new_proc a pointer that should replace old entry in import address table. + /// \returns original value that was in import address table or null pointer on failure. + template + OrigProc hook_iat(const char* mod_name, const char* imp_mod_name, const char* imp_proc_name, NewProc new_proc) { return bit_cast(detail::hooker_hook_iat(mod_name, imp_mod_name, imp_proc_name, detail::any_to_voidp(new_proc))); } + + /// Replaces entry in import address table of specified module. + /// \param mod_name string with name of module whose import table is to be modified. + /// \param imp_mod_name string with name of module which module specified in `mod_name` imports. + /// \param imp_proc_name string with name of symbol imported from module specified in `imp_mod_name`. + /// \param new_proc a pointer that should replace old entry in import address table. + /// \param old_proc a pointer which will receive Can be null. + /// \returns true on success or false on failure. + template + bool hook_iat(const char* mod_name, const char* imp_mod_name, const char* imp_proc_name, NewProc new_proc, OrigProc* old_proc) + { + auto result = hook_iat(mod_name, imp_mod_name, imp_proc_name, detail::any_to_voidp(new_proc)); + if (old_proc) + *old_proc = result; + return result != HOOKER_ERROR; + } +#endif + +}; + +#if HOOKER_USE_SHORT_NAMESPACE +# ifndef HOOKER_SHORT_NAMESPACE +# define HOOKER_SHORT_NAMESPACE hk +# endif +namespace HOOKER_SHORT_NAMESPACE = hooker; +#endif diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4e7c027 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.26) +project(vsdbg-fix) + +add_library(stub SHARED stub.cpp 3rdparty/hooker.c exports.def) + +set(DST_PATH "C:/Users/Fragcolor2/.cursor/extensions/ms-vscode.cpptools-1.21.6-win32-x64/debugAdapters/vsdbg/bin") + +# Set target name to vsdbg.dll +set_target_properties(stub PROPERTIES + OUTPUT_NAME vsdbg +) + +target_compile_definitions(stub PUBLIC + _CRT_SECURE_NO_WARNINGS=1 + HOOKER_X64=1 +) + +# Copy output dll to vsdbg directory +add_custom_command( + OUTPUT ${DST_PATH}/vsdbg.dll + COMMAND ${CMAKE_COMMAND} -E copy $ ${DST_PATH} + + # Copy PDB too + COMMAND ${CMAKE_COMMAND} -E copy $ ${DST_PATH} + DEPENDS stub + COMMENT "Copying vsdbg.dll to ${DST_PATH}" +) + +add_custom_target(copy_vsdbg ALL + DEPENDS ${DST_PATH}/vsdbg.dll +) diff --git a/exports.def b/exports.def new file mode 100644 index 0000000..4abbdbc --- /dev/null +++ b/exports.def @@ -0,0 +1,14 @@ +EXPORTS +InitializePAL = _0 +WrapICorDebugHandleValue = _1 +GetVilValueFieldOffset = _2 +Initialize = _3 +InitializeSession = _4 +RunMainLoop = _5 +ShutdownSession = _6 +Shutdown = _7 +DllGetClassObject = _8 +GetUnixDomainSocketSizes = _9 +AnalyzeSnapshot = _10 +RunDiagnosticAnalysis = _11 +OptionalHeader.AddressOfEntryPoint = _12 \ No newline at end of file diff --git a/stub.cpp b/stub.cpp new file mode 100644 index 0000000..815bcae --- /dev/null +++ b/stub.cpp @@ -0,0 +1,90 @@ +// Stubs for patching calls into vsdbg.1.dll +extern "C" { +void _0() { _0(); } +void _1() { _0(); } +void _2() { _0(); } +void _3() { _0(); } +void _4() { _0(); } +void _5() { _0(); } +void _6() { _0(); } +void _7() { _0(); } +void _8() { _0(); } +void _9() { _0(); } +void _10() { _0(); } +void _11() { _0(); } +void _12() { _0(); } +} + +void (*stuff[])() = { + &_0, &_1, &_2, &_3, &_4, &_5, &_6, &_7, &_8, &_9, &_10, &_11, &_12, +}; + +const char *symlist[] = { + "InitializePAL", + "WrapICorDebugHandleValue", + "GetVilValueFieldOffset", + "Initialize", + "InitializeSession", + "RunMainLoop", + "ShutdownSession", + "Shutdown", + "DllGetClassObject", + "GetUnixDomainSocketSizes", + "AnalyzeSnapshot", + "RunDiagnosticAnalysis", + "OptionalHeader.AddressOfEntryPoint", +}; +const char *origDll = "vsdbg.1.dll"; +const char *logPath = "A:\\Projects\\vsdbg-fix\\log.txt"; + +#include "3rdparty/hooker.h" +#include +#include + +FILE *logFile; + +void loadOrig() { + auto lib = LoadLibraryA(origDll); + for (int i = 0; i < sizeof(symlist) / sizeof(symlist[0]); i++) { + auto sym = GetProcAddress(lib, symlist[i]); + if (sym) { + if (!hooker_write_instruction(stuff[i], (void *)sym, + HOOKER_HOOK_FAT | HOOKER_HOOK_JMP, -1)) { + fprintf(logFile, "Failed to write jmp for %s\n", symlist[i]); + fflush(logFile); + } else { + fprintf(logFile, "Successfully wrote jmp for %s\n", symlist[i]); + fflush(logFile); + } + // fprintf(logFile, "Failed to hotpatch %s\n", symlist[i]); + // } + } + } +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, + LPVOID lpReserved) { + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: + logFile = fopen(logPath, "w"); + + while (!IsDebuggerPresent()) { + Sleep(100); + fprintf(logFile, "Waiting for debugger...\n"); + fflush(logFile); + } + + fprintf(logFile, "Running\n"); + fflush(logFile); + + loadOrig(); + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} diff --git a/vsdbg.txt b/vsdbg.txt new file mode 100644 index 0000000..5f1d0d1 --- /dev/null +++ b/vsdbg.txt @@ -0,0 +1,13 @@ +180000000+13a0 +vsdbg.1.dll:$13a0 + +; Debugger init something +vsdbg.1:$6b2fa + +; Obfus stuff +vsdbg.1:$0aecb0 + +; signatureCheck +vsdbg.1:$6afcc + +vsdbg.1:$6b088 \ No newline at end of file