Land #3247 - Revert #3224 jsobfu string size fixes

bug/bundler_fix
sinn3r 2014-04-12 00:58:27 -05:00
commit 7b6b94acd5
No known key found for this signature in database
GPG Key ID: 2384DB4EF06F730B
4 changed files with 95 additions and 204 deletions

View File

@ -1,25 +1,25 @@
// Case matters, see lib/msf/core/constants.rb
// All of these should match up with constants in ::Msf::HttpClients
var clients_opera = "Opera";
var clients_ie = "MSIE";
var clients_ff = "Firefox";
var clients_chrome= "Chrome";
var clients_safari= "Safari";
clients_opera = "Opera";
clients_ie = "MSIE";
clients_ff = "Firefox";
clients_chrome= "Chrome";
clients_safari= "Safari";
// All of these should match up with constants in ::Msf::OperatingSystems
var oses_linux = "Linux";
var oses_windows = "Microsoft Windows";
var oses_mac_osx = "Mac OS X";
var oses_freebsd = "FreeBSD";
var oses_netbsd = "NetBSD";
var oses_openbsd = "OpenBSD";
oses_linux = "Linux";
oses_windows = "Microsoft Windows";
oses_mac_osx = "Mac OS X";
oses_freebsd = "FreeBSD";
oses_netbsd = "NetBSD";
oses_openbsd = "OpenBSD";
// All of these should match up with the ARCH_* constants
var arch_armle = "armle";
var arch_x86 = "x86";
var arch_x86_64 = "x86_64";
var arch_ppc = "ppc";
arch_armle = "armle";
arch_x86 = "x86";
arch_x86_64 = "x86_64";
arch_ppc = "ppc";
window.os_detect = {};

View File

