Perda superior a US$ 88 milhões: Análise da vulnerabilidade da carteira de hardware Coldcard e rastreamento dos fundos roubados Em 31 de julho, aproximadamente 500 carteiras de hardware Coldcard foram roubadas, totalizando 594 bitcoins, com valor aproximado de US$ 38 milhões. Posteriormente, a Coinkite, empresa responsável pela produção das carteiras Coldcard, confirmou a existência de uma vulnerabilidade de segurança no processo de geração de chaves, afetando múltiplas gerações de produtos, incluindo Coldcard Mk2, Mk3, Mk4, Q e Mk5.
Atualmente, o valor perdido devido a este ataque de vulnerabilidade já ultrapassou US$ 88 milhões, e o ataque ainda está em andamento. Os usuários do Coldcard devem transferir seus fundos para outros endereços o mais rápido possível. A seguir, apresentamos a análise da Beosin sobre esta vulnerabilidade e o rastreamento dos fundos roubados.
I. Análise de vulnerabilidade
Analisando o histórico de commits do firmware do Coldcard, pode-se observar que, no commit anterior 37e4af5451c260c1e7d429fe8972c4cb5e68ee59, a equipe de desenvolvimento atualizou vários trechos de código relacionados à configuração do MK4, incluindo em mpconfigboard.h:
// Temos nossa própria versão deste código.#define MICROPY_HW_ENABLE_RNG (0)
Na plataforma MicroPython STM32, este macro controla o caminho de compilação para o gerador de números aleatórios de hardware padrão (RNG) e a implementação geral de números aleatórios. Defini-lo como 0 faz com que o caminho do RNG de hardware padrão não seja usado como backend para rng_get().
Seu comentário indica que o desenvolvedor implementará o RNG; ao verificar o rng.h personalizado, descobriu-se que apenas dois objetos MicroPython foram declarados:
MP_DECLARE_CONST_FUN_OBJ_0(pyb_rng_get_obj);MP_DECLARE_CONST_FUN_OBJ_1(pyb_rng_get_bytes_obj);
A implementação correspondente é:
/// \function pyb_rng_get()///// Retorna um número aleatório gerado por hardware de 30 bits: ou falha!//STATIC mp_obj_t pyb_rng_get(void){ // Obter e retornar o novo número aleatório return mp_obj_new_int(rng_get_or_fault() >> 2);}
/// \function rng_get_bytes()/// Preenche um buffer com bits aleatórios; o chamador deve fornecer um buffer dimensionado.STATIC mp_obj_t pyb_rng_get_bytes(mp_obj_t buffer_io) {
mp_buffer_info_t bufinfo; mp_get_buffer_raise(buffer_io, &bufinfo, MP_BUFFER_WRITE);
mp_uint_t count = bufinfo.len; if(count < 1) { mp_raise_ValueError(NULL); }
// Ler palavras de 32 bits e desempacotar no buffer fornecido random_buffer(bufinfo.buf, count);
return mp_const_none;}
MP_DEFINE_CONST_FUN_OBJ_0(pyb_rng_get_obj, pyb_rng_get);MP_DEFINE_CONST_FUN_OBJ_1(pyb_rng_get_bytes_obj, pyb_rng_get_bytes);
rng_get_or_fault() lê diretamente o periférico RNG do STM32:
static uint32_t rng_get_or_fault(void){ // Habilita o periférico RNG, se ainda não estiver habilitado rng_init();
// Aguarda até que um novo número aleatório esteja pronto, levando aproximadamente 10us uint32_t start = HAL_GetTick();
while (!(RNG->SR & RNG_SR_DRDY)) { if (HAL_GetTick() - start >= RNG_TIMEOUT_MS) { // falha de hardware... não retorne nada! mp_raise_OSError(MP_EFAULT); } }
// Obtém e retorna o novo número aleatório last_value = RNG->DR;
return last_value;}
Isso indica que o código personalizado do Coldcard realmente deseja usar o RNG de hardware, mas garante apenas o uso dessa função de hardware ao chamar pyb_rng_get* ou seu internal random_buffer().
E durante a criação da carteira, o código a seguir é realmente chamado, onde a função de criação de carteira em shared/seed.py é:
async def make_new_wallet(nwords): # Escolha uma nova semente aleatória. await ux_dramatic_pause('Gerando...', 3) seed = generate_seed() words = await approve_word_list(seed, nwords) if words: await commit_new_words(words)
Essa chamada entra primeiro no módulo shared/random.py do Coldcard. Nas versões anteriores, o random.py dependia explicitamente do ngu.random e mantinha:
# random.py - subconjunto do módulo random, sem compatibilidade e usando RNG de qualidade criptográfica
# para bytes, use ngu.random.byte(len)
# bytes = ngu.random.bytes
Ou seja, o wallet initialization usa random.bytes, não pyb.rng(), e pyb_rng_get_obj personalizado não substitui automaticamente random.bytes.
Como MICROPY_HW_ENABLE_RNG foi definido como 0, ao gerar a carteira não foi utilizado o RNG de hardware, mas sim pyb_rng_yasmarang de micropython/ports/stm32/rng.c:
#if MICROPY_HW_ENABLE_RNG
uint32_t rng_get(void) { // use STM32 hardware RNG ...}
#else
// Para MCUs que não possuem RNG, ainda precisamos fornecer uma função rng_get()// Um pseudo-RNG não é realmente ideal, mas vamos adotá-lo por enquanto.
// Gerador de números aleatórios Yasmarangstatic uint32_t pyb_rng_yasmarang(void) { static bool seeded = false; static uint32_t pad = 0, n = 0; ...}
uint32_t rng_get(void) { return pyb_rng_yasmarang();}
#endif
E pyb_rng_yasmarang é um gerador de números pseudoaleatórios, sendo muito inseguro para geração de sementes de carteiras de hardware; atacantes podem obter a chave por força bruta. Atualmente, a Coinkite já excluiu explicitamente stm32/rng.c no arquivo makefile:
# Não compile o PRNG de fallback do MicroPython. O rng.c específico da placa
# fornece rng_get(), e este objeto vazio satisfaz a lista de objetos upstream.
$(BUILD)/rng.o: CFLAGS += -Dpyb_rng_yasmarang=error-do-not-want-this
$(BUILD)/rng.o:
$(ECHO) "SKIP stm32/rng.c"
$(Q)$(CC) $(CFLAGS) -x c -c /dev/null -o $@
II. Rastreamento de fundos roubados
Atualmente, os fundos de várias carteiras de vítimas já foram transferidos e reunidos, armazenados em vários endereços, ainda não realizada a lavagem adicional. A Beosin Trace, por meio de inteligência de ameaças e análise de comportamento na cadeia, monitorou os seguintes endereços de reunião:
bc1qq85v2c926eg6pgxhwp6q7lf6cnsz80qs3fcu9r (562 BTC)
bc1qx76cae2706qd5q576feh7xq8rfcsjpf2htfhe3 (398,47 BTC)
Além disso, existem os seguintes endereços de coleta, cujos fluxos de fundos são semelhantes ao gráfico acima, e até o momento não houve transferências adicionais:
- bc1q8jy96fe5lf8vfugydnte3cguk92gpev7kwtp3q (89,62 BTC)
- bc1q0rvn88w08j75k4h48lf9fvhan7unjp7vjf5q6m (64,9 BTC)
- bc1qtfrwa4j6rmj9rsgspv6a0yjumkg39js2numu75 (45,9 BTC)
- bc1qmd5m5ktv7m5ffujxv4248fxv36myvdx79n8jp6 (30,18 BTC)
Ataques contra a carteira Coldcard ainda estão em andamento, e a equipe da Beosin continua monitorando endereços de coleta adicionais e analisando os fluxos de fundos relacionados.
III. Conclusão
Este grave incidente de segurança no hardware wallet Coldcard surgiu de um erro na implementação da geração de números aleatórios pela equipe de desenvolvimento, que utilizou um gerador de números pseudoaleatórios no processo crítico de geração da semente da carteira. A equipe de desenvolvimento deve realizar testes e auditorias contínuos e abrangentes no código, enquanto os usuários da carteira Coldcard devem transferir seus ativos o mais rápido possível e manter-se atentos aos próximos comunicados de segurança da Coinkite.
Beosin é uma empresa líder em segurança blockchain e tecnologia de conformidade regulatória, especializada em auditoria de segurança de contratos inteligentes antes do lançamento de projetos, monitoramento e bloqueio de riscos de segurança durante a operação dos projetos, recuperação de ativos roubados, combate à lavagem de dinheiro (AML) para ativos virtuais e investigação e rastreamento. A Beosin já forneceu produtos e serviços de conformidade blockchain “tudo-em-um” para 200+ provedores de ativos virtuais e 4.500+ projetos Web3 em mais de 20 países e regiões globais.

