首页
论坛
课程
招聘
雪    币: 3806
活跃值: 活跃值 (58)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝

[原创]Hook技术:Ring3层下的Inline Hook详解【附源码】

2020-5-2 22:53 4608

[原创]Hook技术:Ring3层下的Inline Hook详解【附源码】

2020-5-2 22:53
4608

Ring3层下的Inline Hook是最常用的Hook手段之一,是一种通过修改机器码的方式来实现hook的技术。


探究API调用正常执行流程

我们知道正常函数的执行流程是call 0xxxxxxxx,然后跳转到调用的函数去执行。

0x1000地址的call指令执行后跳转到0x3000地址处执行,执行完毕后再返回执行call指令的下一条指令。

我们以MessageBox为例

正常调用如下:

函数执行流程: 

首先是call MessageBox

可以看到MessageBox的地址为77101370

跳转到Message的程序开头。

我们在hook的时候,可能会读取或者修改call指令执行之前所压入栈的内容。

那么,我们可以将call指令替换成jmp指令,jmp到我们自己编写的函数,在函数里call原来的函数,函数结束后再jmp回到原先call指令的下一条指令。如图:

探究Inline Hook中的函数调用过程

实现思路比较简单,主要思想:

1、构造跳转指令。

2、在内存中找到欲HOOK函数地址,并保存欲HOOK位置处的前5个字节。(但其实并不一定就是5个字节,像是MessageBox就是6个字节)

3、将构造的跳转指令写入需HOOK的位置处。

4、当被HOOK位置被执行时会转到我们的流程执行。

5、如果要执行原来的流程,那么恢复HOOK,也就是还原被修改的字节。

6、执行函数原来的流程。



在这过程中,主要涉及到几个问题:

1、如何定位自己要hook的地址?

2、如何处理函数开头要替换的字节?

3、如何跳转以及跳转地址如何计算?

4、自己的函数执行完成后,如何跳转回来?


那么带着这几个问题,我们开始尝试实现Inline hook。

我们还是以MessageBox为例子,来探究一下上面的问题。

第一步:构造跳转指令


这里我们使用近距离地址跳转的jmp指令,它的机器码为E9,这种类型的jmp指令需要一个地址参数,为当前jmp指令地址距离目标函数地址的字节数。

一、正常的跳转计算公式如下所示”:

计算公式:jmp跳转的地址=自己实现的函数地址-(jmp所在的地址+5) (这里的5是 jmp xxxxxxxx的五个字节。)

也就是:

*((ULONG*)(__HookCode + 1)) = (ULONG)FakeFuncAddress - ((ULONG)OriginalFuncAddress + 5);

、mov eax xxxxxxxx;

jmp eax


在这里我们使用了mov eax,地址;jmp eax的方式。一共是七个字节。

// 构造新头部代码   
__NewCode[0] = 0xB8;            
memcpy(&__NewCode[1], &JmpAddress, 4);    // mov eax, _JmpAddr    
__NewCode[5] = 0xFF;            //    
__NewCode [6] = 0xE0;           // jmp eax

第二步:找到API函数地址,并保存前7字节

ReadProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __OldCode, 7, NULL);

第三步:将构造的跳转指令写入需HOOK的位置处

首先要修改内存属性为可读可写,然后再进行跳转指令的写入。


DWORD dwOldProtect = 0; //旧保护属性
	// 去内存保护
	::VirtualProtect(__MessageBoxAddress, 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);	//写入跳转,开始Hook
	WriteProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __NewCode, 7, NULL);	// 写内存保护
	::VirtualProtect(__MessageBoxAddress, 7, dwOldProtect, &dwOldProtect);

4、当被HOOK位置被执行时会转到我们的流程执行

进入MessageBoxA之后,会发现前7个字节已经改变为我们的跳板指令。

然后就会转入我们实现的函数,进行执行。

5、如果要执行原来的流程,那么恢复HOOK,也就是还原被修改的字节

我们对原函数前七字节进行修改。

6、执行函数原来的流程

之后再对MessageBoxA()调用,就恢复正常了。

综上我们Inline Hook就已经实现了,现在我们来解决之前的问题:

