Анализ смарт-контрактов
27.03.2024

Скрытые ловушки в Honeypot. Как распознать приманку и избежать потери активов?

Смарт-контракты Honeypot - это один из видов мошеннических смарт-контрактов, которые выглядят привлекательно для пользователей и обещают финансовую выгоду. Однако такие контракты содержат скрытые ловушки или вредоносный код, в результате чего пользователи, наоборот, теряют свои средства.

Чтобы защитить себя от смарт-контрактов Honeypot необходимо тщательно изучать и проверять код контракта перед началом работы с ним. Мир смарт-контрактов и блокчейна постоянно развивается, регулярно появляются новые уязвимости и способы обмана, поэтому разработчикам, аудиторам и пользователям крайне важно быть в курсе последних тенденций и разработок в этой области.

Существует множество примеров смарт-контрактов Honeypot. В данной статье мы рассмотрим некоторые из примеров.

1. Fallback function honeypot

В этом типе honeypot контракт имеет уязвимую функцию «отката», которая, казалось бы, позволяет любому человеку стать владельцем контракта и снять его баланс. Однако такая функция содержит скрытое условие, препятствующее успешному выполнению.

Пример кода:


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);
    }
}

В данном примере функция fallback позволяет любому пользователю стать владельцем. Однако оператор revert() гарантирует, что смена владельца никогда не произойдет, оставляя средства злоумышленника в ловушке контракта.

2. Hidden condition honeypot

Такие контракты содержат скрытое условие или требование, которое должно быть выполнено для успешного выполнения кажущейся уязвимой функции. Пользователи могут считать, что могут использовать контракт, но скрытое условие гарантирует, что они не смогут этого сделать.

Пример кода:


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);
    }
}

В этом контракте пользователь может внести 1 эфир, и, казалось бы, после 100 депозитов он может снять весь баланс контракта. Однако условие counter > 100 гарантирует, что вывод средств возможен только после 101 депозита, что задерживает средства в контракте.

3. Reentrancy honeypot

В этом типе honeypot контракт оказывается уязвимым для атаки reentrancy, когда злоумышленник может неоднократно вызывать функцию контракта во время вывода средств. Однако контракт содержит скрытый механизм, который предотвращает успешную эксплуатацию.

Пример кода:


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;
        }
    }
}

В данном примере контракт оказывается уязвимым для атаки reentrancy из-за использования msg.sender.call.value(_amount)(). Однако контракт не позволяет пользователям пополнять счет на сумму, превышающую начальный баланс, что фактически задерживает их средства.

reentrancy honeypot

4. Gas limit honeypot

В этом типе honeypot контракт вроде бы позволяет пользователю вывести средства, но количество gas, необходимое для выполнения функции вывода, превышает лимит gas-блока, что делает транзакцию невозможной.

Пример кода:


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);
    }
}

В данном примере функция deposit инициализирует массив с 1000 элементами. Функция withdraw переводит баланс контракта отправителю, но при этом изменяет элементы массива. Gas, необходимый для выполнения цикла, превышает лимит gas-блока, что не позволяет осуществить вывод средств и задерживает их в контракте.

5. Timestamp manipulation honeypot

В этом типе honeypot контракт использует временную метку блокчейна Ethereum в качестве условия выполнения. Однако, поскольку майнеры могут манипулировать временной меткой блока, контракт становится уязвимым.

Пример кода:


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;
    }
}

В этом контракте пользователи могут вносить средства и снимать весь баланс контракта по истечении 1 часа с момента последнего взаимодействия. Однако, поскольку майнеры могут манипулировать временной меткой блока, они могут запустить функцию снятия раньше, чем ожидалось, и тем самым фактически украсть средства.

6. Hidden transfer honeypot

В этом типе honeypot контракт содержит скрытый перевод средств на адрес злоумышленника, что создает видимость легитимного контракта, но в действительности приводит к потере средств.

Пример кода:


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);
    }
}

