Hi 游客

更多精彩,请登录!

比特池塘 区块链前沿 正文

深入解析Solidity合约

云丝雨线纱
141 0 0
这次主要讨论Solidity背后的运作原理,到底一个Solidity合约经过编译到部署上链之间的过程是如何运作的,以及后续呼叫合约时的流程,知道得越多就越能写出安全可靠的合约。& u4 \2 X8 U, D8 V
, L3 Q7 S0 M$ [
我们都知道一个Solidity程序代码写完之后,必须将它编译成byte code,才能透过交易部署至链上,变成所谓的智能合约。我们以下面的EtherDice.sol为例子。: ~' T$ B/ Z% ]) f# d# j

2 p9 t6 f5 k7 G# `当EtherDice.sol经过编译之后,会产出下面的一长串byte code,这些可以透过EVM assbemly来一步步解析,但用肉眼很难看出到底是如何运作的,至少我不行。
7 c- s2 v; B$ x/ A6 P3 X7 V
  y& b6 T  V7 X6 y以执行的方式来看,这些bytecode可以先分成固定的三个部分,如下:
3 n) w" Z/ W; J) _$ p, Z4 T; ]: `8 B* Q9 d( a6 w$ |! h2 X
Creation—这部分是用来执行合约里的constructor,而且不会储存在区块内,这也就是为什么constructor只会执行一次。可以看到下图绿色的部分,constructor执行完毕之后,就是取出Runtime部分,最后将它储存在区块中。
% [6 J! M: |3 w: f$ B4 M
, b6 J# }( t# L. j$ NRuntime—这部分是合约的主体,除了constructor之外的部分,链上储存的就是这份bytecode。在部署合约的时候,并不会执行Runtime的部分,而是在后续跟合约互动的时候才会用到。8 K4 T. [. b, |/ _! L
1 }4 v, x" H, O' b, v' v
Constructor Parameters—constructor的参数通通放在最后面,方便用完舍弃。
; `4 d+ G5 @! P  ~9 ]: c: T4 z# `' w$ l3 ~, q
合约部署上链之后,在使用合约内的某个函式时,其流程又是如何运作的?这就归Runtime部分的byte code来负责了。如下图,Runtime可以分成几个固定的部分,其中最常用到的就属 functionselector ,它主要负责逐一比对想要呼叫的function signature是否有存在byte code中,若有就执行,否则就会执行fallback function。若合约连fallbackfunction都没实作,则交易就会被Revert。% ?: A7 a1 G5 E0 G# ]3 Y0 s
9 q& q* n+ [  M* o
简单介绍完合约部署上链及呼叫函式的流程之后,让我们来看看对于EtherDice合约的攻防思路,就会变得相当容易理解。
) Q  L0 D+ _6 F% d0 H* p# Z; Z( m6 P+ a/ `+ N; ~0 G. {) z
小明的发财梦
' P4 z+ y, W9 h: [" `% B
; ]+ ~/ h; ~' d5 ~* u小明是个区块链的初学者,想趁着这股风潮趁机大捞一笔。他心里盘算着,大家满手ether没地方花,不如来开发个赌博游戏吧。这游戏必须符合容易上手、无法精通的重要原则,才能吸引大量玩家。好吧,就来开发经典的骰子游戏,容易理解又可以灵活的控制胜率,根本是稳赚不赔的生意。测试版没两三下就开发好了,小明是个认真用功的好学生,记得老师上课时讲过,要先在测试链上面跑跑看,没问题再放到主链上,才是正确的开发流程。于是小明照办了,就先放到Ropsten Testnet吧!因为是个dApp,所以程序代码当然也是要公开透明,让玩家知道庄家没有一点作弊的可能。为求保险,他还将程序代码上放某个知名的bug bounty网站—solidified,公开悬赏漏洞。一切都设置好之后,终于可以安心睡觉了。: j- @* n9 O( x0 K4 \

  @* ~1 k" l1 y+ F) L; S# h: u2.EtherDice的漏洞
% y& F8 y5 p$ W% b1 H
. }5 ]. J; x9 k+ r! @5 v+ o/ a阿财是个业余的bug hunter,平时最大的嗜好就是挖bug赚奖金。又是个没局的夜晚,阿财跟平常一样独自坐在计算机前看文章,信箱突然登登一声,一封主旨为New bounty posted的email出现在屏幕的角落,阿财马上点开信中的连结,想要抢快挖bug。经验丰富的阿财只看EtherDice的合约名称就马上知道可能的漏洞在哪边,果不其然,又是一个想发财的新手,这下捡到宝了。* F9 i7 H6 C) H6 l, q0 a
4 t4 v' r) @2 W6 B+ ]/ U
骰子游戏中最重要的random number来源居然是使用区块的timestamp,只要写个攻击的合约,透过合约来呼叫EtherDice的bet,就可以事先计算block.timestamp % 6的结果再来下注,就每赌必赢啦!!阿财很迅速的完成攻击合约,并写下建议的修补方式,就提交bug了。
  J" y1 B( F7 L2 Q6 Y- ?" v, U* ^& `1 s
修补EtherDice的漏洞3 `$ F" v5 b9 d- s( w: D/ m6 J
" h( P8 s4 K( f$ y! ]0 ^' O# K
小明才刚躺在床上都还没睡着,手机就传来email的通知,没想到居然这么快就被找到bug,经过测试确认之后,DiceHack.sol的确是可以成功压中每一笔下注。他一边喃喃自语solidified不愧是全球最大的solidity bug bounty网站,效率真高,这钱花的实在值得,一边着手修补漏洞。5 W; A0 @! I: [/ d

& ]0 J& O6 D% w( n6 `7 Z8 c* b( H防御的概念是这样的,只要能判断发出交易的address是一般的 externally owned account (EOA,也就是由使用者控制密钥的账户),还是 contract account,就可以挡下由DiceHack.sol发出的交易,这样就没问题啦。+ `6 s1 \/ I* W8 F8 \9 C
, d7 G- h, @+ T1 `/ ~. `+ [$ T
合约部署上链后,会将runtime部分储存起来,透过判断某个address是否拥有runtime的bytecode就可知它是否为contract address。幸好,solidity支援inline assembly,可以直接在solidity程序代码内使用 extcodesize取出储存的byte code长度,若长度为0就表示是EOA发出的,否则就是合约。
/ x/ Z3 M0 p  G0 b2 j1 \
! h1 c- Z$ ~0 t/ D2 L小明修改完程序代码之后,再跑一次DiceHack.sol,果然成功挡下,看到屏幕上跳出 robot is not allowed!! 讯息,嘴角不禁微微抽动。这下终于可以高枕无忧,离他的发财梦也不远了。0 p1 J. {9 I$ I  t
) c# e4 ^5 Y; b. V* c" X
道高一尺魔高一丈
! m; c" J$ T6 r  F& Y) |% k: b" H- ^3 d+ ~& y. F3 B) m
一切都在阿财的预料之中,但令他感到意外的是这个新手居然马上就修补好漏洞,这样的热血青年实在令人赞赏,沈思几秒之后,阿财心里默默说出,好吧,这次就放过你了。于是着手提交第二个bug,并附上他早就预备好的第二支攻击合约。
: P9 o3 B$ [5 l
- j  E3 y! |4 t, w7 z- N, q! f7 b  `攻击思路是这样的,solidity经过编译之后,主要有creation及runtime两个部分的byte code,首先执行creation,此时,runtime程序代码尚未储存,这样extcodesize(addr) 的回传值一定会是0。因此只要把攻击程序代码写在合约的constructor里面,就可以绕过侦测机制,发动攻击。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

云丝雨线纱 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    17