One Up on Cryptominers: Alert Logic’s Threat Intelligence Unravels Techniques Used to Evade Detections

Introduction

Attackers are constantly looking for new ways to monetize successfully breached victims. This can be in the form of ransom(ware), selling data on the dark web or having a host under their control operate as part of a botnet-for-hire. An increasingly common mechanism of this monetization in 2018, and further into 2019, has been cryptomining. Oftentimes this is achieved through access to a host or server and having it mine for currency and return the output to a crypto wallet controlled by the attackers. Increasingly, we have observed attackers compromising websites and then serving coin miners to the visitors of that website. In this way they are subverting a potentially legitimate way of internet monetization for their own purposes. The challenge for network defenders is, “How do you tell the difference?”

One of the primary ways of distinguishing between legitimate and potentially malicious browser mining payloads is to examine the obfuscation techniques used. We have observed that there is a high correlation between the level and sophistication of the obfuscation of the code and the likelihood that it has been planted maliciously. One such example of obfuscation came across our desks recently and is worth breaking down in greater detail.

We observed the mining payload in question being served to a client in the network of one of our customers, which was caught by our internal Alert Logic campaign tracking telemetry IDS signatures—specifically released signatures purely for the purpose of gathering data for post-processing and batch analytics. The signature in question caught a browser GET request to a known bad IP, requesting download of JavaScript code. The referrer header identified the original source—leading us to the compromised server (which wasn’t one of our customer’s). Further investigation suggested the entry vector in this case is most likely CVE-2018-7600 (Drupalgeddon 2). Attackers continue to make active and continual use of this vector—so if you still haven’t patched your Drupal core code to the latest version then you need to do so yesterday. This vector has also been reportedly used in similar attacks using the same CoinImp key (REF1).

Payload Analysis

NOTE : URLs, domains and IPs contained within these sections are malicious

Interestingly this appeared to have been done twice. It may be an error in the Drupal exploit code or perhaps the site has been re-infected more than once with the same payload.

The attackers were making use of the auth0-extend framework (REF-2) to load a dropper script. The script is lightly obfuscated, revealing the following de-obfuscated code:

Note : At the time of writing the auth0-extend token appears to have now been revoked.

This code was more heavily obfuscated (See section below for details) and de-obfuscation reveals the following:

  • Obfuscated Jquery source code mixed with a further payload
    • Obfuscated coinhive library, Client, Worker classes and configuration information i.e. a Monero miner client

If WASM is available in the browser the following miner worker code is used

If WASM is not available in the browser the ASM.JS fall-back miner worker code is used. Some de-obfuscation of this code reveals that the emscripten tool has been used to convert the native LLVM bytecode to Javascript.

A list of proxy domains are utilized for the mining connection via secure web sockets using AutobahnPython 17.10.1 to implement the web application messaging protocol:

Obfuscation Scheme

NOTE : URLs, domains and IPs contained within these sections are malicious. All references to ‘HTTP’ have been changed to ‘HXXP’ and ‘WSS’ to ‘WXX’

The secondary payload script, which is loaded from the initial wrapper script, is a 680,000-character single line of obfuscated JavaScript which formats to 1263 lines when 'printed pretty' (making it easier to read). The following is a walkthrough of parts of the de-obfuscation process.

Immediately obvious is a large array at the start of the file with what seems to be base64 encoded strings

    e.g. 
