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
34
# 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)
  @preserved_identifiers = opts.fetch(:preserved_identifiers, [])
  @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



125
126
127
128
129
130
131
132
# File 'lib/jsobfu/obfuscator.rb', line 125

def visit_DotAccessorNode(o)
  if @memory_sensitive || @preserved_identifiers.include?(o.accessor)
    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



66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/jsobfu/obfuscator.rb', line 66

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

  super
end

#visit_FunctionExprNode(o) ⇒ Object



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

def visit_FunctionExprNode(o)
  if o.value != 'function' && !@preserved_identifiers.include?(o.value)
    o.value = JSObfu::Utils::random_var_encoding(rename_var(o.value))
  end

  super
end

#visit_NumberNode(o) ⇒ Object



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

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.



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

def visit_ParameterNode(o)
  unless @preserved_identifiers.include?(o.value)
    o.value = JSObfu::Utils::random_var_encoding(rename_var(o.value))
  end

  super
end

#visit_PropertyNode(o) ⇒ Object

A property node in an object “{}”



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

def visit_PropertyNode(o)
  # if it is a non-alphanumeric property, obfuscate the string's bytes
  unless @memory_sensitive || @preserved_identifiers.include?(o.name)
    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.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/jsobfu/obfuscator.rb', line 102

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 || @preserved_identifiers.include?(o.value.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.



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

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|
    unless @preserved_identifiers.include?(key)
      rename_var(key)
    end
  end

  ret = super

  scope.pop!

  ret
end

#visit_StringNode(o) ⇒ Object



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

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

  super
end

#visit_TryNode(o) ⇒ Object



172
173
174
175
176
177
178
179
# File 'lib/jsobfu/obfuscator.rb', line 172

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

#visit_VarDeclNode(o) ⇒ Object

Called whenever a variable is declared.



89
90
91
92
93
94
95
# File 'lib/jsobfu/obfuscator.rb', line 89

def visit_VarDeclNode(o)
  unless @preserved_identifiers.include?(o.name)
    o.name = JSObfu::Utils::random_var_encoding(rename_var(o.name))
  end

  super
end