题目内容
IDA 分析,经典的堆题,保护全开,四个选项增删改查。
add:
1 | __int64 add() |
限制最大分配字节数0x1000,然后用ptr(全局变量)保存返回的堆指针,diary_size(全局变量)保存我们输入的大小。没有漏洞。
edit:溢出8字节
1 | __int64 edit() |
可以修改ptr指向的区域的对应内容,观察检查,允许我们超出diary_size 8个字节写,存在小规模的堆溢出,正好可以覆盖size域,修改邻近高地址chunk的size。
delete:UAF
1 | __int64 delete() |
释放ptr指向的chunk。free_once是一个静态的变量,值为1,也就是说程序只允许我们free一次。另外存在UAF,ptr在free后不置空。
show:
1 | __int64 show() |
打印ptr指向区域diary_size大小的内容,同理,只允许我们泄露一次。
考虑每次分配都会写一次ptr和diary_size,也就意味着程序只能UAF和读取最近一次分配的堆块。
思路
题目名字都提示了,肯定是要用一次house of orange的。我们必须好好利用仅有的一次释放和一次读机会。
我们能够启动攻击的入手点就在那个8字节的溢出和最近分配chunk的UAF。溢出字节只有8写不了fd和bk指针,很多攻击手段就失效了。最近分配chunk的UAF操作和唯一一次free大概率是要用于getshell的任意地址写,所以联想题目名字,泄露地址肯定是house of orange后分配chunk再读。
然后getshell使用一个能任意地址分配的方法,思路采用fastbin attack将chunk 分配到__malloc_hook
附近写为onegadget即可。
操作过程和问题解决
House of Orange与fencepost chunk
第一步用house of orange将原本的top放进unsorted bin中
1 | #--------------House of Orange---------------------- |
奇怪的是,这里动调的时候发现在申请0x1000的chunk后,原来的旧top chunk最高地址处被切了两个0x10的小chunk出来,同时放入unsorted bin的旧top的size也减少了0x20。
这里是因为sysmalloc
函数的缘故。这个函数在处理旧top的时候插入了两个2*SIZE_SZ的围栏(fencepost)边界块,用来防止不同内存区域的错误合并。libc源代码的操作如下:
1 | if (old_size != 0) |
注意上面的两次对chunk_at_offset的size的写入,在释放old_top前创建了两个fencepost块,我们也不用过多管是出于什么目的,反正能对上源码,知道这是sysmalloc在释放oldtop前做的操作就可以了
unsortedbin leak libc,切割old top
这里的地址泄露困扰了我很久这时,unsorted bin里的旧top的fd和bk指向main_arena+88的偏移位置,由于我们等下要用UAF来实现任意地址分配,所以这里的泄露只能通过从旧chunk中切一个新的再读来实现。我们随便分配一个差不多大小的chunk,malloc会从这个unsorted bin中的old_top切一块给我们。然后读一下内容就行了。
1 | add(0x38, b'1')# 将old top 取出来,里面此时有main_arena地址信息 fd末尾写成0x31 |
但是很抽象的是,读取出来的地址并不对,我们能发现读出来的chunk有四个字段都被写了,fd_nextsize和bk_nextsize也被写了。其fd和bk存储的内容发生了变化,不是main_arena+0x58。而被切后仍然留在unsorted bin中的old_top,其fd和bk仍然不变。这是为什么?我们怎么利用读出来的这个地址?
动调了一下,结合ida反汇编的libc文件对照汇编代码以及libc源码终于发现是什么问题了。我们知道malloc在发现fastbin和smallbin不满足的时候会进入一个大循环处理unsorted bin中的chunk,在这里有猫腻。
现在的old_top显然是一个large chunk,因此malloc在循环处理中先将它整个放进了large bin,在其中发生了写操作,将fd和bk,fd_nextsize和bk_nextsize都重新写了。也就是我们后面读出来的fd和bk其实是指向BINS数组中相应大小largebin的位置。操作如下(largebin为空的情况下):
1 | ...//在largebin range内的操作 |
这段操作和我们动调看到的也一致————切出的chunk具有fd_nextsize和bk_nextsize字段且都指向自己。所以malloc还给我们的chunk的内容不是main_arena+0x58.
那怎么办呢?虽然不是0x58但还是指向main_arena内部,所以看一下偏移就好了。动调看一下刚刚的remainder(被放回unsorted bin)的fd和bk,他们是从原来copy过去的肯定还是指向main_arena+88, 作差确定偏移是0x610即可。
remainder的fd和bk
1 | remainder = chunk_at_offset (victim, nb); |
fastbin attack: alloc 2 malloc_hook
拿到了libc基地址随便嗯造就行。这里我们要把一个fastbin大小的堆块分配在malloc_hook附近。但是光改fd不够,我们要在malloc_hook低地址处找到一个合法的size域来构建我们的phantom_chunk.
这其实有个技巧基础,因为libc的基地址大多以0x7f打头,因此我们可以利用字节错位+小端序的特点来找到一个合适的0x7f的size域来通过malloc对fastbin分配的检查(只检查size和第一个是否double free)。动调看一下内存,果然有(见下方exp注释部分)。这个0x7f在mallochook-0x13的位置,算上prev_size,要减0x23。
分配一个真实size 0x70大小的chunk(申请0x68),然后UAF写其fd为malloc_hook-0x23
,连续申请两次0x68大小的chunk,第二次申请出的就是malloc_hook附近的chunk了。接下来直接edit(0x13是因为malloc肯定返回的是跳过size的可写部分),写入one_gadget后随便申请一个chunk就能getshell了。
1 | # fast bin attack |
技巧总结:
-
溢出长度足够8字节的情况下,house of orange的一次额外free机会
-
leak libc地址,malloc不清空chunk内容,但是要注意fd和bk写的是什么,对main_arena+88算下偏移
-
fastbin attack的利用是基于在malloc_hook附近申请并分配一个0x70大小chunk(利用0x7f的字节错位来伪造一个0x70的fastbin范围内的size域)。