作者:高峰 黃紹莽@360 IceSword Lab
博客:https://www.iceswordlab.com
概述
目前,以太坊智能合約的安全事件頻發,從The DAO事件到最近的Fomo3D獎池被盜,每次安全問題的破壞力都是巨大的,如何正確防範智能合約的安全漏洞成了當務之急。本文主要講解了如何通過對智能合約的靜態分析進而發現智能合約中的漏洞。由於智能合約部署之後的更新和升級非常困難,所以在智能合約部署之前對其進行靜態分析,檢測並發現智能合約中的漏洞,可以最大限度的保證智能合約部署之後的安全。
本文包含以下五個章節:
- 智能合約的編譯
- 智能合約彙編指令分析
- 從反編譯代碼構建控制流圖
- 從控制流圖開始約束求解
- 常見的智能合約漏洞以及檢測方法
第一章 智能合約的編譯
本章節是智能合約靜態分析的第一章,主要講解了智能合約的編譯,包括編譯環境的搭建、solidity編譯器的使用。
1.1 編譯環境的搭建
我們以Ubuntu系統為例,介紹編譯環境的搭建過程。首先介紹的是go-ethereum的安裝。
1.1.1 安裝go-ethereum
通過apt-get安裝是比較簡便的安裝方法,只需要在安裝之前添加go-ethereum的ppa倉庫,完整的安裝命令如下:
sudo apt-get install software-properties-common sudo add-apt-repository -y ppa:ethereum/ethereum sudo apt-get update sudo apt-get install ethereum
安裝成功后,我們在命令行下就可以使用geth
,evm
,swarm
,bootnode
,rlpdump
,abigen
等命令。
當然,我們也可以通過編譯源碼的方式進行安裝,但是這種安裝方式需要提前安裝golang的環境,步驟比較繁瑣。
1.1.2 安裝solidity編譯器
目前以太坊上的智能合約絕大多數是通過solidity語言編寫的,所以本章只介紹solidity編譯器的安裝。solidity的安裝和go-ethereum類似,也是通過apt-get安裝,在安裝前先添加相應的ppa倉庫。完整的安裝命令如下:
sudo add-apt-repository ppa:ethereum/ethereum sudo apt-get update sudo apt-get install solc
執行以上命令后,最新的穩定版的solidity編譯器就安裝完成了。之後我們在命令行就可以使用solc命令了。
1.2 solidity編譯器的使用
1.2.1 基本用法
我們以一個簡單的以太坊智能合約為例進行編譯,智能合約代碼(保存在test.sol文件)如下:
pragma solidity ^0.4.25; contract Test { }
執行solc命令:solc --bin? test.sol
輸出結果如下:
======= test.sol:Test ======= Binary: 6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820f633e21e144cae24615a160fcb484c1f9495df86d7d21e9be0df2cf3b4c1f9eb0029
solc命令的--bin
選項,用來把智能合約編譯后的二進制以十六進制形式表示。和--bin
選項類似的是--bin-runtime
,這個選項也會輸出十六進製表示,但是會省略智能合約編譯后的部署代碼。接下來我們執行solc命令:
solc --bin-runtime test.sol
輸出結果如下:
======= test.sol:Test ======= Binary of the runtime part: 6080604052600080fd00a165627a7a72305820f633e21e144cae24615a160fcb484c1f9495df86d7d21e9be0df2cf3b4c1f9eb0029
對比兩次輸出結果不難發現,使用--bin-runtime
選項后,輸出結果的開始部分少了6080604052348015600f57600080fd5b50603580601d6000396000f300
,為何會少了這部分代碼呢,看完接下來的智能合約編譯后的字節碼結構就明白了。
1.2.2 智能合約字節碼結構
智能合約編譯后的字節碼,分為三個部分:部署代碼、runtime代碼、auxdata。
-
部署代碼:以上面的輸出結果為例,其中
6080604052348015600f57600080fd5b50603580601d6000396000f300
為部署代碼。以太坊虛擬機在創建合約的時候,會先創建合約賬戶,然後運行部署代碼。運行完成後它會將runtime代碼+auxdata 存儲到區塊鏈上。之後再把二者的存儲地址跟合約賬戶關聯起來(也就是把合約賬戶中的code hash字段用該地址賦值),這樣就完成了合約的部署。 -
runtime代碼:該例中
6080604052600080fd00
是runtime代碼。 -
auxdata:每個合約最後面的43字節就是auxdata,它會緊跟在runtime代碼後面被存儲起來。
solc命令的--bin-runtime
選項,輸出了runtime代碼和auxdata,省略了部署代碼,所以輸出結果的開始部分少了6080604052348015600f57600080fd5b50603580601d6000396000f300
。
1.2.3 生成彙編代碼
solc命令的--asm
選項用來生成彙編代碼,接下來我們還是以最初的智能合約為例執行solc命令,查看生成的彙編代碼。
執行命令:solc --bin --asm test.sol
輸出結果如下:
======= test.sol:Test ======= EVM assembly: ... */ "test.sol":28:52 contract Test { mstore(0x40, 0x80) callvalue /* "--CODEGEN--":8:17 */ dup1 /* "--CODEGEN--":5:7 * iszero tag_1 jumpi /* "--CODEGEN--":30:31 */ 0x0 /* "--CODEGEN--":27:28 */ dup1 /* "--CODEGEN--":20:32 */ revert /* "--CODEGEN--":5:7 */ tag_1: ... */ "test.sol":28:52 contract Test { pop dataSize(sub_0) dup1 dataOffset(sub_0) 0x0 codecopy 0x0 return stop sub_0: assembly { ... */ /* "test.sol":28:52 contract Test { mstore(0x40, 0x80) 0x0 dup1 revert auxdata: 0xa165627a7a72305820f633e21e144cae24615a160fcb484c1f9495df86d7d21e9be0df2cf3b4c1f9eb0029 }
由1.2.2小節可知,智能合約編譯后的字節碼分為部署代碼、runtime代碼和auxdata三部分。同樣,智能合約編譯生成的彙編指令也分為三部分:EVM assembly標籤下的彙編指令對應的是部署代碼;sub_0標籤下的彙編指令對應的是runtime代碼;sub_0標籤下的auxdata和字節碼中的auxdata完全相同。由於目前智能合約文件並沒有實質的內容,所以sub_0標籤下沒有任何有意義的彙編指令。
1.2.4 生成ABI
solc命令的--abi
選項可以用來生成智能合約的ABI,同樣還是最開始的智能合約代碼進行演示。
執行solc命令:solc --abi test.sol
輸出結果如下:
======= test.sol:Test ======= Contract JSON ABI []
可以看到生成的結果中ABI數組為空,因為我們的智能合約里並沒有內容(沒有變量聲明,沒有函數)。
1.3 總結
本章節主要介紹了編譯環境的搭建、智能合約的字節碼的結構組成以及solc命令的常見用法(生成字節碼,生成彙編代碼,生成abi)。在下一章中,我們將對生成的彙編代碼做深入的分析。
第二章 智能合約彙編指令分析
本章是智能合約靜態分析的第二章,在第一章中我們簡單演示了如何通過solc命令生成智能合約的彙編代碼,在本章中我們將對智能合約編譯后的彙編代碼進行深入分析,以及通過evm命令對編譯生成的字節碼進行反編譯。
2.1 以太坊中的彙編指令
為了讓大家更好的理解彙編指令,我們先簡單介紹下以太坊虛擬機EVM的存儲結構,熟悉Java虛擬機的同學可以把EVM和JVM進行對比學習。
2.1.1 以太坊虛擬機EVM
編程語言虛擬機一般有兩種類型,基於棧,或者基於寄存器。和JVM一樣,EVM也是基於棧的虛擬機。
既然是支持棧的虛擬機,那麼EVM肯定首先得有個棧。為了方便進行密碼學計算,EVM採用了32字節(256比特)的字長。EVM棧以字(Word)為單位進行操作,最多可以容納1024個字。下面是EVM棧的示意圖:
转载请注明:IAMCOOL » 以太坊智能合約靜態分析