日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

使用汇编和反汇编引擎写一个x86任意地址hook

發(fā)布時間:2023/12/24 windows 32 coder
生活随笔 收集整理的這篇文章主要介紹了 使用汇编和反汇编引擎写一个x86任意地址hook 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

最簡單的Hook

剛開始學(xué)的時候,用的hook都是最基礎(chǔ)的5字節(jié)hook,也不會使用hook框架,hook流程如下:

  1. 構(gòu)建一個jmp指令跳轉(zhuǎn)到你的函數(shù)(函數(shù)需定義為裸函數(shù))
  2. 保存被hook地址的至少5字節(jié)機器碼,然后寫入構(gòu)建的jmp指令
  3. 接著在你的代碼里做你想要的操作
  4. 以內(nèi)聯(lián)匯編的形式執(zhí)行被hook地址5字節(jié)機器碼對應(yīng)的匯編指令
  5. 跳轉(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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。