Reptile内核模块分析
这篇文章分析一下内核模块部分,这部分主要功能就两个字:隐藏。通过前边的文章,知道了是通过cmd文件控制是否隐藏文件、模块、进程、网络连接等等。
cmd模块是运行在用户层,rep_mod运行在内核层。下边先来看一下他们之间怎么交互
用户层和内核层交互
用户层发送
从cmd文件开始看,这个文件也简单,前边初始化一些变量,后边是几个if循环,有:root权限提升、隐藏或查看当前模块、文件篡改、隐藏或查看tcp连接、隐藏或查看udp连接。
这几个if都差不多,最终都是把需要的变量传递给ioctl()函数。
ioctl(input/output control)是一个在Unix/Linux系统中广泛使用的系统调用,用于控制设备的输入输出操作。通过ioctl调用,程序可以读取或修改设备的特定属性。例如,程序可以通过ioctl获取一个硬件设备的状态,或者设置设备的参数,如波特率、数据位等。
安装的时候,可以知道利用的PulseAudio硬件。PulseAudio 中的 ioctl 函数可以用来配置音频设备,例如更改音量、设置麦克风、扬声器等。
这样,就把用户层的数据传递给了内核层,那么,内核层怎么接收这些数据?
内核层接收
这里打开的是一个socket套接字,肯定会调用inet_ioctl()函数处理。所以在内核模块hook这个函数。确实简单粗暴。
模块隐藏和显示
内核链表
Linux 内核模块链表是一种数据结构,在内核中用于管理模块(即动态加载到内核中的程序段)。内核模块链表是一个双向链表,每个模块都是链表中的一个节点,用于表示模块的信息,例如模块的名称、加载和卸载的函数等。内核模块链表有助于管理和组织内核中的多个模块,从而使得内核更加灵活。
THIS_MODULE 宏是一个内核定义的全局变量,表示当前模块,并且其中包含了当前模块的详细信息,例如名称,版本号,以及链表中的指针。
THIS_MODULE->list表示当前模块的链表元素。是一个指向struct list_head结构体的指针,指向当前模块在链表中的位置。
可以通过next和prev获取当前模块的下一个和上一个元素。
隐藏
隐藏模块功能其实就几行函数,入下,出现的函数都是内核函数和结构体:
THIS_MODULE->list.prev : 当前模块的前一个元素的指针
list_del():是Linux内核链表操作中的一个函数,它可以将一个节点从链表中删除
mutex_trylock():是一个C语言的内核函数,它的作用是尝试获取锁定的互斥锁。具体地,如果指定的互斥锁当前没有被其他进程锁定,则该函数将锁定该互斥锁,并返回0。否则,该函数返回一个非零值,表示该互斥锁已经被其他进程锁定,因此无法获取。
&module_mutex :是一个互斥锁变量的引用,用于标识要获取锁定的互斥锁。
保存前一个元素地址,是为了查看时,复原用到。
显示
只需需要把当前模块添加到链表即可。
网络连接隐藏和显示原理类似模块的隐藏和显示,不展开分析。
进程隐藏和显示
进程标志位
task_struct是内核中代表一个进程的数据结构,它是描述一个进程的详细信息的数据结构。它涵盖了该进程的内存使用情况、状态信息、信号处理信息、调度信息、文件描述符信息等。
task_struct->flags是 Linux 内核中的进程状态标志位。它的数据类型为 unsigned long,是一个二进制的位操作,用于标识进程的状态。不同的内核版本,定义不同。Linux内核版本3.10.0的定义如下:
当标志位正常时,是一个非零的长整型数。
当标志位清零时,ps等命令中将不会显示该进程。
判断是否已隐藏
判断一个进程是否已隐藏,首先看能不能通过进程ID找到内核中进程结构体。其次,判断标志位。进行位于运算,非零说明进程为正常状态,如果为零,则为隐藏状态。
通过操作标志位进行隐藏和显示
Linux C中,按位与取反常用来清零,可以直接用来清零标志位,从而隐藏该进程。|= 运算符常用来合并二进制数,这样可以给标志位已被清零的进程添加标志位,从而显示该进程。
文件内容隐藏和显示
在Linux系统中,要读取文件内容,会调用vfs_read内核函数。需要隐藏文件中指定内容时,hook这个函数,然后对其进程处理。
待隐藏的这块文件内容,已经定义了隐藏开始标志和隐藏结束标志。如果是在文本编辑器中,要隐藏这块内容,直接从隐藏标志开始位置删到隐藏标志结束位置即可。在内存中,就需要操作对应指针,进行相应操作。
过程大致如下
计算出隐藏结束标志到文件末尾的长度,定义为i
把i这块内容往前移动到隐藏开始标志处
现在文件内容的开始到i这块内容的结束,就是去掉待隐藏的文件内容后的全部内容了。
i的长度
把i移动到隐藏开始标志处。此时p1指针指向带隐藏内容的开始,P2指针指向i这块内容的开始。
memmove((void *)p1, (void *)p2, i);
计算出去掉带隐藏内容后的文件内容长度
newret = size - (p2 - p1);
从分配的内存里截取newret这么长的数据,也就是隐藏掉要隐藏的文件内容之后的数据,返回给用户
copy_to_user((void *)arg, (void *)buf, newret)
文件隐藏和显示
Linux系统内核处理文件的几个函数如下:
fillonedir、filldir、filldir64、compat_fillonedir、compat_filldir64
hook这些函数,然后遍历读取的文件列表,匹配要隐藏的文件名,如果匹配到,直接返回0即可。
原文始发于微信公众号(云智信安云窟实验室):LKM Linux rootkit之Reptile分析二
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论