var a = ['anzDrsO6wqlbw5jCpSo5MA==', 'wojDhcO+w5DDpQ==',... // TRUNCATED FOR READABILITY

## These don't decode to anything readable with a simple base64 decode however
'anzDrsO6wqlbw5jCpSo5MA==' --> 'j|îú©[Ø¥*90'

Scanning down the source file reveals many clearly obfuscated functions, without any clear general indication of the purpose. Some parts which stand out in the clear include apparent 'JQuery' related keywords amongst the obfuscated functions.

    e.g. 
var Bh = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
l1['Deferred'][b('0x40e', 'HI16')] = function (Bi, Bj) {
J['console'] && J[b('0x40f', '4KP0')]['warn'] && Bi && Bh['test'](Bi['name']) && J[b('0x410', ')Kws')][b('0x411', '34Z]')]('jQuery.Deferred\x20exception:\x20' + Bi[b('0x412', 'nx6R')], Bi['stack'], Bj);
}, // TRUNCATED FOR READABILITY

Buried in the middle of the code is a single eval() statement (the only one in the entire file)

    var b = function (c, d) {...}; // TRUNCATED FOR READABILITY 
...
var v = b('0x0', 'O2y(');
...
function f(f) {...}; // TRUNCATED FOR READABILITY
...
v = f(v);
eval(v);

Performing static analysis of the code contained within the 'f' and 'b' functions indicates that these functions contain the de-obfuscation routines that must be required to provide eval with an executable JavaScript string.

The function 'b' takes an offset into the large array of base64 encoded strings at the start of the file 'a' and a 'seed' value used in the unpacking of the elements in the array. Function 'f' wraps 'b' and performs additional unpacking and concatenation of decrypted strings before returning the payload.

Various techniques have been used to obfuscate the payload that is reversed in these functions including:

  • variable and function randomization
  • table lookups
  • functions within functions as variables stored in arrays
  • base64 encoding
  • UTF-16 encoding
  • string splitting and substitution
  • regex search and replace
  • URL encoding wrapping
  • charset arithmetic

Charset arithmetic specifically used with seed value is used to decrypt (variable 's' in the snip below from the variable ‘q’ of function 'b'. The obfuscations are layered. Extracting and implementing these algorithms as stand-alone code blocks helps to analyze their behavior.

    var q = function (r, s) { 
var t = [],
u = 0x0,
v,
w = '',
x = '';
r = atob(r);
for (var y = 0x0, z = r['length']; y < z; y++) {
x += '%' + ('00' + r['charCodeAt'](y)['toString'](0x10))['slice'](-0x2);
}
r = decodeURIComponent(x);
for (var A = 0x0; A < 0x100; A++) {
t[A] = A;
}
for (A = 0x0; A < 0x100; A++) {
u = (u + t[A] + s['charCodeAt'](A % s['length'])) % 0x100;
v = t[A];
t[A] = t[u];
t[u] = v;
}
A = 0x0;
u = 0x0;
for (var B = 0x0; B < r['length']; B++) {
A = (A + 0x1) % 0x100;
u = (u + t[A]) % 0x100;
v = t[A];
t[A] = t[u];
t[u] = v;
w += String['fromCharCode'](r['charCodeAt'](B) ^ t[(t[A] + t[u]) % 0x100]);
}
return w;
}

Applying the de-obfuscation routines against the to-be-evaluated string yields the following start of a string, which is, in fact, JavaScript code. More strings now appear in the clear.

    First few lines : 
var a=['wq3CuTnCnwrCvw==','w614F2fDqA==','woLCrCrCnA==','wq5Nw5zDtMKn','THbDkDQp', ... // TRUNCATED FOR READABILITY

last few lines:

// TRUNCATED FOR READABILITY
'WEBSOCKET_SHARDS': , 'WEBSOCKET_SHARDS_W':

Looking through the new code shows a similar obfuscation method. Function 'b' acts as a decryption algorithm and lookup for the large array of strings in 'a', given a seed. Function 'f' is different and encapsulates various other lookups and functions which can be seen to be used throughout the code to further de-obfuscate areas of the code. These often wrap calls to the function 'b'.

    e.g. 

(function (f) { 
var g = {
'MAIHo': b('0x0', 'Q&^^'),
'CggYA': function (h, i) {
return h + i;
},
'smMxU': 'return (function() ',
'MBXld': b('0x1', '^$Vi'),
'MlbEd': function (j) {
return j();
},
'ztcwb': b('0x2', 's5h6'),
'VyuNw': '_waitingForAuth',
... // TRUNCATED FOR READABILITY

Much of the code is still obfuscated. However, enough is visible to show that this snip contains a coinhive client object config.

    self['Client'] = self['Client'] || {}; 
self['Client'][b('0x247', '[83C')] = {
'LIB_URL': b('0x248', 'K45t'),
'ASMJS_NAME': 'JPWwtn.js',
'REQUIRES_AUTH': ![],
'WEBSOCKET_SHARDS': [
[b('0x249', 'qd1d'), b('0x24a', 'f^OV'), b('0x24b', 'lu[@'), b('0x24c', 'VG&k'), b('0x24d', 'oCj3'), b('0x24e', 'm^[D'), b('0x24f', 's5h6'), b('0x250', 'rqWO'), b('0x251', '@ul4'), b('0x252', '^$Vi'), b('0x253', '93Ve'), b('0x254', '[83C'), 'wxx://jshosting.download.:443/proxy', 'wxx://jshosting.loan.:443/proxy', b('0x255', 'eLiX'), 'wxx://jshosting.racing.:443/proxy', 'wxx://jshosting.review.:443/proxy', b('0x256', '3)K0'), b('0x257', 'lnMU'), b('0x258', 'yS%k')]
],
'WEBSOCKET_SHARDS_W': [
[b('0x259', 'f^OV'), b('0x25a', 'vS1S'), b('0x25b', '*D5N'), b('0x25c', '$dVc'), b('0x25d', 'u1)j'), b('0x25e', 'BkQK'), 'wxx://jshosting.bid.:443/proxy2', b('0x25f', 's5h6'), b('0x260', 'lu[@'), b('0x261', '^CX['), b('0x262', 'yS%k'), b('0x263', 'BkQK'), 'wxx://jshosting.stream.:443/proxy2', 'wxx://hostingcloud.bid.:443/proxy2', 'wxx://hostingcloud.date.:443/proxy2', b('0x264', '*D5N'), 'wxx://jshosting.win.:443/proxy2', b('0x265', 's5h6'), 'wxx://hostingcloud.faith.:443/proxy2', 'wxx://hostingcloud.loan.:443/proxy2']
]
};

Now by applying the algorithms we have discovered to areas of this code it can start to be filled out

e.g. -> 'LIB_URL': b('0x248', 'K45t') -> 'LIB_URL': 'hxxps://www.hostingcloud.racing/'

    Fully decrypted array : 

({0:"1|3|5|8|6|2|4|7|0", 1:"{}.constructor(\"return this\")( )", 2:"21|15|19|10|1|8|3|16|14|6|22|17|0|12|9|11|7|4|24|2|13|20|23|18|5", 3:"WBLOB", 4:"_tokenFromServer", 5:"light", 6:"params", 7:"language", 8:"_hashes", 9:"_tab", 10:"client_a4f550c1", 11:"onmessage", 12:"bind", 13:"hasWASMSupport", 14:"max", 15:"throttle", 16:"_onVerified", 17:"unloaded", 18:"data", 19:"_totalHashesFromDeadThreads", 20:"_onTargetMetBound", 21:"hardwareConcurrency", 22:"_asmjsStatus", 23:"_startNow", 24:"interval", 25:"_useWASM", 26:"loaded", 27:"pending", 28:"ASMJS_NAME", 29:"hashesTotal", 30:"_socket", 31:"_curr3ntJ0b", 32:"_autoThreads", 33:"dontKillTabUpdate", 34:"now", 35:"push", 36:"min", 37:"_targetNumThreads", 38:"stop", 39:"V2ViQXNzZW1ibHk=", 40:"2|0|3|1|5|4", 41:"top: 0;", 42:"bottom: 0;", 43:"Bottom", 44:"; width: 100%; color: ", 45:"0x369a808887", 46:"4|1|2|5|6|3|0", 47:"_connect", 48:"IF_EXCLUSIVE_TAB", 49:"_otherTabRunning", 50:"FORCE_MULTI_TAB", 51:"verifyThread", 52:"lastPingReceived", 53:"ident", 54:"isRunning", 55:"setItem", 56:"stringify", 57:"getHashesPerSecond", 58:"_hashString", 59:"_connect_real", 60:"WEBSOCKET_SHARDS", 61:"onerror", 62:"2|0|4|3|1", 63:"_emit", 64:"type", 65:"toString", 66:"connection_error", 67:"_onClose", 68:"4|3|0|2|5|1", 69:"parse", 70:"_decodeData", 71:"job", 72:"adjustEvery", 73:"_onVerifiedBound", 74:"hashes", 75:"banned", 76:"setJob", 77:"nonce", 78:"_send", 79:"submit", 80:"verified", 81:"_user", 82:"getAcceptedHashes", 83:"_encodeData", 84:"_onMessage", 85:"_onTargetMet", 86:"Client", 87:"forceExclusiveTab", 88:"CggYA", 89:"CggYA", 90:"smMxU", 91:"MBXld", 92:"MlbEd", 93:"console", 99:"console", 100:"log", 101:"console", 102:"debug", 103:"console", 104:"info", 105:"console", 106:"console", 107:"console", 108:"trace", 109:"ztcwb", 110:"_threads", 111:"params", 112:"threads", 113:"_curr3ntJ0b", 114:"params", 115:"'};", 116:"jjgMC", 117:"kxgOr", 118:"REQUIRES_AUTH", 127:"MnDQv", 128:"syUJC", 129:"mmsfB", 130:"random", 131:"BroadcastChannel", 136:"mGRIO", 137:"_autoThreads", 138:"autoThreads", 139:"forceASMJS", 140:"YJNgp", 141:"jzwuW", 142:"_autoReconnect", 143:"_throttle", 144:"vHqWW", 145:"mGRIO", 146:"LCMUo", 147:"mSleZ", 148:"bind", 149:"Vykva", 150:"start", 151:"_tab", 152:"mode", 154:"wECUz", 158:"pcxVj", 189:"prototype", 194:"prototype", 195:"getTotalHashes", 200:"Vykva", 201:"stDVe", 203:"prototype", 204:"getToken", 208:"prototype", 209:"getAutoThreadsEnabled", 211:"prototype", 216:"uXAxa", 226:"gYMvL", 228:"prototype", 229:"setThrottle", 235:"prototype", 236:"getNumThreads", 238:"Vykva", 239:"setNumThreads", 240:"max", 241:"ccQFo", 242:"cMvme", 243:"_threads", 244:"cMvme", 245:"BNbdH", 246:"vUtiN", 250:"AlSXt", 258:"prototype", 259:"hasWASMSupport", 260:"qOTOL", 261:"MEaib", 262:"prototype", 263:"AZNOm", 265:"Vykva", 266:"isMobile", 268:"prototype", 269:"addMiningNotification", 279:"split", 318:"HpZup", 319:"mode", 320:"EvHeU", 322:"mode", 323:"qGgJd", 324:"syUJC", 327:"QdLMM", 328:"_tab", 329:"rmKNV", 332:"EPRDm", 333:"verifyThread", 334:"BNbdH", 335:"setNumThreads", 336:"prototype", 337:"iKhiR", 347:"Vykva", 348:"wSQtT", 365:"Vykva", 366:"fbhZK", 378:"Vykva", 380:"Vykva", 381:"lnsYp", 382:"BCcOz", 383:"DjnnE", 384:"hostname", 385:"replace", 386:"lnsYp", 388:"ZwVZQ", 389:"jzwuW", 391:"_hashString", 392:"_sitek", 393:"cMvme", 394:"ePAxF", 395:"_socket", 396:"onmessage", 397:"_onMessage", 398:"_socket", 399:"ceXVH", 400:"_onError", 401:"_onClose", 402:"_socket", 403:"onopen", 404:"prototype", 405:"_onOpen", 415:"prototype", 416:"_onError", 417:"_emit", 418:"error", 419:"exkqD", 420:"NNMtn", 421:"AlSXt", 422:"_autoReconnect", 423:"tuLze", 424:"iYGoo", 425:"NvmUM", 426:"_socket", 427:"AlSXt", 428:"cMvme", 429:"xtsmD", 430:"code", 431:"xCFsd", 432:"code", 433:"_reconnectRetry", 434:"wviQp", 435:"now", 436:"NvmUM", 437:"oAJxa", 438:"close", 439:"tgCjN", 443:"Vykva", 448:"CTZLA", 491:"xpsAH", 504:"prototype", 506:"prototype", 507:"_send", 509:"PZrEk", 510:"ifExclusiveTab", 511:"FORCE_EXCLUSIVE_TAB", 512:"YLdWR", 513:"PZrEk", 514:"rmKNV", 515:"forceMultiTab", 516:"Client", 518:"Anonymous", 519:"running", 520:"now", 521:"Client", 522:"prototype", 525:"zfiKN", 526:"stop", 527:"kNbDv", 528:"worker", 529:"itCsR", 530:"worker", 531:"running", 537:"WBLOB", 538:"LdkrJ", 539:"jobCallback", 540:"zBLWP", 541:"hashesPerSecond", 542:"Ranpg", 543:"QiCVw", 544:"data", 545:"IFuzS", 546:"XFjnN", 547:"onmessage", 548:"postMessage", 550:"vOSoA", 551:"IZAWE", 552:"curr3ntJob", 553:"qPbZC", 567:"KeVJY", 568:"curr3ntJob", 569:"bbvtS", 570:"EbqFu", 571:"_isReady", 572:"JuleN", 573:"zfiKN", 574:"wWPPq", 575:"oeDzL", 577:"gmihP", 578:"wJLko", 579:"LGmbB", 580:"curr3ntJob", 583:"CONFIG", 584:"hxxps://www.hostingcloud.racing/", 585:"wxx://freecontent.faith.:443/proxy", 586:"wxx://freecontent.party.:443/proxy", 587:"wxx://freecontent.science.:443/proxy", 588:"wxx://freecontent.trade.:443/proxy", 589:"wxx://hostingcloud.accountant.:443/proxy", 590:"wxx://hostingcloud.bid.:443/proxy", 591:"wxx://hostingcloud.date.:443/proxy", 592:"wxx://hostingcloud.download.:443/proxy", 593:"wxx://hostingcloud.faith.:443/proxy", 594:"wxx://hostingcloud.loan.:443/proxy", 595:"wxx://jshosting.bid.:443/proxy", 596:"wxx://jshosting.date.:443/proxy", 597:"wxx://jshosting.party.:443/proxy", 598:"wxx://jshosting.stream.:443/proxy", 599:"wxx://jshosting.trade.:443/proxy", 600:"wxx://jshosting.win.:443/proxy", 601:"wxx://jshosting.party.:443/proxy2", 602:"wxx://freecontent.faith.:443/proxy2", 603:"wxx://freecontent.party.:443/proxy2", 604:"wxx://freecontent.science.:443/proxy2", 605:"wxx://freecontent.trade.:443/proxy2", 606:"wxx://hostingcloud.accountant.:443/proxy2", 607:"wxx://jshosting.date.:443/proxy2", 608:"wxx://jshosting.download.:443/proxy2", 609:"wxx://jshosting.loan.:443/proxy2", 610:"wxx://jshosting.racing.:443/proxy2", 611:"wxx://jshosting.review.:443/proxy2", 612:"wxx://jshosting.trade.:443/proxy2", 613:"wxx://hostingcloud.download.:443/proxy2"})

It’s worth noting that some references decrypted from the 'a' array, by functions within 'b' refer back into the code and are not fully de-obfuscated directly by the output of ‘b’.

    e.g. this[cU[b('0x23c', 'RmoF')]] = ![]; 

572:"JuleN" [Symbol] decrypted from array a by b()

var cU = {
... // TRUNCATED FOR READABILITY
'JuleN': cL[b('0x21f', 'lnMU')], [Symbol] another reference to b() decryption
... // TRUNCATED FOR READABILITY

543:"QiCVw" [Symbol] decrypted result

var cL = {
... // TRUNCATED FOR READABILITY
'Ranpg': 'hashesTotal',
'QiCVw': b('0x207', '8C9z'), [Symbol] another reference to b() decryption – this time directly
... // TRUNCATED FOR READABILITY

519:"running" [Symbol] decrypted result

JobThread: (function () {
// TRUNCATED FOR READABILITY
this[cU[b('0x23c', 'RmoF')]] = ![]; <---- this["running"] = ![]; // AFTER FURTHER DEOBFUSCATION
this['lastMessageTimestamp'] = Date['now']();
}),

General approach taken to de-obfuscate this code:

  1. Scan obfuscated payload to identify any entry points
  2. Follow the eval() backtrace using the f(b(a))) function chains to retrieve the partially de-obfuscated payload
  3. Scan the partially de-obfuscated payload for repeating patterns. The most common is somevar = b('bla', 'bla') etc
  4. As we have already determined by static analysis that the data defined in array ‘a’ is encrypted and can be reversed by applying the algorithms defined with function ‘b’ (and encapsulated sub functions such as ‘q’), the encrypted array and decryption algorithm can be separated out and implemented in a separate script. Each call to ‘b’ can be executed with the offsets and seeds defined in the obfuscated script e.g. ‘: b('0x248', 'K45t')’. This will return the decrypted string which can then be used to replace each call to ‘b’ within the obfuscated script (regex search - /b\('\w+','\w+'\)/g).
  5. Scan the partially de-obfuscated payload for repeating patterns. Now that more code has been de-obfuscated, we can see that some of the remaining obfuscated data is hidden behind further layers, e.g. using the lookups defined in 'cU'. Repeat the process from 4 replacing all calls to /Cu\("\w+"\)/g.
  6. Repeat for further wrapping, implementing each algorithm or lookup table identified as the previous layers are made visible and replacing each occurrence of calls to the functions with the output. The more layers which are removed leave fewer numbers of obfuscated statements for the next iteration.

IOCs and Artifacts

NOTE : URLs, domains and IPs contained within these sections are malicious. All references to ‘HTTP’ have been changed to ‘HXXP’ and ‘WSS’ to ‘WXX’

HTTP requests to any of the following URLs or DNS resolving of any of the hostnames should be considered suspicious.

"hxxps://www.hostingcloud.racing/"
"wxx://freecontent.faith.:443/proxy",
"wxx://freecontent.party.:443/proxy",
"wxx://freecontent.science.:443/proxy",
"wxx://freecontent.trade.:443/proxy",
"wxx://hostingcloud.accountant.:443/proxy",
"wxx://hostingcloud.bid.:443/proxy",
"wxx://hostingcloud.date.:443/proxy",
"wxx://hostingcloud.download.:443/proxy",
"wxx://hostingcloud.faith.:443/proxy",
"wxx://hostingcloud.loan.:443/proxy",
"wxx://jshosting.bid.:443/proxy",
"wxx://jshosting.date.:443/proxy",
"wxx://jshosting.party.:443/proxy",
"wxx://jshosting.stream.:443/proxy",
"wxx://jshosting.trade.:443/proxy",
"wxx://jshosting.win.:443/proxy",
"wxx://jshosting.party.:443/proxy2",
"wxx://freecontent.faith.:443/proxy2",
"wxx://freecontent.party.:443/proxy2",
"wxx://freecontent.science.:443/proxy2",
"wxx://freecontent.trade.:443/proxy2",
"wxx://hostingcloud.accountant.:443/proxy2",
"wxx://jshosting.date.:443/proxy2",
"wxx://jshosting.download.:443/proxy2",
"wxx://jshosting.loan.:443/proxy2",
"wxx://jshosting.racing.:443/proxy2",
"wxx://jshosting.review.:443/proxy2",
"wxx://jshosting.trade.:443/proxy2",
"wxx://hostingcloud.download.:443/proxy2"

References

ID 

Date 

Description 

Link 

1 

19/NOV/2018 

Cryptojacking campaign using the same CoinImp key as seen in this attack 

https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/hackers-wish-come-true-after-infecting-visitors-of-make-a-wish-website-with-cryptojacking/ 

2 

10/JAN/2019 

Auth0-extend 

https://goextend.io/developers