Análise de smart contracts
27.03.2024

Armadilhas ocultas no Honeypot. Como reconhecer o isco e evitar a perda de activos?

Os contratos inteligentes Honeypot são um tipo de contratos inteligentes fraudulentos que parecem atraentes para os utilizadores e prometem benefícios financeiros. No entanto, esses contratos contêm armadilhas ocultas ou código malicioso, fazendo com que os utilizadores percam os seus fundos.

Para se proteger dos contratos inteligentes Honeypot, é necessário pesquisar exaustivamente e verificar o código do contrato antes de o utilizar. O mundo dos contratos inteligentes e da cadeia de blocos está em constante evolução, com o aparecimento regular de novas vulnerabilidades e métodos de engano, pelo que é vital que os programadores, auditores e utilizadores se mantenham a par das últimas tendências e desenvolvimentos nesta área./p>

Existem muitos exemplos de contratos inteligentes Honeypot. Neste artigo, vamos analisar alguns desses exemplos.

1. Honeypot com função Fallback

Neste tipo de honeypot, o contrato tem uma caraterística vulnerável de "reversão" que aparentemente permite que qualquer pessoa se torne o proprietário do contrato e remova o seu saldo. No entanto, essa funcionalidade contém uma condição oculta que impede a execução com êxito.

Exemplo de código:


pragma solidity 0.4.18

contract FallbackHoneypot {
    address public owner;

    function() public payable {
        if (owner == 0) {
            owner = msg.sender;
        } else {
            revert();
        }
    }

    function withdraw() public {
        require(msg.sender == owner);
        msg.sender.transfer(this.balance);
    }
}

Neste exemplo, a função fallback permite que qualquer utilizador se torne o proprietário. No entanto, o operador revert() garante que a mudança de propriedade nunca acontece, deixando os fundos do atacante presos num contrato.

2. Honeypot de condições ocultas

Estes contratos contêm uma condição ou requisito oculto que deve ser cumprido para que a função aparentemente vulnerável seja bem sucedida. Os utilizadores podem pensar que podem utilizar o contrato, mas a condição oculta garante que não podem.

Exemplo de código:


pragma solidity 0.4.18

contract HiddenConditionHoneypot {
    uint256 public counter = 0;

    function deposit() public payable {
        require(msg.value == 1 ether);
        counter++;
    }

    function withdraw() public {
        require(counter > 100);
        msg.sender.transfer(this.balance);
    }
}

Neste contrato, o utilizador pode depositar 1 ether, e parece que após 100 depósitos, o utilizador pode levantar todo o saldo do contrato. No entanto, a condição contador > 100 garante que o levantamento só é possível após 101 depósitos, o que atrasa os fundos no contrato.

3. Honeypot de reentrada

Neste tipo de honeypot, o contrato é vulnerável a um ataque de reentrada, em que um atacante pode invocar repetidamente uma função do contrato durante a retirada. No entanto, o contrato contém um mecanismo oculto que impede uma exploração bem sucedida.

Exemplo de código:


pragma solidity 0.4.18

contract ReentrancyHoneypot {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 _amount) public {
        require(balances[msg.sender] >= _amount);
        if (msg.sender.call.value(_amount)()) {
            balances[msg.sender] -= _amount;
        }
    }
}

Neste exemplo, o contrato é vulnerável a um ataque de reentrada devido à utilização de msg.sender.call.value(_amount)(). No entanto, o contrato não permite que os utilizadores carreguem uma conta com um montante superior ao saldo inicial, atrasando efetivamente os seus fundos.

honeypot de reentrada

4. Limite de gás honeypot

Neste tipo de honeypot, o contrato parece permitir que o utilizador levante fundos, mas a quantidade de gás necessária para executar a função de levantamento excede o limite do bloco de gás, tornando a transação impossível.

Exemplo de código:


pragma solidity 0.4.18

contract GasLimitHoneypot {
    uint256 constant public numArrayElements = 1000;
    uint256[numArrayElements] public someArray;

    function deposit() public payable {
        for (uint256 i = 0; i < numArrayElements; i++) {
            someArray[i] = 0;
        }
    }

    function withdraw() public {
        uint256 balance = address(this).balance;
        for (uint256 i = 0; i < numArrayElements; i++) {
            someArray[i] = 1;
        }
        msg.sender.transfer(balance);
    }
}

Neste exemplo, a função de depósito inicializa uma matriz com 1000 elementos. A função withdraw transfere o saldo do contrato para o remetente, mas modifica os elementos da matriz. O gás necessário para executar o loop excede o limite do bloco de gás, o que impede a retirada e atrasa os fundos no contrato.

5. Manipulação de carimbos temporais

