#About


[그림 1] - Assassin.c 소스 코드 확인




#Solution


[ + ] 소스 코드 해석


1. buffer라는 char 형 변수에 40바이트를 할당


2. 조건문을 통해 사용자 입력 값이 2개 미만이라면 argv error를 출력 후 종료


3. 조건문을 통해 사용자 입력 값 중 48번째 바이트가 \xbf면 stack retbayed you!를 출력 후 종료


4. 조건문을 통해 사용자 입력 값 중 48번째 바이트가 \x40면 library retbayed you, too!!를 출력 후 종료


5. 사용자 입력 값을 buffer에 복사한 뒤 buffer 값 출력


6. memset 함수를 통해 buffer와 sfp 값 초기화


이번 문제는 리턴 주소가 라이브러리 영역을 의미하는 \x40으로 시작하거나 스택영역을 의미하는 \xbf를 사용할 수 없다.

이러할 경우에는 RET SLED라는 기법을 이용해서 이용할 수 있으며 그 원리는 다음과 같다


함수를 종료하는 과정에서의 ret은 pop eip > jmp eip 와 같이 동작을한다. 이 때 pop 명령을 진행하므로 esp 값은 +4바이트 증가한다 

따라서 이 때 ret에 한번 더 ret의 주소를 넣어주게 되면 원래 ret 값의 4바이트 뒤 부분을 사용할 수 있으며 이를 이용한 페이로드 구상도는 아래와 같다


| 0x90 (44byte) | ret (4byte) | &argv[2] (4byte) | \x90 (100) | 셸 코드 (48byte) | 


환경변수도 이용할 수 있지만 argv[2]를 이용하여 문제를 해결할 것이며, 먼저 gdb를 이용하여 ret 주소를 찾을 수 있었다


[그림 2] - ret 명령이 담긴 주소 확인


또한 argv[2]를 이용할 것이므로 argv[2]의 시작주소를 찾아야 하므로 아래와 같이 진행해주었다


[그림 3] - 중단점 설정 및 임의의 페이로드 입력


AAAA부분은 ret 명령이 담긴 주소 값이 될 것이며 BBBB부분은 argv[2] 즉, D가 시작하는 곳이라고 생각하면 된다


위와 같이 입력해준 페이로드가 담긴 메모리를 살펴 보았더니 아래와 같이 값이 담긴것을 확인할 수 있다


[그림 4] - 메모리에 담긴 값 확인


빨간 부분은 ret 명령이 다시 담기게 될 부분이며, 파란 부분은 argv[2]의 시작주소가 담길 부분, 초록 부분은 셸 코드가 담길 argv[2]의 시작 부분이다

(0xbffffbe3)


따라서 페이로드는 아래와 같아 진다


| 0x90 (44byte) | 0x0804851e | 0xbffffbe3 | 셸 코드 |


[그림 5] - giant 권한의 셸 획득


/home/giant/tmp/assassin이 아닌 /home/giant/assassin에 위 과정을 똑같이 해주면 아래와 같이 assassin 권한의 셸을 획득 할 수 있다.


다만 경로가 바뀌므로 입력된 페이로드의 메모리상의 위치가 조금 달라질 수 있음을 유념하자


[그림 6] - 셸 획득



#About


[그림 1] - giant.c 소스 코드 확인




#Solution


[ + ] 소스 코드 해석


1. buffer라는 char형 변수에 40바이트 할당 및 포인터 변수 선언


2. 조건문을 통해 사용자 입력 값이 2개 미만이면 argv error를 출력 후 종료


3. popen 함수와 각종 명령어 이용해 assassin이라는 프로그램을 사용함에 있어 필요한 공유 라이브러리 정보를 출력 후 포인터 변수 fp에 담음


[그림 2] - 사용된 명령어 해석


ldd는 List Dynamic Dependencies의 약자로 실행파일이나 공유 라이브러리들이 요구하는 공유 라이브러리를 출력하는 명령어 이며 위 [그림 2]에서 


확인할 수 있듯, grep libc를 통해 libc가 들어 있는 줄만 출력했고 이를 awk에 넘겨주어 4번째 필드 값인 (0x40018000)만 출력후 포인터 변수 fp에 담음


4. fgets 함수를 통해 buffer에 254 글자 만큼 fp에 담긴 값(0x40018000)을 담음


5. sscanf 함수를 이용해 포맷스트링 %x 형식으로 하여금 lib_addr에 buffer에 담긴 값을 씀


6. 3번 과정과 비슷하며 *.so 파일에 대한 심볼 정보를 확인하기 위해 nm 명령을 사용했으며 구체적인 사용법은 아래와 같다


[그림 3] - 사용된 명령어 해석


nm을 이용하여 __execve가 위치하는 offset 정보가 담긴 정보를 awk로 넘겨주어 첫 번째 필드(00091d48)의 값을 포인터 변수 fp에 담음


7. 3,6번 과정에서 구해준 lib_addr과 execve_offset를 더한 값을 execve_addr에 담음


8. memcpy 함수를 이용하여 사용자 입력 값 중 44번째부터 4바이트를 포인터 변수 ret에 담음


9. ret의 값과 execve_addr과 일치하지 않으면 You must use execve!를 출력 후 종료


10. buffer에 사용자 입력 값 argv[1]을 담고 출력.


전반적으로 해석이랍시고 휘갈겨 적은 감이 있긴하지만 간단히 한줄 요약하면 


execve함수와 RTL기법을 통해 문제해결을 유도하기 위한코드임


문제를 해결하기 위해서는 리턴 값이 execve 함수의 주소 값이여야하며 이를 RTL기법을 적용하기 위한 스택 구조는 아래와 같다


| \x90 (44byte) | execve (4byte) | system (4byte) | exit (4byte) | &"/bin/sh" |


