<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[UNK_DeadDrop]]></title><description><![CDATA[Six technical articles reconstructing a fake-recruitment campaign against developers: a malicious VS Code extension, a GitLab dead drop and tested YARA rules.]]></description><link>https://unk-deaddrop.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ae7ec33f80852d1e222effb/1c72e073-2280-428a-8b31-b888ae27eabd.png</url><title>UNK_DeadDrop</title><link>https://unk-deaddrop.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 12 Nov 2026 08:19:21 GMT</lastBuildDate><atom:link href="https://unk-deaddrop.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Where Overlord Ends and the Collection Toolchain Begins]]></title><description><![CDATA[Overlord build metadata, browser-store selectors, wallet-upload markers and capture components in the Linux agent.]]></description><link>https://unk-deaddrop.hashnode.dev/framework-and-collection-boundary</link><guid isPermaLink="true">https://unk-deaddrop.hashnode.dev/framework-and-collection-boundary</guid><category><![CDATA[Security]]></category><category><![CDATA[Malware]]></category><category><![CDATA[threat intelligence]]></category><category><![CDATA[golang]]></category><category><![CDATA[cybersecurity]]></category><dc:creator><![CDATA[Ilja Krasnjanski]]></dc:creator><pubDate>Mon, 02 Nov 2026 09:33:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae7ec33f80852d1e222effb/d7e166de-70a2-479b-83be-98fd559dacd1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The Linux Go agent embeds the module path <code>overlord-client/cmd/agent</code>, a WebAssembly runtime and SQLite dependencies, and its strings include browser database names, wallet-upload configuration and capture-related symbols; <a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E18.pdf">E18</a> documents the bounded comparison with selected upstream files, while <a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E05.pdf">E05</a> records the sample-specific capture findings. Those components connect a general remote-access framework to the collection functions found in this sample.</p>
<h2>The Go build records an Overlord module path</h2>
<p>The Linux amd64 artifact is 15,790,242 bytes and has this SHA-256:</p>
<pre><code class="language-text">ef183bbb40f4c7c3f4b52994cdba3295f3eab2604fdad99eefa3b67b8d966e8d
</code></pre>
<p>I read these fields from its Go build information:</p>
<pre><code class="language-text">path                 overlord-client/cmd/agent
module               overlord-client (devel)
wazero               v1.7.0
modernc.org/sqlite    v1.34.5
CGO_ENABLED          0
GOARCH               amd64
GOOS                 linux
vcs.revision         056221e342357056c8539585806f76632660e414
vcs.time             2026-07-21T01:14:06Z
</code></pre>
<p>The module path connects the binary to the Overlord agent codebase, the <code>wazero</code> dependency supplies a WebAssembly runtime (consistent with the framework's plugin support), and SQLite provides local database functionality, which becomes relevant alongside the browser-store markers elsewhere in the file.</p>
<p>The <a href="https://go.dev/pkg/runtime/debug/?m=old">Go build-setting documentation</a> defines <code>vcs.revision</code> as the checkout revision and <code>vcs.time</code> as its associated modification time. The timestamp is version-control metadata. The binary's compilation time remains unverified. The recorded repository lookup did not locate the embedded revision, leaving the corresponding source checkout unresolved.</p>
<img src="https://krasforge.github.io/UNK_DeadDrop/diagrams/12-framework-boundary.png" alt="Overlord framework and collection components" style="display:block;margin:0 auto" />

<p><em>Figure 1. The agent combines Overlord framework markers with browser, wallet and capture-related components.</em></p>
<h3>The upstream files shared generic framework features</h3>
<p>The July comparison inspected selected client module and configuration files; it found the shared module path, plugin support and generic transport configuration, and it also recorded a newer wazero version in the examined upstream module file than the sample's v1.7.0. The dependency versions differ. Dating the fork or deployment requires additional records.</p>
<p>SQLite-related dependencies and wallet/browser markers were absent from the selected upstream files, and those files were not retained at a pinned revision, preventing a reproducible source diff; other versions, forks and plugins were not exhaustively compared. The authorship of these components remains unresolved.</p>
<h2>Browser stores, wallet selectors, and capture symbols</h2>
<p>String examinations found <code>Login Data</code>, <code>Local State</code>, <code>logins.json</code> and <code>key4.db</code>, along with wallet names and extension identifiers. These are browser-store and collection selectors. Together with SQLite, these selectors identify what the agent is built to read, but successful access and decryption on a victim host remain unmeasured.</p>
<p>The wallet-related markers include an endpoint setting, a client header and an upload path, and the companion's <a href="https://github.com/KrasForge/unk-scanner/blob/main/rules/yara/deaddrop_wallet_agent.yar">wallet-agent YARA rule</a> combines these with the Overlord module and server-environment markers. A generic framework match would also identify unrelated Overlord deployments. Requiring wallet context narrows the search to files carrying that additional combination.</p>
<p>The binary also contains previous-frame, block-comparison and capture-loop symbols, which provide evidence of capture-related implementation despite the absence of expected packages in the selected upstream dependency files. <a href="https://krasforge.github.io/UNK_DeadDrop/articles/05-screen-capture-analysis/">The capture analysis</a> examines those symbols and the separate Windows bundle. Display access, code reachability and successful transmission were not reproduced.</p>
<p>The phased JavaScript collection, GUS-prefixed configuration and Windows Node/Python pipeline occur in separate components of the toolchain, and their functions extend the collection paths beyond the Go executable, yet they cannot all be assigned to changes in one agent's source.</p>
<p>For file hunting, the wallet-agent rule and <a href="https://github.com/KrasForge/unk-scanner/blob/main/rules/yara/deaddrop_capture_agent.yar">capture-agent rule</a> select different marker combinations. Both remain experimental. A hit identifies content for inspection, and can include source or research material containing those strings. For an unexpected installed agent, preserve its hash, configuration, launch mechanism and outbound destinations alongside the matching offsets.</p>
<hr />
<p><em>Originally published on the</em> <a href="https://krasforge.github.io/UNK_DeadDrop/articles/12-framework-and-collection-boundary/"><em>UNK_DeadDrop research site</em></a><em>.</em></p>
<p><strong>References</strong></p>
<ul>
<li><strong>Campaign paper:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf">UNK_DeadDrop: An Artifact-Based Study of Recruitment Malware</a> (PDF)</li>
<li><strong>Scanner paper:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf">Static AST-Based Triage of Socially-Engineered Developer Lures</a> (PDF)</li>
<li><strong>Scanner:</strong> <a href="https://github.com/KrasForge/unk-scanner">unk-scanner on GitHub</a>, with rules and inert fixtures</li>
<li><strong>Series:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/">all fourteen parts on the UNK_DeadDrop research site</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building and Testing Detections for UNK_DeadDrop]]></title><description><![CDATA[Thirty-three YARA rules replayed against retained artifacts, followed by a separate noise review and static scanner evaluation.]]></description><link>https://unk-deaddrop.hashnode.dev/detection-engineering</link><guid isPermaLink="true">https://unk-deaddrop.hashnode.dev/detection-engineering</guid><category><![CDATA[Security]]></category><category><![CDATA[Malware]]></category><category><![CDATA[yara]]></category><category><![CDATA[detection engineering ]]></category><category><![CDATA[cybersecurity]]></category><dc:creator><![CDATA[Ilja Krasnjanski]]></dc:creator><pubDate>Mon, 02 Nov 2026 09:30:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae7ec33f80852d1e222effb/94c03913-06f8-4bc4-9c93-45bd57029f55.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The published companion snapshot contains 33 named YARA rules in 28 files, and every rule matched at least one object in the saved investigation material, while none matched the small local software baseline. A later, separate local review narrowed seven source rules after selected controls produced collisions; the revisions preserved all earlier artifact matches, but are not part of the published snapshot.</p>
<p>The wallet-agent rule initially matched marker-rich research prose, and requiring ELF magic at byte zero rejects that text fixture. It checks the header, not the executable's behavior.</p>
<p>The <a href="https://github.com/KrasForge/unk-scanner">companion repository</a> contains file-content rules and a parse-only JavaScript scanner, but it no longer includes the event rules or workspace-configuration rules: authentic endpoint logs and the original <code>.vscode</code> task and settings files were not available for replay. Synthetic event records and invented configuration files would show how a rule behaves, not whether it matches the collected targets.</p>
<h2>The wallet-agent rule matched the held Go binary</h2>
<p>The wallet-agent rule now requires ELF magic at byte zero, a file smaller than 64 MiB, an Overlord module path, a server-environment marker and at least two wallet-collection strings. The combination matched the held Go agent. Removing the header or one required marker category gives a negative result in the inert boundary tests.</p>
<p>The earlier rule version matched a harmless fixture with the complete marker set in research prose, and requiring the ELF header at offset zero prevents that ordinary text collision. YARA does not validate the rest of the executable or determine whether the strings are reachable code. So a marker-rich ELF can still match.</p>
<p>A file signature identifies matching content. Execution and data transfer require host evidence; operator attribution requires a separate investigation.</p>
<img src="https://krasforge.github.io/UNK_DeadDrop/diagrams/11-detection-logic.png" alt="YARA conditions and validation results" style="display:block;margin:0 auto" />

<p><em>Figure 1. Every wallet-agent condition is required. The retained rules matched case artifacts, while the local software baseline and marker-rich prose fixture had no hits.</em></p>
<h2>The replay covered 192 files and 395 views</h2>
<p>The read-only replay covered 192 selected files: the 15 directories marked <code>DO-NOT-RUN</code>, compressed lure bundles, web assets, extracted VSIX components and agent archives, source files, and six retained lure messages, and the helper read ZIP, VSIX, TAR, gzip and nested ZIP content in memory without writing extracted files.</p>
<p>It evaluated 395 raw or archive-member views totaling about 234 MB, and all 33 named rules in 28 files matched at least once (at least one rule matched 90 input files, and 102 had no hits). The totals include copied variants and nested archive views, so they are not independent samples or incidents; <a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E15.pdf">Exhibit E15</a> records the companion snapshot and validation scope. No file was executed.</p>
<p>Four ECHOES RAR archives require a password, so their members were not inspected, while the separate sealed-environment archive was scanned only as raw bytes, and exact hashes identify the four ECHOES containers without making claims about their contents.</p>
<p>A separate exact-hash rule identifies the retained Surge Group message. It does not claim the message shares control with UNK_DeadDrop. Other hash rules identify exact retained packages, launchers and messages. Modification or repackaging evades them.</p>
<p>Most unmatched inputs were generic dashboard modules, web assets or research context without a distinctive file-content signature; they were included in the coverage accounting, but weak rules that only identify those records would not be useful detections.</p>
<p>Workspace-file signatures remain absent because neither the original <code>.vscode/tasks.json</code> nor <code>.vscode/settings.json</code> appears in the saved repository archive or its commit history, and event rules were removed because there are no authentic Windows process, Linux audit or comparable endpoint logs in the retained set. The companion therefore has no Sigma rules. The two SQLite databases containing target addresses were also excluded from scanning.</p>
<p>The companion's <a href="https://github.com/KrasForge/unk-scanner/blob/main/docs/actual-artifact-validation.md">artifact-validation report</a> lists per-rule match counts and corpus limits. Its <a href="https://github.com/KrasForge/unk-scanner/blob/main/docs/detection-notes.md">detection notes</a> explain each condition and its limits.</p>
<h2>No matches in 1,914 local software files</h2>
<p>The second scan covered 1,914 files and about 59.2 MB across 2,379 raw and nested object views from the local CPython standard library, installed Python packages, global npm packages and Acorn. No retained rule matched. The 19 Markdown articles and companion notes also produced no hits after source-oriented conditions were narrowed to avoid prose collisions. The baseline consists of local software. Its small, uncurated sample cannot support estimates of fleet false-positive rate or production precision.</p>
<p>The prose fixture remains important alongside that zero-hit result, as it records a false-positive case from the rule's earlier form and confirms the current ELF-header check rejects plain text; analysts should still review file type, acquisition context and matched bytes before treating a hit as suspicious. For process and persistence behavior, collect authentic endpoint logs first; this repository does not publish event rules without them.</p>
<p>A later local review covered 177 case inputs, 380 views and 344 distinct objects, and seven source rules were narrowed after collisions in selected research, dual-use tooling, ordinary software, installed software and upstream-fork controls. Every previous case-artifact match was preserved. The seven revised forms had no hits in the external controls scanned, but this bounded control set does not estimate a production false-positive rate; the revisions remain separate from the public companion snapshot.</p>
<h2>YARA matches byte patterns; unk-scanner parses JavaScript</h2>
<p>The YARA replay measures whether particular bytes match the signatures, while <code>unk-scanner</code> instead parses JavaScript and follows selected expressions, string decoders and launch structures; a file can match a content signature while the scanner misses its behaviour, or produce a scanner warning because it uses obfuscation that also appears in legitimate software.</p>
<p>The expanded scanner evaluation now has its own <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf">paper</a>, which covers popular npm packages and editor extensions, recently changed npm packages, and a separate public malware dataset. The final popular-software scan raised no errors in 188,901 files outside the initial calibration corpus, although an earlier run on those files informed one escalation-rule adjustment, and in the registry changes corpus, two of 19,976 packages raised errors; full review classified both as false positives.</p>
<p>Sensitivity tells a different story. Only 62 of 2,122 known-malicious samples raised warnings, with none reaching error level, and most lacked the clipboard, Electron or encrypted-loader structures that the higher-severity rules recognise; the retained Windows Node agent also stays below warning level, yet its known collection functionality does not become benign because this rule set misses it.</p>
<p><a href="https://krasforge.github.io/UNK_DeadDrop/articles/13-scanner-registry-benchmark/">Part 13</a> covers corpus selection, review depth, parser coverage and throughput. <a href="https://krasforge.github.io/UNK_DeadDrop/articles/14-scanner-recovery-and-bypasses/">Part 14</a> examines nineteen small variants, eighteen of which evaded a rule, lowered severity or defeated string recovery, and a hardened engine that later closed all but one of them without adding error-level findings on the same corpora. These results concern the scanner, not the 33 YARA rules or their earlier control set.</p>
<h2>Rule matches depend on the bytes given to YARA</h2>
<p>YARA can inspect only the bytes supplied to it. A rule for decrypted JavaScript needs those plaintext bytes, a rule for a VSIX member needs the member supplied to the engine, and scanning an outer compressed package alone is not equivalent to the archive-member replay above. Consult each rule's target and match conditions before choosing a sample.</p>
<p>A hunting service may use a different rule set for manual file scans. Check where the uploaded rules are deployed.</p>
<p>The repository includes inert <a href="https://github.com/KrasForge/unk-scanner/blob/main/tests/test_detections.py">YARA boundary tests</a>, a <a href="https://github.com/KrasForge/unk-scanner/blob/main/tests/test_blog_boundaries.py">prose false-positive test</a>, and the <a href="https://github.com/KrasForge/unk-scanner/blob/main/requirements-dev.txt">test requirements</a>. Run the checks using the instructions in its README. These tests are repeatable checks of rule behavior, not malware execution or production validation.</p>
<hr />
<p><em>Originally published on the</em> <a href="https://krasforge.github.io/UNK_DeadDrop/articles/11-detection-engineering/"><em>UNK_DeadDrop research site</em></a><em>.</em></p>
<p><strong>References</strong></p>
<ul>
<li><strong>Campaign paper:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf">UNK_DeadDrop: An Artifact-Based Study of Recruitment Malware</a> (PDF)</li>
<li><strong>Scanner paper:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf">Static AST-Based Triage of Socially-Engineered Developer Lures</a> (PDF)</li>
<li><strong>Scanner:</strong> <a href="https://github.com/KrasForge/unk-scanner">unk-scanner on GitHub</a>, with rules and inert fixtures</li>
<li><strong>Series:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/">all fourteen parts on the UNK_DeadDrop research site</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[From Commit Metadata to a Developer Target List]]></title><description><![CDATA[Reconstructing the harvester, sender configuration and aggregate counts behind two SQLite targeting pools.]]></description><link>https://unk-deaddrop.hashnode.dev/from-commit-metadata-to-a-developer-target-list</link><guid isPermaLink="true">https://unk-deaddrop.hashnode.dev/from-commit-metadata-to-a-developer-target-list</guid><category><![CDATA[Security]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[privacy]]></category><category><![CDATA[threat intelligence]]></category><category><![CDATA[cybersecurity]]></category><dc:creator><![CDATA[Ilja Krasnjanski]]></dc:creator><pubDate>Mon, 02 Nov 2026 09:27:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae7ec33f80852d1e222effb/753beda5-e338-4baa-b00c-559140831746.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The sender configuration pointed to two SQLite databases. I followed those references to the harvesting code and pools containing 79,698 and 62,667 rows, which together contain 142,365 distinct stored address strings associated with 27,698 repository values (the worker, configuration and read-only count records are summarized in <a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E08.pdf">E08</a>), and the harvesting code collected those addresses from public commit metadata for use by the sender tooling.</p>
<h2>One harvester fed two address pools</h2>
<p>The harvester selects repositories by star range, walks their branches to extract author and committer addresses, then lowercases and trims them before filtering, and visits up to twenty branches per repository in the recorded implementation. Worker configurations partition the search. Scheduler and merge code consolidate results into two master pools. MITRE ATT&amp;CK classifies this reconnaissance as <a href="https://attack.mitre.org/techniques/T1589/002/">gathering victim email addresses (T1589.002)</a>. The SQLite relation is simple:</p>
<pre><code class="language-text">emails
  email: stored address
  repo: associated repository value
