More spec
parent
162b6a8ab9
commit
6ab85027a4
|
@ -1,6 +1,5 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'zlib'
|
||||
require 'rex/text'
|
||||
|
||||
module Rex
|
||||
|
@ -13,21 +12,23 @@ module Powershell
|
|||
#
|
||||
# Create hash of string substitutions
|
||||
#
|
||||
# @param strings [Array] array of strings to generate unique names
|
||||
#
|
||||
# @return [Hash] map of strings with new unique names
|
||||
def sub_map_generate(strings)
|
||||
map = {}
|
||||
strings.flatten.each do |str|
|
||||
map[str] = "$#{Rex::Text.rand_text_alpha(rand(2)+2)}"
|
||||
# Ensure our variables are unique
|
||||
while not map.values.uniq == map.values
|
||||
map[str] = "$#{Rex::Text.rand_text_alpha(rand(2)+2)}"
|
||||
end
|
||||
@rig.init_var(str)
|
||||
map[str] = @rig[str]
|
||||
end
|
||||
return map
|
||||
|
||||
map
|
||||
end
|
||||
|
||||
#
|
||||
# Remove comments
|
||||
#
|
||||
# @return [String] code without comments
|
||||
def strip_comments
|
||||
# Multi line
|
||||
code.gsub!(/<#(.*?)#>/m,'')
|
||||
|
@ -38,6 +39,7 @@ module Powershell
|
|||
#
|
||||
# Remove empty lines
|
||||
#
|
||||
# @return [String] code without empty lines
|
||||
def strip_empty_lines
|
||||
# Windows EOL
|
||||
code.gsub!(/[\r\n]+/,"\r\n")
|
||||
|
@ -49,6 +51,7 @@ module Powershell
|
|||
# Remove whitespace
|
||||
# This can break some codes using inline .NET
|
||||
#
|
||||
# @return [String] code with whitespace stripped
|
||||
def strip_whitespace
|
||||
code.gsub!(/\s+/,' ')
|
||||
end
|
||||
|
@ -56,6 +59,7 @@ module Powershell
|
|||
#
|
||||
# Identify variables and replace them
|
||||
#
|
||||
# @return [String] code with variable names replaced with unique values
|
||||
def sub_vars
|
||||
# Get list of variables, remove reserved
|
||||
vars = get_var_names
|
||||
|
@ -68,6 +72,8 @@ module Powershell
|
|||
#
|
||||
# Identify function names and replace them
|
||||
#
|
||||
# @return [String] code with function names replaced with unique
|
||||
# values
|
||||
def sub_funcs
|
||||
# Find out function names, make map
|
||||
# Sub map keys for values
|
||||
|
@ -79,6 +85,7 @@ module Powershell
|
|||
#
|
||||
# Perform standard substitutions
|
||||
#
|
||||
# @return [String] code with standard substitution methods applied
|
||||
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 string_literals.empty?
|
||||
|
@ -87,7 +94,8 @@ module Powershell
|
|||
self.send(modifier)
|
||||
end
|
||||
code.gsub!(/^$|^\s+$/,'')
|
||||
return code
|
||||
|
||||
code
|
||||
end
|
||||
|
||||
end # Obfu
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'zlib'
|
||||
require 'rex/text'
|
||||
|
||||
module Rex
|
||||
module Exploitation
|
||||
|
||||
|
@ -69,26 +66,35 @@ module Powershell
|
|||
#
|
||||
# Get variable names from code, removes reserved names from return
|
||||
#
|
||||
# @return [Array] variable names
|
||||
def get_var_names
|
||||
our_vars = code.scan(/\$[a-zA-Z\-\_]+/).uniq.flatten.map(&:strip)
|
||||
return our_vars.select {|v| !RESERVED_VARIABLE_NAMES.include?(v.downcase)}
|
||||
our_vars = code.scan(/\$[a-zA-Z\-\_0-9]+/).uniq.flatten.map(&:strip)
|
||||
our_vars.select {|v| !RESERVED_VARIABLE_NAMES.include?(v.downcase)}
|
||||
end
|
||||
|
||||
#
|
||||
# Get function names from code
|
||||
#
|
||||
# @return [Array] function names
|
||||
def get_func_names
|
||||
return code.scan(/function\s([a-zA-Z\-\_]+)/).uniq.flatten
|
||||
code.scan(/function\s([a-zA-Z\-\_0-9]+)/).uniq.flatten
|
||||
end
|
||||
|
||||
#
|
||||
# Attempt to find string literals in PSH expression
|
||||
#
|
||||
# @return [Array] string literals
|
||||
def get_string_literals
|
||||
code.scan(/@"(.*)"@|@'(.*)'@/)
|
||||
code.scan(/@"(.+?)"@|@'(.+?)'@/m)
|
||||
end
|
||||
|
||||
#
|
||||
# Scan code and return matches with index
|
||||
#
|
||||
# @param str [String] string to match in code
|
||||
# @param source [String] source code to match, defaults to @code
|
||||
#
|
||||
# @return [Array[String,Integer]] matched items with index
|
||||
def scan_with_index(str,source=code)
|
||||
::Enumerator.new do |y|
|
||||
source.scan(str) do
|
||||
|
@ -100,6 +106,9 @@ module Powershell
|
|||
#
|
||||
# Return matching bracket type
|
||||
#
|
||||
# @param char [String] opening bracket character
|
||||
#
|
||||
# @return [String] matching closing bracket
|
||||
def match_start(char)
|
||||
case char
|
||||
when '{'
|
||||
|
@ -110,6 +119,8 @@ module Powershell
|
|||
']'
|
||||
when '<'
|
||||
'>'
|
||||
else
|
||||
raise ArgumentError, "Unknown starting bracket"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -120,7 +131,16 @@ module Powershell
|
|||
# Once the balanced matching bracket is found, all script content
|
||||
# between idx and the index of the matching bracket is returned
|
||||
#
|
||||
# @param idx [Integer] index of opening bracket
|
||||
#
|
||||
# @return [String] content between matching brackets
|
||||
def block_extract(idx)
|
||||
raise ArgumentError unless idx
|
||||
|
||||
if idx < 0 || idx >= code.length
|
||||
raise ArgumentError, "Invalid index"
|
||||
end
|
||||
|
||||
start = code[idx]
|
||||
stop = match_start(start)
|
||||
delims = scan_with_index(/#{Regexp.escape(start)}|#{Regexp.escape(stop)}/,code[idx+1..-1])
|
||||
|
@ -128,19 +148,36 @@ module Powershell
|
|||
c = 1
|
||||
sidx = nil
|
||||
# Go through delims till we balance, get idx
|
||||
while not c == 0 and x = delims.shift do
|
||||
while ((c != 0) && (x = delims.shift)) do
|
||||
sidx = x[1]
|
||||
x[0] == stop ? c -=1 : c+=1
|
||||
end
|
||||
return code[idx..sidx]
|
||||
|
||||
code[idx..sidx]
|
||||
end
|
||||
|
||||
#
|
||||
# Extract a block of function code
|
||||
#
|
||||
# @param func_name [String] function name
|
||||
# @param delete [Boolean] delete the function from the code
|
||||
#
|
||||
# @return [String] function block
|
||||
def get_func(func_name, delete = false)
|
||||
start = code.index(func_name)
|
||||
|
||||
return nil unless start
|
||||
|
||||
idx = code[start..-1].index('{') + start
|
||||
func_txt = block_extract(idx)
|
||||
code.delete(ftxt) if delete
|
||||
return Function.new(func_name,func_txt)
|
||||
|
||||
if delete
|
||||
delete_code = code[0..idx]
|
||||
delete_code << code[(idx+func_txt.length)..-1]
|
||||
@code = delete_code
|
||||
end
|
||||
|
||||
Function.new(func_name,func_txt)
|
||||
end
|
||||
end # Parser
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
require 'zlib'
|
||||
require 'rex/text'
|
||||
require 'rex'
|
||||
|
||||
module Rex
|
||||
module Exploitation
|
||||
|
@ -36,6 +35,8 @@ module Powershell
|
|||
|
||||
def initialize(code)
|
||||
@code = ''
|
||||
@rig = Rex::RandomIdentifierGenerator.new()
|
||||
|
||||
begin
|
||||
# Open code file for reading
|
||||
fd = ::File.new(code, 'rb')
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/exploitation/powershell'
|
||||
|
||||
describe Rex::Exploitation::Powershell::Obfu do
|
||||
|
||||
let(:example_script_without_literal) do
|
||||
"""
|
||||
function Find-4624Logons
|
||||
{
|
||||
|
||||
if (-not ($NewLogonAccountDomain -cmatch \"NT\\sAUTHORITY\" -or $NewLogonAccountDomain -cmatch \"Window\\sManager\"))
|
||||
{
|
||||
$Key = $AccountName + $AccountDomain + $NewLogonAccountName + $NewLogonAccountDomain + $LogonType + $WorkstationName + $SourceNetworkAddress + $SourcePort
|
||||
if (-not $ReturnInfo.ContainsKey($Key))
|
||||
{
|
||||
$Properties = @{
|
||||
LogType = 4624
|
||||
LogSource = \"Security\"
|
||||
SourceAccountName = $AccountName
|
||||
SourceDomainName = $AccountDomain
|
||||
NewLogonAccountName = $NewLogonAccountName
|
||||
NewLogonAccountDomain = $NewLogonAccountDomain
|
||||
LogonType = $LogonType
|
||||
WorkstationName = $WorkstationName
|
||||
SourceNetworkAddress = $SourceNetworkAddress
|
||||
SourcePort = $SourcePort
|
||||
Count = 1
|
||||
Times = @($Logon.TimeGenerated)
|
||||
}
|
||||
|
||||
$ResultObj = New-Object PSObject -Property $Properties
|
||||
$ReturnInfo.Add($Key, $ResultObj)
|
||||
}
|
||||
else
|
||||
{
|
||||
$ReturnInfo[$Key].Count++
|
||||
$ReturnInfo[$Key].Times += ,$Logon.TimeGenerated
|
||||
}
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
end
|
||||
|
||||
let(:example_script) do
|
||||
"""
|
||||
function Find-4624Logons
|
||||
{
|
||||
$some_literal = @\"
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace $kernel32 {
|
||||
public class func {
|
||||
[Flags] public enum AllocationType { Commit = 0x1000, Reserve = 0x2000 }
|
||||
[Flags] public enum MemoryProtection { ExecuteReadWrite = 0x40 }
|
||||
[Flags] public enum Time : uint { Infinite = 0xFFFFFFFF }
|
||||
[DllImport(\"kernel32.dll\")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
|
||||
[DllImport(\"kernel32.dll\")] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
|
||||
[DllImport(\"kernel32.dll\")] public static extern int WaitForSingleObject(IntPtr hHandle, Time dwMilliseconds);
|
||||
}
|
||||
}
|
||||
\"@
|
||||
if (-not ($NewLogonAccountDomain -cmatch \"NT\\sAUTHORITY\" -or $NewLogonAccountDomain -cmatch \"Window\\sManager\"))
|
||||
{
|
||||
$Key = $AccountName + $AccountDomain + $NewLogonAccountName + $NewLogonAccountDomain + $LogonType + $WorkstationName + $SourceNetworkAddress + $SourcePort
|
||||
if (-not $ReturnInfo.ContainsKey($Key))
|
||||
{
|
||||
$Properties = @{
|
||||
LogType = 4624
|
||||
LogSource = \"Security\"
|
||||
SourceAccountName = $AccountName
|
||||
SourceDomainName = $AccountDomain
|
||||
NewLogonAccountName = $NewLogonAccountName
|
||||
NewLogonAccountDomain = $NewLogonAccountDomain
|
||||
LogonType = $LogonType
|
||||
WorkstationName = $WorkstationName
|
||||
SourceNetworkAddress = $SourceNetworkAddress
|
||||
SourcePort = $SourcePort
|
||||
Count = 1
|
||||
Times = @($Logon.TimeGenerated)
|
||||
}
|
||||
$literal2 = @\"parp\"@
|
||||
$ResultObj = New-Object PSObject -Property $Properties
|
||||
$ReturnInfo.Add($Key, $ResultObj)
|
||||
}
|
||||
else
|
||||
{
|
||||
$ReturnInfo[$Key].Count++
|
||||
$ReturnInfo[$Key].Times += ,$Logon.TimeGenerated
|
||||
}
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
end
|
||||
|
||||
let(:subject) do
|
||||
Rex::Exploitation::Powershell::Script.new(example_script)
|
||||
end
|
||||
|
||||
let(:subject_no_literal) do
|
||||
Rex::Exploitation::Powershell::Script.new(example_script_without_literal)
|
||||
end
|
||||
|
||||
describe "::sub_map_generate" do
|
||||
it 'should return some unique variable names' do
|
||||
map = subject.sub_map_generate(['blah','parp'])
|
||||
map.should be
|
||||
map.should be_kind_of Hash
|
||||
map.empty?.should be_false
|
||||
map.should eq map.uniq
|
||||
end
|
||||
|
||||
it 'should not match upper or lowercase reserved names' do
|
||||
initial_vars = subject.get_var_names
|
||||
subject.code << "\r\n$SHELLID"
|
||||
subject.code << "\r\n$ShellId"
|
||||
subject.code << "\r\n$shellid"
|
||||
after_vars = subject.get_var_names
|
||||
initial_vars.should eq after_vars
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/exploitation/powershell'
|
||||
|
||||
describe Rex::Exploitation::Powershell::Parser do
|
||||
|
||||
let(:example_script) do
|
||||
"""
|
||||
function Find-4624Logons
|
||||
{
|
||||
$some_literal = @\"
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
namespace $kernel32 {
|
||||
public class func {
|
||||
[Flags] public enum AllocationType { Commit = 0x1000, Reserve = 0x2000 }
|
||||
[Flags] public enum MemoryProtection { ExecuteReadWrite = 0x40 }
|
||||
[Flags] public enum Time : uint { Infinite = 0xFFFFFFFF }
|
||||
[DllImport(\"kernel32.dll\")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
|
||||
[DllImport(\"kernel32.dll\")] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
|
||||
[DllImport(\"kernel32.dll\")] public static extern int WaitForSingleObject(IntPtr hHandle, Time dwMilliseconds);
|
||||
}
|
||||
}
|
||||
\"@
|
||||
if (-not ($NewLogonAccountDomain -cmatch \"NT\\sAUTHORITY\" -or $NewLogonAccountDomain -cmatch \"Window\\sManager\"))
|
||||
{
|
||||
$Key = $AccountName + $AccountDomain + $NewLogonAccountName + $NewLogonAccountDomain + $LogonType + $WorkstationName + $SourceNetworkAddress + $SourcePort
|
||||
if (-not $ReturnInfo.ContainsKey($Key))
|
||||
{
|
||||
$Properties = @{
|
||||
LogType = 4624
|
||||
LogSource = \"Security\"
|
||||
SourceAccountName = $AccountName
|
||||
SourceDomainName = $AccountDomain
|
||||
NewLogonAccountName = $NewLogonAccountName
|
||||
NewLogonAccountDomain = $NewLogonAccountDomain
|
||||
LogonType = $LogonType
|
||||
WorkstationName = $WorkstationName
|
||||
SourceNetworkAddress = $SourceNetworkAddress
|
||||
SourcePort = $SourcePort
|
||||
Count = 1
|
||||
Times = @($Logon.TimeGenerated)
|
||||
}
|
||||
$literal2 = @\"parp\"@
|
||||
$ResultObj = New-Object PSObject -Property $Properties
|
||||
$ReturnInfo.Add($Key, $ResultObj)
|
||||
}
|
||||
else
|
||||
{
|
||||
$ReturnInfo[$Key].Count++
|
||||
$ReturnInfo[$Key].Times += ,$Logon.TimeGenerated
|
||||
}
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
end
|
||||
|
||||
let(:subject) do
|
||||
Rex::Exploitation::Powershell::Script.new(example_script)
|
||||
end
|
||||
|
||||
describe "::get_var_names" do
|
||||
it 'should return some variable names' do
|
||||
vars = subject.get_var_names
|
||||
vars.should be
|
||||
vars.should be_kind_of Array
|
||||
vars.length.should be > 0
|
||||
vars.include?('$ResultObj').should be_true
|
||||
end
|
||||
|
||||
it 'should not match upper or lowercase reserved names' do
|
||||
initial_vars = subject.get_var_names
|
||||
subject.code << "\r\n$SHELLID"
|
||||
subject.code << "\r\n$ShellId"
|
||||
subject.code << "\r\n$shellid"
|
||||
after_vars = subject.get_var_names
|
||||
initial_vars.should eq after_vars
|
||||
end
|
||||
end
|
||||
|
||||
describe "::get_func_names" do
|
||||
it 'should return some function names' do
|
||||
funcs = subject.get_func_names
|
||||
funcs.should be
|
||||
funcs.should be_kind_of Array
|
||||
funcs.length.should be > 0
|
||||
funcs.include?('Find-4624Logons').should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "::get_string_literals" do
|
||||
it 'should return some string literals' do
|
||||
literals = subject.get_string_literals
|
||||
literals.should be
|
||||
literals.should be_kind_of Array
|
||||
literals.length.should be > 0
|
||||
literals[0].include?('parp').should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "::scan_with_index" do
|
||||
it 'should scan code and return the items with an index' do
|
||||
scan = subject.scan_with_index('DllImport')
|
||||
scan.should be
|
||||
scan.should be_kind_of Array
|
||||
scan.length.should be > 0
|
||||
scan[0].should be_kind_of Array
|
||||
scan[0][0].should be_kind_of String
|
||||
scan[0][1].should be_kind_of Integer
|
||||
end
|
||||
end
|
||||
|
||||
describe "::match_start" do
|
||||
it 'should match the correct brackets' do
|
||||
subject.match_start('{').should eq '}'
|
||||
subject.match_start('(').should eq ')'
|
||||
subject.match_start('[').should eq ']'
|
||||
subject.match_start('<').should eq '>'
|
||||
expect { subject.match_start('p') }.to raise_exception(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "::block_extract" do
|
||||
it 'should extract a block between brackets given an index' do
|
||||
idx = subject.code.index('{')
|
||||
block = subject.block_extract(idx)
|
||||
block.should be
|
||||
block.should be_kind_of String
|
||||
end
|
||||
|
||||
it 'should raise a runtime error if given an invalid index' do
|
||||
expect { subject.block_extract(nil) }.to raise_error(ArgumentError)
|
||||
expect { subject.block_extract(-1) }.to raise_error(ArgumentError)
|
||||
expect { subject.block_extract(subject.code.length) }.to raise_error(ArgumentError)
|
||||
expect { subject.block_extract(59) }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "::get_func" do
|
||||
it 'should extract a function from the code' do
|
||||
function = subject.get_func('Find-4624Logons')
|
||||
function.should be
|
||||
function.should be_kind_of Rex::Exploitation::Powershell::Function
|
||||
end
|
||||
|
||||
it 'should return nil if function doesnt exist' do
|
||||
function = subject.get_func(Rex::Text.rand_text_alpha(5))
|
||||
function.should be_nil
|
||||
end
|
||||
|
||||
it 'should delete the function if delete is true' do
|
||||
function = subject.get_func('Find-4624Logons', true)
|
||||
subject.code.include?('DllImport').should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue