Video summary
The video focuses on analyzing malware that utilizes Node.js, Electron, and V8 JavaScript bytecode as its primary execution mechanism, often employing these technologies to obfuscate the code from reverse engineers. The presenter begins by examining a sample that appears to be a Minecraft mod but actually functions as a downloader for subsequent malicious stages. By inspecting the file structure and manifest, the analyst identifies the entry points and uses tools like Recaf and 7-Zip to unpack archives containing NSIS scripts and Electron applications. A key aspect of this analysis is understanding how ByteNode compiles JavaScript into bytecode, which serves as a protective layer that makes traditional decompilation difficult and time-consuming compared to standard Java or C++ binaries.
To bypass the need for full decompilation and quickly extract indicators of compromise (IOCs), the presenter demonstrates a dynamic hooking technique using custom JavaScript scripts. The core strategy involves intercepting the `require` function within the Node.js environment to log every module loaded by the malware, thereby revealing its dependencies such as cryptography libraries, file path handlers, and network clients like Axios. This process allows the analyst to infer the malware's behavior—for instance, detecting if it attempts to kill specific processes or steal data from SQLite databases—without needing to understand the entire source code. The presenter also addresses common challenges where malware authors hook standard console logging functions to suppress output, providing solutions that save original function references to ensure logs are still captured even when the environment is tampered with.
The tutorial further explores advanced manipulation of hooked functions to alter the malware's behavior during analysis, such as preventing it from killing critical system processes like Chrome.exe or blocking network calls to command-and-control servers. By creating a reusable script that hooks various Axios methods and other API calls, the analyst can trace communication patterns and identify specific targets, such as Discord stealer functionalities or cryptocurrency wallet theft operations. The video concludes by suggesting that while full decompilation using tools like V8 is possible for detailed reporting, the hooking method presented offers a highly efficient workflow for rapid triage and IOC extraction when time is limited, empowering analysts to flexibly adapt their scripts to investigate specific malicious behaviors without needing deep expertise in JavaScript programming.
Read the full video transcript
Welcome to ME analysis for hedgehogs. A
lot of JavaScript based Melvinware
nowadays is shipped via Node.js,
electron framework and similar. And uh
what we also very often see is a form of
protection where this code is compiled
to byte code for JavaScript. So V8 byte
code usually. Um yeah, and this is this
isn't the default. So this is actually
form of protection that uh the developer
of the malware has to implement. We will
look at a specific malware sample and
how I usually deal with those without
spending too much time on decompilation.
Maybe decompilation will be part of a
future video. I'm not sure yet if I will
do this. No promises. So yeah, have fun.
And as usual, you will find the samples
in sampleedia.cc.
I will put a link in the video
description below. If you haven't
already, also check our discord. And if
you want to learn meta analysis,
consider taking one of my courses at me
analysis forhedgehogs.com.
So as you can see I have here prepared
two files. Um you will see soon why but
generally when the samples are a bit
older the next stage might not be
available anymore if it's reliant on the
infrastructure of the threat actor. So
you can download the files as usual from
sample pedia CC. So if you want to
follow along, please do that. And then
you can repeat everything I do here as
well. Now let's take a look at this
stage here. So let me do a strings
listing
and uh the strings show me that this is
a jar archive. So and why? Because we
have here firstly the typical folder
structure which is used to represent a
package and then we have the
uh class file in here but the most
telltale sign for me is a meter in
manifest mf. So this file will tell Java
where the entry point of the Java
archive is if there is an entry point.
Not all Java archives have one. Um, but
yeah, these ones.
So, I expect to see if I open this that
it starts with PK because that would be
the magic byes for a for an zip archive.
And indeed, that's the case here. Can
see PK.
We can um unpack this with sevenzip.
However, we will be using recaf and
recaf can read it just fine and you
don't need to unpack it. So, let's use
recaf here.
Launch it. And recap is a Java
decompiler and you can put a jar archive
in there.
So let's
open
file and we say we want stage one
and open the workspace.
This is the file here
and the classes.
So what do we see?
This is easy right here because we don't
have that much going on. So there's just
this onecl class file I guess and your
mod. So two class files. However, in the
manifest, it should tell you where
everything starts. But I'm not seeing
much in here. Let me check the mecod
info. Okay, nothing special. I'm a bit
confused that the manifest doesn't
contain more. I think this is supposed
to be a Minecraft mod. I guess they
don't need to be runnable if you double
click on them. So, it's not runnable in
that sense, but it will run if you start
Minecraft, I guess. Check these out.
Now, it's also just named example mod
and just has some stuff in here that
doesn't do much. Let me check your mod.
And that is
yeah pretty much not obiscated at all.
Uh
it runs powershell exe
and runs this command here. So we simply
grab this command and we will decode it.
Let's do the same here. We do
uh plug in B 64D decode
and that's what we get. Let me turn on
word rep.
So that is the URL this downloads the
next stage from. And this is this is
basically all this is doing. So it
downloads this and runs it. It's called
setup exe. Uh puts it into temp as
launcher exe.
I guess to make it seem like this is the
normal behavior of the mod.
So let's take a look at stage two. Do
the same thing. Run the strings listing
here.
And
let's take a look. So this looks more
like a port of executable file, right?
This is a little bit bigger. Let me just
check.
It says 74 megabytes.
So quite a lot of strings. So I'm just
roughly scrolling through. But yeah,
these all don't seem to be actual
strings. They are likely high entropy
areas. So let me run pot analyzer on
that. Do I have it here? Yeah.
And uh yeah, it says it's a NSIS script.
So if you look at the visualization,
yeah, this is all of that is packed uh
stuff in the NSIS application. So that's
why we didn't see much here. Uh this
small little part, that's the actual
code of the NSIS stuff. And this huge
part here is what's packed inside of
this. So yeah, let's simply unpack this
with sevenzip, I guess.
So what do we see here? We have app uh
64.77z.
Um yeah, so this is likely JavaScript
that we will find in here. Let's also
just extract this.
And yeah, looks like it. This is a an
electron application.
So what's electron? That's a framework
which uh for JavaScript which is used to
create desktop applications but with a
Chromium browser. So will look like a
desktop application but underneath it is
a browser that just runs your stuff. So
um
yeah uh so I should probably explain why
I clicked on resources. So the thing is
in in such an application the the
launcher isn't usually the interesting
part. It's where the user clicks on. But
all that this does or is responsible for
is to run the electron environment and
eventually run the actual Java script
code. And this code is inside an Azar
archive. And we can see here these
files. Uh sometimes I've I've seen some
blog posts mentioning that there is this
elevate.exec which is very suspicious
probably used for elevation stuff. But
this is always shipped with these kinds
of applications. It's not unusual. So
even in legitimate applications, you
will see it in there. So we have this
app as unpacked. But that doesn't mean
that this is this file unpacked. Okay.
So we see here note modules and note
modules generally contains some
libraries that the application uses. Uh
this is a little bit weird. This library
has a weird name.
Interesting.
Uh yeah, but we need to unpack app Azar.
And for that, I'm not sure if sevenzip
can do this again. Uh you you can have a
plugin for Zvenzip. I think that does
this. Uh I generally use binary refinery
by now or what we can also use is uh the
builtin electron azar modules for
NodeJS.
So let's just do that.
So we just tell it to use it
and we put it to unpacked. Also, I think
I need internet for that. In case it
doesn't have that, let's
just turn it on. Okay.
Seems to be hanging.
Let me try something else. Um,
it's not supposed to take that long.
Okay. Uh, let me use
npm
We install the electronizer pack package
globally and now it added eight packages
and now we use it directly. Okay, we say
extract app Azar to unpack
and that was way better. So turning off
the internet.
Yeah. Uh so now we get the unpackaged
files
and we can take a look at this. So this
is already very interesting now right
from the start. What we see here is
app.protected.jsc
and an index.js.
Let's take a look at the package. JSON
what it runs. Oh, why does it have to
install? Uh, should have opened it in
Notepad
because this takes always longer. Um, so
this runs index.js as the main close.
So,
um, index.js is the first thing being
executed and yeah, that's what I
expected. So, um, this uses byte node.
Byte node is a compiler. It compiles the
JavaScript code to byte code and this
byte code is the only thing that you as
a reverse engineer see. So it's a way to
protect the code from being reverse
engineered because decompiling it isn't
as easy as it is for Java for instance.
Yeah, that's what the mware authors also
use of course so that you can't see
their code and have difficult time also
to create signatures on that code.
Internally, bite node uses some tricks
to accomplish this so that the original
JavaScript code doesn't have to be
shipped with the electron binary. And
how that works was a part of the last
video that I published before this one
um where I explained the details of the
V8 engine and of bite node protection.
Let's figure out how we can analyze the
sample. So in most of the cases in a
daily work of a me analyst you actually
don't need to decompile this code and
this is also not what we will be doing
today. We there is a possibility to do
that using Vue8 but it takes a lot of
time. So we will not do that and instead
create a reusable script where you can
just run and trace what the application
does and then get out all of the values
that you need like next stages or some
discord hooks or uh well just see what
it generally executes. So let's simply
create a JavaScript file which we use to
hook the application and let's start we
just print something so that we see some
output
hooking
malware something like that.
Save it as JS or
JS.
And what we also need to do is to add
the passes to the module.
Um we need to tell it where all of the
required libraries are that the malware
ships with. So these uh node modules
right here.
So we will be using unshift.
unshift puts everything to the front of
the path uh array or list. Why do we
want it at the front? So that we make
sure it searches there first. So do this
and we
also need to add the other ones that we
saw. So we have this node modules in the
unpacked app aza but we also saw this
app a unpacked which contains different
node modules. We also need to add these
and we should
there anything missing? I think there
was one in resources. No there isn't.
Okay. So this is enough but we also need
to replace
uh we use normal we replace those with
double backslashes.
Yeah that should work fine.
And what I do at the start or what we
should do at the start is we make a hook
on the require
thing uh require loads libraries modules
and I'm not so uh sure about the correct
JavaScript terminology in these but for
me these are like libraries reusable
code like these node module thingies. um
require is what loads them. If we hook
it, we can see everything that the
sample actually uses. Now, how does
hooking actually work? Hooking works
that we say uh we have an original
well we keep an original copy
of function
XY Z or whatever
and then uh modify the original with
hook
and Then
in original copy
we
wait in original call original copy. Um
so that the original function is still
executing because we need require to
actually run so that the sample still
works and that's what we will do here as
well. So how does this work? Here we
have uh
module instructor proto
type require.
This will get us the original require.
And to modify this
we say well let's copy paste this
is um
this anonymous function which has ID as
a parameter.
So here we assign another function for
that and yeah this is the definition of
the function. It doesn't have a name
here in this part of the code. So, it's
this. And we say
require
and we add here the caller which should
be
we add the ID
when we add the caller the caller
is uh this file name. Yeah.
Let's test this. So, how do we run this
on the mware sample?
We use the launcher that this
application ships with. So, we need this
uh where is it? Here. This launcher.
We use this launcher to run our hook
code.
Oh, I put it in here. Let me just save
it to the desktop
so I can find it more easily. Yeah.
Okay. So, this is what we will run. Uh,
and I forgot that we also need to call
the sample from here, of course. So the
way you do this is we say firstly we say
require bite node because this is what
we need what the sample needs to run the
protected
JSC
and
we require the location of this
protected JSC
that should
be this
only in selection.
Yeah.
Oh my. Okay.
So
we require our protected JSC and by uh
using require it will call this
with bite note. So and then we say
we are done
just so we know when it's finished. Now
let's call our hook.
It's hanging there in a loop. Nothing
seems to happen.
That's interesting.
Oh, I I know why because we forgot this
part. Uh call
the original copy, right? So the require
doesn't work. Uh
because we also
need to do this.
This will just return the well original
function
result. So we call the original require
with the arguments that it was given.
And now I hope we see something. Let's
go.
and we don't there is a variable that
you need to set so that you can run an
electron application as nodejs.
So this is
uh we set it here in PowerShell. We say
and
electron run as node and we say one and
this will make it possible to run this
as NodeJS and then we see the output on
the terminal. So let me run this again
here
and we can see that it works. So we see
the require and then we see okay this is
the file that called this
right. So we have here a lot of times
require and then node and then we see
the caller is some byte node or yeah
it's here's it's bite node bite node uh
uh this is all library stuff we are not
interested in and for the actual malware
we don't see any require at all uh which
is odd because the malware should use
something right Everything that we see
here is what we also use with require
byte node call right here. Uh but here
nothing
that's that's really odd. So let me do
two things right here.
Uh the first thing is that we do not use
anything which contains node modules in
the path. So when we have node modules
in the path this one here I actually
don't want to see require output. Why
should I? Um so we say we say if not
this file name
includes
node modules right and then
and only then we will log
that there was a require and I'm
assuming if it doesn't contain that it's
not a library it's the actual malware
that required something and now this is
likely empty. Um, this is a bit messed
up too. Yeah. So, yeah, almost empty,
right? We have our hookjs that's our own
sambber
and we have the um protected JSC
and that's it. So uh from hookjs the
most common reason for this is that the
malware also uses hooking so that
console log doesn't work anymore. That's
very common and in general if you write
such a script we should probably add
some protection for that. How do we do
that? So one thing you could do is you
could log everything in a file instead.
That might work. There are various
methods write to file. Maybe some of
them aren't hooked by the malware. Uh
but what we also can do is we can just
get a copy of the original code. Since
we are the first ones to run, we can
also save the original function of
console log. And if they use hooking, we
just use the original one that we
already saved to do the logging.
[snorts] And yeah, let's try that. It's
quite simple. Same way we hooked here.
We do this uh and save our uh original
log
as
consoli log.
And what we do here with the bind is
that we tell it. So when when the log is
called it needs to know where it came
from that it came from console and this
way we basically tell it to use console
function log
here's the message we want to log this
will be the function we will be using
and all this does is original log
message. So it calls that
and we replace here require with our log
output.
And do we have anything else? Maybe we
should do this as well here since that's
also a sign, right? We didn't see this.
We should have seen this, but maybe it
ran in a loop. And that might be also a
reason that it somehow detects it's
running on the VM and then just runs in
a loop. uh doesn't do that. Uh doesn't
really finish. So, let's let's uh try
this again.
And now we see more. So, this actually
worked. Uh I'm now a little bit So,
yeah. Um stop this. Um
let's remove this part. Let me think.
Yeah, let's just remove the collar. It's
not that necessary anymore. So
and
do that. And now we get a list of all
the required modules that it uses. We
get an idea of what it might be doing.
So uh it it uses do does some
cryptography.
It uh uses the paths to access files and
stuff. Um so SQL Lite 3 is something you
often see with stealers as they will
access uh browsers and their SQLite
databases and yeah some some archiving.
Axios is for get and post requests
likely and of course child or process is
used to to create a process like run
commands for instance with exc. So these
are quite interesting here. It runs in a
loop.
Okay, it's already stopped in PH is a
bit weird with this. Um maybe easier to
use cmd for that.
My friend do this.
So yeah, I'm in cmd. But does this
change anything? I'm not sure.
launcher
pokejs
and yeah cmd is working better.
Uh clear screen. Yeah. Okay. Now we are
in cmd. Uh if we continue with this
approach now we could try and guess what
the sample does next based on the
require calls. So we had seen there was
for instance child process. So maybe it
uses exec to call something and we can
just see something if we hook this
function. So let's do this just to see
how this generally works. Uh
yeah. So
we save child process
here under CP we say
original
exec
is sh process
exec
and uh now say
that this is a new function
So it has bar ads. We represent these
like this.
And
we now log
exec call
and
print
cmd
which is the cmd command. And that's
executed and we return the
original exec.
So
that it calls the original one. Well,
that depends if you actually want to
call it, but yeah, let's see what
happens here.
And uh yeah, that's that's quite
interesting. So this execute task call
on Chrome exe. Let's try this. I'm going
to run Chrome.exe. See if the sample
actually does this.
Yeah. And it does. So what we can do now
is we can adjust this behavior. We could
for instance well let's say not return
the arguments. What happens if we just
say the arguments are null
right?
Do we still see that it's killing
Chrome
and this time we see that it exacts task
on Chrome but now it hangs here.
So, uh, Chrome isn't killed, although it
says so because we do not provide the
cmd command for the execution. And here
it leads to some uh unwanted behavior
from the malware because it likely
expects some kind of response. Yeah, we
could decide to, you know, check cmd if
it contains tasker and then just not do
it because it seems here with task list
it actually needs this to properly work.
Experiment a little bit with it. Let's
say
if uh cmd include this
task
kill
then we don't want this to work.
We say return with null
and otherwise we run this normally and
then it receives the task list results.
See? Yeah. So, this now looks like it
did before, right? Can now still use
Chrome.
Maybe next time we have a sample that
interferes with our reversing tools. Uh,
we could also just rename the tool that
would work as well. But here,
this now doesn't kill our Chrome
anymore. And we have successfully hooked
this. Yeah, let's do one more. I think
one more that might be interesting
and that is uh the uh axios
which is used
probably to contact some form of
C2 server.
And we don't exactly know which function
it uses, get or post. Let's just put
several of the axios functions in here.
Or it might use put, right? So this is a
list of the functions I want to hook and
I say for each
we have
uh the argument here
and
we
define this function.
We access the array
like this. So this will access the
current function that we are using. Um
like if it was get it would be a get or
if it was put it would be you know aios
put for instance.
And
now we replace the function
with URL
function URL
and we just say log and might be a good
idea to know what kind of function was
called. So we just put m
in here.
Wait, without the quotes, of course,
we log this and
we log the URL itself.
So, and then we return
original X applied
this arguments. I don't have internet at
all here right now. So this will not do
anything.
Run our hook and we get this. So that's
another IOC that you can use to research
more about this sample. Before the era
of large language models, this is how I
did this. I just had a big script which
hooked all kinds of functions and then
did this because I'm not a JavaScript
programmer. So my willingness to learn
JavaScript is also pretty low because I
only use this for analysis of these
samps here that use JavaScript. So it's
it's not a language I generally prefer
to use or things like that. So I wasn't
that willing to know or to to learn how
reflection works. But there is of course
a way to do the same stuff without
having to guess which functions it uses
and that is by using reflection. And the
only way I um created a script like that
is with a large language model. Then you
get it back uh may not necessarily
understand everything that it wrote
there. So that possibility exists too
just to say this is how I did it so far.
Give you a minimal example for that.
Yeah, that looks like it. So this was
clanker generated.
Uh but what it does is uh you have here
at the beginning the config where you
put all of the modules inside. So the
basically the module path we also have.
So let's do the same
here.
Did you know how this works? So we copy
and paste these passes in here.
And um
yeah, of course the app protected path.
That's the one we have here.
And uh where the log should be. So this
puts everything into a log file
and we run this generic hook and that's
what we get. So here you can see
what what's happening. So it's here
hanging because it expects something
from this um def x. So it expects to
download something from there which
isn't happening. So what you could do is
run inets sim and then it gets something
back and probably responds or we can
like uh create a response by hooking
this function here. So, but what you
also see directly is, oh yeah, uh this
is accessing discord uh local state
somehow. So, it's probably like a
discord stealer. Um we see other
functions that are typical for stealing
like exodus wallet. So, it's stealing
crypto wallets. Um putting them in a zip
archive event. Then this is very
interesting. So seems to be um galaxy
stealer based on this string here and it
has some kind of licensing information
which it sends to the server here. So uh
that's
I think maybe related to Roblox I'm not
sure. So, and then it builds with path
join. It builds paths to cookies and
uh some some profile data and then it
does some
stats sync call. I'm not sure what stats
sync does, but yeah, here you see the
full process eventually. Um yeah, so
it's pretty clear that this is a stealer
where it connects to and what it does.
And that's just from this um script. I
hope you learned something. We'll put
both scripts into
the video description below. So you can
copy paste them or like if anything goes
wrong just copy paste them and use them.
So but my hopes is just generally you
understand how this works this whole
hooking process. So you can adjust it
for your own purposes and like and then
also hook functions and modify them so
that the mail does what you want right.
So that's the whole purpose of this just
copy pasting the script doesn't do stuff
for you you know but this then you are
flexible and you can do what you want.
So this script that we created or that
you can well built to get your very own
output it's very very useful but of
course it doesn't help for all use
cases. So when it's useful is when you
don't have much time you need to
determine if a sample is malicious and
you want to extract indicators of
compromise. That's enough. Uh if you
actually want to write an article though
like a blog article and a detailed
report how this thing works where
everything needs to be very specific and
in detail uh then you may actually want
to decompile this script and to do that
you can use Vue 8. But how to do that
that might be a topic for a different
video. No promises though.