okx
广告

智能合约薅羊毛技能大揭秘

时间:2023-06-17|浏览:167

欧易

欧易(OKX)

用戶喜愛的交易所

币安

币安(Binance)

已有账号登陆后会弹出下载

前言:

近年来,各大型CTF比赛中都有了区块链攻防的身影,其中绝大多数出现的题目都是区块链智能合约攻防。本系列文章主要以智能合约攻防为中心,来剖析智能合约攻防的要点。前两篇我们分享了合约反编译和反汇编的基础内容。接下来我们会继续分享CTF比赛中智能合约常见题型(如重入,整数溢出,空投和随机数可控等)及解题思路,相信会给读者带来不一样的收获。

本篇文章主要以2020年NSSCCTF上的skybank题目为例,分享智能合约薅羊毛的题型。该题型也是多次出现在CTF比赛中。相对于之前的系列文章内容,本篇薅羊毛题型更容易理解,比较简单易懂。

题目分析:

题目提示:原始合约的opcode需进行反编译;空投及最终判断函数分别为gether和ObtainFlag;触发ObtainFlag函数事件event则攻击成功;需给合约提供资金。

查看合约题目,合约存在0.62 ether,没有给出合约源码。

由于拿到题目后只有合约的opcode,所以需要进行逆向。这里我们推荐OnlineSolidityDecompiler在线网站(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)会判断调用者地址的余额是否大于等于1000000000wei,如果满足该条件,就执行emit sendflag(base64email, md5namectf)代码。从题目可以得出,只要参赛者触发sendflag事件并将参数输出表示获取flag成功。

由于参赛者初始调用题目合约skybank时,调用地址在所属合约的资金为0。因此,我们需要通过合约逻辑获取资金。接下来我们来看获取空投函数gether:

function gether() public { require(balances[msg.sender] == 0); balances[msg.sender] += 10000000; }

在geth函数中,第一句代码require(balances[msg.sender] == 0)判断当前调用者的地址是否为0。如果满足条件,就给该调用者加10000000 wei的资金。在我们最终触发sendflag事件的ObtainFlag函数中,需要1000000000wei。因此,只要调用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,也就是说我们可以重复该函数进行转账操作。

解题思路:

通过以上分析,可以总结出两种解题思路:

第一种:

- 通过A地址调用gether函数获取空投 - 调用Transfer函数将A地址余额转至B地址 -

- 使用多个地址调用gether获取空投 - 将获取空投汇聚至固定地址 - 通过该固定地址调用ObtainFlag并触发事件

攻击演示:

我们来演示一下第一种解题思路的攻击过程,使用Remix+MetaMask对攻击合约进行部署调用。

- 自毁合约给题目合约转币

由于题目合约的初始状态没有ether,因此我们可以通过自毁函数,强行将ether转入题目合约地址。虽然当前题目合约有一定资金,我们为了演示的完整性,也演示一次自毁。

pragma solidity ^0.4.24;

contract burn { function kill() public payable { selfdestruct(address(0xE6BEBc078Bf01C06D80b39E0bb654F70C7B0C273)); } }

部署burn合约,并利用kill函数带入0.02Ether进行自毁,将Ether发送到题目合约地址。

- 使用A地址部署最终调用者合约attacker2

调用代码:

pragma solidity ^0.4.24;

interface skybankInterface { function ObtainFlag(string base64email, string md5namectf); }

contract attacker2 {

热点:区块链 智能合约

« 上一条| 下一条 »
区块链交流群
数藏交流群
区块链币圈-全球区块链数字货币行情、比特币虚拟货币资讯,狗狗币以太坊环保币柚子币莱特币瑞波币等加密数字货币价格非交易行情查询,金色财经巴比特范非小号快讯平台。
趣开心资讯 Qukaixin.cn ©2020-2024版权所有 桂ICP备19010284号-1