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) 时会发生 revert。

相关 PR 见此处。
Moonbeam:ecMul
此漏洞存在于预编译合约 ecMul 中。与 Aurora Engine 不同,Moonbeam 在输入合法时会对交易进行 revert。根据规范,当输入长度小于 64 时,预编译合约 ecMul 应以零填充输入。然而,Moonbeam 并未执行填充操作,而是直接 revert。

我们还发现预编译合约 ecAdd 和 modexp 存在相同问题。
Moonbeam:ecRecover
此漏洞存在于用于恢复以太坊地址的预编译合约 ecRecover 中。
根据规范,input[32..63] 中的 v 表示一个 U256 标识符,其值应为 27 或 28,否则 ecRecover 不应返回任何内容(但整个交易不应 revert)。
然而,Moonbeam 在此处犯了两个错误:
- 它仅检查 input[63],而未将 input[32..63] 转换为 U256 类型后再检查其值。
- 当标识符为 0 或 1 时,输入被视为合法(实际上只有 27 或 28 才合法)。

Moonbeam:ecPairing
此漏洞存在于预编译合约 ecPairing 中。Moonbeam 在输入无效时未对交易进行 revert。根据规范,预编译合约 ecPairing 的输入长度应为 192 的倍数,否则交易应 revert。

然而,Moonbeam 在上述要求不满足时并未对交易进行 revert。
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)
根据上述算法,迭代次数至少为 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 个字节:

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

尽管 Aurora Engine 在这种情况下会丢弃交易,但它仍然增加了签名者的 nonce。
相关 PR 见此处。
3.4 错误的操作码实现
此漏洞涉及特定操作码(即 PUSH)的实现。当 PUSH 操作码后面的字节不完整时,应进行右对齐处理。例如,字节码 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 应用的零日攻击,并发布了多份高影响力安全事件的详细分析报告。



