Class: JSObfu::Obfuscator

Inherits:
ECMANoWhitespaceVisitor show all
Defined in:
lib/jsobfu/obfuscator.rb

Constant Summary

DEFAULT_GLOBAL =

unresolved lookups are rewritten as property lookups on the global object

'window'
BUILTIN_METHODS =

some "global" functions are actually keywords, like void(5)

['void']

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Methods inherited from ECMANoWhitespaceVisitor

#function_params_and_body, #visit_ArgumentsNode, #visit_ArrayNode, #visit_AssignExprNode, #visit_BitwiseNotNode, #visit_BlockNode, #visit_BracketAccessorNode, #visit_BreakNode, #visit_CaseBlockNode, #visit_CaseClauseNode, #visit_CommaNode, #visit_ConditionalNode, #visit_ConstStatementNode, #visit_ContinueNode, #visit_DeleteNode, #visit_DoWhileNode, #visit_ElementNode, #visit_EmptyStatementNode, #visit_ExpressionStatementNode, #visit_FalseNode, #visit_ForInNode, #visit_ForNode, #visit_FunctionBodyNode, #visit_FunctionCallNode, #visit_GetterPropertyNode, #visit_IfNode, #visit_LabelNode, #visit_LessNode, #visit_LogicalNotNode, #visit_NewExprNode, #visit_NullNode, #visit_ObjectLiteralNode, #visit_OpEqualNode, #visit_ParentheticalNode, #visit_PostfixNode, #visit_PrefixNode, #visit_RegexpNode, #visit_ReturnNode, #visit_SetterPropertyNode, #visit_SwitchNode, #visit_ThisNode, #visit_ThrowNode, #visit_TrueNode, #visit_TypeOfNode, #visit_UnaryMinusNode, #visit_UnaryPlusNode, #visit_VarStatementNode, #visit_VoidNode, #visit_WhileNode, #visit_WithNode

Constructor Details

- (Obfuscator) initialize(opts = {})

Returns a new instance of Obfuscator

Parameters:

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

    the options hash

Options Hash (opts):

  • :scope (JSObfu::Scope)

    the optional scope to save vars to

  • :global (String)

    the global object to rewrite unresolved lookups to. Depending on the environment, it may be `window`, `global`, or `this`.



24
25
26
27
28
29
# File 'lib/jsobfu/obfuscator.rb', line 24

def initialize(opts={})
  @scope = opts.fetch(:scope, JSObfu::Scope.new)
  @global = opts.fetch(:global, DEFAULT_GLOBAL).to_s
  @renames = {}
  super()
end

Instance Attribute Details

- (String) global (readonly)

Returns the global object in this JS environment

Returns:

  • (String)

    the global object in this JS environment



12
13
14
# File 'lib/jsobfu/obfuscator.rb', line 12

def global
  @global
end

- (Hash) renames (readonly)

Returns of original var/fn names to our new random neames

Returns:

  • (Hash)

    of original var/fn names to our new random neames



9
10
11
# File 'lib/jsobfu/obfuscator.rb', line 9

def renames
  @renames
end

- (JSObfu::Scope) scope (readonly)

Returns the scope maintained while walking the ast

Returns:



6
7
8
# File 'lib/jsobfu/obfuscator.rb', line 6

def scope
  @scope
end

Instance Method Details

- (Boolean) is_builtin_method?(method) (protected)

Returns:

  • (Boolean)


161
162
163
# File 'lib/jsobfu/obfuscator.rb', line 161

def is_builtin_method?(method)
  BUILTIN_METHODS.include?(method)
end

- (Object) rename_var(var_name, opts = {}) (protected)

Assigns the var var_name a new obfuscated name



157
158
159
# File 'lib/jsobfu/obfuscator.rb', line 157

def rename_var(var_name, opts={})
  @renames[var_name] = scope.rename_var(var_name, opts)
end

- (Object) visit_DotAccessorNode(o)

Called on a dot lookup, like X.Y



114
115
116
117
# File 'lib/jsobfu/obfuscator.rb', line 114

def visit_DotAccessorNode(o)
  obf_str = JSObfu::Utils::transform_string(o.accessor, scope, :quotes => false)
  "#{o.value.accept(self)}[(#{obf_str})]"
end

- (Object) visit_FunctionDeclNode(o)



