写在前面欢迎进入 Week14 的学习在过去的几周里我们深潜了 glibc 堆管理器Week11-12和 IO_FILE 机制Week13掌握了极其强大的内存破坏与控制流劫持手段。然而无论多精妙的利用链都建立在一个绝对前提之上我们需要知道东西在哪Libc 基址、堆基址、栈基址。本周我们将跳出单一漏洞类型的局限转向系统化的漏洞利用工程学。今天第一篇将为你梳理信息泄露的系统化方法论并以此为基础引入现代 CTF 绝对的高频考点——ORWOpen-Read-Write任意文件读取体系。 目录破局之基为什么信息泄露是 PWN 的生命线 信息泄露系统化 Checklist实战速查表沙箱降临为什么execve逐渐失效ORW 基础架构从系统调用到 ROP 链构造总结与下篇预告1. 破局之基为什么信息泄露是 PWN 的生命线现代操作系统普遍开启了 ASLR地址空间随机化、PIE程序代码段随机化和 NX不可执行栈。在没有泄露的情况下我们手里的漏洞如栈溢出、UAF就像是黑夜中拿着一张藏宝图但既不知道自己在哪也不知道宝箱在哪。不知道 Libc 基址找不到system和/bin/sh无法 ret2libc。不知道堆基址无法计算 Tcache 异或密钥Safe-Linking无法精准伪造堆块。不知道栈基址无法进行栈迁移无法将 ROP 链写到正确位置。因此打 PWN 的第一步永远是寻找泄露原语。掌握如何从各种残缺的内存环境中“榨取”地址是成熟 PWN 选手的基本素养。2. 信息泄露系统化 Checklist实战速查表强烈建议将本节内容保存为你的个人 Checklist打题时按图索骥。2.1 泄露 Libc 地址触发场景 / 漏洞类型泄露目标原理与操作Unsorted Bin 残留main_arena88释放一个大于 Fastbin/Tcache 大小的 chunk其fd和bk指向 Unsorted Bin 链表头。通过 UAF 或 Show 功能读取。GOT 表读取libc 函数真实地址程序若有puts/printf配合格式化字符串或任意读直接读取 GOT 表中已解析的函数如putsgot查 Libc 数据库。stdout低位覆盖libc 内部地址溢出覆盖_IO_2_1_stdout_的_IO_write_base低位触发puts打印出 libc 数据段残留的指针。Partial Overwrite无需泄露直接爆破/偏移在不知道 Libc 版本时只覆盖返回地址末尾 1-2 字节跳转到 libc 中的one_gadget或特定代码段需配合环境搭建。2.2 泄露栈 地址触发场景 / 漏洞类型泄露目标原理与操作格式化字符串 (%p)栈上任意数据printf(fmt)的变参在栈上通过%p或%N$p依次泄露栈上的返回地址、旧 RBP 等。environ指针读取栈基址libc 全局变量environ存放了环境变量数组的栈地址。通过任意读读取environ的值即可算出当前栈帧位置。栈残留数据旧 RBP / 返回地址程序再次调用 read/gets 时若缓冲区未清空残留的旧栈帧数据可能被随后的puts打印出来。2.3 泄露堆 地址触发场景 / 漏洞类型泄露目标原理与操作Tcache / Fastbin 残留堆块地址释放单个 chunk 进入 Tcache/Fastbin其fd指针指向下一个堆块或 NULL。通过 UAF 读取。Safe-Linking 泄露堆基址 12glibc 2.32 中Tcache 的fd被加密为(chunk_addr 12) ^ next_ptr。若链表仅有一个 chunkfd即为堆基址的明文右移。Largebinfd_nextsize堆块地址Largebin 中的 chunk 含有fd_nextsize和bk_nextsize指针必定指向堆区UAF 读取即可。2.4 无 Libc 环境的特殊泄露DynELF 技术通过puts或write的任意读漏洞不断泄露内存暴力搜索 ELF 动态链接结构远程还原 libc耗时极长但有效。延迟绑定机制攻击通过覆盖.got.plt的特定字节强制触发_dl_runtime_resolve泄露动态链接器内部地址。3. 沙箱降临为什么execve逐渐失效在掌握信息泄露后我们通常的目标是执行system(/bin/sh)或execve(/bin/sh, NULL, NULL)。这对应着系统调用号59。然而在 2020 年以后的 CTF 赛事中出题人越来越频繁地引入Seccomp (Secure Computing Mode)沙箱保护。Seccomp 通过 BPF伯克利包过滤器在内核态拦截系统调用。常见的沙箱规则line 1: ALLOW syscalls: open, read, write, mmap, mprotect... line 2: KILL syscalls: execve, fork, clone...一旦execve被拦截程序直接被内核 SIGKILL。我们精心构造的one_gadget或system全部失效。出路何在既然不能执行 shell我们就自己写代码把服务器上的flag文件读出来打印到屏幕上。这就是ORW。4. ORW 基础架构从系统调用到 ROP 链构造ORW 是 Open-Read-Write 的缩写是一套标准的文件读取流程open(/flag, 0)- 返回文件描述符fd(通常是 3)read(3, buf, 0x100)- 将 flag 内容读到可写内存buf(如 bss 段或堆)write(1, buf, 0x100)- 将buf内容写到标准输出 (fd1)4.1 构造 ORW 的前置条件能够执行 ROP栈溢出或者堆漏洞结合setcontext/IO_FILE 实现栈迁移。知道 Libc 地址用于寻找syscall; ret等 gadget以及/flag字符串或自己写入。控制寄存器能够控制rdi, rsi, rdx, rax这四个关键寄存器。4.2 ROP 链构造模板 (x64)假设我们已经能控制栈并执行 ROP且已知 Libc 基址。我们需要以下 Gadgetspop rdi; retpop rsi; retpop rdx; ret(如果没有可使用__libc_csu_init中的万能 gadget)pop rax; ret(用于设置系统调用号)syscall; ret完整 ROP 链伪代码// 假设 bss_addr 是一块可写内存用于存放 /flag 字符串和读取的数据 // open(/flag, 0) - sys_open 2 pop rdi; ret; bss_addr; // rdi /flag 字符串地址 pop rsi; ret; 0; // rsi O_RDONLY pop rax; ret; 2; // rax 2 (sys_open) syscall; ret; // read(3, bss_addr0x100, 0x100) - sys_read 0 pop rdi; ret; 3; // rdi fd (open返回值为3) pop rsi; ret; bss_addr 0x100; // rsi 缓冲区地址 pop rdx; ret; 0x100; // rdx 读取长度 pop rax; ret; 0; // rax 0 (sys_read) syscall; ret; // write(1, bss_addr0x100, 0x100) - sys_write 1 pop rdi; ret; 1; // rdi fd (stdout) pop rsi; ret; bss_addr 0x100; // rsi 缓冲区地址 pop rdx; ret; 0x100; // rdx 写入长度 pop rax; ret; 1; // rax 1 (sys_write) syscall; ret;漏洞触发: 栈溢出/堆劫持栈迁移到可控内存布置 ROP 链open 打开 /flagread 读入到 bss/heapwrite 打印到屏幕获取 Flag4.3 常见难点与对策没有/flag字符串ROP 链前面加上read(0, bss_addr, 8)自己从键盘输入/flag\x00写入 bss 段。没有pop rdx; ret利用libc_csu_init中的mov rdx, r15; call [r12]进行复杂调用或者利用read函数本身因为read的第三个参数在调用前可能残留了合适的值。没有syscall如果只禁用了execve而没有禁用open可以直接调用 libc 中的open函数。5. 总结与下篇预告5.1 核心知识点总结信息泄露是地基牢记 Unsorted Bin 泄露 Libc、environ泄露栈、Tcache 残留泄露堆的系统化方法论。沙箱逼出 ORWSeccomp 禁用execve是现代出题趋势必须掌握 ORW 体系。ORW 的本质通过控制寄存器执行open/read/write三个系统调用核心在于对pop rdi/rsi/rdx/rax; syscall链的拼装。5.2 下篇预告在下一篇中我们将深入探讨沙箱的分析与绕过技术。如何使用seccomp-tools工具逆向分析题目的沙箱规则。进阶 ORW如果open也被禁用了怎么办利用openat或/proc/self/mem魔法。ret2mprotect进阶应用在无libc环境下绕过 NX 执行 shellcode。结语信息泄露 Checklist 是你的“寻宝图”ORW 是你的“破墙锤”。当你把这两者烂熟于心时无论题目套了多少层保护你都能找到一条通往 flag 的物理路径。