Pesquisa ANZEN: Como comprometemos a Keystone através de uma vulnerabilidade USB

Principais Resultados
• O USB SDK carece de validação para comprimentos controlados pelo host, resultando em leitura e escrita fora dos limites (OOB) em um buffer fixo
• Atacantes podem sequestrar a tabela de callbacks de USB class para converter a escrita OOB em execução de código arbitrário no MCU
• Configurações incorretas de MPU permitiram a execução direta de shellcode a partir da SRAM
• Após obter o Passcode, o OTP e o material criptográfico do elemento seguro, é possível reconstruir a AES Key e extrair a seed phrase BIP39
• SDKs de terceiros também fazem parte do perímetro crítico de segurança de hardware wallets e devem ser submetidos a auditorias completas
Atualização de segurança (julho de 2026): O problema discutido neste artigo foi descoberto pela OneKey Anzen durante uma auditoria de segurança conjunta realizada com a Keystone em março de 2026, afetando o firmware do Keystone 3 Pro que integra o USB SDK do fornecedor de MCU. A Keystone lançou o firmware v2.4.0 em 1 de abril de 2026, corrigindo completamente o problema; até o momento da publicação do comunicado conjunto, nenhuma das partes encontrou evidências de que essa vulnerabilidade tenha sido utilizada para atacar usuários da Keystone. Uma exploração bem-sucedida exige que o invasor tenha a posse física do dispositivo, saiba o PIN correto, desbloqueie o dispositivo, obtenha a aprovação do usuário para a conexão USB e conecte o dispositivo a um computador sob controle do invasor. Usuários que utilizam versões de firmware inferiores à v2.4.0 devem atualizar imediatamente para a versão mais recente; o uso de assinaturas via QR air-gapped pode evitar essa via de ataque específica por USB, mas ainda assim recomenda-se realizar a atualização. Para mais detalhes, consulte a Atualização Conjunta de Segurança da Keystone e OneKey Anzen.
No início de agosto, foi realizada a Black Hat USA de 2026 em Las Vegas. Devido a questões de visto, nossa apresentação foi alterada para o formato on-demand. Essa palestra originou-se de uma auditoria que realizamos na carteira Keystone, onde descobrimos problemas críticos no USB SDK fornecido pelo fabricante do chip, culminando na extração da seed phrase.
Na verdade, não é a primeira vez que surgem falhas na pilha de protocolos USB. Ao longo da última década, incontáveis ataques graves a dispositivos estiveram associados a pilhas USB. Desde vulnerabilidades de drivers USB e redirecionamento remoto em 2014, passando por Fusée Gelée, checkm8, Kamakiri, até a divulgação do usbliter8 em 2026.
Vulnerabilidades recorrentes no protocolo USB
2014: Ataques USB realmente exigem acesso físico?
Em 2014, o NCC Group apresentou na Black Hat Asia a palestra "USB Attacks Need Physical Access Right? Not Any More...".
Naquela época, as falhas USB costumavam ser categorizadas como "ataques físicos locais": o atacante precisava conectar um dispositivo malicioso na máquina-alvo para disparar bugs de driver por meio de descriptors malformados ou class-specific data.
Contudo, nessa pesquisa, os pesquisadores utilizaram o RemoteFX USB Redirection para redirecionar dispositivos USB de baixo nível para um Windows Server remoto. O dispositivo USB malicioso estava fisicamente conectado ao cliente do atacante, mas o driver que realmente processava os dados USB maliciosos era executado no servidor remoto.
2018, Fusée Gelée: tratando o tamanho da requisição como o tamanho do objeto
Divulgado em 2018, o Fusée Gelée é um dos casos mais clássicos de vulnerabilidade em USB BootROM. A falha localizava-se no Recovery Mode do NVIDIA Tegra, o RCM. O dispositivo inicializava a pilha USB no ambiente primitivo da BootROM, aguardando que o host enviasse uma imagem de recuperação. O ponto vulnerável ocorria em uma requisição de controle GET_STATUS aparentemente comum.
De acordo com a semântica do USB, um objeto de status tem apenas 2 bytes. O host indica por meio de wLength o volume máximo de dados que está disposto a receber, e o dispositivo deve retornar min(tamanho da requisição do host, tamanho real dos dados disponíveis).
Entretanto, ao processar o endpoint recipient, a BootROM executava erroneamente uma lógica semelhante a:
status = get_usb_endpoint_status(index);
/* Erro: usa o tamanho da requisição fornecido pelo host */
size_to_tx = setup_packet.length;
memcpy(dma_buffer, &status, size_to_tx);
Ou seja, o tamanho real do objeto de 2 bytes não era utilizado; em vez disso, o wLength controlado pelo atacante tornava-se diretamente o tamanho do memcpy, podendo alcançar teoricamente 65.535 bytes.
Além disso, antes de validar a assinatura, o RCM já permitia que o atacante carregasse um payload considerável na memória; essa região do payload ficava convenientemente próxima à pilha ativa da BootROM. Ao selecionar o DMA buffer e manipular o layout de memória, a cópia fora dos limites conseguia sobrescrever a pilha de execução com dados controlados pelo atacante.
Naquela época, o ambiente da BootROM não possuía stack canary, ASLR nem proteções efetivas de execução de memória. O resultado final: antes que qualquer bloqueio de segurança ou redução de privilégios ocorresse, o atacante já obtinha execução de código arbitrário no contexto da BootROM.
Isso guarda uma semelhança bastante direta com a vulnerabilidade na carteira de hardware detalhada adiante neste artigo.
2019, checkm8: o atacante controla não apenas os dados, mas também o estado "incompleto"
Divulgado em 2019, o checkm8 é um dos casos de maior impacto entre as vulnerabilidades da Apple SecureROM. Ele afetou múltiplas gerações de dispositivos iOS, permitindo que atacantes obtivessem execução de código no contexto da BootROM em modo DFU, possibilitando o dump da SecureROM, a descriptografia da firmware keybag ou a alteração do dispositivo para o estado de demotion que habilita o JTAG. Como a falha reside na BootROM gravada durante a fabricação do chip, os dispositivos em circulação não podem ser corrigidos via atualizações do iOS.
Diferente do Fusée Gelée, o checkm8 não se resume a um simples erro de checagem de tamanho. Seu núcleo é um use-after-free na máquina de estados USB do DFU da Apple: uma etapa de dados USB não é finalizada normalmente e a variável global que guarda o estado dessa etapa não é limpa; porém, o DFU buffer subjacente é liberado quando a pilha USB é encerrada.
Em condições normais, após a BootROM receber uma requisição DFU_DNLOAD com etapa DATA, ela armazena o buffer de recepção e o wLength. Ao concluir a recepção dos dados, o callback de conclusão limpa esses estados; ao sair do DFU, o buffer de I/O correspondente é liberado.
DFU_DNLOAD(setup)
{
ep0_buffer = dfu_io_buffer;
ep0_remaining = setup.wLength;
}
DATA_complete()
{
ep0_buffer = NULL;
ep0_remaining = 0;
}
DFU_exit()
{
free(dfu_io_buffer);
}
O checkm8 explora precisamente a janela entre essas etapas: o atacante inicia uma requisição com etapa DATA, fazendo com que a BootROM registre o ponteiro do buffer e o tamanho restante, e depois propositalmente não envia todos os dados. Ao cancelar a transmissão ou forçar um timeout, o callback de conclusão de DATA não é executado; em seguida, ele força uma troca de estado no DFU ou um reset USB, fazendo com que a pilha USB seja finalizada e libere o buffer original.
Nesse momento, a memória foi liberada, mas o ponteiro antigo permanece no estado de recepção:
free(dfu_io_buffer);
ep0_buffer = old_dfu_io_buffer; // Ponteiro dangling
Em seguida, é necessário organizar a heap. O exploit público vaza uma série de objetos de requisição USB, alterando as posições de alocação subsequentes para que o novo DFU buffer seja alocado em outro endereço, enquanto o endereço antigo é reocupado por objetos como usb_device_io_request.
Depois disso, ao enviar dados USB novamente, a BootROM ainda escreverá seguindo o ep0_buffer residual, sobrescrevendo na verdade o novo objeto. O exploit modifica campos como callback e next para apontar o endereço do callback para o payload posicionado previamente. Na conclusão da próxima requisição USB, a SecureROM salta para o código do atacante.
2020, Kamakiri: como uma requisição USB se torna um salto indireto
O Kamakiri tem como alvo o modo de download da BootROM em uma série de SoCs da MediaTek. Ele permite que atacantes contornem os caminhos de autenticação que restringem Download Agents não autorizados, executando um payload de primeiro estágio sem assinatura no contexto da BootROM. Com essa capacidade de execução, payloads subsequentes podem manipular a memória flash, extrair dumps de memória, alterar o estado de boot ou recuperar dispositivos que não inicializavam mais normalmente.
A cadeia de exploração do Kamakiri é bastante curta, pois a própria BootROM da MediaTek já fornece a primeira capacidade necessária: o envio de dados para uma região conhecida da SRAM por meio do protocolo de download. O atacante injeta o payload de estágio 1 e dispara a vulnerabilidade através de uma USB control request específica.
A vulnerabilidade localiza-se exatamente no despacho de requisições de controle USB. A engenharia reversa independente resumiu a lógica envolvida em algo semelhante a:
handler = handler_array[value * 13];
handler();
O problema fundamental é que o valor de seleção vindo da requisição USB participa da indexação da tabela de ponteiros de função sem a devida verificação de limites. O atacante precisa apenas encontrar um valor de controle apropriado para fazer com que a leitura da tabela aponte para o endereço do payload na SRAM enviado previamente, transformando uma requisição USB em uma chamada indireta ao payload. Como o layout da BootROM varia entre diferentes SoCs, o índice específico e o endereço do payload podem exigir adaptação ou enumeração.
O PoC público, após enviar o stage 1, dispara a seguinte transferência de controle:
ctrl_transfer(
0xA1, # class request, device-to-host, interface recipient
0,
0,
10, # wIndex
0
)
2026, usbliter8: o controlador USB move o ponteiro de escrita DMA para antes do buffer
Divulgado em junho de 2026, o usbliter8 expandiu novamente as explorações de BootROM não corrigíveis por software para os SoCs Apple A12, A13, bem como S4 e S5.
Uma vez bem-sucedido o exploit, o atacante pode executar código no contexto EL1 da SecureROM, modificar o comportamento do DFU, fazer temporariamente o demote do production mode do chip ou contornar verificações de assinatura para carregar um iBoot original sem assinatura. Isso significa que a cadeia de confiança de inicialização do processador de aplicação foi violada, impossibilitando correções via atualizações posteriores do iOS.
O usbliter8 difere dos casos anteriores: ele explora principalmente a semântica do ponteiro DMA ao processar transações SETUP no próprio controlador USB Synopsys DWC2.
De acordo com o protocolo USB, a seção de dados de uma SETUP transaction é fixada em 8 bytes. O controlador DWC2 permite receber sucessivamente três pacotes SETUP, gravando-os em sequência na memória via DMA. Três pacotes SETUP normais ocupam um total de 24 bytes.
Quando o quarto pacote SETUP chega, o controlador subtrai fixamente 24 bytes do ponteiro de escrita DOEPDMA, retornando o endereço para o início desse grupo de SETUP buffers. Para pacotes SETUP regulares de 8 bytes, essa lógica fecha-se com perfeição.
No entanto, o controlador também aceita dados SETUP anômalos com menos de 8 bytes, avançando o ponteiro DMA conforme o comprimento realmente gravado; a granularidade de escrita interna é de 4 bytes. Quando o atacante envia repetidamente pacotes curtos de 4 bytes, os três primeiros pacotes avançam o endereço em apenas 12 bytes no total, mas quando o quarto pacote chega, o controlador ainda recua fixamente os 24 bytes. Assim, a próxima rodada de dados SETUP não é mais gravada no buffer original, mas sim 12 bytes antes do início do buffer. Ao repetir continuamente esse processo, a posição de escrita DMA se desloca em direção a endereços inferiores com passos previsíveis, formando um buffer underflow estável.
Em termos gerais, um DMA fora dos limites por parte do controlador USB pode não corromper arbitrariamente a memória do sistema. SoCs modernos geralmente utilizam IOMMU para limitar o espaço de endereçamento acessível pelo controlador USB. Porém, no ambiente SecureROM do A12 e A13, o USB DART encontra-se em estado de bypass, carecendo de isolamento efetivo de endereços para o DMA do controlador. Isso faz com que o underflow não se restrinja a um SETUP buffer reduzido, estendendo-se para sobrescrever outros objetos na SRAM.
No A12, o USB DMA buffer é adjacente à stack da USB task; a exploração consegue sobrescrever o link register salvo e assumir o controle do program counter durante a troca de contexto de tarefas. O A13 introduziu Pointer Authentication, inviabilizando a sobrescrita direta do endereço de retorno; por isso, a exploração pública adotou uma cadeia de corrupção de memória mais elaborada, regravando o ponteiro do USB interrupt handler no BSS para entrar no código do atacante na interrupção USB seguinte. Ambos os caminhos atingem a execução em SecureROM EL1.
Da leitura e escrita USB fora dos limites à execução arbitrária
A causa raiz da vulnerabilidade compartilhada nesta Black Hat USA reside, da mesma forma, no USB SDK.
A requisição de line-coding no CDC necessita apenas de uma estrutura fixa minúscula, mas o USB SDK integrado reservou para ela um CmdBuff fixo de 8 bytes, passando em seguida o req->wLength fornecido pelo host diretamente para as funções subjacentes de envio ou recebimento. O código não realizou nenhuma verificação de comprimento e não exigiu que ele correspondesse ao tamanho fixo definido pelo protocolo para essa requisição. Como resultado, gerou-se uma leitura e escrita fora dos limites (out-of-bounds).
Código da vulnerabilidade de leitura e escrita OOB no USB SDK
Naturalmente, obter leitura e escrita OOB é apenas o primeiro passo: tínhamos apenas uma primitiva de corrupção de memória, e o objetivo final era alcançar a capacidade de execução arbitrária.
Como a falha ocorria no fluxo do USB CDC, analisei primeiramente a pilha USB seguindo o fluxo de despacho da requisição atual. Ao examinar g_usbDev, notei o campo dev.class_cb. Ele não é um ponteiro de dados comum, mas sim a tabela de callbacks em tempo de execução do USB class driver; o USB core utiliza essa tabela para chamar funções como Setup, DataIn e DataOut ao processar requisições. Contanto que seja possível sobrescrever class_cb, uma requisição USB regular subsequente passará pelo ponteiro de função sob nosso controle, tornando-o um alvo ideal para sequestro.
Todo o exploit pode ser dividido em duas requisições USB.
A primeira requisição utiliza SET_LINE_CODING para disparar a escrita OOB. Nessa escrita, preciso realizar três coisas simultaneamente: colocar o shellcode no CmdBuff, montar uma tabela de callbacks forjada na SRAM adjacente e, por fim, alterar class_cb para o endereço dessa tabela falsa.
Em seguida, envio uma requisição GET_LINE_CODING comum. O USB core processará essa requisição como de costume, chamando o callback Setup do class driver através de class_cb->Setup(...). A diferença é que agora class_cb não aponta mais para a tabela de callbacks original, mas sim para a nossa tabela forjada; o Setup falso, por sua vez, aponta para o shellcode no CmdBuff. Assim, a segunda requisição USB leva o PC diretamente para o código que escrevemos na SRAM.
Como o shellcode reside na SRAM, normalmente haveria o obstáculo da não-execução em áreas de dados. Contudo, o firmware utilizou um formato incorreto de size ao configurar a MPU, resultando em um intervalo de proteção de 0; a região de MPU destinada a restringir permissões de execução no BSS simplesmente não surtiu efeito. Portanto, esse trecho de código posicionado no CmdBuff pôde ser executado diretamente.
Sequestro da tabela de callbacks USB para execução de shellcode
Com a viabilidade teórica confirmada, passamos para a prática. Primeiro, testei o alcance real de leitura e escrita OOB. Montei transferências de controle com tamanho de 64 bytes: a leitura OOB conseguiu vazar continuamente cerca de 24 KB da SRAM, mas a escrita OOB causava crash estável ao atingir a posição de 4.352 bytes.
Verificações posteriores revelaram que, nessa posição, os dados fora dos limites sobrescreviam um ponteiro ainda em uso em CmdBuff + 0x1130, fazendo com que o EP0 perdesse imediatamente a capacidade de continuar recebendo dados subsequentes.
Posição onde a escrita OOB causa o travamento do dispositivo
Resolver essa questão foi relativamente simples: como já tínhamos a capacidade de leitura OOB, bastava ler o estado real previamente e carregar os dados de estado extraídos no shellcode. Dessa forma, estruturou-se uma lógica completa para execução de código arbitrário.
Agora já era possível sobrescrever class_cb, mas eu não queria destruir o canal USB no processo de sequestro. Uma escrita longa posiciona primeiro o shellcode e a tabela de callbacks forjada na SRAM, fazendo temporariamente com que class_cb aponte para a tabela falsa; em seguida, ao enviar GET_LINE_CODING, o USB core salta para o shellcode via Setup falso. Concluída a tarefa, o shellcode grava o resultado logo após o CmdBuff, restaura o class_cb original e chama o Setup autêntico, permitindo que a transferência atual do EP0 seja encerrada normalmente. Assim, cada sequestro funciona como um desvio efêmero (detour), mantendo o USB plenamente operacional para injetar tarefas subsequentes.
O firmware originalmente também tentou configurar o BSS como não executável via MPU, mas o código de configuração utilizou um formato de size incorreto, calculando uma faixa de proteção nula.
Da execução arbitrária no MCU à extração da seed phrase
Obter execução arbitrária no MCU não significa que a seed phrase possa ser extraída imediatamente. A arquitetura específica da carteira de hardware é apresentada a seguir: em linhas gerais, as informações criptografadas da seed phrase ficam armazenadas no SE DS28S60, enquanto a AES KEY responsável por descriptografar essas informações conta com a participação do OTP e de dois elementos seguros: ATECC608 e DS28S60.
Arquitetura de segurança da carteira de hardware
O processo de geração dessa AES KEY é apresentado a seguir:
Fluxo de geração da AES Key
As credenciais que participam da interação com o SE e da geração da chave AES derivam principalmente do passcode (senha de desbloqueio) da carteira e do OTP, conforme o fluxo abaixo:
Fluxo de geração de credenciais via Passcode e OTP
O Passcode pode ser obtido via força bruta após a extração do password hash; dessa forma, resta apenas obter o salt do OTP para concluir a geração da AES key. Para a extração do OTP, como o shellcode roda no mesmo nível de privilégio do firmware normal, é possível escrever nos registradores da MPU através da vulnerabilidade USB e desativar a MPU, permitindo a leitura do OTP.
O método final de exploração opera como descrito a seguir: após obter a AES KEY, decodifica-se a entropia BIP39 para reconstruir a seed phrase.
Cadeia de ataque completa desde a vulnerabilidade USB até a extração da seed phrase
Conclusão
Durante este processo de pesquisa, a IA foi amplamente utilizada tanto na análise de vulnerabilidades quanto no desenvolvimento do exploit. A introdução da IA aumentou drasticamente a eficiência dos pesquisadores de segurança, transformando também o custo de pesquisa dessas falhas. No passado, identificar e explorar vulnerabilidades exigia uma quantidade massiva de trabalho manual para atingir estabilidade; hoje, o entendimento de código, a correspondência de estruturas, a geração de scripts e a triagem de falhas ocorrem de forma significativamente mais veloz.
Além disso, componentes de terceiros como USB SDKs representam pontos que são facilmente negligenciados em auditorias de segurança de produtos — e, com frequência, é justamente por essa brecha que toda a segurança do dispositivo acaba comprometida.






