OverTheWire Behemoth 

[ level2 -> level3 ]




베히모스 lv2입니다. 실행 시켜보니 touch 명령어로 2520이라는 이름의 파일을 만들며, sleep을 긴 시간 넣어 둔 것 같습니다. 실행을 멈추고 gdb로 disassemble 해보도록 합시다. 



sprintf 부분에서 buffer에 "touch %d"를 넣어주고 있습니다. %d의 값으로는 getpid로 가져 온 pid 값을 집어 넣어 주고 있습니다.

두 번째 박스에서는 system 함수로 아까 저장한 buffer의 내용을 실행시켜줍니다. 즉, "touch PID"를 실행 시키는거죠.

여기서 touch 명령어는 절대 경로가 아닌 'PATH' 환경변수에 지정된 경로에 존재하는 touch 바이너리를 실행시키는 것입니다. 따라서 PATH의 값을 바꿔준다면 이 touch를 이용해서 다른 행위를 할 수 있을 것입니다.



env 명령어를 이용해 환경변수를 확인 해 본 결과입니다. 원하는 경로에 touch 명령어를 생성해주고, 이 명령어를 통해 다음 레벨로 갈 수 있는 패스워드를 알아내는 쉘스크립트를 작성한 후 PATH의 값을 변경해주면 다음 레벨의 패스워드를 알아 낼 수 있을 것 같습니다.



환경변수는 앞에 있는 값부터 확인 하기 때문에 += 연산이 아닌 =연산으로 모두 뜯어고칠 예정이므로, cat 명령어도 절대경로로 지정해주었습니다.



export명령어를 이용하여 PATH의 값을 우리가 만들어준 touch 명령어가 있는 경로로 바꿔주고, behemoth2 바이너리를 실행시키면 다음 레벨로 갈 수 있는 패스워드를 알아 낼 수 있습니다.



[ * ] Specially thanks to Zairo!

OverTheWire Behemoth 

[ level1 -> level2 ]


behemoth 레벨1입니다. narnia와 달리 소스코드가 주어지지 않아 gdb로만 분석을 해야하는 불편함이 있습니다.

바로 확인 해 보도록 합시다. 



/behemoth 디렉토리로 이동하여 behemoth1 바이너리를 실행시켰을 때의 결과입니다.

패스워드를 요구하며, 패스워드에 부합하지 않을 시 "Authentication failure." 라는 메시지를 내뱉고 프로그램을 종료시키는 듯 합니다.

소스코드가 없으니 gdb를 이용해서 main 부분을 확인 해 보도록 하죠.



main 함수를 보니 인증 부분 자체가 없습니다. 그냥 gets 함수로 값을 입력받고, puts 함수로 오류 메시지를 내뱉고 종료시키네요.

인증이고 뭐고 BOF를 이용해서 쉘을 띄우라는 의도인 것 같습니다.

BOF를 위해 입력받은 값이 어디로 들어가는 지 확인 해 보았습니다.



ebp-0x50 부분을 확인 해 보니, 0xffffd5fe부터 값이 차례로 들어가는 것을 알 수 있었습니다.

이제 main 함수의 return address를 찾아야 하는데, 하나씩 확인 해 보니 0xf7e3da63 부분이 __libc_start_main 함수로 이어지는 것을 알 수 있었습니다.

다음 그림을 보도록 하죠.



__libc_start_main 함수에서 main 함수를 호출 한 이후 모든 루틴을 끝냈을 때 다시 __libc_start_main 함수로 돌아와 exit함수를 호출하며 프로그램을 종료시키는 형태입니다.

이를 return address로 간주하고 풀이하도록 하겠습니다.

이 return address와 buffer 사이의 간격이 79byte이기 때문에 문자 'A'를 총 79번 입력 한 이후 쉘코드가 있는 주소를 적어주면 쉘이 띄워질 것입니다.



gdb상에서 A를 79번 입력하기 위해 임시로 'a'라는 파일을 만들어 주었습니다.



gdb로 실행시켜 해당 return 값의 직전까지 값이 들어가는 것을 확인한 결과 화면입니다.

다음으로, 쉘코드를 담을 공간을 확보하는 것인데, 첫번째 방법으로는 우리가 A로 집어 넣은 버퍼 공간에 쉘코드를 집어 넣는 방법이 있으며, 두번째는 환경변수를 이용하여 쉘코드를 입력 한 후, 그 주소를 ret 값에 덮어 씌우는 방식입니다. 저는 두번째 방법인 환경변수를 이용하는 공격 기법으로 풀이하도록 하겠습니다.



