본문 바로가기
  • Show the world what you can do
Web3/Ethernaut

[Ethernaut] Double Entrypoint 풀이

by kaymin 2025. 1. 18.
Instance
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "openzeppelin-contracts-08/access/Ownable.sol";
import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";

interface DelegateERC20 {
    function delegateTransfer(address to, uint256 value, address origSender) external returns (bool);
}

interface IDetectionBot {
    function handleTransaction(address user, bytes calldata msgData) external;
}

interface IForta {
    function setDetectionBot(address detectionBotAddress) external;
    function notify(address user, bytes calldata msgData) external;
    function raiseAlert(address user) external;
}

contract Forta is IForta {
    mapping(address => IDetectionBot) public usersDetectionBots;
    mapping(address => uint256) public botRaisedAlerts;

    function setDetectionBot(address detectionBotAddress) external override {
        usersDetectionBots[msg.sender] = IDetectionBot(detectionBotAddress);
    }

    function notify(address user, bytes calldata msgData) external override {
        if (address(usersDetectionBots[user]) == address(0)) return;
        try usersDetectionBots[user].handleTransaction(user, msgData) {
            return;
        } catch {}
    }

    function raiseAlert(address user) external override {
        if (address(usersDetectionBots[user]) != msg.sender) return;
        botRaisedAlerts[msg.sender] += 1;
    }
}

contract CryptoVault {
    address public sweptTokensRecipient;
    IERC20 public underlying;

    constructor(address recipient) {
        sweptTokensRecipient = recipient;
    }

    function setUnderlying(address latestToken) public {
        require(address(underlying) == address(0), "Already set");
        underlying = IERC20(latestToken);
    }

    /*
    ...
    */

    function sweepToken(IERC20 token) public {
        require(token != underlying, "Can't transfer underlying token");
        token.transfer(sweptTokensRecipient, token.balanceOf(address(this)));
    }
}

contract LegacyToken is ERC20("LegacyToken", "LGT"), Ownable {
    DelegateERC20 public delegate;

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }

    function delegateToNewContract(DelegateERC20 newContract) public onlyOwner {
        delegate = newContract;
    }

    function transfer(address to, uint256 value) public override returns (bool) {
        if (address(delegate) == address(0)) {
            return super.transfer(to, value);
        } else {
            return delegate.delegateTransfer(to, value, msg.sender);
        }
    }
}

contract DoubleEntryPoint is ERC20("DoubleEntryPointToken", "DET"), DelegateERC20, Ownable {
    address public cryptoVault;
    address public player;
    address public delegatedFrom;
    Forta public forta;

    constructor(address legacyToken, address vaultAddress, address fortaAddress, address playerAddress) {
        delegatedFrom = legacyToken;
        forta = Forta(fortaAddress);
        player = playerAddress;
        cryptoVault = vaultAddress;
        _mint(cryptoVault, 100 ether);
    }

    modifier onlyDelegateFrom() {
        require(msg.sender == delegatedFrom, "Not legacy contract");
        _;
    }

    modifier fortaNotify() {
        address detectionBot = address(forta.usersDetectionBots(player));

        // Cache old number of bot alerts
        uint256 previousValue = forta.botRaisedAlerts(detectionBot);

        // Notify Forta
        forta.notify(player, msg.data);

        // Continue execution
        _;

        // Check if alarms have been raised
        if (forta.botRaisedAlerts(detectionBot) > previousValue) revert("Alert has been triggered, reverting");
    }

    function delegateTransfer(address to, uint256 value, address origSender)
        public
        override
        onlyDelegateFrom
        fortaNotify
        returns (bool)
    {
        _transfer(origSender, to, value);
        return true;
    }
}

 

 

Solution

 

나는 위험을 감지하는 감지 봇을 만들어 등록해야 한다. 그리고 위험 발생을 막아야 성공한다.

보면 취약점은 LegacyToken이 DoubleEntryPoint를 delegateTransfer()를 호출할 때 발생하는 것을 볼 수 있다.

CryptoValut 컨트랙트에서 underlying Token은 D.E.P인데, 이는 따라서 Sweep할 수 없어야 하지만 legacyToken의 transfer()가 D.E.P의 취약한 함수를 호출하기에 해당 토큰을 다 터는게 가능한 문제인 것이다.

따라서 그 delegateTransfer()을 호출하면 위험 Alert를 내도록 감지봇을 설계해야 한다.

 

나는 msg.data의 첫 4바이트가 호출하는 함수의 시그니처임을 이용해서 풀었다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IForta {
    function setDetectionBot(address detectionBotAddress) external;
    function notify(address user, bytes calldata msgData) external;
    function raiseAlert(address user) external;
}
contract DetectionBot{
    function handleTransaction(address user, bytes calldata msgData) public{
        address player=0x709c3046593A0b9ee7DFe43F16Fb0155D3D25Ace;
        if (user==player){
            if (bytes4(msgData[:4])==0x9cd1a121){// signature of delegateTransfer
                IForta forta=IForta(0x7d921A956e866C438dbf55C6DD8D0c2F072e2653);
                forta.raiseAlert(player);
            }
        }
    }
    //success
}

'Web3 > Ethernaut' 카테고리의 다른 글

[Ethernaut] Stake 풀이  (0) 2025.01.18
[Ethernaut] Gatekeeper Three 풀이  (0) 2025.01.18
[Ethernaut] Re-entrancy 풀이  (0) 2025.01.18
[Ethernaut] Naught Coin 풀이  (0) 2025.01.18
[Ethernaut] Higher Order 풀이  (0) 2025.01.18

댓글