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'
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

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

  • :memory_sensitive (Boolean)

    the execution environment is sensitive to changes in memory usage (e.g. a heap spray). This disables string transformations and other “noisy” obfuscation tactics. (false)



27
28
29
30
31
32
33
# File 'lib/jsobfu/obfuscator.rb', line 27

def initialize(opts={})
  @scope = opts.fetch(:scope) { JSObfu::Scope.new }
  @global = opts.fetch(:global, DEFAULT_GLOBAL).to_s
  @memory_sensitive = !!opts.fetch(:memory_sensitive, false)
  @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



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

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

#visit_FunctionDeclNode(o) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
# File 'lib/jsobfu/obfuscator.rb', line 63

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



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

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



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

def visit_NumberNode(o)
  unless @memory_sensitive
    o.value = JSObfu::Utils::transform_number(o.value)
  end

  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.



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

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 “{}”



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

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



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/jsobfu/obfuscator.rb', line 95

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 @memory_sensitive || 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.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/jsobfu/obfuscator.rb', line 46

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



155
156
157
158
159
160
161
# File 'lib/jsobfu/obfuscator.rb', line 155

def visit_StringNode(o)
  unless @memory_sensitive
    o.value = JSObfu::Utils::transform_string(o.value, scope)
  end

  super
end

#visit_TryNode(o) ⇒ Object



163
164
165
166
167
168
# File 'lib/jsobfu/obfuscator.rb', line 163

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.



84
85
86
87
88
# File 'lib/jsobfu/obfuscator.rb', line 84

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

  super
end