写在前面在上一篇中我们建立了信息泄露的系统化方法论并学习了 ORWOpen-Read-Write的基础 ROP 构造。然而出题人的防守并非一成不变。当简单的open也被沙箱拦截时基础的 ORW 链便会失效。今天我们将深入剖析 Linux 的沙箱机制学习如何使用seccomp-tools逆向分析规则并掌握进阶的 ORW 绕过手法如openat与/proc/self/mem魔法。 目录沙箱溯源Seccomp 与 BPF 机制简介破译密码使用seccomp-tools分析沙箱规则进阶 ORW当open被禁时的openat替换深水区绕过/proc/self/mem魔法读取总结与下篇预告1. 沙箱溯源Seccomp 与 BPF 机制简介在 Linux 中SeccompSecure Computing Mode是一种用于限制进程可用系统调用的内核机制。现代 CTF 中的沙箱通常基于prctl(PR_SET_NO_NEW_PRIVS, 1)和seccomp(SECCOMP_MODE_FILTER, ...)实现。其核心是BPF (Berkeley Packet Filter)。BPF 最初用于网络数据包过滤后来被引入到系统调用过滤中。出题人会编写一段 BPF 字节码告诉内核“如果进程请求的系统调用号是 X则放行如果是 Y则杀死进程”。由于 BPF 规则是以字节码形式加载到内核的我们在用户态无法直接 patch 掉它。因此我们必须先逆向分析这段字节码找出它的“盲区”。2. 破译密码使用seccomp-tools分析沙箱规则手动逆向 BPF 字节码极其痛苦。感谢安全社区我们拥有神器seccomp-tools。假设题目附件是./pwn我们执行seccomp-tools dump ./pwn它会将内核中的 BPF 规则反编译为易读的伪代码。我们经常会看到如下输出场景 A基础沙箱只禁 execveline 1: ALLOW syscalls: open, read, write, mmap, mprotect... line 2: KILL syscalls: execve (59), execveat (322)*应对策略*使用上一篇讲的基础 ORW 即可。场景 B进阶沙箱禁用 openline 1: ALLOW syscalls: read, write, mmap, mprotect... line 2: KILL syscalls: execve, open (2)*应对策略*open被杀但openat往往幸存。场景 C地狱级沙箱禁用所有 open 家族line 1: ALLOW syscalls: read, write, mmap, mprotect... line 2: KILL syscalls: execve, open (2), openat (257)*应对策略*无法打开新文件但可以利用/proc/self/mem配合write实现任意地址读写。3. 进阶 ORW当open被禁时的openat替换open的系统调用号是 2而openat是 257。它们的功能几乎一样区别在于openat需要额外指定一个目录文件描述符dirfd。3.1 openat 的函数原型int openat(int dirfd, const char *pathname, int flags);如果pathname是绝对路径如/flag则dirfd参数会被忽略。因此我们只需将dirfd设置为任意值通常设为AT_FDCWD(-100) 或 0即可完全等价于open。3.2 ROP 链修改只需将基础 ORW 中的open部分替换为openat// openat(/flag, 0) - sys_openat 257 pop rdi; ret; -100; // rdi AT_FDCWD (-100) pop rsi; ret; bss_addr; // rsi /flag 字符串地址 pop rdx; ret; 0; // rdx O_RDONLY pop rax; ret; 257; // rax 257 (sys_openat) syscall; ret;后续的read和write完全不变。这就是为什么出题人必须把open和openat一起禁掉否则沙箱形同虚设。4. 深水区绕过/proc/self/mem魔法读取当open和openat全部阵亡我们无法获取新的文件描述符。但幸运的是程序启动时默认打开了三个流stdin(0),stdout(1),stderr(2)。更重要的是Linux 提供了一个特殊的虚拟文件/proc/self/mem。4.1 核心思想/proc/self/mem是当前进程内存的镜像。对这个文件进行lseek和read/write等同于直接读写进程自身的内存然而/proc/self/mem并不是一个常规文件它无法直接被open打开即使没禁用 open也可能因为权限问题失败。但它通常已经被打开了在文件描述符表中吗不它没有。等等如果连openat都不能用怎么打开它4.2 终极魔法利用write绕过限制如果沙箱允许openat我们可以打开/proc/self/mem。但如果连openat都禁了呢此时如果允许write和lseek系统调用号 8我们通过 ROP 调用openat打开/proc/self/mem… 不行禁用了。真正的魔法无 open 场景如果题目允许write且我们有一个指向 libc 中__free_hook或类似可写区域的指针我们可以直接写入 shellcode不NX 开启。修正魔法结合 mprotect如果沙箱允许mprotect和write且允许open但不允许openat这太矛盾了。真实的/proc/self/mem利用场景通常发生在允许open或openat但不允许直接读flag例如通过正则过滤了路径名或者read被限制只能读取特定 fd。更极端的场景允许openat打开/proc/self/mem然后利用lseek偏移到目标内存再用write覆盖内存具体流程openat(/proc/self/mem, O_RDWR)- 返回 fd 3lseek(3, target_addr, SEEK_SET)- 将文件指针移动到我们想写的目标地址如__free_hook或栈上的返回地址write(3, payload, len)- 将 payload 写入目标地址这种手法可以绕过某些对write系统调用参数有严格检查的沙箱因为我们写的“文件”是内存本身。4.3 如果连 openat 都没有只有 read/write 怎么办这是最极端的无 libc 场景或极严沙箱。通常需要利用mprotect将内存改为可执行然后写入 shellcode 执行这需要mprotect未被禁。如果mprotect也被禁则可能需要利用内核漏洞超出本周讨论范围。5. 总结与下篇预告5.1 核心知识点总结Seccomp 与 BPF理解沙箱的底层原理沙箱规则是加载到内核的用户态无法绕过只能寻找规则的盲区。seccomp-tools实战必备工具打题第一步必先 dump 沙箱规则。openat替换最基础的绕过姿势利用dirfd AT_FDCWD完美替代open。/proc/self/mem将内存视为文件通过lseekwrite实现极其隐蔽的任意地址写绕过对系统调用参数的直接检查。5.2 下篇预告在解决了沙箱与 ORW 之后下一篇我们将转向另一个实战痛点无 Libc 环境与ret2mprotect进阶应用。当题目不给 libc且远程环境未知时如何利用ret2dlresolve或ret2mprotect破局如何在没有pop rdx; ret等 gadget 时利用__libc_csu_init构造万能 ROPgot2plt劫持在 Partial RELRO 下的妙用。结语沙箱不是不可逾越的高墙而是一道带缝隙的过滤网。出题人受限于系统调用之间的依赖关系永远无法完全封死读写内存的途径。掌握openat和/proc/self/mem你就掌握了在沙箱中“穿墙”的咒语。