OverTheWire_Wargame Leviathan

[ level3 -> level4 ]




이번 문제는 trash 폴더가 숨김 속성으로 꽁꽁 숨어있었습니다. 해당 폴더로 들어가서 ls를 때려주면 bin이라는 바이너리가 존재하는데, 실행시켜보니 다수의 2진코드들이 나타났습니다. 일단 gdb로 분석 해 보도록 하죠.



일단 시작하자마자 fopen으로 파일 하나를 열어둡니다. 그리고 fgets 함수로 어떤 값을 받네요. 그리고 putchar 함수가 있는 것을 보니, putchar 함수를 통해 한 바이트씩 출력을 해 주는 것 같습니다.

fopen을 호출 할 때 상수값을 이용하여 인자를 입력받기 때문에 해당 상수에 어떤 값이 들어있는 지 확인해보도록 합시다.




fopen 함수는 f = fopen("/etc/leviathan_pass/leviathan5", "r"); 와 같이 실행이 된 모양입니다.

그리고 곧바로 if함수로 해당 FILE 포인터(ex. f)가 존재하지 않을 경우(파일이 존재하지 않을 경우) 프로그램을 즉시 종료시킵니다.

정상적으로 파일이 열렸을 경우에는 fgets로 값을 입력받는데, if문에서 FILE 포인터를 검사 할 때 사용했던 [esp+0x1c]를 다시 사용하는 것을 보니, leviathan5로 통하는 패스워드가 들어있는 파일을 읽어내는 듯 합니다.

분석해보면 fgets(buffer, 256, f); 와 같이 사용을 한 것 같습니다.

그리고 아래쪽으로 jmp 등으로 분기하는 부분이 여럿 있는데, 디버깅 능력이 부족하다 보니 이것까지 분석을 하지는 못했습니다.

하지만 putchar 함수를 이용하여 1바이트씩 찍어내는 부분이 있으며, 따로 암호화 하는 부분은 보이지 않기 때문에 해당 파일의 내용을 1바이트씩 출력한 것으로 예상하고 값을 찾아냈습니다.


01010100 : 0x54 : T

01101001 : 0x69 : i

01110100 : 0x74 : t

01101000 : 0x68 : h

00110100 : 0x34 : 4

01100011 : 0x63 : c

01101111 : 0x6f : o

01101011 : 0x6b : k

01100101 : 0x65 : e

01101001 : 0x69 : i

00001010 : 0x0a : 개행


Tith4cokei


Leviathan5 계정의 패스워드로 위 문자열을 집어 넣었더니 정상적으로 접속이 되었습니다.





OverTheWire_Wargame Leviathan

[ level3 -> level4 ]


Leviathan3로 접속을 하면 홈 디렉토리에 level3 바이너리가 우릴 기다리고 있습니다. 해당 바이너리는 leviathan4 권한으로 setuid bit가 걸려있기 때문에 이번 레벨은 level3 바이너리로 풀어내면 될 것 같습니다. 한 번 실행 시켜 보도록 하죠.



바이너리를 실행시키면 패스워드를 요구하는데, 패스워드가 틀렸을 때 오류 메시지를 내뱉고 프로그램을 즉시 종료시킵니다.

긴 문자열을 보내도 segmentation fault같은 메시지를 안 내뱉는 것을 보니 BOF 유형은 아닌 것 같아 보입니다. gdb로 분석해보도록 합시다.



main 함수를 disassemble 한 상태입니다. 중간에 보면 printf로 0x804878f에 들어 있는 정보를 출력합니다. 확인 해 보니 해당 문자열은 "Enter the password> " 였습니다. 이 문자열은 우리가 바이너리를 실행시켰을 때 패스워드를 입력받기 직전에 내뱉는 문자열이었습니다. 따라서 그 이후에 진입하년 do_stuff 함수에 패스워드를 입력 및 비교하는 루틴이 들어 있을 것이라고 예상 할 수 있겠습니다.

그렇다면 do_stuff 함수도 분석 해 보도록 합시다.



