阅读:2747回复:0
linux 中 inline hook 简单分析(do_exit)
author: jonathan
本文档的CopyRight归jonathan所有,可自由转载,转载时请保持文档的完整性。 /*----------------------------------------------------------------------------------------------------------------------------*/ 此文源:http://hi.baidu.com/jonathan2004/blog/item/9c5ca037b2092a0191ef3941.html 本文中重点描述一下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 <sys_exit+0>: push %ebp 0xc042ef4d <sys_exit+1>: mov %esp,%ebp 0xc042ef4f <sys_exit+3>: mov 0x8(%ebp),%eax 0xc042ef52 <sys_exit+6>: shl $0x8,%eax 0xc042ef55 <sys_exit+9>: and $0xffff,%eax 0xc042ef5a <sys_exit+14>: call 0xc042e79a <do_exit> End of assembler dump. (gdb) x/2 0xc042ef5a 0xc042ef5a <sys_exit+14>: 0xfff83be8 0xc08555ff (gdb) disass do_exit Dump of assembler code for function do_exit: 0xc042e79a <do_exit+0>: push %ebp 0xc042e79b <do_exit+1>: mov %esp,%ebp 0xc042e79d <do_exit+3>: push %edi 0xc042e79e <do_exit+4>: push %esi 0xc042e79f <do_exit+5>: push %ebx /*到此为止*, 可以看出do_exit不错,指令基本可以满足要求/ 0xc042e7a0 <do_exit+6>: mov %eax,%ebx 0xc042e7a2 <do_exit+8>: sub $0x38,%esp 0xc042e7a5 <get_current+0>: 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(); // Note: 3.x.x版本已经去掉了这个函数 CLEAR_CR0; memcpy((unsigned char*)g_do_exit_address, g_stub_do_exit, 5); SET_CR0; unlock_kernel(); // Note: 3.x.x版本已经去掉了这个函数 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就崩溃了,原因我没有继续查找,留给您了。 |
|
|