jsobfu 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZjQ5NmUzNTU1Mzk3ODdiZDNlMTkyNTQ5YWNmY2Y2MjJlNDVjNGZkMg==
5
+ data.tar.gz: !binary |-
6
+ MGJhOTA3NDJiMDg4ODY4MDE3NDBkYWRiMGFjNjhhY2FiZjdiYzczZA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NDA3NmNiYTI1MjM0ZjFhNWMyYTJjMWFkNTU2YTdhZjg4ODYxNWJlMmQwNjZh
10
+ MWRjMTUzZmUyNDcwMmJmNjYxZDliN2ViZTYyYjA2MWVmZmFkNzIzZGUwZDhj
11
+ ZDFmMDUwZmY2Y2JiMmNjNjU0MzA2ZjRiZTAyOGIwMzYxZjM5YWE=
12
+ data.tar.gz: !binary |-
13
+ ODI4ZTgyOTFkZDBlZTdhZDFkN2M0ODU2NWJkYmMwYjYwZTFiNTc2Yjg1OGEx
14
+ YzdhZTRmNjcwYTNhNzlkNTgxYzU0YjJjODVkNmIwYWE4ZWYwZTk3MTY4NzQ4
15
+ NDM4ODEzODQ4NTI0ODA3NTJhMWQwZjY1NDJlNzA3ZGFlOGQzN2I=
data/lib/jsobfu.rb ADDED
@@ -0,0 +1,73 @@
1
+ require 'rkelly'
2
+
3
+ # The primary class, used to parse and obfuscate Javascript code.
4
+ class JSObfu
5
+
6
+ require_relative 'jsobfu/scope'
7
+ require_relative 'jsobfu/utils'
8
+ require_relative 'jsobfu/ecma_tight'
9
+ require_relative 'jsobfu/hoister'
10
+ require_relative 'jsobfu/obfuscator'
11
+
12
+ # @return [JSObfu::Scope] the global scope
13
+ attr_reader :scope
14
+
15
+ # Saves +code+ for later obfuscation with #obfuscate
16
+ def initialize(code)
17
+ @code = code
18
+ @scope = Scope.new
19
+ end
20
+
21
+ # Add +str+ to the un-obfuscated code.
22
+ # Calling this method after #obfuscate is undefined
23
+ def <<(str)
24
+ @code << str
25
+ end
26
+
27
+ # @return [String] the (possibly obfuscated) code
28
+ def to_s
29
+ @code
30
+ end
31
+
32
+ # @return [RKelly::Nodes::SourceElementsNode] the abstract syntax tree
33
+ def ast
34
+ @ast || parse
35
+ end
36
+
37
+ # Parse and obfuscate
38
+ #
39
+ # @param opts [Hash] the options hash
40
+ # @option opts [Boolean] :strip_whitespace allow whitespace in the output code
41
+ #
42
+ # @return [String] if successful
43
+ def obfuscate(opts={})
44
+ @obfuscator = JSObfu::Obfuscator.new(scope: @scope)
45
+ @code = @obfuscator.accept(ast).to_s
46
+ if opts.fetch(:strip_whitespace, true)
47
+ @code.gsub!(/(^\s+|\s+$)/, '')
48
+ @code.delete!("\n")
49
+ @code.delete!("\r")
50
+ end
51
+ self
52
+ end
53
+
54
+ # Returns the obfuscated name for the variable or function +sym+
55
+ #
56
+ # @param [String] sym the name of the variable or function
57
+ # @return [String] the obfuscated name
58
+ def sym(sym)
59
+ raise RuntimeError, "Must obfuscate before calling #sym" if @obfuscator.nil?
60
+ @obfuscator.renames[sym.to_s]
61
+ end
62
+
63
+ protected
64
+
65
+ #
66
+ # Generate an Abstract Syntax Tree (#ast) for later obfuscation
67
+ #
68
+ def parse
69
+ parser = RKelly::Parser.new
70
+ @ast = parser.parse(@code)
71
+ end
72
+
73
+ end
@@ -0,0 +1,316 @@
1
+ #
2
+ # Implements an ECMAVisitor with minimal whitespace and indentation.
3
+ #
4
+ # Mostly a line-by-line copy of RKelly::Visitors::ECMAVisitor with the
5
+ # whitespace trimmed out.
6
+ #
7
+ class JSObfu::ECMANoWhitespaceVisitor < RKelly::Visitors::ECMAVisitor
8
+
9
+ def visit_ParentheticalNode(o)
10
+ "(#{o.value.accept(self)})"
11
+ end
12
+
13
+ def visit_SourceElementsNode(o)
14
+ o.value.map { |x| "#{x.accept(self)}" }.join
15
+ end
16
+
17
+ def visit_VarStatementNode(o)
18
+ "var #{o.value.map { |x| x.accept(self) }.join(',')};"
19
+ end
20
+
21
+ def visit_ConstStatementNode(o)
22
+ "const #{o.value.map { |x| x.accept(self) }.join(',')};"
23
+ end
24
+
25
+ def visit_VarDeclNode(o)
26
+ "#{o.name}#{o.value ? o.value.accept(self) : nil}"
27
+ end
28
+
29
+ def visit_AssignExprNode(o)
30
+ "=#{o.value.accept(self)}"
31
+ end
32
+
33
+ def visit_NumberNode(o)
34
+ o.value.to_s
35
+ end
36
+
37
+ def visit_ForNode(o)
38
+ init = o.init ? o.init.accept(self) : ';'
39
+ init << ';' unless init.end_with? ';' # make sure it has a ;
40
+ test = o.test ? o.test.accept(self) : ''
41
+ counter = o.counter ? o.counter.accept(self) : ''
42
+ "for(#{init}#{test};#{counter})#{o.value.accept(self)}"
43
+ end
44
+
45
+ def visit_LessNode(o)
46
+ "#{o.left.accept(self)}<#{o.value.accept(self)}"
47
+ end
48
+
49
+ def visit_ResolveNode(o)
50
+ o.value
51
+ end
52
+
53
+ def visit_PostfixNode(o)
54
+ "#{o.operand.accept(self)}#{o.value}"
55
+ end
56
+
57
+ def visit_PrefixNode(o)
58
+ "#{o.value}#{o.operand.accept(self)}"
59
+ end
60
+
61
+ def visit_BlockNode(o)
62
+ "{#{o.value.accept(self)}}"
63
+ end
64
+
65
+ def visit_ExpressionStatementNode(o)
66
+ "#{o.value.accept(self)};"
67
+ end
68
+
69
+ def visit_OpEqualNode(o)
70
+ "#{o.left.accept(self)}=#{o.value.accept(self)}"
71
+ end
72
+
73
+ def visit_FunctionCallNode(o)
74
+ "#{o.value.accept(self)}(#{o.arguments.accept(self)})"
75
+ end
76
+
77
+ def visit_ArgumentsNode(o)
78
+ o.value.map { |x| x.accept(self) }.join(',')
79
+ end
80
+
81
+ def visit_StringNode(o)
82
+ o.value
83
+ end
84
+
85
+ def visit_NullNode(o)
86
+ "null"
87
+ end
88
+
89
+ def visit_FunctionDeclNode(o)
90
+ "function #{o.value}" + function_params_and_body(o)
91
+ end
92
+
93
+ def visit_ParameterNode(o)
94
+ o.value
95
+ end
96
+
97
+ def visit_FunctionBodyNode(o)
98
+ "{#{o.value.accept(self)}}"
99
+ end
100
+
101
+ def visit_BreakNode(o)
102
+ "break" + (o.value ? " #{o.value}" : '') + ';'
103
+ end
104
+
105
+ def visit_ContinueNode(o)
106
+ "continue" + (o.value ? " #{o.value}" : '') + ';'
107
+ end
108
+
109
+ def visit_TrueNode(o)
110
+ "true"
111
+ end
112
+
113
+ def visit_FalseNode(o)
114
+ "false"
115
+ end
116
+
117
+ def visit_EmptyStatementNode(o)
118
+ ';'
119
+ end
120
+
121
+ def visit_RegexpNode(o)
122
+ o.value
123
+ end
124
+
125
+ def visit_DotAccessorNode(o)
126
+ "#{o.value.accept(self)}.#{o.accessor}"
127
+ end
128
+
129
+ def visit_ThisNode(o)
130
+ "this"
131
+ end
132
+
133
+ def visit_BitwiseNotNode(o)
134
+ "~#{o.value.accept(self)}"
135
+ end
136
+
137
+ def visit_DeleteNode(o)
138
+ "delete #{o.value.accept(self)}"
139
+ end
140
+
141
+ def visit_ArrayNode(o)
142
+ "[#{o.value.map { |x| x ? x.accept(self) : '' }.join(',')}]"
143
+ end
144
+
145
+ def visit_ElementNode(o)
146
+ o.value.accept(self)
147
+ end
148
+
149
+ def visit_LogicalNotNode(o)
150
+ "!#{o.value.accept(self)}"
151
+ end
152
+
153
+ def visit_UnaryMinusNode(o)
154
+ "-#{o.value.accept(self)}"
155
+ end
156
+
157
+ def visit_UnaryPlusNode(o)
158
+ "+#{o.value.accept(self)}"
159
+ end
160
+
161
+ def visit_ReturnNode(o)
162
+ "return" + (o.value ? " #{o.value.accept(self)}" : '') + ';'
163
+ end
164
+
165
+ def visit_ThrowNode(o)
166
+ "throw #{o.value.accept(self)};"
167
+ end
168
+
169
+ def visit_TypeOfNode(o)
170
+ "typeof #{o.value.accept(self)}"
171
+ end
172
+
173
+ def visit_VoidNode(o)
174
+ "void(#{o.value.accept(self)})"
175
+ end
176
+
177
+ [
178
+ [:Add, '+'],
179
+ [:BitAnd, '&'],
180
+ [:BitOr, '|'],
181
+ [:BitXOr, '^'],
182
+ [:Divide, '/'],
183
+ [:Equal, '=='],
184
+ [:Greater, '>'],
185
+ [:GreaterOrEqual, '>='],
186
+ [:In, 'in'],
187
+ [:InstanceOf, 'instanceof'],
188
+ [:LeftShift, '<<'],
189
+ [:LessOrEqual, '<='],
190
+ [:LogicalAnd, '&&'],
191
+ [:LogicalOr, '||'],
192
+ [:Modulus, '%'],
193
+ [:Multiply, '*'],
194
+ [:NotEqual, '!='],
195
+ [:NotStrictEqual, '!=='],
196
+ [:OpAndEqual, '&='],
197
+ [:OpDivideEqual, '/='],
198
+ [:OpLShiftEqual, '<<='],
199
+ [:OpMinusEqual, '-='],
200
+ [:OpModEqual, '%='],
201
+ [:OpMultiplyEqual, '*='],
202
+ [:OpOrEqual, '|='],
203
+ [:OpPlusEqual, '+='],
204
+ [:OpRShiftEqual, '>>='],
205
+ [:OpURShiftEqual, '>>>='],
206
+ [:OpXOrEqual, '^='],
207
+ [:RightShift, '>>'],
208
+ [:StrictEqual, '==='],
209
+ [:Subtract, '-'],
210
+ [:UnsignedRightShift, '>>>'],
211
+ ].each do |name,op|
212
+ define_method(:"visit_#{name}Node") do |o|
213
+ if op =~ /^[a-z]+$/
214
+ "#{o.left.accept(self)} #{op} #{o.value.accept(self)}"
215
+ else
216
+ "#{o.left.accept(self)}#{op}#{o.value.accept(self)}"
217
+ end
218
+ end
219
+ end
220
+
221
+ def visit_WhileNode(o)
222
+ "while(#{o.left.accept(self)})#{o.value.accept(self)}"
223
+ end
224
+
225
+ def visit_SwitchNode(o)
226
+ "switch(#{o.left.accept(self)})#{o.value.accept(self)}"
227
+ end
228
+
229
+ def visit_CaseBlockNode(o)
230
+ "{" + (o.value ? o.value.map { |x| x.accept(self) }.join('') : '') + "}"
231
+ end
232
+
233
+ def visit_CaseClauseNode(o)
234
+ if o.left
235
+ case_code = "case #{o.left.accept(self)}:"
236
+ else
237
+ case_code = "default:"
238
+ end
239
+ case_code += "#{o.value.accept(self)}"
240
+ case_code
241
+ end
242
+
243
+ def visit_DoWhileNode(o)
244
+ "do#{o.left.accept(self)}while(#{o.value.accept(self)});"
245
+ end
246
+
247
+ def visit_WithNode(o)
248
+ "with(#{o.left.accept(self)})#{o.value.accept(self)}"
249
+ end
250
+
251
+ def visit_LabelNode(o)
252
+ "#{o.name}:#{o.value.accept(self)}"
253
+ end
254
+
255
+ def visit_ObjectLiteralNode(o)
256
+ "{" + o.value.map { |x| x.accept(self) }.join(",\n") + '}'
257
+ end
258
+
259
+ def visit_PropertyNode(o)
260
+ "#{o.name}:#{o.value.accept(self)}"
261
+ end
262
+
263
+ def visit_GetterPropertyNode(o)
264
+ "get #{o.name}" + function_params_and_body(o.value)
265
+ end
266
+
267
+ def visit_SetterPropertyNode(o)
268
+ "set #{o.name}" + function_params_and_body(o.value)
269
+ end
270
+
271
+ def visit_FunctionExprNode(o)
272
+ name = (o.value == 'function') ? '' : ' '+o.value
273
+ "function" + name + function_params_and_body(o)
274
+ end
275
+
276
+ # Helper for all the various function nodes
277
+ def function_params_and_body(o)
278
+ "(#{o.arguments.map { |x| x.accept(self) }.join(',')})" +
279
+ "#{o.function_body.accept(self)}"
280
+ end
281
+
282
+ def visit_CommaNode(o)
283
+ "#{o.left.accept(self)},#{o.value.accept(self)}"
284
+ end
285
+
286
+ def visit_IfNode(o)
287
+ "if(#{o.conditions.accept(self)})#{o.value.accept(self)}" +
288
+ (o.else ? "else #{o.else.accept(self)}" : '')
289
+ end
290
+
291
+ def visit_ConditionalNode(o)
292
+ "#{o.conditions.accept(self)}?#{o.value.accept(self)}:" +
293
+ "#{o.else.accept(self)}"
294
+ end
295
+
296
+ def visit_ForInNode(o)
297
+ var = o.left.is_a?(RKelly::Nodes::VarDeclNode) ? 'var ' : ''
298
+ "for(#{var}#{o.left.accept(self)} in #{o.right.accept(self)})" +
299
+ "#{o.value.accept(self)}"
300
+ end
301
+
302
+ def visit_TryNode(o)
303
+ "try#{o.value.accept(self)}" +
304
+ (o.catch_block ? " catch(#{o.catch_var})#{o.catch_block.accept(self)}" : '') +
305
+ (o.finally_block ? " finally#{o.finally_block.accept(self)}" : '')
306
+ end
307
+
308
+ def visit_BracketAccessorNode(o)
309
+ "#{o.value.accept(self)}[#{o.accessor.accept(self)}]"
310
+ end
311
+
312
+ def visit_NewExprNode(o)
313
+ "new #{o.value.accept(self)}(#{o.arguments.accept(self)})"
314
+ end
315
+
316
+ end
@@ -0,0 +1,84 @@
1
+ #
2
+ # Walks a Javascript AST and finds the immediate members of the
3
+ # root scope, which is useful for "hoisting" var and function
4
+ # declaration to the top of the function.
5
+ #
6
+ class JSObfu::Hoister < RKelly::Visitors::Visitor
7
+
8
+ # @return [Hash] the scope maintained while walking the ast
9
+ attr_reader :scope
10
+
11
+ # @return [Array<String>] the function names in the first level of this closure
12
+ attr_reader :functions
13
+
14
+ def initialize(opts={})
15
+ @parent_scope = opts.fetch(:parent_scope, nil)
16
+ @max_depth = 1
17
+ @depth = 0
18
+ @scope = {}
19
+ @resolves = []
20
+ @functions = []
21
+ super()
22
+ end
23
+
24
+ def visit_SourceElementsNode(o)
25
+ return if @max_depth and @depth >= @max_depth
26
+ @depth += 1
27
+ o.value.each { |x| x.accept(self) }
28
+ @depth -= 1
29
+ end
30
+
31
+ def visit_VarDeclNode(o)
32
+ scope[o.name] = o
33
+ end
34
+
35
+ def visit_FunctionDeclNode(o)
36
+ @functions << o.value
37
+ scope[o.value] = o
38
+ end
39
+
40
+ %w{
41
+ AddNode ArgumentsNode ArrayNode AssignExprNode BitAndNode BitOrNode
42
+ BitXOrNode BitwiseNotNode BlockNode BracketAccessorNode BreakNode
43
+ CaseBlockNode CaseClauseNode CommaNode ConditionalNode
44
+ ConstStatementNode ContinueNode DeleteNode DivideNode
45
+ DoWhileNode DotAccessorNode ElementNode EmptyStatementNode EqualNode
46
+ ExpressionStatementNode FalseNode ForInNode ForNode FunctionBodyNode
47
+ FunctionExprNode GetterPropertyNode GreaterNode GreaterOrEqualNode
48
+ IfNode InNode InstanceOfNode LabelNode LeftShiftNode LessNode
49
+ LessOrEqualNode LogicalAndNode LogicalNotNode LogicalOrNode ModulusNode
50
+ MultiplyNode NewExprNode NotEqualNode NotStrictEqualNode NullNode
51
+ NumberNode ObjectLiteralNode OpAndEqualNode OpDivideEqualNode
52
+ OpEqualNode OpLShiftEqualNode OpMinusEqualNode OpModEqualNode
53
+ OpMultiplyEqualNode OpOrEqualNode OpPlusEqualNode OpRShiftEqualNode
54
+ OpURShiftEqualNode OpXOrEqualNode ParameterNode PostfixNode PrefixNode
55
+ PropertyNode RegexpNode ResolveNode ReturnNode RightShiftNode
56
+ SetterPropertyNode StrictEqualNode StringNode
57
+ SubtractNode SwitchNode ThisNode ThrowNode TrueNode TryNode TypeOfNode
58
+ UnaryMinusNode UnaryPlusNode UnsignedRightShiftNode
59
+ VoidNode WhileNode WithNode
60
+ }.each do |type|
61
+ define_method(:"visit_#{type}") do |o|
62
+ end
63
+ end
64
+
65
+ # @return [String] Javascript that declares the discovered variables
66
+ def scope_declaration(opts={})
67
+ keys = scope.keys.dup
68
+ if opts.fetch(:shuffle, true)
69
+ keys = keys.shuffle
70
+ end
71
+
72
+ keys.delete_if { |k| @functions.include? k }
73
+
74
+ if @parent_scope
75
+ keys.delete_if { |k| @parent_scope.has_key? k }
76
+ keys.map! { |k| @parent_scope.renames[k.to_s] || k }
77
+ end
78
+
79
+ str = if keys.empty? then '' else "var #{keys.join(",")};" end
80
+ # puts str if str.length>0
81
+ str
82
+ end
83
+
84
+ end