---
# System prepended metadata

title: Hology 8 2025 CTF — Writeup (Quals)

---

# Hology 8 2025 CTF — Writeup (Quals)

---

## Daftar Isi
- Web
  - pyjail?
- Reverse
  - Hidden Factory
  - ObligatoryFlagCheckerThatIsPacked
- Crypto
  - The Architect’s Hasty Encryption
  - p-power-rsa
- Forensic
  - The Track Less Travelled

---

# Web Exploitation

## pyjail?
Oke, jadi ada endpoint `pyjail.php` yang melakukan `eval` python. Biasanya sih ini di bruteforce pake trik MRO, tapi disini ada WAF yang ngeblok karakter krusial (`' " ( ) _`) jadinya kita engga bisa sembarangan.

Dan ternyata saat dicek lebih lanjut, ada beberapa kesalahan dari challnya, flag nya disimpen di root dokumen dan bisa diakses langsung.

**Flag:** `HOLOGY8{h0l1_m0l1_r3g3x_g07_pwn3d??_51ff8a6c}`

**Jadi tinggal kita buka:** `http://ctf.hology.id:8282/flag.txt`

Maaf cara solve nya unintended :(

---

# Reverse Engineering

## 1. Hidden Factory
**Ringkasnya:** pertama kita connect ke `nc ctf.hology.id 1000`, lalu kita akan dapat ciphertext bernama *ENCRYPTED BLUEPRINT*. Sistem enkripsinya ini sendiri terbagi menjadi tiga layer, dan sistem nya harus kita balikan menjadi : 
Stage‑3 → Stage‑2 → Stage‑1.


- **Stage‑1:** Caesar di alfabet kustom (shift +23). Alfabet: `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789{}_` (panjang 65).
- **Stage‑2:** XOR pake nilai yang bergantung pada posisi: untuk index `i`: `val = (i*7 + 23) % 256`, `byte ^= val`.
- **Stage‑3:** Base64 + substitusi alfabet (map custom ke standard sebelum decode).

**Solve:** 
Kita harus reverse Stage‑3 (balikan map + base64 decode) selanjutnya kita reverse Stage‑2 (xor lagi sama nilai yang sama), dan terakhir kita reverse Stage‑1 (shift -23).

**Final script solver nya:**
```py
import base64
STD_ALPH = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
CUST_ALPH = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"
ALPH1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789{}_"
OFFSET = 23
MULT = 7

def stage3_decode(s):
    trans = str.maketrans(CUST_ALPH, STD_ALPH)
    b64 = s.translate(trans)
    return base64.b64decode(b64)

def stage2_unscramble(bs):
    out = bytearray()
    for i, b in enumerate(bs):
        val = (i * MULT + OFFSET) % 256
        out.append(b ^ val)
    return bytes(out)

def stage1_inverse(s):
    out = []
    L = len(ALPH1)
    for ch in s.decode('latin-1'):
        if ch in ALPH1:
            idx = ALPH1.index(ch)
            out.append(ALPH1[(idx - OFFSET) % L])
        else:
            out.append(ch)
    return ''.join(out)

# pake fungsi di atas untuk decrypt ciphertext dari nc
```

**Flag:** `HOLOGY8{f4ct0ry_r3v3rs3_3ng1n33r1ng_m4st3r_0f_th3_h1dd3n_bluepr1nt_cr4ck3r}`

---

## 2. ObligatoryFlagCheckerThatIsPacked
Binary ini dipack menggunakan UPX. Setelah unpack, program akan membaca input 30 byte lalu dicek pake kombinasi konstanta `partA` dan `partB`.

**Inti chall ini:** ada rumus `local_58[i] = partA[i] ^ ROL8(partB[i],3)` yang membandingkan input dengan `local_58 ^ key[i & 7]` dan `key = "easy_key"`. Jadi kita bisa recover flag dengan ekstrak `partA` & `partB` dari binary dan menghitungnya.

**Hal yang harus kita lakukan untuk solve chall ini:**
1. Unpack UPX (atau run di loader terus dump memory).
2. Cari `partA` & `partB` di .rodata.
3. Menghitung `local_58[i] = partA[i] ^ rol8(partB[i],3)` untuk i 0..29.
4. key = b"easy_key" (8 byte), flag[i] = local_58[i] ^ key[i & 7].

**Contoh pseudo:**
```py
def rol8(x, r=3):
    return ((x << r) | (x >> (8 - r))) & 0xFF
# ambil A,B dari binary -> hitung -> dapat flag
```

**Solver**:
```py
import struct


def rol8(x, r=3):
    return ((x << r) | (x >> (8 - r))) & 0xFF


def read_sections(elf):
    e_shoff     = struct.unpack_from("<Q", elf, 0x28)[0]
    e_shentsz   = struct.unpack_from("<H", elf, 0x3A)[0]
    e_shnum     = struct.unpack_from("<H", elf, 0x3C)[0]
    e_shstrndx  = struct.unpack_from("<H", elf, 0x3E)[0]

    sections = []

    for i in range(e_shnum):
        off = e_shoff + i * e_shentsz
        sh  = struct.unpack_from("<IIQQQQIIQQ", elf, off)

        sections.append({
            "name_off":   sh[0],
            "type":       sh[1],
            "flags":      sh[2],
            "addr":       sh[3],
            "offset":     sh[4],
            "size":       sh[5],
            "link":       sh[6],
            "info":       sh[7],
            "addralign":  sh[8],
            "entsize":    sh[9],
        })

    # Read section header string table
    shstr = sections[e_shstrndx]
    shstr_data = elf[
        shstr["offset"] : shstr["offset"] + shstr["size"]
    ]

    def get_name(off):
        end = shstr_data.find(b"\x00", off)
        return shstr_data[off:end].decode()

    for s in sections:
        s["name"] = get_name(s["name_off"])

    return sections


def read_symbols(elf, sections):
    symtab = next(s for s in sections if s["name"] == ".symtab")
    strtab = next(s for s in sections if s["name"] == ".strtab")

    symdat = elf[
        symtab["offset"] : symtab["offset"] + symtab["size"]
    ]
    strdat = elf[
        strtab["offset"] : strtab["offset"] + strtab["size"]
    ]

    symbols = []

    for i in range(symtab["size"] // symtab["entsize"]):
        off = i * symtab["entsize"]

        st_name, st_info, st_other, st_shndx, st_value, st_size = \
            struct.unpack_from("<IbbHQQ", symdat, off)

        end  = strdat.find(b"\x00", st_name)
        name = strdat[st_name:end].decode()

        symbols.append({
            "name":  name,
            "info":  st_info,
            "shndx": st_shndx,
            "value": st_value,
            "size":  st_size,
        })

    return symbols


def get_bytes(elf, sections, symbols, name):
    sym = next(s for s in symbols if s["name"] == name)
    sec = sections[sym["shndx"]]

    file_off = sec["offset"] + (sym["value"] - sec["addr"])
    return elf[file_off : file_off + sym["size"]]


# ============================================================
# Main
# ============================================================

PATH = "unpacked_rev"  # hasil: upx -d

with open(PATH, "rb") as f:
    elf = f.read()

sections = read_sections(elf)
symbols  = read_symbols(elf, sections)

A = get_bytes(elf, sections, symbols, "partA")
B = get_bytes(elf, sections, symbols, "partB")

local58 = bytes(
    A[i] ^ rol8(B[i], 3)
    for i in range(30)
)

key = b"easy_key"

flag = bytes(
    local58[i] ^ key[i & 7]
    for i in range(30)
)

print(flag.decode())

```

**Flag:** `HOLOGY8{n33d3d_4_4_r3v_p4ck3r}`

---

# Cryptography

## 1. The Architect’s Hasty Encryption
Ada 3 RSA artifacts. Dua diantaranya mempunyai shared prime yang mana bisa kita gunakan **gcd** untuk dapetin **p**. Dan artefak ketiga menggunakan **n = p^2** yang mana akan memudahkan kita untuk dekripsi.

**Flow enkripsi nya:**
- p = gcd(nA, nB)
- Kita faktorkan nX (kalo nX == p^2), kita hitung d = e^{-1} mod phi(nX)
- Setelah itu kita dekripsi cX dan kita akan mendapatkan **KEY** nya
- Terakhir, kita ambil **KEY (ASCII)** jadi **HEX**, lalu kita gabung ke p (jadi string), dan sha256. Setelah itu kita akan mendapatkan **flag** nya.

**Hasil SHA256 yang keluar:**  
`7ec2c6c4821ffe2bd9fa2045ef69791a22e595425e2948844e9b1fd3b37d54ca`

**Flag:** `HOLOGY8{7ec2c6c4821ffe2bd9fa2045ef69791a22e595425e2948844e9b1fd3b37d54ca}`

---

## 2. p‑power‑rsa
Chall ini menggunakan RSA dengan modulus yang tidak square‑free, misal `N = p^r * q` (r≥2). Ada hubungan kecil antara dua private exponents, yang bisa dipakai LLL/Coppersmith untuk menemukan delta kecil.

**Garis besar nya:**
- Formulasi masalah jadi root finding linear kecil.
- Kita akan menggunakan LLL atau small-root technique untuk mendapatkan Δ.
- Dari Δ yang kita dapatkan, gcd akan ngebocorin faktor **p^(r−1)** lalu bisa kita recover **p,q** dan kita decrypt.

```py
# ============================================================
# solve_ppower_rsa_fixed.sage
# ============================================================

from sage.all import *

# ------------------------------------------------------------
# Setup
# ------------------------------------------------------------
Z  = ZZ
PR = PolynomialRing

# ------------------------------------------------------------
# Constants from challenge
# ------------------------------------------------------------
c = Integer(
    111485787416215732083419888356058001033979399362109360508
    26187089114840724405691532084205700450578271560281812344730088409
    50208480418896958145264338686293650231691792222492663896258529529
    87468768744093949285183059141092635835298369742277053305386129494
    49893288884757430080838808926832528531554476875824396277630533852
    26221852536414060400397668638390068574218789239859899349649806086
    78162026614939501709616234022560513068727741227451418843199235135
    74909526934129781982520409344915204011945042876058332259941182649
    38664198308269684384412766681923585648346035966663578374146570211
    99051732261588602224173516664537564148394089550429651483809997867
    45067799292206419334487477074750340090409380178087362694809249384
    83778916870232621005585404027295880314360304324547942498123434383
    90584037127772193904170421087845937142186548759745304853458426108
    89547272654404100971318581885200905937761466010011130409221400225
    29357970105883386976827646232264113729591494752134918980265858392
    27437083313605739136374826611550285890516268274465906516027362250
    60976763088927714433438532990708303684257823041138278098690349309
    61352233448908795589570445785916699320291444553085845395422872534
    81580691466811698093274707201630462570730775293023489199764838103
    52375566246137475119895846742635800668869428611373349947620894937
    86972396117163064777826915719498372710037996238034841685083674404
    66097349584557388186877058472216663997659060248313992041943213504
    83011292871077770270415445375268583670974485113451091685851930681
    09172032501486157989738547685799120192688845063502134676407713464
    85176419582269169891572750725266499997539100897292414501918122708
    31903820127718178087134916043563030557962263828373741474240294496
    36142143723965161304999822527648186438619503913331971241426026581
    89297469501831730837739445119194832277196410874479162266849124548
    98849413857255170640387605610788711387174199311750727941171876289
    82386126756769131559394794494188680420063298157018187160344334249
    36492656520443344944013881853487228563694901400446844531668
)

e = Integer(65537)

N = Integer(
    453749993609862423767406315499943576715619270719355402974
    23859805295539492811573655205214935999778624622035669768208587595
    29218143036086983706210015934982026641371056147977060929637666485
    97009961505999304804868628819913053836743203352789790043629722002
    65756125120047946168238784120998943211497549162417738445189139137
    91868779434356749413927932306679870757960053047235867464992349071
    81428238498088293365909082016312817879158926491633438828864279569
    81388044992890319744231405679808294831634533302012433342227101026
    65582407044626577954373312845684702730716237844639808043564070484
    93506788785174496491522489832266993857493571141791095824560928202
    78785391845490692529410810830763245464318129228423813901413965566
    14688558576411125274758109643984898177268867155526515486406552089
    66527802039076290335379020936965043298763839401599171514405533064
    38288924650907511067102750891160442877581241774036912780601511247
    78162240900133870550291471107609587274609296125964886837151575243
    48089512845563356023108705591279503195318426654042354995168730827
    22427577433858935496084389231152779521025662507616153014801430837
    35102054758031266202731220263554484311016232518079148726570946364
    73462128842533375598676295429191094715741380629940237085984351828
    45540267141135904098507938822378845568711349570185162389495695764
    55509255199128563782586379754441399685527831592764818262945580264
    30514802530631451990707908860581231085522384501312007775067802744
    55072012130971156806367205765189457157795566712527784194089612138
    95974778009995100998978024585195694883991499678909222606659665158
    78625384142699235491857754702310410293104365494144520258022000143
    26549526973003116317052964039607035814432505055426282735050697422
    53165941764318391828345884572254968633128186074911835384215030850
    95259381779598083377226530394482769313660477032407154580895878594
    63272220865967511425425870472798001225357792182807761489625539080
    56978900160113279840292213115387898067931606626553838499139436472
    82192959359920313936222580284259387292748269260001904819421
)

# ------------------------------------------------------------
# Derived values
# ------------------------------------------------------------
A = e1 * e2
B = e1 - e2

# ------------------------------------------------------------
# Utilities
# ------------------------------------------------------------
def recover_p_r_q_from_G(G, N):
    """
    Recover p, r, q assuming:
        G = p^(r-1)
        N = p^r * q
    """
    p = gcd(G, N // G)

    if p <= 1:
        fac = factor(G)
        p = max(f[0] for f in fac)

    r  = valuation(N, p)
    pr = p**r
    q  = N // pr

    assert N == pr * q
    return int(p), int(r), int(q)


def decrypt_ppower(c, e, p, r, q):
    """
    RSA decryption for p^r * q modulus
    """
    dp = inverse_mod(e, (p - 1) * p**(r - 1))
    dq = inverse_mod(e, (q - 1))

    mp = power_mod(c, dp, p**r)
    mq = power_mod(c, dq, q)

    return int(crt([mp, mq], [p**r, q]))


# ------------------------------------------------------------
# Coppersmith: Linear small root with unknown divisor
# ------------------------------------------------------------
def small_root_linear_unknown_divisor(
    A, B, N, X,
    m_list=(3, 4, 5, 6),
    t_list=(1, 2, 3, 4),
    scales=(1, 2, 3, 4, 6, 8, 12, 16, 24, 32)
):
    """
    Find small Δ such that:
        A*Δ - B ≡ 0 (mod φ(N))
    using Howgrave-Graham / LLL
    """

    RZ.<x> = PolynomialRing(ZZ)
    RQ.<y> = PolynomialRing(QQ)

    f = A*x - B
    tried = 0

    for m in m_list:
        for t in t_list:
            for s in scales:
                tried += 1
                print(f"[*] Trying m={m}, t={t}, scale={s} (#{tried})")

                Mmod = N * s
                polys = []

                for j in range(t):
                    polys.append((x**j) * (Mmod**m))

                for i in range(1, m + 1):
                    for j in range(m - i + 1):
                        polys.append((x**j) * (f**i) * (Mmod**(m - i)))

                polys_scaled = [p(x * X) for p in polys]
                deg = max(p.degree() for p in polys_scaled)

                rows = [
                    [int(pp[i]) if i <= pp.degree() else 0 for i in range(deg + 1)]
                    for pp in polys_scaled
                ]

                Bmat = Matrix(ZZ, rows)
                Bred = Bmat.LLL()

                for row in Bred.rows():
                    if all(c == 0 for c in row):
                        continue

                    gZ = sum(row[i] * x**i for i in range(len(row)))
                    gQ = RQ(gZ(x=y / X))

                    if gQ.is_zero():
                        continue

                    den = lcm([c.denominator() for c in gQ.coefficients()])
                    GZ = (den * gQ).change_ring(ZZ)

                    for root, _ in GZ.roots(ZZ):
                        root = int(root)
                        if root.bit_length() > X.nbits() + 2:
                            continue

                        Gcand = gcd(N, A*root - B)
                        if 1 < Gcand < N:
                            return root, int(Gcand)

    return None, None


# ------------------------------------------------------------
# Main
# ------------------------------------------------------------
X = Integer(2)**1012

Delta, G = small_root_linear_unknown_divisor(A, B, N, X)

if not Delta:
    print("[!] Retry with more aggressive parameters...")
    Delta, G = small_root_linear_unknown_divisor(
        A, B, N, X,
        m_list=(5, 6, 7),
        t_list=(2, 3, 4, 5),
        scales=(1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 64, 128)
    )

if not Delta:
    raise RuntimeError("Failed to recover Δ")

print(f"[+] Delta = {Delta}")
print(f"[+] G = {G}")

p, r, q = recover_p_r_q_from_G(G, N)
print(f"[+] p = {p}")
print(f"[+] r = {r}")
print(f"[+] q = {q}")

m = decrypt_ppower(c, e, p, r, q)
pt = Integer(m).to_bytes((m.nbits() + 7) // 8, "big")

print("[+] Plaintext (hex):", pt.hex())
print("[+] Plaintext (ascii):", pt.decode(errors="ignore"))

```

**Flag:** `HOLOGY8{Mu17i_P0w3R_RSA_w17h_W34k_Struc7ur3}`

---

# Forensic

## The Track Less Travelled
Chall ini berupa file MP4 yang punya track audio tersembunyi, handlernya bukan standard jadi player biasa tidak akan bisa membukanya. Isinya merupakan AAC‑LC tanpa ADTS header.

**Cara solve chall ini:**
1. Parse MP4 (`ffprobe` / parse box) cari trak yang **sus**.
2. Dapatkan `AudioSpecificConfig` dari `esds`.
3. Dari `stsz` / `stco` / `stsc` dibuat panjang untuk setiap frame, lalu buat header ADTS 7 byte per frame, dan gabungkan menjadi => `track_hidden.aac`.
4. Convert ke WAV, bersihin noise, lalu kita dengarkan audio nya (**yap, ini mau main CTF apa test TOEFL plis)** flag disebutkan satu persatu hurufnya di audio.

**Solver:**
```py
#!/usr/bin/env python3
"""
Extract hidden AAC tracks from a tricky MP4 by reconstructing ADTS headers
"""

import struct
import sys
from pathlib import Path


# ============================================================
# MP4 box helpers
# ============================================================

def find_child(data, parent_off, parent_size, type_name):
    i = parent_off + 8
    end = parent_off + parent_size

    while i + 8 <= end:
        size = struct.unpack(">I", data[i:i + 4])[0]
        typ = data[i + 4:i + 8].decode("latin1")

        if typ == type_name:
            return i, size

        if size == 0:
            break

        i += size

    return None


def parse_boxes(data, start=0, end=None):
    if end is None:
        end = len(data)

    i = start
    boxes = []

    while i + 8 <= end:
        size = struct.unpack(">I", data[i:i + 4])[0]
        typ = data[i + 4:i + 8].decode("latin1")

        if size == 0:
            size = end - i
            header = 8
        elif size == 1:
            size = struct.unpack(">Q", data[i + 8:i + 16])[0]
            header = 16
        else:
            header = 8

        boxes.append((i, size, typ))

        if typ in ("moov", "trak", "mdia", "minf", "stbl", "udta", "meta"):
            child_start = i + header + (4 if typ == "meta" else 0)
            boxes += parse_boxes(data, child_start, i + size)

        i += size

    return boxes


# ============================================================
# Sample table parsing
# ============================================================

def get_stbl_boxes(data, trak_off, trak_size):
    mdia = find_child(data, trak_off, trak_size, "mdia")
    minf = find_child(data, mdia[0], mdia[1], "minf")
    stbl = find_child(data, minf[0], minf[1], "stbl")

    def child(name):
        return find_child(data, stbl[0], stbl[1], name)

    return {
        name: child(name)
        for name in ("stsd", "stts", "stsc", "stsz", "stco", "co64")
    }


def parse_stco(data, off, size):
    entry_count = struct.unpack(">I", data[off + 12:off + 16])[0]
    return [
        struct.unpack(">I", data[off + 16 + 4 * i:off + 20 + 4 * i])[0]
        for i in range(entry_count)
    ]


def parse_stsz(data, off, size):
    sample_size = struct.unpack(">I", data[off + 12:off + 16])[0]
    entry_count = struct.unpack(">I", data[off + 16:off + 20])[0]

    if sample_size != 0:
        return [sample_size] * entry_count

    return [
        struct.unpack(">I", data[off + 20 + 4 * i:off + 24 + 4 * i])[0]
        for i in range(entry_count)
    ]


def parse_stsc(data, off, size):
    entry_count = struct.unpack(">I", data[off + 12:off + 16])[0]
    entries = []

    for i in range(entry_count):
        a = off + 16 + 12 * i
        first_chunk = struct.unpack(">I", data[a:a + 4])[0]
        samples_per_chunk = struct.unpack(">I", data[a + 4:a + 8])[0]
        sample_desc_index = struct.unpack(">I", data[a + 8:a + 12])[0]

        entries.append(
            (first_chunk, samples_per_chunk, sample_desc_index)
        )

    return entries


def build_chunk_samples(stco_list, stsc_entries, stsz_list):
    chunks = []
    sample_index = 0

    for idx, chunk_offset in enumerate(stco_list, start=1):
        entry = None

        for e in stsc_entries:
            if e[0] <= idx:
                entry = e

        samples_per_chunk = entry[1] if entry else 0
        size = stsz_list[sample_index] if samples_per_chunk else 0

        chunks.append((chunk_offset, size))
        sample_index += samples_per_chunk

    return chunks


# ============================================================
# AAC / ADTS helpers
# ============================================================

def find_decoder_specific_info(esds_blob):
    payload = esds_blob[12:]
    i = 0

    if i >= len(payload) or payload[i] != 0x03:
        return None

    i += 1
    length = 0
    while True:
        b = payload[i]
        i += 1
        length = (length << 7) | (b & 0x7F)
        if not (b & 0x80):
            break

    i += 2  # ES_ID
    flags = payload[i]
    i += 1

    if flags & 0x80:
        i += 2
    if flags & 0x40:
        url_len = payload[i]
        i += 1 + url_len
    if flags & 0x20:
        i += 1

    if payload[i] != 0x04:
        return None

    i += 1
    while payload[i] & 0x80:
        i += 1
    i += 14  # decoder config

    if payload[i] != 0x05:
        return None

    i += 1
    slen = 0
    while True:
        b = payload[i]
        i += 1
        slen = (slen << 7) | (b & 0x7F)
        if not (b & 0x80):
            break

    return payload[i:i + slen]


def parse_asc(asc):
    b0, b1 = asc[0], asc[1]
    aot = (b0 >> 3) & 0x1F
    sf_index = ((b0 & 0x07) << 1) | ((b1 >> 7) & 0x01)
    ch_config = (b1 >> 3) & 0x0F
    return aot, sf_index, ch_config


def build_adts_header(frame_length, aot, sf_index, ch_config):
    profile = aot - 1
    hdr = bytearray(7)

    hdr[0] = 0xFF
    hdr[1] = 0xF1
    hdr[2] = (
        ((profile & 0x3) << 6)
        | ((sf_index & 0xF) << 2)
        | ((ch_config >> 2) & 0x1)
    )
    hdr[3] = ((ch_config & 0x3) << 6) | ((frame_length >> 11) & 0x3)
    hdr[4] = (frame_length >> 3) & 0xFF
    hdr[5] = ((frame_length & 0x7) << 5) | 0x1F
    hdr[6] = 0xFC

    return bytes(hdr)


# ============================================================
# Extraction
# ============================================================

def extract_track_aac(mp4_bytes, trak_off, trak_size, out_path):
    stbl = get_stbl_boxes(mp4_bytes, trak_off, trak_size)

    stsd_off, _ = stbl["stsd"]
    entry_off = stsd_off + 16
    entry_size = struct.unpack(">I", mp4_bytes[entry_off:entry_off + 4])[0]

    region = mp4_bytes[entry_off:entry_off + entry_size]
    esds_pos = region.find(b"esds")

    esds_off = entry_off + esds_pos - 4
    esds_size = struct.unpack(">I", mp4_bytes[esds_off:esds_off + 4])[0]

    asc = find_decoder_specific_info(
        mp4_bytes[esds_off:esds_off + esds_size]
    )
    aot, sf_idx, ch = parse_asc(asc)

    stco = parse_stco(mp4_bytes, *stbl["stco"])
    stsz = parse_stsz(mp4_bytes, *stbl["stsz"])
    stsc = parse_stsc(mp4_bytes, *stbl["stsc"])

    chunks = build_chunk_samples(stco, stsc, stsz)

    out = bytearray()

    for off, size in chunks:
        if size == 0:
            continue

        frame = mp4_bytes[off:off + size]
        hdr = build_adts_header(7 + len(frame), aot, sf_idx, ch)
        out += hdr + frame

    Path(out_path).write_bytes(out)
    return out_path


# ============================================================
# Main
# ============================================================

def main():
    if len(sys.argv) < 2:
        print("Usage: python extract_hidden_track.py chall.mp4")
        sys.exit(1)

    mp4 = Path(sys.argv[1]).read_bytes()
    boxes = parse_boxes(mp4)
    traks = [b for b in boxes if b[2] == "trak"]

    if len(traks) < 2:
        print("Only one track found.")
        sys.exit(1)

    out = extract_track_aac(
        mp4,
        traks[1][0],
        traks[1][1],
        "track2_hidden.aac",
    )

    print("Wrote", out)


if __name__ == "__main__":
    main()

```
**Flag:** `HOLOGY8{4ud4c1ty_s0_g00d_w8r5d6ls}`


---

Sekian terimakasih :>

JANGAN LUPA MAIN CTF YAAA!!!