随着rhel8.0于去年5月初发布以来,开启了rhel8.x的时代,随后一段时间里centos、oracle linux也都发布了基于rhel的8.x系统。前段时间我就安装了个centos8.0,但是在编译运行之前写的hook内核的代码时,却发现之前的hook方法不奏效了。

一波谷歌之后,看到了这篇文章[PATCH 000/109] remove in-kernel calls to syscalls,如下图:

在64位的x86平台,4.17及以上的内核中,采用了新的调用约定,只使用struct pt_regs结构体指针这一个参数,即时解析出所需的参数,然后再传递到真正的系统调用处理函数中。

下面我们通过内核源码来探寻在centos8.0(基于4.18.0-80内核源码)系统中,如何hook系统调用。

本文实验环境:

[root@yglocalhost ~]# uname -r
4.18.0-80.el8.x86_64
[root@yglocalhost ~]# cat /etc/redhat-release
CentOS Linux release 8.0.1905 (Core)

本文以openat系统调用的hook为例切入,对于内核系统调用定义的详细解析见 linux系统调用内核源码解析(基于4.18.0-87版本内核)。

源码探秘

内核源码中openat系统调用的定义,在include/linux/syscalls.h文件中:

asmlinkage long sys_openat(int dfd, const char __user *filename, int flags,         umode_t mode);

01 系统调用原型

在fs\open.c文件中找到了系统调用openat的定义:

SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags, umode_t, mode){if (force_o_largefile())flags |= O_LARGEFILE;  return do_sys_open(dfd, filename, flags, mode);
}

将SYSCALL_DEFINE4(openat, xxx)宏展开后代码如下,详细展开过程看这篇:

SYSCALL_METADATA(_openat, 4, int, dfd, const char __user *, filename, int, flags, umode_t, mode)
asmlinkage long __x64_sys_openat(const struct pt_regs *regs);
ALLOW_ERROR_INJECTION(__x64_sys_openat, ERRNO);
static long __se_sys_openat(__MAP(4,__SC_LONG,__VA_ARGS__));
static inline long __do_sys_openat(__MAP(4,__SC_DECL,__VA_ARGS__));asmlinkage long __x64_sys_openat(const struct pt_regs *regs)
{return __se_sys_openat(SC_X86_64_REGS_TO_ARGS(4,__VA_ARGS__));
}//__IA32_SYS_STUBx(x, name, __VA_ARGS__)
static long __se_sys_openat(__MAP(4,__SC_LONG,__VA_ARGS__))
{long ret = __do_sys_openat(__MAP(4,__SC_CAST,__VA_ARGS__));     __MAP(4,__SC_TEST,__VA_ARGS__); __PROTECT(4, ret,__MAP(4,__SC_ARGS,__VA_ARGS__));return ret;
}static inline long __do_sys_openat(int dfd, const char __user *filename, int flags, umode_t mode){if (force_o_largefile())flags |= O_LARGEFILE;return do_sys_open(AT_FDCWD, filename, flags, mode);
}

所以,应用层到内核的openat系统调用实际上就是:

asmlinkage long __x64_sys_openat(const struct pt_regs *regs);

我们可以通过命令看到

[root@yglocal /]# grep __x64_sys_openat /proc/kallsyms
ffffffffbb0b60e0 T __x64_sys_openat
ffffffffbc796c50 t _eil_addr___x64_sys_openat

x86-64平台,在4.17及以上内核版本中,系统调用名称在之前基础上都加了"_x64"前缀。所以系统调用表里__NR_xxx下标所对应的系统调用内核地址就是指向了这里(__x64_sys_openat)。

现在就很明朗了,hook的系统调用,对应的原型都是这样的:

asmlinkage long __x64_sys_xxxx(const struct pt_regs *);

02 参数解析

我们再来看看,系统调用的唯一参数struct pt_regs结构体的具体定义(arch\x86\include\asm\ptrace.h ):

