Skip to content

Commit

Permalink
Changed cmake to calc deps, removed unnecessary manual shadow space c…
Browse files Browse the repository at this point in the history
…reation, changed argument object, added arg count. Fixed polyhook bug for data type relocations
  • Loading branch information
stevemk14ebr committed Dec 8, 2018
1 parent 9a4ce23 commit 3e30a04
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 49 deletions.
20 changes: 17 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ project(PolyHook_2)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Turn Features On/Off, Set build options
option(FEATURE_DETOURS "Implement detour functionality" ON)
option(FEATURE_EXCEPTION "Implement all exception hooking functionality" ON)
option(FEATURE_VIRTUALS "Implement all virtual table hooking functionality" ON)
Expand All @@ -11,6 +12,12 @@ option(FEATURE_PE "Implement all win pe hooking functionality" ON)
option(BUILD_DLL "Build dll & lib instead of tests" OFF)
option(BUILD_STATIC "If BUILD_DLL is set, create the type that can be statically linked" ON)

# Calculate inclusion of necessary dependencies based on features

# for now only inlinentd uses asmjit
set(DEP_ASMJIT_NEED ${FEATURE_INLINENTD})
# todo: make inclusion of capstone stuff depend on feature flags

set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
#IDE's like it when header file are included as source files
Expand Down Expand Up @@ -129,7 +136,7 @@ if(FEATURE_PE MATCHES ON)
endif()
endif()

if(FEATURE_INLINENTD MATCHES ON)
if(DEP_ASMJIT_NEED MATCHES ON)

# only build tests if making exe
if(BUILD_DLL MATCHES OFF)
Expand All @@ -140,8 +147,15 @@ if(FEATURE_INLINENTD MATCHES ON)
include("${ASMJIT_DIR}/CMakeLists.txt")
include_directories(${ASMJIT_DIR}/src)

set(UNIT_TEST_SOURCES ${UNIT_TEST_SOURCES}
${PROJECT_SOURCE_DIR}/UnitTests/TestAsmJit.cpp)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
# 64 bits
set(UNIT_TEST_SOURCES ${UNIT_TEST_SOURCES}
${PROJECT_SOURCE_DIR}/UnitTests/TestDetourNoTDx64.cpp)
elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
# 32 bits
set(UNIT_TEST_SOURCES ${UNIT_TEST_SOURCES}
${PROJECT_SOURCE_DIR}/UnitTests/TestDetourNoTDx86.cpp)
endif()
endif()
endif()

