兼容 EVM(以太坊虚拟机)的区块链旨在兼容以太坊区块链的智能合约功能、编程语言(Solidity)和工具生态系统。在此过程中,实现一个符合 EVM 的虚拟机是关键步骤之一。然而,在我们研究的过程中,我们发现要为不同实现中的 EVM 保持兼容性并非易事。
为了解决这个问题,BlockSec 开发了一个内部系统,可以系统地定位 EVM 实现中的错误和安全漏洞。这个系统被证明是有效的。它报告了 Aurora Engine 中的四个错误和 Moonbeam 中的四个错误。所有这些错误都已被报告并修复。值得注意的是,去年这种测试方法也被用于定位 Solana rbpf 实现中的两个关键漏洞(CVE-2021–46102、CVE-2022–23066)。
1. 背景
如今,许多不同的区块链被提议,以优化低 Gas 费、高吞吐量和出色的性能。在这种情况下,新的虚拟机或开发语言被提出,这增加了原始 Solidity 开发者的迁移门槛。在这种情况下,许多 EVM 兼容的解决方案被提出,允许用户在这些新链上部署他们用 Solidity 开发的 DApp。Aurora 和 Moonbeam 是这些 EVM 兼容解决方案的代表。然而,这些 EVM 实现的健壮性、可靠性和精确性尚不明确,值得我们关注。为此,我们利用差分模糊测试技术,检查实现中是否存在任何缺陷。
2. 差分模糊测试
基本思想是将相同的输入提供给这些 EVM 实现(例如 Aurora、Moonbeam)以及最先进的以太坊客户端(即 geth),以检查它们是否产生相同的输出。具体来说,我们收集以太坊上的历史交易,并用不同的策略变异合约代码和交易状态来生成测试用例。我们的发现表明,Aurora Engine 和 Moonbeam 在某些情况下不符合规范。幸运的是,所有报告的问题都得到了解决,现在我们将分享细节。
3. 发现的错误
这些错误大多存在于预编译合约中,其根本原因和影响各不相同。例如,一些错误会影响 Nonce 值的计算,而另一些则会影响 Gas 计算,并可能导致 DoS 攻击。所有发现的错误都违反了 EVM 规范的指定执行逻辑,并可能在特定情况下导致意外行为。请参阅下方发现错误的详细描述。
3.1 验证不当
加密逻辑很复杂。在这种情况下,在 EVM 字节码中实现相应的逻辑可能会导致相当大的 Gas 消耗。相反,用原生代码实现的预编译合约可以提高性能。然而,我们发现在预编译合约中存在由于验证不当而导致的几个错误。
Aurora Engine: ecPairing
这个错误存在于预编译合约 ecPairing 中。
ecPairing 的输入由椭圆曲线上的多个点组成。根据规范,点 (0, 0) 存在于两条曲线上,并且应该是有效的输入:


然而,Aurora Engine(版本 2.7.0)在输入包含点 (0,0) 时会回滚。

相关的 PR 在这里。
Moonbeam: ecMul
这个错误存在于预编译合约 ecMul 中。与 Aurora Engine 不同,Moonbeam 在输入有效时会回滚交易。根据规范,当输入的长度小于 64 时,预编译合约 ecMul 应该用零进行填充。然而,Moonbeam 会回滚而不是执行填充操作。

我们还发现预编译合约 ecAdd 和 modexp 存在相同的问题。
相关的 PR 在这里。
Moonbeam: ecRecover
这个错误存在于预编译合约 ecRecover 中,该合约用于恢复以太坊地址。
根据规范,input[32..63] v 表示一个 U256 标识符,期望值为 27 或 28,否则 ecRecover 不应返回任何内容(但整个交易不应回滚)。
然而,Moonbeam 在这里犯了两个错误:
- 它只检查 input[63],而不是将 input[32..63] 转换为 U256 类型然后检查值。
- 当标识符为 0 或 1 时,输入被认为是有效的(它应该只为 27 或 28)。

相关的 PR 在这里。
Moonbeam: ecPairing
这个错误存在于预编译合约 ecPairing 中。当输入无效时,Moonbeam 不会回滚交易。根据规范,预编译合约 ecPairing 的输入应为 192 的倍数。否则,交易应回滚。

