Node is a loader
文章探讨了Node.js支持C++插件及其作为DLL加载器的应用,利用node-gyp构建插件,并通过Zig语言实现示例代码。 2025-3-7 19:36:32 Author: www.atredis.com(查看原文) 阅读量:2 收藏

Node.js supports C++ addons(may be referred to as native modules). They allow you to extend your module functionality using a shared object.

Addons are dynamically-linked shared objects written in C++. The require() function can load addons as ordinary Node.js modules. Addons provide an interface between JavaScript and C/C++ libraries.

The hello world is straightforward, and there is a build tool node-gyp that can be used to build these easily.

What is a loader?

There are various definitions, and at some point, the lines between them begin to blur. To be succinct, a loader is a type of malware or technique that optionally downloads and executes an additional payload. In this case, we will use Node.js as a DLL loader.

This technique has likely been used by threat actors in the wild.

Why Node.js?

Drivers and applications have shipped with node.exe (macOS and Linux are also targets) to run their user interfaces for some time. Node.js has been bundled with graphics drivers, mouse drivers, photo editing software, AV and EDR products, and is also installed as part of Visual Studio. As we will eventually see, Electron is also a target. The node.exe binary is likely to be allowlisted and is signed. From the perspective of a loader, this is beneficial for bypassing AV/EDR. Even if we can't find it already installed on a target system, the binary can be shipped as part of the initial stage, though Node.js is becoming increasingly large.

The well-known symbol

If we have a DLL, we can use Node.js to load it and execute code. Using node-gyp with C++ to build a DLL is straightforward. However, since we only need to support loading and registration, we can use the well-known symbol napi_register_module_v1. This approach is possible with C, Go, Rust, etc.

With Node.js on the system, it is also possible to just write malicious code using JavaScript. And if you minify and obfuscate it, it can be very difficult to triage behavior. It is still, however, human readable. Porting a  C2 agent over to JavaScript may be difficult depending on complexity.

Building with Zig

I have been interested in Zig for some time. It is not necessary here, but it was a good excuse to play around with the ecosystem. In fact, this post started as me just learning how to do this with Zig. The language is young and not yet at version 1.0, but I appreciate the build system. As John Carmack said about Rust, it "feels wholesome."

To follow along, install Zig first. I built this on a Windows system, but cross-compiling should also work.

Project Initialization

Create a new directory and initialize the project:

const std = @import("std");
const windows = std.os.windows;

extern "user32" fn MessageBoxA(hWnd: ?windows.HANDLE, lpText: ?windows.LPCSTR, lpCaption: ?windows.LPCSTR, uType: windows.UINT) c_int;

pub export fn napi_register_module_v1(
    _: *anyopaque,
    exports: *anyopaque,
) *anyopaque {
    _ = MessageBoxA(null, "From Zig from Node!", "Node and Zig", 0);
    return exports;
}

文章来源: https://www.atredis.com/blog/2025/3/7/node-is-a-loader
如有侵权请联系:admin#unsafe.sh