Module: JSObfu::Utils
- Defined in:
- lib/jsobfu/utils.rb
Overview
Some quick utility functions to minimize dependencies
Constant Summary
- MAX_STRING_CHUNK =
The maximum length of a string that can be passed through #transform_string without being chopped up into separate expressions and concatenated
10000
- ALPHA_CHARSET =
([*'A'..'Z']+[*'a'..'z']).freeze
- ALPHANUMERIC_CHARSET =
(ALPHA_CHARSET+[*'0'..'9']).freeze
- JS_ESCAPE_MAP =
For escaping special chars in a Javascript quoted string
{ '\\' => '\\\\', "\r\n" => '\n', "\n" => '\n', "\r" => '\n', '"' => '\\"', "'" => "\\'" }
Class Method Summary (collapse)
-
+ (String) escape_javascript(javascript)
Taken from Rails ActionView: api.rubyonrails.org/classes/ActionView/Helpers/JavaScriptHelper.html.
-
+ (Integer) escape_length(str)
Stolen from obfuscatejs.rb Determines the length of an escape sequence.
-
+ (String) js_eval(code, scope)
call to eval.
-
+ (String) rand_base(num)
Convert a number to a random base (decimal, octal, or hexedecimal).
-
+ (String) rand_text(charset, len)
Returns a random string of the desired length in the desired charset.
-
+ (String) rand_text_alpha(len)
Returns a random alpha string of the desired length.
-
+ (String) rand_text_alphanumeric(len)
Returns a random alphanumeric string of the desired length.
-
+ (String) random_string_encoding(str)
Given a Javascript string str with NO escape characters, returns an equivalent string with randomly escaped bytes.
-
+ (String) random_var_encoding(var_name)
In Javascript, it is possible to refer to the same var in a couple different ways:.
-
+ (Array) safe_split(str, opts = {})
Split a javascript string, str, without breaking escape sequences.
-
+ (String) string_to_bytes(str)
Converts a string to a series of byte values.
-
+ (String) to_hex(str, delimiter = "\\x")
Encodes the bytes in str as hex literals, each preceded by delimiter.
-
+ (Object) transform_number(num)
Return a mathematical expression that will evaluate to the given number num.
-
+ (Object) transform_string(str, scope, opts = {})
Convert a javascript string into something that will generate that string.
-
+ (String) transform_string_fromCharCode(str)
Return a call to String.fromCharCode() with each char of the input as arguments.
-
+ (Object) transform_string_split_concat(str, quote, scope)
Split a javascript string, str, into multiple randomly-ordered parts and return an anonymous javascript function that joins them in the correct order.
Class Method Details
+ (String) escape_javascript(javascript)
Taken from Rails ActionView: api.rubyonrails.org/classes/ActionView/Helpers/JavaScriptHelper.html
127 128 129 |
# File 'lib/jsobfu/utils.rb', line 127 def self.escape_javascript(javascript) javascript.gsub(/(\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] } end |
+ (Integer) escape_length(str)
Stolen from obfuscatejs.rb Determines the length of an escape sequence
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/jsobfu/utils.rb', line 251 def self.escape_length(str) esc_len = nil if str[0,1] == "\\" case str[1,1] when "u"; esc_len = 6 # unicode \u1234 when "x"; esc_len = 4 # hex, \x41 when /[0-7]/ # octal, \123, \0 str[1,3] =~ /([0-7]{1,3})/ if $1.to_i(8) > 255 str[1,3] =~ /([0-7]{1,2})/ end esc_len = 1 + $1.length else; esc_len = 2 # \" \n, etc. end end esc_len end |
+ (String) js_eval(code, scope)
call to eval. A random eval method is chosen.
56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/jsobfu/utils.rb', line 56 def self.js_eval(code, scope) code = '"' + escape_javascript(code) + '"' ret_statement = random_string_encoding 'return ' case rand(7) when 0; "window[#{transform_string('eval', scope, :quotes => false)}](#{code})" when 1; "[].constructor.constructor(\"#{ret_statement}\"+#{code})()" when 2; "(function(){}).constructor('', \"#{ret_statement}\"+#{code})()" when 3; "''.constructor.constructor('', \"#{ret_statement}\"+#{code})()" when 4; "Function(\"#{random_string_encoding 'eval'}\")()(#{code})" when 5; "Function(\"#{ret_statement}\"+#{code})()" when 6; "Function()(\"#{ret_statement}\"+#{code})()" end + ' ' end |
+ (String) rand_base(num)
Convert a number to a random base (decimal, octal, or hexedecimal).
Given 10 as input, the possible return values are:
"10"
"0xa"
"012"
81 82 83 84 85 86 87 |
# File 'lib/jsobfu/utils.rb', line 81 def self.rand_base(num) case rand(3) when 0; num.to_s when 1; "0%o" % num when 2; "0x%x" % num end end |
+ (String) rand_text(charset, len)
Returns a random string of the desired length in the desired charset
40 41 42 |
# File 'lib/jsobfu/utils.rb', line 40 def self.rand_text(charset, len) len.times.map { charset.sample }.join end |
+ (String) rand_text_alpha(len)
Returns a random alpha string of the desired length
31 32 33 |
# File 'lib/jsobfu/utils.rb', line 31 def self.rand_text_alpha(len) rand_text(ALPHA_CHARSET, len) end |
+ (String) rand_text_alphanumeric(len)
Returns a random alphanumeric string of the desired length
23 24 25 |
# File 'lib/jsobfu/utils.rb', line 23 def self.rand_text_alphanumeric(len) rand_text(ALPHANUMERIC_CHARSET, len) end |
+ (String) random_string_encoding(str)
Given a Javascript string str with NO escape characters, returns an
equivalent string with randomly escaped bytes
for every byte
111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/jsobfu/utils.rb', line 111 def self.random_string_encoding(str) encoded = '' str.unpack("C*") { |c| encoded << case rand(3) when 0; "\\x%02x"%(c) when 1; "\\#{c.to_s(8)}" when 2; "\\u%04x"%(c) when 3; [c].pack("C") end } encoded end |
+ (String) random_var_encoding(var_name)
In Javascript, it is possible to refer to the same var in a couple different ways:
var AB = 1;
console.log(\u0041\u0042); // prints "1"
96 97 98 99 100 101 102 103 104 |
# File 'lib/jsobfu/utils.rb', line 96 def self.random_var_encoding(var_name) # TODO: add support for this to the rkelly parser, otherwise we can't encode twice # if var_name.length < 3 and rand(6) == 0 # to_hex(var_name, "\\u00") # end # For now, do no encoding on var names (they are randomized anyways) var_name end |
+ (Array) safe_split(str, opts = {})
Split a javascript string, str, without breaking escape sequences.
The maximum length of each piece of the string is half the total length of the string, ensuring we (almost) always split into at least two pieces. This won't always be true when given a string like "AAx41", where escape sequences artificially increase the total length (escape sequences are considered a single character).
Returns an array of two-element arrays. The zeroeth element is a randomly generated variable name, the first is a piece of the string contained in quotes.
See #escape_length
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/jsobfu/utils.rb', line 218 def self.safe_split(str, opts={}) quote = opts.fetch(:quote) parts = [] max_len = str.length / 2 while str.length > 0 len = 0 loop do e_len = escape_length(str[len..-1]) e_len = 1 if e_len.nil? len += e_len # if we've reached the end of the string, bail break unless str[len] break if len > max_len # randomize the length of each part break if (rand(max_len) == 0) end part = str.slice!(0, len) parts.push("#{quote}#{part}#{quote}") end parts end |
+ (String) string_to_bytes(str)
Converts a string to a series of byte values
with random encodings (decimal/hex/octal)
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 |
# File 'lib/jsobfu/utils.rb', line 311 def self.string_to_bytes(str) len = 0 bytes = str.unpack("C*") encoded_bytes = [] while str.length > 0 if str[0,1] == "\\" str.slice!(0,1) # then this is an escape sequence and we need to deal with all # the special cases case str[0,1] # For chars that contain their non-escaped selves, step past # the backslash and let the rand_base() below decide how to # represent the character. when '"', "'", "\\", " " char = str.slice!(0,1).unpack("C").first # For symbolic escapes, use the known value when "n"; char = 0x0a; str.slice!(0,1) when "t"; char = 0x09; str.slice!(0,1) # Lastly, if it's a hex, unicode, or octal escape, pull out the # real value and use that when "x" # Strip the x str.slice!(0,1) char = str.slice!(0,2).to_i 16 when "u" # This can potentially lose information in the case of # characters like \u0041, but since regular ascii is stored # as unicode internally, String.fromCharCode(0x41) will be # represented as 00 41 in memory anyway, so it shouldn't # matter. str.slice!(0,1) char = str.slice!(0,4).to_i 16 when /[0-7]/ # Octals are a bit harder since they are variable width and # don't necessarily mean what you might think. For example, # "\61" == "1" and "\610" == "10". 610 is a valid octal # number, but not a valid ascii character. Javascript will # interpreter as much as it can as a char and use the rest # as a literal. Boo. str =~ /([0-7]{1,3})/ char = $1.to_i 8 if char > 255 str =~ /([0-7]{1,2})/ char = $1.to_i 8 end str.slice!(0, $1.length) end else char = str.slice!(0,1).unpack("C").first end encoded_bytes << rand_base(char) if char end encoded_bytes.join(',') end |
+ (String) to_hex(str, delimiter = "\\x")
Encodes the bytes in str as hex literals, each preceded by delimiter
49 50 51 |
# File 'lib/jsobfu/utils.rb', line 49 def self.to_hex(str, delimiter="\\x") str.bytes.to_a.map { |byte| delimiter+byte.to_s(16) }.join end |
+ (Object) transform_number(num)
Return a mathematical expression that will evaluate to the given number num.
num can be a float or an int, but should never be negative.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/jsobfu/utils.rb', line 137 def self.transform_number(num) case num when Fixnum if num == 0 r = rand(10) + 1 transformed = "('#{JSObfu::Utils.rand_text_alpha(r)}'.length-#{r})" elsif num > 0 and num < 10 # use a random string.length for small numbers transformed = "'#{JSObfu::Utils.rand_text_alpha(num)}'.length" else transformed = "(" divisor = rand(num) + 1 a = num / divisor.to_i b = num - (a * divisor) # recurse half the time for a a = (rand(2) == 0) ? transform_number(a) : rand_base(a) # recurse half the time for divisor divisor = (rand(2) == 0) ? transform_number(divisor) : rand_base(divisor) transformed << "#{a}*#{divisor}" transformed << "+#{b}" transformed << ")" end when Float transformed = "(#{num-num.floor}+#{rand_base(num.floor)})" end transformed end |
+ (Object) transform_string(str, scope, opts = {})
Convert a javascript string into something that will generate that string.
Randomly calls one of the transform_string_* methods
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/jsobfu/utils.rb', line 175 def self.transform_string(str, scope, opts={}) includes_quotes = opts.fetch(:quotes, true) str = str.dup quote = includes_quotes ? str[0,1] : '"' if includes_quotes str = str[1,str.length - 2] return quote*2 if str.length == 0 end if str.length > MAX_STRING_CHUNK return safe_split(str, :quote => quote).map { |arg| transform_string(arg, scope) }.join('+') end case rand(2) when 0 transform_string_split_concat(str, quote, scope) when 1 transform_string_fromCharCode(str) end end |
+ (String) transform_string_fromCharCode(str)
Return a call to String.fromCharCode() with each char of the input as arguments
Example:
input : "A\n"
output: String.fromCharCode(0x41, 10)
300 301 302 |
# File 'lib/jsobfu/utils.rb', line 300 def self.transform_string_fromCharCode(str) "String.fromCharCode(#{string_to_bytes(str)})" end |
+ (Object) transform_string_split_concat(str, quote, scope)
Split a javascript string, str, into multiple randomly-ordered parts and return an anonymous javascript function that joins them in the correct order. This method can be called safely on strings containing escape sequences. See #safe_split.
275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/jsobfu/utils.rb', line 275 def self.transform_string_split_concat(str, quote, scope) parts = safe_split(str, :quote => quote).map {|s| [scope.random_var_name, s] } func = "(function () { var " ret = "; return " parts.sort { |a,b| rand }.each do |part| func << "#{part[0]}=#{part[1]}," end func.chop! ret << parts.map{|part| part[0]}.join("+") final = func + ret + " })()" final end |