unsortedbin_attack--0ctf zerostorage

unsortedbin attack

代码中存在UAF漏洞时,可以通过操纵unsorted bin 的BK指针时,使其修改某一变量的值。举一个简单的例子:

shellphish/how2heap/unsorted_bin_attack

一个unsorted bin,称为victim,victim具有UAF漏洞,则在释放后操作victim时,可以修改victim->bk为需要修改的变量var的地址-0x10,即&var-0x10,这样在再次将victim分配时,会进行unlink操作,将var的值修改为unsorted bin头的地址:

1
2
3
bck = victim->bk;    //bck = &var-0x10
unsorted_chunk(av)->bk = bck;
bck->fd = unsorted_chunk(av) //*(&var-0x10+0x10) = unsorted_chunk(av) => var = unsorted_chunk(av)

典型的应用是修改global_max_fast,这个全局变量代表着fastbin 中能够放入的最大chunk的大小,通过修改这个值可以让所有的chunk释放之后都挂在fastbin上,由于fastbin的结构简单,是单链结构,进而进行fastbin攻击。

实例:0ctf zerostorage

基本逻辑

典型的对堆进行操作的题:

功能menu

insert函数中可以看出,有一个全局数组entries,应该是一个结构体数组,每一个结构体占24个字节,前四个字节记录是否使用中,接着四个字节的padding,然后是8字节的长度和8字节的内容地址信息,这里地址信息不是直接记录的,而是通过与/dev/urandom中的数字异或后存放的。 content内容地址的分配内存是分三种情况:x< 128 直接分配大小为128;128<=x<=4096 分配大小为x;x>4096只分配4096大小。

insert关键代码

异或的值来自/dev/urandom

结构体结构

update函数用于重新编辑content,必要时重新分配内存,修改length和content addr。

update函数关键代码

merge函数合并两个entry对应的content,释放第一个entry的content,将第一个entry各项置零,并重新找到一个entry,把合并后的content地址赋给新的entry 的 content addr。

merge函数关键代码

delete函数释放entry的content addr 所指的内存,并将指针置零,entry中的各项也置零,很安全,是free的正确使用方法。

delete函数关键代码

view函数查看内容

view函数

漏洞

虽然本题的delete函数非常安全,但是在merge函数中,如果输入的两个编号一样,将会先free 然后又把指针给另一个entry,即UAF。

漏洞利用

本题开了所有保护,关键是PIE

checksec

本题还提到一条,之后会用到:

1
Notice: Latest Ubuntu 14.04.4 LTS, with 3.13.0-79-generic kernel.

libc地址泄露: 第一个释放的chunk(不是fastbin),其FD和BK指针都指向main_arena中bins结构中的unsorted bin头,main_arena中的各个值存放于libc中的bss段,因此,可以在merge 之后读取这一块的前八个字节,正好是libc中的地址,通过偏移可以计算出libc的基址。

由于本题开了PIE,因此不能直接获取程序中指令的地址,但是在 Notice中提到的【Ubuntu 14.04.4 LTS, with 3.13.0-79-generic kernel】的操作系统存在着offsetlibc的漏洞,也就是说可以从libc的基址推断出程序的基址。

unsorted bin attack修改global_max_fast的值: update上一步提到的块,将BK修改为&global_max_fast -0x10,FD修改为一个较大的值。再insert一个新的entry时,会将这个块从unsorted bin上unlink后分给新 entry的content,由此完成了对global_max_fast值的改写,接下来的所有块都将属于fastbin的范围。

fastbin劫持用于读取random的值: 再merge另外两个相同的块,此时释放的块将挂在fastbin上,设某一已经分配的entry的index为idx1,利用UAF修改该fastbin的FD指针为entries[idx1],且保证entries[idx1]和merge的块在fastbin中的大小相同,如均为144,因为fastbin在分配时候会首先检查chunk的size字段是否为当前链中应该的大小。 之后再insert两次,第一次将merge的块从fastbin中摘除,此时fastbin的头就指向了entries[idx1],第二次insert就把entries[idx1]分给了新的entry 称为 entries[idx3],即entries[idx3]的content addr指向entries[idx1]。由于entries[idx3] + 0x16中记载的是entries[idx1]^random,通过update idx3将这个值读出后异或entries[idx1],由此得到random的值

这次的shell的执行没有用system,学到了一个新的知识:

one_gadget

再次update idx3,修改idx1下一个entry idx2的content addr处为 free_hook^ random,这样再update idx2时,相当于修改free_hook的值,将其写为one_gadget的地址。

exp

由于目前手中没有Ubuntu 14.xx的系统,在此只记录思路,之后再做具体复现,exp可以参考

Hanquing Zhao

思路:

用#0,#1,…代表entries[0],entries[1];&0,&1,….代表堆上分配的各个content编号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
insert #0 -> &0
insert #1 -> &1
insert #2 -> &2

merge #1,#1 => #3->&1(已free)
view #3 =>读FD 泄露libc地址,进而获取one_gadget地址、entries地址和free_hook地址

update #3 =>覆写global_max_fast的值,之后所有chunk都属于fastbin

insert #1 -> &1
insert #4 -> &3
insert #5 -> &4

merge #4,#4 => #6->&3(已free)
update #6 =>FD的值写成#5(entries[5])的地址

insert #7->&3
insert #8->#5

view #8 =>获取random的值
update #8 =>覆写#6的各个字段,重点是将content_addr写为random^free_hook
update #6 =>在free_hook中写入one_gadget地址

delete #0 =>调用free_hook即one_gadget