author: jonathan
本文档的CopyRight归jonathan所有,可自由转载,转载时请保持文档的完整性。
/*----------------------------------------------------------------------------------------------------------------------------*/
Hook多年不搞了,总认为是上不了台面的技术。但是由于产品的需要没有办法,还是要弄一弄。
本文中重点描述一下Linux的函数Hook中注意的关键点。
1 概念
Hook两个字描述:劫持
对于目标是对象,就是对象HOOK;对于目标是函数,就叫Inline Hook;对于目标是IAT,就叫IAT Hook.
2 方法
对于函数HOOK,要能够跳转到劫持函数中,再跳转原函数。如何跳转,使用什么指令?其实方法很多,最多就是使用jmp,因为简单。如果使用call也可以,还需要自己维护call产生的栈问题。
当然,插入HOOK点理论上可以在函数任何位置,但是要保障插入HOOK点前后指令的完整性。现在disassembler库也很多,都不是难题。
2.1 jmp方法
jmp dst_address_offset
此命令占5个地址; dst_address_offset = 目的地址 - HOOK点开始位置 - jmp指令占用地址(5)
2.2 call方法
call dst_address_offset
此处注意call的分解:push 返回地址;jmp 目标地址。因此在HOOK函数返回时,注意平衡堆栈,特别时使用jmp方式返回
投机的方式:找到一个以有的call处,修改call处的跳转地址即可。
3 注意事项
3.1 内存要可写,可检查CR0寄存器中的WP位
3.2 处理好堆栈平衡
3.3 原子操作,特别是多cpu情况
4 Windows平台 HOOK
文章数不胜数,略。
5 Linux下函数Inline Hook
这里以do_exit为例。do_exit函数是导出函数,所以可以直接获取函数地址;对于非导出函数,则需要相关函数查找地址。现在假设do_exit未导出。
为了找到非导出函数地址,需要在内存中找特征码。但是对于要搜寻的函数空间也有两点要求:
函数地址可知,否则陷入鸡生蛋问题;
该函数要调用寻找的未导出函数地址。
对于do_exit,我们首先应该想到是sys_exit函数。
5.1 查找do_exit函数地址
(gdb) disass sys_exit
Dump of assembler code for function sys_exit:
0xc042ef4c : push %ebp
0xc042ef4d : mov %esp,%ebp
0xc042ef4f : mov 0x8(%ebp),%eax
0xc042ef52 : shl $0x8,%eax
0xc042ef55 : and $0xffff,%eax
0xc042ef5a : call 0xc042e79a
End of assembler dump.
(gdb) x/2 0xc042ef5a
0xc042ef5a : 0xfff83be8 0xc08555ff
(gdb) disass do_exit
Dump of assembler code for function do_exit:
0xc042e79a : push %ebp
0xc042e79b : mov %esp,%ebp
0xc042e79d : push %edi
0xc042e79e : push %esi
0xc042e79f : push %ebx /*到此为止*, 可以看出do_exit不错,指令基本可以满足要求/
0xc042e7a0 : mov %eax,%ebx
0xc042e7a2 : sub $0x38,%esp
0xc042e7a5 : mov %fs:0xc0858000,%edi
...
具体就不用多说了,看看2中原理,对照一下上面红色字体部分就明白了。
5.2 替换do_exit
static unsigned char g_original_do_exit[5] = { 0 };
static unsigned char g_stub_do_exit[5] = { 0xe9, 0, 0, 0, 0};
static unsigned long g_do_exit_address = 0;
static int hook_do_exit(unsigned char* do_exit_address)
{
int ret = -1;
unsigned long offset = 0;
// g_do_exit_address = (unsigned long)(do_exit_address + 3);
g_do_exit_address = (unsigned long)(do_exit_address );
memcpy(g_original_do_exit, (unsigned char *)g_do_exit_address, 5);
offset = (unsigned long)my_do_exit - g_do_exit_address - 5;
*((unsigned long *)(g_stub_do_exit + 1)) = offset;
lock_kernel();
CLEAR_CR0;
memcpy((unsigned char*)g_do_exit_address, g_stub_do_exit, 5);
SET_CR0;
unlock_kernel();
return ret;
}
static void unhook_do_exit(void)
{
lock_kernel();
CLEAR_CR0;
memcpy((unsigned char*)g_do_exit_address, g_original_do_exit, 5);
SET_CR0;
unlock_kernel();
}
5.3 my_do_exit处理
static long my_do_exit(int error_code)
{
#if 1 /* 从do_exit头开始hook */
asm("pushl %%ebp\n\t"
"movl %%esp,%%ebp\n\t"
"pushl %%edi\n\t"
"pushl %%esi\n\t"
"pushl %%ebx\n\t" /* 为何先pushl?其后面语句也是push ebx?你自己来思考了,这里有小弯 */
"movl %0, %%ebx\n\t" /* 为何选择 ebx而不是eax ,需要你自己来思考 */
"addl $5, %%ebx\n\t"
"jmp *%%ebx\n\t"
::"m"(g_do_exit_address)
);
#else /* 从do_exit + 3的位置hook */
asm("pushl %%edi\n\t"
"pushl %%esi\n\t"
"pushl %%ebx\n\t"
"movl %%eax, %%ebx\n\t"
"movl %0, %%eax\n\t" /* 为什么选择是eax , 您要思考一下 */
"addl $5, %%eax\n\t"
"jmp *%%eax\n\t"::"m"(g_do_exit_address)
);
#endif
return 0;
}
5.4 注意事项
应用层程序退出一般从sys_exit不到信息的,但是一定能够从do_exit获取到信息。
卸载do_exit的hook就崩溃了,原因我没有继续查找,留给您了。