diff --git a/lib/rex/exploitation/jsobfu.rb b/lib/rex/exploitation/jsobfu.rb index 746206b455..4b84d16b53 100644 --- a/lib/rex/exploitation/jsobfu.rb +++ b/lib/rex/exploitation/jsobfu.rb @@ -1,6 +1,7 @@ # -*- coding: binary -*- require 'rex/text' +require 'rex/random_identifier_generator' require 'rkelly' module Rex @@ -47,6 +48,15 @@ module Exploitation # class JSObfu + # 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 # @@ -60,6 +70,11 @@ class JSObfu @funcs = {} @vars = {} @debug = false + @rand_gen = Rex::RandomIdentifierGenerator.new( + :max_length => 15, + :first_char_set => Rex::Text::Alpha+"_$", + :char_set => Rex::Text::AlphaNumeric+"_$" + ) end # @@ -107,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+. # @@ -152,14 +182,12 @@ protected # Variables when ::RKelly::Nodes::VarDeclNode if @vars[node.name].nil? - #@vars[node.name] = "var_#{Rex::Text.rand_text_alpha(3+rand(12))}_#{node.name}" - @vars[node.name] = "#{Rex::Text.rand_text_alpha(3+rand(12))}" + @vars[node.name] = random_var_name end node.name = @vars[node.name] when ::RKelly::Nodes::ParameterNode if @vars[node.value].nil? - #@vars[node.value] = "param_#{Rex::Text.rand_text_alpha(3+rand(12))}_#{node.value}" - @vars[node.value] = "#{Rex::Text.rand_text_alpha(3+rand(12))}" + @vars[node.value] = random_var_name end node.value = @vars[node.value] when ::RKelly::Nodes::ResolveNode @@ -181,8 +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] = "func_#{Rex::Text.rand_text_alpha(3+rand(12))}_#{node.value}" - @funcs[node.value] = "#{Rex::Text.rand_text_alpha(3+rand(12))}" + @funcs[node.value] = random_var_name if @vars[node.value].nil? @vars[node.value] = @funcs[node.value] end diff --git a/spec/lib/rex/exploitation/jsobfu_spec.rb b/spec/lib/rex/exploitation/jsobfu_spec.rb new file mode 100644 index 0000000000..25e05cc34e --- /dev/null +++ b/spec/lib/rex/exploitation/jsobfu_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' +require 'rex/exploitation/jsobfu' + +describe Rex::Exploitation::JSObfu do + + subject(:jsobfu) do + described_class.new("") + end + + describe '#random_var_name' do + subject(:random_var_name) { jsobfu.random_var_name } + + it { should be_a String } + it { should_not be_empty } + + it 'is composed of _, $, alphanumeric chars' do + 20.times { expect(jsobfu.random_var_name).to match(/\A[a-zA-Z0-9$_]+\Z/) } + end + + it 'does not start with a number' do + 20.times { expect(jsobfu.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 + jsobfu.stub(:random_string) { generated.shift } + end + + it { should be random } + 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 + +end