FTZ level18 포인터 특성 활용

2017. 5. 28. 17:42SystemHacking/FTZ

 

 

 

level18의 힌트파일을 보면 " File Descriptor " 개념이 나옵니다. 소스파일을 읽기 어렵게 만들어놓은 함정입니다

switch문만 제대로 파악하면 문제는 풀리지만 나머지 소스에 대해서 설명해 드리겠습니다

그럼 힌트파일을 열어보도록 하겠습니다


< 그림 18.1 >


소스가 엄청 깁니다 천천히 따라서 읽어보세요

 

#include <stdio.h>

#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>

void shellout(void);


int main()

{

  char string[100];

  int check;

  int x = 0;

  int count = 0;

  fd_set fds;                    //  fd_set 타입의 fds 변수선언 ( 파일디스크립터의 집합 or 소켓 구조체 )


  printf("Enter your command: ");                    

  fflush(stdout);              // 출력버퍼를 모두 비운다 ( 목적지로 모두 이동시킨다 )


  while(1)                    // 무한 루프입니다 

    {

      if(count >= 100)                    

        printf("what are you trying to do?\n");

      if(check == 0xdeadbeef)                                        // 우리가 공격해야할 대상입니다

        shellout();                                                         // shellout함수가 실행되어지는 조건문

      else

        {                                                                    // fds는 파일디스크립터들의 집합(소켓구조체) 입니다

          FD_ZERO( &fds );                                             // fds의 비트를 0으로 초기화

          FD_SET( STDIN_FILENO , &fds );                          //  fds를 키보드의 입력으로 설정

 

          if(select(FD_SETSIZE, &fds, NULL, NULL, NULL) >= 1)                 // ①

            {

              if(FD_ISSET(fileno(stdin),&fds))                                          //  ②

                {

                  read(fileno(stdin),&x,1);                                               //  ③ 입력값을 가져오는 과정입니다 

                  switch( x )                                                                // 아래쪽에서 추가 설명하겠습니다

                    {

                      case '\r':

                      case '\n':                                                            // "Enter" 가 입력되면

                        printf("\a");                                                       // "\a" 출력

                        break;

                      case 0x08:                                                           // "0x08" 입력되면

                        count--;                                                            // count = count - 1

                        printf("\b \b");                                                  // "\b \b" 출력

                        break;

                      default:                                                              // 다른 문자들이 입력되면

                        string[count] = x;                                               // string변수에 해당 문자를 전달

                        count++;                                                         // count = count + 1

                        break;

                    }

                }

            }

        }

    }

}

 

void shellout(void)                                                            // level19의 권한으로 쉘을 실행하는 함수

{

  setreuid(3099,3099);

  execl("/bin/sh","sh",NULL);

}   



[ 키보드로 문자를 입력하고 해당 문자를 변수(x)에 저장하는 과정입니다 ]                     


과정 ① 

select( A, B, C, D, E ) : fd구조체를 모니터링 하는 함수입니다

리턴값 : 변화가 발생한 파일디스크립터의 개수를 반환한다 ( 오류발생시 : -1 / 타임아웃시 : 0 )

A : 검사할 파일디스크립터의 범위를 지정한다 ( 검사할 파일의 개수 )

B : 즉시 입력 가능한지 시스템에 의해 확인된다

C : 즉시 출력이 가능한지 시스템에 의해 확인된다

D : 에러가 발생했는지 시스템에 의해 확인된다

E : 무한 대기 상태를 방지하는 타임아웃을 설장한다


=> if ( select(FD_SETSIZE, &fds, NULL, NULL, NULL) >= 1 )    ( 변화된 파일디스크립터의 개수가 1개 이상이라면 )

=> 1024 바이트만큼 검사하고 " fds " 파일디스크립터에 입력할 때만 조건이 만족한다 


과정 ②

if ( FD_ISSET( fileno(stdin) , &fds ) )

FD_ISSET함수는 fds 소켓 구조체에서 stdin의 소켓번호에 해당하는 지점에서 변화가 있었으면 해당 번호를 리턴한다

( fds구조체가 stdin으로 전달한 적절한 정보를 가지고 있는지 확인하는 것 )

( 쉽게말하면, 키보드(stdin)로 입력한 값이 이상이 있는지 여부를 파악 )



과정 ③

read( fileno(stdin) , &x , 1 )

stdin의 소켓번호에 해당하는 입력버퍼에서 1byte만큼의 데이터를 읽어들여 " x "변수에 저장합니다

또한, stdin의 소켓번호는 1 만큼 증가합니다


위의 3개의 과정을 통해서 문자를 입력받고 하나의 문자를 읽어들여서 switch문으로 검사합니다


소스 분석은 끝났고 어떤 식으로 공격을 해야할지에 대해 생각해봅시다.

우선 해당 공격대상에 대한 gdb분석을 통해서 메모리 구조에 대해 알아야 합니다


힌트파일을 복사해서 새로운 파일을 생성해서 gdb분석을 시작합니다 ( 소스파일이 매우 깁니다 )


< 그림 18.2 >


