TJCTF 2026 — loud-packets Çözüm Yazısı
Kendini Bluetooth dosya transferi gibi gösteren bir pcap aslında 459 UDP paketine bölünmüş bir WAV dosyası taşıyor. Ses bir protokol değil — bir Hellschreiber bitmap'i; spektrogram da çözücünün ta kendisi.
TJCTF 2026 — loud-packets
Çözücüyü aracın değil, gözlerinin olduğu bir adli bilişim sorusu.
- Etkinlik: TJCTF 2026
- Kategori: Adli Bilişim (Forensics)
- Dosya:
chall.pcap - Bayrak formatı:
tjctf{...}
1. Yakalama Bluetooth değil
Açıklama, hassas bir dosyanın "bluetooth üzerinden" transfer edildiğini söylüyor. Bu bir yanıltmaca. Yakalamayı açtığınızda HCI yok, L2CAP yok, SDP yok — sadece IPv4 üzerinden 509 UDP çerçevesi var.
eth frames:509
ip frames:509
udp frames:509Akışa göre ayırdığınızda iki çok farklı şey görünür:
- 459 "ana" paket
192.168.1.100:50000 → 192.168.1.200:62000, her biri tam 608 bayt. - 50 "yem" paketi rastgele
10.0.0.xadreslerinden, rastgele görünen port ve yüklerle.
Yem paketleri gürültüden ibaret. Ana akış ise gerçek bir veri akışı.
2. Dosyayı yeniden birleştirme
Her ana paketin yükü aynı 8 baytlık başlıkla başlıyor:
42 54 41 56 BTAV sihirli imza (magic)
00 00 00 ae sıra numarası (big-endian u32)
... 600 bayt parça verisiBTAV, yazarın "Bluetooth Audio" anlamına gelen bir göz kırpması. Sonraki dört bayt, 0..458 aralığında kesintisiz bir sıra numarası (459 değer, boşluk yok). Başlıktan sonraki baytlar tek bir dosyanın parçası: son paket 428 bayt olmak üzere her biri 600 bayt. Toplam 458 * 600 + 428 = 275228 bayt.
8 baytlık başlığı at, sıra numarasına göre sırala, birleştir:
from scapy.all import rdpcap, UDP
def carve(pcap):
chunks = {}
for p in rdpcap(pcap):
if UDP not in p:
continue
if (p[UDP].sport, p[UDP].dport) != (50000, 62000):
continue
data = bytes(p[UDP].payload)
if data[:4] != b"BTAV":
continue
seq = int.from_bytes(data[4:8], "big")
chunks[seq] = data[8:]
return b"".join(chunks[i] for i in sorted(chunks))
blob = carve("chall.pcap")
open("ordered.wav", "wb").write(blob)Sonuç geçerli bir WAV: RIFF başlığı, fmt bloğu, data bloğu — 44.1 kHz, mono, 16 bit PCM, 3.12 saniye. İlk parça, BTAV başlığından hemen sonra RIFF....WAVEfmt ile başlıyor.
3. Ses bir protokol gibi görünüyor
WAV'ı çaldığınızda hızlı, kesik kesik çok tonlu patlamalar duyarsınız. Spektrogramda ise neredeyse fazla düzenli: yedi yatay taşıyıcı, hepsi ~440 Hz'in temiz katlarına oturuyor:
880 1320 1760 2200 2640 3080 3520 HzYaklaşık 26 ayrık patlama var, araları yaklaşık 120 ms. İnsanı MFSK, DTMF, ggwave, SSTV gibi çözücülere yöneltiyor — ama hiçbiri bir şey çözemiyor. Nedeni basit: ortada sembol tablosu yok. Ses, metni bir protokolle kodlamıyor; metni çiziyor.
4. Hellschreiber — resim protokolü
Hellschreiber, bir karakteri küçük bir bitmap olarak, sütun sütun boyayarak iletir. Standart yazı tipi 7 piksel yüksekliğindedir; buradaki yedi taşıyıcı tam olarak o yedi satırdır. Her patlama bir karakter hücresidir:
- Karakter hücresi = 120 ms genişliğinde, her biri 20 ms'lik 6 sütundan oluşur.
- Bir sütunda yanan piksel = karşılık gelen taşıyıcı tonun aktif olması.
- Yalnızca en düşük taşıyıcının (880 Hz) yandığı sütun = alt satırda tek nokta = alt çizgi karakteri.
3.12 saniye boyunca 26 karakter hücresi. Spektrogram çözücüdür — render et ve noktaları oku.
5. Resmi çizmek
İşin sırrı yatay çözünürlükte. Matplotlib'e sütun adımı birkaç örnek olan (neredeyse tam örtüşmeli) bir spektrogram verip figürü genişçe uzatın:
import wave, numpy as np
import matplotlib.pyplot as plt
with wave.open("ordered.wav") as w:
sr = w.getframerate()
data = np.frombuffer(w.readframes(w.getnframes()), dtype=np.int16).astype(np.float32)
Pxx, freqs, t = plt.specgram(data, NFFT=2048, Fs=sr, noverlap=2048 - 4, mode="magnitude")[:3]
plt.close()
fm = (freqs >= 700) & (freqs <= 4000)
img = 20 * np.log10(Pxx[fm, :] + 1e-10)
img -= img.max()
fig, ax = plt.subplots(figsize=(48, 8))
ax.imshow(-img, aspect="auto", origin="lower", cmap="gray",
extent=[t[0], t[-1], freqs[fm][0], freqs[fm][-1]], vmin=0, vmax=35)
plt.tight_layout()
plt.savefig("flag.png", dpi=180)noverlap=2048-4, pencereyi 4 örnek kaydırır; bu da boyanmış glifleri ayırt etmeye yetecek sütun sayısını verir. Renk haritasını ters çevirmek (-img) noktaları açık zeminde koyu çizer; siyah üstünde parlayan kehribardan çok daha okunaklıdır. Yedi taşıyıcı satırı, blok yazı tipinin yedi satırına dönüşür ve bayrak orada, 7 piksel yüksekliğinde harflerle durur.
6. Bayrak
tjctf{v3ry_l0ud_pc4p_f1le}26 karakter, 26 patlama hücresi ve alt çizgiler, yalnızca en düşük taşıyıcının yandığı üç patlamayla bire bir örtüşüyor.
Çıkarılacak ders: bir spektrogramda belirgin bantlanma ve sütun yapısı varsa, çözmeye çalışmadan önce okumayı deneyin. Hellschreiber, spektrogram sanatı ve MFSK aynı özelliği paylaşır — ortada çözücü yoktur, çünkü resmin kendisi zaten mesajdır.
Ne düşünüyorsun?
Tepki bırakarak geri bildirim ver