Linux Rootkit系列一:LKM的基础编写及隐藏
免责声明:本文介绍的安全知识方法以及代码仅用于渗透测试及安全教学使用,禁止任何非法用途,后果自负
前言:作者最近在学习有关linux rootkit的原理与防范,在搜索资料中发现,在freebuf上,对rootkit进行介绍的文章并不是很多。在此我斗胆献丑,总结了下我最近的学习收获,打算发表一系列关于linux rootkit的文章在freebuf上,希望能够帮助到大家。
对于这个系列文章,我的规划如下:这一系列文章的重点集中在介绍linux rootkit中最讨论最多也是最受欢迎的一种:loadable kernel module rootkit(LKM rootkit)。
首先介绍最基础的lkm模块的编写与加载以及如何让lsmod命令无法发现我们的模块(也就是本文的内容),然后是介绍lkm rootkit中最重要的技术,系统调用挂钩,我将会给大家介绍三种不同的系统调用挂钩技术,以便于在不同的场景中选择最恰当的一种。接下来便是系统实战,使用我们之前的知识来进一步完善我们的rootkit,包括如何隐藏进程,隐藏端口,彻底隐藏lkm,以及如何向现有的系统LKM模块注射我们的代码来改造成我们自己的lkm模块。
LKM(可加载内核模块)
LKM的全称为Loadable Kernel Modules,中文名为可加载内核模块,主要作用是用来扩展linux的内核功能。LKM的优点在于可以动态地加载到内存中,无须重新编译内核。由于LKM具有这样的特点,所以它经常被用于一些设备的驱动程序,例如声卡,网卡等等。当然因为其优点,也经常被骇客用于rootkit技术当中。
1.基本的LKM的编写
下面是一个最基本的LKM的实现,接下来我会对这个例子进行讲解
/*lkm.c*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int lkm_init(void)
{
printk("Arciryas:module loadedn");
return 0;
}
static void lkm_exit(void)
{
printk("Arciryas:module removedn");
}
module_init(lkm_init);
module_exit(lkm_exit);
这个程序并不是很复杂:其中我们的lkm_init()是初始化函数,在该模块被加载时,这个函数被内核执行,有点构造函数的感觉;与之相对应的,lkm_init()是清除函数,当模块被卸载时,内核将执行该函数,有点类似析构函数的感觉,注意,如果一个模块未定义清除函数,则内核不允许卸载该模块。
为什么我们的初始化与清除函数中,使用的是printk()函数,而并非是我们熟悉的printf()函数呢?注意下我们这个程序包含的头文件,在LKM中,是无法依赖于我们平时使用的C库的,模块仅仅被链接到内核,只可以调用内核所导出的函数,不存在可链接的函数库。这是内核编程与我们平时应用程序编程的不同之一。printk()函数将内容纪录在系统日志文件里,当然我们也可以用printk()将信息输出至控制台:
printk(KERN_ALERT "output messages");
其中KERN_ALERT指定了消息的优先级。
module_init和module_exit是内核的特殊宏,我们需要利用这两个特殊宏告诉内核,我们所定义的初始化函数和清除函数分别是什么。
代码的描述就到这里,接下来我们需要对我们的LKM程序进行编译,下面是编译所需的Makefile:
obj-m := lkm.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
接下来我们键入make命令开始编译,除去编译的中间产物外,我们仅仅需要的是lkm.ko。
装载LKM我们需要insmod命令。键入
insmod lkm.ko
回车,这时你会发现什么都没有发生,没有关系,这是因为我们并没有对于我们的消息指定KERN_ALERT优先级,此时printk将消息传输到了系统日志syslog中,我们可以在/var/log/messages中查看,当然,在不同的发行版以及不同的syslog配置中,该文件的路径不同。
我们可以cat /var/log/messages或者利用dmesg命令查看printk输出的消息,如下图所示:
为了方便起见我只显示了最后一条信息,也就是我们LKM中初始化函数所输出的信息。
我们再输入lsmod命令查看我们的模块。lsmod命令的作用是显示已载入系统的模块。如下图:
其中lkm当然是我们的模块名称,676则代表的是模块大小,0表示模块的被使用次数。有兴趣的同学可以自己试试lsmod命令查看下系统所加载的其他模块。
OK,现在我们可以对我们的LKM进行卸载了,卸载LKM的命令是rmmod。键入
rmmod lkm.ko
后,我们再查看下系统日志:
可以看出清除函数中的信息也成功输出,这时再试试lsmod命令,你会发现我们的模块在其中不复存在了。
2.从lsmod命令中隐藏我们的模块
现在有个小问题,如果我们既不想让dmesg也不想让lsmod这两个命令察觉到我们的模块呢?对于rootkit来说,隐蔽性是非常重要的,一个lsmod命令就可以让我们的lkm遁形,这显然谈不上隐蔽。对于dmesg命令,我们只要删除掉printk()函数就好,这个函数所起的仅仅是示范作用。但是如何让lsmod命令无法显示我们的模块呢。
在这里我简单介绍下lsmod原理,以便于读者理解之后我是如何在lsmod命令中隐藏我的模块的。lsmod命令是通过/proc/modules来获取当前系统模块信息的。而/proc/modules中的当前系统模块信息是内核利用struct modules结构体的表头遍历内核模块链表、从所有模块的struct module结构体中获取模块的相关信息来得到的。结构体struct module在内核中代表一个内核模块。通过insmod(实际执行init_module系统调用)把自己编写的内核模块插入内核时,模块便与一个 struct module结构体相关联,并成为内核的一部分,所有的内核模块都被维护在一个全局链表中,链表头是一个全局变量struct module *modules。任何一个新创建的模块,都会被加入到这个链表的头部,通过modules->next即可引用到。为了让我们的模块在lsmod命令中的输出里消失掉,我们需要在这个链表内删除我们的模块:
list_del_init(&__this_module.list);
list_del_init函数定义于include/linux/list.h中,我们可以看下它的实现:
static inline void list_del_init (struct list_head * entry)
{
__list_del (entry->prev, entry->next);
INIT_LIST_HEAD (entry);
}
static inline void __list_del (struct list_head * prev, struct list_head * next)
{
next-> prev = prev;
prev-> next = next;
}
static inline void INIT_LIST_HEAD (struct list_head * list)
{
list-> next = list;
list-> prev = list;
}
现在我们将"list_del_init(&__this_module.list);"加入到我们的初始化函数中,保存,编译,装载模块,再输入lsmod,这时你会发现,输出中我们的模块已经找不到了,我们在lsmod命令中成功的隐藏了我们的模块!
3.从sysfs中隐藏我们的模块
当然我们还不能高兴的太早,除了lsmod命令和相对应的查看/proc/modules以外,我们还可以在sysfs中,也就是通过查看/sys/module/目录来发现现有的模块。
这个问题也很好解决,在初始化函数中添加一行代码即可解决问题:
kobject_del(&THIS_MODULE->mkobj.kobj);
THIS_MODULE在include/linux/module.h中的定义如下
extern struct module __this_module;
#define THIS_MODULE (&__this_module)
可以看出THIS_MODULE的作用是指向当前模块。&THIS_MODULE->mkobj.kobj则代表的是struct module结构体的成员struct module_kobject的一部分,结构体的定义如下:
struct module_kobject{
struct kobject kobj;
struct module *mod;
};
其中kobj是一个struct kobject结构体,而kobject是组成设备模型的基本结构。这时我们又要简单介绍下sysfs这个概念,sysfs是一种基于ram的文件系统,它提供了一种用于向用户空间展现内核空间里的对象、属性和链接的方法。sysfs与kobject层次紧密相连,它将kobject层次关系表现出来,使得用户空间可以看见这些层次关系。通常,sysfs是挂在在/sys目录下的,而/sys/module是一个sysfs的一个目录层次, 包含当前加载模块的信息. 我们通过kobject_del()函数删除我们当前模块的kobject就可以起到在/sys/module中隐藏lkm的作用。
好了,这时再将"kobject_del(&THIS_MODULE->mkobj.kobj);"也添加在初始化函数里,保存,编译,装载模块,然后再去看看/sys/module,是不是什么也看不到了?
结语
对于lkm的入门以及lkm的简单隐藏办法已经介绍完了,但是这只是通向lkm rootkit的长征路上第一步,在下次的文章中,我会介绍lkm rootkit编写中最为关键的技术:system call hook,也就是系统调用挂钩技术。
参考资料
关于lkm的编写,《linux设备驱动程序(第三版)》的第二章"构造和运行模块"里有基础的讲解。 关于proc和sysfs文件系统,可以参考《深入linux内核架构》中的第十章"无持久存储的文件系统"。
作者gmail邮箱:arciryas.yang
[作者/arciryas,本文属FreeBuf黑客与极客(Freebuf.COM)独家发布,未经允许禁止转载]
- 通过预测API窃取机器学习模型
- 【自然框架】 页面里的父类——把共用的东东都交给父类,让子类专注于其他。
- 血淋林的例子告诉你,为什么防“上传漏洞”要用白名单
- 关于Int自增字段和GUID字段的性能测试。只有测试,没有分析,呵呵
- 【自然框架】 之 资源角色——列表过滤方案(思路篇)
- UVM(七)之phase及objection
- 【自然框架】 之 主从表的添加、修改
- HLS Lesson6-数据类型转换
- 某开源框架从注入到Getshell
- HLS Lesson4-例子
- Docker初探(一)-有关docker的介绍和简单使用
- MySQL绕过WAF实战技巧
- 注意了,使用XSS平台的你可能被“偷窥”
- 挖洞经验 | 命令注入突破长度限制
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Devtools 老师傅养成[6] - Performance 面板
- RecyclerView上拉加载封装代码
- Devtools 老师傅养成[7] - Memory 内存
- Android实现简单的城市列表功能
- Android Animation之TranslateAnimation(平移动画)
- Android自定义View实现箭头沿圆转动实例代码
- Android 中Context的使用方法详解
- Android自定义水平渐变进度条
- Android+SQLite数据库实现的生词记事本功能实例
- Android 设置颜色的方法总结
- Android支付宝支付的示例代码
- 试图解释清楚【JavaScript Event Loop】
- 结合Event Loop谈谈对Vue中nextTick的理解
- Vue3响应式原理
- ClickHouse和他的朋友们(1)编译、开发、测试