본문 바로가기

WARGAME/hackerschool ftz,lob

[LOB 페도라 FC3] dark_eyes -> hell_fire

이번 문제는 xinetd 데몬으로 돌아가는 hell_fire 서비스의 bof 취약점을 공략하는 문제이다.


remote 문제는 standalone방식과 xinetd방식으로 나뉘는데 이 때 공격 법 또한 달라지게 된다.


이 둘의 차이점은 다음에 포스팅하기로 하고 문제를 보도록 하자.


hell_fire.c는 다음과 같이 구성되어 있다.


#include <stdio.h>


int main ()

{
      char buffer[256];

char saved_sfp[4];

char temp[1024];


printf("hell_fire : What`s this smell?\n");

printf("you : ");

fflush(stdout);


//give me a food

fgets(temp, 1024, stdin);


//save sfp

memcpy(saved_sfp, buffer+264, 4);


//overflow!!

strcpy(buffer, temp);


//restore sfp

memcpy(buffer+264, saved_sfp, 4);


printf("%s\n", buffer);

}


소스를 분석해 보면 fgets()로 temp[1024]에 표준입력을 받은 뒤 다시 strcpy()를 이용해서 buffer[256]에 복사해 준다.

그리고 그 사이에 sfp를 저장했다가 소스 마지막에 원래의 sfp를 다시 복구시킨다.


힌트로는 another fake ebp, got overwriting이 있는데, 가장 먼저 ret sleding으로 풀이하였는데, 장장 4시간의 삽질 결과 실패 했다.


execve, execv, execl 함수를 모두 사용해 봤는데, 전부 실패해서 뒤늦게 검색해보니 



RET Sleding으로 풀수 없다고 한다ㅠㅠ 그래서 왜 그럴까 고민하다가 직접 디버깅 해볼려고 했는데, 또 fgets로 입력받는 방식은 펄로 어떻게 입력해줘야 하는지 몰라서 못했고, (포스팅요망) 같은 소스에서 fgets대신 strcpy(buffer, argv[1])로 직접 입력받게 수정한 다음 해보니 쉘이 잘 떨어지는 것을 볼 수 있었다.





따라서, fgets()의 표준 입력으로 입력받은 상태에서 strcpy로 다시 복사하는 과정에서 뭔가 다른점이 있다는 건데 이는 차후 디버깅 방법을 배우고 디버깅을 해보며 풀이해야 할 것 같다.



그래서 힌트로 풀이하려 하는데 둘 다 낯설어서 이번에는 다른 분들이 쓴 write-up을 보고  another fake ebp, got overwriting, do_system()방식을 모두 사용해서 풀이 해 보았다.






1. do_system()을 이용한 풀이


do_system()은 system()함수 내에서 호출되는 함수로 또 do_system()함수 내에서 execve()함수를 호출한다고 한다. 



system() 함수의 내부를 보면 위 사진과 같이 do_system()을 호출하는 부분이 보인다. do_system() 함수의 내부를 보도록 하자.


do_system()함수의 내부를 보다보면 execve()를 호출하는 부분을 볼 수 있다. 



do_system()으로 풀이한 분들의 write-up을 보면 do_system+1124의 위치인 0x750784를 ret위치에 써주는 것을 볼 수 있다. 

그렇게 되면 아래와 같이 execve()에 필요한 매개변수들을 레지스터에 넣고 execve()를 호출한 뒤 쉘이 떨어지는 것을 볼 수 있다.


 



이 부분에 대한 자세한 설명은 아래 URL을 참고하여 알수 있었다. 

http://sangu1ne.tistory.com/7







2. got overwrting을 이용한 풀이


got overwriting은 다음 url을 참고하여 시도해 보았다.

http://k1rha.tistory.com/entry/LOB-Fedora-BOF-GOT-overwrite-hellfire-evilwizard

http://teamcrak.tistory.com/332


일단 필요한 주소값을 먼저 찾았다.

ppr address : 0x804861c

strcpy@PLT  : 0x080483cc

memcpy@PLT  : 0x080483bc

execve      : 0x007a5490

       0x90 : 0x080493d8 

0x54 : 0x08048a9c

0x7a : 

0x00 : 0x080487b4


main+163    : 0x08048527


그런데 execve, execl, execv 함수의 0x7a를 고정주소 부분에서 아무리 찾아봐도 찾을 수 가 없어서 풀이에 fail.....









another fake ebp를 이용한 풀이


이 방법은 기존 main의 sfp를 바꾸는 것이 아니라 main함수가 종료되고 리턴하는 __libc_start_main 함수의 sfp를 변경하여 원하는 곳으로 esp를 옮기는 방법이다. 


(아래는 main함수가 끝나고 eip 레지스터에 있는 주소를 출력해 놓은 사진이다. main함수가 끝나고 리턴되는 위치는 __libc_start_main+227위치임을 알 수 있다.)




자세한 공격 과정은 다음 URL을 참고하고 이 URL에 나오지 않는 공격파라미터에 대한 설명만 써 보겠다.


http://windowhan.tistory.com/entry/darkeyes-hellfire

http://turtle1000.tistory.com/67

http://cd80.tistory.com/20




먼저 공격 파라미터는 다음과 같다.


low address ... | buffer[256] | dummy(8) | sfp | leave, ret | dummy(88) | fake ebp | leave ret | mprotect() | 쉘 코드 위치 | 

