Parent

Class/Module Index [+]

Quicksearch

Rex::Exploitation::JSObfu

Obfuscate JavaScript by randomizing as much as possible and removing easily-signaturable string constants.

Example:

js = ::Rex::Exploitation::JSObfu.new %Q|
  var a = "0\\612\\063\\x34\\x35\\x36\\x37\\x38\\u0039";
  var b = { foo : "foo", bar : "bar" }
  alert(a);
  alert(b.foo);
|
js.obfuscate
puts js

Example Output:

var VwxvESbCgv = String.fromCharCode(0x30,0x31,062,063,064,53,0x36,067,070,0x39);
var ToWZPn = {
  "\146\157\x6f": (function () { var yDyv="o",YnCL="o",Qcsa="f"; return Qcsa+YnCL+yDyv })(),
  "\142ar": String.fromCharCode(0142,97,0162)
};
alert(VwxvESbCgv);
alert(ToWZPn.foo);

NOTE: Variables MUST be declared with a 'var' statement BEFORE first use (or not at all) for this to generate correct code! If variables are not declared they will not be randomized but the generated code will be correct.

Bad Example Javascript:

a = "asdf"; // this variable hasn't been declared and will not be randomized
var a;
alert(a); // real js engines will alert "asdf" here

Bad Example Obfuscated:

a = (function () { var hpHu="f",oyTm="asd"; return oyTm+hpHu })();
var zSrnHpEfJZtg;
alert(zSrnHpEfJZtg);

Notice that the first usage of a (before it was declared) is not randomized. Thus, the obfuscated version will alert 'undefined' instead of "asdf".

Attributes

ast[R]

Abstract Syntax Tree generated by RKelly::Parser#parse

Public Class Methods

new(code) click to toggle source

Saves code for later obfuscation with obfuscate

# File lib/rex/exploitation/jsobfu.rb, line 60
def initialize(code)
        @code = code
        @funcs = {}
        @vars  = {}
        @debug = false
end

Public Instance Methods

<<(str) click to toggle source

Add str to the un-obfuscated code.

Calling this method after obfuscate is undefined

# File lib/rex/exploitation/jsobfu.rb, line 72
def <<(str)
        @code << str
end
obfuscate() click to toggle source

Parse and obfuscate

# File lib/rex/exploitation/jsobfu.rb, line 107
def obfuscate
        parse
        obfuscate_r(@ast)
end
sym(lookup) click to toggle source

Return the obfuscated name of a symbol

You MUST call obfuscate before this method!

# File lib/rex/exploitation/jsobfu.rb, line 93
def sym(lookup)
        if @vars[lookup]
                ret = @vars[lookup]
        elsif @funcs[lookup]
                ret = @funcs[lookup]
        else
                ret = lookup
        end
        ret
end
to_s() click to toggle source

Return the (possibly obfuscated) code as a string.

If obfuscate has not been called before this, returns the parsed, unobfuscated code. This can be useful for example to remove comments and standardize spacing.

# File lib/rex/exploitation/jsobfu.rb, line 83
def to_s
        parse if not @ast
        @ast.to_ecma
end

Protected Instance Methods

escape_length(str) click to toggle source

Stolen from obfuscatejs.rb

Determines the length of an escape sequence

# File lib/rex/exploitation/jsobfu.rb, line 348
def escape_length(str)
        esc_len = nil
        if str[0,1] == "\\"
                case str[1,1]
                when "u"; esc_len = 6     # unicode \u1234
                when "x"; esc_len = 4     # hex, \x41
                when /[0-7]/              # octal, \123, \0
                        str[1,3] =~ /([0-7]{1,3})/
                        if $1.to_i(8) > 255
                                str[1,3] =~ /([0-7]{1,2})/
                        end
                        esc_len = 1 + $1.length
                else; esc_len = 2         # \" \n, etc.
                end
        end
        esc_len
end
obfuscate_r(ast) click to toggle source

Recursive method to obfuscate the given ast.

ast should be the result of RKelly::Parser#parse

