I observed the following seemingly out of place Javascript snippet on a website’s checkout page, and I was concerned that it might be skimming credit card numbers:
var R = ['1jBCeMi', '81AdhODE', 'keydown', 'val', '7kEITdb', 'click', '626015GsVvlf', '108070kQUXAS', 'ready', 'form.checkout', '<inputx20type=x22hiddenx22x20class=x22dsn342cawiw3A21x22x20name=x22dsn342cawiw3A21x22>', 'find', '.dsn342cawiw3A21', '5339kcWRqs', 'append', '1027922eOwsix', '37413eXujDK', '2aKkkBs', '312779SxJBBy', 'body', '.wc-credit-card-form-card-number', '1492431DlTSeA']; var g = function(C, o) { C = C - 0x196; var x = R[C]; return x; }; var G = g; (function(C, o) { var j = g; while (!![]) { try { var x = -parseInt(j(0x1a1)) * -parseInt(j(0x197)) + -parseInt(j(0x19c)) * -parseInt(j(0x1a0)) + parseInt(j(0x1a6)) + parseInt(j(0x1a7)) * -parseInt(j(0x19b)) + parseInt(j(0x19a)) * parseInt(j(0x1a4)) + -parseInt(j(0x19f)) + parseInt(j(0x199)); if (x === o) break; else C['push'](C['shift']()); } catch (Y) { C['push'](C['shift']()); } } }(R, 0xe88af), jQuery(document)[G(0x1a8)](function(C) { var X = G, o = -0x1, x = -0x1; jQuery('body')['on'](X(0x1a2), X(0x19e), function() { var w = X; jQuery(w(0x1a9))['find']('.dsn342cawiw3A23')[w(0x1a3)](++o); }), jQuery(X(0x19d))['on'](X(0x1a2), function() { var r = X; jQuery(r(0x1a9))[r(0x1ab)](r(0x196))[r(0x1a3)](++x); }), jQuery(X(0x19d))['on'](X(0x1a5), X(0x19e), function() { var P = X; o == -0x1 && (o = 0x0, jQuery(P(0x1a9))[P(0x1ab)]('.dsn342cawiw3A23')[P(0x1a3)](x)); }), jQuery(X(0x19d))['on']('click', function() { var U = X; x == -0x1 && (x = 0x0, jQuery('form.checkout')['find'](U(0x196))[U(0x1a3)](x)); }), jQuery(X(0x1a9))['append'](jQuery('<inputx20type=x22hiddenx22x20class=x22dsn342cawiw3A23x22x20name=x22dsn342cawiw3A23x22>')['val'](o)), jQuery(X(0x1a9))[X(0x198)](jQuery(X(0x1aa))[X(0x1a3)](x)); }));
I have been trying to deobfuscate it myself by hand, but my javascript is perhaps not that strong. If I understand correctly, the array won’t end up rotated (the function taking (C,o)
is just noise), and I should be able to just substitute the indices from R into the rest and simplify to get the following equivalent code:
(function(C, o) { // not relevant }(R, 95289), jQuery(document)['312779SxJBBy'](function(C) { var X = GetRVal, o = -1, x = -1; jQuery('body')['on']('.dsn342cawiw3A21', 'ready', function() { jQuery('body')['find']('.dsn342cawiw3A23')['5339kcWRqs'](++o); }), jQuery('108070kQUXAS')['on']('.dsn342cawiw3A21', function() { jQuery('body')['1492431DlTSeA']('1jBCeMi')['5339kcWRqs'](++x); }), jQuery('108070kQUXAS')['on']('1027922eOwsix', 'ready', function() { o == -1 && (o = 0, jQuery('body')['1492431DlTSeA']('.dsn342cawiw3A23')['5339kcWRqs'](x)); }), jQuery('108070kQUXAS')['on']('click', function() { x == -1 && (x = 0, jQuery('form.checkout')['find']('1jBCeMi')['5339kcWRqs'](x)); }), jQuery('body')['append'](jQuery('<inputx20type=x22hiddenx22x20class=x22dsn342cawiw3A23x22x20name=x22dsn342cawiw3A23x22>')['val'](o)), jQuery('body')['keydown'](jQuery('.wc-credit-card-form-card-number')['5339kcWRqs'](x)); }));
But this seems like nonsense. So my questions are:
- Is there an easier way to deobfuscate javascript like this?
- Should the array R have ended up rotated? Or is there something going on here that I don’t see? And
- Is this malicious code or am I completely off-base?
Advertisement
Answer
There are actually rotations taking place, but we don’t really care if we take the following approach:
Just run the first part that rotates
R
In the second part there are some variables defined in callback functions, which we can make global variables without changing the result of the script. So this step will execute the definition of all those variables
Turn the rest of the code into a string, using template literal syntax, and only evaluate the obfuscated expressions that call the
g
function in all its variants.Print that template literal.
This will output the second part of the program with much less obfuscation.
The following snippet realises the above steps:
// 1. Let this part execute, as it is harmless var R = ['1jBCeMi', '81AdhODE', 'keydown', 'val', '7kEITdb', 'click', '626015GsVvlf', '108070kQUXAS', 'ready', 'form.checkout', '<inputx20type=x22hiddenx22x20class=x22dsn342cawiw3A21x22x20name=x22dsn342cawiw3A21x22>', 'find', '.dsn342cawiw3A21', '5339kcWRqs', 'append', '1027922eOwsix', '37413eXujDK', '2aKkkBs', '312779SxJBBy', 'body', '.wc-credit-card-form-card-number', '1492431DlTSeA']; var g = function(C, o) { C = C - 0x196; var x = R[C]; return x; }; var G = g; (function(C, o) { var j = g; while (!![]) { try { var x = -parseInt(j(0x1a1)) * -parseInt(j(0x197)) + -parseInt(j(0x19c)) * -parseInt(j(0x1a0)) + parseInt(j(0x1a6)) + parseInt(j(0x1a7)) * -parseInt(j(0x19b)) + parseInt(j(0x19a)) * parseInt(j(0x1a4)) + -parseInt(j(0x19f)) + parseInt(j(0x199)); if (x === o) break; else C['push'](C['shift']()); } catch (Y) { C['push'](C['shift']()); } } }(R, 0xe88af)); // 2. Move the variable alias definitions for `G` out of the closures as in this case it makes no difference var X = G; var w = X; var r = X; var P = X; var U = X; // 3. Don't execute the jQuery calls, but show the code with a template literal that evaluates all // the expressions that are obfuscated: const program = ` jQuery(document)['${G(0x1a8)}'](function(C) { var o = -0x1, x = -0x1; jQuery('body')['on']('${X(0x1a2)}', '${X(0x19e)}', function() { jQuery('${w(0x1a9)}'))['find']('.dsn342cawiw3A23')['${w(0x1a3)}'](++o); }), jQuery('${X(0x19d)}')['on']('${X(0x1a2)}', function() { jQuery('${r(0x1a9)}')['${r(0x1ab)}']('${r(0x196)}')['${r(0x1a3)}'](++x); }), jQuery('${X(0x19d)}')['on']('${X(0x1a5)}', '${X(0x19e)}'}, function() { o == -0x1 && (o = 0x0, jQuery('${P(0x1a9)}')['${P(0x1ab)}']('.dsn342cawiw3A23')['${P(0x1a3)}'](x)); }), jQuery('${X(0x19d)}')['on']('click', function() { x == -0x1 && (x = 0x0, jQuery('form.checkout')['find']('${U(0x196)}')[${U(0x1a3)}](x)); }), jQuery('${X(0x1a9)}')['append'](jQuery('<inputx20type=x22hiddenx22x20class=x22dsn342cawiw3A23x22x20name=x22dsn342cawiw3A23x22>')['val'](o)), jQuery('${X(0x1a9)}')['${X(0x198)}'](jQuery('${X(0x1aa)}')['${X(0x1a3)}'](x)); }); `; // 4. Print that program in more readable form: console.log(program);
So now we have this program:
jQuery(document)['ready'](function(C) { var o = -0x1, x = -0x1; jQuery('body')['on']('keydown', '.wc-credit-card-form-card-number', function() { jQuery('form.checkout'))['find']('.dsn342cawiw3A23')['val'](++o); }), jQuery('body')['on']('keydown', function() { jQuery('form.checkout')['find']('.dsn342cawiw3A21')['val'](++x); }), jQuery('body')['on']('click', '.wc-credit-card-form-card-number'}, function() { o == -0x1 && (o = 0x0, jQuery('form.checkout')['find']('.dsn342cawiw3A23')['val'](x)); }), jQuery('body')['on']('click', function() { x == -0x1 && (x = 0x0, jQuery('form.checkout')['find']('.dsn342cawiw3A21')[val](x)); }), jQuery('form.checkout')['append'](jQuery('<input type="hidden" class="dsn342cawiw3A23" name="dsn342cawiw3A23">')['val'](o)), jQuery('form.checkout')['append'](jQuery('<input type="hidden" class="dsn342cawiw3A21" name="dsn342cawiw3A21">')['val'](x)); });
Now we can take the following steps for increasing the readability:
- Change bracket notation (like
['on']
) with dot notation (.on
, …etc) - The argument
C
in the toplevel callback is not used: can be dropped - Replace
&&
operator withif
statement - Replace the comma operator with statement separator.
- Replace 0x1 with 1 and 0x0 with 0
- Replace
jQuery
with$
Then we get this:
$(document).ready(function() { var o = -1, x = -1; $('body').on('keydown', '.wc-credit-card-form-card-number', function() { $('form.checkout')).find('.dsn342cawiw3A23').val(++o); }); $('body').on('keydown', function() { $('form.checkout').find('.dsn342cawiw3A21').val(++x); }); $('body').on('click', '.wc-credit-card-form-card-number'}, function() { if (o == -1) { o = 0; $('form.checkout').find('.dsn342cawiw3A23').val(x); } }); $('body').on('click', function() { if (x == -1) { x = 0; $('form.checkout').find('.dsn342cawiw3A21').val(x); } }); $('form.checkout').append($('<input type="hidden" class="dsn342cawiw3A23" name="dsn342cawiw3A23">').val(o)); $('form.checkout').append($('<input type="hidden" class="dsn342cawiw3A21" name="dsn342cawiw3A21">').val(x)); });
This is plain code, easy to read now. Personally, I think there is a mistake. It would make more sense if the first .val(x)
were .val(o)
.
Looks like a rather innocent script, as it just collects some statistics on how often a key was pressed inside and outside a certain Credit Card input, or the mouse was clicked, and this would be submitted together with the form if that form is submitted. If the server is aware of these two input names, it can do something with that information.
I can’t see how that information is harmful. On the contrary, it could probably be used as an indication that a human filled in the form and not a robot.
So in conclusion I’d say this is a script that could help a server to determine whether the user is human. But then it’s very, very basic.