Frequency Trap
Challenge ini ngasih file PNG bernama frequency_trap.png. Dari tampilannya sih cuma gambar biasa, tapi metadata-nya langsung ngasih clue kalau ada steganography di domain frekuensi: Image Description : Method: YCbCr_DCT_8x8_coeff3x3 Jadi intinya: ambil password dari EXIF, ubah gambar ke YCbCr, pecah per blok 8×8, lalu cek tanda (positif/negatif) dari koefisien DCT. Ada jebakan kecil juga: satu koefisien ngasih flag yang kelihatan valid, tapi ternyata decoy.
Frequency Trap
Triage awal dan bukti
Hash file yang dianalisis:
eccf044f99586cb881f566d33dd34b84c50b79f3ad470473b08491e3d90f5071 frequency_trap.png
Mulai dari cek file dan metadata dulu:
file frequency_trap.png
exiftool frequency_trap.png
Bagian output yang penting:
~/ctf/tracebash/forne
vol3 ❯ file frequency_trap.png 17:51:21
frequency_trap.png: PNG image data, 2500 x 1996, 8-bit/color RGB, non-interlaced
~/ctf/tracebash/forne
vol3 ❯ exiftool frequency_trap.png 17:53:11
ExifTool Version Number : 13.55
File Name : frequency_trap.png
Directory : .
File Size : 1169 kB
File Modification Date/Time : 2026:06:23 22:36:32+07:00
File Access Date/Time : 2026:06:25 15:28:19+07:00
File Inode Change Date/Time : 2026:06:26 17:25:25+07:00
File Permissions : -rw-rw-rw-
File Type : PNG
File Type Extension : png
MIME Type : image/png
Image Width : 2500
Image Height : 1996
Bit Depth : 8
Color Type : RGB
Compression : Deflate/Inflate
Filter : Adaptive
Interlace : Noninterlaced
Exif Byte Order : Big-endian (Motorola, MM)
Image Description : Method: YCbCr_DCT_8x8_coeff3x3
Lens Model : ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.++++++++++++.-------------.++++++++++++.++++.----------------.+++++++++.-----------.++++++++++++++++++++++.---------.---------------.++++++++++++++++++..
Image Size : 2500x1996
Megapixels : 5.0
Dari Image Description, kita dapat roadmap-nya:
convert gambar ke YCbCr;
bagi jadi blok 8×8;
hitung 2D DCT untuk tiap blok;
ambil data dari koefisien DCT, bukan dari pixel yang kelihatan.
Ukuran gambarnya 2500×1996. Jadi jumlah blok 8×8 yang utuh adalah floor(1996 / 8) × floor(2500 / 8) = 249 × 312 = 77.688 blok. Sisa pixel yang tidak cukup membentuk blok 8×8 tinggal di-skip saja.
Ambil key dari EXIF
Lens Model jelas bukan nama lensa. Isinya cuma karakter Brainfuck seperti +, -, dan .. Kalau dijalankan sebagai Brainfuck, kita langsung cek di Brainfuck Language - Online Decoder, Translator, Interpreter hasilnya:
frequencypass
Nah, ini adalah key untuk repeating XOR nanti.
Ekstraksi DCT
Untuk setiap blok 8×8 yang utuh di channel Y (luminance):
kurangi semua nilai pixel dengan 128 supaya terpusat di nol;
hitung normalized 2D DCT;
ambil tanda koefisien AC(3,3);
positif jadi bit
1, negatif atau nol jadi bit0;baca blok dari kiri ke kanan, lalu atas ke bawah;
gabungkan tiap delapan bit menjadi satu byte, MSB dulu;
XOR hasil byte dengan key berulang
frequencypass.
Catatan kecil tapi penting: indexing Python itu mulai dari nol. Jadi dct_block[3, 3] artinya baris keempat dan kolom keempat dari matriks DCT 8×8.
Kenapa flag pertama salah?
Awalnya, kalau memakai koefisien AC(1,1), mapping kebalik (positif → 0, negatif → 1), lalu di-XOR dengan key yang sama, hasilnya:
# sebelumnya, decoy:
ac_11 = dct_block[1, 1]
bits.append(0 if ac_11 > 0 else 1)
# seharusnya, valid:
ac_33 = dct_block[3, 3]
bits.append(1 if ac_33 > 0 else 0)
# hasil decoy: TBCTF{gRequency_trap_successful}
Kelihatannya meyakinkan banget, kan? Tapi flag ini memang jebakan. Huruf R besar di gRequency bukan typo random; itu petunjuk kalau kita belum ada di stream yang benar. Submit flag ini juga ditolak.
Clue coeff3x3 mengarah ke koefisien (3,3). Dengan posisi itu dan polaritas yang benar (positif → 1), hasilnya bersih: frequency semuanya huruf kecil. Jadi ya, jangan ketipu flag cakep yang terlalu cepat muncul.
Solver
Bagian inti ekstraksinya:
img = cv2.imread("frequency_trap.png")
y_channel = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)[:, :, 0]
bits = []
for row in range(y_channel.shape[0] // 8):
for col in range(y_channel.shape[1] // 8):
block = y_channel[row * 8:(row + 1) * 8,
col * 8:(col + 1) * 8].astype(float) - 128.0
coeffs = dct(dct(block.T, norm="ortho").T, norm="ortho")
bits.append(1 if coeffs[3, 3] > 0 else 0)
data = bytearray(
sum(bit << (7 - offset) for offset, bit in enumerate(bits[index:index + 8]))
for index in range(0, len(bits) - len(bits) % 8, 8)
)
key = b"frequencypass"
plaintext = bytes(value ^ key[index % len(key)] for index, value in enumerate(data))
print(plaintext.split(b"}", 1)[0].decode() + "}")
Verifikasi
Saat script dijalankan, output-nya:
[+] Decrypted Flag Found:
TBCTF{frequency_trap_successful}
Flag
TBCTF{frequency_trap_successful}