struct pt_regs {
/** C ABI says these regs are callee-preserved. They aren't saved on kernel entry* unless syscall needs a complete, fully filled "struct pt_regs".*/unsigned long r15;unsigned long r14;unsigned long r13;unsigned long r12;unsigned long bp;unsigned long bx;
/* These regs are callee-clobbered. Always saved on kernel entry. */unsigned long r11;unsigned long r10;unsigned long r9;unsigned long r8;unsigned long ax;unsigned long cx;unsigned long dx;unsigned long si;unsigned long di;
/** On syscall entry, this is syscall#. On CPU exception, this is error code.* On hw interrupt, it's IRQ number:*/unsigned long orig_ax;
/* Return frame for iretq */unsigned long ip;unsigned long cs;unsigned long flags;unsigned long sp;unsigned long ss;
/* top of stack page */
};

那么struct pt_regs怎么对应到系统调用具体的参数上呢,我们注意到__SYSCALL_DEFINEx宏展开后,传给__se_sys_openat函数的参数是SC_X86_64_REGS_TO_ARGS宏,该宏定义如下:

/* Mapping of registers to parameters for syscalls on x86-64 and x32 */
#define SC_X86_64_REGS_TO_ARGS(x, ...)                  \__MAP(x,__SC_ARGS                      \,,regs->di,,regs->si,,regs->dx                \,,regs->r10,,regs->r8,,regs->r9)          \

那么系统调用的6个参数应该依次就是regs->di,regs->si,regs->dx,regs->r10,regs->r8,regs->r9。

另外在entry_64.S文件中也能找到相关说明(arch\x86\entry\entry_64.S):

可以看出6个参数分别是在rdi、rsi、rdx、r10、r8、r9中。

自此,问题就都解决了,hook的系统调用原型及参数都明了了。

编码实现

1 待hook的系统调用原型

typedef asmlinkage long(*sys_call_ptr_t)(const struct pt_regs *);

对于openat,保存系统旧的openat地址,声明如下:

sys_call_ptr_t old_openat;
old_openat = sys_call_table[__NR_openat];

2 自定义hook函数

定义我们自己的my_openat处理函数,用于替换旧的openat

static asmlinkage long my_openat(const struct pt_regs *regs)

3 参数处理

通过前面分析,我们知道系统调用的最多6个参数分别对应struct pt_regs *regs中的6个寄存器,依次为:di、si、dx、r10、r8、r9。

再回过头来看看系统调用openat的声明:

asmlinkage long sys_openat(int dfd,const char__user *filename,int flags,umode_t mode);

所以,
第一个参数dfd在regs->di中
第二个参数filename在regs->si中
第三个参数flags在regs->dx中
第四个参数mode在regs->r10中

4 完整代码实现

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <asm/uaccess.h>MODULE_LICENSE("GPL");typedef asmlinkage long (*sys_call_ptr_t)(const struct pt_regs *);
static sys_call_ptr_t *sys_call_table;
sys_call_ptr_t old_openat; //void disable_write_protect(void)
{write_cr0(read_cr0() & (~0x10000));
}void enable_write_protect(void)
{write_cr0(read_cr0() | 0x10000);
}static asmlinkage long my_openat(const struct pt_regs *regs)
{int dfd = regs->di;char __user *filename = (char *)regs->si;char user_filename[256] = {0};int ret = raw_copy_from_user(user_filename, filename, sizeof(user_filename));printk("%s. proc:%s, pid:%d, dfd:%d, filename:[%s], copy ret:%d\n", __func__,current->group_leader->comm, current->tgid, dfd, user_filename, ret);return old_openat(regs);
}static int __init test_init(void)
{sys_call_table = (sys_call_ptr_t *)kallsyms_lookup_name("sys_call_table");old_openat = sys_call_table[__NR_openat];printk("[info] %s. old_openat:0x%llx\n", __func__, old_openat);disable_write_protect();sys_call_table[__NR_openat] = my_openat;enable_write_protect();printk("%s inserted.\n",__func__);return 0;
}static void __exit test_exit(void)
{disable_write_protect();sys_call_table[__NR_openat] = old_openat;enable_write_protect();printk("%s removed.\n",__func__);
}module_init(test_init);
module_exit(test_exit);

编译成ko后,insmod加载进内核,运行测试结果如下图:


本文同样也适用于ubuntu的4.17以上的内核版本系统,你可以尝试在最新的ubuntu系统中实验下。

有关hook系统调用详细讲解,可以看hook调用完整实例。可以关注我的微信公众号大胖聊编程,通过公众号联系到我,可以加好友一起交流学习。

