From 6b418e01ae490d8edd58719a847ad571a1659e06 Mon Sep 17 00:00:00 2001 From: Stephen Eckels Date: Mon, 21 Sep 2020 19:39:25 -0400 Subject: [PATCH] Add detour reHook() --- UnitTests/TestDetourx64.cpp | 13 +++++++++++++ UnitTests/TestDetourx86.cpp | 12 ++++++++++++ asmjit | 2 +- polyhook2/Detour/ADetour.hpp | 15 +++++++++++++++ sources/ADetour.cpp | 11 +++++++++++ sources/x64Detour.cpp | 14 ++++++++------ sources/x86Detour.cpp | 14 ++++++++------ 7 files changed, 68 insertions(+), 13 deletions(-) diff --git a/UnitTests/TestDetourx64.cpp b/UnitTests/TestDetourx64.cpp index 7ff4f89..504091a 100644 --- a/UnitTests/TestDetourx64.cpp +++ b/UnitTests/TestDetourx64.cpp @@ -111,6 +111,19 @@ TEMPLATE_TEST_CASE("Testing 64 detours", "[x64Detour],[ADetour]", PLH::CapstoneD REQUIRE(detour.unHook() == true); } + SECTION("Normal function rehook") + { + PLH::StackCanary canary; + PLH::x64Detour detour((char*)&hookMe1, (char*)h_hookMe1, &hookMe1Tramp, dis); + REQUIRE(detour.hook() == true); + + effects.PushEffect(); + REQUIRE(detour.reHook() == true); // can only really test this doesn't cause memory corruption easily + hookMe1(); + REQUIRE(effects.PopEffect().didExecute()); + REQUIRE(detour.unHook() == true); + } + // In release mode win apis usually go through two levels of jmps /* 0xe9 ... jmp iat_thunk diff --git a/UnitTests/TestDetourx86.cpp b/UnitTests/TestDetourx86.cpp index 9f99271..aba7cd8 100644 --- a/UnitTests/TestDetourx86.cpp +++ b/UnitTests/TestDetourx86.cpp @@ -141,6 +141,18 @@ TEMPLATE_TEST_CASE("Testing x86 detours", "[x86Detour],[ADetour]", PLH::Capstone REQUIRE(detour.unHook() == true); } + SECTION("Normal function rehook") { + PLH::x86Detour detour((char*)&hookMe1, (char*)h_hookMe1, &hookMe1Tramp, dis); + REQUIRE(detour.hook() == true); + + effects.PushEffect(); + REQUIRE(detour.reHook() == true); // can only really test this doesn't cause memory corruption easily + volatile auto result = hookMe1(); + PH_UNUSED(result); + REQUIRE(effects.PopEffect().didExecute()); + REQUIRE(detour.unHook() == true); + } + SECTION("Jmp into prologue w/ src in range") { PLH::x86Detour detour((char*)&hookMe2, (char*)&h_nullstub, &nullTramp, dis); diff --git a/asmjit b/asmjit index 8474400..d7d3db1 160000 --- a/asmjit +++ b/asmjit @@ -1 +1 @@ -Subproject commit 8474400e82c3ea65bd828761539e5d9b25f6bd83 +Subproject commit d7d3db1c4fb49b789dea482ebfbd4032f9ad69e7 diff --git a/polyhook2/Detour/ADetour.hpp b/polyhook2/Detour/ADetour.hpp index f1aad62..50a4685 100644 --- a/polyhook2/Detour/ADetour.hpp +++ b/polyhook2/Detour/ADetour.hpp @@ -75,6 +75,13 @@ class Detour : public PLH::IHook { virtual bool unHook() override; + /** + This is for restoring hook bytes if a 3rd party uninstalled them. + DO NOT call this after unHook(). This may only be called after hook() + but before unHook() + **/ + virtual bool reHook(); + virtual HookType getType() const override { return HookType::Detour; } @@ -90,6 +97,14 @@ class Detour : public PLH::IHook { PLH::insts_t m_originalInsts; + /*Save the instructions used for the hook so that we can re-write in rehook() + Note: There's a nop range we store too so that it doesn't need to be re-calculated + */ + PLH::insts_t m_hookInsts; + uint16_t m_nopProlOffset; + uint16_t m_nopSize; + uint32_t m_hookSize; + /**Walks the given vector of instructions and sets roundedSz to the lowest size possible that doesn't split any instructions and is greater than minSz. If end of function is encountered before this condition an empty optional is returned. Returns instructions in the range start to adjusted end**/ std::optional calcNearestSz(const insts_t& functionInsts, const uint64_t minSz, diff --git a/sources/ADetour.cpp b/sources/ADetour.cpp index 8ae66be..a06a379 100644 --- a/sources/ADetour.cpp +++ b/sources/ADetour.cpp @@ -192,3 +192,14 @@ bool PLH::Detour::unHook() { m_hooked = false; return true; } + +bool PLH::Detour::reHook() +{ + MemoryProtector prot(m_fnAddress, m_hookSize, ProtFlag::R | ProtFlag::W | ProtFlag::X, *this); + m_disasm.writeEncoding(m_hookInsts, *this); + + // Nop the space between jmp and end of prologue + assert(m_hookSize >= m_nopProlOffset); + writeNop(m_fnAddress + m_nopProlOffset, m_nopSize); + return true; +} \ No newline at end of file diff --git a/sources/x64Detour.cpp b/sources/x64Detour.cpp index df4f745..107c06d 100644 --- a/sources/x64Detour.cpp +++ b/sources/x64Detour.cpp @@ -238,8 +238,10 @@ bool PLH::x64Detour::hook() { } *m_userTrampVar = m_trampoline; + m_hookSize = (uint32_t)roundProlSz; + m_nopProlOffset = (uint16_t)minProlSz; - MemoryProtector prot(m_fnAddress, roundProlSz, ProtFlag::R | ProtFlag::W | ProtFlag::X, *this); + MemoryProtector prot(m_fnAddress, m_hookSize, ProtFlag::R | ProtFlag::W | ProtFlag::X, *this); // we're really space constrained, try to do some stupid hacks like checking for 0xCC's near us auto cave = findNearestCodeCave<8>(m_fnAddress); if (!cave) { @@ -248,13 +250,13 @@ bool PLH::x64Detour::hook() { } MemoryProtector holderProt(*cave, 8, ProtFlag::R | ProtFlag::W | ProtFlag::X, *this, false); - const auto prolJmp = makex64MinimumJump(m_fnAddress, m_fnCallback, *cave); - m_disasm.writeEncoding(prolJmp, *this); + m_hookInsts = makex64MinimumJump(m_fnAddress, m_fnCallback, *cave); + m_disasm.writeEncoding(m_hookInsts, *this); // Nop the space between jmp and end of prologue - assert(roundProlSz >= minProlSz); - const uint8_t nopSz = (uint8_t)(roundProlSz - minProlSz); - writeNop(m_fnAddress + minProlSz, nopSz); + assert(m_hookSize >= m_nopProlOffset); + m_nopSize = (uint16_t)(m_hookSize - m_nopProlOffset); + writeNop(m_fnAddress + m_nopProlOffset, m_nopSize); m_hooked = true; return true; diff --git a/sources/x86Detour.cpp b/sources/x86Detour.cpp index 741affd..5a68902 100644 --- a/sources/x86Detour.cpp +++ b/sources/x86Detour.cpp @@ -88,15 +88,17 @@ bool PLH::x86Detour::hook() { } *m_userTrampVar = m_trampoline; + m_hookSize = (uint32_t)roundProlSz; + m_nopProlOffset = (uint16_t)minProlSz; - MemoryProtector prot(m_fnAddress, roundProlSz, ProtFlag::R | ProtFlag::W | ProtFlag::X, *this); - const auto prolJmp = makex86Jmp(m_fnAddress, m_fnCallback); - m_disasm.writeEncoding(prolJmp, *this); + MemoryProtector prot(m_fnAddress, m_hookSize, ProtFlag::R | ProtFlag::W | ProtFlag::X, *this); + m_hookInsts = makex86Jmp(m_fnAddress, m_fnCallback); + m_disasm.writeEncoding(m_hookInsts, *this); // Nop the space between jmp and end of prologue - assert(roundProlSz >= minProlSz); - const uint8_t nopSz = (uint8_t)(roundProlSz - minProlSz); - writeNop(m_fnAddress + minProlSz, nopSz); + assert(m_hookSize >= m_nopProlOffset); + m_nopSize = (uint16_t)(m_hookSize - m_nopProlOffset); + writeNop(m_fnAddress + m_nopProlOffset, m_nopSize); m_hooked = true; return true;