近年来,各个大型CTF(Capture The Flag,中文一般译作夺旗赛,在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式)比赛中都有了区块链攻防的身影,而且出现的题目绝大多数都是区块链智能合约攻防。此系列文章我们主要以智能合约攻防为中心,来剖析智能合约攻防的要点,前两篇我们分享了合约反编译,反汇编的基础内容。后续的文章中,我们会继续分享CTF比赛中智能合约常见题型(重入,整数溢出,空投,随机数可控等)及解题思路,相信会给读者带来不一样的收获。
本篇我们先来分享CTF比赛中的重入题型,也是比较常见的一类题型,当然多数CTF智能合约题目并不仅仅考察单个漏洞的攻防,合约中的判断条件有时也非常棘手。比如2018年WCTF上BelluminarBank题目,需要用到整数绕过条件限制,还需用到存储溢出,访问权限设置等多个攻击技巧。
本篇分享的重入题型我们选择2019强网杯babybank题目。
题目地址:https://ropsten.etherscan.io/address/0x93466d15A8706264Aa70edBCb69B7e13394D049f#code
题目提示:
function payforflag(string md5ofteamtoken,string b64email) public{
require(balance[msg.sender] >= 10000000000);
balance[msg.sender]=0;
owner.transfer(address(this).balance);
emit sendflag(md5ofteamtoken,b64email);
}
合约源码:
查看合约题目,发现并没有ether,也没有给出合约源码,如下图:
由于拿到题目后只有合约的opcode,所以需要进行逆向,这里我们推荐Online Solidity Decompiler在线网站(https://ethervm.io/decompile),具体逆向时的源码还原我们不再赘述,需要学习的同学可移步系列文章反编译篇,反汇编篇
以下为逆向后的合约代码:
pragma solidity ^0.4.23;
contract babybank {
mapping(address => uint) public balance;
mapping(address => uint) public level;
address owner;
uint secret;
event sendflag(string md5ofteamtoken,string b64email);
constructor()public{
owner = msg.sender;
}
function payforflag(string md5ofteamtoken,string b64email) public{
require(balance[msg.sender] >= 10000000000);
balance[msg.sender]=0;
owner.transfer(address(this).balance);
emit sendflag(md5ofteamtoken,b64email);
}
modifier onlyOwner(){
require(msg.sender == owner);
_;
}
function withdraw(uint256 amount) public {
require(amount == 2);
require(amount <= balance[msg.sender]);
address(msg.sender).call.value(amount * 0x5af3107a4000)(); //重入漏洞点
balance[msg.sender] -= amount;
}
function profit() public {
require(level[msg.sender] == 0);
balance[msg.sender] += 1;
level[msg.sender] += 1;
}
function xxx(uint256 number) public onlyOwner {