heap chunk 구조 분석
보다 힙을 잘 이해하기 위해서 직접 코드를 작성하여 malloc과 free과정에서의 heap chunk의
구조와 변화하는 과정을 분석하였다.
gdb를 이용해서 분석해보도록 하자. 나는 gdb의 상위 버전인 gef를 이용했다.
그리고 64bit 기준으로 컴파일/분석하였다. 32비트는 직접해보길 바란다.
먼저 내가 작성한 코드이다.
>>heapstruct.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <stdio.h> #include <stdlib.h> #include <string.h>
int main(int argc, char *argv[]){ char* buf = (char*)malloc(256); char* buf1 = (char*)malloc(512); strcpy(buf, argv[1]); strcpy(buf1, argv[2]); printf("%s\n", buf); free(buf); free(buf1); } |
매우 간단한 코드이다.
buf를 256만큼 malloc해준다. 그리고 buf1은 512만큼 malloc해준다.
argv[1] 과 argv[2]를 각각 chunk에 strcpy를 해준다.
buf 와 buf1 차례대로 free해준다.
그러면 이제 분석을 해보자.
디스어셈블 한 코드이다.
첫 malloc때 0x100 , 256만큼 잘 들어가는 것을 볼 수 있다.
먼저 처음 malloc 하고 난 다음인 main+25에 breakpoint를 걸어보겠다.
r로 실행한다.
그리고 힙 상태를 봐보겠다. $rax - 16하면 확인 할 수 있다. 32비트에선 $eax-8 이다.
또는 0x602000을 확인 해도 된다.
힙이 잘 할당 되었다. 64비트이기 때문에 g를 이용하여 8바이트씩 출력하였다. 이제 힙 구조를 확인해 보겠다.
그 전에, 알아두어야 할 것이있다. heap chunk의 구조는 할당되었을 때와 free되었을 때와 구조가 다르다.
1 2 3 4 5 6 7 8 9 10 11 | struct malloc_chunk { INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T mchunk_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; };
typedef struct malloc_chunk* mchunkptr; |
이는 malloc_chunk의 구조체이다.
할당된 malloc chunk의 구조는
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |A|M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... .
. .
. (malloc_usable_size() bytes) .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| (size of chunk, but used for application data) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
이렇게 되고
free된 malloc chunk의 구조는
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:' | Size of chunk, in bytes |A|0|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Forward pointer to next chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Back pointer to previous chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Unused space (may be 0 bytes long) .
. .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`foot:' | Size of chunk, in bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
이렇게 된다. 분석을 하면서 비교해보자.
자세한 내용은
https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/malloc_chunk.html
이 글을 참고하면 좋다.
다시 돌아와서,
저 위에 영어로 쓰여진 구조와 비교해보자.
0x602000 -> prev_size
prev_size는 이전 chunk가 free 되면 설정되는 값으로, 플래그를 제외한 이전 청크의 크기를 기록한다. 이를 이용해서 이전 chunk의 위치를 쉽게 알 수 있다.
다음 8바이트를 보면,
0x602008 -> size of chunk
이 8바이트에서는 이 chunk의 총 크기를 기록한다. 하위 1비트는 플래그로, prev_inuse이다.
prev_inuse는 이전 chunk가 사용중일 때 기록되는 플래그이다.
그렇다면 이 chunk의 크기는 0x110일 것이다.
272이다.
data(256) + prev_size(8) + size_of_chunk(8) = 272 딱 맞다.
0x602010 -> data
여기서 부터 256바이트 만큼은 data이다.
좀 이따 strcpy 이후에 값이 들어가는 것을 확인해보겠다.
0x602118 -> Top chunk
0x20ef1이라는 값이 들어가 있다.
모든 chunk의 맨 마지막에는 Top chunk가 존재한다. Top chunk는 어떠한 bin에도 속하지 않으며 항상 heap영역 마지막에 위치한다. 다음 malloc할 때 다음 chunk size를 top chunk에서 할당하고 나머지는 다시 top chunk가 된다.
잘 이해 안가겠지만 계속 분석하면서 보겠다.
다음 malloc 인 main+39에 breakpoint를 걸었다.
힙 영역 상황이다.
매우 신기하다.
아까 Top chunk 였던 0x602118부분이, 2번째 malloc_chunk의 size of chunk로 바뀌였다.
그리고 제일 마지막, 0x602328에 Top chunk가 있는 것을 확인 할 수 있다.
Top chunk를 비교해보자 아까 첫 번째 malloc chunk에 있던 Top chunk 의 값은 0x20ef1
이고 2번째 malloc을 진행 한 뒤 Top chunk 의 값은 0x20ce1이다.
일단 2번째 malloc의 size of chunk 는 0x211이다. 하위 1비트는 flag이니깐 제외하면 0x210, 10진수로 528이다.
data(512) + prev_size(8) + size_of_chunk(8) = 528이 맞다.
그럼 Top chunk의 변화를 보면 0x20ef1 -> 0x20ce1
계산이 딱 맞다.
Top chunk에서 새로 malloc할 size만큼을 Top chunk에서 할당 한 뒤, 나머지는 다시 Top chunk가 된다.
(암튼 신기)
이제 할당 된 malloc 구조는 확인을 했다.
free chunk를 보기 전에 argv[1] 과 argv[2] 에 값을 넣고 data가 들어가는 것을 확인 해보겠다.
strcpy가 끝난 후인
에 breakpoint를 걸어주었다.
다시 r을 통해 실행하고 strcpy가 될 때까지 진행해보겠다.
이렇게 인자를 주었다.
힙 영역을 보겠다.
잘 들어갔다.
이제 free chunk를 분석해보겠다.
브포를 걸고, 힙 영역을 보자.
뭔가 추가 됐다.
아까 free chunk의 구조를 나타낸 표와 비교해보면 fd 와 bk가 free된 chunk에 있는 것을 확인 할 수 있다.
0x602010 -> fd - forward pointer
0x602018 -> bk - backward pointer
각각은 nextchunk를 가리키는 pointer, prevchunk를 가리키는 pointer이다.
free가 한 번 되고 나서는
pointer가 Top chunk 전을 가리키고 있다.
두 번째 free이후 힙 영역을 확인 해보자.
size of chunk 가 0x320 이다 0x110 + 0x210 의 값으로 변경되었다.
후.. 프로그램은 이제 종료가 된다.
지금까지 gdb로 malloc과 free chunk의 구조를 분석하였다.
느낀건, 100번 문서봐도 직접 한 번 해보는게 훨씬 이해가 잘 되고 얻는 것도 많다.