ipwn
[Heap] House of force 본문
이번에 정리해볼 기법은 house of force기법이다.
free고 뭐고 필요 없어서 내가 이해하기 쉬웠다.
이 기법은 간단하게 정리하면 top chunk를 건들여서 size를 무한정 입력받을 수 있게 해주는 기법이다.
이 기법을 이용해서 스택까지의 offset을 구해서 ret을 변조해서 eip를 어떻게 할 수도 있고, 아마 보통은 got overwrite를 하는데 쓰이지 않을까 하는 생각이 든다.
일단 바로 예제 코드를 컴파일하고 어떻게 작동하는 건지 원리 설명을 차근차근 해보겠다.
#include <stdio.h> #include <string.h> #include <stdlib.h> void make_system(){ system("echo this func just gadget"); } int main(int argc, char *argv[]) { char str[256]; char *ptr1, *ptr2, *ptr3; ptr1 = malloc(256); strcpy(ptr1, argv[1]); ptr2 = malloc(strtoul(argv[2], NULL, 16)); ptr3 = malloc(256); strcpy(ptr3, argv[3]); scanf("%255s", str); printf("%d", strlen(str)); return 0; } |
이 코드를 가진 바이너리를 대상으로 한 번 exploit해보겠다.
(s0ngsari님의 블로그를 참고했던 기억을 되짚어서 글을 쓴다.)
일단 argv1, 2, 3을 각각 입력받아서 argv[1]은 ptr1의 힙 부분에 복사해서 넣어주고, argv[2]는 ptr2가 할당 될 사이즈를 적어주는 곳이고, argv[3]은 ptr3의 힙에 입력 될 값을 넣어줄 것이다.
static void* _int_malloc(mstate av, size_t bytes) { INTERNAL_SIZE_T nb; /* normalized request size */ mchunkptr victim; /* inspected/selected chunk */ INTERNAL_SIZE_T size; /* its size */ mchunkptr remainder; /* remainder from a split */ unsigned long remainder_size; /* its size */ checked_request2size(bytes, nb); [...] victim = av->top; size = chunksize(victim); if ((unsigned long)(size) >= (unsigned long)(nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset(victim, nb); av->top = remainder; set_head(victim, nb | PREV_INUSE | (av!=&main_arena ? NON_MAIN_ARENA : 0)); set_head(remainder, remainder_size | PREV_INUSE); check_malloced_chunk(av, victim, nb); void *p = chunk2mem(victim); if (__builtin_expect (perturb_byte, 0)) alloc_perturb (p, bytes); return p; } [...] } |
이 코드가 정말 결정적으로 이해가 됐다.
s0ngsari님 블로그에서 긁어왔는데, victim변수에 av->top의 값이 들어가는 걸 알 수 있다.
그런데 av->top는 우리가 덮어쓸 수 있다.
정리하자면 우리가 총 할당 가능한 size는 top chunk의 값을 가지고, 우리는 top을 덮어씌울 수 있으므로 0xffffffff로 덮어씌우면 어디로든지 우리는 공간 할당이 가능하다.
즉 exploit이 가능하다는 말이다.
일단 한 번 디버깅을 해보겠다.
각 malloc을 한 후, 그리고 각 input을 받은 후 등 중요 변화가 발생할 때 마다 bp를 걸어서 상태를 확인해보겠다.
대충 이정도 bp걸고 실행해보겠다.
색칠한 맨 앞 부분, 맨 뒷 부분은 사용자가 마음대로 write할 수 없어야 하는 공간들이다.
그리고 빨간 색 부분의 뒷 4byte 그러니까 0x20ef9 부분은 top chunk 즉 우리가 총 할당가능한 공간을 의미한다.
strcpy가 된 후인 2번째 bp에서의 힙의 상태이다.
음 좋다 깔쌈하게 0x41414141들이 보이니 기분이 좋다.
지금은 정상적인 input이 들어갔을 때를 가정한 상태들을 보여줄 것이다.
2번째 힙이 할당된 후의 모습이다. 원래 아까 top chunk가 있던 곳에는 2번째 힙의 size가 자리잡았다.
0x20만큼을 할당했는데 왜 0x29만큼의 공간이 할당되었는지는 이미 알고있다고 생각하겠다.
나중에 정리글이나 한 번 써야지.
그리고 두번째 힙의 마지막 부분 + 8byte 부분에 top chunk가 자리 잡은 것을 알 수 있다.
아무튼 top chunk의 변화도 0x20ef9 - 0x28이므로 0x20ed1로 정상적으로 잘 변화한 걸 알 수 있다.
마지막으로 3번째 malloc와 그 힙 영역에 input된 걸 마지막으로 bp의 마지막 부분에 다다랐다.
첫 힙부터 마지막 힙 까지 깔쌈하게 다 봤는데, top chunk도 0x20ed1 - 0x108 이므로 0x20dc9로 잘 변화한 걸 알 수 있고, input도 정상적으로 들어간 걸 확인할 수 있다.
이제 우리는 top chunk를 over write해서 hof를 터트려서 strlen함수의 got를 system함수로 덮어준 후, shell을 가져와 볼 것이다.
가장먼저 top chunk가 잘 변조 되는지 확인해보자.
이렇게 값을 넘겨준 후 2번째 힙이 할당 된 뒤에 top chunk의 변화를 살펴보겠다.
옹 깔끔하게 top chunk가 바뀌었다.
하지만 우리가 할 일은 top chunk를 덮는 것 뿐만이 아니라, strlen의 got부분에 힙을 할당하는 것이다.
힙은 할당되면 user data전에 8byte(prev_size, size)가 할당된다.
즉 우리가 할당해야 할 사이즈는 정확히 strlen got - 0x8 - 2nd malloc가 될 것이다.
gdb로 간단히 계산할 수 있다.
우리가 두 번째 malloc에서 0xffffef08만큼을 할당하면 strlen의 got를 덮을 수 있을 것이다.
이렇게 값을 넘겨주고 결과를 한 번 확인해보자.
이렇게 결과를 확인해보니 3번째 힙이 할당된 후의 eax값 즉 3번째 malloc의 주솟값은 0x804a020즉 strlen의 got로 할당되어있다.
한 번 더 continue해보겠다.
strlen의 got가 0x42424242의 값으로 변조됐다.
이제 이 값을 한 번 system의 plt값으로 변조시키고 /bin/sh를 넘겨줘보겠다.
라고 하려 했는데 plt값이 0x8048400이라서 변조할 수가 없다;;;
그래서 디버깅 모드에서는 aslr이 안걸려있으니 libc영역의 system함수의 주소를 끌어다 쓰기로 했다.
이 값을 strlen의 got에 덮어씌워 보겠다.
값을 /bin/sh로 넘겨주니 shell은 가져와지는데, 디버깅모드라 그런지 에러를 내뿜고 터져버린다.
하지만 중간에 id라는 값을 넘겨준 건 잘 출력이 된 걸 확인할 수 있다.
이렇게 오늘은 hof를 정리해봤다.
혹시나 바이너리가 필요하신 분이 있을까 싶어서 바이너리 첨부합니다.
혹시 틀린 부분이나, 부족한 부분은 지적해주세요..... 아직 많이 부족한 학생입니다.
'Pwnable' 카테고리의 다른 글
[Pwnable] close된 stdin, stdout 열기 (0) | 2018.11.14 |
---|---|
[pwnable] Format String Bug (0) | 2018.07.31 |
[pwnable] Blind ROP (0) | 2018.07.28 |
[Heap] Use After Free (4) | 2018.02.18 |