其他
栈溢出与 ROP 利用:从覆盖返回地址到任意代码执行
在 Linux 用户态二进制存在栈缓冲区溢出时,通过覆盖返回地址构造 ROP 链、ret2libc、ret2csu、ret2dlresolve 或 SROP 实现控制流劫持。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 47/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 47/100 ×0.15
适用场景
栈溢出与 ROP 利用:从覆盖返回地址到通用
解决思路
- 先确认栈布局:返回地址(x86 4 字节 / x86-64 8 字节)、保存的 EBP/RBP、canary(4/8 字节)、局部变量缓冲区起始位置;x86 参数在栈上,x86-64 依次用 RDI、RSI、RDX、RCX、R8、R9 再走栈;系统调用指令 x86 为 int 0x80,x86-64 为 syscall。
- NX 启用时用 ret2libc:32 位直接布置 payload = 填充 + p32(system) + p32(exit) + p32("/bin/sh");64 位需先用 pop rdi; ret 设置参数,payload = 填充 + p64(pop_rdi) + p64(binsh) + p64(system)。
- 泄露 libc 基址:用 puts@plt(puts@GOT) 泄露已解析地址、write@plt(1, read@GOT, 8) 走 write 系统调用、printf("%s", GOT_entry) 走格式化字符串,或在 PIE 下用部分覆盖低字节跳到泄露 gadget;典型模式为 rop = 填充 + p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(main),接收后 libc_base = leak - libc.symbols['puts']。
- 用 one_gadget 找单 gadget RCE:执行 one_gadget /path/to/libc.so.6,使用前必须满足其 constraints(如 rsp & 0xf == 0、rcx == NULL、[rsp+0x40] == NULL 等),检查寄存器与栈状态。
- 选择 ROP 工具:ROPgadget --binary elf --ropchain 自动生成链、ropper -f elf --search "pop rdi" 语义搜索、pwntools 的 rop = ROP(elf); rop.call('system', ['/bin/sh']) 自动构链、xrop -r elf 快速搜索。
- 掌握关键 gadget 模式:pop rdi; ret 设 arg1、pop rsi; pop r15; ret 设 arg2、pop rdx; ret(少见,三参改用 ret2csu)、syscall; ret 直接系统调用、leave; ret 栈迁移、单个 ret 用于对齐。
- 注意 x86-64 栈对齐:system() 等 libc 函数使用 movaps 要求 RSP % 16 == 0,若不对齐则在调用前插入一个额外的 ret gadget。
- 无 pop rdx 时用 ret2csu:利用 __libc_csu_init 的 gadget1(pop rbx/rbp/r12/r13/r14/r15; ret)与 gadget2(mov rdx,r13; mov rsi,r14; mov edi,r15d; call [r12+rbx*8]; add rbx,1; cmp rbp,rbx; jne loop);r12 必须指向目标函数的指针(如 GOT 项)而非函数地址本身,设 rbx=0、rbp=1 跳过循环;注意 edi 只写 32 位。
- 无 libc 泄露时用 ret2dlresolve:控制执行流调用 _dl_runtime_resolve(link_map, reloc_offset),在已知可写地址伪造 Elf_Rel 指向伪造的 Elf_Sym,Elf_Sym 的 st_name 指向伪造字符串 "system\x00",设置 reloc_offset 使解析器使用伪造结构,参数 /bin/sh 放在栈或已知缓冲区;可用 pwntools 自动化:dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["/bin/sh"]); rop.read(0, dlresolve.data_addr); rop.ret2dlresolve(dlresolve)。
- 区分 ret2dlresolve 的 32/64 位差异:32 位用 Elf32_Rel(8 字节)与 Elf32_Sym(16 字节)、对齐宽松;64 位用 Elf64_Rela(24 字节)与 Elf64_Sym(24 字节)、对齐严格(须满足 ndx = reloc_offset / sizeof(Elf64_Rela),sym = symtab[ndx]),且 VERSYM[sym_index] 必须有效或为 0。
- gadget 受限时用 SROP:构造 SigreturnFrame 设置 rax=SYS_execve(59)、rdi=binsh、rsi=0、rdx=0、rip=syscall_ret、rsp=新栈地址,payload = 填充 + p64(pop_rax_ret) + p64(15) + p64(syscall_ret) + bytes(frame);适用于 gadget 少、无 pop rdx、静态二进制或需任意栈迁移的场景。
- 溢出长度受限时做栈迁移:leave; ret(mov rsp,rbp; pop rbp; ret)需控制保存的 RBP 指向伪造栈;xchg rsp, rax; ret 需控制 RAX;pop rsp; ret 直接控制 RSP;SROP 迁移只需 sigreturn gadget。leave;ret 模式为溢出 [AAAA...][fake_rbp → buf][leave_ret_addr],第一次 leave 使 rsp=rbp→fake_rbp 并 pop rbp,ret 跳到 leave_ret,第二次 leave 使 rsp=buf+8 并 pop rbp,ret 跳到 *(buf+8) 即 buf 中的 ROP 链起点。
- 绕过 canary:fork() 服务可逐字节爆破(64 位 256×7=1792 次尝试);有 printf(user_input) 时用 %N$p 泄露;单字节溢出或部分读时覆盖 canary 空字节再通过错误/输出读取;溢出可达 TLS 时同时覆盖 fs:[0x28] 的 stack_guard;利用未初始化栈变量泄露。
- 常用命令速查:checksec ./binary 查看保护(NX、canary、PIE、RELRO);ROPgadget --binary ./binary --ropchain 自动生成链;ropper -f ./binary --search "pop rdi" 语义搜索;one_gadget ./libc.so.6 找一发 RCE;pwn template ./binary --host x --port y 生成 pwntools 骨架。
- 按决策树选择路线:NX 关闭→栈上 shellcode 并 ret 到缓冲区(ret2shellcode);NX 开启且有 canary→fork 爆破 / 格式化字符串泄露 / 信息泄露读取;无 canary 进入 ROP;PIE→泄露代码基址(部分覆盖低 12 位或信息泄露),仅 ASLR→泄露 libc 基址(puts@GOT、write@GOT),都无→地址已知直接 ROP;能泄露 libc→ret2libc 或 one_gadget,不能→ret2dlresolve 或 SROP;需 3 个以上参数且无 pop rdx→ret2csu 或 SROP;溢出太短→栈迁移;静态二进制无 libc→SROP + syscall 链;Full RELRO→不能改 GOT,改打 __free_hook、__malloc_hook 或 _IO_FILE vtable。
适用边界 · 注意事项
- 目标漏洞位于堆而非栈时,应改用堆利用技术而非本技能。
- 需要先通过格式化字符串泄露 canary/libc/PIE 基址时,应先使用格式化字符串利用技能。
- 需要系统性绕过 NX、ASLR、PIE、canary、RELRO 组合保护时,应参考二进制保护绕过技能。