중간에 보면 strcmp 함수를 호출하는 것을 볼 수 있습니다. 또한, strcmp 이후 if문의 조건을 통해 루틴을 분기시키는 부분이 있네요. strcmp를 통해 패스워드 일치/불일치 여부를 확인 한 후 이 if문에서 프로그램을 종료시킬 지, 다음 루틴으로 넘어갈 지 판별하는 듯 합니다.

또한, 조금 더 아래쪽을 살펴보면 system 함수가 호출되는 것을 확인 할 수 있는데, 위의 mov 명령을 통해 esp에 들어있는 주소의 문자열을 인자로 함수를 실행시킵니다.

strcmp 함수와 system 함수의 인자에는 어떤 값들이 전달되는 지 확인해보도록 합시다.



strcmp 함수의 1, 2번 인자는 우리가 입력한 문자열과 "snlprintf"입니다. 즉, 우리가 입력한 문자열이 snlprintf일 경우 프로그램을 종료시키지 않고 다음 루틴을 실행한다는 뜻이 되겠죠. 그리고 값이 동일할 경우 0x8048760에 들어있는 문자열을 출력 한 후 0x8048774에 들어있는 문자열을 인자로 system 함수를 실행시킵니다. 0x8048760은 "[You've got shell]!", 0x8048774는 "/bin/sh"인 것을 보니, snlprintf를 입력받았을 경우 전자의 문자열을 출력하고 쉘을 띄워주는 프로그램인 것 같습니다. gdb를 종료하고 프로그램을 실행시켜봅시다.



leviathan4로 통하는 패스워드를 깔끔하게 뱉어줍니다.



OverTheWire_Wargame Leviathan

[ level2 -> level3 ]




레벨2에는 printfile이라는 바이너리가 자리 잡고 있습니다. 레벨1과 마찬가지로 setuid bit가 걸려있기 때문에 이 파일을 이용해서 레벨3로의 패스워드를 알아 내야 할 것입니다.

gdb로 한번 까 보도록 하겠습니다.



먼저 main을 disassemble 한 상태입니다.

위쪽은 파라미터 갯수 검증하는 부분이고, 빨간 체크박스부터 살펴보면 access 함수를 호출하는 것을 알 수 있습니다.

access 함수의 원형을 살펴보죠.

int access(const char *pathname, int mode);

2가지 인자를 받는데, 경로를 포인터로 받고 mode 또한 숫자 또는 상수로 받습니다.


R_OK(0x01)

 읽기 권한 여부

 W_OK(0x02)

 쓰기 권한 여부

 X_OK(0x03)

 실행 권한 여부

 F_OK(0x04)

 파일 존재 여부


함수 호출 규약에 따라 먼저 들어간 0x04가 mode, eax가 *pathname이 되겠군요.

스택에서 한번 어떤 값이 들어가는 지 살펴보도록 하겠습니다.



eax에 들어가는 값을 따라가 보니 argv[1]에 우리가 넣어줬던 파일 경로의 값이 들어갑니다.

그렇다면 여기서 호출하는 access 함수의 목적은 파일이 존재하는지 존재하지 않는지의 여부를 파악하고, 만약 존재하지 않을 경우 프로그램을 종료시키는 것이 될 것입니다.

사실 여기서 바로 /etc/leviathan_pass/leviathan3 로 넣어주면 깔끔하지 않느냐?

라는 질문이 있을 수도 있는데, 이유는 모르겠지만 그렇게 넣어주면 파일 미존재로 인해 프로그램이 종료되었습니다. 뭔가 다른 장치를 해 놓은 듯 합니다.

혹시 아시는 분이 계시면 댓글 부탁드립니다.


자, 그럼 이제 access 함수 이후에 어떤 행동을 하는지 더 살펴보도록 하죠.



access 함수 이후에 snprintf 함수를 호출하는데, sprintf와 동일하지만 버퍼의 길이를 제한하여 넣어준다는 차이가 있습니다.

이 함수는 대충 이런 모양이 될 것 같습니다.


snprintf(buffer, 0x1ff, "/bin/cat %s", argv[1]);


