System Hacking

RTC (Return to CSU) (수정)

jir4vvit 2021. 4. 19. 17:21

HackCTF에서 RTC 문제를 풀어보려고 하는데, RTC 기법이 뭔지 잘 몰라서 정리를 해본다..


개요

64bit ROP를 진행하려면 필요한 gadget들이 있어야 한다.

예를 들면,,, read함수를 실행시킨다고 가정하면,, read함수는 인자가 3개니까

pop rdi, pop, rsi, pop rdx 이렇게 세 인자를 제어하는 가젯이 필요하다.

 

그런데 가끔 가젯이 모자라거나 없는 경우가 있다.

문제 출제자가 친절해서 저렇게 가젯을 넣어 준 거고.. 필요한 모든 가젯이 바이너리 안에 존재하는 건 드문일이다.

 

이럴 때 RTC 기법을 쓴다고 한다.

RTC (Retrun to CSU)

 

[출처] https://wogh8732.tistory.com/156

ELF 바이너리는 위 사진과 같이

start libc_start_main libc_csu_init ...  main

이런 내부적인 과정을 거쳐서 실제 main이 실행된다.

 

부족한 gadget은 아래의 libc_csu_init에서 얻을 수 있다.

libc_csu_init

두 부분으로 나누어서 보겠다.

 

csu_init

스택에 저장되어 있는 값을 pop하여 rbx, rbp, r12, r13, r14, r15에 저장한다

 

csu_call

add rsp, 8 은 딱히 뭐라고 할 말이 없다. 함수화 시키기 위해 이것까지 같이 묶었다.

 

r13을 rdx로, r14를 rsi로, r15d를 edi로 옮긴다.

여기서 든 생각은 gadget_2에서 r13, r14, r15를 제어할 수 있는데, 이후 바로 gadget_1를 사용함으로써

우리는 rdx, rsi, edi를 제어할 수 있다는 것이다.

* rdi가 아니라 edi라서 조금 아쉽지만 어차피 우리는 보통 첫번째 인자에 rbi의 8바이트 이내의 값을 넣기 때문에 노상관이다.

 

call [r12 + rbx*8] 여기서 우리가 원하는 주소를 실행할 수 있다고 생각할 수 있다. rbx에 0을 넣고 r12에 함수의 got 주소를 넣으면 된다.

* plt는 왜 안되냐고? plt에는 got의 주소가 저장되어 있다. r12에 적힌 값(주소)를 바로 실행하는데,,, plt 주소를 주면 segmentation fault가 발생한다.

 

그 다음 rbx에 1을 더하고, rbx와 rbp를 비교해서 같으면 다음 line을 이어간다.

위에서 rbx를 0으로 줘야 우리가 원하는 주소를 실행시킬 수 있다고 했다.

만약 rbp가 1이면 다음 line으로 가서 0x4006b6으로 간다., 즉 csu_init을 한번 더 실행하는 효과를 내서 원하는 주소를 또 실행할 수 있게 된다. 


 

payload = bof 트리거 ~ 

payload += p64(csu_init)

payload += 'A' * 8           # add rsp, 8
payload += p64(0)           # rbx
payload += p64(1)           # rbp
payload += p64(write_got)   # r12, no plt ok got
payload += p64(8) + p64(read_got) + p64(1) # r13(rdx), r14(rsi), r15(edi)
payload += p64(csu_call)    
# reply

...


 

이걸 함수화 시켜보자. (ㅊㅊ Jsec님 블로그)

def chain(got, A, B, C, csu_call):
        payload = ''
        payload += 'A' * 8
        payload += p64(0)
        payload += p64(1)
        payload += p64(got)
        payload += p64(C)
        payload += p64(B)
        payload += p64(A)
        payload += p64(csu_call)

        return payload

이 글을 수정하면서 add rsp, 8을 같이 csu_call로 묶어줬는데 이는 함수화를 시키기 위해서이다.

csu_call만 실행해주면 (rbp가 1이면) 자동으로 csu_init이 실행된다. 

그러면 add rsp, 8부터 시작하게 되어서 'A' * 8이 필요하다. 

 

만약 내가 수정하기 전 글처럼, pop rbx를 시작지점으로 잡으면 처음에 레지스터 값 넣을떄의 코드랑 두번째 체이닝할 때 값 넣는 코드가 달라지게 된다. 첫번째는 안넣어줘도 되는데, 두번째에는 'A"*8 넣어줘야 하니깐...

 

그래서 아예 add rsp, 8부터 csu_call로 잡게되면 저런 chain함수만 가지고 계속 연계할 수 있다.

 

아 그런데, 뭐 .. leak만 할거면 pop rbx부터 csu_init을 잡아도 되긴 한다.

 

chain 함수 구성할 때 주의할 점

이렇게 가끔... 저렇게 레지스터 순서?가 다르다. 그래서 libc_csu_init 함수룰 보고 나서 저 chain함수를 구성하길 바란다.


원하는 주소로 이동할 때, 주의해야할 점이 있다.

한번 더 말하지만, call [r12] 은 r12 공간에 저장된 데이터를 참조한다는 것이다.

 

main을 call하고 싶다고 r12를 main 주소로 해놓으면 안된다는 것이다.

main으로 가고 싶으면 bss 영역에 main 주소를 넣어놓고, 해당 bss 주소를 r12로 설정해줘야 한다. 

 

또 다른 main으로 가는 법...

gadget2를 실행하고, gadget_2의 코드 길이만큼 offset을 준다. : 8바이트씩 총 7개

 

payload += p64(csu_init)

payload += ... 인자 정리

payload += p64(csu_call)

payload += p64(0) * 7

payload += p64(main)

 

Example 

Hackctf의 RTC 문제

Pwnable.tw의 Unexploitable 문제