1、如何定位自己要hook的地址?

调用API所在Dll,GetProAddress。

2、如何处理函数开头要替换的字节?

一般来说,是对前五个字节保存替换,但要视具体情况而定。

例如这里我们使用的就不是jmp xxxxxxxx,

而是mov eax xxxxxxxx;

       jmp eax

这时候就需要保存替换前7个字节了。

3、如何跳转以及跳转地址如何计算?

具体参考上面实现

根据《加密与解密》中,一共有五种跳转方式。

4、自己的函数执行完成后,如何跳转回来?

看上面的实现流程。

源码:

源码已上传我的资源,无需积分,兄弟们需要自取。

#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <tchar.h>

//修改API入口为 mov eax, 00400000;jmp eax是程序能跳转到自己的函数
BYTE __NewCode[7] = { 0xE9, 0x0, 0x0, 0x0, 0x0, 0x0 };
BYTE __OldCode[7] = { 0 };

FARPROC __MessageBoxAddress;

int WINAPI MyMessageBoxA(
	HWND hWnd,          // handle to owner window
	LPCTSTR lpText,     // text in message box
	LPCTSTR lpCaption,  // message box title
	UINT uType          // message box style
);

void InlineHook();




void main()
{
	
	InlineHook();

	//调用MessageBoxA测试一下。
	MessageBoxA(NULL, "Hello World", "Title", MB_OK);
}

void InlineHook()
{
	HMODULE hModule_User32 = LoadLibrary("user32.dll");
	__MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxA");

	//打印MessageBoxA的地址
	printf("__MessageBoxAddress is %x\n", __MessageBoxAddress);
	//MyMessageBoxA的地址
	printf("MyMessageBoxA Addr is %x\n", MyMessageBoxA);

	//读MessageBoxA函数的前6个字节
	if (ReadProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __OldCode, 7, NULL) == 0)
	{
		printf("ReadProcessMemory error\n");
		return;
	}
	//MessAgeBoxA()API 原6字节
	printf("__OldCode is %x%x%x%x%x%x%x\n", __OldCode[0], __OldCode[1], __OldCode[2],
		__OldCode[3], __OldCode[4], __OldCode[5], __OldCode[6]);

	DWORD JmpAddress = (DWORD)MyMessageBoxA;
	// 计算自定义函数的地址.
	// 构造新头部代码
	__NewCode[0] = 0xB8;            //
	memcpy(&__NewCode[1], &JmpAddress, 4);    // mov eax, _JmpAddr
	__NewCode[5] = 0xFF;            //
	__NewCode [6] = 0xE0;            // jmp eax
	DWORD dwOldProtect = 0;
	printf("NewBytes is %x%x%x%x%x\n", __NewCode[0], __NewCode[1], __NewCode[2], __NewCode[3],
		__NewCode[4], __NewCode[5], __NewCode[6]);

	//DWORD dwOldProtect = 0; //旧保护属性
	// 去内存保护
	::VirtualProtect(__MessageBoxAddress, 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	//写入跳转,开始Hook
	WriteProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __NewCode, 7, NULL);
	// 写内存保护
	::VirtualProtect(__MessageBoxAddress, 7, dwOldProtect, &dwOldProtect);
}

int WINAPI MyMessageBoxA(
	HWND hWnd,          // handle to owner window
	LPCTSTR lpText,     // text in message box
	LPCTSTR lpCaption,  // message box title
	UINT uType          // message box style
)
{
	
	printf("MessageBoxA 已经被Hook\r\n");
	//恢复API头7个字节
	WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)__MessageBoxAddress,
		(void*)__OldCode, 7, NULL);



	//调用正确的函数
	int ret = MessageBoxA(NULL, "Hello World", "Title", MB_OK);


	//写入跳转语句,继续Hook
	WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)__MessageBoxAddress,
		(void*)__NewCode, 7, NULL);

	return ret;
}

运行结果:

参考资料:

《加密与解密(第四版)》——第十三章



小弟在学习Inline hook过程中,遇到了很多问题,作为初学者虽然解决了不少,但一定还有遗漏或不对的地方,请大佬们指点和讨论。