然而,当上述要求不满足时,Moonbeam 不会回滚交易。
相关的 PR 在这里。
3.2 Gas 计算错误
每个预编译合约都有一个算法来确定 Gas 使用量。不正确的 Gas 计算可能导致 DoS 攻击。
然而,我们在 Aurora Engine 和 Moonbeam 中都发现了两个 Gas 计算错误的错误。
Aurora Engine: modexp
错误存在于预编译合约 modexp 中。计算 Gas 使用量的算法由 EIP-2565 定义。Gas 使用量与迭代次数有关。
计算迭代次数的算法如下:
def calculate_iteration_count(exponent_length, exponent):
iteration_count = 0
if exponent_length <= 32 and exponent == 0: iteration_count = 0
elif exponent_length <= 32: iteration_count = exponent.bit_length() - 1
elif exponent_length > 32: iteration_count = (8 * (exponent_length - 32)) + ((exponent & (2**256 - 1)).bit_length() - 1)
return max(iteration_count, 1)
根据上述算法,迭代次数至少为一次。然而,Aurora Engine 直接返回 iteration_count 而不是 max(iteration_count, 1)。在这种情况下,返回值(即 iteration_count)可能为 0,这意味着在特定情况下,Aurora 收取的 Gas 费会比预期少。
相关的 Issue 链接在这里。
Moonbeam: modexp
错误也存在于预编译合约 modexp 的 calculate_iteration_count 函数中。但在 Moonbeam 中被发现。
当 exponent_length 大于 32 时,iteration_count 使用以下算法计算:
(8 * (exponent_length - 32)) + ((exponent & (2**256 - 1)).bit_length() - 1)
请注意,它使用 exponent & (2**256 - 1),它获取 exponent 的最低 32 个字节,而 Moonbeam 的实现遵循此算法。
然而,根据规范,Gas 计算公式应使用 exponent 的最高 32 个字节:

相关的 PR 在这里。
3.3 Nonce 增量错误
外部拥有账户(EOA)的 Nonce 表明该地址已签名的成功交易数量。然而,我们注意到在 Aurora Engine 上发送无效交易也可以增加 Nonce。
根据 EIP-1559,在执行交易之前,EVM 应确保签名者有足够的余额来支付转移的原生代币(例如 ETH)和所需的 Gas。否则,交易应被丢弃,并且签名者的 Nonce 不应增加。

尽管 Aurora Engine 在这种情况下丢弃了交易,但它仍然增加了签名者的 Nonce。
相关的 PR 在这里。
3.4 Opcode 实现错误
这个错误是关于特定 Opcode(即 PUSH)的实现。当 PUSH Opcode 后面的字节不完整时,它们应该右对齐。例如,字节码 0x64ffff 可以解码为:
PUSH5 0xffff
由于操作数应该是右对齐的,值 0xffff000000 应该被推送到堆栈上。然而,Aurora Engine 和 Moonbeam 中的 EVM 实现都推入了 0xffff,这是不正确的。
相关的 PR 在这里。
4. 我们的服务
在 BlockSec,我们理解在不同的区块链实现中保持 EVM 兼容性和安全性的重要性。这就是为什么我们开发了一种系统的错误检测方法,可以轻松地在 EVM 实现中定位漏洞。
我们的内部系统已被证明在定位 EVM 实现中的错误和安全漏洞方面非常有效。它已成功报告并修复了 Aurora Engine 中的四个错误和 Moonbeam 中的四个错误。此外,我们的测试方法去年被用于定位 Solana rbpf 实现中的两个关键漏洞(CVE-2021–46102、CVE-2022–23066),突显了我们方法的有效性。
通过实施我们系统的错误检测方法,我们的客户可以确信他们的 EVM 实现是安全可靠的。我们致力于为 EVM 兼容性和安全性提供一流的解决方案,帮助我们的客户赢得用户和利益相关者的信任。
关于 BlockSec
BlockSec 团队专注于区块链生态系统的安全,并与领先的 DeFi 项目合作以确保其产品的安全。该团队由顶尖的安全研究人员以及来自学术界和工业界的经验丰富的专家组成。他们在著名的会议上发表了多篇区块链安全论文,报告了多个 DeFi 应用的零日攻击,并发布了高影响力安全事件的详细分析报告。