위와 같은 스택 구조를 이루기 위한 system과 exit의 주소를 아래와 같이 gdb를 이용하여 구해 주었다


[그림 4] - 함수 위치 주소 확인


마지막으로 필요한 system 내의 문자열 /bin/sh가 위치하는 주소를 찾기 위해 아래와 같이 C 프로그램을 만든 뒤 컴파일 시켜주었다


[그림 5] - /bin/sh 주소 확인


Exploit을 함에 있어 필요한 함수의 주소 및 인자를 구했으므로 아래와 같이 페이로드를 작성할 수 있고 셸을 획득할 수 있다


[그림 6] - 셸 획득



#About


[그림 1] - bugbear.c 소스 코드 확인




#Solution


[ + ] 소스 코드 해석


1. buffer라는 변수에 40바이트와 정수형 변수 i 4바이트 할당 및 선언


2. 조건문을 통해 전달되는 인자의 개수가 2개미만이면 argv error를 출력 후 종료


3. 조건문을 통해 사용자 입력 값 중 48번째 바이트가 \xbf이라면 stack betrayed you!!를 출력 후 종료


4. 사용자 입력 값을 buffer에 복사 후 출력


이번 문제에서는 if 문의 조건이 argv[1] 영역의 48번째 바이트가 \xbf가 되면 문제를 해결할 수 없으므로 stack 영역을 사용할 수 없고 이를 해결하기 위한 방법으로는 RTL이 있다


Return To Libc (RTL)


RTL은 메모리에 미리 적재되어 있는 공유 라이브러리를 이용해 바이너리 상에 원하는 함수가 없어도 공유 라이브러리에서 원하는 함수를 사용할 수 있는 공격 기법이다. 또한 RTL은 리눅스의 메모리 보호 기법 중 하나인 NX bit(Never Execute Bit)를 우회하기 위해 사용되는 기법이다. NX는 스택 세그먼트의 실행 권한을 제한함으로써, 스택에 셸 코드를 삽입하고 실행시키는 것을 방지하기 위한 메모리 기법에 해당한다



라이브러리는 함수가 시작되기 전, 미리 적재 되므로 아래와 같이 중단점을 걸어주고 셸을 실행시키기 위한 함수 중 system 함수의 주소를 확인했다


[그림 2] - system 함수가 위치하는 주소 확인


또한 system 함수를 실행하기 위한 인자인 /bin/sh를 전달시키기 위해, 해당 문자열이 위치하는 주소를 아래와 같이 C 코딩을 통해 확인할 수 있었다


[그림 3] - /bin/sh이 위치하는 주소 확인


위 과정은 어디까지나 system 함수 내에 /bin/sh라는 문자열이 존재하므로 사용할 수 있고, 존재하지 않는다면 전달 인자로 셸 코드가 담긴 


환경변수의 시작 주소와 같은 방법을 사용할 수 있다 위 과정을 통해 셸을 실행시킬 system 함수의 주소와 그에 필요한 인자인 


/bin/sh의 주소를 확인 했으니 페이로드를 작성 하기전 메모리 구조를 생각해보면 다음과 같다


| buffer (40) | SFP(4) | RET(4) | argc |


위 메모리 구조와 찾은 주소 값을 조합하여 페이로드를 작성하였을 경우 아래와 같은 페이로드가 된다


| 임의의 값 (44byte) | &system() (4) | Dummy (4) | &"/bin/sh" (4) |

[ 페이로드 ] - 1


 RET 주소에 system 함수가 위치하는 주소를 넣어주었으며, RET 명령이 진행되면 다음 4바이트인 Dummy부분이 


system 함수의 RET이 되며 따라서 &system+8이 system 함수의 인자가 위치하는 주소가 된다


[ 페이로드 ] - 1을 참조하여 아래와 같이 페이로드를 작성해주었으며 darkknight 권한의 셸 획득을 확인할 수 있었다


[그림 4] - darkknight 권한의 셸 획득


하지만 RET+4 부분의 "DDDD"는 system 함수의 리턴 값이 담기는 부분이라 했으며 문제를 해결함에 있어 목적은 셸을 띄우는 것이기 때문에


셸을 띄움에 있어 문제가 되지 않지만, system 함수의 리턴 값이 올바르지 않으므로 프로그램의 정상 종료가 이루어지지 않는다.


 따라서 system 함수의 리턴 값이 담기는 부분에 종료를 의미하는 exit() 함수의 주소를 적어주게되면 


정상적으로 프로그램을 끝낼 수 있으며 이 과정을 포함한 bugbear의 셸을 획득하는 과정은 아래와 같다


[그림 5] - exit 함수 주소 확인


system 함수의 주소를 확인하는 과정과 동일하게 진행해주었으며 exit 함수의 주소인 0x400391e0을 "DDDD"부분에 대체해주었다 


[그림 6] - 셸 획득




[ + ] RTL의 구체적인 원리 설명 


스택구조가 [ buffer ] [ SFP ] [ RET ]과 같은 형식으로 있다고 가정한다. call 함수를 이용해 정상적으로 system 함수를 호출할 때 


함수 호출 이후 코드 실행을 위해 saved eip를 스택에 push한다 하지만 RET을 이용한 함수를 호출하면 push eip가 진행되지 않은 상태에서 


함수 내부에서 함수를 빠져나올 때 push eip가 되어 있어야 될 위치의 값을 호출하기 때문에 [ &system ] [ &exit ] [ /bin/sh ]와 같은 구성이 가능하며 


함수 인자를 EBP+8 부터 참조하는 것도 이러한 이유 때문이다 또한 이러한 원리를 계속 이용하는 것이 RTL Chaining이다

출처 : http://xer0s.tistory.com/23



+ Recent posts