Sunichi's Blog

sunichi@DUBHE | Linux & Pwn & Fuzz

0%

Learn Large Bin With 0ctf2018 Heapstorm2

Large Bin基本结构

Large Bin的每个Bin中的chunk的大小都属于同一范围,Large Bin的每个chunk位于两个双向链表中。相比较其它chunk,Large Bin中的chunk多出了fd_nextsizebk_nextsize两个字段,分别指向前一个/后一个与当前chunk大小相邻的不同大小的第一个空闲块(不包括bin头指针)。

Put Unsorted Bin into Large Bin

malloc时,如果Unsorted Bin中的victim的大小无法满足申请所需且属于Large Bin,将会被置入Large Bin中。

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
victim_index = largebin_index(size); // 计算bin数组下标
bck = bin_at(av, victim_index); // 获取bin
fwd = bck->fd; // bin中第一个chunk

if (fwd != bck) {
// 更新大小排序链表
// ...
if (size < bck->bk->size) { // 如果大小小于bin中最小的chunk大小
fwd = bck; // 此时fwd指向bin,bin->fd == bin->bk == 唯一一个chunk
bck = bck->bk; // 此时bck指向唯一一个large bin chunk
// 在bin和bck中插入
victim->fd_nextsize = fwd->fd; // 写入fd_nextsize
victim->bk_nextsize = fwd->fd->bk_nextsize; // 写入bk_nextsize
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize; // 更新fwd->fd的bk_nextsize
} else {
// ...
}
} else {
// ...
}

mark_bin(av, victim_index);
// 更新普通链表
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

从源码中可以看出,Large Bin在更新链表的时候,没有freed chunk中的链表数据的进行任何的安全检查,利用这一点,可以向指定地址写入堆地址数据。利用对齐的特性向堆上写入0x56开头的堆地址,则可以在特定位置制造一个空闲的0x56大小的chunk。当申请0x48大小的chunk时(并触发unsorted bin一系列操作后),特定位置的chunk将会被取出。

同时,利用这个漏洞,也可向特定位置写入特定数值。

0x55与0x56

64位下,PIE和randomize_va_space对地址的影响:

  • 0表示关闭地址空间随机化
  • 1表示对mmap的基地址、栈地址和vdso地址随机化
  • 2表示在1的基础上对堆地址随机化

当程序开启PIE且系统支持地址随机化时,堆地址的非零最高位会在0x55和0x56之间随机。这两个数字对于calloc()来说,只有0x56能够通过检查。

1
2
assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
av == arena_for_chunk (mem2chunk (mem)));

0ctf2018 heapstorm2

明显的off-by-null漏洞。

1
2
3
4
5
input_string(chunk, size);
remainder_space = size + chunk;
*(_QWORD *)remainder_space = 'ROTSPAEH';
*(_DWORD *)(remainder_space + 8) = 'II_M';
*(_BYTE *)(remainder_space + 0xC) = 0; // off by null

利用off-by-null来制造堆块重叠,从而修改已释放的chunk的链表指针数据。通过将Unsorted Bin中的Large Chunk放入Large Bin的操作,向特定地址写入0x56开头的堆地址,使得再次申请chunk时,获得特定地址的chunk,从而改写关键数据以进一步getshell。