59
60
61
62
63
64
65
66
67
68
69
# File 'lib/jsobfu/obfuscator.rb', line 59

def visit_FunctionDeclNode(o)
  o.value = if o.value and o.value.length > 0
    JSObfu::Utils::random_var_encoding(scope.rename_var(o.value))
  else
    if rand(3) != 0
      JSObfu::Utils::random_var_encoding(scope.random_var_name)
    end
  end

  super
end

- (Object) visit_FunctionExprNode(o)



71
72
73
74
75
76
77
# File 'lib/jsobfu/obfuscator.rb', line 71

def visit_FunctionExprNode(o)
  if o.value != 'function'
    o.value = JSObfu::Utils::random_var_encoding(rename_var(o.value))
  end

  super
end

- (Object) visit_NumberNode(o)



137
138
139
140
# File 'lib/jsobfu/obfuscator.rb', line 137

def visit_NumberNode(o)
  o.value = JSObfu::Utils::transform_number(o.value)
  super
end

- (Object) visit_ParameterNode(o)

Called when a parameter is declared. "Shadowed" parameters in the original source are preserved - the randomized name is "shadowed" from the outer scope.



121
122
123
124
125
# File 'lib/jsobfu/obfuscator.rb', line 121

def visit_ParameterNode(o)
  o.value = JSObfu::Utils::random_var_encoding(rename_var(o.value))

  super
end

- (Object) visit_PropertyNode(o)

A property node in an object "{}"



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

def visit_PropertyNode(o)
  # if it is a non-alphanumeric property, obfuscate the string's bytes
  if o.name =~ /^[a-zA-Z_][a-zA-Z0-9_]*$/
     o.instance_variable_set :@name, '"'+JSObfu::Utils::random_string_encoding(o.name)+'"'
  end

  super
end

- (Object) visit_ResolveNode(o)

Called whenever a variable is referred to (not declared).

If the variable was never added to scope, it is assumed to be a global object (like "document"), and hence will not be obfuscated.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/jsobfu/obfuscator.rb', line 91

def visit_ResolveNode(o)
  if is_builtin_method?(o.value)
    return super
  end

  new_val = rename_var(o.value, :generate => false)

  if new_val
    o.value = JSObfu::Utils::random_var_encoding(new_val)
    super
  else
    if o.value.to_s == global.to_s
      # if the ref is the global object, don't obfuscate it on itself. This helps
      # "shimmed" globals (like `window=this` at the top of the script) work reliably.
      super
    else
      # A global is used, at least obfuscate the lookup
      "#{global}[#{JSObfu::Utils::transform_string(o.value, scope, :quotes => false)}]"
    end
  end
end

- (Object) visit_SourceElementsNode(o)

Maintains a stack of closures that we have visited. This method is called everytime we visit a nested function.

Javascript is functionally-scoped, so a function(){} creates its own unique closure. When resolving variables, Javascript looks "up" the closure stack, ending up as a property lookup in the global scope (available as `window` in all browsers)

This is changed in newer ES versions, where a `let` keyword has been introduced, which has regular C-style block scoping. We'll ignore this feature since it is not yet widely used.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/jsobfu/obfuscator.rb', line 42

def visit_SourceElementsNode(o)
  scope.push!

  hoister = JSObfu::Hoister.new(parent_scope: scope)
  o.value.each { |x| hoister.accept(x) }

  hoister.scope.keys.each do |key|
    rename_var(key)
  end

  ret = super

  scope.pop!

  ret
end

- (Object) visit_StringNode(o)



142
143
144
145
# File 'lib/jsobfu/obfuscator.rb', line 142

def visit_StringNode(o)
  o.value = JSObfu::Utils::transform_string(o.value, scope)
  super
end

- (Object) visit_TryNode(o)



147
148
149
150
151
152
# File 'lib/jsobfu/obfuscator.rb', line 147

def visit_TryNode(o)
  if o.catch_block
    o.instance_variable_set :@catch_var, rename_var(o.catch_var)
  end
  super
end

- (Object) visit_VarDeclNode(o)

Called whenever a variable is declared.



80
81
82
83
84
# File 'lib/jsobfu/obfuscator.rb', line 80

def visit_VarDeclNode(o)
  o.name = JSObfu::Utils::random_var_encoding(rename_var(o.name))

  super
end