1. hook一般syscall
在安全、性能分析等领域,经常会需要对系统调用syscall进行hook。有些模块在kernel代码中已经预先hook,例如syscall trace event。
通常syscall使用sys_call_table[]数组来间接调用:
kernelarchx86kernelentry_64.S:ENTRY(system_call)call *sys_call_table(,%rax,8) # XXX: rip relative
sys_call_table[]数组中保存的是所有系统调用的函数指针:
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {__SYSCALL(__NR_read, sys_read)__SYSCALL(__NR_write, sys_write)__SYSCALL(__NR_open, sys_open)__SYSCALL(__NR_close, sys_close)...};
对于其他没有预置代码的模块来说,需要在运行的时候动态hook,通常我们使用inline hook。inline hook的好处是hook完以后,运行时零开销。
实例代码:
void syscallxxx_hook_init(void){unsigned long *sct;void ** g_syscall_table;g_syscall_table = (void **)kallsyms_lookup_name("sys_call_table");make_kernel_page_readwrite();preempt_disable();/* (1) 备份原有g_syscall_table[]数组中的函数指针 */orig_syscallxxx = (void *)g_syscall_table[__NR_syscallxxx];/* (2) 把g_syscall_table[]数组值改为新的函数指针 */sct[__NR_syscallxxx] = (unsigned long)new_syscallxxx;preempt_enable();make_kernel_page_readonly();}↓asmlinkage long new_syscallxxx(...){long rc;/* (2.1) 做一些hook增加的事情 */rc = do_something(...);if (0 != rc)return rc;/* (2.2) 调用原有的syscall处理 */return orig_syscallxxx(....);}
这种hook方式在大部分情况下工作正常,但是某些特殊的系统调用会工作异常。
2. hook stub syscall
2.1 stub_xxx 原理
在4.5版本及以下的内核中,x86架构对某些系统调用有特殊处理。我们可以在sys_call_table[]数组中看到的函数不是sys_xxx而是stub_xxx:
const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {__SYSCALL(__NR_rt_sigreturn, stub_rt_sigreturn)__SYSCALL(__NR_clone, stub_clone)__SYSCALL(__NR_fork, stub_fork)__SYSCALL(__NR_vfork, stub_vfork)__SYSCALL(__NR_execve, stub_execve)__SYSCALL(__NR_sigaltstack, stub_sigaltstack)__SYSCALL(__NR_iopl, stub_iopl)...};
这有点出乎我们的意料,字面上理解是一些桩函数,我们看看其具体做了些什么:
:/*Certain special system calls that need to save a complete full stack frame.*/PTREGSCALL label,func,argENTRY(label)PARTIAL_FRAME 1 8 /* offset 8: return address */subq $REST_SKIP, %rspCFI_ADJUST_CFA_OFFSET REST_SKIPcall save_restDEFAULT_FRAME -2 8 /* offset 8: return address */leaq 8(%rsp), arg /* pt_regs pointer */call func /* (1.1) 调用实际的系统调用sys_xxx()函数 */jmp ptregscall_commonCFI_ENDPROCEND(label).endm(1) stub_clone/fork/vfork/sigaltstack/iopl 函数的定义 */PTREGSCALL stub_clone, sys_clone, %r8PTREGSCALL stub_fork, sys_fork, %rdiPTREGSCALL stub_vfork, sys_vfork, %rdiPTREGSCALL stub_sigaltstack, sys_sigaltstack, %rdxPTREGSCALL stub_iopl, sys_iopl, %rsiENTRY(ptregscall_common)DEFAULT_FRAME 1 8 /* offset 8: return address */RESTORE_TOP_OF_STACK %r11, 8movq_cfi_restore R15+8, r15movq_cfi_restore R14+8, r14movq_cfi_restore R13+8, r13movq_cfi_restore R12+8, r12movq_cfi_restore RBP+8, rbpmovq_cfi_restore RBX+8, rbxret $REST_SKIP /* pop extended registers */CFI_ENDPROCEND(ptregscall_common)(2) stub_execve函数的定义 */ENTRY(stub_execve)CFI_STARTPROCaddq $8, %rspPARTIAL_FRAME 0SAVE_RESTFIXUP_TOP_OF_STACK %r11movq %rsp, %rcxcall sys_execve /* (2.1) 调用实际的系统调用sys_execve()函数 */RESTORE_TOP_OF_STACK %r11movq %rax,RAX(%rsp)RESTORE_RESTjmp int_ret_from_sys_callCFI_ENDPROCEND(stub_execve)/*sigreturn is special because it needs to restore all registers on return.This cannot be done with SYSRET, so use the IRET return path instead.*/(3) stub_rt_sigreturn函数的定义 */ENTRY(stub_rt_sigreturn)CFI_STARTPROCaddq $8, %rspPARTIAL_FRAME 0SAVE_RESTmovq %rsp,%rdiFIXUP_TOP_OF_STACK %r11call sys_rt_sigreturn /* (3.1) 调用实际的系统调用sys_rt_sigreturn()函数 */movq %rax,RAX(%rsp) # fixme, this could be done at the higher layerRESTORE_RESTjmp int_ret_from_sys_callCFI_ENDPROCEND(stub_rt_sigreturn)
为什么系统要对这几个系统调用做stub_xxx的特殊处理?
注释中的一段话说明了大概原因:
/** Certain special system calls that need to save a complete full stack frame.* 某些特殊的系统调用需要保存完整的完整堆栈帧。*/
针对这类特殊的系统调用,我们有两种方法来进行hook。
2.2 方法1:hook stub_xxx
第一种方法我们还是继续替换sys_call_table[]数组中函数指针,但是要自己处理hook函数的栈平衡。
写一段自己的stub_new_syscallxxx函数来替换原有的stub_syscallxxx函数:
stub_new_syscallxxx:/**(1.1) 保存寄存器状态, 保证之后调用原来的stub_syscallxxx的时候CPU执行环境一致其中rdi,rsi,rdx,rcx,rax,r8,r9,r10,r11保存sysenter的参数,rbx作为临时变量*/pushq %rbxpushq %rdipushq %rsipushq %rdxpushq %rcxpushq %raxpushq %r8pushq %r9pushq %r10pushq %r11(1.2) 调用自己的hook函数 */call new_syscallxxxtest %rax, %raxmovq %rax, %rbx(1.3) 恢复寄存器状态 */pop %r11pop %r10pop %r9pop %r8pop %raxpop %rcxpop %rdxpop %rsipop %rdijz new_syscallxxx_done(2.1) new_syscallxxx返回值为非0时 */movq %rbx, %raxpop %rbxret /* 这里不一定要jmp int_ret_from_sys_call,反正syscallxxx已经被我们拦截了 */(2.2) new_syscallxxx返回值为0时 */new_syscallxxx_done:pop %rbxjmp*orig_sys_call_table(,%rax,8)/*调用原始的stub_syscallxxx*/
这种方法要小心处理调用堆栈,在我们hook函数运行之前要小心的保护堆栈,在hook函数运行完成后要完全恢复堆栈。而且不方便实现post hook。
2.3 方法2:hook call sys_xxx
另一种方法我们替换stub_syscallxxx函数中的call sys_syscallxxx语句。例如:
ENTRY(stub_execve)CFI_STARTPROCaddq $8, %rspPARTIAL_FRAME 0SAVE_RESTFIXUP_TOP_OF_STACK %r11movq %rsp, %rcxcall sys_execve // 替换call语句中的sys_execve为new_sys_execveRESTORE_TOP_OF_STACK %r11movq %rax,RAX(%rsp)RESTORE_RESTjmp int_ret_from_sys_callCFI_ENDPROCEND(stub_execve)
查看原始指令码:
(gdb) disassemble /r stub_execveDump of assembler code for function stub_execve:0xffffffff8146f7e0 <+0>: 48 83 c4 08 add $0x8,%rsp...0xffffffff8146f847 <+103>: e8 74 b2 b9 ff callq 0xffffffff8100aac0// call sys_execve ...0xffffffff8146f890 <+176>: e9 77 fd ff ff jmpq 0xffffffff8146f60cEnd of assembler dump.(gdb) p sys_execve$2={long(constchar*,constchar*const*,constchar*const*,structpt_regs*)}0xffffffff8100aac0
我们可以看到call sys_execve对应的命令码为e8 74 b2 b9 ff,其中:
- 
			
