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



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

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



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

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



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

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



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

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.



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

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



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

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.



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

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
65
# 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

  # maintain a single top-level scope
  scope.pop!(retain: scope.depth == 0)

  ret
end

#visit_StringNode(o) ⇒ Object



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

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

  super
end

#visit_TryNode(o) ⇒ Object



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

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.



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

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

  super
end