Skip to content

Commit 6ab8502

Browse files
committed
More spec
1 parent 162b6a8 commit 6ab8502

File tree

5 files changed

+353
-21
lines changed

5 files changed

+353
-21
lines changed

lib/rex/exploitation/powershell/obfu.rb

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# -*- coding: binary -*-
22

3-
require 'zlib'
43
require 'rex/text'
54

65
module Rex
@@ -13,21 +12,23 @@ module Obfu
1312
#
1413
# Create hash of string substitutions
1514
#
15+
# @param strings [Array] array of strings to generate unique names
16+
#
17+
# @return [Hash] map of strings with new unique names
1618
def sub_map_generate(strings)
1719
map = {}
1820
strings.flatten.each do |str|
19-
map[str] = "$#{Rex::Text.rand_text_alpha(rand(2)+2)}"
20-
# Ensure our variables are unique
21-
while not map.values.uniq == map.values
22-
map[str] = "$#{Rex::Text.rand_text_alpha(rand(2)+2)}"
23-
end
21+
@rig.init_var(str)
22+
map[str] = @rig[str]
2423
end
25-
return map
24+
25+
map
2626
end
2727

2828
#
2929
# Remove comments
3030
#
31+
# @return [String] code without comments
3132
def strip_comments
3233
# Multi line
3334
code.gsub!(/<#(.*?)#>/m,'')
@@ -38,6 +39,7 @@ def strip_comments
3839
#
3940
# Remove empty lines
4041
#
42+
# @return [String] code without empty lines
4143
def strip_empty_lines
4244
# Windows EOL
4345
code.gsub!(/[\r\n]+/,"\r\n")
@@ -49,13 +51,15 @@ def strip_empty_lines
4951
# Remove whitespace
5052
# This can break some codes using inline .NET
5153
#
54+
# @return [String] code with whitespace stripped
5255
def strip_whitespace
5356
code.gsub!(/\s+/,' ')
5457
end
5558

5659
#
5760
# Identify variables and replace them
5861
#
62+
# @return [String] code with variable names replaced with unique values
5963
def sub_vars
6064
# Get list of variables, remove reserved
6165
vars = get_var_names
@@ -68,6 +72,8 @@ def sub_vars
6872
#
6973
# Identify function names and replace them
7074
#
75+
# @return [String] code with function names replaced with unique
76+
# values
7177
def sub_funcs
7278
# Find out function names, make map
7379
# Sub map keys for values
@@ -79,6 +85,7 @@ def sub_funcs
7985
#
8086
# Perform standard substitutions
8187
#
88+
# @return [String] code with standard substitution methods applied
8289
def standard_subs(subs = %w{strip_comments strip_whitespace sub_funcs sub_vars} )
8390
# Save us the trouble of breaking injected .NET and such
8491
subs.delete('strip_whitespace') unless string_literals.empty?
@@ -87,7 +94,8 @@ def standard_subs(subs = %w{strip_comments strip_whitespace sub_funcs sub_vars}
8794
self.send(modifier)
8895
end
8996
code.gsub!(/^$|^\s+$/,'')
90-
return code
97+
98+
code
9199
end
92100

93101
end # Obfu

lib/rex/exploitation/powershell/parser.rb

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
# -*- coding: binary -*-
22

3-
require 'zlib'
4-
require 'rex/text'
5-
63
module Rex
74
module Exploitation
85

@@ -69,26 +66,35 @@ module Parser
6966
#
7067
# Get variable names from code, removes reserved names from return
7168
#
69+
# @return [Array] variable names
7270
def get_var_names
73-
our_vars = code.scan(/\$[a-zA-Z\-\_]+/).uniq.flatten.map(&:strip)
74-
return our_vars.select {|v| !RESERVED_VARIABLE_NAMES.include?(v.downcase)}
71+
our_vars = code.scan(/\$[a-zA-Z\-\_0-9]+/).uniq.flatten.map(&:strip)
72+
our_vars.select {|v| !RESERVED_VARIABLE_NAMES.include?(v.downcase)}
7573
end
7674

7775
#
7876
# Get function names from code
7977
#
78+
# @return [Array] function names
8079
def get_func_names
81-
return code.scan(/function\s([a-zA-Z\-\_]+)/).uniq.flatten
80+
code.scan(/function\s([a-zA-Z\-\_0-9]+)/).uniq.flatten
8281
end
8382

83+
#
8484
# Attempt to find string literals in PSH expression
85+
#
86+
# @return [Array] string literals
8587
def get_string_literals
86-
code.scan(/@"(.*)"@|@'(.*)'@/)
88+
code.scan(/@"(.+?)"@|@'(.+?)'@/m)
8789
end
8890

8991
#
9092
# Scan code and return matches with index
9193
#
94+
# @param str [String] string to match in code
95+
# @param source [String] source code to match, defaults to @code
96+
#
97+
# @return [Array[String,Integer]] matched items with index
9298
def scan_with_index(str,source=code)
9399
::Enumerator.new do |y|
94100
source.scan(str) do
@@ -100,6 +106,9 @@ def scan_with_index(str,source=code)
100106
#
101107
# Return matching bracket type
102108
#
109+
# @param char [String] opening bracket character
110+
#
111+
# @return [String] matching closing bracket
103112
def match_start(char)
104113
case char
105114
when '{'
@@ -110,6 +119,8 @@ def match_start(char)
110119
']'
111120
when '<'
112121
'>'
122+
else
123+
raise ArgumentError, "Unknown starting bracket"
113124
end
114125
end
115126

@@ -120,27 +131,53 @@ def match_start(char)
120131
# Once the balanced matching bracket is found, all script content
121132
# between idx and the index of the matching bracket is returned
122133
#
134+
# @param idx [Integer] index of opening bracket
135+
#
136+
# @return [String] content between matching brackets
123137
def block_extract(idx)
138+
raise ArgumentError unless idx
139+
140+
if idx < 0 || idx >= code.length
141+
raise ArgumentError, "Invalid index"
142+
end
143+
124144
start = code[idx]
125145
stop = match_start(start)
126146
delims = scan_with_index(/#{Regexp.escape(start)}|#{Regexp.escape(stop)}/,code[idx+1..-1])
127147
delims.map {|x| x[1] = x[1] + idx + 1}
128148
c = 1
129149
sidx = nil
130150
# Go through delims till we balance, get idx
131-
while not c == 0 and x = delims.shift do
151+
while ((c != 0) && (x = delims.shift)) do
132152
sidx = x[1]
133153
x[0] == stop ? c -=1 : c+=1
134154
end
135-
return code[idx..sidx]
155+
156+
code[idx..sidx]
136157
end
137158

159+
#
160+
# Extract a block of function code
161+
#
162+
# @param func_name [String] function name
163+
# @param delete [Boolean] delete the function from the code
164+
#
165+
# @return [String] function block
138166
def get_func(func_name, delete = false)
139167
start = code.index(func_name)
168+
169+
return nil unless start
170+
140171
idx = code[start..-1].index('{') + start
141172
func_txt = block_extract(idx)
142-
code.delete(ftxt) if delete
143-
return Function.new(func_name,func_txt)
173+
174+
if delete
175+
delete_code = code[0..idx]
176+
delete_code << code[(idx+func_txt.length)..-1]
177+
@code = delete_code
178+
end
179+
180+
Function.new(func_name,func_txt)
144181
end
145182
end # Parser
146183

lib/rex/exploitation/powershell/script.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# -*- coding: binary -*-
22

3-
require 'zlib'
4-
require 'rex/text'
3+
require 'rex'
54

65
module Rex
76
module Exploitation
@@ -36,6 +35,8 @@ class Script
3635

3736
def initialize(code)
3837
@code = ''
38+
@rig = Rex::RandomIdentifierGenerator.new()
39+
3940
begin
4041
# Open code file for reading
4142
fd = ::File.new(code, 'rb')
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# -*- coding:binary -*-
2+
require 'spec_helper'
3+
4+
require 'rex/exploitation/powershell'
5+
6+
describe Rex::Exploitation::Powershell::Obfu do
7+
8+
let(:example_script_without_literal) do
9+
"""
10+
function Find-4624Logons
11+
{
12+
13+
if (-not ($NewLogonAccountDomain -cmatch \"NT\\sAUTHORITY\" -or $NewLogonAccountDomain -cmatch \"Window\\sManager\"))
14+
{
15+
$Key = $AccountName + $AccountDomain + $NewLogonAccountName + $NewLogonAccountDomain + $LogonType + $WorkstationName + $SourceNetworkAddress + $SourcePort
16+
if (-not $ReturnInfo.ContainsKey($Key))
17+
{
18+
$Properties = @{
19+
LogType = 4624
20+
LogSource = \"Security\"
21+
SourceAccountName = $AccountName
22+
SourceDomainName = $AccountDomain
23+
NewLogonAccountName = $NewLogonAccountName
24+
NewLogonAccountDomain = $NewLogonAccountDomain
25+
LogonType = $LogonType
26+
WorkstationName = $WorkstationName
27+
SourceNetworkAddress = $SourceNetworkAddress
28+
SourcePort = $SourcePort
29+
Count = 1
30+
Times = @($Logon.TimeGenerated)
31+
}
32+
33+
$ResultObj = New-Object PSObject -Property $Properties
34+
$ReturnInfo.Add($Key, $ResultObj)
35+
}
36+
else
37+
{
38+
$ReturnInfo[$Key].Count++
39+
$ReturnInfo[$Key].Times += ,$Logon.TimeGenerated
40+
}
41+
}
42+
}
43+
}"""
44+
45+
end
46+
47+
let(:example_script) do
48+
"""
49+
function Find-4624Logons
50+
{
51+
$some_literal = @\"
52+
using System;
53+
using System.Runtime.InteropServices;
54+
namespace $kernel32 {
55+
public class func {
56+
[Flags] public enum AllocationType { Commit = 0x1000, Reserve = 0x2000 }
57+
[Flags] public enum MemoryProtection { ExecuteReadWrite = 0x40 }
58+
[Flags] public enum Time : uint { Infinite = 0xFFFFFFFF }
59+
[DllImport(\"kernel32.dll\")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
60+
[DllImport(\"kernel32.dll\")] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
61+
[DllImport(\"kernel32.dll\")] public static extern int WaitForSingleObject(IntPtr hHandle, Time dwMilliseconds);
62+
}
63+
}
64+
\"@
65+
if (-not ($NewLogonAccountDomain -cmatch \"NT\\sAUTHORITY\" -or $NewLogonAccountDomain -cmatch \"Window\\sManager\"))
66+
{
67+
$Key = $AccountName + $AccountDomain + $NewLogonAccountName + $NewLogonAccountDomain + $LogonType + $WorkstationName + $SourceNetworkAddress + $SourcePort
68+
if (-not $ReturnInfo.ContainsKey($Key))
69+
{
70+
$Properties = @{
71+
LogType = 4624
72+
LogSource = \"Security\"
73+
SourceAccountName = $AccountName
74+
SourceDomainName = $AccountDomain
75+
NewLogonAccountName = $NewLogonAccountName
76+
NewLogonAccountDomain = $NewLogonAccountDomain
77+
LogonType = $LogonType
78+
WorkstationName = $WorkstationName
79+
SourceNetworkAddress = $SourceNetworkAddress
80+
SourcePort = $SourcePort
81+
Count = 1
82+
Times = @($Logon.TimeGenerated)
83+
}
84+
$literal2 = @\"parp\"@
85+
$ResultObj = New-Object PSObject -Property $Properties
86+
$ReturnInfo.Add($Key, $ResultObj)
87+
}
88+
else
89+
{
90+
$ReturnInfo[$Key].Count++
91+
$ReturnInfo[$Key].Times += ,$Logon.TimeGenerated
92+
}
93+
}
94+
}
95+
}"""
96+
97+
end
98+
99+
let(:subject) do
100+
Rex::Exploitation::Powershell::Script.new(example_script)
101+
end
102+
103+
let(:subject_no_literal) do
104+
Rex::Exploitation::Powershell::Script.new(example_script_without_literal)
105+
end
106+
107+
describe "::sub_map_generate" do
108+
it 'should return some unique variable names' do
109+
map = subject.sub_map_generate(['blah','parp'])
110+
map.should be
111+
map.should be_kind_of Hash
112+
map.empty?.should be_false
113+
map.should eq map.uniq
114+
end
115+
116+
it 'should not match upper or lowercase reserved names' do
117+
initial_vars = subject.get_var_names
118+
subject.code << "\r\n$SHELLID"
119+
subject.code << "\r\n$ShellId"
120+
subject.code << "\r\n$shellid"
121+
after_vars = subject.get_var_names
122+
initial_vars.should eq after_vars
123+
end
124+
end
125+
126+
end
127+

0 commit comments

Comments
 (0)