背景
最近工作需要hook函数,然后实现自己的逻辑,之前都是使用的frida来直接hook,但是这里发现,挂在frida之后对性能影响较大,可能是数十倍的影响,之前一直都没发现。所以这里必须自己实现一个hook
参考
这里参照了下面几个文章
过程
先是参照第一篇的,直接全盘复制,例子没通。。。我是使用的LD_PRELOAD的方式来加载so的,不会调试,有会的大佬指点一下呀,然后就准备重写了。
所有的hook,大致流程如下:
取出当前hook点的汇编temp
修改为跳转汇编,跳转到一个跳板指令段
在跳板指令中保存现场
跳转到自定义函数
自定义函数执行完后恢复现场
执行temp指令
跳转到原始指令的下一条
针对我的应用场景,不普适,只求快
首先我们需要开辟一个空间,用于自己hook函数后跳转到的自定义函数的存放。
这里有两个方式,frida和常规的都是在自己so中存放这段函数,但是这里有个问题,通常64位的内存分布中,so和原始elf文件的内存空间间隔很远,都要使用一个长跳跳过去。而我这里主要考虑效率问题(我猜会不会跳的短一点有可能对性能影响小),以及后续期望能够通过5字节的一个jmp指令,减少对原指令的破坏,所以需要在原始的elf中找一段不用的函数,来填写我们的自定义函数。这里参考了frida的实现,我们打开一个elf文件,看其中的段分区,可以看到代码段到堆栈分区中间是有一段空白区的,我们只要在这一段分配一块空间供跳板地址存储。
这里通过mmap的方式,在指定的地址分配一块内存:
void init_addr = (void )0xc62000;
char init_addr = (char)mmap(NULL, 4096, PROT_WRITE|PROT_EXEC|PROT_READ, MAP_ANON|MAP_PRIVATE, -1, 0);
如果这里的点不是五个字节的完整汇编呢,即正好一个2+4等情况,需要我们保存超过5个字节的汇编。这里可以使用反汇编工具,读取当前地址的指令,看保存几个合适,这里我就是参照了第一篇文章的使用了capstone,和他的区别就是我这里后续只需要5个字节,对代码的破坏性更小。
//获取目标函数前几条指令的长度,跳转指令是5个字节,所以取大于等于5的长度
int get_asm_len(intptr_t target)
{
csh handle;
cs_insn* insn;
size_t count;
char code[30] = {0};
int rv;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
memcpy((void
*
)code, (void
*
)target,
30
);
if
(cs_open(CS_ARCH_X86, CS_MODE_64, &handle))
{
printf(
"Error: cs_open\n"
);
return
-
1
;
}
count
=
cs_disasm(handle, code,
30
,
0
,
0
, &insn);
if
(count)
{
for
(size_t i
=
0
; i < count; i
+
+
)
{
/
/
if
(!strcmp(
"call"
, insn[i].mnemonic))
return
0
;
if
(insn[i].address >
=
5
)
{
rv
=
insn[i].address;
break
;
}
}
cs_free(insn, count);
}
else
{
printf(
"Error: cs_disasm\n"
);
return
-
1
;
}
cs_close(&handle);
return
rv;
|
}
这里的汇编中包含call,或者jmp 到相对地址的指令,这里都需要进行特殊处理,我这里是求简单了,当前应用的只需要处理call的,通过之前的get_asm_len如果返回为0来判断需要修改。
//根据当前temp地址重新填写相对地址
int change_asm_code(intptr_t target ,intptr_t temp)
{
csh handle;
cs_insn* insn;
size_t count;
char code[30] = {0};
int rv;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
memcpy((void
*
)code, (void
*
)target,
30
);
if
(cs_open(CS_ARCH_X86, CS_MODE_64, &handle))
{
printf(
"Error: cs_open\n"
);
return
-
1
;
}
count
=
cs_disasm(handle, code,
30
,
0
,
0
, &insn);
if
(count)
{
for
(size_t i
=
0
; i < count; i
+
+
)
{
/
/
if
(!strcmp(
"call"
, insn[i].mnemonic))
{
int
at
=
insn[i].address;
uint32_t originAddr
=
code[at
+
4
]<<
24
|code[at
+
3
]<<
16
|code[at
+
2
]|code[at
+
1
];
uint32_t targetAddr
=
target
+
at
+
originAddr
+
5
;
uint32_t relativeAddr
=
temp
-
targetAddr
-
5
;
uint8_t jumpCode[
5
]
=
{
0xe8
,
0x00
,
0x00
,
0x00
,
0x00
};
memcpy(jumpCode
+
1
; &relativeAddr, sizeof(uint32_t));
change_bytes(targetAddr,(const char
*
) jumpCode,
5
);
}
if
(insn[i].address >
=
5
)
{
rv
=
insn[i].address;
break
;
}
}
cs_free(insn, count);
}
else
{
printf(
"Error: cs_disasm\n"
);
return
-
1
;
}
cs_close(&handle);
return
rv;
|
}
保存现场:
这里可以自定义一个pusha和popa,因为本身64位的汇编不支持pusha了,我这里只保存了rdi,rsi,rdx,rcx,rax五个寄存器,前四个通常是调用的前4个参数,第五个是函数返回。如果需要的话,第五六个参数是r8和r9寄存器。本来也准备保存rsp寄存器的,后来发现编译器给我加上了保存rsp的功能,顾暂时不需要了
进行跳转:
这里网上资料主要都是使用了14个字节的跳转方式,这里我用在一级跳板往自定义代码中以及一级跳板跳回原代码,因为这里不需要考虑对源代码的破坏,长一点不要紧,简单最好。。:
68 XX XX XX XX push LowAddress
C7 44 24 04 XX XX XX XX mov qword ptr ss:[rsp + 4],HighAddress
C3
但是我们为了更小的破坏代码,在修改原地址时只使用了五个字节:
e9 XX XX XX XX jmp 相对地址
这里相对地址的计算公式是:相对地址 = 目的 - 源 - 5(指令长度)
所以我们的一级跳板主要包括四个部分:
13个字节保存自定义函数返回地址到rsp中;
14个字节绝对跳转到自定义函数地址;
n个字节保存原始地址的指令;
14个字节跳回原始地址+n的地址;
自定义函数部分走到的坑:
汇编语言风格:
我这里之前主要是通过frida获取寄存器的值来进行其他操作,所以这里需要取寄存器的值,开始使用
__asm("pop rax")的操作时,总是提示我找不到rax,这里是因为汇编风格的问题,我这不知道为什么都是at&t的风格,需要在rax前面加上%即可。其他很多相应的问题都是asm的汇编语言风格问题。
获取寄存器:
不知道什么原因,在网上搜的很多的在汇编和c++之间共享变量的方式都无法使用,报各种奇怪的错,所以我这里寻求帮助后使用了另外一种方式:
register int *r12 asm ("r12");
这里保存寄存器r12当前的值到变量r12中,后续不会随着寄存器的变化继续变化了。
最终代码:
#include<stdio.h>
#include<stdlib.h>
#include<capstone/capstone.h>
#include<unistd.h>
#include<sys/types.h>
#include<dlfcn.h>
#include<sys/mman.h>
#include<string.h>
#include<stdint.h>
//使用mmap来分配一个内存,用于后续的一级跳板
void *tempAddr = (void )0xc62000;
char temp_func;
//获取目标函数前几条指令的长度,跳转指令是5个字节,所以取大于等于5的长度
int get_asm_len(intptr_t target)
{
csh handle;
cs_insn* insn;
size_t count;
char code[30] = {0};
int rv;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
memcpy((void
*
)code, (void
*
)target,
30
);
if
(cs_open(CS_ARCH_X86, CS_MODE_64, &handle))
{
printf(
"Error: cs_open\n"
);
return
-
1
;
}
count
=
cs_disasm(handle, code,
30
,
0
,
0
, &insn);
if
(count)
{
for
(size_t i
=
0
; i < count; i
+
+
)
{
/
/
if
(!strcmp(
"call"
, insn[i].mnemonic))
return
0
;
if
(insn[i].address >
=
5
)
{
rv
=
insn[i].address;
break
;
}
}
cs_free(insn, count);
}
else
{
printf(
"Error: cs_disasm\n"
);
return
-
1
;
}
cs_close(&handle);
return
rv;
|
}
//根据当前temp地址重新填写相对地址
int change_asm_code(intptr_t target ,intptr_t temp)
{
csh handle;
cs_insn* insn;
size_t count;
char code[30] = {0};
int rv;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
memcpy((void
*
)code, (void
*
)target,
30
);
if
(cs_open(CS_ARCH_X86, CS_MODE_64, &handle))
{
printf(
"Error: cs_open\n"
);
return
-
1
;
}
count
=
cs_disasm(handle, code,
30
,
0
,
0
, &insn);
if
(count)
{
for
(size_t i
=
0
; i < count; i
+
+
)
{
/
/
if
(!strcmp(
"call"
, insn[i].mnemonic))
{
int
at
=
insn[i].address;
uint32_t originAddr
=
code[at
+
4
]<<
24
|code[at
+
3
]<<
16
|code[at
+
2
]|code[at
+
1
];
uint32_t targetAddr
=
target
+
at
+
originAddr
+
5
;
uint32_t relativeAddr
=
temp
-
targetAddr
-
5
;
uint8_t jumpCode[
5
]
=
{
0xe8
,
0x00
,
0x00
,
0x00
,
0x00
};
memcpy(jumpCode
+
1
, &relativeAddr, sizeof(uint32_t));
change_bytes(targetAddr,(const char
*
) jumpCode,
5
);
}
if
(insn[i].address >
=
5
)
{
rv
=
insn[i].address;
break
;
}
}
cs_free(insn, count);
}
else
{
printf(
"Error: cs_disasm\n"
);
return
-
1
;
}
cs_close(&handle);
return
rv;
|
}
//替换目标函数的前len个字节,使之跳转到hook函数
void change_bytes(intptr_t addr, const char code[], int len)
{
memcpy((void*)addr, code, len);
}
void func_hook(intptr_t target_addr, void* hook_func)
{
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
/
/
根据目标函数的地址确定目标函数所在的页,并将该页的权限改为可读可写可执行
intptr_t page_start
=
target_addr &
0xfffffffff000
;
mprotect((void
*
)page_start,
0x1000
, PROT_READ|PROT_WRITE|PROT_EXEC);
int
asm_len
=
get_asm_len(target_addr);
if
(asm_len
=
=
0
)
{
change_asm_code(target_addr, temp_func
+
13
+
14
);
}
if
(asm_len
=
=
-
1
)
{
printf(
"Error: get_asm_code\n"
);
exit(
-
1
);
}
/
/
保存跳板中的地址到rsp中,这样自定义函数返回时可以返回跳板函数继续执行原函数
intptr_t x
=
*
temp_func
+
27
;
char ret_code[
13
]
=
{
0x68
,x&
0xff
,(x&
0xff00
)>>
8
,(x&
0xff0000
)>>
16
,(x&
0xff000000
)>>
24
,
0xC7
,
0x44
,
0x24
,
0x04
,(x&
0xff00000000
)>>
32
,(x&
0xff0000000000
)>>
40
,(x&
0xff000000000000
)>>
48
,
(x&
0xff00000000000000
)>>
56
};
memcpy((void
*
)temp_func, (void
*
)ret_code, asm_len);
/
/
跳板地址跳到自定义函数
intptr_t y
=
hook_func;
char self_code[
14
]
=
{
0x68
,y&
0xff
,(y&
0xff00
)>>
8
,(y&
0xff0000
)>>
16
,(y&
0xff000000
)>>
24
,
0xC7
,
0x44
,
0x24
,
0x04
,(y&
0xff00000000
)>>
32
,(y&
0xff0000000000
)>>
40
,(y&
0xff000000000000
)>>
48
,
(y&
0xff00000000000000
)>>
56
,
0xC3
};
memcpy((void
*
)temp_func
+
13
, (void
*
)self_code, asm_len);
/
/
复制原始指令
memcpy((void
*
)temp_func
+
13
+
14
, (void
*
)target_addr, asm_len);
/
/
跳转指令,跳回原始地址
+
asmlen处
intptr_t z
=
(intptr_t)target_addr
+
asm_len;
/
/
构造push&ret跳转,填入目标地址
char jmp_code[
14
]
=
{
0x68
,z&
0xff
,(z&
0xff00
)>>
8
,(z&
0xff0000
)>>
16
,(z&
0xff000000
)>>
24
,
0xC7
,
0x44
,
0x24
,
0x04
,(z&
0xff00000000
)>>
32
,(z&
0xff0000000000
)>>
40
,(z&
0xff000000000000
)>>
48
,
(z&
0xff00000000000000
)>>
56
,
0xC3
};
memcpy((void
*
)(temp_func
+
asm_len
+
13
+
14
), (void
*
)jmp_code,
14
);
/
/
目标地址改为跳到跳板地址
uint32_t relativeAddr
=
target_addr
-
(uint32_t)temp_func
-
5
;
uint8_t jumpCode[
5
]
=
{
0xe9
,
0x00
,
0x00
,
0x00
,
0x00
};
memcpy(jumpCode
+
1
, &relativeAddr, sizeof(uint32_t));
change_bytes(target_addr,(const char
*
) jumpCode,
5
);
/
/
用于后续的hook函数使用
temp_func
=
temp_func
+
60
;
|
}
1
2
3
4
5
6
7
|
/
*
__asm{
pop rax;
pop rcx;
pop rdx;
pop rsi;
pop rdi;
}
*
/
|
}
//so被加载后会首先执行这里的代码
attribute((constructor))
void load()
{
temp_func = (char*)mmap(tempAddr, 4096, PROT_WRITE|PROT_EXEC|PROT_READ, MAP_ANON|MAP_PRIVATE, -1, 0);
func_hook(0x666666, (void*)test_hook);
1
|
printf(
"inject suceessfully\n"
);
|
}
这里就是粗略的实现了,反正能用。。而且是从内网往回敲的,可能有点错误还,反正就是这么个思路,好多不是很优美,等后续有时间了不断完善可以
更多【linux下的一个简单hook实现】相关视频教程:www.yxfzedu.com