EVM 存儲機制詳解:深入理解乙太坊技術與安全問題!

前言

EVM 是一個輕量級的虛擬機,其設計初衷就是提供一種可以忽略硬件、操作系統等兼容性的虛擬的執行環境供以太坊網絡運行智能合約。

簡單來說 EVM 是一個完全獨立的沙盒,在 EVM 中運行的代碼是無法訪問網絡、文件系統和其他進程的,以此來避免錯誤的代碼能讓智能合約毀滅或者影響外部環境。

在此基礎上,知道創宇區塊鏈安全實驗室 帶大家一起深入理解 EVM 的存儲機制和安全問題。

EVM存儲結構

深入理解 EVM 存儲機制及安全問題

可以看到 EVM 存儲數據分為兩類:

存儲在 code 和 storage 里的數據是 non-volatile (不容易丟失的)

存儲在 stack,args,memory 里數據是volatile(容易丟失的)

各個存儲位置的含義

Code

code 部署合約時儲存 data 字段也就是合約內容的空間,即專門存儲智能合約的二進制源碼的空間

Storage

Storage 是一個可以讀寫修改的持久存儲的空間,也是每個合約持久化存儲數據的地方。Storage 是一個巨大的 map,一共 2^256 個插槽 (slot),每個插糟有 32byte,合約中的“狀態變量”會根據其具體類型分別保存到這些插槽中。

Stack

stack 即所謂的“運行棧”,用來保存 EVM 指令的輸入和輸出數據。可以免費使用,沒有 gas 消耗,用來保存函數的局部變量,數量被限制在 16 個。stack 的最大深度為 1024 ,其中每個單元是 32 byte。

深入理解 EVM 存儲機制及安全問題

Args

args 也叫 calldata,是一段只讀的可尋址的保存函數調用參數的空間,與棧不同的地方的是,如果要使用 calldata 里面的數據,必須手動指定偏移量和讀取的字節數。

Memory

Memory 一個簡單的字節數組,主要是在運行期間存儲數據,將參數傳遞給內部函數。基于 32byte 進行尋址和擴展。

EVM 數據存儲概述

前面已經說過 Storage 是每個合約持久化存儲數據的地方其儲存數據的方式是通過插槽來實現的,現在就具體介紹它是怎麼實現的:

狀態變量

1.對于大小在 32 字節以內的變量(常量),以其定義的順序作為它的索引值來存儲。即第一個變量的索引為 key(0),第二個變量的索引為 key(1)…

2.對于連續較小的值,可能被優化存儲在同一個位置,比如:合約中前四個狀態變量都是 uint64 類型的,則四個狀態變量的值會被打包成一個 32 字節的值存儲在 0 位置。

未優化:

pragma solidity ^0.4.11;

contract C {

   uint256 a = 12;

   uint256 c = 12;

   uint256 b = 12;

   uint256 d = 12;

   function m() view public returns(uint256,uint256,uint256,uint256){

       return (a,b,c,d);

   }

}

深入理解 EVM 存儲機制及安全問題

優化后:

pragma solidity ^0.4.11;

contract C {

   uint64 a = 12;

   uint64 c = 12;

   uint64 b = 12;

   uint64 d = 12;

   function m() view public returns(uint64,uint64,uint64,uint64){

       return (a,b,c,d);

   }

}

深入理解 EVM 存儲機制及安全問題

結構體

對于大小在 32 字節以內的結構體同樣也是順序存儲,例如結構體變量索引定義在位置 0,結構體內部有兩個成員,則這兩個成員的依序為 0 和 1。

pragma solidity ^0.4.11;

contract C {

struct Info {

   uint256 a ;

   uint256 b ;

}

   function m()  external returns(uint256,uint256){

       Info storage info;

       info.a = 12 ;

       info.b = 24 ;

       return(info.a,info.b);

   }

}

深入理解 EVM 存儲機制及安全問題

