@@ -48,71 +48,14 @@ module Exploitation
48
48
#
49
49
class JSObfu
50
50
51
- # A single Javascript scope, used as a key-value store
52
- # to maintain uniqueness of members in generated closures.
53
- # For speed this class is implemented as a subclass of Hash.
54
- class Scope < Hash
55
-
56
- # these keywords should never be used as a random var name
57
- # source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Reserved_Words
58
- RESERVED_KEYWORDS = %w(
59
- break case catch continue debugger default delete do else finally
60
- for function if in instanceof new return switch this throw try
61
- typeof var void while with class enum export extends import super
62
- implements interface let package private protected public static yield
63
- )
64
-
65
- # these vars should not be shadowed as they may be used in other obfuscated code
66
- BUILTIN_VARS = %w(
67
- String window unescape
68
- )
69
-
70
- # @param [Rex::Exploitation::JSObfu::Scope] parent an optional parent scope,
71
- # sometimes necessary to prevent needless var shadowing
72
- def initialize ( parent = nil )
73
- @parent = parent
74
- @rand_gen = Rex ::RandomIdentifierGenerator . new (
75
- :max_length => 15 ,
76
- :first_char_set => Rex ::Text ::Alpha +"_$" ,
77
- :char_set => Rex ::Text ::AlphaNumeric +"_$" ,
78
- :min_length => 1
79
- )
80
- end
81
-
82
- # @return [String] a unique random var name that is not a reserved keyword
83
- def random_var_name
84
- len = 1
85
- loop do
86
- text = random_string ( len )
87
- unless has_key? ( text ) or
88
- RESERVED_KEYWORDS . include? ( text ) or
89
- BUILTIN_VARS . include? ( text )
90
-
91
- self [ text ] = nil
92
- return text
93
- end
94
- len += 1
95
- end
96
- end
97
-
98
- # @return [Boolean] var is in scope
99
- def has_key? ( key )
100
- super or ( @parent and @parent . has_key? ( key ) )
101
- end
102
-
103
- # @return [String] a random string that can be used as a var
104
- def random_string ( len )
105
- @rand_gen . generate ( len )
106
- end
107
-
108
- end
109
-
110
- #
111
- # The maximum length of a string that can be passed through
112
- # #transform_string without being chopped up into separate
113
- # expressions and concatenated
114
- #
115
- MAX_STRING_CHUNK = 10000
51
+ # these keywords should never be used as a random var name
52
+ # source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Reserved_Words
53
+ RESERVED_KEYWORDS = %w(
54
+ break case catch continue debugger default delete do else finally
55
+ for function if in instanceof new return switch this throw try
56
+ typeof var void while with class enum export extends import super
57
+ implements interface let package private protected public static yield
58
+ )
116
59
117
60
#
118
61
# Abstract Syntax Tree generated by RKelly::Parser#parse
@@ -126,8 +69,12 @@ def initialize(code)
126
69
@code = code
127
70
@funcs = { }
128
71
@vars = { }
129
- @scope = Scope . new
130
72
@debug = false
73
+ @rand_gen = Rex ::RandomIdentifierGenerator . new (
74
+ :max_length => 15 ,
75
+ :first_char_set => Rex ::Text ::Alpha +"_$" ,
76
+ :char_set => Rex ::Text ::AlphaNumeric +"_$"
77
+ )
131
78
end
132
79
133
80
#
@@ -175,8 +122,23 @@ def obfuscate
175
122
obfuscate_r ( @ast )
176
123
end
177
124
125
+ # @return [String] a unique random var name that is not a reserved keyword
126
+ def random_var_name
127
+ loop do
128
+ text = random_string
129
+ unless @vars . has_value? ( text ) or RESERVED_KEYWORDS . include? ( text )
130
+ return text
131
+ end
132
+ end
133
+ end
134
+
178
135
protected
179
136
137
+ # @return [String] a random string
138
+ def random_string
139
+ @rand_gen . generate
140
+ end
141
+
180
142
#
181
143
# Recursive method to obfuscate the given +ast+.
182
144
#
@@ -220,12 +182,12 @@ def obfuscate_r(ast)
220
182
# Variables
221
183
when ::RKelly ::Nodes ::VarDeclNode
222
184
if @vars [ node . name ] . nil?
223
- @vars [ node . name ] = @scope . random_var_name
185
+ @vars [ node . name ] = random_var_name
224
186
end
225
187
node . name = @vars [ node . name ]
226
188
when ::RKelly ::Nodes ::ParameterNode
227
189
if @vars [ node . value ] . nil?
228
- @vars [ node . value ] = @scope . random_var_name
190
+ @vars [ node . value ] = random_var_name
229
191
end
230
192
node . value = @vars [ node . value ]
231
193
when ::RKelly ::Nodes ::ResolveNode
@@ -247,7 +209,7 @@ def obfuscate_r(ast)
247
209
# Functions can also act as objects, so store them in the vars
248
210
# and the functions list so we can replace them in both places
249
211
if @funcs [ node . value ] . nil? and not @funcs . values . include? ( node . value )
250
- @funcs [ node . value ] = @scope . random_var_name
212
+ @funcs [ node . value ] = random_var_name
251
213
if @vars [ node . value ] . nil?
252
214
@vars [ node . value ] = @funcs [ node . value ]
253
215
end
@@ -349,15 +311,14 @@ def transform_string(str)
349
311
str = str [ 1 , str . length - 2 ]
350
312
return quote *2 if str . length == 0
351
313
352
- if str . length > MAX_STRING_CHUNK
353
- return safe_split ( str , quote ) . map { |args | transform_string ( args [ 1 ] ) } . join ( '+' )
354
- end
355
-
356
- transformed = case rand ( 2 )
314
+ case rand ( 2 )
357
315
when 0
358
- transform_string_split_concat ( str , quote )
316
+ transformed = transform_string_split_concat ( str , quote )
359
317
when 1
360
- transform_string_fromCharCode ( str )
318
+ transformed = transform_string_fromCharCode ( str )
319
+ #when 2
320
+ # # Currently no-op
321
+ # transformed = transform_string_unescape(str)
361
322
end
362
323
363
324
#$stderr.puts "Obfuscating str: #{str.ljust 30} #{transformed}"
@@ -392,12 +353,12 @@ def safe_split(str, quote)
392
353
break unless str [ len ]
393
354
break if len > max_len
394
355
# randomize the length of each part
395
- break if ( rand ( max_len ) == 0 )
356
+ break if ( rand ( 4 ) == 0 )
396
357
end
397
358
398
359
part = str . slice! ( 0 , len )
399
360
400
- var = @scope . random_var_name
361
+ var = Rex :: Text . rand_text_alpha ( 4 )
401
362
parts . push ( [ var , "#{ quote } #{ part } #{ quote } " ] )
402
363
end
403
364
@@ -448,6 +409,12 @@ def transform_string_split_concat(str, quote)
448
409
final
449
410
end
450
411
412
+
413
+ # TODO
414
+ #def transform_string_unescape(str)
415
+ # str
416
+ #end
417
+
451
418
#
452
419
# Return a call to String.fromCharCode() with each char of the input as arguments
453
420
#
@@ -456,17 +423,9 @@ def transform_string_split_concat(str, quote)
456
423
# output: String.fromCharCode(0x41, 10)
457
424
#
458
425
def transform_string_fromCharCode ( str )
459
- "String.fromCharCode(#{ string_to_bytes ( str ) } )"
460
- end
461
-
462
- #
463
- # Return a comma-separated list of byte values with random encodings (decimal/hex/octal)
464
- #
465
- def string_to_bytes ( str )
466
- len = 0
426
+ buf = "String.fromCharCode("
467
427
bytes = str . unpack ( "C*" )
468
- encoded_bytes = [ ]
469
-
428
+ len = 0
470
429
while str . length > 0
471
430
if str [ 0 , 1 ] == "\\ "
472
431
str . slice! ( 0 , 1 )
@@ -513,12 +472,16 @@ def string_to_bytes(str)
513
472
else
514
473
char = str . slice! ( 0 , 1 ) . unpack ( "C" ) . first
515
474
end
516
- encoded_bytes << rand_base ( char )
475
+ buf << " #{ rand_base ( char ) } ,"
517
476
end
477
+ # Strip off the last comma
478
+ buf = buf [ 0 , buf . length -1 ] + ")"
479
+ transformed = buf
518
480
519
- encoded_bytes . join ( ',' )
481
+ transformed
520
482
end
521
483
484
+
522
485
end
523
486
end
524
487
end
0 commit comments