| 인자 1 | 인자 2 | 인자 3 | (쉘 코드) | ... high address


이제 스택 구조 앞의 buffer에서 부터 순서대로 설명하도록 하겠다. 


1. buffer[256] + dummy(8byte) + sfp(4byte) 의 값에는 쓰레기 값이 들어가므로 A로 채워준다. "A"x268


2. 스택에서 ret의 위치에 leave, ret의 주소를 넣어준다.


3. 쓰레기 값(dummy)를 88개 넣어준다. "A"x88

( 그 이유는 main함수의 sfp값과 __libc_start_main함수의 sfp값의 거리를 계산하면 96이 된다. )



즉, | buffer[256] | dummy(8) | sfp | leave, ret | dummy(88) | fake ebp | 이 부분에서


| buffer[256] | dummy(8) | sfp | leave, ret | dummy(88) | fake ebp |


빨간 색부터 파란 색까지의 거리가 96이므로 leave,ret 까지 써주고 fake ebp에 뭔가를 쓰려면 96 - (sfp, leaveret의 크기=8) = 96 - 8 = 88 임을 알 수 있다.


4. fake ebp 부분에는 stdin의 주소인 0xf6ffe000 을 기준으로 하는데, 스택에서 buffer의 시작에서 부터 fake ebp 까지의 크기를 더해주면 구할 수 있다.


즉, | buffer[256] | dummy(8) | sfp | leave, ret | dummy(88) | fake ebp | leave ret | mprotect() | 쉘 코드 위치 | 

| 인자 1 | 인자 2 | 인자 3 | (쉘 코드)  


빨간색 부분의 크기를 더해준 것이다. 그 이유는 그 다음 leave ret을 지나면서 esp가 mprotect()를 가리키게 하기 위해서 이다.




5. mprotect() 는 http://www.linuxcertif.com/man/2/mprotect/ko/ 이곳을 보면 잘 알 수 있는데, 간단하게 메모리 영역의 접근 제어를 담당하는 함수라고 한다. 다음과 같이 첫번쨰 인자부터 메모리 영역의 값, 크기, 권한을 매개변수로 받고 있다.

int mprotect(const void *addr, size_t len, int prot);



6. 파라미터 마지막 부분에 쉘 코드를 올렸는데, NOP을 충분히 많이 올렸으므로 4번에서 얻은 주소값에 적당한 값을 더하여 작성한다.


7. 인자 1에는 실행 권한을 주고자 하는 메모리 영역의 주소를 적어주는데 여기서는 stdin을 사용하므로 stdin의 주소인 0xf6ffe000을 적어준다.


8. 인자 2에는 표준 입력으로 우리가 입력한 만큼의 적당한 값을 넣어준다.


9. 인자 3에는 rwx권한을 주기위해 0x07 값을 준다.


10. 쉘코드를 올려준다.






fake_ebp 방법의 자세한 동작 과정


0. 마지막 main함수의 leave ret이 시작하기 전 스택 상태 | sfp(main) | leaveret1 | CCCCC | fake ebp | leaveret2 | mprotect | &shellcode | argv1 | argv2 | argv3 | shellcode | ↑esp eip : leaveret of main 1. 먼저 main함수의 leaveret에 의해서 esp는 __libc_start_main의 sfp(fakeebp)를 가리키고 있고 우리가 처음 써준 leaveret을 eip에 넣음 | sfp(main) | leaveret1 | CCCCC | fake ebp | leaveret2 | mprotect | &shellcode | argv1 | argv2 | argv3 | shellcode | ↑esp eip : leaveret1 2. 우리가 써준 leaveret이 동작하면서 fakeebp가 가리키는 곳으로 esp가 이동 (우리의 입력 구문에서 mprotect의 위치, 0x16c, 즉, fake ebp와 leave ret2 사이) pop ebp해서 한칸 줄어들고, pop eip 하면서 다시 우리가 써준 두 번째 leaveret 실행 될 것임. | sfp(main) | leaveret1 | CCCCC | fake ebp | leaveret2 | mprotect | &shellcode | argv1 | argv2 | argv3 | shellcode | ↑esp eip : leaveret2 3. 현재 esp는 leaveret1에 의해서 fake ebp가 가리키는 곳으로 가있어서 fake ebp, leave ret사이에 위치하고 있음 이 상태에서 leaveret2이 다시 실행되면 쓰레기값이 지정한 위치로 esp를 위치시키고 eip에 mprotect가 들어가서 실행됨 실행되면 스택 구조는 다음과 같다. | sfp(main) | leaveret1 | CCCCC | fake ebp | 쓰레기값sfp | mprotect | &shellcode | argv1 | argv2 | argv3 | shellcode | ↑esp eip : mprotect ebp : 쓰레기값sfp의 값 4. mprotect가 실행되면서 스택 구조는 다음과 같이 변하고 코드를 진행시킴 | sfp(main) | leaveret1 | CCCCC | fake ebp | 쓰레기값sfp | ebp of mprotect | &shellcode | argv1 | argv2 | argv3 | shellcode | ↑esp eip : mprotect+3 ebp == esp


ps 쓰레기값 sfp는 그냥 이해하기 편하게 써놓은것이고 사실 3번의 leaveret2가 실행되면서 ebp는 leaveret의 주소로 이동함