ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

CTF Wiki 智能合約安全實戰:深入解析 EVM 整數溢位與下溢(Integer Overflow and Underflow)

CTF Wiki 智能合約安全實戰:深入解析 EVM 整數溢位與下溢(Integer Overflow and Underflow) 文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载本篇技術指南以 ctf-wiki 區塊鏈章節為核心系統講解以太坊虛擬機EVM中整數溢位Overflow與下溢Underflow漏洞的成因、利用手法與防禦方案。通過對 Capture The Ether 的 Token sale 經典題目的完整推導、一個錯誤的取款合約示例以及與重入攻擊的關聯分析讀者將掌握如何在 CTF 比賽中識別、計算並利用整數回繞漏洞同時了解 SafeMath 等緩解措施的底層原理與適用前提。原理EVM 整數的位寬與靜默取模以太坊虛擬機中所有整數計算都發生在 256 位字word的基礎上。Solidity 為開發者提供了兩類整數類型int有符號整數uint無符號整數。在int或uint之後可以跟隨一個 8 的倍數用來表示該整數的位數例如 8 位的uint8、16 位的uint16。位數上限為 256 位因此int和uint本質上分別是int256和uint256的別名在實際的智能合約代碼中uint的使用更為常見。關鍵的漏洞機制在於當整數計算的結果超出其位數所能表示的上限或下限時EVM 不會拋出錯誤而是靜默地對結果進行取模wrap around操作。這與傳統語言如 C/C中無符號整數的溢出行為一致但對於依賴餘額計算的智能合約而言後果可能是災難性的。從 EVM 指令層面看算術運算對應的 opcode如ADD、MUL、SUB本身不攜帶任何溢位檢測結果直接以 256 位截斷後返回。相關指令的語義可以參考 Ethereum Opcodes 一章操作碼助記符棧輸入棧輸出表達式01ADD| a | b || a b |a b02MUL| a | b || a * b |a * b03SUB| a | b || a - b |a - b從攻擊者的視角看我們通常希望費用向上溢出變得極小或者存款向下溢出變得極大——前者讓我們花極少的錢買到大量資產後者讓我們能取出超過餘額的資金。而對於合約開發者而言則可以借助 SafeMath 庫來防禦SafeMath 中的add、sub、mul等函數在運算發生溢位時會主動觸發revert回滾整筆交易從而使溢位不可利用。實戰案例一Token sale 的乘法溢位Capture The Ether原題以 Capture The Ether 的 Token sale 挑戰為例其合約源碼如下pragma solidity ^0.4.21; contract TokenSaleChallenge { mapping(address uint256) public balanceOf; uint256 constant PRICE_PER_TOKEN 1 ether; function TokenSaleChallenge(address _player) public payable { require(msg.value 1 ether); } function isComplete() public view returns (bool) { return address(this).balance 1 ether; } function buy(uint256 numTokens) public payable { require(msg.value numTokens * PRICE_PER_TOKEN); balanceOf[msg.sender] numTokens; } function sell(uint256 numTokens) public { require(balanceOf[msg.sender] numTokens); balanceOf[msg.sender] - numTokens; msg.sender.transfer(numTokens * PRICE_PER_TOKEN); } }題目邏輯清晰合約部署時要求打入 1 ether構造函數中的require(msg.value 1 ether)isComplete的判斷條件是address(this).balance 1 ether即把合約裡的錢抽到不足 1 ether即可通關。溢位計算推導購買單個代幣需要支付 1 ether即require(msg.value numTokens * PRICE_PER_TOKEN)。在 EVM 中貨幣以 wei 為最小單位1 ether 實際上是 $10^{18}$ wei即十六進制的0xde0b6b3a7640000wei。若讓這裡的numTokens取得足夠大numTokens * PRICE_PER_TOKEN這一乘法就可能發生 256 位回繞。令numTokens 2^256 // 10^18 1//表示整數除法則有numTokens * 10^18 (2^256 // 10^18 1) * 10^18 2^256 (10^18 - 2^256 mod 10^18)該值超過了uint256的最大值 $2^{256}-1$EVM 對其取模後實際得到(2^256 // 10^18 1) * 10^18 mod 2^256 415992086870360064 wei ≈ 0.416 ether也就是說攻擊者只需支付約0.4 ether就能買到數量近乎天文數字的代幣餘額記錄在balanceOf[msg.sender]中。由於balanceOf也由uint256保存這筆巨額代幣可以正常參與後續的sell操作。接下來只需將買到的代幣部分賣出合約餘額便會低於 1 ether滿足isComplete條件。值得一提的是0.4 ether的數值與部署時注入的 1 ether 之和約為 1.4 ether而賣出操作會按numTokens * PRICE_PER_TOKEN向合約索取資金合約只要沒有餘額不足的限制此處sell中僅檢查了balanceOf[msg.sender] numTokens攻擊者即可反覆賣出直至將合約掏空。攻擊復現要點復現該題目時可以結合 Ethereum Basics 中介紹的 Remix 或 web3.py / web3.js 工具鏈用 Remix 部署TokenSaleChallenge並傳入 1 ether計算numTokens 2^256 // 10^18 1以msg.value 0.416 ether左右調用buy(numTokens)調用sell賣出部分代幣直到address(this).balance 1 ether觸發isComplete()返回true。注意該題目基於^0.4.21編譯未啟用任何溢位檢查這是漏洞成立的版本前提。從 Solidity 0.8.0 開始編譯器會在語言層面默認插入溢位檢查遇到溢位直接revert同源代碼在新版本下將無法以相同方式利用。實戰案例二取款邏輯的下溢Underflow整數下溢的典型場景是減法操作。假設有一個合約實現了如下功能contract Bank { mapping(address uint256) public balanceOf; ... function withdraw(uint256 amount) public { require(balanceOf[msg.sender] - amount 0); balanceOf[msg.sender] - amount; msg.sender.send.value(amount)(); } }乍看之下似乎沒有問題——取款前先檢查了餘額減去金額不小於 0。但實際上require那一行的balanceOf[msg.sender] - amount的結果作為無符號整數uint256無論運算結果在數學上為多少最終值永遠是大於等於 0 的。原因正是本節開頭所述的靜默取模當amount balanceOf[msg.sender]時balanceOf[msg.sender] - amount會回繞為一個接近 $2^{256}$ 的巨大正數永遠滿足 0的檢查導致攻擊者可以任意取款甚至把餘額扣成一個巨大的數字。正確的寫法應該是先比較、後運算require(balanceOf[msg.sender] amount); balanceOf[msg.sender] - amount;這裡的對比正好呼應了 以太坊存儲佈局 一章中的要點balanceOf這類映射的值最終以 32 字節256 位的形式存放在特定插槽slot中負數在存儲層面上就是0xffff...ffff這樣的全 1 大數讀出來便是巨大正數而web3.eth.getStorageAt()可以隨時讀取這些狀態。與重入攻擊的關聯下溢的另一種形態整數下溢的另一個重要例子與重入攻擊Re-Entrancy密切相關。經典的重入攻擊流程是先給錢後記賬在 Re-Entrancy 一章的Bank.withdraw實現中合約先執行msg.sender.call.value(amount)()轉賬再執行balanceOf[msg.sender] - amount扣賬。由於轉賬時收款合約的 fallback 函數會被調用攻擊合約可以在 fallback 中再次調用withdraw此時餘額尚未扣減檢查條件依然滿足於是可以反覆取款。這就產生了兩種典型的利用結果將持有數為 1 的物品賣出兩次將 1 ether 的存款取出兩次。每一次透支都會讓balanceOf[msg.sender]在數學上變成負數而由於uint256的存儲特性負數實際上保存為一個極大的正數下溢回繞攻擊合約後續可以繼續使用這個大數額的存款形成取之不盡的資金來源。因此在 CTF 中絕大部分重入攻擊題目都涉及到向下溢位兩類漏洞常常組合出現重入提供了反覆觸發的攻擊路徑下溢則提供了餘額被扣成巨數的狀態後果。兩者還有共同的防禦要點先檢查、後生效Checks-Effects-Interactions先更新狀態扣減餘額再進行外部調用可同時堵住重入與下溢導致的錯誤狀態限制外部調用的 gas使用transfer/send僅提供 2300 gas替代call可削弱重入的執行能力詳見 Re-Entrancy 章節中的注意點說明。溢位的相關攻擊面與防禦總結容易出現溢位的操作乘法MUL如 Token sale 中numTokens * PRICE_PER_TOKEN以及各種amount * rate形式的定價計算加法ADD如balanceOf[msg.sender] numTokens累加型餘額易發生上溢減法SUB如balanceOf[msg.sender] - amount扣減型餘額易發生下溢。此外以太坊存儲佈局 storage.md 中提到的任意寫如 uninitialized-storage-pointer與 short address 攻擊 等漏洞也常與算術回繞共同作用構造出組合利用鏈感興趣的讀者可以在 attacks 目錄 中進一步閱讀。防禦手段SafeMath 庫對add、sub、mul等運算封裝檢查發生溢位時revert回滾整筆交易使漏洞不可利用。這是原文檔明確推薦的經典方案Solidity 0.8.0 內建檢查新版本編譯器默認啟用算術溢出檢查可通過unchecked區塊顯式關閉從語言層面根除靜默回繞先檢查後運算如require(balanceOf[msg.sender] amount)寫在減法之前Checks-Effects-Interactions 模式先更新內部狀態再做外部調用兼顧重入與下溢兩類風險注意適用前提SafeMath 與 0.8.0 的內建檢查都依賴於運算發生時的revert能力若合約使用低版本編譯器如^0.4.x必須手動引入檢查邏輯。賽題指引絕大部分重入攻擊題目都涉及向下溢位可參照 重入攻擊 部分。不涉及重入攻擊的溢位題目相對較少可以參考以下題目ByteCTF 2019題目名稱hf題目名稱bet!!! note 注題目附件相關內容可至 ctf-wiki 的 ctf-challenges/blockchain 倉庫尋找位於 ctf-wiki 組織下的附屬挑戰倉庫用於存放歷屆 CTF 題目附件與合約源碼。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐A2UI Express 格式优化实录run_020 数据路径斜杠预处理如何让评测通过率稳定在 100%A2UI Express 格式优化实录run_020 数据路径斜杠预处理如何让评测通过率稳定在 100% 本篇以 A2UI 仓库中一次完整的迭代优化报告 ev文档网络安全教程ctf-wiki 以太坊智能合約重入攻擊Re-Entrancy完全解析從 The DAO 硬分叉到 CTF 實戰ctf wiki 以太坊智能合約重入攻擊Re Entrancy完全解析從 The DAO 硬分叉到 CTF 實戰 導讀 本文以 ctf wiki 中 重入文档网络安全教程ctf-wiki 橢圓曲線加密ECC從入門到實戰離散對數基礎、ElGamal 方案與 SECCON CTF 破解ctf wiki 橢圓曲線加密ECC從入門到實戰離散對數基礎、ElGamal 方案與 SECCON CTF 破解 本篇技術指南以 ctf wiki 的 e文档网络安全教程上一篇如何在AMD MI300X上部署DeepSeek-R1SGLang优化指南下一篇Ultimate Hacking Keyboard Agent开发指南从USB通信到I2C设备调试创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表