GachiLoader: Defeating Node.js Malware with API Tracing

samanthar@checkpoint.com
Wed, 17 Dec 2025 06:12:46 -0800

Check Point Research exposes GachiLoader, a Node.js loader in the YouTube Ghost Network, and shows how API tracing defeats its obfuscation.

Research by: **Sven Rath** ( **@eversinc33**), Jaromír Hořejší ( **@JaromirHorejsi**) ## Key Points - The YouTube Ghost Network is a malware distribution network that uses compromised accounts to promote malicious videos and spread malware, such as infostealers. - One of the observed campaigns uses a new, heavily obfuscated loader malware written in Node.js, which we call _GachiLoader_. - To make it easier to analyze obfuscated Node.js malware, Check Point Research developed an open-source Node.js tracer, which significantly reduces the effort needed to analyze this type of malware and extract configurations. - One variant of _GachiLoader_ deploys a second stage malware, _Kidkadi_, that implements a novel technique for Portable Executable (PE) injection. This technique loads a legitimate DLL and abuses Vectored Exception Handling to replace it on-the-fly with a malicious payload. ## Introduction In a previous publication, we examined the YouTube Ghost Network, a coordinated collection of compromised accounts that abuse the platform to promote malware. In our current research, we analyze one specific campaign of this network, which stood out as the deployed malware implements a previously undocumented PE injection method which abuses Vectored Exception Handling to load its malicious payload. ## Campaign Overview Similar to campaigns we previously documented, the infection chain begins with compromised accounts that host videos designed to lure viewers into downloading malware from an external file hosting platform. The theme of this campaign are game cheats and various cracked software: The video’s descriptions then provide a password for the archive containing the malware, as well as instructions that usually include disabling Windows Defender. We identified **more than a hundred videos** belonging to this campaign, which collected approximately **220.000 views**. The videos were spread across **39 compromised accounts**, with the **first video uploaded on December 22, 2024**. This means that this campaign has been running for more than 9 months. After we reported these videos to YouTube, most have been taken offline, although new videos will continue to appear on newly compromised accounts. Since we started monitoring this specific campaign, it deployed the Rhadamanthys infostealer as a final payload, which is distributed through a custom loader which we call _GachiLoader_. ## GachiLoader _GachiLoader_ is a heavily obfuscated Node.js JavaScript malware used to deploy additional payloads to an infected machine. Node.js is one of a long line of threat actors always adapt their arsenal using non-traditional programming languages and platforms adapted by threat actors in their quest to spread malware. As obfuscated JavaScript requires a lot of time and effort to manually deobfuscate, we developed a **tracer for Node.js scripts** to dynamically analyze this type of malware, defeat common anti-analysis tricks and significantly reduce the manual analysis effort. This tool is not only useful for _GachiLoader_, but is useful for anyone analyzing heavily obfuscated Node.js malware. Therefore, we decided to share it with the research community here: Some of the analyzed _GachiLoader_ samples drop a second-stage loader, which we call _Kidkadi._ This loader is particularly interesting, as it implements a **novel technique for PE injection**, which tricks the Windows loader into loading a malicious PE from memory instead of a legitimate DLL. We analyzed this technique, which we call **Vectored Overloading** and reimplemented it in a Proof-of-Concept (PoC) shared below. ### Technical Analysis _GachiLoader_’s JavaScript module is bundled into a self-contained executable, using the `nexe` packer, with sizes roughly between 60 and 90 MB. `nexe` is an open-source project, that compiles a Node.js application into a single executable file, bundled with a Node.js runtime, so that the file can run on a host without Node.js installed. While the size of the executable is quite big, it isn’t suspicious as the victim expects to receive a software package. The tool nexe\_unpacker can be used to extract the obfuscated JavaScript source code from the PE. ### Anti-Analysis Features To avoid analysis by a security researcher or an automated sandbox, the _GachiLoader_ JavaScript module employs several anti-VM and anti-analysis checks: - Checks if the total amount of RAM is at least 4GB - Checks if at least 2 CPU cores are available - Compares the username against a list of usernames, that can be associated with various sandboxes or analysis systems (see Appendix A for a list of all names). - Checks the hostname against a similar list of hostnames (see Appendix B for a list of all hostnames). - Probes the running programs and compares against a list of programs, such as analysis tools, sandbox indicators or common programs running on VMs (see Appendix C for a list of all process names). The malware then proceeds to run several PowerShell commands to enumerate the system resources and capabilities over `WMI` . - Check if at least one port connector object exists: `(Get-WmiObject Win32_PortConnector).Count` - Get drive manufacturers and compare against a blacklist: `Get-WmiObject Win32_DiskDrive | Select-Object -ExpandProperty Model` (See Appendix D for a list of all drive manufacturers). - Resolve video controllers via `Get-WmiObject Win32_VideoController | Select-Object -ExpandProperty Name`, and check the names against a blacklist associated with VM environments (See Appendix E for a list of all video controller names). If any of these checks indicate a virtual machine, sandbox or analysis environment the malware enters a loop of sending `HTTP GET` requests to benign websites such as `linkedin.com`, `grok.com`, `whatsapp.com` or `twitter.com` : Finally, to avoid running multiple times in a short period of time, a mutex file with a random-per-sample name and the `.lock` extension is created in the `%TEMP%` directory on running for the first time. If this file already exists or was modified within the last 5 minutes, the program terminates. We were able to easily bypass all of these anti-analysis with Node.js Tracer: the tool hooks into the respective methods and spoofs the results to the caller, in this case the malware, allowing the script to run and expose its malicious actions: ### Privilege Elevation via UAC Prompt If the malware decides that the environment is not that a sandbox, it then checks if it is running in an elevated context by running `net session` , a command that is expected to fail if run by a non-administrative user. If the command fails, the malware tries to restart itself in an elevated context using the following PowerShell command: `powershell -WindowStyle Hidden -Command "Start-Process cmd.exe -Verb RunAs -WindowStyle Hidden -ArgumentList '/c \"\"'"` While this triggers a UAC prompt, that prompt is likely to be accepted by the victim, as they expect to run an installer for some sort of software, which usually requires administrative privileges. ### Defense Evasion To avoid detections of subsequent payloads, the malware attempts to kill Windows Defender’s `SecHealthUI.exe` process by running `taskkill /F /IM SecHealthUI.exe` and adds Defender exclusions via `Add-MpPreference -ExclusionPath` for the following paths: - `C:\Users\` - `C:\ProgramData\` - `C:\Windows\` - For all other existing drives, at the root (e.g. `D:\` ) In addition, an exclusion for `*.sys` files is added via `Add-MpPreference -ExclusionExtension '.sys'`, although we have not observed any `*.sys` files being dropped by the analyzed samples. ## Payload Delivery and Execution To retrieve the next stage’s payload, the malware comes in two variants. - One variant gets the payload from a remote URL - The other variant drops another loader, `kidkadi.node`, which loads the final payload using the Vectored Overloading method. This payload is embedded in the loader’s JavaScript source. ### First Variant – Remote Payload _GachiLoader_ first obtains information about the host it is running on, such as antivirus products and the OS version, and sends them via a `POST` request to the `/log` endpoint of its C2 (Command and Control) addresses. The samples all have multiple C2 addresses embedded for redundancy and try out each one in succession, as we saw when tracing the calls through our tracer: Next, a `GET` request to the `/richfamily/` endpoint (where `` is a value unique to each sample) with the `X-Secret: gachifamily` header gets the URL of the final payload to download, encoded in Base64. This final payload can only be retrieved if using the correct `X-Secret` header again – this time using a unique key embedded in the binary, e.g. `X-Secret: 5FZQY1gYj0UKw4ZC99d1oNYR8LvTPtrfN357Eh5gmRvsMaPYgXtMxRXpMb2bTFOb2h2HqMnvUKT9CUpj9864gckmPUzf9uLIIU9`. Otherwise, the web server returns a `Forbidden` error. The final payload is then downloaded to the `%TEMP%` directory and saved with a random name, mimicking legitimate software such as `KeePass.exe`, `GoogleDrive.exe` , `UnrealEngine.exe` or others which contain the Rhadamanthys infostealer, packed and protected with VMProtect or Themida. ### Second Variant – Kidkadi The second variant we observed in the wild did not reach out to a C2 server to get the second payload, but instead had an embedded payload which is executed through another loader that is dropped to disk under `%TEMP%` as `kidkadi.node`: `.node` files are native addons for Node.js, which are essentially just DLLs that can be called from Node.js code via `dlopen`. Therefore, they can be used by developers whenever the Node-API does not expose sufficient functionality. The malware exposes a function for Node.js to call, where the name of the method differs across samples. In some cases, the name as well as the error messages in some samples are of Russian origin: The loader passes the payload PE as a binary buffer to _Kidkadi_ through this exposed function, which then runs this payload via reflective PE loading. We found that this loader uses a novel spin on Module Overloading, abusing Vectored Exception Handlers (VEHs) to trick the Windows operating system to run the final payload, when invoking `LoadLibrary` to load an arbitrary DLL. This technique, not yet documented, shows that the author has a decent understanding of Windows internals. We named this method _Vectored Overloading_. ### PE Loading via Vectored Overloading The malware first creates a new section with `SEC_IMAGE` from the legitimate `wmp.dll`, a DLL used by Windows Media Player. It then overwrites this section with the content of the payload (the PE to be loaded) and maps a view of that section into the process via `NtMapViewOfSection`. The PE’s sections are then copied into memory one by one and relocations as well as the correct protections are applied: This results in a view of the malicious PE, mapped to the process, which is backed by the legitimate DLL `wmp.dll`. This section view is what the Windows loader (meaning `ntdll!Ldr*`) will be tricked into loading later on. Since the Windows loader, called via `LoadLibrary`, does not load arbitrary PEs, but only those that have DLL characteristics, the `Characteristics` of the `FileHeader` are set to `IMAGE_FILE_DLL` , if the payload is not a DLL. Additionally, the entry point is zeroed out, likely to avoid the loader calling an entry point that is not that of a DLL. If the payload is a DLL, the header is not changed. Afterwards, the malware registers a Vectored Exception Handler (VEH). VEHs are user-mode callbacks that are invoked by the OS when an exception occurs. A common malware technique abusing VEHs is to register a hardware breakpoint on a specific instruction, which triggers an exception whenever this instruction is reached. This exception is then handled by the VEH, which can intercept the call and, for example, change the parameters. This essentially allows hooking functions without patching memory, such as when using classic trampoline hooks. In this case, the hardware breakpoint (HWBP) is set on `NtOpenSection` : The malware then loads `amsi.dll` via `LoadLibrary` , which kicks off the injection: A call to `LoadLibrary` internally ends up in the Windows loader creating a section object of the target DLL to load, which is opened through a call to `NtOpenSection` . This triggers the hardware breakpoint, and subsequently the VEH, which were registered earlier. This is where the main injection logic is implemented. To make the loader map the malicious PE instead of the actual `amsi.dll` section, the section object pointing to `amsi.dll` is swapped with the malicious payload section from earlier. The VEH simply places the section handle created earlier on the stack position that corresponds to the `[out] PHANDLE SectionHandle` argument of `NtOpenSection`. The VEH then advances the instruction pointer `eip` to the `ret` instruction and resumes execution. This skips the actual call to the kernel while still giving back a valid handle, essentially emulating `NtOpenSection`: Before stepping out of the VEH, the hardware breakpoint is re-set to `NtMapViewOfSection.` `NtMapViewOfSection` is then used by the Windows loader to map the section into the process, which again triggers the hardware breakpoint. To make sure the malicious payload is mapped, the syscall is again emulated by advancing the instruction pointer and replacing the `[out]` arguments with the relevant values, such as the section base address or the section size. This is possible, because the section view was mapped by the malware earlier, when the malicious payload was written into the view of `wmp.dll`: A final hardware breakpoint is then set on `NtClose` , where the malware simply verifies that the correct section handle is closed. Back in the regular flow of the program, outside the VEH, the entry point will be invoked if the payload is a regular PE. If it is a DLL, the loader expects it to be another `.node` module and resolves the correct exports to invoke: Completely unrelated to this campaign, we found a file with an original filename of `HookPE.exe`, which is a 64-bit PoC version of the technique with debug prints that uses the technique to load `calc.exe` into memory. Error strings in this binary indicate that the loader uses code from libpeconv for PE manipulation. This injection technique has multiple advantages over “classic” RunPE-style reflective loading: - Just like when using the Module Overloading technique, the injected DLL will show up as backed by a legitimate image (such as `wmp.dll`), since the section was originally created for this DLL. However, since the code in memory will differ from the code on disk, tools such as Moneta are able to detect it: - Some loader work is offloaded to the Windows loader. This significantly reduces complexity for the malware author as they do not have to implement e.g. resolving imports or TLS callbacks, which in turn increases payload compatibility. For example, many publicly available PE loaders do not properly handle TLS callbacks. - By emulating syscalls, the respective kernel side callbacks such as ETWti are not invoked, as the call to the kernel is skipped entirely. This may fool security solutions that rely only on these section ETWti events. Of course, the earlier calls before the injection (when mapping the image) still trigger those events, but not in the order usually expected. We published a reimplementation of the 64bit variant of this injection method as a tool for security researchers to analyze the technique and test detections: As deobfuscation of the JavaScript source is a tedious and partially manual process, we decided to run all available samples of _GachiLoader_ through Node.js Tracer to bypass the anti-analysis checks and receive the final payloads. By hooking filesystem-related Node APIs, the downloaded files are saved for the analyst before they can be deleted by the malware trying to remove its traces. The final payloads of both variants of _GachiLoader_ were all packed and protected by Themida or VMProtect. Dumping the unprotected configuration from memory when running them in an automated sandbox then allowed us to extract the C2 servers of the final payloads. All the analyzed samples that were part of this campaign dropped Rhadamanthys as the final malware. The extracted C2 servers can be found in the IoC section below. ## Conclusion Malware written for the Node.js platform has become increasingly common and is mostly found in obfuscated form, which is tedious to statically deobfuscate and analyze. By enabling analysts to trace and hook Node-API execution dynamically with our open source Node.js Tracer, the time that has to be spent on triage and analysis is significantly reduced, and common anti-analysis checks can easily be defeated. The threat actor behind _GachiLoader_ demonstrated proficiency with Windows internals, coming up with a new variation of a known technique. This highlights the need for security researchers to stay up-to-date with malware techniques such as PE injections and to proactively look for new ways in which malware authors try to evade detections. The threat actors behind the YouTube Ghost Network exploit the trust in the YouTube platform to trick victims into downloading malware. Users should be particularly cautious of offers for cracked software, cracks, trainers, or cheats, as these files are frequently laced with malware designed to steal data and/or compromise a device. While both the security community and YouTube actively work to identify and remove such content, these attacks remain persistent. ## Protections Check Point Threat Emulation and Harmony Endpoint provide comprehensive coverage of attack tactics, filetypes, and operating systems, and protect against the attacks and threats described in this report. ## Indicators of Compromise ## Appendix A – Username Blocklist ## Appendix B – Hostname Blocklist ## Appendix C – Process Blocklist ## Appendix D – Drive Manufacturer Blocklist ## Appendix E – Video Controller Blocklist