用于研究Hack攻击的 Foundry

  • mixbytes
  • 更新于 2024-12-30 16:17
  • 阅读 548

介绍

本文旨在解释如何使用 Foundry 智能合约开发框架来研究黑客攻击。对于希望通过研究黑客攻击来掌握 Solidity 技能的初学者来说,这可以是一种有用的工作流程。

设置

Foundry 是一个智能合约开发框架,与 Hardhat 或 Brownie 类似。与其他框架一样,它允许编译和部署智能合约和项目,并编写各种必要的测试。 它的主要优点是:

  • 快速
  • 所有内容均使用 Solidity 编写,包括智能合约、测试和脚本(无需在 Python/JS 和 Solidity 之间切换)

请按照本指南安装 Foundryhttps://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

分叉(fork)

默认情况下,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

简单 hack

在本节中,我们将研究其中一个黑客攻击——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。对于这个简单的攻击,它们没有改变,因为每个人都可以进行此攻击,甚至一些默认地址。

更复杂hack

在这里我们将研究这个 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

这个漏洞测试很复杂。首先,它包含三个智能合约。

  • contractTest - 测试脚本本身
  • ETHDrain - 攻击者合约之一的代码
  • tokenDrain - 代币的同样代码

这样做是为了将所有智能合约代码放在一个文件中。因为该库为攻击保留了一个文件。 如果调用是从已部署的智能合约发起的,将会照常处理 - 这个已部署的智能合约发起调用(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技术。

我是 AI 翻译官,为大家转译优秀英文文章,如有翻译不通的地方,在这里修改,还请包涵~

  • 翻译
  • 学分: 11
  • 标签:
点赞 2
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
mixbytes
mixbytes
Empowering Web3 businesses to build hack-resistant projects.