@ -48,71 +48,14 @@ module Exploitation
#
class JSObfu
# A single Javascript scope, used as a key-value store
# to maintain uniqueness of members in generated closures.
# For speed this class is implemented as a subclass of Hash.
class Scope < Hash
# these keywords should never be used as a random var name
# source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Reserved_Words
RESERVED_KEYWORDS = %w(
break case catch continue debugger default delete do else finally
for function if in instanceof new return switch this throw try
typeof var void while with class enum export extends import super
implements interface let package private protected public static yield
)
# these vars should not be shadowed as they may be used in other obfuscated code
BUILTIN_VARS = %w(
String window unescape
)
# @param [Rex::Exploitation::JSObfu::Scope] parent an optional parent scope,
# sometimes necessary to prevent needless var shadowing
def initialize(parent=nil)
@parent = parent
@rand_gen = Rex::RandomIdentifierGenerator.new(
:max_length => 15,
:first_char_set => Rex::Text::Alpha+"_$",
:char_set => Rex::Text::AlphaNumeric+"_$",
:min_length => 1
)
end
# @return [String] a unique random var name that is not a reserved keyword
def random_var_name
len = 1
loop do
text = random_string(len)
unless has_key?(text) or
RESERVED_KEYWORDS.include?(text) or
BUILTIN_VARS.include?(text)
self[text] = nil
return text
end
len += 1
end
end
# @return [Boolean] var is in scope
def has_key?(key)
super or (@parent and @parent.has_key?(key))
end
# @return [String] a random string that can be used as a var
def random_string(len)
@rand_gen.generate(len)
end
end
#
# The maximum length of a string that can be passed through
# #transform_string without being chopped up into separate
# expressions and concatenated
#
MAX_STRING_CHUNK = 10000
# these keywords should never be used as a random var name
# source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Reserved_Words
RESERVED_KEYWORDS = %w(
break case catch continue debugger default delete do else finally
for function if in instanceof new return switch this throw try
typeof var void while with class enum export extends import super
implements interface let package private protected public static yield
)
#
# Abstract Syntax Tree generated by RKelly::Parser#parse
@ -126,8 +69,12 @@ class JSObfu
@code = code
@funcs = {}
@vars = {}
@scope = Scope.new
@debug = false
@rand_gen = Rex::RandomIdentifierGenerator.new(
:max_length => 15,
:first_char_set => Rex::Text::Alpha+"_$",
:char_set => Rex::Text::AlphaNumeric+"_$"
)
end
#
@ -175,8 +122,23 @@ class JSObfu
obfuscate_r(@ast)
end
# @return [String] a unique random var name that is not a reserved keyword
def random_var_name
loop do
text = random_string
unless @vars.has_value?(text) or RESERVED_KEYWORDS.include?(text)
return text
end
end
end
protected
# @return [String] a random string
def random_string
@rand_gen.generate
end
#
# Recursive method to obfuscate the given +ast+.
#
@ -220,12 +182,12 @@ protected
# Variables
when ::RKelly::Nodes::VarDeclNode
if @vars[node.name].nil?
@vars[node.name] = @scope.random_var_name
@vars[node.name] = random_var_name
end
node.name = @vars[node.name]
when ::RKelly::Nodes::ParameterNode
if @vars[node.value].nil?
@vars[node.value] = @scope.random_var_name
@vars[node.value] = random_var_name
end
node.value = @vars[node.value]
when ::RKelly::Nodes::ResolveNode
@ -247,7 +209,7 @@ protected
# Functions can also act as objects, so store them in the vars
# and the functions list so we can replace them in both places
if @funcs[node.value].nil? and not @funcs.values.include?(node.value)
@funcs[node.value] = @scope.random_var_name
@funcs[node.value] = random_var_name
if @vars[node.value].nil?
@vars[node.value] = @funcs[node.value]
end
@ -349,15 +311,14 @@ protected
str = str[1,str.length - 2]
return quote*2 if str.length == 0
if str.length > MAX_STRING_CHUNK
return safe_split(str, quote).map { |args| transform_string(args[1]) }.join('+')
end
transformed = case rand(2)
case rand(2)
when 0
transform_string_split_concat(str, quote)
transformed = transform_string_split_concat(str, quote)
when 1
transform_string_fromCharCode(str)
transformed = transform_string_fromCharCode(str)
#when 2
# # Currently no-op
# transformed = transform_string_unescape(str)
end
#$stderr.puts "Obfuscating str: #{str.ljust 30} #{transformed}"
@ -392,12 +353,12 @@ protected
break unless str[len]
break if len > max_len
# randomize the length of each part
break if (rand(max_len) == 0)
break if (rand(4) == 0)
end
part = str.slice!(0, len)
var = @scope.random_var_name
var = Rex::Text.rand_text_alpha(4)
parts.push( [ var, "#{quote}#{part}#{quote}" ] )
end
@ -448,6 +409,12 @@ protected
final
end
# TODO
#def transform_string_unescape(str)
# str
#end
#
# Return a call to String.fromCharCode() with each char of the input as arguments
#
@ -456,17 +423,9 @@ protected
# output: String.fromCharCode(0x41, 10)
#
def transform_string_fromCharCode(str)
"String.fromCharCode(#{string_to_bytes(str)})"
end
#
# Return a comma-separated list of byte values with random encodings (decimal/hex/octal)
#
def string_to_bytes(str)
len = 0
buf = "String.fromCharCode("
bytes = str.unpack("C*")
encoded_bytes = []
len = 0
while str.length > 0
if str[0,1] == "\\"
str.slice!(0,1)
@ -513,12 +472,16 @@ protected
else
char = str.slice!(0,1).unpack("C").first
end
encoded_bytes << rand_base(char)
buf << "#{rand_base(char)},"
end
# Strip off the last comma
buf = buf[0,buf.length-1] + ")"
transformed = buf
encoded_bytes.join(',')
transformed
end
end
end
end

View File

