Heap Overflow Length Extension for GHOST vulnerability
Heap Overflow Length Extension for GHOST vulnerability
이 글은 CVE-2015-0235, 일명 'ghost' 취약점에 대해서 분석하면서 dl-malloc 자체에 대해서도 같이 분석하게 된 내용을 정리한 것이다. ghost 취약점은 glibc 의 gethostbyname 함수내부적으로 heap overflow 가 발생하는 취약점인데, 이 취약점에서 발생하는 overflow 는 제한된 범위의 제이터로 고작 4바이트(32비트) 또는 8바이트(64비트) 밖에 되지 않기 때문에 일반적으로 exploitable 하기가 매우 어렵다.
CVE-2015-0235 의 exploitability 는 heap layout 과 malloc 방식에 매우 의존적이기 때문에, ghost 취약점의 파급력을 판단하기 위해서는 malloc 을 상세히 분석하는것이 중요하다.
- Terminology
mallc 포인터 : malloc 을 해서 받은 포인터. 유저가 사용하는 데이터의 시작주소
chunk : malloc 이 관리하는 자료구조. chunk 의 구조는 그림으로 보면 다음과 같음
chunk 포인터 : malloc 에 대한 메타데이터까지 포함하는 청크의 시작주소. prev_size 필드의 시작.
bin : 비슷한 사이즈의 chunk 들의 집합. 연결리스트로(fastbin 의 경우 싱글, 나머지는 이중) 연결되어있음.
wilderness / arena / top chunk : sbrk() 로 OS 로부터 할당받은 사용되지 않은 힙영역. malloc 은 기본적으로 이미 free 된 chunk 들을 뒤지다가 정 없으면 이영역에서 새롭게 chunk 를 할당함.
An allocated chunk looks like this:
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if allocated | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... .
. .
. (malloc_usable_size() bytes) .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
...
chunk 속에 있는 각각의 필드들은 chunk 가 할당된 상태냐 free 된 상태냐에 따라 의미가 달라짐.
- Allocation Size of malloc
malloc 으로 할당되는 메모리 사이즈는 8 의 배수로만 할당되는데, 청크사이즈가 8n 이면 정확히 8n 바이트만큼 자기한테 할당된 공간을 쓰지만 8n+1 만큼의 사이즈를 요쳥하는경우 8(n+1) 만큼의 사이즈가 잡히는게 아니라 8n+4 까지는 다음번 청크의 prev_size 부분을 공간으로 활용한다. 왜냐하면 다음청크의 prev_size 는 현재 청크가 free 되기 전까지는 쓰일일이 없기때문에 공간을 절약하기 위함이다. 이때문에 heap overflow 가 일어날때 해당 청크의 size 가 몇인지에 따라 덮어쓰게되는 뒤쪽청크의 레이아웃이 달라진다. 이는 ghost 취약점과 같은 상황에서 매우 중요한 요소다. 64비트의 경우 16n 이다
- Allocation Algorithm of malloc
malloc 이 메모리를 할당하는 알고리즘은 메모리 사이즈에 의존한다. 이는 크게 3가지 사이즈 범위의 기준으로 나위어지는데,
1. 64바이트 이하의 malloc 요청의 경우 fastbin 에서 먼저 가용한 공간이 있는지 검색한다. fastbin 의 free chunk 들은 비록 free chunk 이지만 inuse 비트가 셋 되어있는 상태를 유지한다. 이는 다른 인접한 chunk 와 merge 되는일이 없도록 하기 위함이다. 만약 가용한 freechunk 가 fastbin 에 없다면 free 된 small chunk 들중에 best fit 등의 알고리즘으로 할당할 chunk 를 찾는다. 만약 small chunk 들도 가용한게 하나도 없다면 wilderness 에서 잘라온다.
2. 64 바이트 이상 malloc 요청의 경우 fastbin 에서 찾지않고 곧바로 small chunk 부터 찾는다.
3. 512 바이트 전/후 사이즈로 처리가 조금 다른데 자세히 분석하지 않았음.
4. 64비트 malloc 에서는 fastbin 의 기준사이즈가 120 바이트이다
- What happens when you free
1. 64바이트 이하의 청크들은 fastbin 에 들어가는데 inuse 비트를 유지한다. 다만 fd 포인터 위치가 0 으로 채워진다(아마 어떤 인덱스를 의미하는것같다). 그렇다면 free 된 청크인지 아닌지, 그 리스트들을 어떻게 접근하는지는 어떻게 처리되는가? -> 분석안해서 잘모르겠다
2. 64바이트 이상의 청크들은 자신의 사이즈에 따라 자신이 속한 bin 으로 들어간다. 일단 인접한 chunk 의 inuse 비트가 해제되고 청크의 malloc pointer 위치에 fd/bk 포인터가 생성된다. 이 포인터는 이중연결리스트로서 bin 에서의 next chunk, previous chunk 를 가리킨다.
3. fastbin 에 들어간게 아닌 어떤 chunk 가 free 됬을때 인접한 chunk 랑 merge 할수있는지 판단해서 가능하다면 unlink 한다. 아래를 보면 자명하다. free 할때 인접한 청크가 또 free 되있으면 merge 를 수행하는데, 그렇지 않고 띄엄띄엄 free 시켜보면 free 청크의 fd/bk 포인터들이 계속해서 생기는것을 볼수있다.
chunk before free(0804b070)(40)
[00000000][00000031][41414141][41414141]...[00000039]
chunk after free(0804b070)(40)
[00000000][00000031][00000000][41414141]...[00000039]
chunk before free(0804b0d8)(56)
[00000000][00000041][41414141][41414141]...[00000049]
chunk after free(0804b0d8)(56)
[00000000][00000041][00000000][41414141]...[00000049]
... 여기까지는 fastbin 에 들어간 free chunk 라서 prev_inuse 비트가 유지되고 fd 포인터 위치에 0 이 들어간걸 볼수있다. 그러나 이후부터는 inuse 비트가 해제되고 fd/bk 에 청크 포인터가 들어간다.
chunk before free(0804b160)(72)
[00000000][00000051][41414141][41414141]...[00000059]
chunk after free(0804b160)(72)
[00000000][00000051][b7fc5470][b7fc5470]...[00000058]
chunk before free(0804b208)(88)
[00000000][00000061][41414141][41414141]...[00000069]
chunk after free(0804b208)(88)
[00000000][00000061][0804b160][b7fc5470]...[00000068]
- Difference of malloc / realloc
realloc 의 경우 현재 청크를 제자리에서 확장할수있나 보고 그게 안되면 현재청크를 free 한뒤에 malloc 을 더 큰사이즈로 호출해준다. 여기서 중요한점은 realloc 이 chunk 를 extend 하는 과정에서 인접한 chunk 의 사용유무를 판단하는 방식이 free chunk bin 의 연결리스트를 따라다니면서 찾는것이 아니라 realloc 청크의 size 를 통해서 인접 chunk 의 prev_inuse 비트를 체킹하는 방식이라는 것이 중요하다.
- Unresolved Issues
분석하지 못한부분들이 많이 남아있어서 그런것같은데, 일단 디버깅을 한 결과만 가지고볼때 위에서 언급한 Heap Overflow Length Extension 을 수행하고자 할 때, 공격자가 제어할 수 있는 malloc 이전에 제어할수 없는 다른 malloc 이 끼게되는 상황이라면 문제가 발생한다.
즉, 예를들어 100바이트 청크를 free 하고나서, malloc 이 일어나고 (제어할수 없는 malloc), 그 이후에 오버플로우에 의해서 free 청크 사이즈를 3000 정도로 조작했다고 가정하면, 이후 공격자가 제어할수 있는 malloc(3000) 을 요청하게되면 조작된 사이즈를 가진 free 청크주소에 malloc 할당이 되지 않는다. malloc(3000) 을 사이즈 조작 직후 바로 수행하면 문제없이 되는데, 이에대한 원인은 분석을 안한부분에 있는것 같다.
정적분석을 하진 않았지만 이 현상에 대해서 디버깅을 해보면 다음과 같다.
일단 free 된 청크의 사이즈를 조작한 직후에 fd 주소를 따라가보면 다음과 같다
(gdb) x/30wx 0x7ffff7dd3798
0x7ffff7dd3798: 0x00602d20 0x00000000 0x00000000 0x00000000
0x7ffff7dd37a8: 0xf7dd3798 0x00007fff 0xf7dd3798 0x00007fff
0x7ffff7dd37b8: 0xf7dd37a8 0x00007fff 0xf7dd37a8 0x00007fff
0x7ffff7dd37c8: 0xf7dd37b8 0x00007fff 0xf7dd37b8 0x00007fff
0x7ffff7dd37d8: 0xf7dd37c8 0x00007fff 0xf7dd37c8 0x00007fff
0x7ffff7dd37e8: 0xf7dd37d8 0x00007fff 0xf7dd37d8 0x00007fff
0x7ffff7dd37f8: 0xf7dd37e8 0x00007fff 0xf7dd37e8 0x00007fff
그런데 제어할 수 없는 malloc (제어할수 없음과 동시에 조작된 size 에 맞아떨어지지 않는 malloc) 이 수행된 이후에 free 청크를 보면 fd 주소가 바뀌어있는것을 볼 수 있고, 따라가보면 다음과 같다.
(gdb) x/30wx 0x7ffff7dd3858
0x7ffff7dd3858: 0xf7dd3848 0x00007fff 0xf7dd3848 0x00007fff
0x7ffff7dd3868: 0x00602410 0x00000000 0x00602410 0x00000000
0x7ffff7dd3878: 0xf7dd3868 0x00007fff 0xf7dd3868 0x00007fff
0x7ffff7dd3888: 0xf7dd3878 0x00007fff 0xf7dd3878 0x00007fff
0x7ffff7dd3898: 0xf7dd3888 0x00007fff 0xf7dd3888 0x00007fff
0x7ffff7dd38a8: 0xf7dd3898 0x00007fff 0xf7dd3898 0x00007fff
0x7ffff7dd38b8: 0xf7dd38a8 0x00007fff 0xf7dd38a8 0x00007fff
0x7ffff7dd38c8: 0xf7dd38b8 0x00007fff