우리가 넣어 준 파일 경로 앞에 /bin/cat을 넣어주고 buffer 변수에 넣는거죠.

그리고 마지막엔 system함수의 인자로 buffer을 넣어줘서 결국 "/bin/cat 파일"을 실행하는 것입니다.

자, 여기까지 이해를 했다면 어떤식으로 문제를 풀어야 할 지 감이 오지 않나요?



/tmp/ 경로에 fstr3am_2라는 디렉토리를 만들어주고, 그 곳에 ".bashrc;sh"라는 이름의 파일을 하나 던져줍니다.

세미콜론 앞에 오는 문자열은 어떤 문자가 오던 간에 상관은 없습니다.

이렇게 한 이유는 access 함수를 우회하기 위해 실제로 존재하는 파일을 생성해주는것이고, 파일명을 /bin/cat에 붙여주기 때문에 결과적으로 버퍼에 들어가는 문자열은 아래와 같이 될 것입니다.


"/bin/cat .bashrc;sh"


이 프로그램에서는 실행을 위해 system 함수를 이용하기 때문에 system("/bin/cat .bashrc;sh") 라는 명령어가 완성이 되는군요.

결과적으로 /bin/cat .bashrc, sh 두 가지의 명령어가 따로 실행이 되는것입니다.



깔끔하게 쉘을 떨어뜨렸습니다. 권한이 leviathan3로 정상적으로 상승된 것을 확인하고, leviathan3의 패스워드를 뱉어내도록 합시다.

OverTheWire_Wargame Leviathan

[ level1 -> level2 ]




Level1입니다. ls를 때려보니 check라는 바이너리가 떡 하고 자리 잡고 있네요.

게다가 leviathan2의 id로 setuid가 걸려있습니다. 이 check 바이너리를 가지고 요리 하면 level2의 패스워드를 획득 할 수 있을것만 같습니다.

gdb로 까보도록 하죠.



check 바이너리를 실행했을 때 password가 맞지 않으면 에러를 내뱉는 구조이기 때문에 strcmp는 당연히 들어있을 것입니다.

main 함수를 disassemble 했을 때 strcmp@plt의 위치를 잡고 breakpoint를 걸어주도록 합시다.

그리고 strcmp의 구조 상 스택에 들어가는 순서는 source, destination, function call 입니다.

그러므로 자연스럽게 $esp+0x18(=$esp+4)는 src, $esp+0x14(=$esp)는 dst가 될 것입니다.

스택을 확인하면서 보도록 합시다.



일단 string값을 확인 해 보았는데, sex, god, love, secret 등 우리가 알아 볼 수 있는 단어들이 여럿 나열 되어 있습니다.

이 중 하나가 우리가 찾는 이 바이너리의 password 일 것입니다.



hex값으로 보면 esp+0과 esp+4만 확인하면 됩니다.

esp+4의 위치에 있는 'sex'가 바로 우리가 찾는 password였습니다.



'sex'를 password로 입력하자 bash shell이 떨어졌고, euid와 groups 권한을 보니 leviathan2로 상승 되어 있었습니다. 이제 leviathan2의 패스워드만 알아 내면 될 것입니다.

find로 leviathan2의 유저 권한을 가지고 있는 파일을 찾아보았더니 bandit과 유사한 경로에 leviathan2의 패스워드가 자리 잡고 있는 것을 확인 할 수 있었습니다.

cat으로 깔끔하게 패스워드를 뱉어줍시다.

OverTheWire_Wargame Leviathan

[ level0 -> level1 ]


Leviathan 시작합니다.



홈 디렉토리를 확인하니 backup 폴더가 숨김 속성으로 고스란히 자리잡고 있습니다.

들어 가 보니, bookmarks.html 파일이 하나 있는데, 용량이 큰 것으로 봐서 cat으로 출력하면 있던 답도 노답이 될것만 같습니다.

grep으로 왠지 있을것만 같은 leviathan을 필터링 해 봅시다.



아니나 다를까, leviathan1의 패스워드를 뱉어줍니다.