</code></pre>
<p>The sender configuration refers to both pools. Different intervals, cooldowns and warm-up settings control how the tools consume them, and the toolkit names the two pools <code>safe</code> and <code>antispam</code>; the database references connect harvesting to message preparation, but no send logs were recovered to establish which addresses received messages.</p>
<img src="https://krasforge.github.io/UNK_DeadDrop/diagrams/07-targeting.png" alt="Two pools, two different counting problems" style="display:block;margin:0 auto" />

<p><em>Figure 1. The address sets are disjoint, while repository values overlap between the two pools.</em></p>
<h3>142,365 distinct address strings</h3>
<p>The two databases share some repository values. A SQL union counts each value once across both pools. Read-only aggregate queries on 23 October found:</p>
<ul>
<li>79,698 rows in the safe pool;</li>
<li>62,667 rows in the antispam pool;</li>
<li>an exact address overlap of zero between them;</li>
<li>142,365 distinct address strings across both;</li>
<li>27,698 distinct repository values across both.</li>
</ul>
<p>The queries check address overlap and count the combined repository values:</p>
<pre><code class="language-sql">-- Address overlap
SELECT count(*)
FROM main.emails AS s
JOIN other.emails AS a ON s.email = a.email;

-- Combined repository values
SELECT count(*) FROM (
  SELECT repo FROM main.emails
  UNION
  SELECT repo FROM other.emails
);
</code></pre>
<p>In this comparison, the stored address sets are disjoint but the repository sets overlap. Adding the two repository counts would therefore overcount. The queries compare stored strings without additional address canonicalization or identity resolution, so aliases and multiple addresses belonging to one person remain separate entries.</p>
<p>The sender code configures <code>mail.privateemail.com</code> through variables carrying a <code>ZOHO</code> prefix, which is a toolkit setting, not proof that this campaign used the service, and retained message headers show Mailgun VERP envelope behavior for the D2S and Category Labs messages, while the Nodit message used its own domain in the envelope. A forwarding service obscured the original sending IP, so its relay address is not attributed to the operators, and the <a href="https://krasforge.github.io/UNK_DeadDrop/indicators/">indicator reference</a> records the observed sender and tracking-domain details, with the limits of each observation.</p>
<h3>Star ranges divided the repository search</h3>
<p>The three worker configurations each query a different band of repository star counts: <code>stars:101..137</code>, <code>stars:138..174</code> and <code>stars:175..210</code>. None specifies a language, topic, keyword or organization. The harvester therefore searches public repositories across those bands instead of selecting a particular kind of developer.</p>
<p>Of the 27,698 repository values, 595 (2.1 percent) have names suggesting 3D or graphics work, such as Blender, Unity, Unreal, WebGL, shaders or rendering. That count comes from repository names alone. The <code>3dscenebox[.]online</code> lure in <a href="https://krasforge.github.io/UNK_DeadDrop/articles/01-clickfix-clipboard-manipulation/">Part 1</a> initially looked like a campaign against 3D artists, but the harvester shows no separate sourcing channel for that group, and the evidence fits a new cover story sent to the same broad pool.</p>
<h2>Three repositories carried delivery kits</h2>
<p>I also examined the accounts supplying the recruitment material, separate from the repositories harvested for addresses; those five accounts active in July held 172 public repositories. I reviewed cached metadata and file trees, then classified repositories by language, project type and apparent role. Twelve repositories had directly evidenced operational roles:</p>
<ul>
<li>Three repositories carried delivery kits: two weaponised project copies in <a href="https://krasforge.github.io/UNK_DeadDrop/articles/02-vsix-editor-execution/">Part 2</a> and the ECHOES branch in <a href="https://krasforge.github.io/UNK_DeadDrop/articles/09-echoes-lineage-image-provenance/">Part 9</a>.</li>
<li>Three were clean take-home assignments whose weaponised versions were delivered separately.</li>
<li>Five were lure funnels.</li>
<li>One held the harvesting and sending toolkit.</li>
</ul>
<p>Seven others were forks of third-party projects, including two AI coding tools, while the catalogue labels 152 repositories as credibility props based on their names, file layouts and creation patterns and separately flags one more prop for an encrypted <code>.env</code>, bringing the total to 172.</p>
<p>The credibility props include NFT marketplaces, staking programs, dice and coinflip games, trading bots, pump.fun clones and small "GPT" tools, and they use the Rust, TypeScript and Python stack expected of a Solana or EVM contractor. Together, the 153 props make up roughly nine in ten repositories, giving each account the appearance of an active engineer's portfolio.</p>
<p>The creation dates show how some of the accounts were built. On the flagship account, 67 of 90 repositories appeared within five days in April 2024, while a second persona account had been dormant since 2017, then received 68 repositories between 27 June and 1 July 2025. Three had exact byte-size matches under new names on the flagship account.</p>
<p><code>pridnet</code> forked five real trading and LLM projects in March 2026, went quiet for 68 days, then published the weaponised assessment repository. <code>Lyracartoon</code> held only its two weaponised repositories. A byte-level sweep using two methods found operator indicators only in the known weaponised copies.</p>
<p>The other signature hits were false positives I checked by reading the files: a benign clipboard helper copied the text it displayed, and a marker matched inside the word <code>bogus</code> in a fork of Aider. Seven install hooks belonged to ordinary build tooling. The credibility repositories carried no payload, but their apparent activity could make a recruiter seem trustworthy.</p>
<h2>A public contract repo directed questions to a private contact</h2>
<p>Five of the repositories invert the recruitment model, so that the developer is drawn in rather than written to, and the most visible, a "pump.fun-compatible" Solana contract with 128 stars and about fifty forks, implements initialization and liquidity provision. Its fund-moving <code>remove_liquidity</code> handler, however, has been reduced to a stub:</p>
<pre><code class="language-rust">pub fn remove_liquidity(/* ... */) -&gt; Result&lt;()&gt; {
    // If you want to Interact with CPI, then plz contact to me.
    Ok(())
}
</code></pre>
<p>The repository's pull requests show the pool-removal and migration code being written, then withheld from the public version, which makes the missing code deliberate rather than unfinished. The README, contribution guide, <code>SECURITY.md</code> and API documentation direct questions to the same Telegram contact listed in the operators' portfolio material.</p>
<p>An issue from a user who hit the missing module shows the stub at work. Eight inspected forks contain the same 942-byte stub, and none publishes a working version, which suggests the full code was offered privately to people who asked. The public repository has registry-only dependencies, no install hooks, no editor configuration and no hard-coded operator key. The contents of a private version remain unknown. It might add authority over deployed pools, include a developer-machine payload, or be ordinary paid contract work. The evidence cannot distinguish among these possibilities. Obtaining it would require engaging the operators.</p>
<p>The public repository names a real 2024 launchpad as its homepage, and a second operator repository describes its Solidity contract as written for the same site. The project had its own public team, on-chain program and deployer authorities. None matches a wallet in the operator set. The link supports contract work for the launchpad, not ownership of it. The domain's later re-registration served an unrelated site.</p>
<h2>No message logs or endpoint records were recovered</h2>
<p>The harvester collected addresses for messages; the public contract projects drew developers toward private conversations. Sender configuration connects the databases to message preparation, but counting people contacted or compromised would require send logs and endpoint records, which were not recovered. The repository total also counts stored values. I did not reverify each repository's existence or ownership.</p>
<p>The <a href="https://krasforge.github.io/UNK_DeadDrop/articles/08-administration-platform/">administration frontend</a> contains mail and wallet views, but its connection to these particular SQLite pools remains unverified.</p>
<hr />
<p><em>Originally published on the</em> <a href="https://krasforge.github.io/UNK_DeadDrop/articles/07-developer-target-harvesting/"><em>UNK_DeadDrop research site</em></a><em>.</em></p>
<p><strong>References</strong></p>
<ul>
<li><strong>Campaign paper:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf">UNK_DeadDrop: An Artifact-Based Study of Recruitment Malware</a> (PDF)</li>
<li><strong>Scanner paper:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf">Static AST-Based Triage of Socially-Engineered Developer Lures</a> (PDF)</li>
<li><strong>Scanner:</strong> <a href="https://github.com/KrasForge/unk-scanner">unk-scanner on GitHub</a>, with rules and inert fixtures</li>
<li><strong>Series:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/">all fourteen parts on the UNK_DeadDrop research site</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Following the GitLab Dead Drop: Configuration Changes and C2 History]]></title><description><![CDATA[How a GitLab-hosted configuration file let the UNK_DeadDrop operators change servers without new payloads, and what its revision history reveals.]]></description><link>https://unk-deaddrop.hashnode.dev/gitlab-dead-drop-infrastructure</link><guid isPermaLink="true">https://unk-deaddrop.hashnode.dev/gitlab-dead-drop-infrastructure</guid><category><![CDATA[Security]]></category><category><![CDATA[Malware]]></category><category><![CDATA[GitLab]]></category><category><![CDATA[threat intelligence]]></category><category><![CDATA[cybersecurity]]></category><dc:creator><![CDATA[Ilja Krasnjanski]]></dc:creator><pubDate>Mon, 02 Nov 2026 09:21:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae7ec33f80852d1e222effb/e348fda1-1304-45d1-bb11-58a8793ad1ce.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>On 13 July 2026, a GitLab commit replaced a list of server names with <code>api.cklomino[.]ink</code> and <code>api.dsfmot[.]xyz</code> (the retained repository bundle and file history are documented in <a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E07.pdf">E07</a>). The file held configuration, not a payload. The client reads its destination from this resource; changing the server list therefore changes where an unchanged payload can connect. The payload hash stays the same. The configuration history records the changing destinations, and certificate and panel observations connect one of those domains to a historical origin address, while the <a href="https://krasforge.github.io/UNK_DeadDrop/indicators/">historical indicator reference</a> lists every recorded resolver value, the separate unreferenced file, related lure hosts and the limits on each IP association.</p>
<p>The later July installer family used a separate mechanism; its <code>upd_m</code>, <code>upd_w</code>, <code>upda_m</code> and <code>upda_w</code> droppers embedded <code>api.aazdias[.]ink</code> as the primary store and <code>api.fallaazdis[.]ink</code> as the fallback, and those values came from the recovered dropper configuration, not from the GitLab <code>config.json</code> history. The origin association to <code>207.241.172[.]222</code> came from dated certificate and passive-scan observations, not a direct fetch from that address.</p>
<h2>The payload fetches <code>config.json</code> from GitLab</h2>
<p>The payload references this resource:</p>
<pre><code class="language-text">gitlab[.]com/drop-indexing/tasks/-/raw/main/config.json
</code></pre>
<p>The repository also contains a separate <code>config-alt.json</code> file with another pair of destination strings. Neither retained payload references it. Client use of this second file remains unknown.</p>
<p>The recovered JavaScript has its own hard-coded fallback, <code>api.domatisc[.]ink</code>, and a resolver function that requests the raw GitLab <code>config.json</code> resource, though no retained endpoint log confirms that a victim fetched either file. This code pattern is relevant to MITRE ATT&amp;CK's <a href="https://attack.mitre.org/techniques/T1102/001/">dead drop resolver technique (T1102.001)</a>. A payload with the same hash can receive different destinations depending on when it fetches this resource.</p>
<img src="https://krasforge.github.io/UNK_DeadDrop/diagrams/05-resolver.png" alt="Timeline of the referenced config.json changes and the separate config-alt.json file" style="display:block;margin:0 auto" />

