Class: JSObfu::Scope

Inherits:
Hash
  • Object
show all
Defined in:
lib/jsobfu/scope.rb

Overview

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.

Constant Summary collapse

RESERVED_KEYWORDS =

these keywords should never be used as a random var name source: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Reserved_Words

%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
  const let
)
BUILTIN_VARS =

these vars should not be shadowed as they in the exploit code, and generating them would cause problems.

%w(
  String window unescape location chrome document navigator location
  frames ActiveXObject XMLHttpRequest Function eval Object Math CSS
  parent opener event frameElement Error TypeError setTimeout setInterval
  top arguments Array Date
)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Scope

Returns a new instance of Scope.

Parameters:

  • opts (Hash) (defaults to: {})

    the options hash

Options Hash (opts):

  • :parent (Rex::Exploitation::JSObfu::Scope)

    an optional parent scope, sometimes necessary to prevent needless var shadowing

  • :min_len (Integer)

    minimum length of the var names



41
42
43
44
45
46
47
48
# File 'lib/jsobfu/scope.rb', line 41

def initialize(opts={})
  @parent         = opts[:parent]
  @first_char_set = opts[:first_char_set] || [*'A'..'Z']+[*'a'..'z']+['_', '$']
  @char_set       = opts[:first_char_set] || @first_char_set + [*'0'..'9']
  @min_len        = opts[:min_len] || 1
  @renames        = {}
  @depth          = -1
end

Instance Attribute Details

#depthNumber

Returns the 0-indexed depth of the scope stack.

Returns:

  • (Number)

    the 0-indexed depth of the scope stack



35
36
37
# File 'lib/jsobfu/scope.rb', line 35

def depth
  @depth
end

#parentJSObfu::Scope

Returns parent that spawned this scope.

Returns:



29
30
31
# File 'lib/jsobfu/scope.rb', line 29

def parent
  @parent
end

#renamesHash

Returns mapping old var names to random ones.

Returns:

  • (Hash)

    mapping old var names to random ones



32
33
34
# File 'lib/jsobfu/scope.rb', line 32

def renames
  @renames
end

Instance Method Details

#empty?Boolean

Returns scope has members.

Returns:

  • (Boolean)

    scope has members



101
102
103
# File 'lib/jsobfu/scope.rb', line 101

def empty?
  self.keys.empty? and (parent.nil? or parent.empty?)
end

#has_key?(key) ⇒ Boolean

Check if we’ve used this var before. This will also check any attached parent scopes (and their parents, recursively)

Returns:

  • (Boolean)

    whether var is in scope



120
121
122
# File 'lib/jsobfu/scope.rb', line 120

def has_key?(key)
  super or (parent and parent.has_key?(key))
end

#pop!(opts = {}) ⇒ Object

“Consumes” the parent and replaces self with it



138
139
140
141
142
143
144
145
146
147
# File 'lib/jsobfu/scope.rb', line 138

def pop!(opts={})
  retain = opts.fetch(:retain, false)
  @depth -= 1
  clear unless retain
  if @parent
    merge! @parent
    @renames = @parent.renames
    @parent = @parent.parent
  end
end

#push!Object

replaces this Scope in the “parent” chain with a copy, empties current scope, and returns. Essentially an in-place push operation



127
128
129
130
131
132
133
134
135
# File 'lib/jsobfu/scope.rb', line 127

def push!
  @depth += 1
  replacement = dup
  replacement.parent = @parent
  replacement.renames = @renames
  @renames = {}
  @parent = replacement
  clear
end

#random_string(len) ⇒ String

Returns a random string that can be used as a var.

Returns:

  • (String)

    a random string that can be used as a var



150
151
152
# File 'lib/jsobfu/scope.rb', line 150

def random_string(len)
  @first_char_set.sample + (len-1).times.map { @char_set.sample }.join
end

#random_var_nameString

Generates a unique, “safe” random variable

Returns:

  • (String)

    a unique random var name that is not a reserved keyword



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/jsobfu/scope.rb', line 52

def random_var_name
  len = @min_len
  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

#rename_var(var_name, opts = {}) ⇒ String

Re-maps your var_name to a unique, random names in the current scope

Parameters:

  • var_name (String)

    the name you want to replace. This name will be remembered in the #renames hash

  • opts (Hash) (defaults to: {})

    the options hash

Options Hash (opts):

  • :generate (Boolean)

    if the variable was not explicitly renamed before, in this scope or any parent scope, generate a new random name

Returns:

  • (String)

    the randomly generated replacement name

  • nil if generate=false and var_name was not already replaced



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/jsobfu/scope.rb', line 79

def rename_var(var_name, opts={})
  return var_name if BUILTIN_VARS.include?(var_name)

  generate = opts.fetch(:generate, true)
  unresolved = opts.fetch(:unresolved, [])
  renamed = @renames[var_name]

  if renamed.nil? and parent
    renamed = parent.rename_var(var_name, :generate => false)
  end

  if renamed.nil? and generate
    @renames[var_name] = random_var_name
    renamed = @renames[var_name]
  end

  #puts "Mapped #{var_name} => #{renamed}" if renamed

  renamed
end

#topObject



110
111
112
113
114
# File 'lib/jsobfu/scope.rb', line 110

def top
  p = self
  p = p.parent until p.parent.nil?
  p
end

#top?Boolean

Returns scope has no parent.

Returns:

  • (Boolean)

    scope has no parent



106
107
108
# File 'lib/jsobfu/scope.rb', line 106

def top?
  parent.nil?
end