inlinehook的核心就是:跳转、跳板。
hook的中文含义是钩子,介绍hook含义之前,先放一个“现实世界里的hook”:
如果把作战行动当作一个函数,下达作战指令当作一次函数调用,川建国同志当作函数调用者,那么米利的行为就相当于hook了作战函数。这样,米利会监控所有作战行动,并且可以中止作战行动。
通过上面的例子,大家应该对hook有了一个初步的认识了,hook后调用原函数时,直接跳到指定的函数。
hook技术的应用很广泛,例如笔记本上的杀毒软件、网吧系统里的监控软件、pc游戏的反外挂防护系统、屏幕录制软件fraps都会使用hook来实现一些高级功能。
安全软件:360等安全软件会hook一些入口函数(例如驱动加载)获得系统最高权限。
监控软件:你在网吧上网时,监控软件hook了connect、send、recv等函数,这样访问的所有网站都会被记录下来(不要做坏事)。
游戏攻防:例如fps游戏外挂会hook d3d的DrawIndexedPrimitive实现透视功能。
#3、什么是inlinehook?
hook的实现有很多种,inlinehook是hook的一种方式。通过修改原函数开头的汇编指令,直接跳转到指定函数。
开源的inlinehook库有很多,例如subhook和微软的Detours,本文会带大家从0开始,一步一步实现一个基础的inlinehook库。
#4、inlinehook库代码结构
代码已经上传gitee,不用github的主要原因是国内gitee网速好一些。文章先介绍一些hook库代码结构,再介绍典型的使用场景。
关注微信“东北码农”,回复inlinehook可获取代码地址。
xx_mem.hpp:修改代码段属性,改为可读写、可执行
xx_asm.hpp:工具函数,向目标地址写入汇编指令,例如jmp,ret,push等。
xx_inline_hook.hpp:封装hook的c接口。包括hook、制作跳板、偏移重定位等操作。
test_hook_jmp32:inlinehook入门示例,基础0xe9的jmp跳转。
test_hook_jmp64:64bit操作系统跳转函数,hook系统函数必备。
test_trampoline:跳板函数,hook以后如何再调用原函数?
test_trampoline_relocation:跳板函数中包含相对偏移,如何重定位?
咱们通过这几个经典场景,来说一说。
inlinehook本质是一种汇编代码修改技术,修改原函数,开头第一条汇编指令改为jmp跳转到我们的函数。先看一下demo:
执行结果屏幕输出
下面说说实现,主要有两步:修改代码段内存属性;插入jmp汇编指令。
修改代码段内存属性
// 修改被hook函数内存属性为可写
xx_mem_unprotect(hello_world, 4096);
一段内存有是否可读、是否可写、是否可执行等属性。代码段默认是不可写的(不可修改),所以需要先设置为可写才能修改。xx_mem_unprotect的windows实现,调用VirtualProtect系统api来实现(linux下的api是mmap)
static bool xx_mem_unprotect(void* address, size_t size) {
DWORD old_flags;
BOOL result = VirtualProtect(address,
size,
PAGE_EXECUTE_READWRITE,
&old_flags);
return result == TRUE;
}
插入jmp汇编指令
// 在函数开头插入jmp语句,跳转到my_hello_world
xx_setjmp32(&hello_world, &my_hello_world);
hook以后,hello_world的汇编指令如下:
jmp指令共占用5个字节,指令opcode 0xe9占用1字节,偏移量占用4字节。偏移量是目标地址相对本地址的偏移。
上面的07FF664241140(下一条指令地址)-00007FF6642410B5(目标地址)=8b,刚好是e9后面的值。
代码实现部分
首先制作一个辅助类,用于向目标地址写入jmp汇编指令
汇编指令
其中write是写入指令,size返回本指令长度,后续会不断扩充汇编指令类,都有这两个接口。
下面再实现xx_setjmp32 就简单多了,计算一下偏移,然后写入指令。
现在,我们的hook库支持功能如下:
上一个场景使用的jmp指令跳转,jmp指令有一个限制是,跳转地址与原地址的偏移不能超过int32的范围,在64bit操作系统下可能无法跳过去。我们的hook库需要进化一下,支持在64位地址空间下任意跳转。
偏移足够小时,尽量使用32位jmp跳转。因为32位跳转只修改5字节,而64位大跳需要修改14字节,hook时尽量减少对原函数修改。
先看一下demo,为了展示“大跳”,hook一个系统函数
执行结果屏幕输出
如何判断是否需要大跳
判断时,计算原函数和跳转函数的偏移,是否在int32的范围即可。
64位大跳实现
实现时借助ret汇编指令。先说说ret汇编指令,在正常函数调用时,会先将返回地址入栈,等函数执行完后再调用ret指令完成返回地址出栈+跳转。
实现64位跳转时,先把要跳转的地址入栈,然后再调用ret指令实现跳转。
先看一下修改后,原函数的汇编代码:
代码实现部分
首先也是汇编指令辅助类,这里需要3个push、mov、ret。每个辅助类还是有write和size两个接口。
写入这3条汇编,代码如下:
现在,我们的hook库支持功能如下:
为了方便使用,封装了一个xx_setjmp函数,自动判断偏移量,选择合适的跳转方式,代码如下:
前两个场景都没有调用原函数,如果想调用原函数怎么做呢?例如实现监控程序,跳转函数中记录函数参数,然后再调用原函数。直接调用原函数是不行的,会再次跳转到跳转函数。
我们先看一下demo,模拟监控功能,记录函数调用参数后,借助“跳板”执行原逻辑。
执行结果屏幕输出
如何制作跳板?例如hook时破坏了原函数的前3条汇编指令,那么hook前需要申请一块内存把前3条指令复制过去,然后再跳转到原函数第四条指令,这块内存就是所谓的跳板。
跳板需要复制多少代码
复制汇编代码时,是以汇编指令为单位来复制的,不能修改了5字节,就复制5字节,那样有可能复制到半条指令。究竟复制多少字节呢?好多hook库都内嵌了一个反汇编引擎,自动判断需要拷贝条数,本文为了让大家更深入理解,决定人工计算,通过参数指定拷贝字节数。
首先先判断原函数需要修改多少字节(使用32位跳转还是64位跳转),xx_setjmp 的返回值是修改字节数,就是为了这里使用。我们的demo是修改5字节。
最少修改5字节 ,完整指令,所以需要复制2条指令,4+4=8字节。
复制代码+跳回去
制作跳板比较简单,复制代码后面跟着跳转语句跳回原函数。
调用前,别忘了给跳板内存增加可执行属性!
现在,我们的hook库支持功能如下:
上个场景中,跳板直接复制原函数代码就行了,不过有时复制来的代码确是错的,例如下面这个demo,我们看一下
什么时候跳板需要重定位?
我们在第25行执行了偏移重定位,我们看一下重定位之前的原函数和跳板函数的汇编指令。
原函数
跳板
大家发现没有,第二条指令,字节是一样的,但是指令不同!
原函数
因为这条mov汇编指令使用的是相对偏移,指令所在位置不同,绝对地址就不同,大家可以回想一下前面的jmp指令。这种参数是偏移量的指令,就需要重定位。
以后我们会内嵌一个反汇编引擎,查表可以判断是否需要重定位,不用肉眼看了。
重定位概念很有用,以后手动加载dll或内存完整性校验时也会提到。
重定位过程
首先通过原函数计算绝对地址,然后根据跳板地址计算偏移。
至此,我们的hook库功能基本齐全,可以用了。支持功能如下:
接下来会介绍远程线程注入、然后配合inlinehook去观察其它软件如何工作。
最后,求关注、点赞、转发,谢谢~
2021
年
9
月,曾报道“水门事件”的华盛顿邮报记者鲍勃·伍德沃德,披露出了一件大事,直接引发了世界震动。
马克·米利,美国的
4
星上将,美参联主席,目前美国军方的
4
号人物,绝对的美国高层。
在
2021
年
1
月
8
日,美国国会山骚乱事件的两天后,米利再次给中国打了个电话:
“美国的情况“很稳定”,一切都很好,国会山骚乱仅仅只是“偶然事件””。
“你知道的,民主有时候就是有点乱糟糟的”。
同一天,米利在五角大楼里秘密召开了一次会议,告知其他高级将领:
“自今天之后,军队执行任何重大行动之前必须首先与他磋商。”
“不管谁给你们下达命令,你们都要按照“程序”来执行,而我就是这个“程序”!”
2021
年
9
月,曾报道“水门事件”的华盛顿邮报记者鲍勃·伍德沃德,披露出了一件大事,直接引发了世界震动。
马克·米利,美国的
4
星上将,美参联主席,目前美国军方的
4
号人物,绝对的美国高层。
在
2021
年
1
月
8
日,美国国会山骚乱事件的两天后,米利再次给中国打了个电话:
“美国的情况“很稳定”,一切都很好,国会山骚乱仅仅只是“偶然事件””。
“你知道的,民主有时候就是有点乱糟糟的”。
同一天,米利在五角大楼里秘密召开了一次会议,告知其他高级将领:
“自今天之后,军队执行任何重大行动之前必须首先与他磋商。”
“不管谁给你们下达命令,你们都要按照“程序”来执行,而我就是这个“程序”!”
/
/
test_hook_jmp32
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
void hello_world()
{
printf(
"[call %s]\n"
,__FUNCTION__);
}
void my_hello_world()
{
printf(
"[call %s]\n"
, __FUNCTION__);
}
void test_hook_jmp32()
{
/
/
修改被hook函数内存属性为可写
xx_mem_unprotect(hello_world,
4096
);
/
/
在函数开头插入jmp语句,跳转到my_hello_world
xx_setjmp32(&hello_world, &my_hello_world);
/
/
测试一下
hello_world();
}
int
main()
{
printf(
"\n\n======test_hook_jmp32=================\n"
);
test_hook_jmp32();
}
/
/
test_hook_jmp32
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
void hello_world()
{
printf(
"[call %s]\n"
,__FUNCTION__);
}
void my_hello_world()
{
printf(
"[call %s]\n"
, __FUNCTION__);
}
void test_hook_jmp32()
{
/
/
修改被hook函数内存属性为可写
xx_mem_unprotect(hello_world,
4096
);
/
/
在函数开头插入jmp语句,跳转到my_hello_world
xx_setjmp32(&hello_world, &my_hello_world);
/
/
测试一下
hello_world();
}
int
main()
{
printf(
"\n\n======test_hook_jmp32=================\n"
);
test_hook_jmp32();
}
=
=
=
=
=
=
test_hook_jmp32
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
[call my_hello_world]
可见,调用hello_world时,实际执行的是my_hello_world。
=
=
=
=
=
=
test_hook_jmp32
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
[call my_hello_world]
可见,调用hello_world时,实际执行的是my_hello_world。
void hello_world()
{
00007FF6642410B0
E9
8B
00
00
00
jmp my_hello_world (
07FF664241140h
)
00007FF6642410B5
void hello_world()
{
00007FF6642410B0
E9
8B
00
00
00
jmp my_hello_world (
07FF664241140h
)
00007FF6642410B5
/
/
JMP rel32
class
jmp_rel32
{
public:
struct asm_cmd {
uint8_t opcode_;
int32_t rel32_;
};
static void write(void
*
cmd_addr, int32_t rel32) {
auto
*
cmd
=
(asm_cmd
*
)cmd_addr;
cmd
-
>opcode_
=
0xe9
;
cmd
-
>rel32_
=
rel32;
}
static uint8_t size() {
return
sizeof(asm_cmd); }
};
/
/
JMP rel32
class
jmp_rel32
{
public:
struct asm_cmd {
uint8_t opcode_;
int32_t rel32_;
};
static void write(void
*
cmd_addr, int32_t rel32) {
auto
*
cmd
=
(asm_cmd
*
)cmd_addr;
cmd
-
>opcode_
=
0xe9
;
cmd
-
>rel32_
=
rel32;
}
static uint8_t size() {
return
sizeof(asm_cmd); }
};
/
/
ret:两个地址的偏移
static int64_t xx_get_offset(void
*
src, void
*
dst) {
return
(char
*
)dst
-
(char
*
)src;
}
/
/
ret:返回从src jmp 到dst的偏移
static int64_t xx_get_jmp32_offset(void
*
src, void
*
dst) {
return
xx_get_offset((char
*
)src
+
jmp_rel32::size(), dst);
}
/
/
写入汇编,
32bit
位移跳转,jmp到dst
/
/
ret
5
(修改
5
byte)
static uint32_t xx_setjmp32(void
*
src, void
*
dst) {
int32_t offset
=
(int32_t)xx_get_jmp32_offset(src, dst);
jmp_rel32::write(src, offset);
return
jmp_rel32::size();
}
/
/
ret:两个地址的偏移
static int64_t xx_get_offset(void
*
src, void
*
dst) {
return
(char
*
)dst
-
(char
*
)src;
}
/
/
ret:返回从src jmp 到dst的偏移
static int64_t xx_get_jmp32_offset(void
*
src, void
*
dst) {
return
xx_get_offset((char
*
)src
+
jmp_rel32::size(), dst);
}
/
/
写入汇编,
32bit
位移跳转,jmp到dst
/
/
ret
5
(修改
5
byte)
static uint32_t xx_setjmp32(void
*
src, void
*
dst) {
int32_t offset
=
(int32_t)xx_get_jmp32_offset(src, dst);
jmp_rel32::write(src, offset);
return
jmp_rel32::size();
}
/
/
test_hook_jmp64
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
int
__cdecl my_fclose(
_Inout_
FILE
*
_Stream
) {
printf(
"[call %s]\n"
, __FUNCTION__);
return
0
;
}
void test_hook_jmp64()
{
/
/
判断偏移是否满足
32
位
int64_t offset
=
xx_get_offset(&fclose, &my_fclose);
bool
need_far_jmp
=
xx_int32_overflow(offset);
printf(
"need_far_jmp=%u \n"
, need_far_jmp);
/
/
修改被hook函数内存属性为可写
xx_mem_unprotect(&fclose,
1024
);
/
/
借助ret汇编指令实现
64
位跳转
xx_setjmp64(&fclose, &my_fclose);
/
/
测试一下
fclose(nullptr);
}
int
main()
{
printf(
"\n\n======test_hook_jmp64=================\n"
);
test_hook_jmp64();
}
/
/
test_hook_jmp64
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
/
int
__cdecl my_fclose(
_Inout_
FILE
*
_Stream
) {
printf(
"[call %s]\n"
, __FUNCTION__);
return
0
;
}
void test_hook_jmp64()
{
/
/
判断偏移是否满足
32
位
int64_t offset
=
xx_get_offset(&fclose, &my_fclose);
bool
need_far_jmp
=
xx_int32_overflow(offset);
printf(
"need_far_jmp=%u \n"
, need_far_jmp);
/
/
修改被hook函数内存属性为可写
xx_mem_unprotect(&fclose,
1024
);
/
/
借助ret汇编指令实现
64
位跳转
xx_setjmp64(&fclose, &my_fclose);
/
/
测试一下
fclose(nullptr);
}
int
main()
{
printf(
"\n\n======test_hook_jmp64=================\n"
);
test_hook_jmp64();
}
=
=
=
=
=
=
test_hook_jmp64
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
need_far_jmp
=
1
[call my_fclose]
need_far_jmp
=
1
代表需要“大跳”。
=
=
=
=
=
=
test_hook_jmp64
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
need_far_jmp
=
1
[call my_fclose]
need_far_jmp
=
1
代表需要“大跳”。
/
/
ret:是否超过int32范围
static
bool
xx_int32_overflow(int64_t val) {
return
val < INT32_MIN || val > INT32_MAX;
}
/
/
ret:是否超过int32范围
static
bool
xx_int32_overflow(int64_t val) {
return
val < INT32_MIN || val > INT32_MAX;
}
00007FF97A2596A0
68
D0
10
A0 BE push
0FFFFFFFFBEA010D0h
00007FF97A2596A5
C7
44
24
04
F7
7F
00
00
mov dword ptr [rsp
+
4
],
7FF7h
00007FF97A2596AD
C3 ret
push
+
mov指令负责把地址压栈,ret指令负责跳转。
00007FF97A2596A0
68
D0
10
A0 BE push
0FFFFFFFFBEA010D0h
00007FF97A2596A5
C7
44
24
04
F7
7F
00
00
mov dword ptr [rsp
+
4
],
7FF7h
00007FF97A2596AD
C3 ret
push
+
mov指令负责把地址压栈,ret指令负责跳转。
/
/
PUSH imm32
class
push_imm32
{
public:
struct asm_cmd {
uint8_t opcode_;
uint32_t imm32_;
};
static void write(void
*
cmd_addr, uint32_t imm32) {
auto
*
cmd
=
(asm_cmd
*
)cmd_addr;
cmd
-
>opcode_
=
0x68
;
/
/
jmp
cmd
-
>imm32_
=
imm32;
}
static uint8_t size() {
return
sizeof(asm_cmd); }
};
/
/
mov dword ptr[rsp
+
offset],imm32
class
mov_rsp_ptr_imm32
{
public:
struct asm_cmd {
uint8_t opcode_;
uint8_t para1_;
uint8_t reg_type_;
int8_t offset_;
uint32_t imm32_;
};
static void write(void
*
cmd_addr, int8_t off, uint32_t imm32) {
auto
*
cmd
=
(asm_cmd
*
)cmd_addr;
cmd
-
>opcode_
=
0xc7
;
/
/
mov
cmd
-
>para1_
=
0x44
;
/
/
to reg ptr
cmd
-
>reg_type_
=
0x24
;
/
/
rsp
cmd
-
>offset_
=
off;
cmd
-
>imm32_
=
imm32;
}
static uint8_t size() {
return
sizeof(asm_cmd); }
};
/
/
ret
class
ret
{
public:
struct asm_cmd {
uint8_t opcode_;
};
static void write(void
*
cmd_addr) {
auto
*
cmd
=
(asm_cmd
*
)cmd_addr;
cmd
-
>opcode_
=
0xc3
;
}
static uint8_t size() {
return
sizeof(asm_cmd); }
};
/
/
PUSH imm32
class
push_imm32
{
public:
struct asm_cmd {
uint8_t opcode_;
uint32_t imm32_;
};
static void write(void
*
cmd_addr, uint32_t imm32) {
auto
*
cmd
=
(asm_cmd
*
)cmd_addr;
cmd
-
>opcode_
=
0x68
;
/
/
jmp
cmd
-
>imm32_
=
imm32;
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)