Malware shellcode delivery via signal - part 4. Embedding Bell 202 FSK into MP3 and recovering the symbol clock. Python and C example
﷽
Hello, cybersecurity enthusiasts and white hackers!

In part 3, we made the live Bell 202 FSK receiver much more reliable. We fixed ALSA overruns and searched several sub-bit offsets instead of assuming that capture starts exactly at a symbol boundary.
In this part, I want to change the transport. Instead of generating a standalone modem-like signal, I will mix the same framed FSK stream into a normal music file, encode the result as MP3, play it, and recover the frame on the receiving side.
This is not an MP3 parser exploit. The media player only produces sound. A separate receiver must already be running and listening to the audio channel. Therefore, this experiment demonstrates an audio data channel, not automatic code execution by an MP3 player.
protocol
The protocol remains compatible with part 3:
0xAA 0xAA 0xAA 0xAA 0x7E | uint16 length | payload | XOR checksum
Bits are transmitted LSB-first using two frequencies:
bit 0 -> 1200 Hz
bit 1 -> 2200 Hz
sample rate -> 48000 Hz
baud rate -> 300
nominal samples per bit -> 160
For bit (b), the generated signal is
\[s_b[n] = \sin\left(2\pi\frac{f_b}{F_s}n + \phi_n\right), \qquad f_b = \begin{cases} 1200, & b=0,\\ 2200, & b=1. \end{cases}\]The phase is continued between symbols to avoid unnecessary clicks. The FSK frame is mixed with the decoded music samples:
\[y[n] = x[n] + \alpha s[n], \qquad \alpha = 10^{G/20},\]where (x[n]) is the music, (s[n]) is the FSK signal, and (G) is the selected FSK level in dBFS. The practical default in this experiment is -12 dBFS.
building the MP3
The Python transmitter performs the following operations:
- Decode the source music to stereo
48 kHzfloating-point PCM using FFmpeg. - Build the framed Bell 202 signal.
- Insert three copies of the frame with
250 msgaps. - Mix the signal into the music starting at
2 seconds. - Normalize the result to prevent clipping.
- Encode it using
libmp3lameat320 kbit/s. - Decode the resulting MP3 again and verify that at least one complete frame can still be recovered.
The important part is the final verification. Creating an MP3 without an encoder error proves almost nothing: lossy compression may damage the two FSK tones. The transmitter accepts the result only when the complete post-compression frame is recovered byte-for-byte.
for gain in gain_candidates:
mixed = mix_signal(carrier, signal, offset_samples, gain)
encode_mp3(mixed, output_path, args.bitrate)
verification = verify_mp3(output_path, frame, expected_starts)
if verification is not None:
break
if verification is None:
raise RuntimeError(
"MP3 was created, but the FSK frame did not survive compression"
)
Create the demo file:
python3 transmit_live.py bongo.mp3 demo.mp3
Example output:

why the part 3 receiver failed
The part 3 receiver assumes that every symbol occupies exactly 160 captured samples. That assumption is correct for a generated PCM array, but not always for a complete playback path:
MP3 decoder -> audio server -> DAC -> speaker -> microphone -> ADC -> ALSA
The playback and capture devices have independent clocks. Even when both devices report 48000 Hz, their physical clocks are not identical. A small error accumulates across the frame, moving the Goertzel window toward the boundary between two symbols.
There was also a simpler logic bug. The transmitter writes three copies, but the part 3 receiver stops searching after the first valid preamble. If that frame has a bad checksum, copies two and three are never examined at the same alignment.
symbol-clock recovery
Part 4 first performs a coarse search using 32 offsets with a 5-sample step. After locating an approximate preamble, it uses the known 40 preamble bits to refine two parameters:
where (\tau) is the frame start, (T) is samples per bit, (e_k) is the expected preamble bit, and (P_f) is the Goertzel power at frequency (f). In the implementation:
start correction: -12 ... +12 samples
symbol period: 158.0 ... 162.0 samples
step: 0.1 sample
For each symbol, the receiver compares the two Goertzel powers:
\[\hat{b}_k = \begin{cases} 1, & P_{2200}(k) > P_{1200}(k),\\ 0, & \text{otherwise}. \end{cases}\]Only the central 84% of a symbol is analyzed. Removing the edges reduces contamination from adjacent symbols, timing errors, and short room echoes.
The updated receiver also captures 12 seconds and continues after a checksum failure, so every repeated frame can be tested.
demo and decoding test
Compile the receiver:
gcc -O2 receiver.c -o receiver -lasound -lm

For a deterministic test, decode the first 12 seconds of the MP3 to the exact raw format expected by the receiver:
ffmpeg -t 12 -i demo.mp3 -f s16le -acodec pcm_s16le -ar 48000 -ac 1 demo.s16le

./receiver --raw demo.s16le
The --raw mode is decode-only: it verifies and prints the recovered bytes but deliberately skips execution. On my generated demo.mp3, the result is:

This proves that the complete frame survives real MP3 encoding and decoding. It does not, by itself, prove reliability through arbitrary speakers, microphones, rooms, or volume levels. Those introduce a separate acoustic channel, and success depends mainly on signal-to-noise ratio, frequency response, echo, and clock mismatch.
For the live experiment, start the receiver and then play the MP3 immediately:
./receiver default
ffplay -nodisp -autoexit demo.mp3

I might add something interesting and show it at my next conference presentation. For a conference demo, an ALSA loopback is more reproducible than an open-air microphone. The exact device numbers must be checked using aplay -l and arecord -l.
defensive perspective
This channel is not invisible. A defender can detect persistent energy around the two carrier frequencies, search decoded audio for the repeated preamble, or alert when an audio-capturing process later allocates executable memory. The strongest detection combines both sides: unusual spectral structure in the media channel and suspicious behavior in the receiver process.
conclusion
Part 3 solved coarse symbol alignment for a live FSK signal. Part 4 adds a lossy music container and deals with the new failure modes: MP3 distortion, repeated frames, delayed playback, symbol-clock drift, and boundary interference.
The key engineering rule is simple: never claim that a transport works merely because the output file was created. Decode the final artifact, recover the complete frame, and verify its checksum.
The complete source code is available in the signal-malware-delivery-poc repository.
demo video - telegram
This is a practical case for educational purposes only.
Thanks for your time, happy hacking and good bye!
PS. All drawings and screenshots are mine