Neste tipo de honeypot, o contrato utiliza o carimbo de data/hora da cadeia de blocos Ethereum como condição de execução. No entanto, como os mineiros podem manipular o carimbo de data/hora do bloco, o contrato torna-se vulnerável.

Exemplo de código:


pragma solidity 0.4.18

contract TimestampHoneypot {
    uint256 public lastInteraction;
    uint256 public prize;

    function deposit() public payable {
        lastInteraction = now;
        prize += msg.value;
    }

    function withdraw() public {
        require(now >= lastInteraction + 1 hours);
        msg.sender.transfer(prize);
        prize = 0;
    }
}

Neste contrato, os utilizadores podem depositar fundos e levantar o saldo total do contrato após 1 hora da última interação. No entanto, uma vez que os mineiros podem manipular o carimbo de data/hora do bloco, podem acionar a função de levantamento mais cedo do que o esperado e, assim, roubar efetivamente os fundos.

6. Honeypot de transferência oculta

Neste tipo de honeypot, o contrato contém uma transferência oculta de fundos para o endereço do atacante, o que dá a aparência de um contrato legítimo mas, na realidade, resulta numa perda de fundos.

Exemplo de código:


pragma solidity 0.4.18

contract HiddenTransferHoneypot {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    function deposit() public payable {
        if (msg.value >= 1 ether) {
            owner.transfer(msg.value);
        }
    }

    function withdraw() public {
        require(msg.sender == owner);
        owner.transfer(address(this).balance);
    }
}

Neste exemplo, o contrato parece permitir depósitos e levantamentos. No entanto, a função de carregamento contém uma transferência oculta de fundos para o proprietário (o atacante) se o montante do carregamento for igual ou superior a 1 ether. Os utilizadores que carregam as suas contas sem suspeitar perderão os seus ethers, uma vez que serão transferidos para o endereço do atacante.

7. Reentrancy honeypot

O honeypot de reentrada baseia-se na exploração de uma vulnerabilidade de reentrada num contrato, em que uma função de contrato pode ser chamada recursivamente antes de o seu estado ser atualizado.

Exemplo de código:


pragma solidity 0.4.18

contract ReentrancyHoneypot {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() public {
        uint256 amount = balances[msg.sender];
        (bool success,) = msg.sender.call.value(amount)("");
        require(success, "Withdrawal failed.");
        balances[msg.sender] = 0;
    }
}

Neste exemplo, o contrato permite depósitos e levantamentos. No entanto, é vulnerável a ataques de reentrada. Um atacante poderia criar um contrato malicioso que chamaria recursivamente a função withdraw antes de o saldo ser definido para zero, esgotando todo o saldo do contrato.

8. Fundição de tipo e alvéolo de transbordo

Neste tipo de honeypot, o atacante utiliza a conversão de tipos e transbordos de números inteiros para enganar os utilizadores e fazê-los acreditar que o contrato é seguro, mas na realidade contém armadilhas ocultas.

Exemplo de código:


pragma solidity 0.4.18

contract TypeCastingHoneypot {
    uint8 public count = 0;
    uint256 public reward = 1 ether;

    function increment() public payable {
        require(msg.value >= reward);

        uint8 prevCount = count;
        count++;

        if (count < prevCount) {
            msg.sender.transfer(address(this).balance);
        }
    }
}

Neste exemplo, o contrato recompensa os utilizadores que incrementam a variável count quando enviam um montante igual ou superior ao montante da recompensa. No entanto, devido ao uso de uint8 para a variável count, ocorre um estouro de número inteiro quando o valor 255 é atingido. Após o estouro, a contagem é redefinida para 0 e um invasor pode obter todo o saldo do contrato.

fundição de tipo e alvéolo de transbordo

9. Honeypot de chamada delegada

Neste tipo de honeypot, o atacante utiliza a função delegatecall para executar código malicioso em nome de um contrato aparentemente seguro.

Exemplo de código:


pragma solidity 0.4.18

contract DelegateCallHoneypot {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    function() external payable {}

    function withdraw() public {
        require(msg.sender == owner);
        owner.transfer(address(this).balance);
    }

    function execute(address _target, bytes memory _data) public {
        require(msg.sender == owner);
        (bool success,) = _target.delegatecall(_data);
        require(success, "Execution failed.");
    }
}

Neste exemplo, o contrato permite depósitos, levantamentos e execução de código através da função execute. No entanto, um atacante poderia criar código malicioso que poderia manipular o estado do contrato, incluindo a variável do proprietário, executando a função delegatecall. Ao alterar o proprietário, um atacante poderia ganhar o controlo do contrato e esvaziar o seu saldo.

10. Manipulación de almacenamiento oculto honeypot

Neste tipo de honeypot, o atacante manipula secretamente as variáveis de armazenamento para induzir os utilizadores a assumir o controlo do contrato.