文件好像传不上,附上下载网址:https://github.com/Powerful99/Hook



HWS计划·2020安全精英夏令营来了!我们在华为松山湖欧洲小镇等你

最后于 2020-5-5 09:24 被Qz王大碗编辑 ,原因: 图片挂了
上传的附件:
最新回复 (30)
雪    币: 9083
活跃值: 活跃值 (401)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 2020-5-3 03:02
2
0
mark,图挂了。。。
雪    币: 371
活跃值: 活跃值 (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
黑夜丶空白 活跃值 2020-5-3 06:35
3
0
老哥 很遗憾 一个图也看不到-.- .......  
雪    币: 3972
活跃值: 活跃值 (79)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kxzpy 活跃值 2020-5-3 08:15
4
0
不错,顶顶
雪    币: 4595
活跃值: 活跃值 (73)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xie风腾 活跃值 2020-5-3 09:47
5
0

提示附件是空的哟
雪    币: 1889
活跃值: 活跃值 (86)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
柒雪天尚 活跃值 2020-5-3 09:50
6
0
LONG __cdecl InterlockedExchange(
  __inout  LONG volatile* Target,
  __in     LONG Value
);
雪    币: 76
活跃值: 活跃值 (40)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kongfubull 活跃值 2020-5-3 15:00
7
0
最近这种文章很多呀
雪    币: 256
活跃值: 活跃值 (150)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ZwCopyAll 活跃值 2020-5-3 16:41
8
0
图片挂了
雪    币: 3806
活跃值: 活跃值 (58)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Qz王大碗 活跃值 2020-5-3 22:00
9
0
不知道图片为什么老挂,上传好几次了
雪    币: 3806
活跃值: 活跃值 (58)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Qz王大碗 活跃值 2020-5-4 13:24
10
0
图片已经搞定
雪    币: 4788
活跃值: 活跃值 (2622)
能力值: (RANK:65 )
在线值:
发帖
回帖
粉丝
Editor 活跃值 2020-5-4 19:41
11
0
Qz王大碗 图片已经搞定
还有几张图片挂了。
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
nobrume 活跃值 2020-5-4 22:04
12
0
牛,看看学习
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
YenKoc 活跃值 2020-5-5 10:44
13
0
师傅,在messageBox里修改开头的7个字节,之后跳转到自己定义的函数,执行完成后,再回到原来的执行流程,是不是要在自定义函数结束时,再把之前修改的指令还原,再跳转过去原来的的7个字节
雪    币: 3806
活跃值: 活跃值 (58)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Qz王大碗 活跃值 2020-5-5 10:49
14
0
YenKoc 师傅,在messageBox里修改开头的7个字节,之后跳转到自己定义的函数,执行完成后,再回到原来的执行流程,是不是要在自定义函数结束时,再把之前修改的指令还原,再跳转过去原来的的7个字节
对的,要不然原函数开头的7个字节还是跳转指令。
//恢复API头7个字节
    WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)__MessageBoxAddress,
        (void*)__OldCode, 7, NULL);
 
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
YenKoc 活跃值 2020-5-5 11:05
15
0
秒啊,好厉害,全是硬编码来改,那些api都没见过,是靠硬背吗,2333
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_rnblmuif 活跃值 2020-5-5 21:41
16
0
很厉害,学习中
雪    币: 13
活跃值: 活跃值 (31)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_0xC05StackOver 活跃值 2020-5-6 15:53
17
0
其实inline hook 函数头这是一个老生常谈的问题了,但是存在一种情况,假定Safengine这种shell会去偷函数前五个字节或者函数前一段代码在jmp回去这就有问题了,因为你在api上挂钩根本就不走,那题主有没有想设计过一个任意地址hook呢
雪    币: 352
活跃值: 活跃值 (13)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
IBinary 活跃值 2020-5-6 16:53
18
0
可以的,后面你会遇到 什么是热HOOK 为什么会有这种HOOK .为什么x86 使用JMP改跳转方式来进行HOOK. 原子交换HOOK,保证线程同步. HOOK简单,但是主义的问题很多. 多线程问题,HOOK位置的指令长度. 如何写通用的HOOK.  x64下的inlineHOOK.  x64下的HOOK 可否适用于x86  HOOK的原理是啥(让EIP跳走, push + ret, jmp rip方式 mov rax/eax,jmp rax 都可以)
雪    币: 3806
活跃值: 活跃值 (58)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Qz王大碗 活跃值 2020-5-6 20:37
19
0
IBinary 可以的,后面你会遇到 什么是热HOOK 为什么会有这种HOOK .为什么x86 使用JMP改跳转方式来进行HOOK. 原子交换HOOK,保证线程同步. HOOK简单,但是主义的问题很多. 多线程问题, ...
谢谢大佬指点,之后会对这些内容进行研究学的
雪    币: 6030
活跃值: 活跃值 (138)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
落叶似秋 活跃值 2 2020-5-7 16:05
20
0
好像有两张是我画的图
雪    币: 3806
活跃值: 活跃值 (58)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Qz王大碗 活跃值 2020-5-7 16:58
21
0
p1和p7是我借鉴的,看到的时候觉得很好,大佬不好意思,没有备注出处
雪    币: 3806
活跃值: 活跃值 (58)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Qz王大碗 活跃值 2020-5-7 17:02
22
0
落叶似秋 [em_39]好像有两张是我画的图
p1和p7看到的时候觉得画的很好,大佬不好意思,没有备注出处,现备注出处:https://www.cnblogs.com/luoyesiqiu/p/12306336.html
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_Mr Bai 活跃值 2020-5-7 17:05
23
0
顶顶顶顶顶顶!!!!!!
雪    币: 564
活跃值: 活跃值 (55)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Bw 活跃值 2020-5-7 17:06
24
0
大佬 强啊     !!!!!!!!!!!!!
雪    币: 6030
活跃值: 活跃值 (138)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
落叶似秋 活跃值 2 2020-5-7 18:02
25
0
Qz王大碗 p1和p7看到的时候觉得画的很好,大佬不好意思,没有备注出处[em_85],现备注出处:https://www.cnblogs.com/luoyesiqiu/p/12306336.html
看到用自己画的图还挺开心的
雪    币: 0
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
雲瑞 活跃值 2020-5-19 09:47
26
0
如何全局HOOK呢。。所有进程message都替换掉
雪    币: 1049
活跃值: 活跃值 (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
走在北风里 活跃值 2020-5-19 11:35
27
0
雲瑞 如何全局HOOK呢。。所有进程message都替换掉
pde,pte读写位置1,干掉写copy,然后正常hook,hook的代码丢到共享内存里就可以了
雪    币: 229
活跃值: 活跃值 (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
苏啊树 活跃值 2020-5-21 21:36
28
0
如果在你_OldCode数组后面,直接加上跳回到__MessageBoxAddress+5的地址处的机器码,并把_OldCode改为可执行,执行完你想要的操作再跳到_OldCode不就可以了,省得来来回回Hook,浪费性能或则漏勾
雪    币: 2133
活跃值: 活跃值 (72)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hhkqqs 活跃值 2020-6-27 11:40
29
0
苏啊树 如果在你_OldCode数组后面,直接加上跳回到__MessageBoxAddress+5的地址处的机器码,并把_OldCode改为可执行,执行完你想要的操作再跳到_OldCode不就可以了,省得来来 ...
有弊端,如果函数头长这样:
55                     push ebp
8B EC               mov ebp, esp
B8 78 56 34 12 mov eax, 12345678h
替换5字节之后,Addr+5处是截断的指令56 34 12,肯定不能直接jmp过去
雪    币: 567
活跃值: 活跃值 (17)
能力值: ( LV13,RANK:330 )
在线值:
发帖
回帖
粉丝
Fpc 活跃值 4 2020-6-28 17:06
30
0
嗯,第一行如果是有用代码,这样会crash。如果遇到实例crash可以具体分析,借助反汇编再增加健壮性
雪    币: 215
活跃值: 活跃值 (66)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
阿腊婆 活跃值 2020-7-3 20:50
31
0
谢谢
游客
登录 | 注册 方可回帖
返回