ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [HackCTF]Unexploitable #3 Write-up
    표튜터와 함께하는 Pwnable/HackCTF Write-up 2019. 8. 13. 12:50

    이번에 풀어볼 문제는 Unexploitalbe #3이다.

    풀면서 들었던 생각은 자주자주 풀지않아서 그런지 머리가 굳는 느낌이다..ㅠ

    저번에도 그랬지만 어쨌든 풀어서 참 다행이다.. ㅎㅎ 






    해당 바이너리는 NX가 걸려있고 Partial RELRO이다.

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

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





    우선 바이너리를 실행시켜보겠다.

    특정한 문자열이 출력되고나서 사용자로부터 입력을 받고 있었다.







    IDA를 이용해보니 역시나 fgets함수 즉, 사용자로부터 입력을 받는 부분에서

    버퍼오버플로우를 일으킬 수 있는 취약점이 있다는 것을 알 수 있다.






    조금 더 자세히 들여다보면 rbp-0x10 즉, 16개의 버퍼의 크기보다

    훨씬 큰 0x100을 입력할 수 있으므로 BOF가 가능하며 16(버퍼크기) + 8(sfp) = 24

    dummy로 덮어주면 ret를 핸들링 할 수 있다는 것도 확인할 수 있었다.




    그렇다면 "그 다음은 어떻게 진행해야할까?" 하고 곰곰히 생각해보았다..

    fwrite와 fgets를 이용해서 ROP를 사용하면 될 것 같다는 생각이 들었고

    그렇다면 사용할 수 있는 gadget이 있는지 확인해봐야겠다는 생각이 들었다.





    우선 ROP를 하기위해 필요한 것들을 구해보자.

    그에 앞서 이용될 함수에 대해 생각해보자. 우선 fwrite함수에 필요한

    인자는 4개, fgets에 필요한 인자는 3개이다. 입력할 수 있는 길이가 충분하기 때문에

    인자들은 RTC(Return to CSU)를 이용하면 셋팅해줄 수 있을 것이다.





    또한 바이너리에 어떠한 함수가 존재하는지 확인해보니

    이전 문제와 같이 gift라는 함수가 존재했고

    "Useless gadget for you"라는 문자열이 있었다.

    "쓸모없다지만 선물이니까 언젠가 쓸 일이 있겠지"하고 넘어가자



    RTC를 이용해서 함수를 호출할 수 있는지 확인해보았다.

    fwrite함수의 인자를 셋팅하려는데 문제가 발생한다. fwrite의 rcx, 즉 4번째 인자 stdout을

    셋팅해주지 못한다는 것이다. 그렇다면 이 rcx를 어떻게하면 셋팅해줄 수 있을까??



    이 문제를 푸는 핵심이 바로 이 부분을 해결하는 것이다.





    우선 fwrite 함수가 어떻게 동작하는지 확인해보았다.





    0x601050의 내부에 있는 값 0x7ffff7dd2620을 QWORD PTR로 참조해서

    rax에 저장하고 rax에 저장한 값을 rcx에 저장해서 인자를 넘기고 있었다.





     그러므로 fwrite함수를 사용하기 위해서는

    stdout이 저장되어있는 0x601050 주소 내부의 값을 참조하도록

    인자를 넘겨줘야 한다는 것을 알 수 있었다.




    현재 4번째 인자인 rcx를 제외한 모든 인자를 RTC로 셋팅할 수 있다.

    우리에게 필요한 rcx를 셋팅하는 방법은 다음과 같다.






    pop rdi, ret gadget을 이용해서 rdi에 stdout가 저장된 고정주소영역 0x601050을 저장한다.





    그 다음 rdi에 저장된 값(0x601050)을 QWORD PTR로 참조한 것을 rcx에 저장하는

    gadget을 이용하면 rcx에 우리가 원하던 stdout을 인자로 넘겨줄 수 있게된다. 

    이 gadget은 아까 우리가 본 gift함수에 들어존재하고 있었다!!

     




    Payload를 살짝보자면 다음과 같아진다.





    이제 우리는 fwrite 함수를 사용할 수 있게 되었다. RTC를 이용해서

    fwrite를 호출하여 fgets의 실제 주소를 받아올 수 있다.

    그렇게 받아온 fgets함수에서 fgets의 offset을 빼주면

    libc의 주소를 구할 수 있고 libc를 구할 수 있으니 stdin의 실제주소와

    system함수의 실제주소도 쉽게 구할 수 있게 되었다~~





    그렇다면 하나씩 진행해보자!!

    위의 사진에서 볼 수 있듯, 간단하게 fgets의 offset을 구했다.





    마찬가지의 방법으로 system offset도 구할 수 있었다.





    stdin의 경우 fgets의 실제주소와의 offset차이를 이용해서 구해보기로 했다.

    즉, libc + fgets offset + (stdin addr - fgets_addr) = stdin의 실제주소이다.




    이제 ROP에 이용할 fwrite와 fgets 모두 사용할 수 있게 되었다.

    Payload는 원리는 다음과 같이 구성해볼 수 있다.




     1. fwrite함수를 이용해서 fgets의 실제주소와 offset을 통해 libc addr를 구한다.

    2. libc addr을 기반으로 system함수와 stdin을 구해서 사용할 수 있도록 한다.

    3. fgets 함수를 이용하여 고정주소 영역에 "/bin/sh"을 저장한다.

    4. 마찬가지로 고정주소 영역에 system함수의 실제주소를 저장해준다.

    -> 여기서 system함수의 실제주소를 고정주소 영역에 넣어서 사용하는 이유는

    RTC를 이용해서 함수를 호출할 때 QWORD PTR을 이용해 해당 주소 내부에

    저장된 값을 QWORD만큼 참조해서 호출하기 때문이다.





    이제 일반적인 ROP를 하듯 위에서 설명한대로 Payload를 구성하면된다.

    한 가지 다른점은 RTC를 사용하면 Payload가 길어져서

    256길이를 넘길 수 있기 때문에 main을 다시 호출해서 해결하면 된다

    ( Payload를 더 줄일 수 있을 것 같은데 일단은 푸는 것에 목적을...ㅎㅎ )





    또한 이 문제에서 살짝 알고 갈 것은 바로 고정주소 영역 bss에 stdout과 stdin이

    존재한다는 것이다. bss영역의 시작 주소를 보면 0x601050이다. 아까 위에서 봤던

    낯이 익은 주소이다. 바로 stdout의 주소이며 0x601060에는 stdin이 존재한다.






    < Payload >

    이런식으로 진행하게되면~~




    짜잔~~ FLAG를 볼 수 있었다ㅎㅎ


    반응형

    댓글

Designed by Tistory.