Exemplo de código:


pragma solidity 0.4.18

contract HiddenStorageManipulation {
    mapping(address => uint256) public balances;
    uint256 public totalSupply;
    address public owner;

    constructor() public {
        owner = msg.sender;
        balances[owner] = 1000;
        totalSupply = 1000;
    }

    function transfer(address _to, uint256 _value) public {
        require(balances[msg.sender] >= _value);
        balances[msg.sender] -= _value;
        balances[_to] += _value;

        if (totalSupply - balances[owner] >= 1000) {
            owner = _to;
        }
    }
}

Neste exemplo, o contrato assemelha-se a um simples contrato de token. Os utilizadores podem passar tokens uns aos outros, e o proprietário é inicialmente definido como o criador do contrato. No entanto, um atacante pode manipular secretamente as variáveis totalSupply e balances para recuperar o controlo do contrato. Quando a diferença entre totalSupply e balances[owner] atinge 1000, a variável owner é actualizada, permitindo ao atacante recuperar o controlo do contrato.

11. Nome da função: honeypot de colisão

Neste tipo de honeypot, o atacante utiliza colisões de nomes de funções para induzir os utilizadores a chamar a função errada.

Exemplo de código:


pragma solidity 0.4.18

contract FunctionNameCollision {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    function withdraw() external {
        require(msg.sender == owner);
        msg.sender.transfer(address(this).balance);
    }

    function withdraw(uint256 amount) external {
        require(msg.sender == owner);
        owner.transfer(amount);
    }

    function() external payable {}
}

Neste exemplo, o contrato tem duas funções de levantamento. Uma delas permite que o proprietário retire todo o saldo e a outra permite que o proprietário retire um determinado montante. No entanto, se o utilizador tentar levantar um determinado montante chamando withdraw(uint256), devido a uma colisão de nomes de funções, o utilizador chamará inadvertidamente withdraw() sem argumentos, o que resultará no levantamento de todo o saldo do contrato.

12. Honeypot da vulnerabilidade Delegatecall

Neste tipo de honeypot, o atacante utiliza o delegatecall para executar uma função maliciosa noutro contrato, o que pode levar ao roubo de fundos dos utilizadores.

Exemplo de código:


pragma solidity 0.4.18

contract DelegatecallVulnerability {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    function withdraw() external {
        require(msg.sender == owner);
        msg.sender.transfer(address(this).balance);
    }

    function () external payable {
        if (msg.value > 1 ether) {
            this.delegatecall(msg.data);
        }
    }
}

contract Malicious {
    function withdraw() external {
        msg.sender.transfer(address(this).balance);
    }
}                

Neste exemplo, o contrato parece inócuo, com uma simples função withdraw() que permite ao proprietário retirar o saldo do contrato. No entanto, o contrato também contém a função fallback, que quando mais de 1 éter é recebido, executa uma delegatecall de outro contrato usando msg.data. Se um atacante criar um contrato malicioso com uma função withdraw() que rouba o saldo do contrato e envia mais de 1 ether para o contrato vítima, a delegatecall executará a função maliciosa withdraw(), roubando o saldo do contrato.

honeypot da vulnerabilidade Delegatecall

13. Pseudo-inteiros com honeypot de overflow

Este tipo de honeypot baseia-se no facto de o Solidity não ter suporte integrado para números de vírgula flutuante. Os atacantes podem criar contratos que exteriormente utilizam números decimais, mas que na realidade manipulam números inteiros de forma fraudulenta.

Exemplo de código:


pragma solidity 0.4.18

contract PseudoIntegersOverflow {
    mapping(address => uint256) public balances;

    function deposit() external payable {
        require(msg.value > 0);
        uint256 pseudoValue = msg.value * 100;
        balances[msg.sender] += pseudoValue;
    }

    function withdraw(uint256 amount) external {
        uint256 pseudoAmount = amount * 100;
        require(balances[msg.sender] >= pseudoAmount);
        balances[msg.sender] -= pseudoAmount;
        msg.sender.transfer(amount);
    }
}

Este exemplo dá a impressão de que o contrato gere o saldo do utilizador com precisão decimal, multiplicando os valores por 100 antes de os guardar. No entanto, esta abordagem é enganadora porque o Solidity não suporta números de vírgula flutuante. Quando o utilizador tenta levantar os seus fundos, o contrato calcula incorretamente a pseudo-soma devido ao truncamento de números inteiros, e o utilizador pode não conseguir levantar todo o seu saldo.

Para se proteger contra este tipo de honeypot, é necessário compreender as limitações da linguagem Solidity e a forma como esta lida com as operações numéricas. Tenha cuidado ao trabalhar com contratos que afirmam suportar precisão decimal e verifique sempre o código do contrato para detetar possíveis problemas.