В данном примере контракт вроде бы позволяет вносить и снимать средства. Однако функция пополнения счета содержит скрытый перевод средств владельцу (злоумышленнику), если сумма пополнения равна или больше 1 эфира. Ничего не подозревающие пользователи, пополнившие счет, потеряют свои эфиры, так как они будут переведены на адрес злоумышленника.

7. Reentrancy honeypot

Reentrancy honeypot основан на использовании уязвимости reentrancy в контракте, когда функция контракта может быть вызвана рекурсивно до обновления его состояния.

Пример кода:


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;
    }
}

В данном примере контракт разрешает ввод и вывод средств. Однако он уязвим для reentrancy-атак. Злоумышленник может создать вредоносный контракт, который будет рекурсивно вызывать функцию withdraw до того, как баланс будет установлен в ноль, что приведет к истощению всего баланса контракта.

8. Type casting and overflow honeypot

В этом типе honeypot злоумышленник использует приведение типов и целочисленные переполнения, чтобы обмануть пользователей и заставить их поверить, что контракт безопасен, но на самом деле он содержит скрытые ловушки.

Пример кода:


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);
        }
    }
}

В данном примере контракт вознаграждает пользователей, которые увеличивают переменную count при отправке суммы, равной или превышающей размер вознаграждения. Однако из-за использования uint8 для переменной count при достижении значения 255 происходит целочисленное переполнение. После переполнения счетчик сбрасывается в 0, и злоумышленник может получить весь баланс контракта.

overflow honeypot

9. Delegatecall honeypot

В этом типе honeypot злоумышленник использует функцию delegatecall для выполнения вредоносного кода от имени, казалось бы, безопасного контракта.

Пример кода:


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.");
    }
}

В данном примере контракт разрешает пополнение, снятие средств и выполнение кода через функцию execute. Однако злоумышленник может создать вредоносный код, который при выполнении функции delegatecall сможет манипулировать состоянием контракта, в том числе переменной owner. Изменив владельца, злоумышленник может получить контроль над контрактом и опустошить его баланс.

10. Hidden storage manipulation honeypot

В этом типе honeypot злоумышленник скрыто манипулирует переменными хранилища, чтобы обмануть пользователей и получить контроль над контрактом.

Пример кода:


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;
        }
    }
}

В данном примере контракт выглядит как простой токен-контракт. Пользователи могут передавать токены друг другу, а владельцем изначально установлен создатель контракта. Однако злоумышленник может скрыто манипулировать переменными totalSupply и balances, чтобы вернуть контроль над контрактом. Когда разница между totalSupply и balances[owner] достигает 1000, переменная owner обновляется, что позволяет злоумышленнику вернуть контроль над контрактом.

11. Function name collision honeypot

В этом типе honeypot злоумышленник использует коллизии имен функций, чтобы обманом заставить пользователей вызвать неправильную функцию.

Пример кода:


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 {}
}

В данном примере контракт имеет две функции снятия средств. Одна из них позволяет владельцу снять весь остаток средств, а другая - определенную сумму. Однако если пользователь попытается снять определенную сумму, вызвав функцию withdraw(uint256), то из-за коллизии имен функций он непроизвольно вызовет функцию withdraw() без аргументов, что приведет к снятию всего баланса контракта.

12. Delegatecall vulnerability honeypot

В этом типе honeypot злоумышленник использует delegatecall для выполнения вредоносной функции в другом контракте, что может привести к краже средств у пользователей.

Пример кода:


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);
    }
}                

В данном примере контракт кажется безобидным, с простой функцией withdraw(), позволяющей владельцу снять баланс контракта. Однако в контракте также присутствует функция fallback, которая при получении более 1 эфира выполняет delegatecall другого контракта, используя msg.data. Если злоумышленник создаст вредоносный контракт с функцией withdraw(), которая похищает баланс контракта, и отправит более 1 эфира на контракт-жертву, то delegatecall выполнит вредоносную функцию withdraw(), похитив баланс контракта.

