fastbin_dup

c0wb3ll ㅣ 2021. 1. 26. 03:44

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에 할당되게 된다.