<p><em>Figure 1. The referenced configuration changed in May and July. A separate file was added in June, but no retained payload references it.</em></p>
<h3>Five May revisions, then a new server pair in July</h3>
<p>The primary file has five recorded revisions on 26 May, beginning with an initial set of <code>domatisc[.]ink</code>, <code>coslyintra[.]online</code> and <code>valorecuiting[.]online</code>, after which subsequent edits remove and restore an entry, introduce <code>api.</code> prefixes, and finally leave a set of two hosts. The recorded states after that, in order, are:</p>
<ol>
<li>26 May 2026, final listed revision of <code>config.json</code>: <code>api.domatisc[.]ink</code> and <code>api.coslyintra[.]online</code>.</li>
<li>1 June 2026, separate file <code>config-alt.json</code>: <code>api.onoplainai[.]ink</code> and <code>api.migadyn[.]info</code>. Its use by the recovered client is unestablished.</li>
<li>13 July 2026, about 07:10 UTC, <code>config.json</code>: <code>api.cklomino[.]ink</code> and <code>api.dsfmot[.]xyz</code>.</li>
</ol>
<p>Commit <code>c3c71d65</code> replaces the primary destination set left by the May revisions, and the retained history contains six commits to <code>config.json</code> and one to <code>config-alt.json</code> (the latter is a separately recorded resource, not an evidenced client fallback).</p>
<p>Client refresh behavior determines when a configuration change takes effect. A client that fetches the file again may receive a new address, while one using a cached destination may continue trying the old server, and the retained history contains no client execution trace to resolve that behavior. Blocking a destination can interrupt traffic, while a client that consults this configuration resource may receive another address, so connection records are needed to identify the destination each client used.</p>
<h2>A July hostname linked to a reported origin</h2>
<p>I compared the resolver hostnames with historical scan records to investigate the services behind them, and July scan notes associate <code>207.241.172[.]222</code> with C2 and administration services, and they record a certificate naming <code>api.cklomino[.]ink</code> and a third-party bare-IP scan that returned a Subtensor sign-in panel. The raw scan responses and certificate were not retained. The panel HTML and JavaScript available for inspection were retrieved from <code>api.domatisc[.]ink</code>, so they cannot independently reproduce the reported response from the bare IP.</p>
<p>The panel adds an application-level association to the certificate evidence, while further relationships came from certificate facets, passive DNS and reverse-DNS fields. These observations came from research-service records. The origin analysis did not directly scan the server or log in. <a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E20.pdf">E20</a> and <a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E21.pdf">E21</a> record the source and limits of those historical associations.</p>
<p>The July observations associated <code>207.241.172[.]222</code> with C2 and administration, <code>170.205.31[.]182</code> with lure hosting, and <code>23.137.105[.]75</code> with earlier C2 infrastructure. These roles describe the recorded periods. Current availability and ownership were not checked.</p>
<h3>The SSH host-key search found no new address</h3>
<p>The saved service enumeration listed SSH, HTTP and HTTPS. Its SSH profile included a host key, banner and handshake information, but these had different value as search pivots, as the common OpenSSH banner and handshake fingerprint matched broad populations of hosts, while the more selective host key returned only the address already known. The search found no backup address. Hosts absent from that dataset remain outside its coverage. The reported panel response did not enumerate every tenant at the address, and other services may have shared the address, so an IP block could affect unrelated traffic.</p>
<h3>The 23 July certificates added hostnames</h3>
<p>The 23 July certificate enumeration added names that were absent from the inspected payloads: a <code>dashboard</code> host on the cklomino base, <code>dev</code> and <code>hr</code> mail subdomains on lure domains, and a <code>hiring</code> host alongside a career-themed API. The <a href="https://krasforge.github.io/UNK_DeadDrop/indicators/">indicator reference</a> records the names and first-issuance dates. These records identify certificate names, not proof that each host served a malicious application.</p>
<p>The certificates did not provide a cross-domain link. Subject alternative names stayed within individual registered bases, with combinations of the base, wildcards and subdomains such as <code>api.</code>, <code>www.</code> and <code>email.</code>, and no retained enumeration entry combined two bases in one certificate. Wildcards also mean that a new subdomain can appear without a new certificate-log entry.</p>
<p>Timing offered more context. Certificates for four bases were first issued on 13 July, the date of the resolver's change to <code>api.cklomino[.]ink</code> and <code>api.dsfmot[.]xyz</code>. On 23 July, <code>aazdias[.]ink</code> and <code>fallaazdis[.]ink</code> were registered about six minutes apart through the same registrar and privacy service, with the same Cloudflare nameserver pair. Their primary-and-fallback naming repeats the earlier <code>aavzx[.]ink</code> and <code>fallbaav[.]ink</code> arrangement, and the current stores, a new dashboard host and several older lure domains also received certificates that day.</p>
<p>Shared panels and certificate automation can produce this cadence across unrelated customers, so I used the timing to check code and configuration relationships, rather than to assign hosts to the cluster. These registration and enumeration observations come from restricted 23 July records and are discussed in the <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf">paper</a>, but are not independently reproduced in the public evidence exhibits.</p>
<p>A sweep of two /23 ranges belonging to the hosting provider covered about a thousand addresses; 291 exposed services. The provider used ordinary shared hosting. About twenty unrelated tenants ran services on port 3000, while the free scan dataset returned no results for the agent's port 5173 in either range, and those observations make port-based hunting a poor discriminator: port 3000 was common among unrelated tenants, and the dataset did not cover the agent's port. The results come from research services and have not been refreshed since 24 July.</p>
<p>Part 7 follows the target-acquisition tooling behind the two SQLite pools.</p>
<hr />
<p><em>Originally published on the</em> <a href="https://krasforge.github.io/UNK_DeadDrop/articles/06-gitlab-dead-drop-infrastructure/"><em>UNK_DeadDrop research site</em></a><em>.</em></p>
<p><strong>References</strong></p>
<ul>
<li><strong>Campaign paper:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf">UNK_DeadDrop: An Artifact-Based Study of Recruitment Malware</a> (PDF)</li>
<li><strong>Scanner paper:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf">Static AST-Based Triage of Socially-Engineered Developer Lures</a> (PDF)</li>
<li><strong>Scanner:</strong> <a href="https://github.com/KrasForge/unk-scanner">unk-scanner on GitHub</a>, with rules and inert fixtures</li>
<li><strong>Series:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/">all fourteen parts on the UNK_DeadDrop research site</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Parsing Without Executing: JavaScript Deobfuscation with Acorn]]></title><description><![CDATA[Rebuilding an AES key from sixteen obfuscated fragments with Acorn, proven by authenticated decryption without running the extension]]></description><link>https://unk-deaddrop.hashnode.dev/parsing-without-executing-javascript-deobfuscation-with-acorn</link><guid isPermaLink="true">https://unk-deaddrop.hashnode.dev/parsing-without-executing-javascript-deobfuscation-with-acorn</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Security]]></category><category><![CDATA[Malware]]></category><category><![CDATA[reverse engineering]]></category><category><![CDATA[ast]]></category><dc:creator><![CDATA[Ilja Krasnjanski]]></dc:creator><pubDate>Mon, 02 Nov 2026 09:11:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae7ec33f80852d1e222effb/38ad2916-d44e-4716-9c3b-7681d07254ff.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The VSIX loader assembled one AES key from sixteen fragments: nine decoder calls and seven literals. Recovering it required mapping wrapper functions and a rotated string table, and my first regex missed a wrapper that declared an intermediate value before returning, while five plausible rotations then failed checks across the call sites.</p>
<p>All three encrypted payloads passed GCM authentication under the reconstructed key. The recovered components and authentication procedure are recorded in <a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E04.pdf">Exhibit E04</a>. <a href="https://www.proofpoint.com/us/blog/threat-insight/dont-fear-repo-unkdeaddrop-phishing-campaign-targets-developers-steal">Proofpoint's report</a> also documents the key and Windows architecture.</p>
<h2>Wrapper calls hide the key's construction</h2>
<p>The key assignment combines literals with calls to several wrapper functions, and a wrapper may change the index, reorder its arguments or add an offset before invoking the underlying decoder, so each call must be traced through its wrapper. The July analysis identified seven wrappers in the bootstrap and five in <code>extension.js</code>. Each needed three fields mapped:</p>
<pre><code class="language-text">Which argument supplies the string-table index?
Which argument supplies the decoder key?
What offset is applied before the underlying call?
</code></pre>
<p>A regex that expects every wrapper to return a decoder call immediately can miss a function that first declares an intermediate value, while <a href="https://github.com/acornjs/acorn">Acorn</a> represents both statements in the syntax tree. It does not solve the decoder automatically, but it provides a more reliable structure to inspect than text patterns.</p>
<h3>One wrapper assigns a temporary value</h3>
<p>This synthetic example parses a wrapper containing a declaration before its return statement:</p>
<pre><code class="language-javascript">import { parse } from 'acorn';

