与第一篇文章作者所想象的不同,本文不打算给大家介绍三种不同的系统调用挂钩技术,相反,本文仅详细讲解最简单的系统调用挂钩方案,并且基于这个方案实现最基本的文件监视工具。这样,既可以让读者轻松上手进行实际应用, 又可以加深、巩固读者对LKM 的理解,同时还免去了一次学习多种挂钩方案的理论知识压力。

所以,本文力求以实验为核心,每一个步骤都可能有对应的实验代码。代码仓库: https://github.com/NoviceLive/research-rootkit 。代码在最新的 64 比特 Arch 与Kali 上面测试正常。

测试建议: 不要在物理机测试!不要在物理机测试!不要在物理机测试!

如果读者使用 tmux 或者类似的工具,则可以垂直分割你的终端窗口, 一个窗口开一个 sudo dmesg -C && dmesg -w,用于查看日志; 另一个窗口用来做其他操作,比如构建、加载内核模块。 不用tmux 也没关系,开两个终端,各占半个屏幕。

第一部分:基于修改 sys_call_table 的系统调用挂钩

在系统调用挂钩技术中,最简单、最流行的方案是修改sys_call_table, 成员类型为函数指针的一维数组。

asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {/** Smells like a compiler bug -- it doesn't work* when the & below is removed.*/[0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/syscalls_64.h>
};

要修改它,首先得拿到它在内存里的位置。 然后,由于sys_call_table所在的内存是有写保护的, 所以我们需要先去掉写保护,再做修改。

1. 获得 sys_call_table 的内存地址

在综合考量了几种可选的获取方案之后,笔者决定采用从内核起始地址开始暴力搜索内存空间的方案。 (但是这种方案有可能被欺骗 。)

其他可能的方案有,一,从/boot/System.map 中读取,感兴趣的读者可以查阅 Hooking the Linux System CallTable, 这篇文章便是使用这种方案来获取sys_call_table的地址的。

二,从使用了sys_call_table的某些未导出函数的机器码里面进行特征搜索, 感兴趣的读者可以查阅Kernel-LandRootkits, 作者花了几张 slides 阐述了如何从导出的函数中获取使用了sys_call_table的未导出函数, 进而搜索那个未导出函数的机器码, 得到sys_call_table的地址;等等。

值得指出的是, 感兴趣的读者在测试这些本文未涉及的方案时,如果遇到了疑惑或者困难,也可以与笔者联系、交流。

直接看代码。

unsigned long **
get_sys_call_table(void)
{unsigned long **entry = (unsigned long **)PAGE_OFFSET;for (;(unsigned long)entry < ULONG_MAX; entry += 1) {if (entry[__NR_close] == (unsigned long *)sys_close) {return entry;}}return NULL;
}

PAGE_OFFSET是内核内存空间的起始地址。 因为sys_close是导出函数(需要指出的是, sys_open 、 sys_read 等并不是导出的),我们可以直接得到他的地址;因为系统调用号 (也就是sys_call_table这个一维数组的索引) 在同一ABI (x86跟 x64 不是同一 ABI)上具有高度的后向兼容性,更重要的是,我们可以直接使用这个索引(代码中的 __NR_close )!

从内核内存的起始地址开始, 逐一尝试每一个指针大小的内存:把它当成是sys_call_table的地址, 用某个系统调用的编号(也就是索引)访问数组中的成员,如果访问得到的值刚好是是这个系统调用号所对应的系统调用的地址,那么我们就认为当前尝试的这块指针大小的内存就是我们要找的sys_call_table的地址。

实验效果如图。

2. 关闭写保护

写保护指的是写入只读内存时出错。 这个特性可以通过CR0寄存器控制:开启或者关闭, 只需要修改一个比特,也就是从 0 开始数的第 16个比特。

看代码。我们可以使用read_cr0 /write_cr0 来读取 /写入 CR0 寄存器,免去我们自己写内联汇编的麻烦。

函数原型。

static inline unsigned long read_cr0(void);static inline void write_cr0(unsigned long x);

关闭写保护的源代码:将CR0 寄存器从 0开始数的第 16 个比特置为 0。

void
disable_write_protection(void)
{unsigned long cr0 = read_cr0();clear_bit(16, &cr0);write_cr0(cr0);
}

开启写保护的源代码:将CR0 寄存器从 0开始数的第 16 个比特置为 1。

void
enable_write_protection(void)
{unsigned long cr0 = read_cr0();set_bit(16, &cr0);write_cr0(cr0);
}

在设置或者清除某个比特,我们使用了set_bit与clear_bit。 它们是 Linux 内核提供给内核模块使用的编程接口,简单易懂,同时还免去了我们自己写那种很难读的位运算的痛苦。

函数原型。

static __always_inline void
set_bit(long nr, volatile unsigned long *addr);static __always_inline void
clear_bit(long nr, volatile unsigned long *addr);

实验结果截图。

3. 修改 sys_call_table

一维数组赋值,当之无愧最简单的方案。当然,我们需要先把真正的值保存好,以备后面之需。

disable_write_protection();
real_open = (void *)sys_call_table[__NR_open];
sys_call_table[__NR_open] = (unsigned long*)fake_open;
real_unlink = (void *)sys_call_table[__NR_unlink];
sys_call_table[__NR_unlink] = (unsigned long*)fake_unlink;
real_unlinkat = (void *)sys_call_table[__NR_unlinkat];
sys_call_table[__NR_unlinkat] = (unsigned long*)fake_unlinkat;
enable_write_protection();

4. 恢复

disable_write_protection();
sys_call_table[__NR_open] = (unsigned long*)real_open;
sys_call_table[__NR_unlink] = (unsigned long*)real_unlink;
sys_call_table[__NR_unlinkat] = (unsigned long*)real_unlinkat;
enable_write_protection();

第二部分:基于系统调用挂钩的初级文件监视

监视文件的创建与删除。 我们挂钩sys_open,sys_unlink,sys_unlinkat这三个函数, 并且在我们的钩子函数把操作到的文件名打印出来,然后把控制交给真正的系统调用处理。

1. sys_open 的钩子函数: fake_open

考虑到在系统运行时,对文件的读写操作从未中断,这里只打印了进行创建操作的文件名,准确地说是,sys_open 的 flags中包含 O_CREAT 。

asmlinkage long
fake_open(const char __user *filename, int flags, umode_t mode)
{if ((flags & O_CREAT) && strcmp(filename, "/dev/null") != 0) {printk(KERN_ALERT "open: %s\n", filename);}return real_open(filename, flags, mode);
}

注:这里的strcmp也是内核提供的。

2. sys_unlink 与 sys_unlinkat 的钩子函数: fake_unlink 与 fake_unlinkat

简单处理,直接打印路径名。

asmlinkage long
fake_unlink(const char __user *pathname)
{printk(KERN_ALERT "unlink: %s\n", pathname);return real_unlink(pathname);
}asmlinkage long
fake_unlinkat(int dfd, const char __user * pathname, int flag)
{printk(KERN_ALERT "unlinkat: %s\n", pathname);return real_unlinkat(dfd, pathname, flag);
}

3. 测试我们的文件监视工具

初级的文件监视就到这了,以后我们在做进一步的改进与完善。

效果见下图。

第三部分:参考资料与延伸阅读

1. 参考资料

  • Linux Cross Reference
  • The Linux KernelAPI
  • How the Linux kernel handles a systemcall
  • CR0

2. 延伸阅读

  • Hooking the Linux System CallTable
  • Kernel-LandRootkits

Linux Rootkit 系列二:基于修改 sys_call_table 的系统调用挂钩相关推荐

  1. linux rootkit 端口复用,Linux Rootkit系列三:实例详解 Rootkit 必备的基本功能

    前言鉴于笔者知识能力上的不足,如有疏忽,欢迎纠正. 测试建议: 不要在物理机测试!不要在物理机测试! 不要在物理机测试! 概要 在 上一篇文章中笔者详细地阐述了基于直接修改系统调用表 (即 sys_c ...

  2. Linux Rootkit 系列三:实例详解 Rootkit 必备的基本功能

    本文所需的完整代码位于笔者的代码仓库:https://github.com/NoviceLive/research-rootkit. 测试建议: 不要在物理机测试!不要在物理机测试! 不要在物理机测试 ...

  3. Linux学习系列二:Linux中的常用命令

    这个系列的Linux教程主要参考刘遄老师的<Linux就该这么学>.用的系统是RHEL8,如果遇见一些命令出现问题,请首先检查自己的系统是否一致,如果不一致,可网上查一下系统间某些命令之间 ...

  4. Linux Rootkit 系列四:对于系统调用挂钩方法的补充

    本篇文章按照之前文章所说的,来介绍linux rootkit中的系统调用挂钩技术. 1.背景 本次环境依然是linux 2.6系列内核,ubuntu10.04. 本篇文章及上篇文章的示例代码:Gith ...

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

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

  6. Linux Rootkit 系列五:感染系统关键内核模块实现持久化

    前言 照旧,本文所需的相关代码位于如下代码仓库:Github 测试建议:为了愉快地 Happy Hacking,请不要在物理机玩火. 概要 本文分为两大部分, 第一部分是基于链接与修改符号表感染并劫持 ...

  7. 北京峰会系列二|基于SPDK的UDisk全栈优化

    今天给大家带来的是UCloud基于SPDK的UDisk全栈优化解决方案分享.通过介绍UDisk如何结合SPDK(包括SPDK vhost虚拟化方案)来进行端到端IO路径上的CPU.网络.磁盘性能的优化 ...

  8. 深度学习入门笔记系列 ( 二 )——基于 tensorflow 的一些深度学习基础知识

    本系列将分为 8 篇 .今天是第二篇 .主要讲讲 TensorFlow 框架的特点和此系列笔记中涉及到的入门概念 . 1.Tensor .Flow .Session .Graphs TensorFlo ...

  9. Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

最新文章

  1. python股票自动交易从零开始-python程序化交易编程-python制作自动交易程序!
  2. Hashtable与Dictionary
  3. Java 403 forbidden错误解决
  4. 【工程化】从0搭建VueJS移动端组件库开发框架
  5. CF 1475 D. Cleaning the Phone 思维模型
  6. 恒生估值系统_恒生指数和恒生国企指数投资价值分析
  7. 《iOS 9 开发指南》——第6章,第6.7节iOS 9控件的属性
  8. /bin/sh^M:损坏的解释器: 没有那个文件或目录
  9. 第二个冲刺周期第五天
  10. bert模型可以做文本主题识别吗_BERT模型可以使用无监督的方法做文本相似度任务吗?...
  11. vosviewer保存成PDF文件时没有文字
  12. C++中局部和全局变量的地址分配
  13. 奥维地图电脑端手机端不能用了,有没有可替代的地图工具
  14. 在Linux下安装软件
  15. GitHub每月优秀热门项目推荐:2021年12月
  16. BZOJ 1633: [Usaco2007 Feb]The Cow Lexicon 牛的词典
  17. python requests 乱码_解决使用requests中文乱码
  18. 精选20个高品质的免费素材,可以下载PSD格式
  19. 怎样学好中医,非常值得一读
  20. 软件开发版本 Alpha、Beta、RC、GA

热门文章

  1. Ironic 裸金属管理服务的底层技术支撑
  2. Openstack Nova 源码分析 — RPC 远程调用过程
  3. 自动化办公 Python 操控 Word
  4. 关于坐标系的那些事儿!(转)
  5. 开源网络备份软件bacula(安装bacula)
  6. zz职位是有负面作用的
  7. Wizard 开源文档管理系统1.0发布啦
  8. 节点对象图与DOM树形图
  9. 强生进军医疗机器人、Deepmind利用深度学习算法检查乳腺癌X光,AI医疗的风口已到来?...
  10. dplyr 数据操作 常用函数(2)