- 原文链接:mixbytes.io/blog...
- 译者:AI翻译官,校对:翻译小组
- 本文链接:learnblockchain.cn/article…
本文旨在解释如何使用 Foundry 智能合约开发框架来研究黑客攻击。对于希望通过研究黑客攻击来掌握 Solidity 技能的初学者来说,这可以是一种有用的工作流程。
Foundry 是一个智能合约开发框架,与 Hardhat 或 Brownie 类似。与其他框架一样,它允许编译和部署智能合约和项目,并编写各种必要的测试。 它的主要优点是:
请按照本指南安装 Foundry: https://book.getfoundry.sh/getting-started/installation
构建项目的最简单方法是运行:
forge init
结果将生成以下项目结构。
├── lib
│ └── forge-std
...
├── script
│ └── Counter.s.sol
├── src
│ └── Counter.sol
└── test
│ └── Counter.t.sol
├── foundry.toml
├── README.md
该项目包含一个基本的智能合约和一个简单的单元测试。我们可以运行测试:
forge test
默认情况下,Foundry 在本地机器上启动一个网络。但你可以轻松自定义以使用任何真实网络——Foundry 允许进行网络分叉,包括指定确切的区块。
在实践中,它广泛用于进行真实的测试——一些项目更喜欢在真实代币和其他项目的环境中测试他们的项目。 这里我们应该介绍 Cheatcodes 的概念。Foundry 内置了预编译功能——它将某些智能合约调用视为保留命令,允许一些在真实网络中不允许的魔法操作。
有一个可用 Cheatcode 命令的庞大列表: https://learnblockchain.cn/docs/foundry/i18n/zh//forge/cheatcodes.html
操控 Cheatcode 是掌握 Foundry 的关键。 现在让我们编写一个测试:
pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
contract ForkTest is Test {
function setUp() public {
vm.createFork(MAINNET_RPC_URL);
}
function test_PrintBalanceCETH {
address cETH = 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5;
uint256 balanceOnCETH = cETH.balance;
console.log("Balance on cETH: ", balanceOnCETH);
}
}
我们创建了 ForkTest 文件,它继承自 Test(从 forge-std/Test.sol 导入)。这就是我们连接 Cheatcodes 的方式。
它具有函数 setUp()——这是一个保留的函数名称。它将在每个其他测试函数之前运行。因此,在这里我们可以指示文件中每个测试函数相同的操作。分叉在这里非常适合:
vm.createFork("MAINNET_RPC_URL")
该 Cheatcode 尝试在项目目录的 .env 文件中查找名为 "MAINNET_RPC_URL" 的 RPC。
因此,我们应该配置一个 env 文件——你应该在项目目录中添加 ".env" 文件。 用这一行填充它:
MAINNET_RPC_URL=https://eth.llamarpc.com
你可以在 RPC 提供商网站上注册以获取密钥(比如 Alchemy)。 或者你可以搜索任何公开可用的 RPC(但请记住,并非所有 RPC 都允许分叉)。
如果你的测试需要多个 RPC,你可以在 .env 文件中指定任意数量的 RPC。 从技术上讲,可以在一个测试文件中运行多个分叉并在它们之间切换,所有这些都使用 Cheatcode 命令。因此,拥有多个 RPC 是可以的。
当函数 setUp() 设置分叉后,是时候进行测试了。它们必须以 test_YourTestName 命名。
function test_PrintBalanceCETH {
address cETH = 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5;
uint256 balanceOnCETH = cETH.balance;
console.log("Balance on cETH: ", balanceOnCETH);
}
这个函数引入了一个新的 Cheatcode——console.log()。 它非常方便,可以在你的控制台上打印输出。
因此,此测试获取主网 cETH 地址并打印其 ETH 余额。 要执行测试,请运行以下 Forge 终端命令:
forge test
你会注意到没有任何输出。这是因为 Foundry 具有多级脚本详细程度。
如 Foundry 文档所述: 第 1 级(forge test):仅显示通过和失败测试的摘要。 第 2 级(forge test -vv):显示在测试期间发出的日志。这包括测试中的断言错误,显示预期与实际数据。 第 3 级(forge test -vvv):还显示失败测试的堆栈跟踪。 第 4 级(forge test -vvvv):堆栈跟踪显示所有测试,以及失败测试的设置跟踪。 第 5 级(forge test -vvvvv):始终显示堆栈跟踪和设置跟踪。
因此,如果你以以下方式运行测试,将会看到你的 console.log():
forge test -vv
分叉以研究黑客攻击
我们将使用 SunWeb3Sec 的这个仓库来研究黑客攻击库。 https://github.com/SunWeb3Sec/DeFiHackLabs
这是一个大型 Forge 仓库,具有长长的黑客演示列表。 你可以在 src/test 找到所有测试。截至本文撰写时,该仓库包含超过 300 个黑客攻击。 首先,请按照该仓库 README 中的设置说明进行操作,因为克隆一个仓库有不同的脚本。 当你在本地获取后,查看仓库结构——该仓库有许多测试,但不要用 forge test 运行所有测试——这样会花费大量时间来运行它们。
相反,使用以下命令:
forge test --contracts src/test/NAME_OF_THE_FILE.t.sol -vv
或者这个命令:
forge test --match-path src/test/NAME_OF_THE_FILE.t.sol -vv
在本节中,我们将研究其中一个黑客攻击——CowSwap exploit。 https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/CowSwap_exp.sol
这是黑客的交易记录。 https://etherscan.io/tx/0x90b468608fbcc7faef46502b198471311baca3baab49242a4a85b73d4924379b 如你所见,攻击者成功构建了恶意 calldata。因此,这个黑客攻击将非常简单地演示 Foundry 的其他可能性。
首先,看看 setUp() 函数:
function setUp() public {
cheats.createSelectFork("mainnet", 16_574_048);
vm.label(address(DAI), "DAI");
vm.label(address(swapGuard), "SwapGuard");
vm.label(address(GPv2Settlement), "GPv2Settlement");
}
这种类型的分叉几乎是相同的。它们之间的小区别在于:
vm.createSelectFork()
和
vm.createFork()
你可以在这里深入了解: https://book.getfoundry.sh/cheatcodes/forking
cheats 和 vm 在我们的目的上没有区别。 但是 createSelectFork() 和 createFork() 都可以接受第二个参数——区块号。如你所见,对于此攻击,它是 16_574_048。 然后你有这个 Cheatcode:
vm.label(address(DAI), "DAI");
它有助于更好地阅读跟踪。如果控制台中的跟踪打印地址 0x6B175474E89094C44Da98b954EedeAC495271d0F,则将以 "DAI" 标签显示。这在分析复杂的跟踪时非常方便。
然后,你在函数 testExploit() 中有攻击流程。问题是——在这个测试中,谁是攻击者?对于所有测试,Foundry 有默认的 msg.sender 和 tx.origin。对于这个简单的攻击,它们没有改变,因为每个人都可以进行此攻击,甚至一些默认地址。
在这里我们将研究这个 Hundred Finance 的黑客攻击。 https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/HundredFinance_2_exp.sol
官方事后分析: https://blog.hundred.finance/15-04-23-hundred-finance-hack-post-mortem-d895b618cf33 它利用了通货膨胀攻击,这是一种众所周知的攻击向量。如果你不熟悉,我们建议你阅读我们阐释该主题的文章。 https://mixbytes.io/blog/overview-of-the-inflation-attack
这个漏洞测试很复杂。首先,它包含三个智能合约。
这样做是为了将所有智能合约代码放在一个文件中。因为该库为攻击保留了一个文件。 如果调用是从已部署的智能合约发起的,将会照常处理 - 这个已部署的智能合约发起调用(msg.sender 会根据 EVM 规则进行更改)。
在攻击脚本中,我们有以下代码行:
...
cheats.startPrank(HundredFinanceExploiter);
hWBTC.transfer(address(this), 1_503_167_295);
cheats.stopPrank();
...
startPrank() 是一个极其重要的 cheatcode。它允许改变下一个调用的 msg.sender。以 cheats.stopPrank() 结束,这会将 msg.sender 更改回默认地址。
广泛使用的替代方案是 prank()。它只在下一个调用中更改 msg.sender。 脚本以获取闪电贷结束。但这并不是结束,因为它在闪电贷回调中继续 - 在函数 executeOperation() 中。 这个函数执行几个攻击 - 一个 ETHDrains() 和多个 tokenDrains()。你可以在下面找到这些函数。 每个函数都遵循相同的模式,其中一些攻击步骤被放置在 tokenDrains() 函数或 tokenDrain 智能合约构造函数中。
每个步骤后面都有 console.log() - 结果是攻击过程文档记录得非常清晰。
现在你对 Foundry 有了了解。你可以在开发中使用它,或继续以与我们在这里相同的方式研究hack技术。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!