# File lib/rex/exploitation/jsobfu.rb, line 119
def obfuscate_r(ast)
        ast.each do |node|
                #if node.respond_to? :value and node.value.kind_of? String and node.value =~ /bodyOnLoad/i
                #    $stdout.puts("bodyOnLoad: #{node.class}: #{node.value}")
                #end

                case node
                when nil
                        nil

                when ::RKelly::Nodes::SourceElementsNode
                        # Recurse
                        obfuscate_r(node.value)

                #when ::RKelly::Nodes::ObjectLiteralNode
                        # TODO
                        #$stdout.puts(node.methods - Object.new.methods)
                        #$stdout.puts(node.value.inspect)

                when ::RKelly::Nodes::PropertyNode
                        # Property names must be bare words or string literals NOT
                        # expressions!  Can't use transform_string() here
                        if node.name =~ /^[a-zA-Z_][a-zA-Z0-9_]*$/
                                n = '"'
                                node.name.unpack("C*") { |c|
                                        case rand(3)
                                        when 0; n << "\\x%02x"%(c)
                                        when 1; n << "\\#{c.to_s 8}"
                                        when 2; n << [c].pack("C")
                                        end
                                }
                                n << '"'
                                node.name = n
                        end

                # 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))}"
                        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))}"
                        end
                        node.value = @vars[node.value]
                when ::RKelly::Nodes::ResolveNode
                        #$stdout.puts("Resolve bodyOnload: #{@vars[node.value]}") if "bodyOnLoad" == node.value
                        node.value = @vars[node.value] if @vars[node.value]
                when ::RKelly::Nodes::DotAccessorNode
                        case node.value
                        when ::RKelly::Nodes::ResolveNode
                                if @vars[node.value.value]
                                        node.value.value = @vars[node.value.value]
                                end
                        #else
                        #   $stderr.puts("Non-resolve node as target of dotaccessor: #{node.value.class}")
                        end

                # Functions
                when ::RKelly::Nodes::FunctionDeclNode
                        #$stdout.puts("FunctionDecl: #{node.value}")
                        # 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))}"
                                if @vars[node.value].nil?
                                        @vars[node.value] = @funcs[node.value]
                                end
                                node.value = @funcs[node.value]
                        end
                when ::RKelly::Nodes::FunctionCallNode
                        # The value of a FunctionCallNode is some sort of accessor node or a ResolveNode
                        # so this is basically useless
                        #$stdout.puts("Function call: #{node.name} => #{@funcs[node.name]}")
                        #node.value = @funcs[node.value] if @funcs[node.value]

                # Transformers
                when ::RKelly::Nodes::NumberNode
                        node.value = transform_number(node.value)
                when ::RKelly::Nodes::StringNode
                        node.value = transform_string(node.value)
                else
                        #$stderr.puts "#{node.class}: #{node.value}"
                        #$stderr.puts "#{node.class}"
                end

                #unless node.kind_of? ::RKelly::Nodes::SourceElementsNode
                #    $stderr.puts "#{node.class}: #{node.value}"
                #end
        end

        nil
end
parse() click to toggle source

Generate an Abstract Syntax Tree (ast) for later obfuscation

# File lib/rex/exploitation/jsobfu.rb, line 220
def parse
        parser = RKelly::Parser.new
        @ast = parser.parse(@code)
end
rand_base(num) click to toggle source

Convert a number to a random base (decimal, octal, or hexedecimal).

Given 10 as input, the possible return values are:

"10"
"0xa"
"012"
# File lib/rex/exploitation/jsobfu.rb, line 233
def rand_base(num)
        case rand(3)
        when 0; num.to_s
        when 1; "0%o" % num
        when 2; "0x%x" % num
        end
end
safe_split(str, quote) click to toggle source

Split a javascript string, str, without breaking escape sequences.

The maximum length of each piece of the string is half the total length of the string, ensuring we (almost) always split into at least two pieces. This won't always be true when given a string like "AAx41", where escape sequences artificially increase the total length (escape sequences are considered a single character).

Returns an array of two-element arrays. The zeroeth element is a randomly generated variable name, the first is a piece of the string contained in +quote+s.

See escape_length

# File lib/rex/exploitation/jsobfu.rb, line 318
def safe_split(str, quote)
        parts = []
        max_len = str.length / 2
        while str.length > 0
                len = 0
                loop do
                        e_len = escape_length(str[len..-1])
                        e_len = 1 if e_len.nil?
                        len += e_len
                        # if we've reached the end of the string, bail
                        break unless str[len]
                        break if len > max_len
                        # randomize the length of each part
                        break if (rand(4) == 0)
                end

                part = str.slice!(0, len)

                var = Rex::Text.rand_text_alpha(4)
                parts.push( [ var, "#{quote}#{part}#{quote}" ] )
        end

        parts
end
transform_number(num) click to toggle source

Return a mathematical expression that will evaluate to the given number num.

num can be a float or an int, but should never be negative.