export 명령어를 이용해 EGG라는 이름의 환경변수를 만들어주고, 그 값으로 0x90을 100번 반복하고 그 뒤에 쉘코드를 입력해주었습니다. 0x90을 여러번 입력 해 주는 이유는 0x90이 NOP(아무 행동도 하지 않고 다음에 나오는 명령을 실행)의 의미이기 때문에 주소의 오차 범위를 줄임과 동시에, 스택 상에서 환경변수의 주소를 찾기 용이하게 하기 위해 입력 해 주었습니다.



gdb에서 스택을 확인하였더니 그림과 같이 0x90의 반복되는 부분을 확인 할 수 있었습니다. 이 중 아무 주소나 선택하면 되는데, 저는 0xffffd8b8을 리턴 주소로 선택하여 exploit 하도록 하겠습니다.



위에서 구한 EGG 환경변수의 주소를 ret 값에 덮어 씌워주면 behemoth2 권한의 쉘을 띄울 수 있습니다.





OverTheWire Behemoth 

[level0 -> level1]



OverTheWire Behemoth의 첫 번째 문제입니다.




다른 난이도와 동일하게 /behemoth 디렉토리에 문제가 자리 해 있습니다. 하지만 다른 난이도와 다른 점이 있다면 원본 소스코드를 지원 해 주지 않는 다는 점이 있겠네요. 

현재 behemoth0 계정이므로 behemoth0 바이너리를 실행시켜보았습니다. 패스워드를 요구하며, 패스워드가 맞지 않을 경우 Access denied.. 를 출력하며 프로그램을 종료시킵니다. 만약 지정된 패스워드를 입력 할 경우 쉘이 떠지는 방식인 것 같습니다.

gdb로 루틴을 확인 해 보도록 하겠습니다.



main 함수를 disassemble 한 결과, 중간에 strcmp 함수(두 번째 네모 박스)가 있는 것을 확인 할 수 있습니다. esp+0x1f와 esp+0x2b를 비교하는데, esp+0x2b는 위의 scanf에서 받아오는 문자열이 저장 된 위치입니다. 그리고 esp+0x1f는 시작 부분에서 0x475e4b4f에 있는 문자열을 저장 해 놓은 위치입니다. 이 문자열은 암호화 된 상태로 입력되었으며, 첫 번째 네모 박스에 있는 memfrob 함수를 이용하여 복호화합니다. 다음은 memfrob 함수의 원형입니다.



 void * memfrob(void *s, size_t n); 


문자열 s의 각 요소를 '42'와 XOR 하는 함수입니다. 무조건 '42'와 XOR 하기 때문에 암호화라기 보다는 문자열을 숨기기 위한 임시 방편이라고 생각 하는 편이 좋을 것 같습니다. 처음 memfrob을 사용하면 원래의 문자열을 xor 하여 암호화 하고, 다시 memfrob 함수를 사용하면 복호화 되는 방식입니다. 위에서 설명 했듯이, esp+0x1f에 입력 되어 있는 문자열은 memfrob함수로 이미 암호화 된 값이 들어가 있기 때문에 첫 번째 네모 박스에서 복호화를 진행하는 과정이라고 생각하면 되겠습니다.

이 복호화 된 값과 입력 받은 값을 strcmp 하여 값이 같지 않으면 main+195로 점프하여 프로그램을 종료시키네요. 분기하지 않았을 경우는 main+188(세 번째 네모 박스)에서 execl을 호출하는 것으로 보아 쉘을 떨어뜨려주는 것 같습니다.

그럼 memfrob 함수를 호출 한 이후에 breakpoint를 걸어 esp+0x1f에 들어있는 문자열을 확인 해 보도록 하겠습니다.



저는 main+129의 위치에 breakpoint를 걸어주고 값을 확인 했습니다.

해당 위치에는 "eatmyshorts"라는 문자열이 들어 가 있었습니다. 이 문자열을 바이너리를 실행 했을 때 패스워드 부분에 입력 해 주면 쉘이 떨어 질 것 같습니다.



- password : eatmyshorts




예상대로 Access granted.. 라는 메시지를 띄워주면서 쉘을 뱉어냈습니다. behemoth1 권한을 가지고 해당 레벨로 가는 패스워드를 얻어냈습니다.


+ Recent posts