
Notas de engenharia reversa e ferramentas em Python para o firmware do JBL Wave Buds 2 (BES1306): formato de contêiner OTA, layout da flash, protocolo BLE/GATT e modelo de confiança CRC-32.
Notas de engenharia reversa e ferramentas reproduzíveis para o JBL Wave Buds 2
(Bestechnic BES1306, PID 2100): o layout na flash, o formato do contêiner
OTA até o byte, o protocolo de comandos BLE/GATT e o transporte criptografado
"TOTA v2" da BES, as fronteiras de confiança do lado do dispositivo e do app, o
console de fábrica, e um método reproduzível para carregar uma imagem
autoconstruída em uma unidade que você possui.
A análise abrange duas builds — o wavebuds.elf analisado no IDA (abr 2025) e o
pacote de distribuição 2.9.0 (set 2026) — um contêiner OTA de 8 streams
totalmente especificado (verificado por repack byte-idêntico), o modelo de
integridade CRC-32 confirmado tanto no lado do app quanto no do dispositivo, uma
tabela de comandos de fábrica com 54/59 entradas, 12 variáveis de estado da RAM
de OTA, uma enumeração de 7.960 funções, e um diferencial entre builds. A
recuperação semântica é profunda para os subsistemas de OTA/flash/CRC/fábrica e
apenas de enumeração nos demais — veja a auditoria de completude.
[!WARNING] Esta é uma pesquisa de hardware realizada pelo proprietário, não uma atualização oficial da JBL. Gravar uma imagem modificada é irreversível: a fase de download é escalonada e à prova de brick, mas a ativação de uma imagem válida em CRC porém quebrada não possui caminho de recuperação exposto. Leia o SAFETY.md antes de reproduzir qualquer coisa.
Versão em turco: README.tr.md

Resultado de hardware na unidade testada: após a validação do pipeline OTA via app + MITM com CA do usuário, ambos os fones se conectam, reportam 100%, e todos os controles do app (ANC/Ambient, gestos, EQ) funcionam. A imagem modificada em um byte foi aceita da mesma forma e o dispositivo permaneceu totalmente funcional.
Os endereços são da build analisada (wavebuds.elf); veja o
registro de endereços e a proveniência.
0x2C0918BC verifica a configuração,
cada segmento e a imagem inteira com a rotina CRC-32 0x2C0CE944 (tabela
0x2C0F5218) mais um magic "BEST". Uma imagem válida em CRC com um byte
modificado foi aceita e inicializada.com.jbl.counterfeit.Rsa2048DynamicVerifier) é
anticontrafação (autenticidade do dispositivo), não assinatura de firmware.0x28120000 enquanto a aplicação em execução permanece intacta, então a fase
de download é à prova de brick.0x2C0905E4:
MEMORY[0x20026BA0] ∈ {2,4}).UNRESOLVED.A modificação é intencionalmente mínima — um byte da imagem descomprimida. Ela
altera a string do kernel RTX RTX V5.5.1 → V5.5.2 no offset descomprimido
64096, e o CRC-32 do contêiner é recalculado. É cosmética por design: o ponto
é provar que o dispositivo aceita uma imagem modificada porém válida em CRC, não
alterar o comportamento.
Base (owner-supplied; not redistributed)
official 2.9.0 package 549,558 bytes SHA-256 836cb251f9fb2261dde14f39ba18d358c48018817f3c3d1b527e1ac5f3ab7ba2
decompressed image 955,596 bytes SHA-256 489663e80b76f20f4c94671064908569fac5f4e3df10ab6d7369baffe094f67d
container CRC-32 A0C7CCEC (valid), input range [0x0, 0x862AD)
O repositório intencionalmente não contém firmware JBL. Forneça seu próprio pacote obtido legalmente e reproduza:
python3 tools/ota_unpack.py <official_2.9.0_package.bin> --extract image.bin
python3 tools/ota_repack.py make-test <official_2.9.0_package.bin> test.bin
python3 tools/ota_unpack.py test.bin # exactly one decompressed byte differs; CRC valid
Os bytes exatos do contêiner reempacotado dependem do codificador LZMA; o dispositivo verifica CRC-32, não um hash do pacote (ver docs/04), e o diff descomprimido de um byte é determinístico. A entrega através do app está em docs/08.
| Mapa de flash | Particionamento de NOR de 2 MB |
| Fluxo de comandos OTA | a sequência e seus checkpoints CRC-32 |
| Limites de confiança | o que cada estágio verifica, e onde repousa a confiança na imagem |