const source = `
function wrapper(index, key) {
  const adjusted = index - 17;
  return decode(adjusted, key);
}
`;

const ast = parse(source, {
  ecmaVersion: 2022,
  sourceType: 'script',
  locations: true
});

const fn = ast.body[0];
const returned = fn.body.body.find(
  node =&gt; node.type === 'ReturnStatement'
);

console.log(fn.type, returned.argument.type);
// FunctionDeclaration CallExpression
</code></pre>
<p><code>decode</code> is undefined. But parsing does not require it to exist; Acorn reads the program as a string and returns its syntax tree. Parsing leaves the declarations and expressions unevaluated. I reconstructed the wrappers' index and key transformations from their syntax trees, then implemented those transformations separately. The extension was never imported or executed.</p>
<img src="https://krasforge.github.io/UNK_DeadDrop/diagrams/03-deobfuscation.png" alt="From obfuscated syntax to authenticated files" style="display:block;margin:0 auto" />

<p><em>Figure 1. Wrapper mapping and string-table recovery supply the key fragments; GCM authentication checks the reconstructed key against the payloads.</em></p>
<p>The string array had been rotated, so a valid-looking index could point to the wrong encoded element until the array order was resolved, and searching from one expected <code>crypt</code> anchor yielded five plausible rotations. Comparing each rotation against all resolved call sites narrowed the candidates.</p>
<p>The recorded offsets were 391 for the extension and 261 for the bootstrap. The selected candidate produced printable decodes at 15 of 15 tested sites. The runner-up produced 3 of 15. That gap was useful. But printability remained a heuristic; the next checks were whether each string fit its call context and whether the key fragments could be reconstructed in source order. The encrypted payloads would provide a stronger validation.</p>
<h3>Putting the sixteen fragments back in order</h3>
<p>The key expression combined nine decoder results with seven literal fragments. Recovering their values was only half the work. The fragments also had to be concatenated in the original order, including values nested inside parenthesized additions, and a small bounded evaluator concatenated string literals and already-decoded values in source order. The recovered key could then be checked against the encrypted payloads.</p>
<h2>Authenticated decryption verifies all three payloads</h2>
<p>The loader specified the encrypted blob layout directly:</p>
<pre><code class="language-text">bytes  0–11    initialization vector
bytes 12–27    authentication tag
bytes 28–end   ciphertext
</code></pre>
<p>My initial <code>openssl enc</code> attempt failed: that command lacked the required GCM authentication check, so I switched to <a href="https://nodejs.org/api/crypto.html">Node's cryptographic library</a> to verify the stored tag. The failure was in the chosen tool.</p>
<p>With the corrected operation, all three payloads passed authentication: <code>detect_malware.py</code> (60,054 bytes), the browser-related decryption component; <code>windows-agent-node.js</code> (56,093 bytes), the Windows collection agent; and <code>windows-js-pipeline.js</code> (32,324 bytes), the collection and elevation coordinator.</p>
<p>All three payloads decrypted to readable, commented source. Authentication tied each plaintext to its encrypted input under the recovered parameters. Execution and collection outcomes require endpoint evidence.</p>
<h3>The scanner uses the checksum loop to find the rotation</h3>
<p>The manual analysis selected a rotation by comparing printable output across call sites, while the later scanner uses another feature of the same obfuscation: a loop that decodes numeric-looking strings, combines them with <code>parseInt</code> and shifts the array until the checksum equals an embedded constant.</p>
<p>The evaluated engine identifies the decoder variant from its syntax, reimplements decoding and evaluates that checksum for candidate rotations. For this extension the variant is RC4 behind a shuffled base64 alphabet, with an index offset of 333, and it reaches rotation 391 and decodes 2,139 of 2,140 call sites, while for the bootstrap it reaches rotation 261 and decodes 15 of 17. These agree with the manually established rotations without executing the extension.</p>
<p>Recovered strings become available to other rules, including calls whose module names are hidden behind decoders, and if the checksum cannot be solved, a printable-consensus heuristic may help. Otherwise the array remains unresolved.</p>
<p>A later test replaced <code>push(shift())</code> rotation with <code>splice</code>. The evaluated engine missed the loop and incorrectly accepted rotation zero, and a hardened engine now recognizes more rotation steps and accepts stored order only when the decoded call sites read as text. <a href="https://krasforge.github.io/UNK_DeadDrop/articles/14-scanner-recovery-and-bypasses/">Part 14</a> covers the defect and subsequent tests.</p>
<p>The scanner does not assemble the AES key or decrypt the payloads. Authenticated decryption remains a separate check on the recovered fragments, and partial string recovery must be read alongside the unresolved call sites.</p>
<p>The <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf">scanner paper</a> describes the evaluated implementation, including its per-process restrictions, resource limits and AST-only TypeScript/JSX fallback, and the command-line output records which isolation controls were applied (Linux network-namespace isolation depends on host support). Installation and current release instructions are maintained in the <a href="https://github.com/KrasForge/unk-scanner">companion repository</a>, with packages on <a href="https://www.npmjs.com/package/unk-scanner">npm</a> and <a href="https://pypi.org/project/unk-scanner/">PyPI</a>.</p>
<p>The larger registry evaluation is in <a href="https://krasforge.github.io/UNK_DeadDrop/articles/13-scanner-registry-benchmark/">Part 13</a>. It found few error-level alerts and missed most generic package malware. A completed scan is a pattern check, not a safety verdict.</p>
<pre><code class="language-text">detect_malware.py
4c3fdd86a59d52a0e29f472666cbf9472b6fd83c38ef2a4b547f31c72b8ee87e