OverTheWire_Wargame Bandit

[ level25 -> level26 ]


와.. 드디어 마지막 문제입니다. bandit는 난이도가 어려운 편은 아니었지만 단계가 너무 많아서 시간이 오래 걸렸던 것 같습니다.

바로 시작합니다!


이번 문제는 bandit26은 /bin/bash로 로그인이 되지 않는다고 합니다. 어떤 식의 문제일 지 한번 살펴보죠.



아.. RSA key를 던져줬습니다. 이건 이전에 풀었던 문제와 동일한 문제인 것 같습니다. 오히려 더 쉽네요. 방법은 아래 글에 정리 되어 있습니다.


OverTheWire Bandit [Level13 -> Level14]




이렇게 bandit26이 나타나면서 끝이 납니다!


이렇게 putty로 keygen을 해서 접속하는 방법 외에 그냥 바로 ssh 명령어로 붙을 수도 있습니다.



해당 RSA key를 ssh -i 속성을 줘서 import 시키고 bandit26으로 접속하면 동일하게 클리어 가능합니다.





OverTheWire Bandit 끝!

OverTheWire_Wargame Bandit

[ level24 -> level25 ]




자, 이번 문제는 드디어 cron에서 벗어났네요.

한 데몬이 30002포트로 listening 상태에 있다고 합니다. 만약 bandit24의 패스워드와 함께 4자리의 숫자 핀코드를 제대로 때려박아준다면 bandit25로 향하는 패스워드를 알려주겠다네요.

전형적인 브루트포싱 문제입니다. 파이썬으로 코드를 짜서 돌리도록 합시다.



먼저, nc로 반응이 어떠한지 확인을 해 보았습니다.

한 번 연결하면 계속 입력을 받는 구조군요. 실패 시 출력 값은 Fail! You did not supply enough data. Try again. 입니다.

그런줄 알았는데....

Fail 부분은 제대로 된 길이의 인자를 전송하지 않았을 때 발생하는 에러 출력값이었습니다.

제대로 된 문자열을 전송했을 경우의 실패 메시지는 Wrong! Please enter the correct pincode. 입니다.

이걸 기반으로 코드를 짜보도록 하겠습니다.



4중 for문으로 브루트포싱하였습니다. 맨 처음 기본 출력값이 있기 때문에 recv를 한번 해주었고, 그 이후에는 send와 recv를 번갈아가며 실행, 실패 메시지가 출력되지 않았을 경우 그 문자열을 뱉어내는 코드입니다.



bandit25로 향하는 문을 열어줄 패스워드를 뱉어냈습니다!

OverTheWire_Wargame Bandit

[ level23 -> level24 ]




이번 레벨 또한 cron을 이용한 문제입니다.

NOTE 부분을 확인 해 보니 이번엔 우리가 쉘스크립트를 짜야 한다고 하네요.

바로 cron을 확인 해 봅시다.



역시 다음 레벨을 위한 cronjob_bandit24가 우릴 기다리고 있습니다.

내용은 /usr/bin/cronjob_bandit24.sh을 실행하는 것입니다.

cronjob_bandit24.sh에는 어떤 내용이 있는 지 살펴보도록 하죠.



이 쉘스크립트를 실행하는 주체는 bandit24이므로, $myname에는 bandit24가 들어 갈 것입니다.

그리고 경로를 /var/spool/bandit24로 바꿔주는군요.

그리고 해당 경로에 있는 모든 파일(와일드 카드로 *, .*이기에 숨김파일도 포함)의 이름을 i에 저장하면서 루프를 돌기 시작합니다.

현재 경로와 이전 경로를 나타내는 .과 ..을 제외한 나머지(모든 파일)의 경우 timeout 명령어를 이용하여 프로그램을 실행합니다.

그리고 프로그램의 실행이 끝나면 rm 명령어로 해당 프로그램을 삭제합니다.

모든 파일이 삭제 될 때 까지 루프를 도는 쉘 스크립트입니다.

