EVM (Ethereum Virtual Machine,以太坊虛擬機) 兼容區塊鏈,旨在與以太坊區塊鏈的智能合約功能、程式語言 (Solidity) 及工具生態系統保持兼容。在此過程中,實現與 EVM 兼容的虛擬機是關鍵步驟之一。然而,在我們的研究中發現,在不同的實現中保持 EVM 的兼容性並非易事。
為了解決這個問題,BlockSec 開發了一套內部系統,可以系統性地定位 EVM 實現內部的錯誤與安全漏洞。該系統已被證實非常有效。它成功發現了 Aurora Engine 中的四個錯誤以及 Moonbeam 中的四個錯誤。所有這些錯誤均已報告並修復。值得注意的是,這種測試方法去年也曾被用於定位 Solana rbpf 實現中的兩個關鍵漏洞 (CVE-2021–46102, CVE-2022–23066)。
1. 背景
如今,許多不同的區塊鏈被提出,旨在優化低手續費 (gas fee)、高吞吐量及高性能。在此情況下,新的虛擬機或開發語言被提出,這提高了原始 Solidity 開發者的遷移門檻。因此,許多 EVM 兼容解決方案應運而生,允許用戶將使用 Solidity 開發的 DApp 部署在這些新鏈上。Aurora 和 Moonbeam 即是這些 EVM 兼容解決方案的代表。然而,這些 EVM 實現的穩健性、可靠性及精確度仍屬未知,值得我們關注。為此,我們利用差異化模糊測試 (differential fuzzing) 技術來檢查這些實現中是否存在任何缺陷。
2. 差異化模糊測試
其基本理念是將相同的輸入饋送至這些 EVM 實現(例如 Aurora、Moonbeam)以及最先進的以太坊客戶端(即 geth),以檢查它們是否具有相同的輸出。具體而言,我們收集以太坊上的歷史交易,並通過不同的策略變異合約代碼和交易狀態來生成測試案例。我們的發現表明,Aurora Engine 和 Moonbeam 在某些情況下並不符合規範。幸運的是,所有被報告的問題都已得到解決,現在我們將分享詳細資訊。
3. 已發現的錯誤
這些錯誤大多存在於預編譯合約 (precompiled contracts) 中,且其根源與影響各不相同。例如,某些錯誤可能會影響 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 會報錯交易。根據規範,當輸入長度小於 64 時,預編譯合約 ecMul 應對輸入進行零填充 (padding)。然而,Moonbeam 在此情況下會直接報錯,而非執行填充操作。

我們還發現預編譯合約 ecAdd 和 modexp 也存在同樣的問題。
Moonbeam: ecRecover
此錯誤位於預編譯合約 ecRecover 中,該合約用於恢復以太坊地址。
根據規範,輸入部分的 [32..63] v 代表一個 U256 標識符,預期應為 27 或 28,否則 ecRecover 不應返回任何內容(但整個交易不應報錯)。
然而,Moonbeam 在此處犯了兩個錯誤:
- 它僅檢查 input[63],而非將 input[32..63] 轉換為 U256 類型後再檢查數值。
- 當標識符為 0 或 1 時,輸入即被視為有效(正確應僅為 27 或 28)。

Moonbeam: ecPairing
此錯誤位於預編譯合約 ecPairing 中。當輸入無效時,Moonbeam 沒有報錯交易。根據規範,預編譯合約 ecPairing 的輸入應為 192 的倍數,否則交易應報錯。

然而,當不滿足上述要求時,Moonbeam 並未報錯交易。
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 費用比預期的要少。
相關問題連結請見 此處。
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 應該被推入堆疊 (stack)。然而,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 應用的零日攻擊,並發布了多份高影響力安全事件的詳細分析報告。



