관리 메뉴

c0smicb0y

[Lord of BOF] Level 14 (bugbear -> giant) 본문

정보보안/Lord of BOF

[Lord of BOF] Level 14 (bugbear -> giant)

2015. 2. 10. 22:56

1. 문제의 힌트 확인

ls 명령어를 통해서 디렉토리의 파일 목록을 살펴보자.



[그림1] bugbear 계정의 디렉토리 목록

 

 

suid가 걸려있는 giant에서 쉘을 작동시키면 될 것이다. giant의 소스 코드를 살펴보자.


[그림2] giant의 소스 코드

 

 

이번 소스 코드는 약간 길다. 힌트에 RTL2라고 적혀있다. RTLLevel13에서 다뤘으므로 기본지식은 똑같다.

 

2. RTL (Return To Libc)

이 기법은 일반적인 BOF로 스택에서 코드가 실행되는 것을 막은 non-executable stack 보호기법이 나오고 나서 제안된 방법이다. return addresslibc 영역(공유라이브러리)으로 점프시켜서 원하는 libc함수를 실행한다. 예를 들어 printf함수를 사용했다고 하면 실제로 이 함수를 정의하고 있는 /lib/libc.so.6파일이 이 영역에 적재된다. 여기서 중요한 점은 printf함수 하나만 사용을 하더라고 /lib/libc.so.6파일이 통째로 메모리에 적재된다. 그러므로 이 파일에서 원하는 함수의 주소를 끄집어내서 RET주소에 넣어주게 되면 그 함수를 실행할 수 있다. 간단하게 그림으로 나타내면 이렇다.


[그림3] RTL 기법

 

 

3. 실제

giant의 소스 코드를 다시 살펴보자.



[그림4] giant의 소스 코드

 

 

일단 인자가 없으면 argv error를 출력하고 프로그램을 종료한다. 인자가 있을 경우에는 /usr/bin/ldd/home/giant/assassin을 인자로 넣은 출력으로 /bin/grep libc을 수행하고 그 출력을 /bin/awk'{print $4}' 옵션을 걸어서 수행한 것을 fp에 넣어서 그것을 buffer에 넣고 그것을 lib_addr에 넣는다.

/usr/bin/ldd는 해당 프로그램이 사용하는 공유 라이브러리를 출력하는 명령이고 bin/grep은 입력에서 인자에 있는 문자열을 찾는 명령이다. 그리고 /bin/awk는 입력을 토큰화 시켜서 옵션에 따라 출력하는 명령인데 여기선 '{print $4}'을 통해 네 번째 토큰이 출력되게 되어있다.

그리고 동일하게 /usr/bin/nm/lib/libc.so.6을 인자로 넣고 그 출력으로 /bin/grep __execve를 하고 bin/awk를 통해 첫 번째 토큰을 출력하게 되어 있다. 이것을 또 buffer에 넣어서 execve_offset에 넣는다.

/usr/bin/nm은 라이브러리, 컴파일 된 오브젝트 모듈, 공유 오브젝트 파일, 독립 실행 파일 등의 바이너리 파일을 검사해서 그 파일들에 저장된 내용 또는 메타 정보를 표시하는 명령이다.

 

그리고 lib_addrexecve_offset을 합하여 execve_addr에 넣고, ret에 첫 번째 입력 인자의 44번째부터 4바이트를 복사한다. 이 값이 evecve_addr과 같은지 확인한다. 다르면 You must use execve!를 출력하고 프로그램을 종료하고, 같으면 BOF 취약점을 가지고 있는 strcpy함수를 수행하고 printf함수를 수행한다.

 

프로그램의 스택 상황은 이러할 것이다.


[그림5] main의 스택 상황

 

 

Level12와 비슷한데, 이번에는 RETsystem함수가 아닌 execve함수로 보내야 한다. gdb를 통해 execve의 주소 값을 획득하자.



[그림6] gdb를 통해 execve함수의 주소를 획득

 

 

giant에서 execve의 주소를 계산한 것처럼 계산도 해보자.

/usr/bin/ldd의 인자로 들어가는 /home/giant/assassin은 권한 문제로 접근할 수 없으니 현재 디렉토리에 있는 /home/bugbear/giant를 집어넣자. 공유 라이브러리이기 때문에 값에는 차이가 없을 것이다.



[그림7] main에서 lib_addr에 들어가는 값 수행

 

 

/usr/bin/nm /lib/libc.so.6 | /bin/grep __execve | /bin/awk '{print $1}' 도 수행해보자.


[그림8] main에서 execve_offset에 들어가는 값 수행

 

 

두 값을 더해보자.

0x40018000 + 0x00091d48 = 0x400a9d48

 

gdb를 통해 획득한 execve의 값과 동일하다!

 

execve를 실행하기 위하기까지의 공격 코드는 다음과 같다.

 

