ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [HackCTF] World Best Encryption Tool Write-up
    표튜터와 함께하는 Pwnable/HackCTF Write-up 2019. 6. 10. 00:26

    이번 문제는 World Best Encryption Tool 이다!! 

    Payload를 짜는데 많이 헷갈려서 조금은 오래걸린 것 같다...





    해당 바이너리는 Canary가 걸려있는 상태이고 NX가 적용되어있다.

    RERLO의 경우 Partial이므로 stack, heap, data영역에 실행권한이

    없으며 Got Overwrite가 가능하다. 하지만 Canary가 적용되어있기

    때문에 BOF시에 Canary를 신경쓰지 않으면 경고메세지가 출력될 것이다.





    문제를 실행시켜보면 우선 사용자로부터 입력을 받는다.

    입력값의 길이에는 제한이 없는 것으로 보여진다. 그 뒤,

    사용자가 입력한 문자열중 일부의 길이를 암호화하는 것 같다.

    마지막으로 다른 text를 암호화할 것인지 물어보고 "Yes"를 입력하면

    입력을 계속해서 받고 "No"를 입력하면 종료된다는 것을 알 수 있다.





    그렇다면 IDA를 이용해서 코드를 보도록 하자.

    역시나 길이 제한없이 scanf 함수로 입력을 받고 있었고

    0x31 즉, 입력값 중 49개의 문자를 0x1c와 xor 연산하였다.

    그 뒤 연산한 값과 나머지 값들을 dest버퍼에 0x39(57)개 만큼

    strncpy를 진행하고 있었다. 마지막으로 "Yes"인지 "No"인지를

    물어보는 부분이 나오고 있었고 "Yes"가 아닐 경우 while문이

    종료되었고 "No"를 입력하면 종료되며 "No"가 아닌 문자열일 경우

    "It's not on the option"이라는 문자열이 출력되었다.





    GDB를 이용해서 어셈블리어 코드를 보도록 하자.

    시작하자마자 Canary값을 셋팅하는 부분을 만날 수 있었다.

    우선 위의 그림에서 보이는 fs란 fs segment를 의미하며 커널영역에

    있는 값을 의미한다. 또한 DWORD PTR fs:0x28의 의미는

    "커널의 fs segment+0x28에 있는 값을 DWORD만큼 참조하겠다"

    이며 거기서 참조한 값을 rax에 넣고 다시 [rbp-0x8]에 저장하겠다는

    의미이다. 이 부분의 값이 변조되면 Canary값이 변조되었다는 의미이다.

    이 값은 실행할 때마다 값이 랜덤하게 바뀌며 만약 누군가에 의해 변조가 되면




    다음과 같은 "stack smashing detected"라는 문자열을 출력되면서 종료된다.

    (때문에 무작정 BOF를 시도하는 것이 아니라 Canary를 우회할 수 있어야한다)





    Canary값이 rbp-0x8에 제대로 저장되어있는 것을 볼 수 있었고

    다음 8byte는 sfp, 그 다음 8byte는 RET라는 것을 알 수 있었다.





    또한 입력의 경우 rbp-0x80부터 받고 있다는 것을 알 수 있었고





    strncpy는 우리가 입력한 값인 rbp-0x80의 값을

    dest버퍼인 rbp-0x40에 복사한다는 것도 알 수 있었다.






    그렇다면 우리가 필요한 것은 거의다 구한 것 같다.

    이 문제를 풀기위해서는 Canary를 leak 할 수 있어야한다.

    Canary를 leak하는 방법은 생각보다 간단하다.

    Canary는 하위 1바이트가 항상 \x00이기 때문에

    1바이트를 덮게되면 나머지 Canary값을 leak할 수 있다.

    그렇게 leak한 Canary값의 마지막 1byte를 다시 \x00으로

    바꾸어주면 완벽한 Canary leak이 완성된다.

    그러므로 위의 그림의 경우 \x62만 \x00으로 바꾸어주면

    완벽한 Canary를 leak한 것이 된다. 나는 이 부분을

    운이 좋게도 구글링을 하기도 전에 삽질을 통해서

    알게 되었는데 생각보다 빨리 알게되어서 이득이었다.ㅎㅎ



    이렇게 되는 이유는 다음과 같다.

    NULL을 만나기 전까지를 입력을 받는데

    Canary의 마지막 1byte가 바로 NULL이다. 그렇기 때문에

    이 1byte를 손대면 그 다음에 다시 NULL이나 입력 종료를

    인지하기 전까지를 입력으로 받아들이기 때문에 나머지 Canary를

    볼 수 있게 되는 것이다. (물론 나는 뽀록으로 ...)




    그 예시로 이 문제에서 56개 다음이 Canaray인데

    "a"*56 + "bbbb"를 하면 Canary를 덮었기 때문에

    경고메시지가 출력되는 것을 확인할 수 있다.





    하지만 "a"*56 + NULL + "bbbb"를 해보면 분명히 Canary를 덮도록 send 된 것처럼 보이나

    NULL까지만 입력받기 때문에 "bbbb"가 Canary를 덮지 못하게 된다.






    그러므로 Canary가 변조되지 않기 때문에 아무런 경고메시지가 출력되지 않는다!!




    이 다음부터는 Payload를 보며 설명하도록 하겠다.

    첫 번째 상자는 위에서 설명한 Canary를 leak하는 부분이다.

    우리가 입력한 값이 rbp-0x40으로 복사가 되기 때문에

    64개의 dummy이후로 RET가 된다. 하지만 우리가 하려는 것은

    Canary leak이다. Canary는 아까 보았듯 rbp-0x8의 위치이므로

    우리가 leak을 하기위해서는 56개의 dummy 다음임을 알 수 있다.

    우리는 56개의 dummy와 Canary 하위 1byte를 덮기위한 "b"를

    입력해주었다. 그렇게 되면 아까의 사진처럼 나머지 7byte의

    Canary를 leak할 수 있게 된다. 우리는 그 Canary의 하위 1byte를

    다시 "\x00"으로 바꾸어주어서 완벽한 Canary를 leak 할 수 있다.

    "b" 즉, 0x62가 하위 1byte에 덮혔으므로 간단하게 0x62를 빼주어서

    하위 1byte를 "\x00"으로 만들어 주었다.

    그 다음 "Yes"를 입력해주어서 다시 입력으로 돌아간다.



    두 번째 상자에서 진행 할 것은 바로 libc를 leak하는 것이다. 이번에는 처음의

    scanf 함수에서 BOF를 진행해보았다. rbp-0x80부터 입력이되므로

    120개 다음에 Canary가 존재하고 그 다음 sfp가 존재하고 있다.



    < ROPgadget >

    아까 구해놓은 Canary를 넣어주어서 우회 한 뒤, ROPgadget Tool을

    이용해서 구한 pr gadget을 넣어주어 leak에 사용될 함수인

    put@plt를 호출한다.(인자를 하나만 필요하므로 pr을 구함)

    인자로는 leak될 주소인 scanf@got를 주었다.

    여기서 중요한 것은 그 다음인데 "Yes"가 아닌 "No"를 해주어야한다.

    그래야 main함수가 끝나고 ret되면서 우리가 넣어놓은 Payload의 ret로 진행되어

    scanf@got 즉, scanf의 주소가 leak 될 것이다. "Yes"를 하게되면 main이

    종료되지 않고 다시 입력을 받으므로 leak을 할 수 없다. 

    하지만 우리는 한 번 더 입력을 해서 쉘을 따는 Payload를 진행해야하는데

    "No"를 입력하면  scanf 함수 주소를 leak 할 수 있지만 프로그램이 종료된다.

    그렇다면 어떻게 다시 입력을 받을까?




    방법은 바로 두 번째 빨간 상자 마지막 Payload에서 볼 수 있듯이 main을 다시 호출해주면된다.

    main이 호출이 가능한 이유는 main이 우리가 호출한 puts 함수의 ret이기 때문에

    가능한 일이다. 즉, pr의 경우 main함수의 ret이고 puts 함수의 경우 pr의 pop, ret에서의

    ret이다. 마지막 main의 경우는 puts함수의 ret이므로 이 과정이 가능한 것이다.

    우리는 leak된 scanf의 주소에서 scanf의 offset을 빼주어 libc주소를 구할 수 있었다.




    마지막으로 세 번째 상자에서 볼 수 있듯, 쉘을 따는 Payload를 전송해주면된다.

    이번 문제에서는 최초로 oneshot gadget을 사용해보았다. 특정한 조건을 

    맞춰주기만하면 해당 gadget을 사용할 수 있다. 아주아주 편리한 툴이다.ㅎㅎ

    oneshot gadget은 libc의 offset이기 때문에 아까 구한 libc에 더해서 사용하면된다.





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

    짜잔~ 하고 FLAG를 볼 수 있게 된다.~~








    * 번외 *

    처음 이 문제를 풀때 puts 함수를 이용해서 printf 함수의 주소를 leak하려고 했는데

    제대로 안되었다. (왜 printf는 안되는지 모르겠다. scanf는 잘 동작하던데..)

    그래서 반대로 printf 함수를 이용해서 puts를 leak하려고 했다.

    하지만 첫 번째 인자인 "%s"의 주소가 0x400913이 제대로 입력되지 않았다.

    그 이유는 printf 함수 자체가 인자로 NULL이 있는 경우 입력받지 않고

    무시한다고 한다. 기억해두자!!


    * Thanks to Malhyuk *



    반응형

    댓글

Designed by Tistory.