windows-agent-node.js
e5b7d6411d0c95da047185d94fca98d454b73df2e7f46f2753e212af2ca45da5

windows-js-pipeline.js
bf820623e8deaa37092650c7fae594e56b8d81c27b541419006cca2d0536353b
</code></pre>
<hr />
<p><em>Originally published on the</em> <a href="https://krasforge.github.io/UNK_DeadDrop/articles/03-acorn-deobfuscation/"><em>UNK_DeadDrop research site</em></a><em>.</em></p>
<p><strong>References</strong></p>
<ul>
<li><strong>Scanner paper:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf">Static AST-Based Triage of Socially-Engineered Developer Lures</a> (PDF)</li>
<li><strong>Campaign paper:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf">UNK_DeadDrop: An Artifact-Based Study of Recruitment Malware</a> (PDF)</li>
<li><strong>Scanner:</strong> <a href="https://github.com/KrasForge/unk-scanner">unk-scanner on GitHub</a>, with rules and inert fixtures</li>
<li><strong>Series:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/">all fourteen parts on the UNK_DeadDrop research site</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How a Malicious VSIX Uses the Signed Editor Runtime]]></title><description><![CDATA[How a fake Google Update Support extension installs its components and runs the editor's own Electron binary as a Node interpreter.]]></description><link>https://unk-deaddrop.hashnode.dev/vsix-editor-execution</link><guid isPermaLink="true">https://unk-deaddrop.hashnode.dev/vsix-editor-execution</guid><category><![CDATA[Security]]></category><category><![CDATA[vscode extensions]]></category><category><![CDATA[Malware]]></category><category><![CDATA[Electron]]></category><category><![CDATA[cybersecurity]]></category><dc:creator><![CDATA[Ilja Krasnjanski]]></dc:creator><pubDate>Mon, 02 Nov 2026 07:04:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae7ec33f80852d1e222effb/f6df33c9-b3df-49f4-9710-c78f09b49481.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The extension calls itself <strong>Google Update Support</strong> and claims to secure and update the development environment, but inside the 17.6 MB package are native agents for macOS and Linux, encrypted Windows components, and code that selects deployment settings. On Windows, one path uses the editor's Electron executable to interpret attacker-controlled JavaScript. The environment variable <code>ELECTRON_RUN_AS_NODE=1</code> enables that mode.</p>
<p>This is the repository-based branch: a project task leads to the extension installer, which supplies the collection pipeline. Part 1's clipboard lure is a separate entry point. Related Windows elevation code appears in both branches.</p>
<p>The July repository review identified five weaponised copies:</p>
<ul>
<li><code>github[.]com/Lyracartoon/predict-together</code></li>
<li><code>github[.]com/pridnet/dscufo-assessment-fe</code></li>
<li><code>gitlab[.]com/pulsynk/rekt-db</code></li>
<li><code>gitlab[.]com/stefan[.]kano/forge-4626-invariants</code></li>
<li><code>gitlab[.]com/predicttogether/forge-4626-invariants</code></li>
</ul>
<p>A sixth copy, <code>gitlab[.]com/lautaro19/rekt-db</code>, contained matching payload markers. The evidence supports likely operator control, not confirmed ownership. GitLab reportedly removed <code>pulsynk-org/rekt-db</code>; <code>pulsynk/rekt-db</code> is a different repository.</p>
<p>The <a href="https://krasforge.github.io/UNK_DeadDrop/indicators/">historical indicator reference</a> records the dated observations, and the <a href="https://krasforge.github.io/UNK_DeadDrop/artifacts/">artifact reference</a> lists hashes for selected task and settings files. Current availability has not been checked.</p>
<p><a href="https://www.proofpoint.com/us/blog/threat-insight/dont-fear-repo-unkdeaddrop-phishing-campaign-targets-developers-steal">Proofpoint's UNK_DeadDrop analysis</a> documents the VSIX route and Windows architecture also present in this package, while Jamf Threat Labs has separately documented <a href="https://www.jamf.com/blog/threat-actors-expand-abuse-of-visual-studio-code/">threat actors abusing Visual Studio Code</a>, and the editor's own <a href="https://code.visualstudio.com/docs/debugtest/tasks">task documentation</a> and Electron's <a href="https://www.electronjs.org/docs/latest/api/environment-variables">environment-variable reference</a> describe the two legitimate features this chain repurposes.</p>
<h2>A <code>folderOpen</code> task points to the installer</h2>
<p>The repository task uses <code>runOn: folderOpen</code> and points into an ordinary-looking project directory. Editor trust and task permissions gate execution.</p>
<p>After installation, the extension is no longer confined to the project; it declares startup activation, while surrounding scripts clean up the task and staging files, and deleting the clone leaves the installed extension outside that cleanup.</p>
<img src="https://krasforge.github.io/UNK_DeadDrop/diagrams/02-vsix.png" alt="How the editor becomes an interpreter" style="display:block;margin:0 auto" />