映射(map)

map 存儲位置是通過 keccak256 (bytes32(key) + bytes32(position) ) 計算得到的,position 表示 key 對應 storage 類型變量存儲的位置。

pragma solidity ^0.4.11;

contract Test {

 mapping(uint256 => uint256) knownsec;

 function go() public {

     knownsec[0x60] = 0x40;

 }

}

深入理解 EVM 存儲機制及安全問題

數組

定長數組

同上,只要在 32 字節以內也是順序存儲,不過在編譯時編譯器會進行邊界檢查防止越界。

pragma solidity ^0.4.11;

contract C {

   uint256[3] a = [12,24,48] ;

   

   function m() public view returns(uint256,uint256,uint256){

       return (a[0],a[1],a[2]);

   }

   

}

深入理解 EVM 存儲機制及安全問題

可變長度數組

由于可變長度數組長度不定,一般在編譯可變長度數組時會提前預留存儲空間,所以就會使用狀態變量的位置存儲可變長度數組的長度。

而具體的數據地址會通過計算 keccak256 (bytes32(position)) 算得數組首地址,再加數組長度偏移量獲得具體的元素。

pragma solidity ^0.4.11;

contract C {

   uint256[] a = [12,24,48] ;

   

   function m() public view returns(uint256,uint256,uint256){

       return (a[0],a[1],a[2]);

   }

   

}

深入理解 EVM 存儲機制及安全問題

字節數組和字符串

如果長度小于等于31字節 :

1.對于定長字節數組則是同定長數組一樣;

2.對于可變字節數組和字符串,會在存儲值位置補0一直到32字節,并用補0的最后一個字節存儲字符串的編碼長度。

pragma solidity ^0.4.4;

contract A{

   string public name0 = “knownsec”;

   bytes8 public name=0x6b6e6f776e736563;

   bytes public g ;

   

   function test() public {

       g.push(0xAA);

       g.push(0xBB);

       g.push(0xCC);

   }

   function go() public view returns(bytes){

       return g;

   }

}

深入理解 EVM 存儲機制及安全問題

當節數組和字符串長度大于31字節時

1.變量位置存儲編碼長度,并且編碼長度公式更換為編碼長度 = 字符數 * 2 + 1

2.真實存儲值第一個位置通過公式 keccak256(bytes32(position)) 獲取,剩余值在獲取到的位置順序存儲,同樣在最后存儲位置補0到32字節。

string public name = “knownsecooooooooooooooooooooooooo”;

深入理解 EVM 存儲機制及安全問題

安全問題

前面已經講到EVM的存儲結構及存儲機制,現在我們再來探討其安全問題。

未初始化變量

漏洞原理:

在官方手冊中提到結構體,數組和映射的局部變量默認是放在 storage 中的,而 solidity 語言中函數中設置的局部變量的默認類型取決于它們本身的類型。

因此如果在函數內部設置以上 storage 類型變量卻沒有進行初始化,他們就相當于存儲指針指向合約中的其他變量,當我們對其進行改變時改變的就是其指向的變量。漏洞合約,目的修改 owner 為自己地址:

pragma solidity ^0.4.0;

contract testContract{

   bool public unlocked = false;

   address public owner = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;

struct Person {

   bytes32 name;

   address mappedAddress;

}

   function test(bytes32 _name , address  _mappedAddress) public{

       Person person;

       person.name = _name;

       person.mappedAddress = _mappedAddress;

       require(unlocked);

  }

}

漏洞合約分析:

可以看到該合約在函數部分創建新的結構體時沒有進行初始化,由此我們可以利用該函數進行對owner的修改。不過使用該函數我們還要通過require驗證,不過這也不難因為狀態變量unlocked也同樣在我們可控的范圍內。

具體操作:

調用test函數分別傳入向_name 傳入:0x0000000000000000000000000000000000000000000000000000000000000001(真值)

