본문 바로가기

Let's Study/Basic System Hacking Technique

7. Use After Free



system_hacking8.pdf

uaf

uaf.c


(uaf, uaf.c 바이너리, 코드 요청하셔서 올려드립니다.)







 8번째 강의는 Use After Free입니다. 말 그대로 사용한 후 해제했을 때 취약점이 일어날 수 있습니다. 정확히는 heap space에서 alloc된 공간을 free하고 reuse할 때에 일어날 수 있는 취약점입니다. 이 use after free는 browser exploit을 할 때에 자주 쓰이는 기법인데 heap spray와 병행해서 사용합니다.


여튼, use after free에 대해 간단한 원리정도? 설명하도록 하겠습니다.




1. Heap? use and free




스택과 힙은 이런 특성을 가지고 있습니다.


 스택은 정적으로 할당되기 때문에 컴파일 시 미리 스택에 공간이 할당되어 있습니다. 그렇기 때문에 스택에 생성될 배열 사이즈가 상수로 고정될 수 밖에 없고요.

바이너리 실행 중에 스택이 더 넓어질 수는 없겠죠? 예전에 제가 설명드렸던 것 중, sub esp, 0x20 같이 스택을 미리 32바이트만큼 할당하는 코드를 보시면 알 수 있듯이 스택은 컴파일할 때에 할당되는 영역입니다.

 반면에 은 런타임 시 할당되는 영역인데 바이너리가 실행되고 죽기 전까지 힙에 데이터가 남아있게 됩니다. 런타임시 동적으로 할당되기 때문에 힙에 생성될 배열 사이즈는 임의로 바꿀 수 있습니다.

 




간단하게, malloc을 가지고 테스트해보도록 하겠습니다. malloc 아시죠? C언어 공부하면 동적할당 실습할 때 자주 쓰는 함수입니다. 


vuln을 malloc(128) 로 128바이트만큼 할당하면 위 그림과 같이 vuln의 첫번째 주소값에 prev_size, size, 그 뒤로 우리들이 입력한 data가 입력됩니다.

prev_size는 이전에 할당된 힙의 사이즈를 나타내므로 처음 vuln을 힙에 할당하면 당연 0이 들어가 있을 수 밖에 없습니다.


 그리고 그 뒤로 vuln2를 malloc(128)했을 때 prev_size, size, data가 입력됩니다.

 마찬가지로 prev_size는 첫번째로 할당한 vuln의 힙이 free되지 않았기 때문에 0이 들어있습니다.


첫번째 free(vuln)가 일어났을 때 sizeof(vuln)이 가리키는 곳(prev_size)에 128인 0x80을 넣고 자신의 size영역에 PREV_INUSE 비트를 세팅합니다.

PREV_INUSE는 힙이 free될 때 size 의 맨끝 1비트에 이전 힙영역이 사용되는 중이라면 1이 세팅되고, 사용되는 중이 아니면 0이 세팅됩니다.

그러나 첫번째 힙의 경우(Top Chunk, 최상단의 힙)는 PREV_INUSE가 1로 세팅됩니다.


 즉, free된 size에 들어가는 값은 128 byte + size공간 (4 byte) + prev_size (4 byte) + prev_inuse bit (1 bit) = 137 (0x89) 가 들어갑니다.


 두번째 free(vuln2)가 일어났을 때 vuln2의 prev_size 역시 128인 0x80이 들어가고 vuln2의 size는 136, 0x88이 들어갑니다. 첫번째 free된 vuln이 사용중이지 않기 때문입니다. ( PREV_INUSE = 0 )





 힙을 6개 할당하고 그 중, 3개를 free해보도록 합니다. heap이 생성될 때에 data영역에 fd (foward pointer to next chunk in list)bk (back pointer to previous chunk in list)가 생성되는데 이는 Double Linked List의 prev, next라고 보셔도 됩니다. 

 힙은 free될 때에 fd, bk를 참조하면서 어느 메모리가 free 되어있는지 확인 가능한 구조로 되어있습니다. fd는 데이터영역의 맨 첫 4바이트, bk는 바로 다음 4바이트에 생성됩니다.




 그 다음 경우를 볼까요. vuln3과 vuln4가 연속적으로 free되었는데 이 때, PREV_INUSE에서 이전 heap이 사용중인지 아닌지 체크하여 free된 인접한 heap끼리 병합합니다. 해서, vuln3의 size는 120 + 130 + 8(size + prev_size) + 8(size + prev_size) + 1 (PREV_INUSE) 인 267 ( 0x10b)가 들어가게 되는거고요. vuln4는 vuln3과 병합되었기 때문에 fd와 bk는 없고 vuln3에서 unused heap과 vuln을 가리키게 됩니다.



 

 다시 한번 heap space 상태를 그림으로 표현해보면 이렇게 그려질 수 있습니다.


 여기까지 설명은 힙이 할당되고 free될 때에 힙 구조가 어떻게 되는지 알려드렸습니다. 이 구조를 자세하게 알면 다음에 '힙 영역에 왜 이 값이 들어있지..?' 할 때 어느정도 감 잡으실 수 있을겁니다. heap 구조는 os에 따라 달라진다고 들었지만 비슷비슷할겁니다.