Linux Hook系统调用(适用基于x86_64的4.17.0以上的内核版本)相关推荐

  1. Linux hook系统调用open/read/write

    测试系统: curtis@curtis-virtual-machine:~/Desktop$ uname -a Linux curtis-virtual-machine 4.2.0-42-generi ...

  2. Linux 2.6中基于Sysenter的系统调用机制

    https://articles.manugarg.com/systemcallinlinux2_6.html 目录 1. 什么是系统调用? 2. 系统调用中发生了什么? 3. 很好的老方法 4. 新 ...

  3. Linux内核Hook系统调用execve

    资源下载地址:linux内核hook系统调用execve函数-Linux文档类资源-CSDN下载 (已在内核为 4.19.0-amd64-desktop版本uos编译通过,并成功达到目的) 在Linu ...

  4. Linux系统调用表(x86_64)

    内核版本 Linux 4.7 Note:64位系统中,不再使用int 0x80来进行系统调用,取而代之的是syscall指令 %rax System call %rdi %rsi %rdx %r10 ...

  5. Linux HOOK

    0 - 前言 1 - 绪论 2 - 介绍 2.1 - 什么是中断(interrupt)? 2.2 - 中断和异常(exception) 2.3 - 中断向量 2.4 - 什么是IDT? 3 - 异常 ...

  6. Linux的系统调用、网络连接状态、磁盘I/O;可疑行为监控/日志收集、SHELL命令执行流程

    http://man7.org/linux/man-pages/man7/capabilities.7.html http://www.cnblogs.com/LittleHann/p/3850653 ...

  7. linux mkdir 系统调用,Linux Rootkit 系列四:对于系统调用挂钩方法的补充

    免责声明:本文介绍的安全知识方法以及代码仅用于渗透测试及安全教学使用,禁止任何非法用途,后果自负 前言 我将会把系列文章继续写下去,由于本系列文章novice同学也在写,所以我俩的顺序可能有点乱,不过 ...

  8. Linux Hook技术实践

    LInux Hook技术实践 什么是hook 简单的说就是别人本来是执行libA.so里面的函数的,结果现在被偷偷换成了执行你的libB.so里面的代码,是一种替换. 为什么hook 恶意代码注入 调 ...

  9. Linux open系统调用流程

    Linux open系统调用流程(1) http://blog.csdn.net/chenjin_zhong/article/details/8452453 1.概述 我们知道,Linux把设备看成特 ...

最新文章

  1. Python+OpenCV 图像处理系列(3)—— 画线、矩形、画圆、画椭圆、画多边形
  2. 只适合小模型小训练集的交叉验证
  3. 写在Silverlight 5发布前 - 盘点2010年Silverlight开发书籍
  4. 【explain】MySQL联表查询中的驱动表
  5. SGS 客户端协议解析
  6. 算法设计与分析——动态规划——最长公共子序列
  7. MM看过来!教你如何打扮变成时尚达人 - 生活至上,美容至尚!
  8. qt 串口 环形缓存_qt linux串口 缓冲区多大
  9. Tomcat 在mac上(Idea)端口冲突解决办法
  10. 情人节:找一个程序员当老公的10大好处
  11. 5 Transforms 转移 笔记
  12. python yield
  13. AutoMapper不用任何配置就可以从dynamic(动态)对象映射或映射到dynamic对象。
  14. 我的大数据之路 -- 猫眼电影再战
  15. Jmeter接口性能测试分布式的环境搭建
  16. 【模仿学习】南京大学港中文联合总结: 29页中文详述模仿学习完整过程
  17. 用简单的Python HTTP server来共享文件
  18. 置信区间的置信区间_什么是置信区间,为什么人们使用它们?
  19. 中国与印度软件工程师的比较
  20. 如何获取网页真实地址

热门文章

  1. (附源码)Springboot电子病历管理 毕业设计 010350
  2. 详解hive的列分隔符和行分隔符的使用
  3. AI中分类算法与聚类算法
  4. 基于 YOLO 的车牌检测与识别
  5. 几个效果比较好的臀部训练动作,帮你更好的锻炼臀部肌肉
  6. 78 bays朴素贝叶斯文本挖掘Chinese
  7. 数据中心供配电怎么监测
  8. 监督分类-支持向量机分类法
  9. 支持向量机之线性可分问题
  10. 小学计算机课每周几节,小学一节课到底几分钟?时间不一样,内容有啥不一样...