그렇다면 /var/spool/bandit24로 넘어가서 쉘 스크립트를 짜보도록 하겠습니다.



이 쉘 스크립트의 이름은 test.sh로 해주었으며, bandit24로 통하는 패스워드를 1이라는 파일명으로 출력해주는 스크립트입니다.

처음에는 /tmp 경로에 폴더를 새로 만들어 출력 해 주려고 하였으나, 폴더를 만들 경우 bandit23에만 권한이 있기 때문에 파일 생성이 되지 않았습니다.

그래서 결국 동일한 경로에 생성을 해 주기로 생각하였습니다.

또한, cronjob_bandit24.sh의 경우 60초 주기로 돌기 때문에 1이라는 파일은 삭제가 되기까지 약간의 시간이 있을거라 생각하고 풀이하였습니다.



test.sh에 실행 권한을 주고 약간의 텀을 주면서 ls를 여러 번 실행 한 결과 1이라는 이름의 파일만 남게 되었고, cat으로 출력해주면 24레벨로 통하는 패스워드를 얻을 수 있습니다.

OverTheWire_Wargame Bandit

[ level22 -> level23 ]




이번 레벨도 cron을 이용한 문제네요. 저번 문제와 동일하게 쉘스크립트가 실행되고 있는 모양입니다. 확인 해 보도록 합시다.



/etc/cron.d/ 폴더 안에 cronjob_bandit23이 존재하고 있습니다.



cat으로 확인 해 본 결과, 역시 쉘스크립트를 실행하고 있네요.

스크립트는 echo I am user $myname | md5sum | cut -d ' ' -f 1 을 실행하고, 그 결과값을 파일명으로 패스워드를 저장하네요.

whoami의 결과는 현재 환경에서는 bandit22가 나오겠네요. 하지만 우리가 찾아야 하는 것은 bandit23의 패스워드이므로, $myname은 bandit23이 되어야 합니다.

md5sum은 md5 checksum을 확인하는 용도로, 해당 이름의 md5값을 반환시켜줍니다. 그리고 cut 명령어는 문자열을 자르는 substring과 비슷한 명령어입니다.

cut 명령어의 -d 옵션은 문자열을 자르기 위한 기준 문자를 지정해주는데, 위 스크립트에서는 공백을 기준으로 잡았습니다. -f 옵션은 필드를 뜻하는데, 첫 번째 필드에 있는 문자열을 반환하는거죠.

md5sum에 의해 "I am user bandit23"의 md5값을 반환시키는데, 본래 md5sum의 반환값이 [md5변환값 원래문자열] 이기 때문에, cut 명령어를 실행하면 md5변환값만 남게 됩니다.

자, 이제 동작 원리는 파악했으니 직접 명령어를 때려보도록 하죠.



$myname 부분을 bandit23으로 수정 후 바로 출력시켜줍니다.

반환 된 md5값을 파일명으로 cat 해보니 레벨23으로 갈 수 있는 패스워드가 출력되었습니다.

OverTheWire_Wargame Bandit

[ level21 -> level22 ]




이번 문제는 윈도우에서 예약작업에 해당하는 cron에 대한 것입니다.

/etc/cron.d/ 폴더를 확인하여 어떤 작업이 실행되고 있는 지 확인하라고 하네요. 한 번 확인해주도록 하죠.



해당 폴더에 들어가 보니 딱 bandit22라는 이름이 들어 가 있는 게 보이네요. cat으로 읽어줍니다.

/usr/bin/cronjob_bandit22.sh이라는 쉘스크립트를 실행시키네요. 이건 또 어떤 작업을 하는 지 확인 해봐야겠죠?



cat으로 읽어 준 결과, /etc/bandit_pass/bandit22 에 적혀있는 내용을 /tmp/t706lds9S0RqQh9aMcz6ShpAoZKF7fgv에 옮겨줍니다.

이 말은 bandit22의 패스워드를 저 파일 안에 넣어준다는 소리죠.

cat으로 저 파일을 읽어주면 다음 레벨로 통하는 패스워드를 획득 할 수 있습니다.


+ Recent posts