<p><em>Figure 1. The repository installs an extension whose Windows branch invokes the editor's Electron runtime in Node mode.</em></p>
<p>The extension manifest contains these fields:</p>
<pre><code class="language-json">{
  "name": "google-update-support",
  "displayName": "Google Update Support",
  "publisher": "google-dev-tools",
  "version": "1.0.0",
  "main": "./extension.js",
  "activationEvents": ["onStartupFinished"]
}
</code></pre>
<p>The installed identifier is <code>google-dev-tools.google-update-support</code>. These names are part of the masquerade; they establish neither Google authorship nor a trusted signature. The VSIX collected on 22 July 2026 is 17,647,582 bytes. <a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E03.pdf">Exhibit E03</a> records the package, nested archive and native-agent hashes:</p>
<pre><code class="language-text">SHA-256
52886aab179f26421678ff23af1b0fabf0a17ffbb534369cdbbac8008cbed8e7
</code></pre>
<p>The <code>onStartupFinished</code> declaration gives the extension an opportunity to activate after the original workspace is gone. Payload startup still depends on the platform-specific code path.</p>
<p>The extension exposes its settings under the <code>googleUpdateSupport</code> namespace:</p>
<pre><code class="language-text">googleUpdateSupport.agentZipRelativePath
googleUpdateSupport.windowsActivationMode
googleUpdateSupport.companyWalletServerUrl
googleUpdateSupport.companyWalletClientId
googleUpdateSupport.companyWalletAgentToken
</code></pre>
<p>The deployment reads server and client values from configuration, so the same VSIX can be reused with different workspace settings. Preserve the package and project configuration together: a VSIX hash identifies the archive but may not reveal where a particular installation was configured to send data.</p>
<p>The nested agent archive contains Go binaries for macOS arm64, macOS amd64 and Linux amd64, and their hashes match samples identified in the case's comparison with prior reporting, but the Windows branch does not depend on dropping those binaries. The encrypted Windows modules and their recovered hashes are documented in <a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E04.pdf">E04</a>.</p>
<h2>Three encrypted scripts inside the VSIX</h2>
<p>Static deobfuscation recovered three readable components from the package:</p>
<ul>
<li><code>windows-js-pipeline.js</code> coordinates collection, elevation, upload and cleanup.</li>
<li><code>windows-agent-node.js</code> implements the JavaScript collection agent.</li>
<li><code>detect_malware.py</code> handles browser-related key and credential decryption.</li>
</ul>
<p>The Node source describes a Windows route that does not depend on a Go executable. A detector limited to native-agent hashes would miss this path. The Python component contains DPAPI and browser elevation-service logic, including App-Bound Encryption handling, while the coordinator attempts to obtain the privileges and execution context this stage needs, and the <a href="https://krasforge.github.io/UNK_DeadDrop/articles/03-acorn-deobfuscation/">Acorn analysis</a> describes their recovery through static deobfuscation and authenticated decryption.</p>
<p>The recovered builder constructs a PowerShell script that requests an elevated COM object associated with CMSTPLUA and the ICMLuaUtil interface. The identifiers are assembled from numeric fields, so searching for a literal GUID is less useful than tracing how the script builds and calls them, and it then creates a delegate at runtime to dispatch the shell-execution method.</p>
<p>Similar builder code appears in the ClickFix stage-three script, linking the implementations across the two delivery routes. <a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E19.pdf">E19</a> records the relevant source spans.</p>
<p>The failure paths show that elevation may not succeed. The VSIX pipeline can fall back to RunAs, which may display a consent prompt, while ClickFix retries and uses a sentinel to check for completion, and success depends on Windows version, user privileges, policy and process context. These paths were analyzed statically. Their runtime outcomes were not reproduced.</p>
<h3>The extension launches the editor binary as Node</h3>
<p>The source sets <code>ELECTRON_RUN_AS_NODE</code> and invokes the editor executable with an agent-script path. <code>process.execPath</code> supplies the executable location from inside the extension. The code uses the editor binary as a Node interpreter for collection code supplied by the chain, rather than its normal startup code, and MITRE ATT&amp;CK tracks this behavior as <a href="https://attack.mitre.org/techniques/T1218/015/">Electron Applications (T1218.015)</a>.</p>
<p>A process name or publisher check is incomplete here. A legitimate signature authenticates the editor executable, not every script passed to it. On an endpoint, investigate the transition: the editor launching an elevation helper, then an unexpectedly elevated interpreter with a collection-script argument, then access to browser or wallet data. Decryption remains a separate condition. Elevation alone does not prove that a password was recovered.</p>
<h2>Recruitment email in the clean repository</h2>
<p>The weaponised <code>predict-together</code> copy has a clean twin with its own recruitment material. An email template stored beside the clean version shows that the operators also addressed designers, project managers, founders and builders, and it invites them to review an open-source market-analysis platform in Cursor.</p>
<p>The template describes four steps, taking "about 30 minutes":</p>
<ol>
<li>Clone the repository.</li>
<li>Open it in Cursor and analyze it with AI.</li>
<li>Write a few prompts.</li>
<li>Send the prompts back.</li>
</ol>
<p>The message says, "No coding needed," and offers payment, a Cursor Ultra license or a place on the team for useful feedback. Its repository link is a placeholder. The copy sent to any particular recipient remains unknown.</p>
<p>The public repository is a working FastAPI and React application, with seeded demo users and green test and coverage badges, and I reviewed its full tree, including dot-directories, and found no code that would run on its own.</p>
<p>The review found no <code>.vscode/tasks.json</code>, <code>.cursor/</code> rules, <code>mcp.json</code>, <code>.devcontainer/</code> hooks or workflows; the Python and npm dependencies came from ordinary package registries and had no install scripts, and the documentation showed standard install, test and run commands.</p>
<p>The repository's <code>.gitignore</code> also excludes <code>.vscode/</code>. The copy with the folder-open task belongs to a different account. The template presents the clean repository for inspection, while the weaponised copy is a separate artifact.</p>
<p>The email targets people who are not being asked to write code. A developer doing a coding exercise may inspect the project and notice an unusual task or install hook, while someone told that no coding is needed may instead open the repository in an AI editor and let its agent act without reading the project. The retained weaponised copy used a folder-open task.</p>
<p>Other editor-based routes are possible, including devcontainer hooks, workspace rules or instructions, an MCP server supplied by the workspace, and a dependency that runs when the agent is asked to start the project. None of those appeared in the copies reviewed here.</p>
<p>The review covered the public repository and the retained weaponised copy, not a message received by a target; the exact repository and configuration sent to any individual therefore remain unknown. The public reports cited in this series describe developer-directed assessments. They do not describe this non-coder invitation.</p>
<p>Before opening an unsolicited project in an AI editor:</p>
<ul>
<li>Use a disposable machine.</li>
<li>Turn off automatic task execution and agent auto-run.</li>
<li>Disable workspace-supplied MCP configuration.</li>
<li>Keep wallet keys, SSH keys, browser profiles and <code>.env</code> secrets off it.</li>
</ul>
<h2>Inspect the extension path and process arguments</h2>
<p>Repository-only review can miss the installed extension. A hunt limited to native binaries can miss the JavaScript Windows branch. Preserve task definitions, editor and agent configuration directories, workspace settings, extension metadata, script arguments, process ancestry and any resulting persistence. The extension identifier and package hash provide exact selectors. The editor-to-interpreter transition gives responders a behavior to investigate when names or builds change.</p>
<p>The companion repository has a <a href="https://github.com/KrasForge/unk-scanner/blob/main/rules/yara/deaddrop_vsix_manifest.yar">YARA rule for the recovered VSIX manifest</a>. It must scan extracted archive members.</p>
<p>The companion scanner covers JavaScript. Its <code>electron-run-as-node</code> rule reports the recovered pipeline at error level because that file writes a PowerShell script, sets <code>$env:ELECTRON_RUN_AS_NODE</code> and launches it with a hidden window. The scanner also reports the extension's obfuscated string array as recovered.</p>
<p>Setting the variable alone is not enough to raise an error. In a benchmark of 3,000 popular packages and extensions, TypeScript's language server, SSH proxy shims and local helper servers launched the editor binary as Node in sixteen places. None hid the launch.</p>
<p>The repository has no process or file-event rules because authentic endpoint logs were not retained for replay. The manifest rule identifies package metadata. Confirming execution and subsequent collection requires endpoint records.</p>
<hr />
<p><em>Originally published on the</em> <a href="https://krasforge.github.io/UNK_DeadDrop/articles/02-vsix-editor-execution/"><em>UNK_DeadDrop research site</em></a><em>.</em></p>
<p><strong>References</strong></p>
<ul>
<li><strong>Campaign paper:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf">UNK_DeadDrop: An Artifact-Based Study of Recruitment Malware</a> (PDF)</li>
<li><strong>Scanner paper:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf">Static AST-Based Triage of Socially-Engineered Developer Lures</a> (PDF)</li>
<li><strong>Scanner:</strong> <a href="https://github.com/KrasForge/unk-scanner">unk-scanner on GitHub</a>, with rules and inert fixtures</li>
<li><strong>Series:</strong> <a href="https://krasforge.github.io/UNK_DeadDrop/">all fourteen parts on the UNK_DeadDrop research site</a></li>
</ul>
]]></content:encoded></item></channel></rss>