Malware Analysis - Deobfuscating NodeJs pkg packed stealer MythJs
Watch on YouTubeVideo summary
The video focuses on analyzing a sophisticated Node.js stealer application packed with the PKG packer, which presents significant challenges for automated analysis tools. Standard unpackers and deobfuscators often fail to properly handle this specific malware, leaving the JavaScript code compressed and obfuscated in a way that prevents immediate inspection. The presenter demonstrates that while generic tools like `pkg-unpacker` can initially extract the virtual file system, they frequently encounter issues with Brotli compression or produce unreadable garbage data. To overcome these hurdles, the analyst decides to manually explore the PKG virtual file system using custom scripts written in Python, successfully extracting the payload and revealing the entry point located within an overlay folder.
Once the files are extracted, the analysis reveals that the code is heavily obfuscated using a tool called JSConfuser, which complicates further reverse engineering efforts. General-purpose deobfuscators attempt to process the code but inevitably fail at specific steps, such as during the transform control flattening phase or when encountering invalid syntax in postfix operations. Rather than attempting to fix every single error or bug within the deobfuscation pipeline, the presenter adopts a pragmatic approach by wrapping critical sections of the deobfuscator script in try-catch blocks. This strategy allows the process to skip over failing transformations and continue running, ultimately producing a partially deobfuscated version of the code that is readable enough for analysis.
The final goal of this manual intervention is to strip away the obfuscation layers sufficiently to identify the malware's core functionality and exfiltration mechanisms. By cleaning up the output and ignoring minor syntax errors, the analyst successfully uncovers key indicators of compromise, including references to Chrome processes and Discord webhooks used for data theft. The resulting readable code reveals features such as emoji-based communication channels on Discord and webhook URLs designed to send stolen credentials back to the attacker's server. This method highlights that perfect deobfuscation is not always necessary; obtaining a version of the code where the logic and malicious strings are visible allows security researchers to understand the threat effectively without getting stuck on unfixable technical errors.
Read the full video transcript
Welcome to meta analysis for hedgehogs.
Today we look at miss JS stealer. I
posted a little bit about this stealer
on X. It's a node JS application that is
packed with PKG.
Automatic tools fail to unpack it
properly and afterwards
automatic deobfuscators fail to
deobfuscate the JavaScript code.
I have already published an
overview-ish video how to deobfuscate
JavaScript code, but this time I will
use another trick that is best described
as a try catch patch for a failing
deobfuscation.
As usual, the sample download is in the
description below,
so you can repeat what I did.
If you have any questions, feel free to
hop onto our Discord server. The
invitation is in the description below.
If you want to learn meta analysis,
check out the course link in the
description below.
So, let's try to analyze this file here.
And
as usual,
I typically run
Detect It Easy.
Um one thing you may have already
noticed is this is pretty huge and the
description says node JS JavaScript
runtime.
And we can see here that this uses the
packer PKG.
So, of course we
Google for an unpacker for this and you
might have come across this unpacker pkg
unpacker.
Uh so this is what I tried first.
Of course
I think the right way to start this is I
usually this way.
Um now the unpacker tells us this
requires an option
minus I.
And to add arguments to npm start you
need to do two dashes here and then you
can say for instance minus help. Now we
get the output of the unpacker here.
And yeah, we want to specify an input
and an output. I think that
might be
nice.
And as
output let's say
Yeah, just the desktop.
So this already tells us hey, we I
detected this entry point
which is C snapshot myth main.js
and myth that's uh the folder of the
that the developer used for this
project. Quite interesting. We got here
our output.
Let's take a look at this.
And usually I try to open the
package.json first because it tells us a
lot about the dependencies and entry
point. However,
this isn't readable.
And the rest doesn't look good either.
So it's just garbage. And it's not
like this is just the wrong encoding or
something like that. It seems to be
compressed.
Okay. of the denseness of these here.
And uh yeah, so it says it detected
Brotli compression. So, I guess we just
need to try to decompress with Brotli,
right?
So, going to say
image and then
Brotli
and pick.
And it says failure, decompression
failed. And if I try others,
unfortunately, it also fails. So, none
of that helps.
So, I thought to myself, "Hey, actually,
it's not that hard to read the um
virtual file system of
PKG
and just write your own extractor. And
maybe also explore
the options in that file system." So, I
used wipe coding for that because the
results are easily verifiable.
And um
let's
show you what I got.
I'm going
clear this up a little bit.
So, okay, let's see what we can do with
with this.
With the interesting flag, we get just
the most interesting data for this. So,
let's check this out, and we can also
see here, okay, we have in uh 6,000
entries in the virtual file system, or
more than 6,000.
And uh this is the entry point, so this
is interesting. There's broadly
compression
and yeah, the payload
position is in the overlay here.
Um
Now, let's extract this stuff, right?
So, we say
extract to
the desktop
and wait a little and it says it
extracted 5,310
Now, we get the same folder that we saw
before, but you already see that these
are bigger. So, these files are not
compressed anymore
and we can
read them.
This worked well.
So, I'm not sure if this is a bug in the
other unpacker. I didn't explore why
that happens, but this works, so let's
just use this tool here.
Take a look at our script here.
And we can see it's obfuscated.
What a lot of people refer to as heavily
obfuscated. So, one of the first things
I usually try is
to use general purpose deobfuscators on
this.
I did that and it didn't work.
Um but we can see
uh which deobfuscator which obfuscator
this is. So, with the
pkgvfs
extract.py
I if you execute this just like that on
the sample, you get all of the file
entries
that it finds in the virtual file
system, but there's also an option where
you specify just the packages.
Like the sole
uh which is a little bit less output and
this is similar to checking the imports
of a sample. It's recommended to do that
for triage just to see what the sample
might be doing, what it's capable of.
And here we can see a few interesting
things and one of them is JS confuser.
So, this is likely the obfuscator that
was used here.
And once you know this obfuscator
you have with the advantage that you can
also figure out if there is a
deobfuscator for this, right?
So, let's check. I need to enable
internet first. Let me do that.
And now
check.
Uh JS confuser deobfuscator
and we find this. This is a
GitHub entry for JS confuser
deobfuscator.
So, let's get this. Let's get copy this
URL.
And we get clone that.
And here.
So, how does this work?
We run
npm install.
We place the obfuscated code in
input.obfuscated.js
and then we run npx tsx index.ts.
Okay.
So, you run this
and uh
So, in this case, I already did that. If
you have any troubles with npm install,
downgrade to Node.js 20. This should
work
with Node.js 20.
So, I mean
Yes.
So, if you do this,
it says we need to install tsx as well.
Now, it says assertion [laughter]
failed.
Just try this again.
But,
okay, it's generally working. I think
there's already an example in this
folder.
So,
uh it's trying to deobfuscate the
example that's in there.
Let's just
remove this and use our main.js.
All right. And now, we run this again.
So, attempting to deobfuscate our
miss
sample,
and it seems it starts out quite well.
And now, it fails.
So, we have progress a little bit.
You can see uh 13 of 55
steps. I'm not sure what this is
exactly, but we have progress a little
bit with this and then there is an
error. Sometimes these errors can be
easily handled if you just add some more
try except code. Sometimes you just need
to ignore
some lines of the code or stuff like
that.
This is
failing in the transform control through
flattening step.
So, I'm going to search what where this
is used.
So, here
it's used in the transform TS. There is
an import for it.
Let's look at this.
This is put into this list of or array
of transforms named all transform. Where
is this used?
And this is used here
for
these of all transforms.
There is a context visitor appended. And
the context visitor does
uh, these steps here.
So, where is the visitor used?
>> This is visitor excheaterus.
So,
what if we just ignore errors and
continue
at this point?
A lot of times when you have an error
like this, I mean, you don't need to
deobfuscate everything perfectly.
Actually, you just want to deobfuscate
most of it such that you get the
interesting data.
And this is why I would just try to skip
parts where the deobfuscator failed. You
don't have to fix every bug that you
come across.
That's not the goal here.
Um
So, we just put a try catch
and try again.
Now, it failed at a different point.
Invalid left-hand side in postfix
operation. So, this could be because of
our changes that we now produce invalid
JavaScript code.
Let's just It's just a guess, actually,
but we have put this in here and the
error changed. Maybe let's also
put this everywhere else where
um
we have these
transformations.
See what happens.
So, now we got a deobfuscated result,
and all I did was just put a bunch of
try-catch around this here.
Um
so
let me see if the result is usable,
though. That was the goal, right?
Right. We need to go
share
And that's it, your obfuscated script.
So, the beginning doesn't look so good.
But, if you go down
This looks actually readable when it
then it comes to the strings.
But, I'm looking for Chrome because we
saw this
uh in virus total there was kill Chrome
process. We see stuff like this here.
So, this is already quite nice. Well, as
a quick fix, you can just replace those
with Notepad.
And here we are.
Now, you can start reading code.
Maybe copy-paste all of this into a
large language model asking where the
interesting
code is
because
this is huge. It's a lot of code.
Has a lot of features probably, but
there are also like these huge string
errors here.
And um yeah, here for instance, you can
see a few emojis
for Discord
uh exfiltration.
And
that we search for webhook.
So, here we find the Discord webhook.
Which is used to exfiltrate the data.
So, that's usually interesting.
And yeah.
For the rest, enjoy exploring this.
>> Mhm.