Module: Rex::Powershell::Obfu

Included in:
Function, Script
Defined in:
lib/rex/powershell/obfu.rb

Constant Summary collapse

MULTI_LINE_COMMENTS_REGEX =
Regexp.new(/<#(.*?)#>/m)
SINGLE_LINE_COMMENTS_REGEX =
Regexp.new(/^\s*#(?!.*region)(.*$)/i)
WINDOWS_EOL_REGEX =
Regexp.new(/[\r\n]+/)
UNIX_EOL_REGEX =
Regexp.new(/[\n]+/)
WHITESPACE_REGEX =
Regexp.new(/\s+/)
EMPTY_LINE_REGEX =
Regexp.new(/^$|^\s+$/)

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.descate_string_literal(string) ⇒ String

Deobfuscate a Powershell literal string value that was previously obfuscated by #scate_string_literal.

Parameters:

  • string (String)

    The obfuscated Powershell expression to deobfuscate.

Returns:

  • (String)

    The string literal value.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/rex/powershell/obfu.rb', line 73

def self.descate_string_literal(string)
  string = string.strip
  nest_level = [string.match(/^(\(*)/)[0].length, string.match(/(\)*)$/)[0].length].min
  string = string[nest_level...-nest_level].strip if nest_level > 0
  format_args = nil
  if (string =~ /\((?>[^)(]+|\g<0>)*\)/) == 0
    format = Regexp.last_match(0)
    format_args = string[format.length..-1].strip
    unless format_args =~ /-f\s*('.',\s*)*('.')/
      raise Exceptions::PowershellError, 'The obfuscated string structure is unsupported'
    end
    format_args = format_args[2..-1].strip.scan(/'(.)'/).map { |match| match[0] }
    string = format[1...-1].strip
  end

  unless string =~ /^'.*'$/
    raise Exceptions::PowershellError, 'The obfuscated string structure is unsupported'
  end
  string = string.gsub(/'\s*\+\s*'/, '') # process all concatenation operations
  unless format_args.nil? # process all format string operations
    string = string.gsub(/\{\s*\d+\s*\}/) do |index|
      format_args[index[1...-1].to_i]
    end
  end

  string[1...-1]
end

.scate_string_literal(string, threshold: 0.15) ⇒ String

Obfuscate a Powershell literal string value. The character set of the string is limited to alpha-numeric characters and some punctuation. This routine will use a combination of of techniques including formatting and concatenation. The result is an expression that can either be passed to a function or assigned to a variable.

Parameters:

  • string (String)

    The string value to obfuscate.

  • threshold (Float) (defaults to: 0.15)

    A floating point value between 0 and 1 that controls how much of the string is obfuscated. Higher values result in more obfuscation while 0 returns the original string without any obfuscation.

Returns:

  • (String)

    An obfuscated Powershell expression that evaluates to the specified string.

Raises:

  • (ArgumentError)


25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/rex/powershell/obfu.rb', line 25

def self.scate_string_literal(string, threshold: 0.15)
  # this hasn't been thoroughly tested for strings that contain alot of punctuation, just simple ones like
  # 'AmsiUtils', the most important characters that are assumed to be missing are quotes and braces
  raise ArgumentError.new('string contains an unsupported character') if string =~ /[^a-zA-Z0-9,+=\.\/]/
  raise ArgumentError.new('threshold must be between 0 and 1') unless threshold.between?(0, 1)

  new = original = string
  occurrences = {}
  original.each_char { |char|
    occurrences[char] = 0 unless occurrences.key?(char)
    occurrences[char] += 1
  }
  char_map = occurrences.group_by { |k,v| v }.sort_by { |k,v| -k }.map { |k,v| v.shuffle }.flatten(1)

  # phase 1
  format = []
  char_subs = 0.0
  while (char_subs / original.length.to_f) < threshold
    orig_char, occurrence_count = char_map.pop
    new = new.gsub(/(?<!\{)#{Regexp.escape(orig_char)}(?!\})/, "{#{format.length}}")
    format << "'#{orig_char}'"
    char_subs += occurrence_count
  end

  # phase 2
  concat = "'+'"
  positions = threshold > 0 ? (0..new.length).to_a.shuffle[0..(new.length * threshold)] : []
  positions.sort!
  positions.each_with_index do |position, index|
    new = new.insert(position + (index * concat.length), concat)
  end

  new = "'#{new}'"
  new = "(#{new})" unless threshold == 0

  final = new
  final << "-f#{format.join(',')}" unless format.empty?
  final = "(#{final})" unless format.empty? && threshold == 0
  final
end

Instance Method Details

#standard_subs(subs = %w(strip_comments strip_whitespace sub_funcs sub_vars)) ⇒ String

Perform standard substitutions

Returns:

  • (String)

    code with standard substitution methods applied



170
171
172
173
174
175
176
177
178
179
180
# File 'lib/rex/powershell/obfu.rb', line 170

def standard_subs(subs = %w(strip_comments strip_whitespace sub_funcs sub_vars))
  # Save us the trouble of breaking injected .NET and such
  subs.delete('strip_whitespace') unless get_string_literals.empty?
  # Run selected modifiers
  subs.each do |modifier|
    send(modifier)
  end
  code.gsub!(EMPTY_LINE_REGEX, '')

  code
end

#strip_commentsString

Remove comments

Returns:

  • (String)

    code without comments



105
106
107
108
109
110
111
112
# File 'lib/rex/powershell/obfu.rb', line 105

def strip_comments
  # Multi line
  code.gsub!(MULTI_LINE_COMMENTS_REGEX, '')
  # Single line
  code.gsub!(SINGLE_LINE_COMMENTS_REGEX, '')

  code
end

#strip_empty_linesString

Remove empty lines

Returns:

  • (String)

    code without empty lines



118
119
120
121
122
123
124
125
# File 'lib/rex/powershell/obfu.rb', line 118

def strip_empty_lines
  # Windows EOL
  code.gsub!(WINDOWS_EOL_REGEX, "\r\n")
  # UNIX EOL
  code.gsub!(UNIX_EOL_REGEX, "\n")

  code
end

#strip_whitespaceString

Remove whitespace This can break some codes using inline .NET

Returns:

  • (String)

    code with whitespace stripped



132
133
134
135
136
137
# File 'lib/rex/powershell/obfu.rb', line 132

def strip_whitespace
  code.gsub!(WHITESPACE_REGEX, ' ')
  code.strip!

  code
end

#sub_funcsString

Identify function names and replace them

Returns:

  • (String)

    code with function names replaced with unique values



157
158
159
160
161
162
163
164
# File 'lib/rex/powershell/obfu.rb', line 157

def sub_funcs
  # Find out function names, make map
  get_func_names.each do |var, _sub|
    code.gsub!(var, @rig.init_var(var))
  end

  code
end

#sub_varsString

Identify variables and replace them

Returns:

  • (String)

    code with variable names replaced with unique values



143
144
145
146
147
148
149
150
# File 'lib/rex/powershell/obfu.rb', line 143

def sub_vars
  # Get list of variables, remove reserved
  get_var_names.each do |var, _sub|
    code.gsub!(var, "$#{@rig.init_var(var)}")
  end

  code
end