14. Taxas ocultas em contratos inteligentes

Alguns contratos inteligentes maliciosos podem impor pagamentos ocultos aos utilizadores sem o seu conhecimento. Esses pagamentos podem estar disfarçados no código do contrato ou ser accionados apenas em determinadas condições.

Exemplo de código:


pragma solidity 0.4.18

contract HiddenFees {
    address public owner;
    uint256 public feePercentage;

    constructor(uint256 _feePercentage) public {
        owner = msg.sender;
        feePercentage = _feePercentage;
    }

    function withdraw(uint256 amount) external {
        uint256 fee = amount * feePercentage / 100;
        uint256 netAmount = amount - fee;
        require(address(this).balance >= netAmount);
        msg.sender.transfer(netAmount);
        owner.transfer(fee);
    }
}

Neste exemplo, o criador do contrato define uma taxa (feePercentage) aquando da implementação do contrato. Ao interagir com o contrato, os utilizadores podem não ter conhecimento desta taxa. Quando um utilizador levanta os seus fundos, o contrato calcula a comissão, subtrai-a do montante do levantamento e envia o restante para o utilizador. A comissão é então transferida para o proprietário do contrato.

Para evitar cair nesta armadilha, reveja cuidadosamente o código do contrato e certifique-se de que compreende todas as taxas associadas. Procure cálculos ocultos ou disfarçados.

15. Manipulação de estados ocultos

Em alguns casos, os contratos maliciosos podem permitir que o proprietário do contrato ou um atacante manipule o estado interno do contrato, levando a consequências inesperadas para outros utilizadores.

Exemplo de código:


pragma solidity 0.4.18

contract HiddenStateManipulation {
    address public owner;
    mapping(address => uint256) public balances;

    constructor() public {
        owner = msg.sender;
    }

    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount);
        msg.sender.transfer(amount);
        balances[msg.sender] -= amount;
    }

    function manipulateBalance(address user, uint256 newBalance) external {
        require(msg.sender == owner);
        balances[user] = newBalance;
    }
}

Neste exemplo, o contrato permite aos utilizadores depositar e levantar fundos. No entanto, a função manipulateBalance permite ao proprietário do contrato alterar arbitrariamente o saldo de qualquer utilizador. Isto pode resultar em perdas inesperadas para os utilizadores ou permitir que o proprietário do contrato roube fundos.

O código do contrato deve ser analisado para detetar características que permitam a manipulação pelo Estado, especialmente se apenas o titular do contrato ou determinados endereços tiverem acesso às mesmas.

manipulação de estados ocultos

16. Roubo oculto de fichas

Em alguns casos, os contratos maliciosos podem conter características ocultas que permitem ao proprietário do contrato ou ao atacante roubar tokens a utilizadores desprevenidos.

Exemplo de código:


pragma solidity 0.4.18

contract HiddenTokenStealing {
    address public owner;
    mapping(address => uint256) public balances;

    constructor() public {
        owner = msg.sender;
    }

    function deposit(IERC20 token, uint256 amount) external {
        require(token.transferFrom(msg.sender, address(this), amount));
        balances[msg.sender] += amount;
    }

    function withdraw(IERC20 token, uint256 amount) external {
        require(balances[msg.sender] >= amount);
        require(token.transfer(msg.sender, amount));
        balances[msg.sender] -= amount;
    }

    function stealTokens(IERC20 token, address user, uint256 amount) external {
        require(msg.sender == owner);
        require(token.transfer(owner, amount));
        balances[user] -= amount;
    }
}

Neste exemplo, o contrato permite aos utilizadores depositar e levantar tokens ERC20. No entanto, a função stealTokens permite que o proprietário do contrato roube tokens a qualquer utilizador.

Conclusão:

O Honeypot é apenas um dos muitos esquemas fraudulentos da DeFi que utiliza não só a restrição de levantamento, mas também as várias armadilhas ocultas descritas acima.

Para evitar cair no engodo, é necessário compreender as peculiaridades do esquema fraudulento, as subtilezas da linguagem de programação Solidity e estudar minuciosamente os contratos inteligentes antes de interagir com eles.

Mantendo-se informado e efectuando diligências adequadas, pode minimizar os riscos associados à utilização de contratos inteligentes.

 

Se tiver dúvidas sobre a segurança ou a funcionalidade de um contrato inteligente, utilize a nossa plataforma Lotus Market.

 

A plataforma Lotus Market foi criada por uma equipa de programadores experientes e auditores profissionais. O objetivo do projeto é minimizar os riscos de investimento em tokens fraudulentos e ajudar a negociar criptomoedas de forma segura e confortável.

Cumprimentos, equipa do Lotus Market.

All posts

Connect to a wallet

Metamask