连年来,各个大型CTF(Capture The Flag,中文一般译作夺旗赛,在网络安详规模中指的是网络安详技能人员之间举办技能竞技的一种角逐形式)角逐中都有了区块链攻防的身影,并且呈现的题目绝大大都都是区块链智能合约攻防。此系列文章我们主要以智能合约攻防为中心,来分解智能合约攻防的要点,前两篇我们分享了合约反编译,反汇编的基本内容。后续的文章中,我们会继承分享CTF角逐中智能合约常见题型(重入,整数溢出,空投,随机数可控等)及解题思路,相信会给读者带来纷歧样的收获。
上篇文章中我们分享了CTF角逐中常考的整数溢出裂痕题型,其顶用到了变量包围等多种进攻能力,需要读者仔细推敲。本篇文章我们继承分享CTF角逐中的空投题型,也就是薅羊毛。在系列文章整数溢出题型中,也用到了空投,但只是挪用一次空投到达触发其他裂痕的判定条件,并没有举办批量获取空投。
本篇我们以2020年NSSC CTF上skybank题目为例,分享智能合约薅羊毛的题型,该题型也是多次呈此刻CTF的赛场。相对付之前的系列文章内容,本篇薅羊毛题型更容易领略。
题目地点:https://ropsten.etherscan.io/address/0xe6bebc078bf01c06d80b39e0bb654f70c7b0c273#code
题目阐明
题目提示
原始合约的opcode需举办反编译;
空投及最终判定函数别离为gether()和ObtainFlag();
触发ObtainFlag()函数事件event则进攻乐成;
需给合约提供资金。
合约源码
查察合约题目,合约存在0.62ether,没有给出合约源码,如下图:
由于拿到题目后只有合约的opcode,所以需要举办逆向,这里我们推荐Online Solidity Decompiler在线网站(https://ethervm.io/decompile),详细逆向时的源码还原我们不再赘述,需要进修的同学可移步系列文章反编译篇,反汇编篇。
以下为逆向后的合约代码:
pragma?solidity?^0.4.24;
contract?skybank{
mapping(address?=>?uint)?public?balances; ????event?sendflag(string?base64email,string?md5namectf); ????bytes20?addr?=?bytes20(msg.sender);
function?ObtainFlag(string?base64email,string?md5namectf){ ????????require(balances[msg.sender]?>=?1000000000); ????????emit?sendflag(base64email,md5namectf); ????}
function?gether()?public?{ ????????require(balances[msg.sender]?==?0); ????????balances[msg.sender]?+=?10000000; ????}
function?Transfer(address?to,?uint?bur)?public?{ ????????require(bur?==?balances[msg.sender]); ????????balances[to]?+=?bur; ????????balances[msg.sender]?-=?bur; ????} }
合约阐明
先来看题目最终的判定函数ObtainFlag():
function?ObtainFlag(string?base64email,string?md5namectf){ ????require(balances[msg.sender]?>=?1000000000); ????emit?sendflag(base64email,md5namectf); }
从该函数可以看出,obtainFlag()函数传入两个参数(base64email,md5namectf),函数第一行代码require(balances[msg.sender] >= 1000000000);会判定挪用者地点余额是否大于便是1000000000 wei,假如满意该条件,,则执行emit sendflag(base64email,md5namectf);代码,从题目可以得出,只要参赛者触发sendflag事件并将参数输出暗示获取flag乐成。
由于参赛者初始挪用题目合约skybank时,挪用地点在所属合约的资金为0,所以需要通过合约逻辑获取资金,继承来看获取空投函数gether():
function?gether()?public?{ ????require(balances[msg.sender]?==?0); ????balances[msg.sender]?+=?10000000; }
gether()函数中,第一句代码require(balances[msg.sender] == 0);判定当前挪用者的地点是否为0,假如满意条件,则给该挪用者加10000000 wei的资金,我们最终触发sendflag事件的ObtainFlag()函数中,需要1000000000 wei,所以只要挪用gether高出100次就可以触发sendflag事件。
继承阐明合约的转账函数Transfer():
function?Transfer(address?to,?uint?bur)?public?{ ????require(bur?==?balances[msg.sender]); ????balances[to]?+=?bur; ????balances[msg.sender]?-=?bur; }
Transfer()函数中,首先第一行代码require(bur == balances[msg.sender]);判定传入的参数bur和今朝挪用者地点的余额是否相等,假如条件满意,将该余额转至传入的地点to中,之后将挪用者地点的余额减掉。这里很是重要的一点是:转账之后的挪用者地点余额再次变为0,也就是说我们可以反复该函数举办转账。
解题思路
通过以上skybank题目合约阐明,可以总结出两种解题思路:
第一种:
通过A地点挪用gether()函数获取空投
挪用Transfer()函数将A地点余额转至B地点
从头利用A地点挪用gether()函数获取空投,并将余额转至B地点(不绝轮回)
利用B地点挪用ObtainFlag()并触发事件
第二种:
利用多个地点挪用gether()获取空投
将获取空投汇聚至牢靠地点
通过该牢靠地点挪用ObtainFlag()并触发事件
进攻演示
我们举办第一种解题思路的进攻演示,利用Remix+MetaMask对进攻合约举办陈设挪用
1. 自毁给题目合约转币
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。