./giant `python -c'print"A"*44+"\x48\x9d\x0a\x40"'`

 

그런데 이 코드를 수행하면 You must use execve!가 뜨지 말아야 하는데 아직도 뜬다.


[그림9] execve의 값을 바르게 계산하여 넣었는데도 예상한 결과가 나오지 않음

 

 

python에서 \x0a를 개행문자로 인식하여 생기는 문제라고 한다. 전체를 "로 감싸주면 해결된다고 한다. 감싸주고 수행해 보자.


[그림10] "로 전체를 감싸준 후 실행한 모습

 

 

You must use execve!가 뜨지 않는다. 모든 제약사항을 넘기고 strcpy함수와 printf함수가 작동한 것이다. 이제 execve의 인자를 넣어 주면 될 것이다. execve의 인자는 세 가지이다. 첫 번째 인자는 전체 경로 명이고, 두 번째 인자는 인수의 배열이다. 마지막 인자에는 환경 설정인데 일반적으로 NULL을 집어넣는다. 우리가 실행하고 싶은 프로그램은 /bin/sh이므로 첫 번째 인자에는 "/bin/sh", 두 번째 인자에는 {"/bin/sh",NULL}, 마지막 인자에는 NULL을 넣어 주면 될 것이다. level13에서 해 주었던 것처럼 프로그램을 작성하여 "/bin/sh"의 주소를 구하자.


[그림11] "/bin/sh"의 주소를 구해주는 프로그램의 소스 코드

 

 

shellexecve의 주소를 집어넣고 그 주소가 "/bin/sh"의 주소와 동일할 때 까지 비교하고 난 뒤, 그 값을 출력한다.

 

컴파일하고 실행해 보자.


[그림12] "/bin/sh"의 주소를 출력하는 모습

 

 

두 번째 인자로는 {"/bin/sh", NULL}을 집어 넣어야 하는데 어떻게 하면 될까? gdb로 스택의 끝부분을 살펴보았다.

 


[그림13] gdb로 스택의 끝부분 조회

 

 

0xbfffffe4에 파일경로가 있고 그 다음으로 NULL이 있는 것을 알 수 있다. 우리가 이 프로그램에 전해주어야 할 인자는 {"/bin/sh", NULL}인데 "/bin/sh"문자열이 저기에 들어가면 다음에는 NULL이 옮으로 {"/bin/sh", NULL}처럼 전달 될 수 있을 것이다. 그럼 "/bin/sh"의 문자열이 저기에 들어가지만 실행하는 것은 똑같이 어떻게 해줄 수 있을까? 그러기 위해서는 심볼릭 링크에 대한 이해가 필요하다. 심볼릭 링크는 간단히 말해서 바로가기 같은 기능을 한다. 하지만 이 심볼릭 링크의 이름을 "/bin/sh"의 주소로 전달하면 파일경로가 들어갈 자리에 "/bin/sh"가 들어갈 것이고 다음 자리에는 NULL이 들어갈 것이다. 심볼릭 링크는 ln -s <심볼릭링크를 걸 파일이름> <지정할 이름> 으로 걸어줄 수 있다.

 


[그림14] 심볼릭 링크를 걸어주는 모습

 

 

링크를 걸어주고 아까 그 곳의 스택의 상태를 살펴보자


[그림15] 심볼릭 링크를 걸어준 후의 스택의 모습

 

 

그런데 "/bin/sh"의 앞 경로는 필요가 없다. 프로그램 경로의 시작 주소에서 앞에 있는 길이만큼 더해주자.


[그림16] "bin/sh"로 심볼릭 링크를 걸어준 곳의 주소.

 

 

NULL은 그 다음에 있으므로 0xbffffffc를 적어주면 될 것이다.

 

그렇다면 최종 공격 코드는

./<심볼릭 링크의 이름> "`python -c'print"<더미값>"*44+"<"/bin/sh"의 주소>"+"<더미값>"*4+"<"/bin/sh"의 주소>"+"<{"/bin/sh", NULL}의 시작주소>"+"<NULL 주소>"'`"가 될 것이다.

 

./`python -c'print "\xf9\xbf\x0f\x40"'` "`python -c'print"A"*44+"\x48\x9d\x0a\x40"+"AAAA"+\xf9\xbf\x0f\x40"+"\xf7\xff\xff\xbf"+"\xfc\xff\xff\xbf"'`"가 될 것이다.

 

공격해보자.


[그림17] 최종 공격 코드

 

 


[그림18] 쉘이 작동한 모습

 

 

쉘이 작동하였다!

 

id를 통해 권한을 확인해보자.


[그림19] id 명령어로 권한 조회

 

 

권한이 상승한 것을 볼 수 있다.

 

my-pass로 다음 단계의 패스워드를 획득하자.


[그림20] 다음 단계의 패스워드 조회

 

 

 

 

Comments