@ -1,50 +0,0 @@
require 'spec_helper'
require 'rex/exploitation/jsobfu'
describe Rex::Exploitation::JSObfu::Scope do
subject(:scope) do
described_class.new()
end
describe '#random_var_name' do
subject(:random_var_name) { scope.random_var_name }
it { should be_a String }
it { should_not be_empty }
it 'is composed of _, $, alphanumeric chars' do
20.times { expect(scope.random_var_name).to match(/\A[a-zA-Z0-9$_]+\Z/) }
end
it 'does not start with a number' do
20.times { expect(scope.random_var_name).not_to match(/\A[0-9]/) }
end
context 'when a reserved word is generated' do
let(:reserved) { described_class::RESERVED_KEYWORDS.first }
let(:random) { 'abcdef' }
let(:generated) { [reserved, reserved, reserved, random] }
before do
scope.stub(:random_string) { generated.shift }
end
it { should eq random }
end
context 'when a non-unique random var is generated' do
let(:preexisting) { 'preexist' }
let(:random) { 'abcdef' }
let(:generated) { [preexisting, preexisting, preexisting, random] }
before do
scope.stub(:random_string) { generated.shift }
scope[preexisting] = 1
end
it { should eq random }
end
end
end

View File

@ -7,66 +7,44 @@ describe Rex::Exploitation::JSObfu do
described_class.new("")
end
# surround the string in quotes
def quote(str, q='"'); "#{q}#{str}#{q}" end
describe '#random_var_name' do
subject(:random_var_name) { jsobfu.random_var_name }
describe '#transform_string' do
context 'when given a string of length > MAX_STRING_CHUNK' do
let(:js_string) { quote "ABC"*Rex::Exploitation::JSObfu::MAX_STRING_CHUNK }
it { should be_a String }
it { should_not be_empty }
it 'calls itself recursively' do
expect(jsobfu).to receive(:transform_string).at_least(2).times.and_call_original
jsobfu.send(:transform_string, js_string.dup)
end
it 'is composed of _, $, alphanumeric chars' do
20.times { expect(jsobfu.random_var_name).to match(/\A[a-zA-Z0-9$_]+\Z/) }
end
context 'when given a string of length < MAX_STRING_CHUNK' do
let(:js_string) { quote "A"*(Rex::Exploitation::JSObfu::MAX_STRING_CHUNK/2).to_i }
it 'does not call itself recursively' do
expect(jsobfu).to receive(:transform_string).once.and_call_original
jsobfu.send(:transform_string, js_string.dup)
end
end
end
describe '#safe_split' do
let(:js_string) { Rex::Text.to_hex("ABCDEFG"*100, "\\x") }
let(:quote) { '"' }
let(:parts) { 50.times.map { jsobfu.send(:safe_split, js_string.dup, quote).map{ |a| a[1] } } }
describe 'quoting' do
context 'when given a double-quote' do
let(:quote) { '"' }
it 'surrounds all the split strings with the same quote' do
expect(parts.flatten.all? { |part| part.start_with?(quote) }).to be_true
end
end
context 'when given a single-quote' do
let(:quote) { "'" }
it 'surrounds all the split strings with the same quote' do
expect(parts.flatten.all? { |part| part.start_with?(quote) }).to be_true
end
end
it 'does not start with a number' do
20.times { expect(jsobfu.random_var_name).not_to match(/\A[0-9]/) }
end
describe 'splitting' do
context 'when given a hex-escaped series of bytes' do
let(:js_string) { Rex::Text.to_hex("ABCDEFG"*100, "\\x") }
context 'when a reserved word is generated' do
let(:reserved) { described_class::RESERVED_KEYWORDS.first }
let(:random) { 'abcdef' }
let(:generated) { [reserved, reserved, reserved, random] }
it 'never splits in the middle of a hex escape' do
expect(parts.flatten.all? { |part| part.start_with?('"\\') }).to be_true
end
before do
jsobfu.stub(:random_string) { generated.shift }
end
context 'when given a unicode-escaped series of bytes' do
let(:js_string) { Rex::Text.to_unescape("ABCDEFG"*100).gsub!('%', '\\') }
it { should be random }
end
it 'never splits in the middle of a unicode escape' do
expect(parts.flatten.all? { |part| part.start_with?('"\\') }).to be_true
end
context 'when a non-unique random var is generated' do
let(:preexisting) { 'preexist' }
let(:random) { 'abcdef' }
let(:vars) { { 'jQuery' => preexisting } }
let(:generated) { [preexisting, preexisting, preexisting, random] }
before do
jsobfu.stub(:random_string) { generated.shift }
jsobfu.instance_variable_set("@vars", vars)
end
it { should be random }
end
end