A registros de notarização totalmente legítimos foram adicionados registros maliciosos duplicados com uma raiz de estado (state root) substituída. Esses registros foram assinados pelos notários da Verus e depois transmitidos para a Ethereum, onde substituíram a state root confiável real. Ao obter o controle desse valor, o invasor conseguiu enviar pela ponte uma prova de importação falsa que permitiu retirar muito mais fundos do que os efetivamente enviados, apesar de a exportação original ter sido de apenas 0.01 VRSC.
O que é uma notarização
Na rede Verus, uma notarização é um ponto de verificação assinado entre duas blockchains. É ela que define o estado da rede em relação ao qual são posteriormente verificadas as provas de transferências e operações de exportação.
Essa confirmação contém:
- o identificador da blockchain ou da moeda;
- a altura da notarização;
- uma ou mais raízes de prova (proofRoots), que incluem:
- o identificador da rede;
- a altura do bloco;
- a raiz de estado (state/proof root);
- o hash do bloco;
- o poder de computação acumulado;
- informações sobre a moeda e o estado do conversor;
- uma referência à notarização anterior;
- o hash da notarização cross-chain anterior;
- informações sobre o nó e o iniciador.
As raízes de prova são formadas a partir dos dados públicos das blockchains Verus e Ethereum. Qualquer usuário pode criar e transmitir um candidato a notarização, mas a rede só o aceitará se os requisitos de consenso forem atendidos e for utilizado o UTXO correto da cadeia de notarizações anterior.
As assinaturas dos notários (CNotarySignature) fazem parte da estrutura CNotaryEvidence e são publicadas de forma aberta. Graças a isso, o invasor conseguiu obter essas assinaturas e reutilizá-las.
Cronologia do ataque (UTC)
Os invasores se prepararam minuciosamente para o hack e agiram ao longo de dois dias.
Como o ataque se desenrolou: quatro etapas
Todo o algoritmo do hack do protocolo pode ser dividido em quatro etapas principais.
1. Substituição da notarização na rede Verus
A primeira transação utilizou a saída confirmada anterior da cadeia de notarizações (4f4f…:1) e criou uma nova (0c247…:0). Embora na interface do explorador de blocos fossem exibidos apenas dois registros legítimos, dentro dos dados serializados foram inseridos de forma encoberta registros adicionais com state roots falsas.
A causa estava nas particularidades do processamento de dados. A Verus primeiro lia a lista de raízes como um array e depois a transferia para uma estrutura Map. Graças a isso, o invasor conseguiu criar duas transações consecutivas, cada uma das quais continha adicionalmente vários registros maliciosos ocultos com state roots substituídas.
2. Substituição da raiz de estado na Ethereum
Depois disso, o invasor esperou até que o software dos notários verificasse os primeiros registros corretos. Em seguida, 11 nós notariais assinaram a notarização por completo, incluindo os registros duplicados ocultos com dados maliciosos, que o sistema Verus na prática ignorava.
As assinaturas obtidas foram incluídas na estrutura CNotaryEvidence. Depois, por meio da interface RPC da Verus, o invasor obteve essas assinaturas e então construiu uma chamada à função setLatestData() na Ethereum.
Do lado da Ethereum, o invasor atuou apenas como retransmissor: copiou os dados em bytes da notarização junto com as assinaturas legítimas dos notários e os transmitiu ao contrato inteligente.
Durante a execução da função deserializeNotarization(), o contrato da Ethereum processava o array proofRoots de forma sequencial. Com isso, cada registro com o identificador do sistema Verus sobrescrevia o valor de stateRoot:
- o primeiro registro - a state root real;
- o segundo - dados da Ethereum;
- o terceiro - uma state root maliciosa;
- o quarto - novamente maliciosa;
- o quinto - também maliciosa.
Como resultado, o último registro substituiu por completo a state root confiável real. Foi justamente essa state root falsa que foi posteriormente utilizada para verificar a prova de transferência fraudulenta através da ponte.
3. Preparação da exportação via Verus
Em seguida, o invasor criou uma solicitação de transferência cross-chain totalmente comum. Na transação 5b5043febfd7f7c089e976d11d0f87a904881266bb96dd118b3d69b0e02aab52, um novo endereço iniciou uma exportação de apenas 0.01 VRSC, pagando uma taxa de cerca de 12 unidades via Bridge.vETH.
Depois disso, a solicitação foi processada pelo conversor, e o LuckPool formou uma exportação em lote em uma transação separada. Foi justamente essa solicitação totalmente legítima que mais tarde se tornou a base para construir a prova falsa.
Em particular, foram utilizados os parâmetros:
- hashtransfers: 440787f9114dbb41b7d3d6f6c48980d51221f62ef0a33613d1d305b98ae85053
- sourceheightstart: 4162638
- sourceheightend: 4162847
4. Importação falsa e retirada de fundos via Ethereum
Na etapa final, o invasor chamou a função submitImports(). Na estrutura CCE, de 14 campos, apenas alguns parâmetros-chave foram alterados. O campo hashtransfers foi completamente falsificado de modo a corresponder às transações de retirada de fundos.
Além disso:
- o valor numInputs foi alterado para 8;
- firstInput - para 0;
- os campos totalAmounts, totalFees e totalBurned estavam completamente ausentes.
Parte da prova permaneceu original. Em particular, foram utilizados valores reais:
- o identificador da transação (TxID);
- o primeiro nó da árvore de provas (First node);
- os parâmetros gerais do formato das provas, as versões e os identificadores dos sistemas.
Todos os demais elementos foram gerados artificialmente. Graças ao fato de que a state root maliciosa já havia sido registrada pela Ethereum como confiável, a prova resultante passou pela verificação com sucesso, apesar da ausência do volume de fundos correspondente na transferência original.
Em que consistia a vulnerabilidade: Verus e Ethereum liam os dados de forma diferente
A Verus tratava os registros serializados como uma estrutura na qual os elementos duplicados eram, na prática, ignorados após a conversão do array em um Map.
A Ethereum, ao contrário, processava todos os registros de forma sequencial, de modo que cada registro seguinte podia sobrescrever o valor da state root.
Como resultado, os notários da Verus assinaram dados que consideravam totalmente corretos, mas a Ethereum interpretou esses mesmos bytes de outra forma e, no fim, armazenou como state root confiável um valor totalmente controlado pelo invasor.
Depois que a Ethereum reconheceu essa state root falsa como confiável, qualquer prova de importação construída a partir dela era considerada legítima pelo sistema.
Um fator adicional foi um erro lógico no contrato da ponte da Ethereum. O contrato não verificava se o valor indicado na solicitação de importação correspondia ao volume de ativos que havia de fato sido exportado da rede Verus.
Bastava apenas construir um parâmetro hashtransfers falso que coincidisse com as transações de retirada de fundos para passar com sucesso pela verificação da estrutura CCE.
Movimentação dos fundos roubados: 2,778 ETH via Tornado Cash

Após explorar com sucesso a vulnerabilidade, o invasor trocou os ativos roubados por 2,778.8662 ETH via Relay, e depois transferiu os fundos obtidos para o serviço de anonimização Tornado Cash.
