wargame/Lord Of Buffer overflow

[LOB] gremlin

LimeLee 2018. 8. 7. 02:47


소스코드 분석



argc가 2개 미만 즉 인자가 없으면 프로그램을 종료하고, 아니라면 첫 번째 인자 값을 버퍼에 복사한 뒤 출력한다.

simple BOF문제이다. 



문제 풀이


위의 소스에서 보았듯이 BOF문제다. 문제는 strcpy 함수를 사용하고 있기 때문에 BOF가 발생한다.

strcpy 함수는 메모리 경계를 검사하지 않아 buffer의 크기보다 큰 길이의 값을 복사하게 되면 buffer를 넘어서 다른 값 까지 덮어씌운다.

그렇다면 buffer의 크기를 넘어선 값을 입력하게 되면 무슨 일이 일어나는지 확인해보자.



다음은 gremlin 프로그램에 A를 261개 삽입했을때 strace를 이용하여 바이너리를 추적한 결과값이다. ret 값을 덮어쓰기 시작하여 segmentation fault가 발생한 것을 알 수 있고, 261번째 A부터 ret 주소를 덮어 씌우는 것을 확인 할 수 있다.

261번째 값부터 쉘코드가 있는 주소로 덮어 씌운다면 ret을 할 때 쉘코드의 위치로 점프하고 쉘코드가 실행될 것이다.

쉘 코드를 어디에 삽입하는 지에 따라 찾아야 할 주소가 다르겠지만, buffer의 크기가 256으로 매우 큰 값이므로 입력값에 쉘코드를 삽입하여 입력값이 스택에 저장되는 주소를 찾아낸다.



취약한 strcpy 함수는 main+54에서 실행된다.



breakpoint를 main+59에 걸고 256개의 인자값을 삽입한 뒤 스택 주소를 확인한다.

32bit 프로그램이므로 스택 레지스터는 $esp이고 0xbffffc08부터 시작하는 것을 확인 할 수 있다.


참고로 다른 사용자의 setuid가 걸린 프로그램은 strace는 물론이고 gdb를 통해 실행시킬수 없다. 

cp 명령어를 통해 자신의 setuid가 걸린 프로그램을 하나 생성해주어 테스트를 진행하도록 한다.

LOB는 자신의 홈디렉토리에 쓰기 권한이 있다. 문제 프로그램을 덮어씌우면 다음 문제의 쉘을 획득할 수 없으므로 덮어 씌우지 않도록 조심하자.


main+59에 브레이크를 걸었던 이유는 문제를 풀 당시에는 strcpy함수를 call하는 것이기 때문에 strcpy 함수에서 main함수로 다시 돌아올 때, 문제가 발생할 것이라고 예상했었다. 예상대로라면 bof가 일어나는 페이로드를 요청시 main+59에 브레이크가 걸리기 전에 다른 주소로 점프하여 브레이크가 걸리지 않아야하지만 bof가 일어나거나 아닌 페이로드를 넣어도 main+59에서 브레이크가 걸렸다.

buffer[256]은 main에서 선언된 변수이고 strcpy는 이 main함수에서 선언된 buffer에 값을 복사하는 것이기 대문에 strcpy의 ret 주소를 덮어씌운것이 아닌 main 함수가 프로그램을 종료할 때 사용하는 ret 주소를 덮어씌우게 된다. gdb로 디버깅을 해보면 알겠지만 main 함수의 ret 명령어를 실행시키면 bof가 발생하는 것을 확인할 수 있다.



페이로드를 "( 쉘 코드 ) + DUMMY * (260-쉘 코드 길이) + (쉘코드 주소)" 로 생각했다.

다만 왜인지 모르겠지만 스택에 들어가는 입력값이 주소가 조금씩 바뀌어서 쉘코드의 첫 주소를 완벽하게 알아내기가 힘들었다.

코딩을 할 때 존재하지 않는 명령어를 넣으면 에러가 나듯, 쉘 코드가 시작하는 첫 주소를 완벽하게 가르키지 않으면 쉘코드가 아닌 이상한 명령으로 인식하여 쉘을 획득할 수 없다. 


그래서 NOP sled 기법을 사용한다. NOP이라는 명령어는 no operation의 약자로 아무것도 하지 않는 명령어이다. 하지만 NOP은 존재하지 않는 명령어가 아니라 존재하는 명령어지만 프로그램에 아무런 동작을 주지 않는 명령어 일 뿐이기에 에러가 나지 않고 다음 명령어를 실행시키게 된다. NOP sled기법은 NOP 명령어를 길게 나열함으로서 대략적인 주소를 알고 있더라해도 NOP이 있는 아무 주소에만 도착하면 NOP은 다음 명령을 불러와 결국 쉘 코드를 실행시키게 된다. 


위와 같은 이유로 페이로드를 "NOP * 200 + ( 쉘 코드 ) + DUMMY * (260 - (200 + 쉘 코드 길이)) + (입력 값의 대략적인 주소)" 로 변경한다. 입력 값의 대략적인 주소는 정확히 말하면 NOP * 200개가 들어가있는 주소 중 한 군데를 말한다.



페이로드를 인자 값으로 보내게 되면 gremlin의 쉘을 획득할 수 있다. 대략적인 주소값은 0xbffff930으로 정했다.


여기에서도 입력 값의 대략적인 주소는 bfff~~ 가 될 텐데, LOB의 서버에서 사용하는 bash 쉘 버전이 너무 낮아 주소를 입력하는 과정에서 \xff 를 \x00이라 인식하여 쉘의 주소로 이동할 수 없다. 문제를 풀 때에는 bash2 명령어를 사용하던가, root의 권한으로 일단 이동한 뒤, /etc/passwd에서 사용자의 로그인 쉘을 /bin/bash에서 /bin/bash2로 바꿔준 뒤 LOB를 풀어야 한다.



bash에서 쉘 코드를 넣었을 때에 덮어씌워진 주소값은 \x01\x01\x00\x40으로 \xff가 인식되지 않았지만
bash2에서 같은 코드를 삽입했을 때는 정상적으로 \x01\x01\xff\xff가 덮어 씌워진 것을 확인할 수 있다.


'wargame > Lord Of Buffer overflow' 카테고리의 다른 글

[LOB] orc  (0) 2018.08.13
[LOB] goblin  (0) 2018.08.13
[LOB] cobolt  (0) 2018.08.07