vulnerability honeypot

13. Pseudo-integers overflow honeypot

Этот тип honeypot основан на том, что Solidity не имеет встроенной поддержки чисел с плавающей точкой. Злоумышленники могут создавать контракты, которые внешне используют десятичные числа, но на самом деле манипулируют целыми числами обманным путем.

Пример кода:


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);
    }
}

В данном примере создается впечатление, что контракт управляет балансом пользователя с десятичной точностью, умножая значения на 100 перед их сохранением. Однако такой подход вводит в заблуждение, поскольку Solidity не поддерживает числа с плавающей точкой. Когда пользователь пытается снять свои средства, контракт неверно вычисляет псевдосумму из-за целочисленного усечения, и пользователь может оказаться не в состоянии снять весь свой баланс.

Для защиты от такого типа honeypot необходимо понимать ограничения языка Solidity и то, как он обрабатывает числовые операции. Будьте осторожны при работе с контрактами, в которых заявлена поддержка десятичной точности, и всегда проверяйте код контракта на наличие потенциальных проблем.

14. Hidden fees in smart contracts

Некоторые вредоносные смарт-контракты могут навязывать пользователям скрытые платежи без их ведома. Такие платежи могут быть замаскированы в коде контракта или срабатывать только при определенных условиях.

Пример кода:


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);
    }
}

В данном примере создатель контракта при развертывании устанавливает размер платы (feePercentage). При взаимодействии с контрактом пользователи могут не знать об этой комиссии. Когда пользователь снимает свои средства, контракт рассчитывает комиссию, вычитает ее из суммы снятия и отправляет оставшуюся часть пользователю. Затем комиссия перечисляется владельцу контракта.

Чтобы не попасть в эту ловушку, внимательно изучите код контракта и убедитесь, что вам понятны все связанные с ним комиссии. Ищите скрытые или завуалированные расчеты.

15. Hidden state manipulation

В некоторых случаях вредоносные контракты могут позволить владельцу контракта или злоумышленнику манипулировать внутренним состоянием контракта, что приведет к неожиданным последствиям для других пользователей.

Пример кода:


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;
    }
}

В данном примере контракт позволяет пользователям вносить и снимать средства. Однако функция manipulateBalance позволяет владельцу контракта произвольно изменять баланс любого пользователя. Это может привести к неожиданным потерям для пользователей или позволить владельцу контракта украсть средства.

Необходимо внимательно изучать код контракта на предмет наличия функций, позволяющих манипулировать состоянием, особенно если доступ к ним имеет только владелец контракта или определенные адреса.

hidden state manipulation

16. Hidden token stealing

В некоторых случаях вредоносные контракты могут содержать скрытые функции, позволяющие владельцу контракта или злоумышленнику похищать токены у ничего не подозревающих пользователей.

Пример кода:


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;
    }
}

В данном примере контракт позволяет пользователям вводить и выводить токены ERC20. Однако функция stealTokens позволяет владельцу контракта украсть токены у любого пользователя.

Заключение:

Honeypot - это только одна из множества мошеннических схем в DeFi, которая использует не только ограничение на вывод средств, но и различные скрытые ловушки, описанные выше.

Чтобы не попасть на приманку необходимо понимать особенности мошеннической схемы, тонкости языка программирования Solidity и тщательно изучать смарт-контракты перед взаимодействием с ними.

Оставаясь информированным и проводя тщательную проверку, вы сможете минимизировать риски, связанные с использованием смарт-контрактов.

 

Если у вас появились сомнения в безопасности или функциональности смарт-контракта воспользуйтесь нашей платформой Lotus Market.

 

Платформа Lotus Market создана командой опытных разработчиков и профессиональных аудиторов. Цель проекта - минимизировать риски инвестирования в мошеннические токены и помочь безопасно и комфортно торговать криптовалютами.

С уважением, команда Lotus Market.

All posts

Connect to a wallet

Metamask