ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [HackCTF] Register Write-up
    표튜터와 함께하는 Pwnable/HackCTF Write-up 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

    반응형

    댓글

Designed by Tistory.