More spec

bug/bundler_fix
Meatballs 2014-05-05 17:47:30 +01:00
parent 162b6a8ab9
commit 6ab85027a4
No known key found for this signature in database
GPG Key ID: 5380EAF01F2F8B38
5 changed files with 353 additions and 21 deletions

View File

@ -1,6 +1,5 @@
# -*- coding: binary -*- # -*- coding: binary -*-
require 'zlib'
require 'rex/text' require 'rex/text'
module Rex module Rex
@ -13,21 +12,23 @@ module Powershell
# #
# Create hash of string substitutions # 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) def sub_map_generate(strings)
map = {} map = {}
strings.flatten.each do |str| strings.flatten.each do |str|
map[str] = "$#{Rex::Text.rand_text_alpha(rand(2)+2)}" @rig.init_var(str)
# Ensure our variables are unique map[str] = @rig[str]
while not map.values.uniq == map.values
map[str] = "$#{Rex::Text.rand_text_alpha(rand(2)+2)}"
end end
end
return map map
end end
# #
# Remove comments # Remove comments
# #
# @return [String] code without comments
def strip_comments def strip_comments
# Multi line # Multi line
code.gsub!(/<#(.*?)#>/m,'') code.gsub!(/<#(.*?)#>/m,'')
@ -38,6 +39,7 @@ module Powershell
# #
# Remove empty lines # Remove empty lines
# #
# @return [String] code without empty lines
def strip_empty_lines def strip_empty_lines
# Windows EOL # Windows EOL
code.gsub!(/[\r\n]+/,"\r\n") code.gsub!(/[\r\n]+/,"\r\n")
@ -49,6 +51,7 @@ module Powershell
# Remove whitespace # Remove whitespace
# This can break some codes using inline .NET # This can break some codes using inline .NET
# #
# @return [String] code with whitespace stripped
def strip_whitespace def strip_whitespace
code.gsub!(/\s+/,' ') code.gsub!(/\s+/,' ')
end end
@ -56,6 +59,7 @@ module Powershell
# #
# Identify variables and replace them # Identify variables and replace them
# #
# @return [String] code with variable names replaced with unique values
def sub_vars def sub_vars
# Get list of variables, remove reserved # Get list of variables, remove reserved
vars = get_var_names vars = get_var_names
@ -68,6 +72,8 @@ module Powershell
# #
# Identify function names and replace them # Identify function names and replace them
# #
# @return [String] code with function names replaced with unique
# values
def sub_funcs def sub_funcs
# Find out function names, make map # Find out function names, make map
# Sub map keys for values # Sub map keys for values
@ -79,6 +85,7 @@ module Powershell
# #
# Perform standard substitutions # Perform standard substitutions
# #
# @return [String] code with standard substitution methods applied
def standard_subs(subs = %w{strip_comments strip_whitespace sub_funcs sub_vars} ) def standard_subs(subs = %w{strip_comments strip_whitespace sub_funcs sub_vars} )
# Save us the trouble of breaking injected .NET and such # Save us the trouble of breaking injected .NET and such
subs.delete('strip_whitespace') unless string_literals.empty? subs.delete('strip_whitespace') unless string_literals.empty?
@ -87,7 +94,8 @@ module Powershell
self.send(modifier) self.send(modifier)
end end
code.gsub!(/^$|^\s+$/,'') code.gsub!(/^$|^\s+$/,'')
return code
code
end end
end # Obfu end # Obfu

View File

@ -1,8 +1,5 @@
# -*- coding: binary -*- # -*- coding: binary -*-
require 'zlib'
require 'rex/text'
module Rex module Rex
module Exploitation module Exploitation
@ -69,26 +66,35 @@ module Powershell
# #
# Get variable names from code, removes reserved names from return # Get variable names from code, removes reserved names from return
# #
# @return [Array] variable names
def get_var_names def get_var_names
our_vars = code.scan(/\$[a-zA-Z\-\_]+/).uniq.flatten.map(&:strip) our_vars = code.scan(/\$[a-zA-Z\-\_0-9]+/).uniq.flatten.map(&:strip)
return our_vars.select {|v| !RESERVED_VARIABLE_NAMES.include?(v.downcase)} our_vars.select {|v| !RESERVED_VARIABLE_NAMES.include?(v.downcase)}
end end
# #
# Get function names from code # Get function names from code
# #
# @return [Array] function names
def get_func_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 end
#
# Attempt to find string literals in PSH expression # Attempt to find string literals in PSH expression
#
# @return [Array] string literals
def get_string_literals def get_string_literals
code.scan(/@"(.*)"@|@'(.*)'@/) code.scan(/@"(.+?)"@|@'(.+?)'@/m)
end end
# #
# Scan code and return matches with index # 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) def scan_with_index(str,source=code)
::Enumerator.new do |y| ::Enumerator.new do |y|
source.scan(str) do source.scan(str) do
@ -100,6 +106,9 @@ module Powershell
# #
# Return matching bracket type # Return matching bracket type
# #
# @param char [String] opening bracket character
#
# @return [String] matching closing bracket
def match_start(char) def match_start(char)
case char case char
when '{' when '{'
@ -110,6 +119,8 @@ module Powershell
']' ']'
when '<' when '<'
'>' '>'
else
raise ArgumentError, "Unknown starting bracket"
end end
end end
@ -120,7 +131,16 @@ module Powershell
# Once the balanced matching bracket is found, all script content # Once the balanced matching bracket is found, all script content
# between idx and the index of the matching bracket is returned # 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) def block_extract(idx)
raise ArgumentError unless idx
if idx < 0 || idx >= code.length
raise ArgumentError, "Invalid index"
end
start = code[idx] start = code[idx]
stop = match_start(start) stop = match_start(start)
delims = scan_with_index(/#{Regexp.escape(start)}|#{Regexp.escape(stop)}/,code[idx+1..-1]) delims = scan_with_index(/#{Regexp.escape(start)}|#{Regexp.escape(stop)}/,code[idx+1..-1])
@ -128,19 +148,36 @@ module Powershell
c = 1 c = 1
sidx = nil sidx = nil
# Go through delims till we balance, get idx # 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] sidx = x[1]
x[0] == stop ? c -=1 : c+=1 x[0] == stop ? c -=1 : c+=1
end end
return code[idx..sidx]
code[idx..sidx]
end 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) def get_func(func_name, delete = false)
start = code.index(func_name) start = code.index(func_name)
return nil unless start
idx = code[start..-1].index('{') + start idx = code[start..-1].index('{') + start
func_txt = block_extract(idx) 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
end # Parser end # Parser

View File

@ -1,7 +1,6 @@
# -*- coding: binary -*- # -*- coding: binary -*-
require 'zlib' require 'rex'
require 'rex/text'
module Rex module Rex
module Exploitation module Exploitation
@ -36,6 +35,8 @@ module Powershell
def initialize(code) def initialize(code)
@code = '' @code = ''
@rig = Rex::RandomIdentifierGenerator.new()
begin begin
# Open code file for reading # Open code file for reading
fd = ::File.new(code, 'rb') fd = ::File.new(code, 'rb')

View File

@ -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

View File

@ -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