본문 바로가기

Let's Study/Hacker's CTF

[2015 Plaid CTF] pwnable_EBP 160p

대학원 준비하느라 많이 바쁜 관계로 가볍게 한문제 풀고 포스팅합니다.




2015. 4. 18일 오전 3시인가 0시부터 시작했던 ppp팀 주관의 2015 plaid ctf ,  pwnable 문제 ebp 입니다.

전형적인 포맷스트링 버그 문제입니다.




바이너리는 매우 단순합니다. 제가 문제풀면서 적당히 정리한 text 라 지금 response와 buf 배열이 함수 내에 존재하는데

원래 바이너리에 buf[1024]와 response[1024]는 전역변수로 선언되어 있습니다.


이 바이너리를 간단하게 요약하면


#include <stdio.h>


char buf[1024];

char response[1024];


void main()

{

while(1)

{

fgets(buf, sizeof(buf), stdin);

echo();

}

}


void echo()

{

make_response();

puts(response);

fflush(stdout);

}


void make_response()

{

snprintf(response, sizeof(buf), buf);

}



main 함수에서 fgets로 1024바이트만큼 buf에 받아서

make_response 에서 snprintf로 response로 안전하게 1024바이트만큼 buf의 값을 출력한뒤, 

echo의 puts(response)에서 response의 내용을 출력합니다.


문제되는 부분은 puts(response) 입니다. 


포맷스트링 버그 참조 링크 : http://hackerschool.org/HS_Boards/zboard.php?id=Free_Lectures&no=1152

  링크2 : http://geundi.tistory.com/124


여기 링크가 자세히 설명되어 있어서 걸어놓을게요.


이러이러한 이유로 (링크참조) 포맷스트링을 사용하게 되면 스택의 값들을 출력하게 됩니다.


포맷스트링 버그에 대해 간단하게 설명드리면 puts(response) 부분에서 puts가 포맷스트링을 만나게 되면 현재 가리키는 스택에서 +4만큼 한 곳의 값을 출력합니다.

포맷스트링을 연속적으로 사용한다면 스택에 어떤 것들이 쌓여있는지 확인가능하겠죠.





본격적으로 바이너리를 분석합니다.




AAAA에 포맷스트링 %x를 10번 입력하면 AAAA는 문자열로 인식해서 그대로 출력이 되지만 그 뒤의 포맷스트링은 esp+4,+8, ... 이 출력됩니다.


확인해볼까요.




0x804a080(buf)에는 현재 우리가 입력한 AAAA %x %x %x %x %x %x %x %x %x %x이 들어있습니다. 여기서 포맷스트링을 만나면 0xbffff44c, 0xbffff450, ...이

출력되겠죠. 이 결과는 바로 전 사진을 보시면 확인할 수 있습니다.


이걸 확인했으니 현재 주어진 스택에서 어떤 걸 활용할 수 있는지 봅니다.


ebp 값을 확인해보면 0xbffff458 -> 0xbffff478 -> 0xbffff498 -> 0 인 걸 보실 수 있는데 전부 우리가 확인할 수 있는 스택입니다.

이 말은, 연결되어있는 ebp를 이용해, 처음 payload에서 ebp값을 우리가 원하는 임의의 주소로 변경하고 그 후에는 그 원하는 주소값에 원하는 값을 넣을 수 있다는 말입니다. 예를 들어 got_overwrite 같이요.


이게 무슨 말이냐고요? 


우선 조작방법부터 보여드리겠습니다.



%n은 현재까지 출력된 값들을 count해서 그 다음 주소값에 그대로 대입합니다.


%x 를 세번하고 %n을 한다는 것은 %x %x %x %x 를 했을 때 보여지는 네번째 %x주소에 값을 넣겠다는 말입니다.

이 말은, 사진에 보여진 네번째 %x, 0xbfc9f998 의 주소에 0x303e를 넣습니다.

4번째에 값을 넣은 건, 우리가 포맷스트링으로 스택에서 확인할 수 있는 ebp값이기 때문입니다.

이해가 잘 안되신다면 gdb-peda로 까놓은 스크린샷에서 스택을 자세히 확인해주시기 바랍니다!

( 스크린샷에서는 4번째 %x가 0xbffff478로 되어있네요. 0xbffff478에 값을 넣겠다는 뜻이 되겠습니다. )


16진수 303e는 10진수로 12350, 

%x (1) + 공백(1) + %x (1) + 공백(1) + %12345x(12345) + 공백(1) = 12350 ==> 0x303e


이렇게 변경되는 것을 확인할 수 있으니 어디에 공격할 수 있는지 확인합니다.




바이너리 상태를 확인해보니 스택에 dep 기법이 걸려있지 않네요. (그래도 저기는 사용못하니까 패스합시다.) 

1번은 buf와 response 주소가 존재하는 곳입니다.

2번은 스택이지만 임의로 주소값이 변하기 때문에 사용할 수 없습니다.


결과적으로 우리는 1번 주소, buf와 response에 쉘코드를 넣어서 공격을 할 수 있습니다.


이제 공격퍼즐을 구상해봅니다.


1. ebp구조를 이용해 스택에 원하는 주소값을 넣을 수 있다.

2. 공격할 주소를 찾았다. buf와 response 주소이다.

3. 바이너리의 구조를 이용하자. echo에서 puts가 실행되고 나면 main에서 fgets로 다시 값을 받는 부분이 나온다. fgets_got값에 buf주소를 overwrite 하자.


이렇게 구상을 했다면 payload를 짭니다.


첫번째 payload ==> %x %x %134520843x %n      ( 이렇게 넣으면 0x804a010, fgets_got 주소가 우리가 원하는 주소에 들어갑니다.)

두번째 payload ==> (300바이트 nopsled [보험으로.. 딱히 안써도 됩니다]) + (24바이트 쉘코드) + %x %x %x %x %x %x %x %x %x %x %134520568x %n

(300 + 24 + 134520568 + 공백 + 포맷스트링 10바이트 ==> 0x804a080 [buf 주소])


첫번째 페이로드에서 %x를 12번한 부분에 fgets_got 주소를 넣을 수 있고

두번째 페이로드에서 fgets_got 주소를 buf 주소로 overwrite시켜서 puts(response) 가 실행된 후 fgets 대신에 buf주소로 이동해서 쉘코드를 실행하게 됩니다.







원본 바이너리와 제가 작성한 exploit 코드입니다.


ebp_a96f7231ab81e1b0d7fe24d660def25a.elf


fsb.py