Module: JSObfu::Utils
- Defined in:
- lib/jsobfu/utils.rb
Overview
Some quick utility functions to minimize dependencies
Constant Summary collapse
- 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
-
.escape_javascript(javascript) ⇒ String
Taken from Rails ActionView: api.rubyonrails.org/classes/ActionView/Helpers/JavaScriptHelper.html.
-
.escape_length(str) ⇒ Integer
Stolen from obfuscatejs.rb Determines the length of an escape sequence.
-
.js_eval(code, scope) ⇒ String
call to
eval
. -
.rand_base(num) ⇒ String
Convert a number to a random base (decimal, octal, or hexedecimal).
-
.rand_text(charset, len) ⇒ String
Returns a random string of the desired length in the desired charset.
-
.rand_text_alpha(len) ⇒ String
Returns a random alpha string of the desired length.
-
.rand_text_alphanumeric(len) ⇒ String
Returns a random alphanumeric string of the desired length.
-
.random_string_encoding(str) ⇒ String
Given a Javascript string
str
with NO escape characters, returns an equivalent string with randomly escaped bytes. -
.random_var_encoding(var_name) ⇒ String
In Javascript, it is possible to refer to the same var in a couple different ways:.
-
.safe_split(str, opts = {}) ⇒ Array
Split a javascript string,
str
, without breaking escape sequences. -
.string_to_bytes(str) ⇒ String
Converts a string to a series of byte values.
-
.to_hex(str, delimiter = "\\x") ⇒ String
Encodes the bytes in
str
as hex literals, each preceded bydelimiter
. -
.transform_number(num) ⇒ Object
Return a mathematical expression that will evaluate to the given number
num
. -
.transform_string(str, scope, opts = {}) ⇒ Object
Convert a javascript string into something that will generate that string.
-
.transform_string_fromCharCode(str) ⇒ String
Return a call to String.fromCharCode() with each char of the input as arguments.
-
.transform_string_split_concat(str, quote, scope) ⇒ Object
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
.escape_javascript(javascript) ⇒ String
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 |
.escape_length(str) ⇒ Integer
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 |
.js_eval(code, scope) ⇒ String
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 |
.rand_base(num) ⇒ String
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 |
.rand_text(charset, len) ⇒ String
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 |
.rand_text_alpha(len) ⇒ String
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 |
.rand_text_alphanumeric(len) ⇒ String
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 |
.random_string_encoding(str) ⇒ String
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 |
.random_var_encoding(var_name) ⇒ String
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 |
.safe_split(str, opts = {}) ⇒ Array
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_to_bytes(str) ⇒ String
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("U").first end encoded_bytes << rand_base(char) if char end encoded_bytes.join(',') end |
.to_hex(str, delimiter = "\\x") ⇒ String
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 |
.transform_number(num) ⇒ Object
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 Integer 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 |
.transform_string(str, scope, opts = {}) ⇒ Object
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 |
.transform_string_fromCharCode(str) ⇒ String
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 |
.transform_string_split_concat(str, quote, scope) ⇒ Object
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 |