← Back to Writeups
CUSTOMMediumSteganography

Short Term Fuel Trim

XESXOR9/2/20264 min read
#steganography#custom#medium#audio_reconstruction#inverse_stft#speech_to_text

Short Term Fuel Trim

Platform: tamuctf | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-03-21 | Status: Solved Techniques: audio_reconstruction, inverse_stft, speech_to_text

Summary

Task: Text file with complex numbers representing STFT coefficients (Short-Time Fourier Transform). Solution: Inverse STFT to reconstruct audio, then speech recognition with Whisper to transcribe the spoken flag.

Short Term Fuel Trim — TAMUctf

Description

Some stupid numbers or something. Not really sure what they're for, maybe you can figure it out?

NOTE: The flag is fully lowercase!

Files: short-term-fuel-trim.tar.gz containing numbers.txt

The archive contains a 9.7MB text file with 178,021 lines of complex numbers.

Analysis

Initial Recon

Extracting the archive reveals numbers.txt — a large file with complex numbers:

# STFT shape: complex64 (129, 1380)
(0.0017089844+0j)
(-0.0010986328-0.00048828125j)
(0.00048828125+0.00024414062j)
...

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key observations:

  1. First line is a comment revealing the data structure: STFT shape: complex64 (129, 1380)
  2. STFT = Short-Time Fourier Transform — the task name "Short Term Fuel Trim" is a clever wordplay!
  3. Shape (129, 1380) means 129 frequency bins and 1380 time frames
  4. 129 bins suggests nperseg=256 (since 256/2+1 = 129)
  5. 178,020 complex numbers = 129 * 1380 = 177,720 (matches!)

Understanding STFT

The Short-Time Fourier Transform converts a time-domain signal into frequency-domain representation over time. Given STFT coefficients, we can reconstruct the original audio using the inverse STFT (iSTFT). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Parameters deduced:

  • nperseg = 256 (window size, giving 129 frequency bins)
  • fs = 22050 Hz (common sample rate for speech)
  • Duration: ~8 seconds

Solution

Step 1: Parse Complex Numbers

python
#!/usr/bin/env python3
"""
Inverse STFT to reconstruct audio from frequency-domain data
"""

import numpy as np
from scipy.signal import istft
import scipy.io.wavfile as wavfile

# Parse complex numbers from file
with open('numbers.txt', 'r') as f:
    lines = f.readlines()

# Skip comment line, parse complex numbers
complex_nums = []
for line in lines[1:]:  # skip "# STFT shape: complex64 (129, 1380)"
    line = line.strip().lstrip('(').rstrip(')')
    if line:
        complex_nums.append(complex(line.replace(' ', '')))
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

print(f"Parsed {len(complex_nums)} complex numbers")
# Output: Parsed 177720 complex numbers

Step 2: Reconstruct Audio with Inverse STFT

python
# Reshape to STFT matrix (129 freq bins x 1380 time frames)
stft_matrix = np.array(complex_nums, dtype=np.complex64).reshape(129, 1380)

# Perform inverse STFT
# fs=22050 is standard for speech, nperseg=256 gives 129 bins
t, x = istft(stft_matrix, fs=22050, nperseg=256)

print(f"Reconstructed audio: {len(x)} samples, {len(x)/22050:.2f} seconds")
# Output: Reconstructed audio: 176896 samples, 8.02 seconds
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Normalize and save as WAV
x_normalized = x / np.max(np.abs(x)) * 0.9
x_int16 = (x_normalized * 32767).astype(np.int16)
wavfile.write('output.wav', 22050, x_int16)

Step 3: Spectrogram Analysis

python
import matplotlib.pyplot as plt

# Generate spectrogram to visualize
plt.figure(figsize=(14, 6))
plt.specgram(x, Fs=22050, NFFT=1024, noverlap=512, cmap='viridis')
plt.xlabel('Time (s)')
plt.ylabel('Frequency (Hz)')
plt.title('Reconstructed Audio Spectrogram')
plt.colorbar(label='Intensity (dB)')
plt.savefig('spectrogram.png', dpi=150)
plt.show()

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The spectrogram shows typical speech formant patterns — this is spoken audio, not visual steganography.

Step 4: Speech Recognition with Whisper

python
import whisper

# Load Whisper model
model = whisper.load_model("medium")

# Transcribe
result = model.transcribe("output.wav")
print(result["text"])

Whisper output:

G I G E M left curly bracket FFT underscore IS underscore five zero underscore zero P right curly bracket

Decoding the Flag

The spoken text spells out the flag character by character:

  • "G I G E M" → gigem
  • "left curly bracket" → {
  • "FFT underscore IS underscore" → fft_is_
  • "five zero underscore zero P" → 50_0p
  • "right curly bracket" → } ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Leet speak interpretation: 50_0p = "so op" (5=s, 0=o), meaning "FFT is so OP" (overpowered)!

Complete Solution Script

python
#!/usr/bin/env python3
"""
TAMUctf 2026 - Short Term Fuel Trim
Inverse STFT audio reconstruction + speech recognition
"""

import numpy as np
from scipy.signal import istft
import scipy.io.wavfile as wavfile

# Parse complex numbers
with open('numbers.txt', 'r') as f:
    lines = f.readlines()

complex_nums = []
for line in lines[1:]:
    line = line.strip().lstrip('(').rstrip(')')
    if line:
        complex_nums.append(complex(line.replace(' ', '')))
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Reshape and inverse STFT
stft_matrix = np.array(complex_nums, dtype=np.complex64).reshape(129, 1380)
t, x = istft(stft_matrix, fs=22050, nperseg=256)

# Save WAV
x_normalized = x / np.max(np.abs(x)) * 0.9
x_int16 = (x_normalized * 32767).astype(np.int16)
wavfile.write('output.wav', 22050, x_int16)

print("Audio saved to output.wav")
print("Use Whisper or any speech recognition to transcribe:")
print("  whisper output.wav --model medium")

# Alternative: use whisper directly
try:
    import whisper
    model = whisper.load_model("base")
    result = model.transcribe("output.wav")
    print(f"\nTranscription: {result['text']}")
except ImportError:
    print("\nInstall whisper: pip install openai-whisper")

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Theory

Short-Time Fourier Transform (STFT)

STFT divides a signal into short overlapping segments and computes the FFT of each segment, producing a 2D time-frequency representation:

  • Frequency bins: nperseg/2 + 1 (for real input)
  • Time frames: depends on signal length and hop size
  • Output: complex numbers representing magnitude and phase

Inverse STFT (iSTFT)

Given STFT coefficients, the original signal can be reconstructed using overlap-add synthesis. The scipy.signal.istft() function handles this automatically. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Common Parameters

ParameterTypical ValuesNotes
fs22050, 44100Sample rate (Hz)
nperseg256, 512, 1024Window size
noverlapnperseg/250% overlap is common

References