Expand Down
43 changes: 34 additions & 9 deletions UnitTests/TestAsmJit.cpp → UnitTests/TestDetourNoTDx64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,31 @@ TEST_CASE("Minimal Example", "[AsmJit]") {
#include "headers/Detour/X64Detour.hpp"
#include "headers/CapstoneDisassembler.hpp"


NOINLINE void hookMeInt(int a) {
volatile int var = 1;
int var2 = var + a;
printf("%d %d\n", var, var2);
}

NOINLINE void hookMeFloat(float a) {
volatile float ans = 0.0f;
float ans = 1.0f;
ans += a;
printf("%f %f\n", ans, a);
}
uint64_t hookMeTramp = 0;

NOINLINE void myCallback(const PLH::ILCallback::Parameters* p) {
printf("holy balls it works: asInt:%d asFloat:%f\n", *(int*)p->getArgPtr(0), *(float*)p->getArgPtr(0));
NOINLINE void hookMeIntFloatDouble(int a, float b, double c) {
volatile float ans = 0.0f;
ans += (float)a;
ans += c;
ans += b;
printf("%d %f %f %f\n", a, b, c, ans);
}

NOINLINE void myCallback(const PLH::ILCallback::Parameters* p, const uint8_t count) {
printf("Argument Count: %d\n", count);
for (int i = 0; i < count; i++) {
printf("Arg: %d asInt:%d asFloat:%f asDouble:%f\n", i, *(int*)p->getArgPtr(i), *(float*)p->getArgPtr(i), *(double*)p->getArgPtr(i));
}
}

TEST_CASE("Minimal ILCallback", "[AsmJit][ILCallback]") {
Expand All @@ -59,11 +68,11 @@ TEST_CASE("Minimal ILCallback", "[AsmJit][ILCallback]") {
asmjit::FuncSignature sig;
std::vector<uint8_t> args = { asmjit::TypeIdOf<int>::kTypeId };
sig.init(asmjit::CallConv::kIdHost, asmjit::TypeIdOf<void>::kTypeId, args.data(), (uint32_t)args.size());
uint64_t JIT = callback.getJitFunc(sig, &myCallback, &hookMeTramp);
uint64_t JIT = callback.getJitFunc(sig, &myCallback);
REQUIRE(JIT != 0);

PLH::CapstoneDisassembler dis(PLH::Mode::x64);
PLH::x64Detour detour((char*)&hookMeInt, (char*)JIT, &hookMeTramp, dis);
PLH::x64Detour detour((char*)&hookMeInt, (char*)JIT, callback.getTrampolineHolder(), dis);
REQUIRE(detour.hook() == true);
hookMeInt(1337);
REQUIRE(detour.unHook());
Expand All @@ -74,14 +83,30 @@ TEST_CASE("Minimal ILCallback", "[AsmJit][ILCallback]") {
asmjit::FuncSignature sig;
std::vector<uint8_t> args = { asmjit::TypeIdOf<float>::kTypeId };
sig.init(asmjit::CallConv::kIdHost, asmjit::TypeIdOf<void>::kTypeId, args.data(), (uint32_t)args.size());
uint64_t JIT = callback.getJitFunc(sig, &myCallback, &hookMeTramp);
uint64_t JIT = callback.getJitFunc(sig, &myCallback);
REQUIRE(JIT != 0);

PLH::CapstoneDisassembler dis(PLH::Mode::x64);
PLH::x64Detour detour((char*)&hookMeFloat, (char*)JIT, &hookMeTramp, dis);
PLH::x64Detour detour((char*)&hookMeFloat, (char*)JIT, callback.getTrampolineHolder(), dis);
REQUIRE(detour.hook() == true);

hookMeFloat(1337.1337f);
REQUIRE(detour.unHook());
}

SECTION("Int, float, double arguments") {
// void func(int), ABI must match hooked function
asmjit::FuncSignature sig;
std::vector<uint8_t> args = { asmjit::TypeIdOf<int>::kTypeId, asmjit::TypeIdOf<float>::kTypeId, asmjit::TypeIdOf<double>::kTypeId };
sig.init(asmjit::CallConv::kIdHost, asmjit::TypeIdOf<void>::kTypeId, args.data(), (uint32_t)args.size());
uint64_t JIT = callback.getJitFunc(sig, &myCallback);
REQUIRE(JIT != 0);

PLH::CapstoneDisassembler dis(PLH::Mode::x64);
PLH::x64Detour detour((char*)&hookMeIntFloatDouble, (char*)JIT, callback.getTrampolineHolder(), dis);
REQUIRE(detour.hook() == true);

hookMeIntFloatDouble(1337, 1337.1337f, 1337.1337);
REQUIRE(detour.unHook());
}
}
81 changes: 81 additions & 0 deletions UnitTests/TestDetourNoTDx86.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include <Catch.hpp>

#include "headers/Detour/ILCallback.hpp"
#pragma warning( disable : 4244)

typedef int(*Func)(void);
TEST_CASE("Minimal Example", "[AsmJit]") {
asmjit::JitRuntime rt; // Runtime specialized for JIT code execution.

asmjit::CodeHolder code; // Holds code and relocation information.
code.init(rt.getCodeInfo()); // Initialize to the same arch as JIT runtime.

asmjit::X86Assembler a(&code); // Create and attach X86Assembler to `code`.
a.mov(asmjit::x86::eax, 1); // Move one to 'eax' register.
a.ret(); // Return from function.
// ----> X86Assembler is no longer needed from here and can be destroyed <----

Func fn;
asmjit::Error err = rt.add(&fn, &code); // Add the generated code to the runtime.
if (err) {
REQUIRE(false);
}

int result = fn(); // Execute the generated code.
REQUIRE(result == 1);

// All classes use RAII, all resources will be released before `main()` returns,
// the generated function can be, however, released explicitly if you intend to
// reuse or keep the runtime alive, which you should in a production-ready code.
rt.release(fn);
}

#include "headers/Detour/X86Detour.hpp"
#include "headers/CapstoneDisassembler.hpp"

NOINLINE void hookMeInt(int a) {
volatile int var = 1;
int var2 = var + a;
printf("%d %d\n", var, var2);
}

NOINLINE void hookMeFloat(float a) {
volatile float ans = 0.0f;
ans += a;
printf("%f %f\n", ans, a);
}

NOINLINE void hookMeIntFloatDouble(int a, float b, double c) {
volatile float ans = 0.0f;
ans += (float)a;
ans += c;
ans += b;
printf("%d %f %f %f\n", a, b, c, ans);
}

NOINLINE void myCallback(const PLH::ILCallback::Parameters* p, const uint8_t count) {
printf("Argument Count: %d\n", count);
for (int i = 0; i < count; i++) {
printf("Arg: %d asInt:%d asFloat:%f asDouble:%f\n", i, *(int*)p->getArgPtr(i), *(float*)p->getArgPtr(i), *(double*)p->getArgPtr(i));
}
}

//TEST_CASE("Minimal ILCallback", "[AsmJit][ILCallback]") {
// PLH::ILCallback callback;
//
// SECTION("Integer argument") {
// // void func(int), ABI must match hooked function
// asmjit::FuncSignature sig;
// std::vector<uint8_t> args = { asmjit::TypeIdOf<int>::kTypeId };
// sig.init(asmjit::CallConv::kIdHost, asmjit::TypeIdOf<void>::kTypeId, args.data(), (uint32_t)args.size());
// uint64_t JIT = callback.getJitFunc(sig, &myCallback);
// REQUIRE(JIT != 0);
//
// PLH::CapstoneDisassembler dis(PLH::Mode::x86);
// PLH::x64Detour detour((char*)&hookMeInt, (char*)JIT, callback.getTrampolineHolder(), dis);
// REQUIRE(detour.hook() == true);
// hookMeInt(1337);
// REQUIRE(detour.unHook());
// }
//
//}
2 changes: 1 addition & 1 deletion headers/Detour/ADetour.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class Detour : public PLH::IHook {
uint64_t& minProlSz,
uint64_t& roundProlSz);

void buildRelocationList(insts_t& prologue, const uint64_t roundProlSz, const int64_t delta, PLH::insts_t &instsNeedingEntry, PLH::insts_t &instsNeedingReloc);
bool buildRelocationList(insts_t& prologue, const uint64_t roundProlSz, const int64_t delta, PLH::insts_t &instsNeedingEntry, PLH::insts_t &instsNeedingReloc);

template<typename MakeJmpFn>
PLH::insts_t relocateTrampoline(insts_t& prologue, uint64_t jmpTblStart, const int64_t delta, const uint8_t jmpSz, MakeJmpFn makeJmp, const PLH::insts_t& instsNeedingReloc, const PLH::insts_t& instsNeedingEntry);
Expand Down
8 changes: 6 additions & 2 deletions headers/Detour/ILCallback.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ namespace PLH {
// asm depends on this specific type
uint64_t m_arguments[];
};
typedef void(*tUserCallback)(const Parameters* params);
typedef void(*tUserCallback)(const Parameters* params, const uint8_t count);

ILCallback() = default;
~ILCallback();
uint64_t getJitFunc(const asmjit::FuncSignature sig, const tUserCallback callback, uint64_t* userTrampVar);
uint64_t getJitFunc(const asmjit::FuncSignature sig, const tUserCallback callback);
uint64_t* getTrampolineHolder();
private:
// does a given type fit in a general purpose register (i.e. is it integer type)
bool isGeneralReg(const uint8_t typeId) const;
Expand All @@ -35,5 +36,8 @@ namespace PLH {
asmjit::VMemMgr m_mem;
uint64_t m_callbackBuf;
asmjit::X86Mem argsStack;

// ptr to trampoline allocated by hook, we hold this so user doesn't need to.
uint64_t m_trampolinePtr;
};
}
2 changes: 1 addition & 1 deletion headers/Detour/x64Detour.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class x64Detour : public Detour {

uint8_t getPrefJmpSize() const;
private:
std::optional<insts_t> makeTrampoline(insts_t& prologue);
bool makeTrampoline(insts_t& prologue, insts_t& trampolineOut);
};
}
#endif //POLYHOOK_2_X64DETOUR_HPP
2 changes: 1 addition & 1 deletion headers/Detour/x86Detour.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class x86Detour : public Detour {

uint8_t getJmpSize() const;
private:
std::optional<insts_t> makeTrampoline(insts_t& prologue);
bool makeTrampoline(insts_t& prologue, insts_t& trampolineOut);
};
}
#endif //POLYHOOK_2_X86DETOUR_HPP
21 changes: 20 additions & 1 deletion sources/ADetour.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,15 @@ bool PLH::Detour::expandProlSelfJmps(insts_t& prol,
return true;
}

