From b83acd469bef429c922cc299ccd27d72b7e1189c Mon Sep 17 00:00:00 2001 From: Menooker Date: Fri, 14 Jun 2024 10:12:48 +0800 Subject: [PATCH] Add CI and checking of the tests --- .clang-format | 5 + .github/workflows/ccpp.yml | 21 +++ .gitignore | 1 + CMakeLists.txt | 4 +- hooktest.cpp | 350 +++++++++++++++++++++++++++---------- inline_hook.cpp | 7 +- 6 files changed, 290 insertions(+), 98 deletions(-) create mode 100644 .clang-format create mode 100644 .github/workflows/ccpp.yml diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..9b200d0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,5 @@ + +BasedOnStyle: LLVM +IndentWidth: 4 +AlwaysBreakTemplateDeclarations : true +# BreakBeforeBraces: Allman \ No newline at end of file diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml new file mode 100644 index 0000000..6b55446 --- /dev/null +++ b/.github/workflows/ccpp.yml @@ -0,0 +1,21 @@ +name: C/C++ CI + +on: + push: + branches: + - master + pull_request: + +jobs: + test-linux: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: make + working-directory: ./ + run: mkdir build && cd build && cmake .. && cmake --build . --target hook-test --config Release -- -j + - name: test + working-directory: ./ + run: ./build/hook-test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 93f02cf..70a6def 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ include/* hooktest 3rdparty/* build/* +.vscode/* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ac634bb..267e286 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,5 +14,5 @@ include_directories( add_library(${LIB_NAME} inline_hook.cpp) target_link_libraries(${LIB_NAME} Zydis) add_dependencies(${LIB_NAME} Zydis) -add_executable(hooker-test hooktest.cpp) -target_link_libraries(hooker-test ${LIB_NAME} Zydis) +add_executable(hook-test hooktest.cpp) +target_link_libraries(hook-test ${LIB_NAME} Zydis) diff --git a/hooktest.cpp b/hooktest.cpp index bfed2d3..92f4681 100644 --- a/hooktest.cpp +++ b/hooktest.cpp @@ -1,7 +1,12 @@ #include "PFishHook.h" #include -#include +#include +#include +#include #include +#include +#include +#include int main(); @@ -13,16 +18,15 @@ gofurther2:\n\ call main\n\ "); extern "C" void testfunc(); +extern "C" int gofurther2; -void(*poldfunc)(); -void test_replace() -{ - return poldfunc(); -} +void (*poldfunc)(); +void test_replace() { return poldfunc(); } asm(R"(testfunc2: jne gofurther3 cmpl $0x0,0x10(%rip) +testfunc2_jmp_back: ja gofurther4 ja gofurther5 gofurther3: @@ -33,111 +37,269 @@ ja gofurther5 call main )"); extern "C" void testfunc2(); +extern "C" int testfunc2_jmp_back; +extern "C" int gofurther3; +extern "C" int gofurther4; +extern "C" int gofurther5; extern "C" void testfunc_lea(); asm(R"(testfunc_lea: lea 0x123450(%rip),%ecx +testfunc_lea_jmp_back: lea 0x123450(%rip),%rcx ret )"); +extern "C" int testfunc_lea_jmp_back; -extern "C" void testfunc_call(); -asm(R"(testfunc_call: -jmp main -call main -ret -)"); +volatile int check_mask = 0; -void(*poldfunc2)(); -void(*poldfunc3)(); -void(*poldfunc4)(); -void(*poldfunc5)(); -void(*poldfunc6)(); -void(*poldfunc7)(); -void test_dummy(){ - printf("DUMMY: orig func\n"); +void (*poldfunc5)(); +void (*poldfunc6)(); +void (*poldfunc7)(); +void test_dummy() { + printf("DUMMY: orig func\n"); + check_mask |= 1; } -void test_replace_d() -{ -printf("shadow 1\n"); - return poldfunc5(); +void test_replace_d() { + printf("shadow 1\n"); + check_mask |= (1 << 1); + return poldfunc5(); } -void test_replace_d2() -{ - printf("shadow 2\n"); - return poldfunc6(); +void test_replace_d2() { + printf("shadow 2\n"); + check_mask |= (1 << 2); + return poldfunc6(); } -void test_replace_d3() -{ - printf("shadow 3\n"); - return poldfunc7(); +void test_replace_d3() { + printf("shadow 3\n"); + check_mask |= (1 << 3); + return poldfunc7(); } -void test_replace2() -{ - return poldfunc2(); +void test_replace2() { return; } + +struct ExpectedCode { + enum Kind { CODE, DATA32, DATA64 } kind; + union { + const char *code; + int32_t data32; + int64_t data64; + }; + size_t numInstructions; + ExpectedCode(const char *code, size_t numInstructions) + : kind{CODE}, code{code}, numInstructions{numInstructions} {} + ExpectedCode(int32_t data) : kind{DATA32}, data32{data} {} + ExpectedCode(int64_t data) : kind{DATA64}, data64{data} {} +}; + +static std::string disas(ZydisFormatter &formatter, ZydisDecoder &decoder, + uint8_t *f, int sz, bool print, + uint8_t **out = nullptr) { + std::stringstream ss; + uint8_t *readPointer = (uint8_t *)f; + ZydisDecodedInstruction instruction; + while (ZYDIS_SUCCESS(ZydisDecoderDecodeBuffer( + &decoder, readPointer, 128, (uint64_t)readPointer, &instruction))) { + char buffer[256]; + ZydisFormatterFormatInstruction(&formatter, &instruction, buffer, + sizeof(buffer)); + if (print) { + printf("%p: %s\n", readPointer, buffer); + } + ss << buffer << '\n'; + readPointer += instruction.length; + sz--; + if (sz <= 0) + break; + } + if (out) + *out = readPointer; + return ss.str(); } +static void checkASM(ZydisFormatter &formatter, ZydisDecoder &decoder, + void *target, + const std::vector &expectedAfterHook) { + uint8_t *resolvePosition = (uint8_t *)target; + for (auto &expected : expectedAfterHook) { + if (expected.kind == ExpectedCode::CODE) { + auto hooked = + disas(formatter, decoder, resolvePosition, + expected.numInstructions, true, &resolvePosition); + if (hooked != expected.code) { + printf("\nUnexpected ASM result. Expecting\n%s\n", + expected.code); + exit(2); + } + } else if (expected.kind == ExpectedCode::DATA64) { + printf("%p: [data64]\n", resolvePosition); + int64_t val = *(int64_t *)resolvePosition; + if (expected.data64 != val) { + printf("\nUnexpected data64 result. Expecting %ld, met %ld\n", + expected.data64, val); + exit(2); + } + resolvePosition += 8; + } else { + printf("%p: [data32]\n", resolvePosition); + int32_t val = *(int32_t *)resolvePosition; + if (expected.data32 != val) { + printf("\nUnexpected data32 result. Expecting %d, met %d\n", + expected.data32, val); + exit(2); + } + resolvePosition += 4; + } + } +} +typedef void (*functype)(); +static void runhook(ZydisFormatter &formatter, ZydisDecoder &decoder, + const char *name, functype target, functype *old, + functype newfunc, size_t origsize) { + printf("==============================\n%s\n===========================" + "===\nBefore Hook:\n", + name); + disas(formatter, decoder, (uint8_t *)target, origsize, true); + fputs("\n================\n", stdout); + auto ret = HookIt((void *)target, (void **)old, (void *)newfunc); + printf("Hook status=%d\n", ret); + if (ret != FHSuccess) { + printf("Failed to hook\n"); + exit(2); + } +} +static void test_ja_call(ZydisFormatter &formatter, ZydisDecoder &decoder) { + runhook(formatter, decoder, "testfunc", testfunc, &poldfunc, test_replace, + 3); + char code[256]; + snprintf(code, 255, + R"(jmp 0x%016lX +int3 +int3 +call 0x%016lX +)", + (uintptr_t)&test_replace, (uintptr_t)&main); + printf("After Hook:\n"); + checkASM(formatter, decoder, (void *)testfunc, {{code, 4}}); + + printf("\nShadow Func:\n"); + snprintf(code, 255, + R"(jnbe 0x%016lX +call 0x%016lX +jmp [0x%016lX] +)", + 0x15 + (uintptr_t)poldfunc, (uintptr_t)&main, + 0xd + (uintptr_t)poldfunc); + char code2[256]; + snprintf(code2, 255, + R"(jmp [0x%016lX] +)", + 0x1B + (uintptr_t)poldfunc); + checkASM(formatter, decoder, (void *)poldfunc, + {{code, 3}, + {int64_t((intptr_t)&gofurther2)}, + {code2, 1}, + {int64_t((intptr_t)&gofurther2)}}); +} + +static void test_jne_cmp(ZydisFormatter &formatter, ZydisDecoder &decoder) { + void (*poldfunc2)(); + runhook(formatter, decoder, "testfunc2", testfunc2, &poldfunc2, + test_replace2, 4); + char code[256]; + snprintf(code, 255, + R"(jmp 0x%016lX +int3 +int3 +int3 +int3 +jnbe 0x%016lX +jnbe 0x%016lX +)", + (uintptr_t)&test_replace2, (uintptr_t)&gofurther4, + (uintptr_t)&gofurther5); + printf("After Hook:\n"); + checkASM(formatter, decoder, (void *)testfunc2, {{code, 7}}); + + printf("\nShadow Func:\n"); + snprintf(code, 255, + R"(jnz 0x%016lX +cmp dword ptr [0x%016lX], 0x00 +jmp [0x%016lX] +)", + 0x17 + (uintptr_t)poldfunc2, (uintptr_t)&testfunc2 + 0x19, + 0xf + (uintptr_t)poldfunc2); + char code2[256]; + snprintf(code2, 255, + R"(jmp [0x%016lX] +)", + 0x1D + (uintptr_t)poldfunc2); + checkASM(formatter, decoder, (void *)poldfunc2, + {{code, 3}, + {int64_t((intptr_t)&testfunc2_jmp_back)}, + {code2, 1}, + {int64_t((intptr_t)&gofurther3)}}); +} + +static void test_lea(ZydisFormatter &formatter, ZydisDecoder &decoder) { + void (*poldfunc2)(); + runhook(formatter, decoder, "testfunc_lea", testfunc_lea, &poldfunc2, + test_replace2, 3); + char code[256]; + snprintf(code, 255, + R"(jmp 0x%016lX +int3 +lea rcx, [0x%016lX] +ret +)", + (uintptr_t)&test_replace2, (uintptr_t)&testfunc_lea + 0x12345d); + printf("After Hook:\n"); + checkASM(formatter, decoder, (void *)testfunc_lea, {{code, 4}}); + + printf("\nShadow Func:\n"); + snprintf(code, 255, + R"(lea ecx, [0x%016lX] +jmp [0x%016lX] +)", + (uintptr_t)&testfunc_lea + 0x123456, (uintptr_t)poldfunc2 + 0xc); + checkASM(formatter, decoder, (void *)poldfunc2, + {{code, 2}, {int64_t((intptr_t)&testfunc_lea_jmp_back)}}); +} + + +void test_multi_hook(ZydisFormatter &formatter, ZydisDecoder &decoder) { + // test for dup hooks + printf("running multi-hook checks\n"); + runhook(formatter, decoder, "testfunc_dup", test_dummy, &poldfunc5, + test_replace_d, 5); + runhook(formatter, decoder, "testfunc_dup2", test_dummy, &poldfunc6, + test_replace_d2, 5); + runhook(formatter, decoder, "testfunc_dup3", test_dummy, &poldfunc7, + test_replace_d3, 5); + test_dummy(); + if (check_mask != (1 << 4) - 1) { + printf("\nUnexpected checkmask. Expecting %d, got %d\n", (1 << 5) - 1, + check_mask); + exit(2); + } +} +int main() { + ZydisFormatter formatter; + ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL); + // ZydisFormatterSetProperty(&formatter, ZYDIS_FORMATTER_PROP_ADDR_FORMAT, + // ZYDIS_ADDR_FORMAT_RELATIVE_UNSIGNED); + ZydisDecoder decoder; + ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, + ZYDIS_ADDRESS_WIDTH_64); -int main() -{ - ZydisFormatter formatter; - ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL); - //ZydisFormatterSetProperty(&formatter, ZYDIS_FORMATTER_PROP_ADDR_FORMAT, ZYDIS_ADDR_FORMAT_RELATIVE_SIGNED); - ZydisDecoder decoder; - ZydisDecoderInit( - &decoder, - ZYDIS_MACHINE_MODE_LONG_64, - ZYDIS_ADDRESS_WIDTH_64); - - typedef void(*functype)(); - auto disas = [&](functype f, int sz) - { - uint8_t* readPointer = (uint8_t*)f; - ZydisDecodedInstruction instruction; - while (ZYDIS_SUCCESS(ZydisDecoderDecodeBuffer( - &decoder, readPointer, 128, (uint64_t)readPointer, &instruction))) - { - char buffer[256]; - ZydisFormatterFormatInstruction( - &formatter, &instruction, buffer, sizeof(buffer)); - printf("0x%p: %s\n", readPointer, buffer); - sz--; - if (sz <= 0) - break; - readPointer += instruction.length; - } - printf("==============================\n"); - }; - - - auto runtest = [&](const char* name,functype target, functype* old, functype newfunc) - { - printf("==============================\n%s\n==============================\nBefore Hook:\n", name); - disas(target, 5); - auto ret = HookIt((void*)target, (void**)old, (void*)newfunc); - printf("Hook status=%d\n", ret); - if (ret == FHSuccess) - { - printf("After Hook:\n"); - disas(target, 7); - printf("\nShadow Func:\n"); - disas(*old, 10); - } - }; - - printf("main=%p\ntest_replace=%p\ntest_replace2=%p\n", main, test_replace, test_replace2); - - runtest("testfunc", testfunc, &poldfunc, test_replace); - runtest("testfunc2", testfunc2, &poldfunc2, test_replace2); - runtest("testfunc_lea", testfunc_lea, &poldfunc3, test_replace2); - runtest("testfunc_call", testfunc_call, &poldfunc4, test_replace2); - //test for dup hooks - runtest("testfunc_dup", test_dummy, &poldfunc5, test_replace_d); - runtest("testfunc_dup2", test_dummy, &poldfunc6, test_replace_d2); - runtest("testfunc_dup3", test_dummy, &poldfunc7, test_replace_d3); - test_dummy(); - return 0; + printf("main=%p\ntest_replace=%p\ntest_replace2=%p\n", main, test_replace, + test_replace2); + test_ja_call(formatter, decoder); + test_jne_cmp(formatter, decoder); + test_lea(formatter, decoder); + test_multi_hook(formatter, decoder); + printf("All tests are done\n"); + return 0; } diff --git a/inline_hook.cpp b/inline_hook.cpp index dedce95..94f7171 100644 --- a/inline_hook.cpp +++ b/inline_hook.cpp @@ -165,13 +165,16 @@ static MemChunk* TryCreateChunk(void* address){ } if(((uintptr_t)addr >> 32) == 0 && addr!=0) flags|= MAP_32BIT; const uintptr_t search_step=(MCHUNK_SZ<<1); - for(;addr>search_step;addr-=search_step){ - MemChunk* res=(MemChunk*)mmap(addr, MCHUNK_SZ, PROT_READ | PROT_WRITE | PROT_EXEC,flags,-1,0); + for(auto tryaddr = addr;;tryaddr-=search_step){ + MemChunk* res=(MemChunk*)mmap(tryaddr, MCHUNK_SZ, PROT_READ | PROT_WRITE | PROT_EXEC,flags,-1,0); if(ADDR_OK(res,reinterpret_cast(addr))){ res->init(); return res; } munmap(res,MCHUNK_SZ); + if(tryaddr