Class: JSObfu::Obfuscator

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

Constant Summary collapse

DEFAULT_GLOBAL =

unresolved lookups are rewritten as property lookups on the global object

'window'

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

#initialize(opts = {}) ⇒ Obfuscator

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`.



21
22
23
24
25
26
# File 'lib/jsobfu/obfuscator.rb', line 21

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

Instance Attribute Details

#globalString (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

#renamesHash (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

#scopeJSObfu::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

#visit_DotAccessorNode(o) ⇒ Object

Called on a dot lookup, like X.Y



107
108
109
110
# File 'lib/jsobfu/obfuscator.rb', line 107

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

#visit_FunctionDeclNode(o) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/jsobfu/obfuscator.rb', line 56

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

#visit_FunctionExprNode(o) ⇒ Object



68
69
70
71
72
73
74
# File 'lib/jsobfu/obfuscator.rb', line 68

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

  super
end

#visit_NumberNode(o) ⇒ Object



130
131
132
133
# File 'lib/jsobfu/obfuscator.rb', line 130

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

#visit_ParameterNode(o) ⇒ Object

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



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

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

  super
end

#visit_PropertyNode(o) ⇒ Object

A property node in an object “{}”



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

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

#visit_ResolveNode(o) ⇒ Object

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.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/jsobfu/obfuscator.rb', line 88

def visit_ResolveNode(o)
  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

#visit_SourceElementsNode(o) ⇒ Object

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.



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

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

#visit_StringNode(o) ⇒ Object



135
136
137
138
# File 'lib/jsobfu/obfuscator.rb', line 135

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

#visit_TryNode(o) ⇒ Object



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

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

#visit_VarDeclNode(o) ⇒ Object

Called whenever a variable is declared.



77
78
79
80
81
# File 'lib/jsobfu/obfuscator.rb', line 77

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

  super
end