mirror of
https://github.com/mumble-voip/mumble.git
synced 2025-03-19 14:53:28 +00:00

Keeping these up-to-date is just super tedious and they don't really fulfill any purpose these days.
379 lines
12 KiB
C++
379 lines
12 KiB
C++
// Copyright The Mumble Developers. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file at the root of the
|
|
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
|
|
|
#include "HardHook.h"
|
|
#include "ods.h"
|
|
|
|
void *HardHook::pCode = nullptr;
|
|
unsigned int HardHook::uiCode = 0;
|
|
|
|
const int HardHook::CODEREPLACESIZE = 6;
|
|
const int HardHook::CODEPROTECTSIZE = 16;
|
|
|
|
/**
|
|
* @brief Constructs a new hook without actually injecting.
|
|
*/
|
|
HardHook::HardHook() : bTrampoline(false), call(0), baseptr(nullptr) {
|
|
for (int i = 0; i < CODEREPLACESIZE; ++i) {
|
|
orig[i] = replace[i] = 0;
|
|
}
|
|
|
|
// assert(CODEREPLACESIZE == sizeof(orig) / sizeof(orig[0]));
|
|
// assert(CODEREPLACESIZE == sizeof(replace) / sizeof(replace[0]));
|
|
}
|
|
|
|
/**
|
|
* @brief Constructs a new hook by injecting given replacement function into func.
|
|
* @see HardHook::setup
|
|
* @param func Funktion to inject replacement into.
|
|
* @param replacement Function to inject into func.
|
|
*/
|
|
HardHook::HardHook(voidFunc func, voidFunc replacement) : bTrampoline(false), call(0), baseptr(nullptr) {
|
|
for (int i = 0; i < CODEREPLACESIZE; ++i)
|
|
orig[i] = replace[i] = 0;
|
|
setup(func, replacement);
|
|
}
|
|
|
|
/**
|
|
* @return Number of extra bytes.
|
|
*/
|
|
static unsigned int modrmbytes(unsigned char a, unsigned char b) {
|
|
unsigned char lower = (a & 0x0f);
|
|
if (a >= 0xc0) {
|
|
return 0;
|
|
} else if (a >= 0x80) {
|
|
if ((lower == 4) || (lower == 12))
|
|
return 5;
|
|
else
|
|
return 4;
|
|
} else if (a >= 0x40) {
|
|
if ((lower == 4) || (lower == 12))
|
|
return 2;
|
|
else
|
|
return 1;
|
|
|
|
} else {
|
|
if ((lower == 4) || (lower == 12)) {
|
|
if ((b & 0x07) == 0x05)
|
|
return 5;
|
|
else
|
|
return 1;
|
|
} else if ((lower == 5) || (lower == 13))
|
|
return 4;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Tries to construct a trampoline from original code.
|
|
*
|
|
* A trampoline is the replacement code that features the original code plus
|
|
* a jump back to the original instructions that follow.
|
|
* It is called to execute the original behavior. As it is a replacement for
|
|
* the original, the original can then be overwritten.
|
|
* The size of the trampoline is at least CODEREPLACESIZE. Thus, CODEREPLACESIZE
|
|
* bytes of the original code can afterwards be overwritten (and the trampoline
|
|
* called after those instructions for the original logic).
|
|
* CODEREPLACESIZE has to be smaller than CODEPROTECTSIZE.
|
|
*
|
|
* As commands must not be destroyed they have to be disassembled to get their length.
|
|
* All encountered commands will be part of the trampoline and stored in pCode (shared
|
|
* for all trampolines).
|
|
*
|
|
* If code is encountered that can not be moved into the trampoline (conditionals etc.)
|
|
* construction fails and nullptr is returned. If enough commands can be saved the
|
|
* trampoline is finalized by appending a jump back to the original code. The return value
|
|
* in this case will be the address of the newly constructed trampoline.
|
|
*
|
|
* pCode + offset to trampoline:
|
|
* [SAVED CODE FROM ORIGINAL which is >= CODEREPLACESIZE bytes][JUMP BACK TO ORIGINAL CODE]
|
|
*
|
|
* @param porig Original code
|
|
* @return Pointer to trampoline on success. nullptr if trampoline construction failed.
|
|
*/
|
|
void *HardHook::cloneCode(void **porig) {
|
|
if (!pCode || uiCode > 4000) {
|
|
pCode = VirtualAlloc(nullptr, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
|
uiCode = 0;
|
|
}
|
|
// If we have no memory to clone to, return.
|
|
if (!pCode) {
|
|
return nullptr;
|
|
}
|
|
|
|
unsigned char *o = (unsigned char *) *porig;
|
|
|
|
DWORD origProtect;
|
|
if (!VirtualProtect(o, CODEPROTECTSIZE, PAGE_EXECUTE_READ, &origProtect)) {
|
|
fods("HardHook: CloneCode failed; failed to make original code read and executable");
|
|
return nullptr;
|
|
}
|
|
|
|
// Follow relative jumps to next instruction. On execution it doesn't make
|
|
// a difference if we actually perform all the jumps or directly jump to the
|
|
// end of the chain. Hence these jumps need not be part of the trampoline.
|
|
while (*o == 0xe9) { // JMP
|
|
unsigned char *tmp = o;
|
|
int *iptr = reinterpret_cast< int * >(o + 1);
|
|
o += *iptr + 5;
|
|
|
|
fods("HardHook: CloneCode: Skipping jump from %p to %p", *porig, o);
|
|
*porig = o;
|
|
|
|
// Assume jump took us out of our read enabled zone, get rights for the new one
|
|
DWORD tempProtect;
|
|
VirtualProtect(tmp, CODEPROTECTSIZE, origProtect, &tempProtect);
|
|
if (!VirtualProtect(o, CODEPROTECTSIZE, PAGE_EXECUTE_READ, &origProtect)) {
|
|
fods("HardHook: CloneCode failed; failed to make jump target code read and executable");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
unsigned char *n = (unsigned char *) pCode;
|
|
n += uiCode;
|
|
unsigned int idx = 0;
|
|
|
|
do {
|
|
unsigned char opcode = o[idx];
|
|
unsigned char a = o[idx + 1];
|
|
unsigned char b = o[idx + 2];
|
|
unsigned int extra = 0;
|
|
|
|
switch (opcode) {
|
|
case 0x50: // PUSH
|
|
case 0x51:
|
|
case 0x52:
|
|
case 0x53:
|
|
case 0x54:
|
|
case 0x55:
|
|
case 0x56:
|
|
case 0x57:
|
|
case 0x58: // POP
|
|
case 0x59:
|
|
case 0x5a:
|
|
case 0x5b:
|
|
case 0x5c:
|
|
case 0x5d:
|
|
case 0x5e:
|
|
case 0x5f:
|
|
break;
|
|
case 0x6a: // PUSH immediate
|
|
extra = 1;
|
|
break;
|
|
case 0x68: // PUSH immediate
|
|
extra = 4;
|
|
break;
|
|
case 0x81: // CMP immediate
|
|
extra = modrmbytes(a, b) + 5;
|
|
break;
|
|
case 0x83: // CMP
|
|
extra = modrmbytes(a, b) + 2;
|
|
break;
|
|
case 0x8b: // MOV
|
|
extra = modrmbytes(a, b) + 1;
|
|
break;
|
|
default: {
|
|
int rmop = ((a >> 3) & 7);
|
|
if (opcode == 0xff && rmop == 6) { // PUSH memory
|
|
extra = modrmbytes(a, b) + 1;
|
|
break;
|
|
}
|
|
|
|
fods("HardHook: CloneCode failed; Unknown opcode %02x at %d: %02x %02x %02x %02x %02x %02x %02x %02x "
|
|
"%02x %02x %02x %02x",
|
|
opcode, idx, o[0], o[1], o[2], o[3], o[4], o[5], o[6], o[7], o[8], o[9], o[10], o[11]);
|
|
DWORD tempProtect;
|
|
VirtualProtect(o, CODEPROTECTSIZE, origProtect, &tempProtect);
|
|
return nullptr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
n[idx] = opcode;
|
|
++idx;
|
|
|
|
for (unsigned int i = 0; i < extra; ++i)
|
|
n[idx + i] = o[idx + i];
|
|
idx += extra;
|
|
|
|
} while (idx < CODEREPLACESIZE);
|
|
|
|
DWORD tempProtect;
|
|
VirtualProtect(o, CODEPROTECTSIZE, origProtect, &tempProtect);
|
|
|
|
// Add a relative jmp back to the original code, to after the copied code
|
|
n[idx++] = 0xe9;
|
|
int *iptr = reinterpret_cast< int * >(&n[idx]);
|
|
const int JMP_OP_SIZE = 5;
|
|
int offs = o - n - JMP_OP_SIZE;
|
|
*iptr = offs;
|
|
idx += 4;
|
|
|
|
uiCode += idx;
|
|
|
|
FlushInstructionCache(GetCurrentProcess(), n, idx);
|
|
|
|
fods("HardHook: trampoline creation successful at %p", n);
|
|
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* @brief Makes sure the given replacement function is run once func is called.
|
|
*
|
|
* Tries to construct a trampoline for the given function (@see HardHook::cloneCode)
|
|
* and then injects replacement function calling code into the first 6 bytes of the
|
|
* original function (@see HardHook::inject).
|
|
*
|
|
* @param func Pointer to function to redirect.
|
|
* @param replacement Pointer to code to redirect to.
|
|
*/
|
|
void HardHook::setup(voidFunc func, voidFunc replacement) {
|
|
if (baseptr)
|
|
return;
|
|
|
|
fods("HardHook: Setup: Asked to replace %p with %p", func, replacement);
|
|
|
|
unsigned char *fptr = reinterpret_cast< unsigned char * >(func);
|
|
unsigned char *nptr = reinterpret_cast< unsigned char * >(replacement);
|
|
|
|
call = (voidFunc) cloneCode((void **) &fptr);
|
|
|
|
if (call) {
|
|
bTrampoline = true;
|
|
} else {
|
|
// Could not create a trampoline. Use alternative method instead.
|
|
// This alternative method is dependant on the replacement code
|
|
// restoring before calling the original. Otherwise we get a jump recursion
|
|
bTrampoline = false;
|
|
call = func;
|
|
}
|
|
|
|
DWORD origProtect;
|
|
if (VirtualProtect(fptr, CODEPROTECTSIZE, PAGE_EXECUTE_READ, &origProtect)) {
|
|
replace[0] = 0x68; // PUSH immediate 1 Byte
|
|
unsigned char **iptr = reinterpret_cast< unsigned char ** >(&replace[1]);
|
|
*iptr = nptr; // (imm. value = nptr) 4 Byte
|
|
replace[5] = 0xc3; // RETN 1 Byte
|
|
|
|
// Save original 6 bytes at start of original function
|
|
for (int i = 0; i < CODEREPLACESIZE; ++i)
|
|
orig[i] = fptr[i];
|
|
|
|
baseptr = fptr;
|
|
|
|
inject(true);
|
|
|
|
DWORD tempProtect;
|
|
VirtualProtect(fptr, CODEPROTECTSIZE, origProtect, &tempProtect);
|
|
} else {
|
|
fods("HardHook: setup failed; failed to make original code read and executable");
|
|
}
|
|
}
|
|
|
|
void HardHook::setupInterface(IUnknown *unkn, LONG funcoffset, voidFunc replacement) {
|
|
fods("HardHook: setupInterface: Replacing %p function #%ld", unkn, funcoffset);
|
|
void **ptr = reinterpret_cast< void ** >(unkn);
|
|
ptr = reinterpret_cast< void ** >(ptr[0]);
|
|
setup(reinterpret_cast< voidFunc >(ptr[funcoffset]), replacement);
|
|
}
|
|
|
|
void HardHook::reset() {
|
|
baseptr = 0;
|
|
bTrampoline = false;
|
|
call = nullptr;
|
|
for (int i = 0; i < CODEREPLACESIZE; ++i) {
|
|
orig[i] = replace[i] = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Injects redirection code into the target function.
|
|
*
|
|
* Replaces the first 6 Bytes of the function indicated by baseptr
|
|
* with the replacement code previously generated (usually a jump
|
|
* to mumble code). If a trampoline is available this injection is not needed
|
|
* as control flow was already permanently redirected by HardHook::setup .
|
|
*
|
|
* @param force Perform injection even when trampoline is available.
|
|
*/
|
|
void HardHook::inject(bool force) {
|
|
if (!baseptr)
|
|
return;
|
|
if (!force && bTrampoline)
|
|
return;
|
|
|
|
DWORD origProtect;
|
|
if (VirtualProtect(baseptr, CODEREPLACESIZE, PAGE_EXECUTE_READWRITE, &origProtect)) {
|
|
for (int i = 0; i < CODEREPLACESIZE; ++i) {
|
|
baseptr[i] = replace[i]; // Replace with jump to new code
|
|
}
|
|
|
|
DWORD tempProtect;
|
|
VirtualProtect(baseptr, CODEREPLACESIZE, origProtect, &tempProtect);
|
|
|
|
FlushInstructionCache(GetCurrentProcess(), baseptr, CODEREPLACESIZE);
|
|
}
|
|
|
|
// Verify that the injection was successful
|
|
for (int i = 0; i < CODEREPLACESIZE; ++i) {
|
|
if (baseptr[i] != replace[i]) {
|
|
fods("HardHook: Injection failure noticed at byte %d", i);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Restores the original code in a target function.
|
|
*
|
|
* Restores the first 6 Bytes of the function indicated by baseptr
|
|
* from previously stored original code in orig. If a trampoline is available this
|
|
* restoration is not needed as trampoline will correctly restore control
|
|
* flow.
|
|
*
|
|
* @param force If true injection will be reverted even when trampoline is available.
|
|
*/
|
|
void HardHook::restore(bool force) {
|
|
if (!baseptr)
|
|
return;
|
|
if (!force && bTrampoline)
|
|
return;
|
|
|
|
DWORD origProtect;
|
|
if (VirtualProtect(baseptr, CODEREPLACESIZE, PAGE_EXECUTE_READWRITE, &origProtect)) {
|
|
for (int i = 0; i < CODEREPLACESIZE; ++i)
|
|
baseptr[i] = orig[i];
|
|
DWORD tempProtect;
|
|
VirtualProtect(baseptr, CODEREPLACESIZE, origProtect, &tempProtect);
|
|
|
|
FlushInstructionCache(GetCurrentProcess(), baseptr, CODEREPLACESIZE);
|
|
}
|
|
}
|
|
|
|
void HardHook::print() {
|
|
fods("HardHook: code replacement: %02x %02x %02x %02x %02x => %02x %02x %02x %02x %02x (currently effective: %02x "
|
|
"%02x %02x %02x %02x)",
|
|
orig[0], orig[1], orig[2], orig[3], orig[4], replace[0], replace[1], replace[2], replace[3], replace[4],
|
|
baseptr[0], baseptr[1], baseptr[2], baseptr[3], baseptr[4]);
|
|
}
|
|
|
|
/**
|
|
* @brief Checks whether injected code is in good shape and injects if not yet injected.
|
|
*
|
|
* If injected code is not found injection is attempted unless 3rd party overwrote
|
|
* original code at injection location.
|
|
*/
|
|
void HardHook::check() {
|
|
if (memcmp(baseptr, replace, CODEREPLACESIZE) != 0) {
|
|
// The instructions do not match our replacement instructions
|
|
// If they match the original code, inject our hook.
|
|
if (memcmp(baseptr, orig, CODEREPLACESIZE) == 0) {
|
|
fods("HardHook: Reinjecting hook into function %p", baseptr);
|
|
inject(true);
|
|
} else {
|
|
fods("HardHook: Function %p replaced by third party. Lost injected hook.");
|
|
}
|
|
}
|
|
}
|