109 lines
2.8 KiB
Ruby
109 lines
2.8 KiB
Ruby
require 'rex/parser/ini'
|
|
|
|
module Rex
|
|
module Parser
|
|
module WinSCP
|
|
PWDALG_SIMPLE_MAGIC = 0xA3
|
|
PWDALG_SIMPLE_FLAG = 0xFF
|
|
|
|
def read_and_parse_ini(filename)
|
|
file = File.read(filename)
|
|
return if file.to_s.empty?
|
|
parse_ini(file)
|
|
end
|
|
|
|
def parse_protocol(fsprotocol)
|
|
return 'Unknown' if fsprotocol.nil?
|
|
|
|
case fsprotocol
|
|
when 5 then 'FTP'
|
|
when 0 then 'SSH'
|
|
else
|
|
'Unknown'
|
|
end
|
|
end
|
|
|
|
def parse_ini(file)
|
|
results = []
|
|
raise RuntimeError, 'No data to parse' if file.nil? || file.empty?
|
|
|
|
ini = Rex::Parser::Ini.from_s(file)
|
|
|
|
if ini['Configuration\\Security']
|
|
# if a Master Password is in use we give up
|
|
if ini['Configuration\\Security']['UseMasterPassword'].to_i == 1
|
|
raise RuntimeError, 'Master Password Set, unable to recover saved passwords!'
|
|
end
|
|
end
|
|
|
|
# Runs through each group in the ini file looking for all of the Sessions
|
|
ini.each_key do |group|
|
|
if group.include?('Sessions') && ini[group].has_key?('Password')
|
|
# Decrypt our password, and report on results
|
|
encrypted_password = ini[group]['Password']
|
|
user = ini[group]['UserName']
|
|
host = ini[group]['HostName']
|
|
sname = parse_protocol(ini[group]['FSProtocol'].to_i)
|
|
plaintext = decrypt_password(encrypted_password, "#{user}#{host}")
|
|
|
|
results << {
|
|
hostname: host,
|
|
password: plaintext,
|
|
portnumber: ini[group]['PortNumber'] || 22,
|
|
username: user,
|
|
protocol: sname
|
|
}
|
|
end
|
|
end
|
|
|
|
results
|
|
end
|
|
|
|
# Decrypts the next character in the password sequence
|
|
def decrypt_next_char(pwd)
|
|
if pwd.nil? || pwd.length <= 0
|
|
return 0, pwd
|
|
end
|
|
|
|
# Takes the first char from the encrypted password and then left shifts the returned index by 4 bits
|
|
a = pwd[0].hex << 4
|
|
|
|
# Takes the second char from the encrypted password
|
|
b = pwd[1].hex
|
|
|
|
# Adds the two results, XORs against 0xA3, NOTs it and then ANDs it with 0xFF
|
|
result = ~((a + b) ^ PWDALG_SIMPLE_MAGIC) & PWDALG_SIMPLE_FLAG
|
|
|
|
# Strips the first two chars off and returns our result
|
|
return result, pwd[2..-1]
|
|
end
|
|
|
|
def decrypt_password(pwd, key)
|
|
flag, pwd = decrypt_next_char(pwd)
|
|
|
|
if flag == PWDALG_SIMPLE_FLAG
|
|
_, pwd = decrypt_next_char(pwd)
|
|
length, pwd = decrypt_next_char(pwd)
|
|
else
|
|
length = flag
|
|
end
|
|
|
|
del, pwd = decrypt_next_char(pwd)
|
|
pwd = pwd[del*2..-1]
|
|
|
|
result = ""
|
|
length.times do
|
|
r, pwd = decrypt_next_char(pwd)
|
|
result << r.chr
|
|
end
|
|
|
|
if flag == PWDALG_SIMPLE_FLAG
|
|
result = result[key.length..-1]
|
|
end
|
|
|
|
result
|
|
end
|
|
end
|
|
end
|
|
end
|