IMyoungho 2019. 5. 26. 20:07

이번에 풀어볼 문제는 64bit에서 진행되는 Register이다. 

또하나 교훈을 얻게되는 문제였다!!

* Thanks to Py0zz1 *




이 문제도 역시 NX와 Partial RELRO를 가지고 있다.

그러므로 stack, heap, .data 영역에 실행권한이 없으면서

Got Overwrite가 가능하다는 것을 알 수 있다.





문제를 실행시켜보도록 하겠다.

RAX, RDI... 등의 register에 값을 입력하는 것으로 보이는

Code가 보여진다. 또한 한 바퀴를 돌고나니까

다시 RAX부터 register 값을 입력받고 있었다.




이번에는 IDA를 이용하여 코드를 보도록 하자!!




< main >

main 함수의 경우 5초의 alarm 함수가 진행되었으며

build 함수를 호출하고 있는 것을 알 수 있었다.




< build >

 

build 함수의 경우 signal 함수가 호출되었으며

시그널 번호는 14번이며 이 14번 시그널을

handler(신호 핸들러)에서 처리하도록 되어있었다. 14번 시그널은

SIGALRM으로 알람에 의해 발생되는 시그널이다.

쉽게 말해서 14번 시그널 SIGARLM이 발생하면

신호핸들러인 handler가 동작한다는 의미이다.





< handler >

handler는 다시 exec_syscall_obj를 호출하고 있었으며

그러므로 main에 있는 알람 함수가 동작하면서 5초 뒤에

14번 시그널이 발생될 것이고 그 말은 즉, 5초 뒤에

handler가 동작하여 exec_syscall_obj가 진행된다는 의미가 된다.






< exec_syscall_obj >

이 함수에서는 syscall을 호출하고 있었다.





또한 build 함수에서는 get_obj를 호출하고 있었는데

< get_obj >

아까 우리가 보았던 register 이름들이 출력되었고

get_ll를 호출하고 있는 것으로 보아 입력을 받는 부분이 있을 것이다.





< get_ll >

해당 함수에는 get_inp가 호출되었고 인자로 넘겨진

nptr을 atol함수를 이용하여 문자열을 long형 정수로 바꿔주고 있었다.





< get_inp >

마지막으로 get_inp를 보니 역시 예상했던대로 입력을 받고 있었다.





다시 build를 보면 while문의 조건으로 validate_syscall_obj가 있고

종결조건인 validate_syscall_obj의 리턴값이 거짓이면 raise함수가 

14번 시그널을 실행 중인 프로그램에게 보내게된다. 그러므로

프로그램이 시작되고 5초 후에 alarm 함수로 인해 14번 시그널이

발생되고 syscall을 진행할 수 있게되며 다음 while문이 진행될 때

validate_syscall_obj가 거짓이 되면 다시 raise 함수로 인해

14번 시그널이 발생하게되는 로직이다. 이 점을 잘 이용해야한다.





< validate_syscall_obj >



build 함수의 모습을 어셈블리어 코드로 보게되면

validate_syscall_obj 함수의 실행 다음을 보면 jne로 비교해서

다르면 build+155로 jmp한다. 만약 같아서 다음으로 넘어가면 raise를

호출하게되고 build+38로 jmp해서 다시 입력을 받는다. 만약 위에서 처럼

달라서 build+155로 뛰더라도 다시 build+156에서 jmp를 만나서

build + 38로 뛰게되어있다. 결국 계속 입력을 받는다는 의미이다..

차이점은 raise 함수를 실행하냐 마느냐 이다.




test eax, eax의 의미는 함수의 return값이 저장되는 eax를

비교하여 그 값이 0인지 아닌지를 확인하는 것이다.

test는 두 값을 AND 연산하며 연산결과를 따로 저장하지 않는다.

대신 FLAG Register에 그 흔적을 남기게 된다. 값이 0인경우 ZF = 1이 된다.


참고 : https://p3ace.tistory.com/41





그렇다면 여기까지 진행했을 때 이 문제는 특정 메모리 leak을

할 필요가 없이 64bit syscall table을 보고 우리가 필요한 함수들을

syscall 해주면 될 것임을 알 수 있다. 그러면 간단하게 풀 수 있다.




Payload는 다음과 같이 구성하게 된다.

read 함수를 호출하기 위해 rax, rdi를 0으로 진행하고

고정주소영역의 버퍼를 rsi값으로 준다. 우리는 고정주소영역에

"/bin/sh\x00"을 저장할 것이기 때문에 rdx로 "/bin/sh\x00"의 길이

8을 줄 것이다. 그 다음으로는 "/bin/sh"을 실행하기위해 execute 함수를

호출할 것이다. 그러므로 rax는 59, rdi는 "/bin/sh\x00"이 저장된

고정주소 영역 나머지는 Null로 채워주면 쉘이 따질 것이다.






나는 고정주소 영역으로 .data영역을 사용하였다.






< Payload >

주의할 점은 해당 Payload대로 Syscall을 하기전에 

alarm 함수를 유의해야한다. 5초 이후에 알람 시그널(14)를

전송하기 때문에 Syscall이 일어나려면 시간을 잘 맞춰주어야한다.

나는 시간을 맞춰주기위해서 sleep(3)을 사용하였다.






* 또한 이렇게 반복되는 Payload의 경우 가독성을 위해 함수화하자 *





위의 Payload대로 진행을 하게되면~

짜잔~ FLAG를 볼 수 있었다!! 문제를 풀 때는 항상 함수들을 꼼꼼히 보도록하자!!



참고 : https://www.ibm.com/support/knowledgecenter/ko/ssw_ibm_i_73/rtref/raise.htm

http://forum.falinux.com/zbxe/index.php?document_srl=413254&mid=C_LIB

https://blockdmask.tistory.com/23

반응형