-
[ Rookiss ] AllocaSystem/Pwnable.kr 2019. 3. 24. 17:58
" Buffer Overflow를 예방하는 법에 대해서 알려주겠다. "고 한다.
[ Alloca.c ]
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273#include <stdio.h>#include <string.h>#include <stdlib.h>void callme(){system("/bin/sh");}void clear_newlines(){int c;do{c = getchar();}while (c != '\n' && c != EOF);}int g_canary;int check_canary(int canary){int result = canary ^ g_canary;int canary_after = canary;int canary_before = g_canary;printf("canary before using buffer : %d\n", canary_before);printf("canary after using buffer : %d\n\n", canary_after);if(result != 0){printf("what the ....??? how did you messed this buffer????\n");}else{printf("I told you so. its trivially easy to prevent BOF :)\n");printf("therefore as you can see, it is easy to make secure software\n");}return result;}int size;char* buffer;int main(){printf("- BOF(buffer overflow) is very easy to prevent. here is how to.\n\n");sleep(1);printf(" 1. allocate the buffer size only as you need it\n");printf(" 2. know your buffer size and limit the input length\n\n");printf("- simple right?. let me show you.\n\n");sleep(1);printf("- whats the maximum length of your buffer?(byte) : ");scanf("%d", &size);clear_newlines();printf("- give me your random canary number to prove there is no BOF : ");scanf("%d", &g_canary);clear_newlines();printf("- ok lets allocate a buffer of length %d\n\n", size);sleep(1);buffer = alloca( size + 4 ); // 4 is for canaryprintf("- now, lets put canary at the end of the buffer and get your data\n");printf("- don't worry! fgets() securely limits your input after %d bytes :)\n", size);printf("- if canary is not changed, we can prove there is no BOF :)\n");printf("$ ");memcpy(buffer+size, &g_canary, 4); // canary will detect overflow.fgets(buffer, size, stdin); // there is no way you can exploit this.printf("\n");printf("- now lets check canary to see if there was overflow\n\n");check_canary( *((int*)(buffer+size)) );return 0;}cs 서버에 접속하면 'Alloca.c' 소스파일을 제공해준다.
코드 내용이 다소 길지만, 중요한 부분만 보면 아래와 같다.
[ alloca(size_t size ) ]
우선 문제 타이틀인 alloca( )에 대해서 알아보도록 하자
이 함수는 Heap이 아닌 Stack에 동적할당을 하고자할 때 사용하는 함수이다.
그때 그때 필요한 만큼 스택을 할당하고 해제하니까 나름 효율적일 것만 같지만,
alloca( )에 대해서 조금만 찾아보면 다들 사용을 자제하라고 한다.
그 이유는 신뢰할 수 없는 입력으로 부터 '큰 값'이나 '음수 값'이 입력될 수 있기 때문이다.
큰 값이나 음수 값이 입력되면 해당 값을 할당하다가 Stack Frame이 깨져버린다.
해당 문제도 이러한 취약점을 이용해서 Exploit을 진행하면 된다.
[ memcpy ]
63 memcpy(buffer+size, &g_canary, 4); // canary will detect overflow.
63번째 코드를 보면, alloca( )로 할당한 buffer뒤에 4Byte를 우리가 입력한 'g_canary'로 덮는다.
만약 우리가 Stack Frame을 조작할 수 있다면, EIP가 참조하는 [ESP]부분을 'g_canary'로 덮을 수 있을 것이다.
[ Callme( ) ]
Exploit하기 편하게 One_shot함수도 제공해주고 있다.
[ How to Exploit? ]
우선 정적 분석보다는 GDB를 이용한 동적분석을 추천한다.
alloca( )가 어떤식으로 동적할당을 하는지 흐름을 알아야 정확하게 EIP를 핸들링할 수 있다.
[ Alloca( ) in GDB ]
GDB에서 alloca( )코드에 해당하는 코드는 위 Assemble들이다.
[ Alloca( ) in IDA ]
IDA에서는 이렇게 두줄을 참고하면 된다.
정적분석으로 알고 넘어가야 할점은 우리가 Input으로 넣어주는 'size'값에 +34가 더해진다는 점이다.
자 그럼 동적분석으로 Alloca( )가 어떻게 동작하는지 알아보도록 하자
[ 1. mov eax, ds:0x804a048 ]
먼저 나는 size값에 '-70'을 넣어줬다.
[mov eax, ds:0x804a048]에서는 입력한 size값을 EAX에 가져오는데 어떤값이 넘어오는지 보자
우리는 분명 '-70'을 입력했는데, size값에는 [0xFFFF FFBA]가 저장되어 있다.
어찌보면 당연한 결과이기도 하다.
사이즈를 DWORD로 설정하고, -70을 입력해보면 Hex값으로는 저 값이 맞다.
따라서, 음수값을 입력하게 되면 size에 비정상적으로 큰 값이 들어가게 되고
이 때문에 Stack Frame이 깨지게 되는 것 이다.
[ 2. div ecx ]
다음으로 볼 코드는 [div ecx]다.
ECX에는 0x10으로 저장되어 있고, 이 값으로 EAX와 div 연산을 하게 되면
EAX의 마지막 값인 [0xC]가 잘리게 되면서, [0xFFFF FFD]으로 EAX값이 세팅된다.
그 다음에 [imul eax, eax,0x10] 연산을 하는 것을 볼 수 있다.
imul은 두번째와 세번째 인자를 곱한 값을 첫번째 인자에 저장하는 연산이며, 결과 값은 [0xFFFF FFD0]이 된다.
따라서, [div ecx] / [imul eax, eax,0x10] 코드는 할당 Size를 16의 배수로 맞춰주기 위한 연산으로 보면 된다.
[ 3. sub esp, eax ]
그 후, [sub esp, eax]로 입력한 만큼 Stack을 할당해준다.
여기가 제일 중요한 부분이다.
해당 Instruction이 실행되면, ESP는 +48만큼 줄어들게 된다.
왜 이런 결과가 나오는지 계산을 천천히 해보자
[ ESP ]
ESP는 [0xFFFF CE00]으로 10진수로 [-12,800]이다.
[ EAX ]
EAX는 [0xFFFF FFD0]으로 10진수로 [-48]이다.
우리가 입력한 값은 -70이였고, 이 값에 +34가 더해졌으니 -36, 이 값을 16배수로 맞추니 -48로 바뀐 것이다.
아무튼 ESP와 EAX 두 값을 sub연산을 하게 되면 아래와 같은 연산식이 만들어 질 것이다.
-12,800 - (-48) == -12,800 + 48
[ ESP-EAX ]
따라서, 연산결과는 -12,752 Hex값으로는 [0xFFFF CE30]이라는 값이 나온다.
GDB에서도 확인해보면 ESP가 [0xFFFF CE30]으로 바뀐것을 확인할 수 있다.
이러한 흐름으로 우리는 원하는 위치로 Stack를 맞춰줄 수 있다.
[ memcpy ]
이제 원하는 위치에 값을 쓸 차례다.
위에서 말한 memcpy(buffer+size,g_canary,4)를 이용하면 된다.
우선 [mov eax, ds:0x804a050]으로 buffer의 시작주소를 EAX로 가져온다.
[ buffer+size ]
그 다음 EDX(Size값)에 EAX를 더 해주면서 g_canary가 저장될 위치를 잡아준다.
[ Save g_canary ]
그 후, [mov eax, ds:0x804A04C]로 우리가 입력한 g_canary값을 가져온다.
나는 '-1431655766'(0xaaaa aaaa)를 g_canary값으로 입력해놨다.
그 다음 [mov DWORD PTR [edx], eax]로 g_canary값을 [edx]에 4Byte만큼 저장한다.
자 이제 우리는 원하는 곳에 원하는 값을 4Byte만큼 쓸 수 있게 됐다.
[ Func Epilog ]
함수 에필로그를 보면 [mov ecx, DWORD PTR [ebp-0x4]]로 ECX에 값을 가져온 후,
[lea esp, [ecx-0x4]]로 EIP포인터를 설정해주는 것을 확인할 수 있다.
우리는 이 흐름을 이용해서 [EBP-4]에 'Callme( )의 주소가 저장된 주소+4'를 저장해놓게 되면
에필로그 때, EIP가 해당 주소를 참조하게 되면서 Callme( )가 실행되게 될 것이다.
자 그럼 문제는 &Callme( )가 저장된 주소가 필요한 건데...
이 부분에서 많이 헤맸다.
처음 접근은 size와 buffer가 전역변수로 선언되어 있어서 이 부분을 이용해서
&callme( )가 저장된 주소를 넘기면 되겠거니 했는데, 불가능했다..
내 머리로는 도저히 방법이 안나와서 Writeup을 참고했는데, Intended한 방법이 BruteForce라니..
이 부분보고서 딱 질렸다.
문제에서 의도한 방법이 무작정 때려박는 BruteForce라니..ㅋㅋ 분석을 통해서 익스하는 것도 아니고
그냥 환경변수에 Callme( ) 겁나 때려박고 환경변수 부분으로 계속 Bruteforce하다 보면 쉘이 따진다.
BruteForce만 아니면 정말 괜찮은 문젠데, 흠.. 말을 아끼겠다.
[ Exploit ]
'System > Pwnable.kr' 카테고리의 다른 글
[ Rookiss ] Ascii_easy (2) 2019.04.05 [ Toddler's Bottle ] asm (4) 2019.03.09 [ Rookiss ] otp (1) 2019.03.06 [ Rookiss ] Simple Login (2) 2019.01.17 [ Rookiss ] loveletter (0) 2018.08.26 댓글