1. 程式人生 > >inline hook的原理及實現

inline hook的原理及實現

網上沒有找到多少關於inline hook的文章,感覺這方面資料不是很完整,所以決定自己寫一份存檔,重點講述inline hook的實現。

inline hook的核心思想是:通過替換目標函式頭部指令實現在函式執行之前跳轉到其他的指令區域,執行完畢跳轉回到原來的函式,跳轉到的指令區域通常是我們自己編寫的函式。inline hook技術對於編寫外掛和外掛式補丁意義重大。

步驟:

1.使用VirtualAllocEx函式在目標程序建立一塊記憶體區域,用於儲存我們將要執行的指令。

VirtualAllocEx申請指令空間的一般用法如下:VirtualAllocEx(hProcess, NULL,len, MEM_COMMIT, PAGE_EXECUTE);

2.使用WriteProcessMemory函式將指令寫入步驟一申請的記憶體區域。例如:WriteProcessMemory(hProcess,(void*)addr,(void*)val,len,NULL);

3.在目標函式頭部替換指令,儲存原來的指令,並且獲取下一個完整指令的地址。這裡我們必須說明要損壞的指令長度,因為jmp指令佔用5個位元組,假設目標函式頭部第一條指令長度不足5位元組,那麼就需要破壞第二條指令,下一條完整的指令就是第三條指令。反之如果目標函式頭部第一條指令長度大於5位元組,下一條完整的指令就是第二條指令。

4.等待函式執行完畢。

5.回覆目標函式頭部被損壞的指令,使用VirtualFreeEx清理申請的記憶體區域。

具體實現如下:

typedef struct
{
    byte* HOOK_CODE;//指令陣列指標
    DWORD HOOK_CODE_LENGTH;//指令佔的位元組數
    void* HOOK_PATH;//將要hook的目標函式地址
    void* ASM_PATH;//被跳轉的函式地址,即申請的記憶體空間地址
} HOOK_INFO,*PHOOK_INFO;
/*
hook一個程序的某個地址
hProcess:目標程序控制代碼
path:目標函式地址
dstlen:將要損壞的指令總長度,如果要損壞兩條指令,需要兩條指令的總長,用於計算下一條完整指令的地址
code:將要執行的自定指令
codelen:將要執行的自定指令的位元組數
hi:儲存hook的資訊
*/
void inline_hook(HANDLE hProcess,void*path,int dstlen,byte *code,int codelen,PHOOK_INFO hi)
{
    byte *ASM_CODE=(byte*)malloc(codelen+5);//自定函式結束需要跳回原函式,所以結尾需要5位元組長度的jmp,故申請記憶體長度需要+5
    memcpy(ASM_CODE,code,codelen);//將指令複製
    ASM_CODE[codelen]=0xe9;//0xE9是jmp指令
    DWORD*retjmp=(DWORD *)&ASM_CODE[codelen+1];
    PWSTR pCode=(PWSTR)VirtualAllocEx(hProcess, NULL, sizeof(codelen+5), MEM_COMMIT, PAGE_EXECUTE);//在目標程序申請記憶體
    *retjmp=(DWORD)path+dstlen-((DWORD)pCode+codelen+5);
    //上面這句是計算跳轉的長度,path+dstlen即下一條完整指令的地址,減去自定函式結尾jmp的地址即跳轉長度,+5是因為jmp指令本身有長度5
    WriteProcessMemory(hProcess,(void*)pCode,(void*)&ASM_CODE[0],codelen+5,NULL);//把自定函式寫入記憶體
    byte HookCode[5]= {0xE9};//將要替換至目標函式頭部的jmp指令,長度5
    DWORD*jmp=(DWORD*)&HookCode[1];
    *jmp=(DWORD)pCode-(DWORD)path-5;//計算跳轉的長度,自定函式地址減去目標函式地址得到長度,但是jmp本身也有5的長度,故再減5
    
    hi->HOOK_CODE=malloc(dstlen);
    hi->HOOK_CODE_LENGTH=dstlen;
    ReadProcessMemory(hProcess,path,hi->HOOK_CODE,dstlen,NULL);
    WriteProcessMemory(hProcess,path,HookCode,5,NULL);
    //
    hi->HOOK_PATH=path;
    hi->ASM_PATH=pCode;
    Sleep(200);//等待執行,自定函式體積太大需要更長時間,安全的做法是在自定函式中寫入通訊的方式,例如使用一個記憶體資料作為標號,0代表沒結束,1代表結束。
}

/*
解除安裝hook
hProcess:目標程序
hi:從inline_hook得到的hook資訊
*/
void un_inline_hook(HANDLE hProcess,PHOOK_INFO hi)
{
    WriteProcessMemory(hProcess,hi->HOOK_PATH,hi->HOOK_CODE,hi->HOOK_CODE_LENGTH,NULL);
    VirtualFreeEx(hProcess,hi->ASM_PATH,0,MEM_RELEASE);
}
//具體的使用方法
//hook一段特徵碼取得esi暫存器的值
int main()
{
    int r;
    int path=aobscan("8BBE2C030000897B04");//aobscan的實現可以參考我的另一篇文章。
    if(path<=0)
    {
        return 0;
    }
    byte ASM_CODE[]= {0x89,0x35,0x00,0x00,0x00,0x00,//mov [0],esi
                      0x8B,0x86,0x2c,0x03,0x00,0x00//jmp 0
                     };

    //hProcess:目標程序
    PWSTR pData=(PWSTR)VirtualAllocEx(hProcess, NULL, 4, MEM_COMMIT, PAGE_READWRITE);
    *(DWORD*)&ASM_CODE[2]=(DWORD)pData;
    HOOK_INFO hi;
    inline_hook(hProcess,(void*)path,6,ASM_CODE,sizeof(ASM_CODE),&hi);
    ReadProcessMemory(hProcess,(void*)pData,&r,4,NULL);
    un_inline_hook(hProcess,&hi);
    VirtualFreeEx(hProcess,(void*)pData,0,MEM_RELEASE);
    printf("%x",r);
    return 0;
}

上面程式碼除了main函式外,其他的可以照搬使用,轉載請註明出處。