# File lib/rex/exploitation/jsobfu.rb, line 247
def transform_number(num)
        case num
        when Fixnum
                if num == 0
                        r = rand(10) + 1
                        transformed = "('#{Rex::Text.rand_text_alpha(r)}'.length - #{r})"
                elsif num > 0 and num < 10
                        # use a random string.length for small numbers
                        transformed = "'#{Rex::Text.rand_text_alpha(num)}'.length"
                else
                        transformed = "("
                        divisor = rand(num) + 1
                        a = num / divisor.to_i
                        b = num - (a * divisor)
                        # recurse half the time for a
                        a = (rand(2) == 0) ? transform_number(a) : rand_base(a)
                        # recurse half the time for divisor
                        divisor = (rand(2) == 0) ? transform_number(divisor) : rand_base(divisor)
                        transformed << "#{a}*#{divisor}"
                        transformed << "+#{b}"
                        transformed << ")"
                end
        when Float
                transformed = "(#{num - num.floor} + #{rand_base(num.floor)})"
        end

        #puts("#{num} == #{transformed}")

        transformed
end
transform_string(str) click to toggle source

Convert a javascript string into something that will generate that string.

Randomly calls one of the transform_string_* methods

# File lib/rex/exploitation/jsobfu.rb, line 283
def transform_string(str)
        quote = str[0,1]
        # pull off the quotes
        str = str[1,str.length - 2]
        return quote*2 if str.length == 0

        case rand(2)
        when 0
                transformed = transform_string_split_concat(str, quote)
        when 1
                transformed = transform_string_fromCharCode(str)
        #when 2
        #     # Currently no-op
        #     transformed = transform_string_unescape(str)
        end

        #$stderr.puts "Obfuscating str: #{str.ljust 30} #{transformed}"
        transformed
end
transform_string_fromCharCode(str) click to toggle source

Return a call to String.fromCharCode() with each char of the input as arguments

Example:

input : "A\n"
output: String.fromCharCode(0x41, 10)
# File lib/rex/exploitation/jsobfu.rb, line 400
def transform_string_fromCharCode(str)
        buf = "String.fromCharCode("
        bytes = str.unpack("C*")
        len = 0
        while str.length > 0
                if str[0,1] == "\\"
                        str.slice!(0,1)
                        # then this is an escape sequence and we need to deal with all
                        # the special cases
                        case str[0,1]
                        # For chars that contain their non-escaped selves, step past
                        # the backslash and let the rand_base() below decide how to
                        # represent the character.
                        when '"', "'", "\\", " "
                                char = str.slice!(0,1).unpack("C").first
                        # For symbolic escapes, use the known value
                        when "n"; char = 0x0a; str.slice!(0,1)
                        when "t"; char = 0x09; str.slice!(0,1)
                        # Lastly, if it's a hex, unicode, or octal escape, pull out the
                        # real value and use that
                        when "x"
                                # Strip the x
                                str.slice!(0,1)
                                char = str.slice!(0,2).to_i 16
                        when "u"
                                # This can potentially lose information in the case of
                                # characters like \u0041, but since regular ascii is stored
                                # as unicode internally, String.fromCharCode(0x41) will be
                                # represented as 00 41 in memory anyway, so it shouldn't
                                # matter.
                                str.slice!(0,1)
                                char = str.slice!(0,4).to_i 16
                        when /[0-7]/
                                # Octals are a bit harder since they are variable width and
                                # don't necessarily mean what you might think. For example,
                                # "\61" == "1" and "\610" == "10".  610 is a valid octal
                                # number, but not a valid ascii character.  Javascript will
                                # interpreter as much as it can as a char and use the rest
                                # as a literal.  Boo.
                                str =~ /([0-7]{1,3})/
                                char = $1.to_i 8
                                if char > 255
                                        str =~ /([0-7]{1,2})/
                                        char = $1.to_i 8
                                end
                                str.slice!(0,$1.length)
                        end
                else
                        char = str.slice!(0,1).unpack("C").first
                end
                buf << "#{rand_base(char)},"
        end
        # Strip off the last comma
        buf = buf[0,buf.length-1] + ")"
        transformed = buf

        transformed
end
transform_string_split_concat(str, quote) click to toggle source

Split a javascript string, str, into multiple randomly-ordered parts and return an anonymous javascript function that joins them in the correct order. This method can be called safely on strings containing escape sequences. See safe_split.

# File lib/rex/exploitation/jsobfu.rb, line 372
def transform_string_split_concat(str, quote)
        parts = safe_split(str, quote)
        func = "(function () { var "
        ret = "; return "
        parts.sort { |a,b| rand }.each do |part|
                func << "#{part[0]}=#{part[1]},"
        end
        func.chop!

        ret  << parts.map{|part| part[0]}.join("+")
        final = func + ret + " })()"

        final
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.