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[]数组中保存的是所有系统调用的函数指针:
#define __syscall(nr, sym) [nr] = sym,const sys_call_ptr_t sys_call_table[__nr_syscall_max+1] = { #define __nr_read 0 __syscall(__nr_read, sys_read) #define __nr_write 1 __syscall(__nr_write, sys_write) #define __nr_open 2 __syscall(__nr_open, sys_open) #define __nr_close 3 __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] = { #define __nr_rt_sigreturn 15 __syscall(__nr_rt_sigreturn, stub_rt_sigreturn) #define __nr_clone 56 __syscall(__nr_clone, stub_clone) #define __nr_fork 57 __syscall(__nr_fork, stub_fork) #define __nr_vfork 58 __syscall(__nr_vfork, stub_vfork) #define __nr_execve 59 __syscall(__nr_execve, stub_execve) #define __nr_sigaltstack 131 __syscall(__nr_sigaltstack, stub_sigaltstack) #define __nr_iopl 172 __syscall(__nr_iopl, stub_iopl) ...};
这有点出乎我们的意料,字面上理解是一些桩函数,我们看看其具体做了些什么:
kernelarchx86kernelentry_64.s:/* * certain special system calls that need to save a complete full stack frame. */ .macro ptregscall label,func,argentry(label) partial_frame 1 8 /* offset 8: return address */ subq $rest_skip, %rsp cfi_adjust_cfa_offset rest_skip call save_rest default_frame -2 8 /* offset 8: return address */ leaq 8(%rsp), arg /* pt_regs pointer */ call func /* (1.1) 调用实际的系统调用sys_xxx()函数 */ jmp ptregscall_common cfi_endprocend(label) .endm /* (1) stub_clone/fork/vfork/sigaltstack/iopl 函数的定义 */ ptregscall stub_clone, sys_clone, %r8 ptregscall stub_fork, sys_fork, %rdi ptregscall stub_vfork, sys_vfork, %rdi ptregscall stub_sigaltstack, sys_sigaltstack, %rdx ptregscall stub_iopl, sys_iopl, %rsientry(ptregscall_common) default_frame 1 8 /* offset 8: return address */ restore_top_of_stack %r11, 8 movq_cfi_restore r15+8, r15 movq_cfi_restore r14+8, r14 movq_cfi_restore r13+8, r13 movq_cfi_restore r12+8, r12 movq_cfi_restore rbp+8, rbp movq_cfi_restore rbx+8, rbx ret $rest_skip /* pop extended registers */ cfi_endprocend(ptregscall_common) /* (2) stub_execve函数的定义 */entry(stub_execve) cfi_startproc addq $8, %rsp partial_frame 0 save_rest fixup_top_of_stack %r11 movq %rsp, %rcx call sys_execve /* (2.1) 调用实际的系统调用sys_execve()函数 */ restore_top_of_stack %r11 movq %rax,rax(%rsp) restore_rest jmp int_ret_from_sys_call cfi_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_startproc addq $8, %rsp partial_frame 0 save_rest movq %rsp,%rdi fixup_top_of_stack %r11 call sys_rt_sigreturn /* (3.1) 调用实际的系统调用sys_rt_sigreturn()函数 */ movq %rax,rax(%rsp) # fixme, this could be done at the higher layer restore_rest jmp int_ret_from_sys_call cfi_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 %rbx pushq %rdi pushq %rsi pushq %rdx pushq %rcx pushq %rax pushq %r8 pushq %r9 pushq %r10 pushq %r11 /* (1.2) 调用自己的hook函数 */ call new_syscallxxx test %rax, %rax movq %rax, %rbx /* (1.3) 恢复寄存器状态 */ pop %r11 pop %r10 pop %r9 pop %r8 pop %rax pop %rcx pop %rdx pop %rsi pop %rdi jz new_syscallxxx_done /* (2.1) new_syscallxxx返回值为非0时 */ movq %rbx, %rax pop %rbx ret /* 这里不一定要jmp int_ret_from_sys_call,反正syscallxxx已经被我们拦截了 */ /* (2.2) new_syscallxxx返回值为0时 */new_syscallxxx_done: pop %rbx jmp *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_startproc addq $8, %rsp partial_frame 0 save_rest fixup_top_of_stack %r11 movq %rsp, %rcx call sys_execve // 替换call语句中的sys_execve为new_sys_execve restore_top_of_stack %r11 movq %rax,rax(%rsp) restore_rest jmp int_ret_from_sys_call cfi_endprocend(stub_execve)
查看原始指令码:
(gdb) disassemble /r stub_execvedump of assembler code for function stub_execve: 0xffffffff8146f7e0 : 48 83 c4 08 add $0x8,%rsp ... 0xffffffff8146f847 : e8 74 b2 b9 ff callq 0xffffffff8100aac0 // call sys_execve ... 0xffffffff8146f890 : e9 77 fd ff ff jmpq 0xffffffff8146f60c end of assembler dump.(gdb) p sys_execve$2 = {long (const char *, const char * const *, const char * const *, struct pt_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的正确方法
视频会议的音视频信号分析
国产Jeep全新指南者上市 售15.98万起
电源管理较预期有所下降,新能源是救星?
在高端音频应用中电阻的研究
NB-IoT是怎么工作的,是否支持基站定位?
Inline Hook Syscall详解
泰克任意/波形函数发生器的常用功能
小米Note2真机谍照曝光:首款双曲面屏手机登台
以太坊在未来有希望成为下一代交易网络
基于LM35和LM3914的室温温度计的制作
雷电4接口发布 支持双4K显示输出
vivo Z5x添新配色 小清新幻彩粉明日上市
解码器的工作原理
德国Roboworker直角坐标机器人,应用于玻璃生产线的设计
宝马、高通取得巨大胜利 欧盟拒绝WiFi作为车联网技术标准的提案
屏幕专家预测苹果iPhone8:OLED屏是核心改动
浅谈6G智能超表面技术的先进算法与架构设计
基于嵌入式操作系统uClinux的虹膜图像采集以及预处理
食用油过氧化值检测仪的产品特点是怎样的
建博会完美收官 | 罗曼斯全面升级,绽放品牌实力!