使用汇编和反汇编引擎写一个x86任意地址hook
最簡單的Hook
剛開始學(xué)的時候,用的hook都是最基礎(chǔ)的5字節(jié)hook,也不會使用hook框架,hook流程如下:
- 構(gòu)建一個jmp指令跳轉(zhuǎn)到你的函數(shù)(函數(shù)需定義為裸函數(shù))
- 保存被hook地址的至少5字節(jié)機器碼,然后寫入構(gòu)建的jmp指令
- 接著在你的代碼里做你想要的操作
- 以內(nèi)聯(lián)匯編的形式執(zhí)行被hook地址5字節(jié)機器碼對應(yīng)的匯編指令
- 跳轉(zhuǎn)回被hook的地址下一條指令
這樣操作比較繁瑣,每次hook都要定義一堆東西,還得自己補充hook地址被修改的匯編指令,最重要的是這種hook無法擴展到Python里使用。
加入反匯編和匯編引擎
csdn有一篇文章說了可以通過引入?yún)R編和反匯編引擎來去掉第二步和第四步,也就是不需要關(guān)心hook地址的匯編是什么。
文章中用的匯編引擎是XEDParse,我試了下用vs2017編譯不通過,看了文檔和issue,必須得使用vs2013及以下的版本才能編譯成功,所以就放棄了,改成使用keystone。想編譯keystone和Beaengine可以看另一篇文章keystone和beaengine的編譯
我也對文章中的代碼進行了一些小優(yōu)化,這也是為了方便引入到Python中使用。
開始寫代碼
下面的說明可能會啰嗦一些,對每行代碼都做了解釋。你也可以去看c++ 源碼,也對每行代碼做了注釋。
定義一個hook函數(shù), 參數(shù)有四個,返回值是被修改的字節(jié)數(shù):
- hookAddress: 要hook的地址
- hookFunc: hook的回調(diào)函數(shù)
- hookOldCode:保存被修改的字節(jié)
- hookOldSize:hookOldCode的緩沖區(qū)大小
size_t HookAnyAddress(__in DWORD hookAddress, __in AnyHookFunc hookFunc, __out BYTE* hookOldCode, __in size_t hookOldSize)
AnyHookFunc的函數(shù)指針定義:
typedef void(_stdcall * AnyHookFunc)(RegisterContext*);
RegisterContext結(jié)構(gòu)體的定義
struct RegisterContext
{
DWORD EFLAGS;
DWORD EDI;
DWORD ESI;
DWORD EBP;
DWORD ESP;
DWORD EBX;
DWORD EDX;
DWORD ECX;
DWORD EAX;
};
首先定義一個內(nèi)存的shellcode,用來存放裸函數(shù)里的指令
BYTE ShellCode[0x40] = {
0x60, //pushad
0x9C, //pushfd
0x54, //push esp
0xB8, 0x90, 0x90, 0x90, 0x90, //mov eax,hookFunc
0xFF, 0xD0, //call eax
0x9D, //popfd
0x61, //popad
};
這里的4個0x90是存放hook回調(diào)函數(shù)的地址,接著寫入回調(diào)函數(shù)地址
memcpy(&ShellCode[0x4], &hookFunc, 4);
分配一塊可執(zhí)行的內(nèi)存, 用于存放這段shellcode
DWORD shellcodeMemAddr = (DWORD)VirtualAlloc(NULL, 0x100, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (shellcodeMemAddr == 0) {
return 0;
}
因為shellcode已經(jīng)寫了0xC個字節(jié),所以后面的指令從+0xC開始寫
DWORD shellcodeMemAddrStart = shellcodeMemAddr + 0xC;
定義反匯編引擎和匯編引擎,keystone也是老朋友了,之前x86發(fā)消息的時候就已經(jīng)用過了:
// 定義反匯編引擎
DISASM MyDisasm;
memset(&MyDisasm, 0, sizeof(DISASM));
MyDisasm.EIP = (UIntPtr)hookAddress;
// 設(shè)置為32位x86平臺
MyDisasm.Archi = 32;
MyDisasm.Options = PrefixedNumeral + ShowSegmentRegs;
// PrefixedNumeral: 數(shù)值前加0x,ShowSegmentRegs: 顯示段寄存器的值
// 定義匯編引擎
ks_engine *ks;
ks_err err = ks_open(KS_ARCH_X86, KS_MODE_32, &ks);
if (err != KS_ERR_OK) {
return 0;
}
開始計算hook地址的指令,并將指令寫到shellcodeMemAddr里
// 保存返回hook地址下一條指令的地址
DWORD hookRetAddr = 0;
// 記錄被修改的指令長度
size_t hookSize = 0;
// 開始循環(huán)反匯編,直到滿足5個字節(jié)
while (true) {
// 開始反匯編,每次反匯編一條指令,返回這條指令的長度
int DisasmCodeSize = Disasm(&MyDisasm);
if (DisasmCodeSize < 1) {
return 0;
}
// hook的地址不能包含ret指令
if (MyDisasm.Instruction.BranchType == RetType)
{
return 0;
}
hookSize += DisasmCodeSize;
// 保存匯編指令條數(shù)
size_t encodingCount;
// 保存匯編后的指令
unsigned char *encodingCode;
// 保存匯編后的指令長度
size_t encodingSize;
// 利用keystone將反匯編后的指令再轉(zhuǎn)為機器碼,這么操作可以自動處理相對地址
// 前三個參數(shù)是輸入?yún)?shù),第二個參數(shù)是反匯編會的指令,第三個參數(shù)是指令所在的內(nèi)存地址(用于計算相對偏移)
// 后三個參數(shù)為輸出參數(shù),見定義處
if (ks_asm(ks, MyDisasm.CompleteInstr, shellcodeMemAddrStart, &encodingCode, &encodingSize, &encodingCount) != KS_ERR_OK) {
return 0;
}
// 將匯編后的機器碼寫到shellcode
memcpy(&ShellCode[shellcodeMemAddrStart - shellcodeMemAddr], encodingCode, encodingSize);
ks_free(encodingCode);
// 注意: 反匯編和匯編的機器碼和長度可能是不一樣的
shellcodeMemAddrStart += encodingSize;
// 開始下一條指令的反匯編和匯編
MyDisasm.EIP += DisasmCodeSize;
// 如果指令達到5個字節(jié)就結(jié)束
if (hookSize >= 5)
{
hookRetAddr = MyDisasm.EIP;
break;
}
}
ks_close(ks);
開始構(gòu)建跳轉(zhuǎn)指令,跳轉(zhuǎn)回hook地址的下一條指令的位置
// 保存原始內(nèi)存屬性值
DWORD dwOldProtect = 0;
// 給hook的地址賦予可寫權(quán)限
BOOL bRet = VirtualProtect((LPVOID)hookAddress, 0x20, PAGE_EXECUTE_READWRITE, &dwOldProtect);
if (!bRet) {
return 0;
}
// 保存被覆蓋的機器碼
memcpy(hookOldCode, (LPVOID)hookAddress, hookSize);
// 構(gòu)建跳轉(zhuǎn)指令
BYTE pushRetCode[6] = {
0x68, 0x90, 0x90, 0x90, 0x90, // push hookRetAddr
0xC3 // ret
};
memcpy(&pushRetCode[1], &hookRetAddr, 4);
將構(gòu)架的跳轉(zhuǎn)指令寫入到shellcode里,并將shellcode寫到申請的內(nèi)存shellcodeMemAddr里
memcpy(&ShellCode[shellcodeMemAddrStart - shellcodeMemAddr], pushRetCode, sizeof(pushRetCode));
// 將shellcode寫入申請的內(nèi)存地址
memcpy((LPVOID)shellcodeMemAddr, ShellCode, sizeof(ShellCode));
開始修改hook地址的機器碼,跳轉(zhuǎn)到申請的內(nèi)存地址shellcodeMemAddr
BYTE jmpCode[5] = { 0xE9, 0xFF, 0xFF, 0xFF, 0xFF };
*(DWORD*)(jmpCode + 1) = shellcodeMemAddr - (DWORD)hookAddress - 5;
memcpy((LPVOID)hookAddress, jmpCode, 5);
BYTE nopCode[2] = { 0x90,0x90};
如果被修改的指令超過了五個字節(jié),其他字節(jié)用nop填充
if (hookSize > 5) {
memset((LPVOID)(hookAddress + 5), 0x90, hookSize - 5);
}
最后還原內(nèi)存屬性,返回被修改的指令長度
VirtualProtect((LPVOID)hookAddress, 0x20, dwOldProtect, &dwOldProtect);
return hookSize;
取消hook,只需要將保存的機器碼還原:
DWORD UnHookAnyAddress(__in DWORD hookAddress, __in BYTE* hookOldCode, __in size_t hookOldSize) {
DWORD dwOldProtect = 0;
VirtualProtect((LPVOID)hookAddress, 0x20, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy((LPVOID)hookAddress, hookOldCode, hookOldSize);
VirtualProtect((LPVOID)hookAddress, 0x20, dwOldProtect, &dwOldProtect);
return 0;
}
Python中使用
將這個編譯成dll就能在Python里加載了,不過dll只能用于hook當(dāng)前進程,這是因為函數(shù)不能跨進程調(diào)用,你創(chuàng)建的回調(diào)函數(shù),其他進程無法調(diào)用。
解決這個問題也很簡單,可以在目標進程申請一塊可執(zhí)行的內(nèi)存,用匯編引擎和反匯編引擎將回調(diào)函數(shù)寫到這塊內(nèi)存里。
不過我的使用場景是將Python注入到了進程,Python作為線程在目標進程里運行,不用這么繁瑣。使用案例看另一篇文章封裝32位和64位hook框架實戰(zhàn)hook日志
參考
- https://blog.csdn.net/sunflover454/article/details/49029615
總結(jié)
以上是生活随笔為你收集整理的使用汇编和反汇编引擎写一个x86任意地址hook的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机吃鸡卧倒锁定怎么解除(手机手机报价)
- 下一篇: 研发提效必备技能:手把手教你基于Dock