Submind YouTube summaries
Thumbnail for Malware Analysis - Deobfuscating NodeJs pkg packed stealer MythJs

Malware Analysis - Deobfuscating NodeJs pkg packed stealer MythJs

Watch on YouTube

Video 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.