_mappedAddress 傳入:0xfB89eCb0188cb83c220aADDa1468C1635208e821(個人地址)

傳參前:

深入理解 EVM 存儲機制及安全問題

傳參后:

深入理解 EVM 存儲機制及安全問題

可以看到已經成功更改了地址。

總結

可以看到 EVM 的存儲器就是一個 key=>value 的健值數據庫,存儲的數據可以通過校驗和來確保一致。但是其也是和智能合約語言進行交互的,當其中一些規則發生沖突很可能就被別有用心的人用來作惡,所以規范的使用智能合約語言是避開漏洞的必要條件。

發文者:鏈站長,轉載請註明出處:https://www.jmb-bio.com/4159.html

讚! (0)
Donate 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
Previous 2023 年 2 月 28 日 下午 3:42
Next 2023 年 2 月 28 日 下午 3:49

相關文章

  • DeFi 安全攻略:保障您的數字資產安全!

    無論是開發DeFi協議還是其他的智能合約應用,在上線到區塊鏈主網前都需要考慮到許多安全因素。很多團隊在審核代碼時只關注Solidity相關的陷阱,但要確保dApp的安全性足夠支撐上線主網,通常還有很多工作要做。了解大多數流行的DeFi安全漏洞可能會為你和你的用戶節省數十億美元并且免除后續的各種煩惱,如預言機攻擊、暴力攻擊和許多其他威脅等。 考慮到這一點,我們…

    2023 年 2 月 28 日
  • 虛擬通道技術詳解:如何創建狀態通道網路!

    在本文中,我們介紹了一種叫作虛擬通道(virtual channel)的新型狀態通道結構。虛擬通道不僅使得付費文件流(點擊此處,查看 demo!)等新型應用場景成為可能,還可以簡化去中心化的 Graph 查詢支付、Filecoin 內容檢索、帶有經濟激勵機制的狀態提供者網絡等有趣的應用場景。 動機 讓我們來設計一個免信任的付費文件流支付系統。這個系統中有 s…

    2023 年 2 月 28 日
  • 乙太坊的設計理念詳解:探索乙太坊的發展歷程和設計理念!

    叔塊(uncle blocks)獎勵 GHOST 協議是一項不起的創新,由 Yonatan Sompolinsky 和 Aviv Zohar 在 2013 年 10 月首次提出的。它是解決快速出塊伴生問題的第一個認真嘗試。 GHOST 的用意是解決這樣一個難題:更短的出塊時間(因此確認速度會更快)會導致有更多區塊 “過時” 因而安全性會下降 —— 因為區塊在…

    2023 年 2 月 28 日
  • LiquiDEX 兩步原子切換式通訊協定解析:掌握最新區塊鏈技術!

    長話短說:LiquiDEX 是一個在 Liquid網絡上執行兩步原子交換(atomic swaps)的協議,它只需要交換方的單次交互,這極大地改善了用戶體驗。而使用該協議,我們可以構建出更復雜系統的構建塊,例如自動 OTC 交易柜臺、拍賣系統甚至去中心化交易所 (DEX)。 注:LiquiDEX的產品還不可用。 簡介:Liquid網絡和原子交換技術 Liqu…

    2023 年 2 月 28 日
  • NFT 資產安全問題:白帽駭客 samczsun 警告攻擊頻繁!

    注:原文作者是擁有“審計上帝”之稱的白帽黑客samczsun,同時他也是Paradigm的研究合伙人,其最近出手拯救了BitDAO MISO荷蘭拍賣資金池中的3.5億美元資產,而在這篇文章中,他提醒了關于NFT代幣標準的潛在安全風險,他還預測稱,隨著ERC-721和ERC-1155代幣標準變得越來越流行,針對NFT的攻擊很可能會越來越頻繁。 如果你從事軟件工…

    2023 年 2 月 28 日
每日鏈頭條給你最新幣圈相關資訊!