main함수를 디스어셈블한 후 소스분석을 시작하겠습니다


< 그림 18.3 >


[1] Procedure Prelude

0x110 = 272byte만큼의 공간을 지역변수에게 할당합니다


[2] $0xfffffff0 , 0xffffff80 , 0xffffff7c

변수 check , x , count들을 선언 했을 때의 주소입니다. 소스가 진행되면서 CPU에 의해서 주소가 변할 수도 있습니다

먼저 선언한 순서대로 stack에 높은 숫자로 쌓이는 것을 확인할 수 있습니다 

처음 해당주소를 보자마자 변수들의 주소라고 확정하는 것이아니라 뒤쪽부분과 함께 분석해서 알아낸 결과입니다


[3] <main+71>:    cmpl    $0x63 , 0xffffff7c

if ( count >= 100 )

jle = Jump if Less or Equal : 100보다 작거나 같으면 <main+96>으로 점프한다

* 해당 부분에서 count의 주소를 알 수 있었습니다 !


[4] <main+96>:    cmpl $0xdeadbeef , 0xffffff84

if ( check == deadbeef )    

일치하면 shellout 함수를 실행시킵니다. 그 후 다시 while의 처음으로 이동한다

* 해당 부분에서 check의 주소를 알 수 있었습니다 ! ( 처음 선언한 주소에서 이동되었음 )


< 그림 18.4 >


그림에 써놓은 것처럼 해당 함수들의 어셈블러 코드입니다. 그림 참고하시면 됩니다


< 그림 18.5 >


[1] <main+288>~<main+332> 

" switch( x ): case " 함수 부분입니다

cmpl: $0x08 , 0xfffffeec 코드에서 "0xfffffeec" 는 x의 주소임을 알 수 있습니다

소스코드에서 봤던 것처럼 조건에 따라서 count변수의 값이 변하는 것을 알 수 있습니다

어셈블러 dec ( descend ) , inc ( increment ) 를 이용하고 있다 ㅡ 그림 18.5 , 6


< 그림 18.6 >


gdb분석을 통해서 해당 변수들의 주소들을 알아내었습니다. 그림을 그려서 나타내보겠습니다


< 그림 18.7 >


이제까지 버퍼 오버 플로우 문제에서는 입력문자가 들어가는 변수가 제일 앞쪽에 있었습니다

그래서 버퍼 오버 플로우 시켜서 원하는 주소에 원하는 값을 덮어씌어주면 되었습니다

level18은 입력문자가 들어가는 변수가 뒤쪽에 있어서 지금까지와는 다른 방식으로 공격해야 합니다


주소값을 알았는데, 어떻게 check 변수에 deadbeef 라는 값을 덮어씌울까? " 포인터의 특성을 이용합니다 "

일단 gdb분석을 통해서 각 변수의 주소들을 알아냈었습니다


&string = 0xffffff88

&check = 0xffffff84

&x = 0xffffff80

&count = 0xffffff7c


각 변수들 사이에 dummy는 없고 각 변수의 크기 4byte씩만큼 간격을 두고 stack에 쌓여있습니다

( 배열의 이름은 해당 배열의 첫번째 주소값이 된다 )

( 만약 char *ptr = string 이라고 가정한다면 )

string[-12] 변수 x의 시작주소입니다 ( = (ptr-3) )

string[-8] 변수 count의 시작주소입니다 ( = (ptr-2) )

string[-4] 변수 check의 시작주소 ( = (ptr-1) )

string[0] 은 string의 시작주소입니다 ( = (ptr+0) )


해당 포인터와 배열의 원리를 알았으니 문제에 적용시켜보도록 합시다

변수 count = -4 에서부터 count = -1 지점까지 0xdeadbeef 값을 넣어준다면 check=deadbeef 조건이 만족될 것이다

공격순서는 먼저 "0x08" 을 입력해서 count = count - 1 시켜 count를 -4 로 만들어줍니다

그 다음 deadbeef 를 순서에 맞게 입력해서 count = -4 ~ count = -1 까지 입력하면 됩니다

그리고 0x08 문자를 입력시키려면 스크립트를 통해서 실행파일로 전달시켜야합니다

함수의 코드에서 한문자 씩만 읽어들이도록 설계되어있어서 직접 입력하면 "0" "x" "0" "8" 문자로 인식되어집니다


< 그림 18.8 >


16진수 0x08 을 4번 입력시켜서 count = -4 가 되었습니다. 그리고 count=-4 ~ count= -1 지점에 0xdeadbeef 를 입력시켰습니다.

level19의 쉘이 떨어졌습니다 ! level19로 넘어가겠습니다.













'SystemHacking > FTZ' 카테고리의 다른 글

FTZ level20 포맷 스트링 버그 [FSB]  (0) 2017.05.30
FTZ level19 Return to Libc  (0) 2017.05.30
FTZ level17 포인터 변조 2  (0) 2017.05.28
FTZ level16 포인터 변조 1  (0) 2017.05.23
FTZ level15 분기 루틴 2  (0) 2017.05.23