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의 패스워드를 뱉어내도록 합시다.

+ Recent posts