void PLH::Detour::buildRelocationList(insts_t& prologue, const uint64_t roundProlSz, const int64_t delta, PLH::insts_t& instsNeedingEntry, PLH::insts_t& instsNeedingReloc) {
bool PLH::Detour::buildRelocationList(insts_t& prologue, const uint64_t roundProlSz, const int64_t delta, PLH::insts_t& instsNeedingEntry, PLH::insts_t& instsNeedingReloc) {
assert(instsNeedingEntry.size() == 0);
assert(instsNeedingReloc.size() == 0);
assert(prologue.size() > 0);

const uint64_t prolStart = prologue.front().getAddress();

for (auto& inst : prologue) {
// types that change control flow
if (inst.isBranching() && inst.hasDisplacement() &&
(inst.getDestination() < prolStart ||
inst.getDestination() > prolStart + roundProlSz)) {
Expand All @@ -102,7 +103,25 @@ void PLH::Detour::buildRelocationList(insts_t& prologue, const uint64_t roundPro
instsNeedingReloc.push_back(inst);
}
}

// data operations (duplicated because clearer)
if (!inst.isBranching() && inst.hasDisplacement()) {
const uint8_t dispSzBits = (uint8_t)inst.getDispSize() * 8;
const uint64_t maxInstDisp = (uint64_t)(std::pow(2, dispSzBits) / 2.0 - 1.0);
if ((uint64_t)std::llabs(delta) > maxInstDisp) {
/*EX: 48 8d 0d 96 79 07 00 lea rcx, [rip + 0x77996]
If instruction is moved beyond displacement field width
we can't fix the load. TODO: generate equivalent load
with asmjit and insert it at position
*/
ErrorLog::singleton().push("Cannot fixup IP relative data operation, relocation beyond displacement size", ErrorLevel::SEV);
return false;
}else {
instsNeedingReloc.push_back(inst);
}
}
}
return true;
}

bool PLH::Detour::unHook() {
Expand Down
22 changes: 13 additions & 9 deletions sources/ILCallback.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "headers/Detour/ILCallback.hpp"

uint64_t PLH::ILCallback::getJitFunc(const asmjit::FuncSignature sig, const PLH::ILCallback::tUserCallback callback, uint64_t* userTrampVar) {
uint64_t PLH::ILCallback::getJitFunc(const asmjit::FuncSignature sig, const PLH::ILCallback::tUserCallback callback) {
asmjit::CodeHolder code;
code.init(asmjit::CodeInfo(asmjit::ArchInfo::kTypeHost));

Expand All @@ -10,7 +10,7 @@ uint64_t PLH::ILCallback::getJitFunc(const asmjit::FuncSignature sig, const PLH:

// to small to really need it
cc.getFunc()->getFrameInfo().disablePreservedFP();

// map argument slots to registers, following abi.
std::vector<asmjit::Reg> argRegisters;
for (uint8_t arg_idx = 0; arg_idx < sig.getArgCount(); arg_idx++) {
Expand Down Expand Up @@ -68,24 +68,25 @@ uint64_t PLH::ILCallback::getJitFunc(const asmjit::FuncSignature sig, const PLH:
asmjit::X86Gp argStruct = cc.newIntPtr("argStruct");
cc.lea(argStruct, argsStack);

// fill reg to pass struct arg count to callback
asmjit::X86Gp argCountParam = cc.newU8();
cc.mov(argCountParam, (uint8_t)sig.getArgCount());

// call to user provided function (use ABI of host compiler)
cc.sub(asmjit::x86::rsp, 32);
auto call = cc.call(asmjit::imm_ptr((unsigned char*)callback), asmjit::FuncSignature1<void, const Parameters*>(asmjit::CallConv::kIdHost));
auto call = cc.call(asmjit::imm_ptr((unsigned char*)callback), asmjit::FuncSignature2<void, Parameters*, uint8_t>(asmjit::CallConv::kIdHost));
call->setArg(0, argStruct);
cc.add(asmjit::x86::rsp, 32);
call->setArg(1, argCountParam);

// deref the trampoline ptr (must live longer)
asmjit::X86Gp orig_ptr = cc.newUInt64();
cc.mov(orig_ptr, (uint64_t)userTrampVar);
cc.mov(orig_ptr, (uint64_t)getTrampolineHolder());
cc.mov(orig_ptr, asmjit::x86::ptr(orig_ptr));

// call trampoline, map input args same order they were passed to us
cc.sub(asmjit::x86::rsp, 32);
auto orig_call = cc.call(orig_ptr, sig);
for (uint8_t arg_idx = 0; arg_idx < sig.getArgCount(); arg_idx++) {
orig_call->setArg(arg_idx, argRegisters.at(arg_idx));
}
cc.add(asmjit::x86::rsp, 32);

// end function
cc.endFunc();
Expand All @@ -106,6 +107,10 @@ uint64_t PLH::ILCallback::getJitFunc(const asmjit::FuncSignature sig, const PLH:
return m_callbackBuf;
}

uint64_t* PLH::ILCallback::getTrampolineHolder() {
return &m_trampolinePtr;
}

bool PLH::ILCallback::isGeneralReg(const uint8_t typeId) const {
switch (typeId) {
case asmjit::TypeId::kI8:
Expand All @@ -124,7 +129,6 @@ bool PLH::ILCallback::isGeneralReg(const uint8_t typeId) const {
}
}


bool PLH::ILCallback::isXmmReg(const uint8_t typeId) const {
switch (typeId) {
case asmjit::TypeId::kF32:
Expand Down
Loading

0 comments on commit 3e30a04

Please sign in to comment.