diff --git a/modules/post/windows/gather/enum_av_excluded.rb b/modules/post/windows/gather/enum_av_excluded.rb new file mode 100644 index 0000000000..0aa167b783 --- /dev/null +++ b/modules/post/windows/gather/enum_av_excluded.rb @@ -0,0 +1,141 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' + +class Metasploit3 < Msf::Post + include Msf::Post::Windows::Registry + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Windows Antivirus Exclusions Enumeration', + 'Description' => %q( + This module will enumerate the file, directory, process and + extension-based exclusions from supported AV products, which + currently includes Microsoft Defender, Microsoft Security + Essentials/Antimalware, and Symantec Endpoint Protection. + ), + 'License' => MSF_LICENSE, + 'Author' => [ + 'Andrew Smith', # original metasploit module + 'Jon Hart ' # improved metasploit module + ], + 'Platform' => [ 'win' ], + # XXX: this will work with 'shell' when the sysinfo parts are removed + # and https://github.com/rapid7/metasploit-framework/issues/6328 and + # perhaps https://github.com/rapid7/metasploit-framework/issues/6316 + # are fixed + 'SessionTypes' => [ 'meterpreter' ] + ) + ) + + register_options( + [ + OptBool.new('DEFENDER', [true, 'Enumerate exclusions for Microsoft Defender', true]), + OptBool.new('ESSENTIALS', [true, 'Enumerate exclusions for Microsoft Security Essentials/Antimalware', true]), + OptBool.new('SEP', [true, 'Enumerate exclusions for Symantec Endpoint Protection (SEP)', true]) + ] + ) + end + + DEFENDER = 'Windows Defender' + DEFENDER_BASE_KEY = 'HKLM\\SOFTWARE\\Microsoft\\Windows Defender' + ESSENTIALS = 'Microsoft Security Essentials / Antimalware' + ESSENTIALS_BASE_KEY = 'HKLM\\SOFTWARE\\Microsoft\\Microsoft Antimalware' + SEP = 'Symantec Endpoint Protection (SEP)' + SEP_BASE_KEY = 'HKLM\\SOFTWARE\\Symantec\\Symantec Endpoint Protection' + + def av_installed?(base_key, product) + if registry_key_exist?(base_key) + print_good("Found #{product}") + true + else + false + end + end + + def excluded_sep + base_exclusion_key = "#{SEP_BASE_KEY}\\Exclusions\\ScanningEngines\\Directory" + admin_exclusion_key = "#{base_exclusion_key}\\Admin" + client_exclusion_key = "#{base_exclusion_key}\\Client" + + admin_paths = [] + if (admin_exclusion_keys = registry_enumkeys(admin_exclusion_key, @registry_view)) + admin_exclusion_keys.map do |key| + admin_paths << registry_getvaldata("#{admin_exclusion_key}\\#{key}", 'DirectoryName', @registry_view) + end + print_exclusions_table(SEP, 'admin path', admin_paths) + end + client_paths = [] + if (client_exclusion_keys = registry_enumkeys(client_exclusion_key, @registry_view)) + client_exclusion_keys.map do |key| + client_paths << registry_getvaldata("#{client_exclusion_key}\\#{key}", 'DirectoryName', @registry_view) + end + end + print_exclusions_table(SEP, 'client path', client_paths) + end + + def excluded_defender + print_exclusions_table(DEFENDER, 'extension', registry_enumvals("#{DEFENDER_BASE_KEY}\\Exclusions\\Extensions", @registry_view)) + print_exclusions_table(DEFENDER, 'path', registry_enumvals("#{DEFENDER_BASE_KEY}\\Exclusions\\Paths", @registry_view)) + print_exclusions_table(DEFENDER, 'process', registry_enumvals("#{DEFENDER_BASE_KEY}\\Exclusions\\Processes", @registry_view)) + end + + def excluded_mssec + print_exclusions_table(ESSENTIALS, 'extension', registry_enumvals("#{ESSENTIALS_BASE_KEY}\\Exclusions\\Extensions", @registry_view)) + print_exclusions_table(ESSENTIALS, 'path', registry_enumvals("#{ESSENTIALS_BASE_KEY}\\Exclusions\\Paths", @registry_view)) + print_exclusions_table(ESSENTIALS, 'process', registry_enumvals("#{ESSENTIALS_BASE_KEY}\\Exclusions\\Processes", @registry_view)) + end + + def print_exclusions_table(product, exclusion_type, exclusions) + exclusions ||= [] + exclusions = exclusions.compact.reject(&:blank?) + if exclusions.empty? + print_status("No #{exclusion_type} exclusions for #{product}") + return + end + table = Rex::Ui::Text::Table.new( + 'Header' => "#{product} excluded #{exclusion_type.pluralize}", + 'Indent' => 1, + 'Columns' => [ exclusion_type.capitalize ] + ) + exclusions.map { |exclusion| table << [exclusion] } + print_line(table.to_s) + end + + def setup + # all of these target applications seemingly store their registry + # keys/values at the same architecture of the host, so if we happen to be + # in a 32-bit process on a 64-bit machine, ensure that we read from the + # 64-bit keys/values, and otherwise use the native keys/values + @registry_view = sysinfo['Architecture'] =~ /WOW64/ ? REGISTRY_VIEW_64_BIT : REGISTRY_VIEW_NATIVE + unless datastore['DEFENDER'] || datastore['ESSENTIALS'] || datastore['SEP'] + fail_with(Failure::BadConfig, 'Must set one or more of DEFENDER, ESSENTIALS or SEP to true') + end + end + + def run + print_status("Enumerating Excluded Paths for AV on #{sysinfo['Computer']}") + + found = false + if datastore['DEFENDER'] && av_installed?(DEFENDER_BASE_KEY, DEFENDER) + found = true + excluded_defender + end + if datastore['ESSENTIALS'] && av_installed?(ESSENTIALS_BASE_KEY, ESSENTIALS) + found = true + excluded_mssec + end + if datastore['SEP'] && av_installed?(SEP_BASE_KEY, SEP) + found = true + excluded_sep + end + + print_error "No supported AV identified" unless found + end +end