文章目录

  • 1. hook一般syscall
  • 2. hook stub syscall
    • 2.1 stub_xxx 原理
    • 2.2 方法1:hook `stub_xxx`
    • 2.3 方法2:hook `call sys_xxx`
  • 参考文档:

1. hook一般syscall

在安全、性能分析等领域,经常会需要对系统调用syscall进行hook。有些模块在kernel代码中已经预先hook,例如syscall trace event。

通常syscall使用sys_call_table[]数组来间接调用:

kernel\arch\x86\kernel\entry_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)...
};

这有点出乎我们的意料,字面上理解是一些桩函数,我们看看其具体做了些什么:

kernel\arch\x86\kernel\entry_64.S:/** Certain special system calls that need to save a complete full stack frame.*/.macro PTREGSCALL label,func,arg
ENTRY(\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_ENDPROC
END(\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_ENDPROC
END(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_ENDPROC
END(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_ENDPROC
END(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_ENDPROC
END(stub_execve)

查看原始指令码:

(gdb) disassemble /r stub_execve
Dump 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 <sys_execve>  // call sys_execve...0xffffffff8146f890 <+176>:   e9 77 fd ff ff  jmpq   0xffffffff8146f60c <int_ret_from_sys_call>
End of assembler dump.
(gdb) p sys_execve
$2 = {long (const char *, const char * const *, const char * const *, struct pt_regs *)} 0xffffffff8100aac0 <sys_execve>

我们可以看到call sys_execve对应的命令码为e8 74 b2 b9 ff,其中:

  • e8对应call指令。
  • ffb9b274表示被调用函数和当前pc的偏移:
被call函数地址 - 当前地址 - 当前指令长度 = offset
0xffffffff8100aac0 - 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\n", ret);return ret;
}

具体代码放在inlinehook_syscall_example。

参考文档:

1.x86平台inline hook原理和实现
2.execmon
3.Linux x64下hook系统调用execve的正确方法

Inline Hook Syscall 详解相关推荐

  1. linux内核中的hook函数详解,linux内核中的hook函数详解

    在编写linux内核中的网络模块时,用到了钩子函数也就是hook函数.现在来看看linux是如何实现hook函数的. 先介绍一个结构体: struct nf_hook_ops,这个结构体是实现钩子函数 ...

  2. linux hook 任意内核函数,linux内核中的hook函数详解

    在编写linux内核中的网络模块时,用到了钩子函数也就是hook函数.现在来看看linux是如何实现hook函数的. 先介绍一个结构体: struct nf_hook_ops,这个结构体是实现钩子函数 ...

  3. syscall指令_linux syscall 详解【转】

    引言:分析Android源码的过程中,要想从上至下完全明白一行代码,往往涉及app.framework.native一直到kernel,可能迷失到代码世界,明白了系统调用原理,或许能帮你峰回路转,找到 ...

  4. linux syscall 详解

    引言:分析Android源码的过程中,要想从上至下完全明白一行代码,往往涉及app.framework.native一直到kernel,可能迷失到代码世界,明白了系统调用原理,或许能帮你峰回路转,找到 ...

  5. 【pytest】pytest的Hook函数详解

    文章目录 Hook函数的定义 pytest的Hook函数,修改pytest-html报告 装饰器pytest.hookimpl(hookwrapper=True) Hook函数排序/调用示例 Hook ...

  6. inline函数用法详解

    inline函数定义 内联函数的编程风格 慎用内联 inline 和 预处理的区别 参考资料 inline函数定义 在函数声明或定义中函数返回类型前加上关键字inline即把min()指定为内联. i ...

  7. 详解CSS float属性

    转自:http://luopq.com/2015/11/08/CSS-float/ \详解CSS float属性 Posted on 2015-11-08   |   In CSS   |   5条评 ...

  8. @kubernetes(k8s)的kubectl的使用及资源类型pod生命周期与资源清单详解

    文章目录 kubernetes 一.kubernetes kubectl的使用 1.kubectl 的概述: 2.kubectl的使用 2.kubectl可操作的资源对象类型 3.kubectl子命令 ...

  9. 、简述global关键字的作用_详解static inline关键字

    详解static inline关键字 本文章为知乎用户 @徐yang哟 原创,禁止抄袭! 灵感来源 在查stm32的LL库部分函数的API时,有时会查到这种函数: __STATIC_INLINE vo ...

最新文章

  1. clang-format-3.6格式化代码
  2. 文本分类入门(四)训练Part 1
  3. 数字信号处理学习笔记(二)|快速傅里叶变换
  4. 通向架构师的道路(第十一天)之Axis2 Web Service(二)
  5. linux phpunit 安装,在CentOS 7/CentOS 8系统中安装PHPUnit的方法
  6. 全中国加油:Github 开源了新型肺炎防疫项目,一起助力
  7. jenkins JDK的集成
  8. [ZJOI2012]小蓝的好友
  9. 机器人路径规划之RRT算法
  10. python numpy 矩阵运算_NumPy向量和矩阵的运算
  11. 手写签名制作电子签名详细步骤
  12. Python24中使用urllib时遇到IOError的正确打开方式
  13. 大学四年,我做过哪些兼职
  14. QChart动态生成图表(曲线)
  15. 微信api接入验证的坑!!!
  16. lisp角度转换弪度_角度和弧度换算(角度和弧度怎么换算)
  17. 计算机操作系统--网络操作系统和嵌入式操作系统
  18. 【取模软件PCtoLCD2002使用教程】
  19. python由大到小排序_Python选择从小到大的排序,python
  20. unordered_map使用详解

热门文章

  1. Python爬虫实战:制作各大音乐平台的聚合的音乐下载器
  2. 代码随想录——哈希表篇
  3. 云产品泛滥!小生来理一理各种云产品的区别~
  4. kafka入门(4)-java操作kafka
  5. 自媒体人都在用的自媒体热点网站
  6. 一款免开发慢煮机如何打造?
  7. 十年架构师耗尽心血带你如何进行微服务的单元、集成和系统测试?
  8. 轻松提高搜索能力-实用网站合集
  9. H3C 交换机端口隔离实现相同vlan下相互隔离
  10. 计算机设备替换方案,IT之家学院:第二代WP改机型升级FCU报错0x80070273的解决方案...