code patch hook

code patch hook

今天在逆向分析一个程序的时候接触到了code patch hook,其实这个hook技术我在接触逆向之初就已经知道了,但是今天遇到的有点特殊

code patch hook

原理是通过修改api的前5个字节,jmp到自己的函数

当用户调用api时,会跳转到自己的函数

  1. 脱钩
  2. 调用原始api
  3. 脱钩

为正常调用原API,需要先1. 脱钩(若不脱钩2. 调用原始Api就会陷入无限循环)。然后再钩取函数,在退出函数之前再次3.挂钩,使之进入钩取状态。也就是在每次调用原api时都必须反复进行脱钩/挂钩操作,不仅会造成整体性能低下,更严重的是在多线程环境运行下产生错误,这是由脱钩/挂钩操作要对原API的前5个字节进行修改(覆写)引起的。

一种特殊的code patch hook

如何避免呢,其实只要避免反复进行脱钩/挂钩操作就行了

  1. 获取原函数前面几个指令的总长度,一直到字节数>5为止
  2. 创建总长度+5的内存pMem
  3. 将原函数前面总长度的指令复制到内存中pMem的前总长度字节中
  4. 计算pMem+总长度+ 5 到原函数 + 总长度的偏移,并将 E9 XXXXXXXX(偏移)复制到pMem+总长度的位置
  5. 计算原函数 + 5 到自己的函数的偏移,并将E9 XXXXXXXX(偏移)复制到原函数的前面5个字节
  6. pMem返回并保存起来,这个就是调用原函数正常功能的函数地址
    这样用户调用API时,会转到我们的函数,而我们调用原函数时只需调用pMem即可,避免了反复进行脱钩/挂钩操作。

具体实现

下面是我的实现代码,其实还不完善,第一点我并没有计算原函数前面几个字节的长度,而是默认为5,因为Windows API的前面几个指令的字节长度刚好是5,所以下面的代码只能用于x86 Windows api

#include <Windows.h>
#include <iostream>

using namespace std;

typedef DWORD(__stdcall *MyGetFileAttributes)(LPCWSTR lpFileName);
MyGetFileAttributes originMyGetFileAttributes;
DWORD __stdcall __GetFileAttributes(LPCWSTR lpFileName) {
    cout << "__GetFileAttributes" << endl;
    return originMyGetFileAttributes(lpFileName);;
}


int main() {

    originMyGetFileAttributes =  hookCodePatch<MyGetFileAttributes>(::GetFileAttributes, __GetFileAttributes);

    ::GetFileAttributes(LR"(C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\Notepad.lnk)");

}



template<typename T>
 T hookCodePatch(T originFun, T hookFuntion)
{
    const int patchSize = 10; // 假设需要 patch 的字节数
    const int pageSize = 5;
    unsigned char* patchedMemory = new unsigned char[patchSize];
    unsigned char* originFunPtr = reinterpret_cast<unsigned char*>(originFun);
    
   // 如有必要, 将原函数转到实际的函数地址
    DWORD oldProtect;
    VirtualProtect(patchedMemory, patchSize, PAGE_EXECUTE_READWRITE, &oldProtect);

    if (originFunPtr[0] == 0xFF &&originFunPtr[1] == 0x25) {
        originFunPtr = reinterpret_cast<unsigned char*>(**(int **)(originFunPtr + 2));
    }
    if (originFunPtr[0] == 0xEB ) {
        originFunPtr = originFunPtr + originFunPtr[1] + 2;
        if (originFunPtr[0] == 0xFF && originFunPtr[1] == 0x25) {
            originFunPtr = reinterpret_cast<unsigned char*>(* (int*)(originFunPtr + 2));
        }
        if (originFunPtr[0] == 0xE9) {
            originFunPtr = originFunPtr +*(int*)(originFunPtr + 1);
        }
    }
    
    // 复制 originFun 的前五个字节到 patchedMemory
    memcpy(patchedMemory, originFunPtr, 5);

    intptr_t hookOffset = (reinterpret_cast<intptr_t>(originFunPtr) + 5) - reinterpret_cast<intptr_t>(patchedMemory + 0xA);

    // 修改 patchedMemory 的后五个字节为跳转到 originFun + 5 的指令
    patchedMemory[5] = 0xE9; // x86 跳转指令的操作码
    memcpy(patchedMemory + 6, &hookOffset, sizeof(hookOffset));
    // 计算跳转偏移量
    hookOffset = reinterpret_cast<intptr_t>(hookFuntion) - (reinterpret_cast<intptr_t>(originFunPtr) + 5);
    
    
    // 修改 originFun 的前五个字节为跳转到 hookFunction 的指令
   
    VirtualProtect(originFunPtr, pageSize, PAGE_EXECUTE_READWRITE, &oldProtect);
    originFunPtr[0] = 0xE9; // x86 跳转指令的操作码
    memcpy(originFunPtr + 1, &hookOffset, sizeof(hookOffset));
    VirtualProtect(originFunPtr, pageSize, oldProtect, &oldProtect);
    return (T)patchedMemory;
}
posted @ 2023-06-18 23:49  乘舟凉  阅读(40)  评论(0编辑  收藏  举报