Format String Bug (FSB)

오랜만에 글을 작성하네요.
이번에 정리할 기법은 FSB입니다.

Format String Bug (FSB)가 일어날 일은 최근에 없지만 뭐,,프로그래머의 실수로 인해 생길 수도 있으니 알아두면 나쁘지 않을 것 같습니다.
먼저 이 글을 읽기 전에 프로그래밍을 해본 적이 있고 Format String에 대해 알고 있으면 편하게 읽으실 수 있습니다.

Format String은 C프로그래밍을 해보셨다면 printf() 함수를 이용하며 포맷에 맞게 출력을 할 때 보실 수 있었을 겁니다.


printf("I'm %s\n, name);


위의 코드는 간단한 name을 출력하는 코드입니다.


Format String Attack은 역시 프로그래머의 게으른 작은 실수로 인해 발생하는 취약점입니다.



/*

case 2

*/

printf("%s", name);


/*

case 2

*/

printf(name);



일반적으로 코드를 작성할 때에는 원하는 문자열을 포맷에 맞게 변수를 넘겨주게 코드를 작성하지만,

어느 몇몇 게으른 프로그래머는 case2 로 코드를 작성을 합니다.


물론 위의 2가지의 코드는 잘못된 코드는 아닙니다.

어떻게보면 case2의 코드가 case1의 코드보다 적은 양의 코드를 작성하고 더 현명하게 보이지만,

case2의 코드로 작성하게 되면 해커들에게 프로그램의 흐름을 바꿀 수 있는 취약점을 만든다는 사실을 깨달아야됩니다.


과연 case2의 코드에서는 무엇이 잘못일까요?


case1의 코드는 printf() 함수를 사용할 경우 출력할 형식 포맷만큼 출력시킬 변수를 인자에 넘겨주게 되지만,

case2의 코드는 사용자의 입력에 따라 지시자의 수가 달라질 수 있게 되므로, 악의적인 공격자에 의해 스택의 내용이 확인될 수 있는 것이 문제입니다!


name에 '%s, %d, %n' 등의 지시자들이 있다면 그 지시자에 맞는 값들을 출력하게 되어 스택의 내용이 보여지게 되는 것입니다!


앗...? 그럼 %n 지시자는 무슨 역할을 하는 지시자일까요?


%n 지시자는 출력된 갯수를 카운트하여 그 값을 주소에 저장을 시키는 지시자입니다!

말로 해봤자 이해가 어려울 수도 있으니 코드를 디버깅하며 이해를 해보도록 하겠습니다!



#include <stdio.h>


int main(int argc, char * argv[])

{

int vlun = 100;


printf("now vlun is %d\n", vlun);

        printf("1234567890%n\n",&vlun);

printf("but, now vlun is %d\n", vlun);

return 0;

}



위의 코드는 간단한 코드이기에 이해를 하실 수 있으실 것입니다!

vlun 변수에는 100이라는 값이 들어있기에 처음 printf 출력에서는 now vlun is 100 라는 문자열이 출력이 될 것이라는 것은 알 수 있습니다.

이제 8번째 라인 코드에서부터가 중요합니다.

위에서 말 했듯이 %n 지시자는 출력된 갯수를 카운트를 하여 그 값을  주소에 저장을 시키는 지시자라고 하였습니다.

앞에 출력되는 "1234567890" 은 총 10개라는 것은 초등학생도 알 수 있습니다.

%n 지시자에 &vlun 주소가 넘겨지며 이 주소에 10이라는 값이 저장이 되게 되는 겁니다!

그렇다면 마지막 출력에서는 but, now vlun is 10 라는 문자열이 출력이 될 것이라 알 수 있습니다.

실제로 그렇게 출력이 되는지 확인을 해보도록 하겠습니다.



실제로 그렇게 출력이 되는 것을 디버깅을 통해 알 수 있었으며, %n 지시자가 무슨 역할을 하는지 디버깅을 통해 이해할 수 있었습니다!


이제 %n 지시자 하나만 알고 있다면 여러분은 fsb attack을 할 수 있습니다!

간단한 2가지 문제를 풀며 이해를 하도록 하겠습니다.


이 2가지의 문제들은 root-me.org 라는 워게임 사이트의 문제입니다.



#include <stdio.h>

#include <unistd.h>


int main(int argc, char *argv[]){

        FILE *secret = fopen("/challenge/app-systeme/ch5/.passwd", "rt");

        char buffer[32];

        fgets(buffer, sizeof(buffer), secret);

        printf(argv[1]);

        fclose(secret);

        return 0;

}



지금까지의 글을 읽어보시고 이해를 하셨다면 위의 문제에서의 취약점이 어디에 존재하는지 한번에 아실 수 있으실겁니다.

printf(argv[1]); 

argv[1] 인자 넘겨주는 값을 그냥 printf 함수로 출력해주는 것을 알 수 있었습니다.

그리고 secret 값이 buffer에 들어가는 것도 볼 수 있네요 (웃음)


그렇다면 매우 많이 문자열을 출력해준다면 시크릿 값을 볼 수 있게 됩니다.



이제 보시다보면 스택 구조의 주소들 중 이상한 문자열이 중간에 껴있음을 알 수 있습니다.


리틀엔디안, 빅엔디안 잘 보시고 플래그를 뽑아내시면 됩니다!


39617044 -> 44706139


2번째 문제는 %n 지시자를 이용하는 문제입니다!!!



#include <stdio.h>

#include <stdlib.h>


int main( int argc, char ** argv )


{


int var;

int check  = 0x04030201;


char fmt[128];


if (argc <2)

exit(0);


memset( fmt, 0, sizeof(fmt) );


printf( "check at 0x%x\n", &check );

printf( "argv[1] = [%s]\n", argv[1] );


snprintf( fmt, sizeof(fmt), argv[1] );


if ((check != 0x04030201) && (check != 0xdeadbeef))

printf ("\nYou are on the right way !\n");


printf( "fmt=[%s]\n", fmt );

printf( "check=0x%x\n", check );


if (check==0xdeadbeef)

    {

printf("Yeah dude ! You win !\n");

      system("/bin/dash");

    }

}



이 문제의 취약점은 어디에 일어날까요?

printf( "fmt=[%s]\n", fmt );


여기서 일어날 것이라는 걸 알 수 있습니다!

이 문제는 매우 친절하게 check의 주소를 알려주고 check를 deadbeef로 값을 바꾸면 되는 걸 알 수 있습니다!! 빠빰!!!

왜 check 값을 0xdeadbeef 로 바꿔야되냐구요? (웃음)


if (check==0xdeadbeef)

{

printf("Yeah dude ! You win !\n");

system("/bin/dash");

}


바로 위의 코드때문입니다!


그렇다면 문제를 직접 풀면서 이해를 해보도록 하겠습니다.



check 의 주소는 0xbffffb78이며 저희가 넘겨준 1이 잘 출력되는 것과 check의 값은 0x4030201이라고 아주 자세하게 알려주고 있습니다.

그렇다면 이 문제를 풀기위해서는 0xbffffb78 주소를 넘겨주고 0xdeadbeef를 넘겨주면 되겠군요?!


앗 하지만 여기서 매우 크나 큰 관문이 생겼습니다.


0xbffffb78의 값은 3221224312 입니다.

int의 범위의 값을 넘어서버렸습니다..

벗어난 값을 입력하게 되면 오버플로우가 떠버릴지도 모른다구요~? (웃음 wwww


그래서 2바이트 2바이트씩 나눠서 넣어주면 됩니다.

0xbffffb78 : fe eb 00 00

0xbffffb7a : 00 00 da ed

이렇게 넣게 된다면?

0xbffffb78 : fe eb da ed


가 되게 되서 0xdeadbeef가 들어가게 되겠죠?!


먼저 우리가 원하는 주소가 스택에 있는지 찾아보기위해 무작위로 원하는 개수 만큼 "-%8x-"*?? 를 넣어줍니다.



이런! 없군요! 하지만 우리가 넘겨준 argv의 값이 출력되는 것을 볼 수 있습니다.

이걸 이용해 우리가 입력한 값으로 주소로 넘겨줄 수 있겠네요?


바로 이렇게 말이죠!



보시면 우리가 입력한 AAAA가 스택에 보이는 것을 볼 수 있어요.

이렇게 되면 우리가 입력한 값이 주소가 될 수도 있다는 것을 깨달을 수 있죠 (큰 깨달음


그렇다면 주소를 넘겨주고 0xdeadbeef를 만들면되겠네요!



익스플로잇 코드는 위와 같습니다! 

이제 알아보도록 하겠습니다.

먼저 check의 주소를 먼저 넘겨주면 스택에 들어가 가젯역할을 할 수 있게 됩니다!

그 후 dummy의 값을 주고 check의 2byte 만큼 높은 주소를 줍니다!

왜 2byte 높은 주소를 주는 이유는 위에 있습니다!

그 후 이제 %8x 로 스택의 현재 넘겨준 가젯의 위치를 잡습니다!

그 후는 계산만 남아있는데요!


먼저 0xbeef가 들어갈 예정입니다.

0xbeef의 값은 48879 입니다.

다만 앞에 있는 값들을 계산해줘야되겠죠?

0xbeef - (4 [0xbffffb48] + 4 [dummy] + 4 [0xbffffb48+2] + 8*7) = 48811 입니다.

그 뒤에는 0xdead - 0xbeef 한 값을 넘겨주면 되겠습니다!

0xdead - 0xbeef = 8126


그렇게 된다면 check의 주소인 0xbffffb48에는 0xbeef이 들어가고 0xbffffb4a에는 0xdead가 들어가 0xdeadbeef가 완성되게 되는거죠!


하지만 여기서 왜 더미가 들어가야될까요?

그 이유는 아래와 같습니다.

현재 포인터는 0xbffffb48을 잡고 값을 들어가게 됬습니다.

%n 지시자를 통해 주소는 4바이트 상승해서 AAAA 인 0x41414141을 잡고 있겠죠!

하지만 %8126c 인 %c 지시자를 사용하게 되어 4바이트 상승하여 그 다음 주소를 잡고 있게 되어 그 주소에 값이 들어가게 되는 것 입니다!


이렇게 위의 2문제를 풀며 Format String Bug(FSB) 기법을 볼 수 있었습니다.

'0x20 Security > 0x21 System' 카테고리의 다른 글

Save Frame Pointer Overwrite (SFO)  (0) 2016.07.25
Buffer Overflow (BOF)  (2) 2016.07.22
About LD_PRELOAD  (0) 2016.07.20
Use After Free (UAF)  (1) 2016.07.13
How main() is executed on linux  (2) 2016.07.04

+ Recent posts