diff --git a/lib/msf/core/exploit.rb b/lib/msf/core/exploit.rb index 81c37cab047c5..bf7a41b1e0cf1 100644 --- a/lib/msf/core/exploit.rb +++ b/lib/msf/core/exploit.rb @@ -706,7 +706,14 @@ def is_payload_compatible?(name) return false unless p # Skip over payloads that are too big - return false if payload_space && p.cached_size && p.cached_size > payload_space + if payload_space + csize = p.cached_size + if csize.nil? + meta = Msf::Modules::Metadata::Cache.instance.get_module_reference(type: Msf::MODULE_PAYLOAD, reference_name: p.refname) + csize = meta.payload_cached_size if meta && meta.respond_to?(:payload_cached_size) + end + return false if csize && csize > payload_space + end begin pi = p.new diff --git a/lib/msf/core/modules/metadata/cache.rb b/lib/msf/core/modules/metadata/cache.rb index aa10e2005e007..c784e6a52f6b8 100644 --- a/lib/msf/core/modules/metadata/cache.rb +++ b/lib/msf/core/modules/metadata/cache.rb @@ -119,6 +119,10 @@ def get_unchanged_module_references next end + if module_metadata.type == 'payload' && !module_metadata.instance_variable_defined?(:@payload_cached_size) + next + end + skip_reference_name_set = skip_reference_name_set_by_module_type[module_metadata.type] skip_reference_name_set.add(module_metadata.ref_name) end diff --git a/lib/msf/core/modules/metadata/obj.rb b/lib/msf/core/modules/metadata/obj.rb index aaea792589948..262b293133742 100644 --- a/lib/msf/core/modules/metadata/obj.rb +++ b/lib/msf/core/modules/metadata/obj.rb @@ -129,6 +129,8 @@ def build_platform_list(platform_string) attr_reader :stage_refname # @return [String, nil] Name of the stager if applicable attr_reader :stager_refname + # @return [Integer, nil] Cached size of the payload if applicable + attr_reader :payload_cached_size def initialize(module_instance, obj_hash = nil) unless obj_hash.nil? @@ -205,6 +207,18 @@ def initialize(module_instance, obj_hash = nil) @adapter_refname = module_instance.adapter_refname @adapted_refname = module_instance.adapted_refname end + if module_instance.respond_to?(:cached_size) + @payload_cached_size = module_instance.cached_size + if @payload_cached_size.nil? && module_instance.dynamic_size? + begin + require 'msf/util/payload_cached_size' + opts = Msf::Util::PayloadCachedSize.module_options(module_instance) + @payload_cached_size = module_instance.replicant.generate_simple(opts).bytesize + rescue => e + elog("Failed to generate a default size for dynamic payload #{module_instance.refname}: #{e.class} #{e.message}") + end + end + end # Due to potentially non-standard ASCII we force UTF-8 to ensure no problem with JSON serialization force_encoding(::Encoding::UTF_8) @@ -253,6 +267,7 @@ def to_json(*args) 'staged' => @staged, 'stage_refname' => @stage_refname, 'stager_refname' => @stager_refname, + 'payload_cached_size'=> @payload_cached_size, }.compact data.merge!(payload_data) end @@ -322,6 +337,9 @@ def init_from_hash(obj_hash) @staged = obj_hash['staged'] @stage_refname = obj_hash['stage_refname'] @stager_refname = obj_hash['stager_refname'] + if obj_hash.key?('payload_cached_size') + @payload_cached_size = obj_hash['payload_cached_size'] + end end def sort_platform_string diff --git a/spec/api/json_rpc_spec.rb b/spec/api/json_rpc_spec.rb index d8b861c766c54..4581f41d8a6f9 100644 --- a/spec/api/json_rpc_spec.rb +++ b/spec/api/json_rpc_spec.rb @@ -722,7 +722,7 @@ def mock_rack_env(mock_rack_env_value) host: host_ip, analyze_options: { payloads: [ - 'linux/x86/meterpreter_reverse_http' + 'windows/meterpreter_reverse_http' ] } } diff --git a/spec/lib/msf/core/exploit_spec.rb b/spec/lib/msf/core/exploit_spec.rb new file mode 100644 index 0000000000000..5facec7b5e4b5 --- /dev/null +++ b/spec/lib/msf/core/exploit_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +RSpec.describe Msf::Exploit do + let(:framework) { instance_double(Msf::Framework) } + let(:payloads) { double('Payloads') } + let(:subject) do + mod = Class.new(Msf::Exploit).new + allow(mod).to receive(:framework).and_return(framework) + allow(mod).to receive(:payload_space).and_return(2000) + allow(mod).to receive(:compatible?).and_return(true) + allow(mod).to receive(:privileged).and_return(true) + mod + end + + before do + allow(framework).to receive(:payloads).and_return(payloads) + end + + describe '#is_payload_compatible?' do + let(:payload_name) { 'test/payload' } + let(:payload_class) { double('PayloadClass', refname: payload_name) } + let(:payload_instance) { double('PayloadInstance', privileged: true) } + + before do + allow(payloads).to receive(:[]).with(payload_name).and_return(payload_class) + allow(payload_class).to receive(:new).and_return(payload_instance) + end + + context 'when payload cached_size is present' do + it 'returns false if cached_size is greater than payload_space' do + allow(payload_class).to receive(:cached_size).and_return(3000) + expect(subject.is_payload_compatible?(payload_name)).to be_falsey + end + + it 'returns true if cached_size is less than or equal to payload_space' do + allow(payload_class).to receive(:cached_size).and_return(1000) + expect(subject.is_payload_compatible?(payload_name)).to be_truthy + end + end + + context 'when payload cached_size is nil (e.g. dynamic)' do + before do + allow(payload_class).to receive(:cached_size).and_return(nil) + end + + context 'and cache contains payload_cached_size' do + let(:cache_instance) { instance_double(Msf::Modules::Metadata::Cache) } + let(:meta) { double('Metadata', payload_cached_size: 2500) } + + before do + allow(Msf::Modules::Metadata::Cache).to receive(:instance).and_return(cache_instance) + allow(cache_instance).to receive(:get_module_reference).with(type: Msf::MODULE_PAYLOAD, reference_name: payload_name).and_return(meta) + end + + it 'returns false if metadata payload_cached_size is greater than payload_space' do + expect(subject.is_payload_compatible?(payload_name)).to be_falsey + end + + it 'returns true if metadata payload_cached_size is less than payload_space' do + allow(meta).to receive(:payload_cached_size).and_return(1000) + expect(subject.is_payload_compatible?(payload_name)).to be_truthy + end + end + + context 'and cache does not contain payload_cached_size' do + let(:cache_instance) { instance_double(Msf::Modules::Metadata::Cache) } + + before do + allow(Msf::Modules::Metadata::Cache).to receive(:instance).and_return(cache_instance) + allow(cache_instance).to receive(:get_module_reference).with(type: Msf::MODULE_PAYLOAD, reference_name: payload_name).and_return(nil) + end + + it 'returns true (bypasses size check)' do + expect(subject.is_payload_compatible?(payload_name)).to be_truthy + end + end + end + end +end diff --git a/spec/lib/msf/core/modules/metadata/cache_spec.rb b/spec/lib/msf/core/modules/metadata/cache_spec.rb index 87a2bcf15522f..59fe4964d2e64 100644 --- a/spec/lib/msf/core/modules/metadata/cache_spec.rb +++ b/spec/lib/msf/core/modules/metadata/cache_spec.rb @@ -71,6 +71,7 @@ def make_module_instance(type:, refname:, path: '/modules/test.rb') allow(mod).to receive(:respond_to?).with(:targets).and_return(false) allow(mod).to receive(:respond_to?).with(:session_types).and_return(false) allow(mod).to receive(:respond_to?).with(:payload_type).and_return(false) + allow(mod).to receive(:respond_to?).with(:cached_size).and_return(false) mod end diff --git a/spec/lib/msf/core/modules/metadata/obj_spec.rb b/spec/lib/msf/core/modules/metadata/obj_spec.rb new file mode 100644 index 0000000000000..763191e9726c7 --- /dev/null +++ b/spec/lib/msf/core/modules/metadata/obj_spec.rb @@ -0,0 +1,110 @@ +require 'spec_helper' +require 'msf/core/modules/metadata/obj' + +RSpec.describe Msf::Modules::Metadata::Obj do + let(:module_instance) do + double('ModuleInstance', + name: 'Test Payload', + realname: 'payload/windows/test', + aliases: [], + disclosure_date: nil, + rank: 300, + type: 'payload', + description: 'Test Description', + author: ['test_author'], + references: [], + post_auth?: false, + default_cred?: false, + platform_to_s: 'Windows', + platform: Msf::Module::PlatformList.new('Windows'), + arch_to_s: 'x86', + datastore: { 'RPORT' => 4444 }, + file_path: '/modules/payloads/test.rb', + refname: 'windows/test', + needs_cleanup: false, + has_check?: false, + notes: {}, + session_types: [], + payload_type: Msf::Payload::Type::Single, + staged?: false, + cached_size: 1024, + dynamic_size?: false + ) + end + + before do + allow(module_instance.class).to receive(:refname).and_return('windows/test') + allow(::File).to receive(:mtime).and_return(Time.now) + end + + describe 'serialization and deserialization' do + it 'correctly serializes and deserializes payload_cached_size' do + obj = described_class.new(module_instance) + expect(obj.payload_cached_size).to eq(1024) + + json = obj.to_json + hash = JSON.parse(json) + + expect(hash['payload_cached_size']).to eq(1024) + + restored_obj = described_class.from_hash(hash) + expect(restored_obj.payload_cached_size).to eq(1024) + end + + context 'with dynamic payload_cached_size' do + let(:module_instance_dynamic) do + double('ModuleInstanceDynamic', + name: 'Dynamic Payload', + realname: 'payload/windows/dynamic', + aliases: [], + disclosure_date: nil, + rank: 300, + type: 'payload', + description: 'Test Description', + author: ['test_author'], + references: [], + post_auth?: false, + default_cred?: false, + platform_to_s: 'Windows', + platform: Msf::Module::PlatformList.new('Windows'), + arch_to_s: 'x86', + datastore: { 'RPORT' => 4444 }, + file_path: '/modules/payloads/dynamic.rb', + refname: 'windows/dynamic', + needs_cleanup: false, + has_check?: false, + notes: {}, + session_types: [], + payload_type: Msf::Payload::Type::Single, + staged?: false, + cached_size: nil, + dynamic_size?: true + ) + end + + before do + allow(module_instance_dynamic.class).to receive(:refname).and_return('windows/dynamic') + allow(module_instance_dynamic).to receive(:shortname).and_return('dynamic') + allow(module_instance_dynamic).to receive(:module_info).and_return({}) + + replicant = double('Replicant') + generated_payload = double('GeneratedPayload', bytesize: 250000) + allow(replicant).to receive(:generate_simple).and_return(generated_payload) + allow(module_instance_dynamic).to receive(:replicant).and_return(replicant) + end + + it 'generates a size and serializes/deserializes it' do + obj = described_class.new(module_instance_dynamic) + expect(obj.payload_cached_size).to eq(250000) + + json = obj.to_json + hash = JSON.parse(json) + + expect(hash['payload_cached_size']).to eq(250000) + + restored_obj = described_class.from_hash(hash) + expect(restored_obj.payload_cached_size).to eq(250000) + end + end + end +end