HackCTF에서 RTC 문제를 풀어보려고 하는데, RTC 기법이 뭔지 잘 몰라서 정리를 해본다..
개요
64bit ROP를 진행하려면 필요한 gadget들이 있어야 한다.
예를 들면,,, read함수를 실행시킨다고 가정하면,, read함수는 인자가 3개니까
pop rdi, pop, rsi, pop rdx 이렇게 세 인자를 제어하는 가젯이 필요하다.
그런데 가끔 가젯이 모자라거나 없는 경우가 있다.
문제 출제자가 친절해서 저렇게 가젯을 넣어 준 거고.. 필요한 모든 가젯이 바이너리 안에 존재하는 건 드문일이다.
이럴 때 RTC 기법을 쓴다고 한다.
RTC (Retrun to CSU)
ELF 바이너리는 위 사진과 같이
start → libc_start_main → libc_csu_init → ... → main
이런 내부적인 과정을 거쳐서 실제 main이 실행된다.
부족한 gadget은 아래의 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 문제
'System Hacking' 카테고리의 다른 글
Sigreturn-oriented programming (SROP) (0) | 2021.05.10 |
---|---|
Stack Pivoting (스택 피봇팅) (0) | 2021.05.03 |
[HackingCamp 22] 퍼징의 이해 - 장대희님 (0) | 2021.03.02 |
힙풍수 (Heap Feng Shui) (2) | 2021.03.02 |
Integer issue (정수의 범위, 묵시적 형변환, integer overflow) (0) | 2021.01.27 |