ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Return-to-Csu 기법 정리
    공부정리 2019. 3. 5. 23:47

    포너블 문제를 풀 때, 64Bit 바이너리가 까다로운 이유가 바로 'Gadget' 때문이다.


    64Bit의 Calling Convention은 Fastcall로 호출된 함수에 인자를 레지스터로 전달한다.

    이 때문에 Exploit을 구성할 때도 [POP RDI]와 같은 가젯이 반드시 필요하다.


    이러한 특이점 때문에 문제에서 가장 흔하게 볼 수있는게 '가젯 제한'이다.

    이 상황에서 EDI,RSI,RDX 를 구성할 수 있는 방법이 있는데, 바로 Return_to_csu기법이다.


    [ Return to csu란? ]

    ELF바이너리를 IDA와 같은 디버깅도구로 열어보면, __libc_csu_init( )를 볼 수 있다.

    이 함수가 하는일은 바이너리의 _start( )를 호출하는 함수인 gmon_start( )를 호출하는 역할을 한다.


    사실 바이너리가 시작되면 main( )가 바로 시작되는 것이 아니다.

    __libc_csu_init( ) -> _start( ) -> __libc_start_main( ) 를 거쳐서 main( )가 실행되는 것이다.


    그럼 어떻게 __libc_csu_init( )에서 인자셋팅을 해줄 수 있는 것일까?


    [ __libc_csu_init( ) ]

    위 사진은 __libc_csu_init( )에서 공격기법에 사용되는 부분이다.

    Return_to_csu기법은 2개의 Stage로 나뉘게 된다.


    [ Attack Flow ]

    <Stage1>

    1.RET를 0x40075A로 조작한 뒤, 스택에 저장한 데이터들을 각 레지스터에 POP한다.


    <Stage2>

    2.그 다음 RET를 0x400740으로 돌려서 EDI, RSI, RDX를 셋팅한다.

    3.R12+RBX*8에 저장된 주소를 Call해서 함수를 호출한다.

    이런식으로 인자들을 셋팅하고 원하는 함수를 Call할 수 있게되는 것이다.

    인자가 4개 이상인 함수는 호출이 불가능 하다는 점을 유의해야 한다.


    그럼 상황 하나를 가정하고 각 Stage가 어떻게 돌아가는지 확인해 보도록 하자

    현재 상황은 RET를 0x40075A로 핸들링한 상태이며

    Return_to_csu를 이용해서 .bss영역에 "/bin/sh\x00" 문자열을 저장하려고 한다.



    [ Stage 1 ]

    이렇게 Stage2에서 MOV되는 레지스터를 고려해서 각 레지스터 데이터들을 넣어주고

    RET를 Stage2인 0x400740으로 돌려주면 Stage1은 끝난다.



    [ Stage2 ]

    Stage2에서는 Stage1에서 설정한 레지스터들을 인자들로 셋팅하고 함수를 Call하는 역할을 한다.


    READ_GOT에는 __libc_read( )의 주소가 저장되어 있으니

    qword ptr로 __libc_read( )주소를 참조해서 Call하는 모습이다.


    여기서 유의해야 할 점은 qword ptr로 R12공간에 저장된 데이터를 참조한다는 것이다.

    내가 main( )를 Call하고 싶다고 R12를 main( )주소로 설정해놓으면 Segmentation Fault가 발생하게 된다.


    qword ptr로 저장된 데이터를 참조해서 Call하는 것이기 때문에

    .bss영역과 같은 저장공간에 main( )주소를 넣어놓고, 해당 .bss주소를 R12로 셋팅해줘야 main( )이 호출된다.


    자칫하면 실수 할수도 있지만, Call하는 방식이 저렇기 때문에 libc_leak을 하지 않아도 된다는 장점도 있다.

    여러모로 쓸모있는 기법이다. :)


    그리고 RBP값을 1로 설정한 이유는 지속적인 Csu Chaining을 위해서다.

    빨간 박스를 C코드로 바꾸면 while( RBX != RBP )를 의미하는데

    RBX는 우리가 Call 연산식 때문에 0으로 설정해줬고, ADD로 1이 증가하면 RBX값이 1로 바뀐다.

    이 상태에서 JNZ컴페어를 우회하려면 RBX와 RBP가 같도록 해줘야 하기 때문에, 초기에 1로 설정해준 것이다.


    처음보면 어렵게 느껴질 수 있지만, 한 두번 사용해보면 정말 단비같은 존재가 아닐 수 없다.

    해당 기법을 제대로 이해했다고 생각된다면, Pwnable.tw의 'Unexploitable'문제를 풀어보기를 추천한다.


    여러가지 방법으로도 풀 수 있지만, 위에서 소개한 Return_to_csu기법으로 풀 수 있는 문제 중 하나이다.

    아마 이 문제 풀면 Return_to_csu기법은 절대 안 까먹을거다..

    댓글

Designed by Tistory.