2. Use After Free



 

 그렇다면 본격적으로 Use After Free가 뭔지 알려드리고자 합니다. 간단하게 vuln = malloc(100)으로 100바이트 dynamic memory allocated 합니다. 그 후 이 영역에 문자열 "Shayete"를 넣고 free합니다. 그 후 vuln_string = malloc(100)으로 새 heap을 할당한 뒤 바로 이 영역의 문자열을 출력하면?


Shayete가 출력됩니다. 왜그럴까요.




 malloc 함수에는 caching 기능이 있는데 그 중, Deferred Coalescing ( 병합 지연 ) 속성이 있습니다. 이는 free된 heap이 realloc될 때에 같은 사이즈로 요청받을 수 있는데, {vuln = malloc(100), free(vuln), vuln_string = malloc(100)} heap을 병합하거나 분할하는 시간을 절약하고자 free된 heap을 남겨뒀다가 reuse할 때에 free된 영역을 그대로 사용하게 해주는 방법입니다. 어떻게 보면 유용할 수도 있는데, 잘못사용하게 되면 use after free 취약점이 발생합니다. 




 실제로 gdb로 확인해봤을 때의 모습입니다. 첫번째로 malloc되고 free됬을 때에 처음 힙이 생성된 곳 0x0804b000 을 확인해보면, 구조가 이렇게 되어있습니다. vuln이 처음 할당되고 free된 영역이기 때문에 굳이 fd, bk가 필요없어 생성되지 않았습니다. (첫번째 힙과 사용되지 않는 unused free space가 병합되어서 그렇습니다.)




 두번째 그림은 free된 후 새로운 힙 영역을 똑같은 사이즈로 할당한 모습입니다. 할당되었을 때, free되었던 영역의 주소가 새로운 힙 영역의 주소로 할당된걸 보실 수 있는데, 이는 malloc의 caching 기능 때문입니다. 




 그럼 아주 심플하게 코드를 짜고, 간단한 use after free 하나 보여드리도록 하겠습니다. struct로 VULN을 만들고 그 안에 함수포인터 2개를 넣었습니다. 

그 후, 이름을 입력하면 vuln->greetings가 실행되고 realloc 후 string을 입력하면 입력한 값이 vuln->bye에 출력되는 억지스러운 코드입니다.




 vuln의 함수포인터 greetings와 bye를 사용했기 때문에 heap space의 어딘가에 greetings와 bye 함수를 가리키는 곳이 있을건데, (이 함수주소가 어떤 메커니즘으로 힙에 쓰여지는지, 그건 잘 모르겠네요.) 처음 할당된 힙의 data영역에서 200바이트만큼 떨어진 곳에 greetings와 bye가 있네요.


 메모리가 재사용 되었을 때에 처음 힙을 덮을 수 있다는 취약점을 이용해 bye의 return address를 vuln() 함수로 덮어보도록 하겠습니다.

vuln() 함수에는 system("/bin/sh")가 존재하여, 쉘이 실행되는 함수입니다.





 vuln을 가리키는 주소의 끝자리가 0x0c였기 때문에 여긴 패스하고 system의 인자를 가리키는 주소로 넣었습니다. scanf("%s")로 값을 받을 때에 0x20 (space)와 0x0c (form feed) 이기 때문에 이 두개 문자는 들어가지 않기 때문입니다.


 지금 보여드린 use after free 는 정말 간단하게, heap에서 사용되는 함수포인터의 주소를 덮어 다른 주소를 불렀습니다. 


 이 글의 목적은 힙이 생성되고 해제되어, 다시 사용되어질 때에 구조가 어떻게 되는지 알려드리기 위함이었습니다.  use after free는 이런 구조의 취약점을 이용한 공격이고요.


도움이 되었으면 좋겠네요!