e8对应call指令。
 - 
			
ffb9b274表示被调用函数和当前pc的偏移:
 
被call函数地址 - 当前地址 - 当前指令长度 = offset0xffffffff8100aac0-0xffffffff8146f847-5=0xFFFFFFFFFFB9B274&0xFFFFFFFF=0xFFB9B274
所以我们只要定义个参数完全一致的新函数new_sys_execve(),把sys_execve()的对应偏移ffb9b274替换成new_sys_execve()的相对偏移即可。
static asmlinkage long new_sys_execve(const char __user * filename,const char __user * const __user * argv,const char __user * const __user * envp, struct pt_regs *regs) {size_t exec_line_size;char * exec_str = NULL;char ** p_argv = (char **) argv;long ret = 0;/* (1) pre hook 点 *//* Finally, call the original sys_execve *//* (2) 调用原始系统调用 */ret = orig_sys_execve_fn(filename, argv, envp, regs);/* (3) post hook 点 */printk("orig_sys_execve_fn ret = %d ", ret);return ret;}
具体代码放在inlinehook_syscall_example。
参考文档:
1.x86平台inline hook原理和实现
2.execmon
3.Linux x64下hook系统调用execve的正确方法
审核编辑 :李倩
- 
                                模块
                                +关注
关注
7文章
2696浏览量
47438 - 
                                代码
                                +关注
关注
30文章
4780浏览量
68539 - 
                                inline
                                +关注
关注
0文章
4浏览量
1638 
原文标题:Inline Hook Syscall 详解
文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
请问static inline有什么作用?
linux的类似hook函数
怎样去使用RT-Thread系统中的Hook功能呢
讲讲Hook威廉希尔官方网站 的攻防对抗思路
利用Hook威廉希尔官方网站 实现进程控制
Linux下的网络HOOK实现
基于NDIS-HOOK的个人防水墙设计
    
在嵌入式设备中使用Malloc Hook的试验
内核级HOOK的几种实现方法与应用说明
使用内核三步实现InlineHook的详细分析
    
RTOS操作系统中HOOK函数有什么用途?
    
          
        
        
Inline Hook Syscall详解
 
    
           
            
            
                
            
评论