blob: 9ce08168019f5893e322a42b58f1d2f90af6e569 [file] [log] [blame]
// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-
/* Copyright (c) 2007, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* 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 COPYRIGHT
* OWNER 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.
*
* ---
* Author: Joi Sigurdsson
* Author: Scott Francis
*
* Implementation of PreamblePatcher
*/
#include "preamble_patcher.h"
#include "mini_disassembler.h"
// compatibility shims
#include "base/logging.h"
// Definitions of assembly statements we need
#define ASM_JMP32REL 0xE9
#define ASM_INT3 0xCC
#define ASM_JMP32ABS_0 0xFF
#define ASM_JMP32ABS_1 0x25
#define ASM_JMP8REL 0xEB
#define ASM_JCC32REL_0 0x0F
#define ASM_JCC32REL_1_MASK 0x80
#define ASM_NOP 0x90
// X64 opcodes
#define ASM_REXW 0x48
#define ASM_MOVRAX_IMM 0xB8
#define ASM_JMP 0xFF
#define ASM_JMP_RAX 0xE0
namespace sidestep {
PreamblePatcher::PreamblePage* PreamblePatcher::preamble_pages_ = NULL;
long PreamblePatcher::granularity_ = 0;
long PreamblePatcher::pagesize_ = 0;
bool PreamblePatcher::initialized_ = false;
static const unsigned int kPreamblePageMagic = 0x4347414D; // "MAGC"
// Handle a special case that we see with functions that point into an
// IAT table (including functions linked statically into the
// application): these function already starts with ASM_JMP32*. For
// instance, malloc() might be implemented as a JMP to __malloc().
// This function follows the initial JMPs for us, until we get to the
// place where the actual code is defined. If we get to STOP_BEFORE,
// we return the address before stop_before. The stop_before_trampoline
// flag is used in 64-bit mode. If true, we will return the address
// before a trampoline is detected. Trampolines are defined as:
//
// nop
// mov rax, <replacement_function>
// jmp rax
//
// See PreamblePatcher::RawPatchWithStub for more information.
void* PreamblePatcher::ResolveTargetImpl(unsigned char* target,
unsigned char* stop_before,
bool stop_before_trampoline) {
if (target == NULL)
return NULL;
while (1) {
unsigned char* new_target;
if (target[0] == ASM_JMP32REL) {
// target[1-4] holds the place the jmp goes to, but it's
// relative to the next instruction.
int relative_offset; // Windows guarantees int is 4 bytes
SIDESTEP_ASSERT(sizeof(relative_offset) == 4);
memcpy(reinterpret_cast<void*>(&relative_offset),
reinterpret_cast<void*>(target + 1), 4);
new_target = target + 5 + relative_offset;
} else if (target[0] == ASM_JMP8REL) {
// Visual Studio 7.1 implements new[] as an 8 bit jump to new
signed char relative_offset;
memcpy(reinterpret_cast<void*>(&relative_offset),
reinterpret_cast<void*>(target + 1), 1);
new_target = target + 2 + relative_offset;
} else if (target[0] == ASM_JMP32ABS_0 &&
target[1] == ASM_JMP32ABS_1) {
jmp32rel:
// Visual studio seems to sometimes do it this way instead of the
// previous way. Not sure what the rules are, but it was happening
// with operator new in some binaries.
void** new_target_v;
if (kIs64BitBinary) {
// In 64-bit mode JMPs are RIP-relative, not absolute
int target_offset;
memcpy(reinterpret_cast<void*>(&target_offset),
reinterpret_cast<void*>(target + 2), 4);
new_target_v = reinterpret_cast<void**>(target + target_offset + 6);
} else {
SIDESTEP_ASSERT(sizeof(new_target) == 4);
memcpy(&new_target_v, reinterpret_cast<void*>(target + 2), 4);
}
new_target = reinterpret_cast<unsigned char*>(*new_target_v);
} else if (kIs64BitBinary && target[0] == ASM_REXW
&& target[1] == ASM_JMP32ABS_0
&& target[2] == ASM_JMP32ABS_1) {
// in Visual Studio 2012 we're seeing jump like that:
// rex.W jmpq *0x11d019(%rip)
//
// according to docs I have, rex prefix is actually unneeded and
// can be ignored. I.e. docs say for jumps like that operand
// already defaults to 64-bit. But clearly it breaks abs. jump
// detection above and we just skip rex
target++;
goto jmp32rel;
} else {
break;
}
if (new_target == stop_before)
break;
if (stop_before_trampoline && *new_target == ASM_NOP
&& new_target[1] == ASM_REXW && new_target[2] == ASM_MOVRAX_IMM)
break;
target = new_target;
}
return target;
}
// Special case scoped_ptr to avoid dependency on scoped_ptr below.
class DeleteUnsignedCharArray {
public:
DeleteUnsignedCharArray(unsigned char* array) : array_(array) {
}
~DeleteUnsignedCharArray() {
if (array_) {
PreamblePatcher::FreePreambleBlock(array_);
}
}
unsigned char* Release() {
unsigned char* temp = array_;
array_ = NULL;
return temp;
}
private:
unsigned char* array_;
};
SideStepError PreamblePatcher::RawPatchWithStubAndProtections(
void* target_function, void *replacement_function,
unsigned char* preamble_stub, unsigned long stub_size,
unsigned long* bytes_needed) {
// We need to be able to write to a process-local copy of the first
// MAX_PREAMBLE_STUB_SIZE bytes of target_function
DWORD old_target_function_protect = 0;
BOOL succeeded = ::VirtualProtect(reinterpret_cast<void*>(target_function),
MAX_PREAMBLE_STUB_SIZE,
PAGE_EXECUTE_READWRITE,
&old_target_function_protect);
if (!succeeded) {
SIDESTEP_ASSERT(false && "Failed to make page containing target function "
"copy-on-write.");
return SIDESTEP_ACCESS_DENIED;
}
SideStepError error_code = RawPatchWithStub(target_function,
replacement_function,
preamble_stub,
stub_size,
bytes_needed);
// Restore the protection of the first MAX_PREAMBLE_STUB_SIZE bytes of
// pTargetFunction to what they were before we started goofing around.
// We do this regardless of whether the patch succeeded or not.
succeeded = ::VirtualProtect(reinterpret_cast<void*>(target_function),
MAX_PREAMBLE_STUB_SIZE,
old_target_function_protect,
&old_target_function_protect);
if (!succeeded) {
SIDESTEP_ASSERT(false &&
"Failed to restore protection to target function.");
// We must not return an error here because the function has
// likely actually been patched, and returning an error might
// cause our client code not to unpatch it. So we just keep
// going.
}
if (SIDESTEP_SUCCESS != error_code) { // Testing RawPatchWithStub, above
SIDESTEP_ASSERT(false);
return error_code;
}
// Flush the instruction cache to make sure the processor doesn't execute the
// old version of the instructions (before our patch).
//
// FlushInstructionCache is actually a no-op at least on
// single-processor XP machines. I'm not sure why this is so, but
// it is, yet I want to keep the call to the API here for
// correctness in case there is a difference in some variants of
// Windows/hardware.
succeeded = ::FlushInstructionCache(::GetCurrentProcess(),
target_function,
MAX_PREAMBLE_STUB_SIZE);
if (!succeeded) {
SIDESTEP_ASSERT(false && "Failed to flush instruction cache.");
// We must not return an error here because the function has actually
// been patched, and returning an error would likely cause our client
// code not to unpatch it. So we just keep going.
}
return SIDESTEP_SUCCESS;
}
SideStepError PreamblePatcher::RawPatch(void* target_function,
void* replacement_function,
void** original_function_stub) {
if (!target_function || !replacement_function || !original_function_stub ||
(*original_function_stub) || target_function == replacement_function) {
SIDESTEP_ASSERT(false && "Preconditions not met");
return SIDESTEP_INVALID_PARAMETER;
}
BOOL succeeded = FALSE;
// First, deal with a special case that we see with functions that
// point into an IAT table (including functions linked statically
// into the application): these function already starts with
// ASM_JMP32REL. For instance, malloc() might be implemented as a
// JMP to __malloc(). In that case, we replace the destination of
// the JMP (__malloc), rather than the JMP itself (malloc). This
// way we get the correct behavior no matter how malloc gets called.
void* new_target = ResolveTarget(target_function);
if (new_target != target_function) {
target_function = new_target;
}
// In 64-bit mode, preamble_stub must be within 2GB of target function
// so that if target contains a jump, we can translate it.
unsigned char* preamble_stub = AllocPreambleBlockNear(target_function);
if (!preamble_stub) {
SIDESTEP_ASSERT(false && "Unable to allocate preamble-stub.");
return SIDESTEP_INSUFFICIENT_BUFFER;
}
// Frees the array at end of scope.
DeleteUnsignedCharArray guard_preamble_stub(preamble_stub);
SideStepError error_code = RawPatchWithStubAndProtections(
target_function, replacement_function, preamble_stub,
MAX_PREAMBLE_STUB_SIZE, NULL);
if (SIDESTEP_SUCCESS != error_code) {
SIDESTEP_ASSERT(false);
return error_code;
}
// Flush the instruction cache to make sure the processor doesn't execute the
// old version of the instructions (before our patch).
//
// FlushInstructionCache is actually a no-op at least on
// single-processor XP machines. I'm not sure why this is so, but
// it is, yet I want to keep the call to the API here for
// correctness in case there is a difference in some variants of
// Windows/hardware.
succeeded = ::FlushInstructionCache(::GetCurrentProcess(),
target_function,
MAX_PREAMBLE_STUB_SIZE);
if (!succeeded) {
SIDESTEP_ASSERT(false && "Failed to flush instruction cache.");
// We must not return an error here because the function has actually
// been patched, and returning an error would likely cause our client
// code not to unpatch it. So we just keep going.
}
SIDESTEP_LOG("PreamblePatcher::RawPatch successfully patched.");
// detach the scoped pointer so the memory is not freed
*original_function_stub =
reinterpret_cast<void*>(guard_preamble_stub.Release());
return SIDESTEP_SUCCESS;
}
SideStepError PreamblePatcher::Unpatch(void* target_function,
void* replacement_function,
void* original_function_stub) {
SIDESTEP_ASSERT(target_function && replacement_function &&
original_function_stub);
if (!target_function || !replacement_function ||
!original_function_stub) {
return SIDESTEP_INVALID_PARAMETER;
}
// Before unpatching, target_function should be a JMP to
// replacement_function. If it's not, then either it's an error, or
// we're falling into the case where the original instruction was a
// JMP, and we patched the jumped_to address rather than the JMP
// itself. (For instance, if malloc() is just a JMP to __malloc(),
// we patched __malloc() and not malloc().)
unsigned char* target = reinterpret_cast<unsigned char*>(target_function);
target = reinterpret_cast<unsigned char*>(
ResolveTargetImpl(
target, reinterpret_cast<unsigned char*>(replacement_function),
true));
// We should end at the function we patched. When we patch, we insert
// a ASM_JMP32REL instruction, so look for that as a sanity check.
if (target[0] != ASM_JMP32REL) {
SIDESTEP_ASSERT(false &&
"target_function does not look like it was patched.");
return SIDESTEP_INVALID_PARAMETER;
}
const unsigned int kRequiredTargetPatchBytes = 5;
// We need to be able to write to a process-local copy of the first
// kRequiredTargetPatchBytes bytes of target_function
DWORD old_target_function_protect = 0;
BOOL succeeded = ::VirtualProtect(reinterpret_cast<void*>(target),
kRequiredTargetPatchBytes,
PAGE_EXECUTE_READWRITE,
&old_target_function_protect);
if (!succeeded) {
SIDESTEP_ASSERT(false && "Failed to make page containing target function "
"copy-on-write.");
return SIDESTEP_ACCESS_DENIED;
}
unsigned char* preamble_stub = reinterpret_cast<unsigned char*>(
original_function_stub);
// Disassemble the preamble of stub and copy the bytes back to target.
// If we've done any conditional jumps in the preamble we need to convert
// them back to the original REL8 jumps in the target.
MiniDisassembler disassembler;
unsigned int preamble_bytes = 0;
unsigned int target_bytes = 0;
while (target_bytes < kRequiredTargetPatchBytes) {
unsigned int cur_bytes = 0;
InstructionType instruction_type =
disassembler.Disassemble(preamble_stub + preamble_bytes, cur_bytes);
if (IT_JUMP == instruction_type) {
unsigned int jump_bytes = 0;
SideStepError jump_ret = SIDESTEP_JUMP_INSTRUCTION;
if (IsNearConditionalJump(preamble_stub + preamble_bytes, cur_bytes) ||
IsNearRelativeJump(preamble_stub + preamble_bytes, cur_bytes) ||
IsNearAbsoluteCall(preamble_stub + preamble_bytes, cur_bytes) ||
IsNearRelativeCall(preamble_stub + preamble_bytes, cur_bytes)) {
jump_ret = PatchNearJumpOrCall(preamble_stub + preamble_bytes,
cur_bytes, target + target_bytes,
&jump_bytes, MAX_PREAMBLE_STUB_SIZE);
}
if (jump_ret == SIDESTEP_JUMP_INSTRUCTION) {
SIDESTEP_ASSERT(false &&
"Found unsupported jump instruction in stub!!");
return SIDESTEP_UNSUPPORTED_INSTRUCTION;
}
target_bytes += jump_bytes;
} else if (IT_GENERIC == instruction_type) {
if (IsMovWithDisplacement(preamble_stub + preamble_bytes, cur_bytes)) {
unsigned int mov_bytes = 0;
if (PatchMovWithDisplacement(preamble_stub + preamble_bytes, cur_bytes,
target + target_bytes, &mov_bytes,
MAX_PREAMBLE_STUB_SIZE)
!= SIDESTEP_SUCCESS) {
SIDESTEP_ASSERT(false &&
"Found unsupported generic instruction in stub!!");
return SIDESTEP_UNSUPPORTED_INSTRUCTION;
}
} else {
memcpy(reinterpret_cast<void*>(target + target_bytes),
reinterpret_cast<void*>(reinterpret_cast<unsigned char*>(
original_function_stub) + preamble_bytes), cur_bytes);
target_bytes += cur_bytes;
}
} else {
SIDESTEP_ASSERT(false &&
"Found unsupported instruction in stub!!");
return SIDESTEP_UNSUPPORTED_INSTRUCTION;
}
preamble_bytes += cur_bytes;
}
FreePreambleBlock(reinterpret_cast<unsigned char*>(original_function_stub));
// Restore the protection of the first kRequiredTargetPatchBytes bytes of
// target to what they were before we started goofing around.
succeeded = ::VirtualProtect(reinterpret_cast<void*>(target),
kRequiredTargetPatchBytes,
old_target_function_protect,
&old_target_function_protect);
// Flush the instruction cache to make sure the processor doesn't execute the
// old version of the instructions (before our patch).
//
// See comment on FlushInstructionCache elsewhere in this file.
succeeded = ::FlushInstructionCache(::GetCurrentProcess(),
target,
MAX_PREAMBLE_STUB_SIZE);
if (!succeeded) {
SIDESTEP_ASSERT(false && "Failed to flush instruction cache.");
return SIDESTEP_UNEXPECTED;
}
SIDESTEP_LOG("PreamblePatcher::Unpatch successfully unpatched.");
return SIDESTEP_SUCCESS;
}
void PreamblePatcher::Initialize() {
if (!initialized_) {
SYSTEM_INFO si = { 0 };
::GetSystemInfo(&si);
granularity_ = si.dwAllocationGranularity;
pagesize_ = si.dwPageSize;
initialized_ = true;
}
}
unsigned char* PreamblePatcher::AllocPreambleBlockNear(void* target) {
PreamblePage* preamble_page = preamble_pages_;
while (preamble_page != NULL) {
if (preamble_page->free_ != NULL) {
__int64 val = reinterpret_cast<__int64>(preamble_page) -
reinterpret_cast<__int64>(target);
if ((val > 0 && val + pagesize_ <= INT_MAX) ||
(val < 0 && val >= INT_MIN)) {
break;
}
}
preamble_page = preamble_page->next_;
}
// The free_ member of the page is used to store the next available block
// of memory to use or NULL if there are no chunks available, in which case
// we'll allocate a new page.
if (preamble_page == NULL || preamble_page->free_ == NULL) {
// Create a new preamble page and initialize the free list
preamble_page = reinterpret_cast<PreamblePage*>(AllocPageNear(target));
SIDESTEP_ASSERT(preamble_page != NULL && "Could not allocate page!");
void** pp = &preamble_page->free_;
unsigned char* ptr = reinterpret_cast<unsigned char*>(preamble_page) +
MAX_PREAMBLE_STUB_SIZE;
unsigned char* limit = reinterpret_cast<unsigned char*>(preamble_page) +
pagesize_;
while (ptr < limit) {
*pp = ptr;
pp = reinterpret_cast<void**>(ptr);
ptr += MAX_PREAMBLE_STUB_SIZE;
}
*pp = NULL;
// Insert the new page into the list
preamble_page->magic_ = kPreamblePageMagic;
preamble_page->next_ = preamble_pages_;
preamble_pages_ = preamble_page;
}
unsigned char* ret = reinterpret_cast<unsigned char*>(preamble_page->free_);
preamble_page->free_ = *(reinterpret_cast<void**>(preamble_page->free_));
return ret;
}
void PreamblePatcher::FreePreambleBlock(unsigned char* block) {
SIDESTEP_ASSERT(block != NULL);
SIDESTEP_ASSERT(granularity_ != 0);
uintptr_t ptr = reinterpret_cast<uintptr_t>(block);
ptr -= ptr & (granularity_ - 1);
PreamblePage* preamble_page = reinterpret_cast<PreamblePage*>(ptr);
SIDESTEP_ASSERT(preamble_page->magic_ == kPreamblePageMagic);
*(reinterpret_cast<void**>(block)) = preamble_page->free_;
preamble_page->free_ = block;
}
void* PreamblePatcher::AllocPageNear(void* target) {
MEMORY_BASIC_INFORMATION mbi = { 0 };
if (!::VirtualQuery(target, &mbi, sizeof(mbi))) {
SIDESTEP_ASSERT(false && "VirtualQuery failed on target address");
return 0;
}
if (initialized_ == false) {
PreamblePatcher::Initialize();
SIDESTEP_ASSERT(initialized_);
}
void* pv = NULL;
unsigned char* allocation_base = reinterpret_cast<unsigned char*>(
mbi.AllocationBase);
__int64 i = 1;
bool high_target = reinterpret_cast<__int64>(target) > UINT_MAX;
while (pv == NULL) {
__int64 val = reinterpret_cast<__int64>(allocation_base) -
(i * granularity_);
if (high_target &&
reinterpret_cast<__int64>(target) - val > INT_MAX) {
// We're further than 2GB from the target
break;
} else if (val <= 0) {
// Less than 0
break;
}
pv = ::VirtualAlloc(reinterpret_cast<void*>(allocation_base -
(i++ * granularity_)),
pagesize_, MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
}
// We couldn't allocate low, try to allocate high
if (pv == NULL) {
i = 1;
// Round up to the next multiple of page granularity
allocation_base = reinterpret_cast<unsigned char*>(
(reinterpret_cast<__int64>(target) &
(~(granularity_ - 1))) + granularity_);
while (pv == NULL) {
__int64 val = reinterpret_cast<__int64>(allocation_base) +
(i * granularity_) - reinterpret_cast<__int64>(target);
if (val > INT_MAX || val < 0) {
// We're too far or we overflowed
break;
}
pv = ::VirtualAlloc(reinterpret_cast<void*>(allocation_base +
(i++ * granularity_)),
pagesize_, MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
}
}
return pv;
}
bool PreamblePatcher::IsShortConditionalJump(
unsigned char* target,
unsigned int instruction_size) {
return (*(target) & 0x70) == 0x70 && instruction_size == 2;
}
bool PreamblePatcher::IsShortJump(
unsigned char* target,
unsigned int instruction_size) {
return target[0] == 0xeb && instruction_size == 2;
}
bool PreamblePatcher::IsNearConditionalJump(
unsigned char* target,
unsigned int instruction_size) {
return *(target) == 0xf && (*(target + 1) & 0x80) == 0x80 &&
instruction_size == 6;
}
bool PreamblePatcher::IsNearRelativeJump(
unsigned char* target,
unsigned int instruction_size) {
return *(target) == 0xe9 && instruction_size == 5;
}
bool PreamblePatcher::IsNearAbsoluteCall(
unsigned char* target,
unsigned int instruction_size) {
return *(target) == 0xff && (*(target + 1) & 0x10) == 0x10 &&
instruction_size == 6;
}
bool PreamblePatcher::IsNearRelativeCall(
unsigned char* target,
unsigned int instruction_size) {
return *(target) == 0xe8 && instruction_size == 5;
}
bool PreamblePatcher::IsMovWithDisplacement(
unsigned char* target,
unsigned int instruction_size) {
// In this case, the ModRM byte's mod field will be 0 and r/m will be 101b (5)
return instruction_size == 7 && *target == 0x48 && *(target + 1) == 0x8b &&
(*(target + 2) >> 6) == 0 && (*(target + 2) & 0x7) == 5;
}
SideStepError PreamblePatcher::PatchShortConditionalJump(
unsigned char* source,
unsigned int instruction_size,
unsigned char* target,
unsigned int* target_bytes,
unsigned int target_size) {
// note: rel8 offset is signed. Thus we need to ask for signed char
// to negative offsets right
unsigned char* original_jump_dest = (source + 2) + static_cast<signed char>(source[1]);
unsigned char* stub_jump_from = target + 6;
__int64 fixup_jump_offset = original_jump_dest - stub_jump_from;
if (fixup_jump_offset > INT_MAX || fixup_jump_offset < INT_MIN) {
SIDESTEP_ASSERT(false &&
"Unable to fix up short jump because target"
" is too far away.");
return SIDESTEP_JUMP_INSTRUCTION;
}
*target_bytes = 6;
if (target_size > *target_bytes) {
// Convert the short jump to a near jump.
//
// 0f 8x xx xx xx xx = Jcc rel32off
unsigned short jmpcode = ((0x80 | (source[0] & 0xf)) << 8) | 0x0f;
memcpy(reinterpret_cast<void*>(target),
reinterpret_cast<void*>(&jmpcode), 2);
memcpy(reinterpret_cast<void*>(target + 2),
reinterpret_cast<void*>(&fixup_jump_offset), 4);
}
return SIDESTEP_SUCCESS;
}
SideStepError PreamblePatcher::PatchShortJump(
unsigned char* source,
unsigned int instruction_size,
unsigned char* target,
unsigned int* target_bytes,
unsigned int target_size) {
// note: rel8 offset is _signed_. Thus we need signed char here.
unsigned char* original_jump_dest = (source + 2) + static_cast<signed char>(source[1]);
unsigned char* stub_jump_from = target + 5;
__int64 fixup_jump_offset = original_jump_dest - stub_jump_from;
if (fixup_jump_offset > INT_MAX || fixup_jump_offset < INT_MIN) {
SIDESTEP_ASSERT(false &&
"Unable to fix up short jump because target"
" is too far away.");
return SIDESTEP_JUMP_INSTRUCTION;
}
*target_bytes = 5;
if (target_size > *target_bytes) {
// Convert the short jump to a near jump.
//
// e9 xx xx xx xx = jmp rel32off
target[0] = 0xe9;
memcpy(reinterpret_cast<void*>(target + 1),
reinterpret_cast<void*>(&fixup_jump_offset), 4);
}
return SIDESTEP_SUCCESS;
}
SideStepError PreamblePatcher::PatchNearJumpOrCall(
unsigned char* source,
unsigned int instruction_size,
unsigned char* target,
unsigned int* target_bytes,
unsigned int target_size) {
SIDESTEP_ASSERT(instruction_size == 5 || instruction_size == 6);
unsigned int jmp_offset_in_instruction = instruction_size == 5 ? 1 : 2;
unsigned char* original_jump_dest = reinterpret_cast<unsigned char *>(
reinterpret_cast<__int64>(source + instruction_size) +
*(reinterpret_cast<int*>(source + jmp_offset_in_instruction)));
unsigned char* stub_jump_from = target + instruction_size;
__int64 fixup_jump_offset = original_jump_dest - stub_jump_from;
if (fixup_jump_offset > INT_MAX || fixup_jump_offset < INT_MIN) {
SIDESTEP_ASSERT(false &&
"Unable to fix up near jump because target"
" is too far away.");
return SIDESTEP_JUMP_INSTRUCTION;
}
if ((fixup_jump_offset < SCHAR_MAX && fixup_jump_offset > SCHAR_MIN)) {
*target_bytes = 2;
if (target_size > *target_bytes) {
// If the new offset is in range, use a short jump instead of a near jump.
if (source[0] == ASM_JCC32REL_0 &&
(source[1] & ASM_JCC32REL_1_MASK) == ASM_JCC32REL_1_MASK) {
unsigned short jmpcode = (static_cast<unsigned char>(
fixup_jump_offset) << 8) | (0x70 | (source[1] & 0xf));
memcpy(reinterpret_cast<void*>(target),
reinterpret_cast<void*>(&jmpcode),
2);
} else {
target[0] = ASM_JMP8REL;
target[1] = static_cast<unsigned char>(fixup_jump_offset);
}
}
} else {
*target_bytes = instruction_size;
if (target_size > *target_bytes) {
memcpy(reinterpret_cast<void*>(target),
reinterpret_cast<void*>(source),
jmp_offset_in_instruction);
memcpy(reinterpret_cast<void*>(target + jmp_offset_in_instruction),
reinterpret_cast<void*>(&fixup_jump_offset),
4);
}
}
return SIDESTEP_SUCCESS;
}
SideStepError PreamblePatcher::PatchMovWithDisplacement(
unsigned char* source,
unsigned int instruction_size,
unsigned char* target,
unsigned int* target_bytes,
unsigned int target_size) {
SIDESTEP_ASSERT(instruction_size == 7);
const int mov_offset_in_instruction = 3; // 0x48 0x8b 0x0d <offset>
unsigned char* original_mov_dest = reinterpret_cast<unsigned char*>(
reinterpret_cast<__int64>(source + instruction_size) +
*(reinterpret_cast<int*>(source + mov_offset_in_instruction)));
unsigned char* stub_mov_from = target + instruction_size;
__int64 fixup_mov_offset = original_mov_dest - stub_mov_from;
if (fixup_mov_offset > INT_MAX || fixup_mov_offset < INT_MIN) {
SIDESTEP_ASSERT(false &&
"Unable to fix up near MOV because target is too far away.");
return SIDESTEP_UNEXPECTED;
}
*target_bytes = instruction_size;
if (target_size > *target_bytes) {
memcpy(reinterpret_cast<void*>(target),
reinterpret_cast<void*>(source),
mov_offset_in_instruction);
memcpy(reinterpret_cast<void*>(target + mov_offset_in_instruction),
reinterpret_cast<void*>(&fixup_mov_offset),
4);
}
return SIDESTEP_SUCCESS;
}
}; // namespace sidestep