God.Game智能合约攻击事务剖析

宣布时间 2018-08-24
 8月19日,God.Game在以太坊区块链上安排了自己的智能合约(地址位于https://etherscan.io/token/0xca6378fcdf24ef34b4062dda9f1862ea59bafd4d,简称God合约),时隔一天攻击者就偷取了该合约的243个以太币,价值凌驾6万美元 。尊龙凯官网入口ADLab在监控到该事务后,对该攻击举行了详细的剖析和重现 。


一、攻击回溯


        通过etherscan可以看到攻击者的以太币提取生意:

尊龙凯时 - 人生就是搏!

        生意详情如下,即攻击者0xc30e89db73798e4cb3b204be0a4c735c453e5c74(简称攻击者1)挪用了God合约的withdraw函数举行提币:

尊龙凯时 - 人生就是搏!


        审查攻击者1在God合约中是否持有token,靠近20万的数目 。

尊龙凯时 - 人生就是搏!

        审查攻击者1在withdraw挪用之前对God合约的挪用,如下:

尊龙凯时 - 人生就是搏!

        从攻击者1的生意来看,它发往God合约的最早生意是sell挪用,说明在sell之前它就已经有了God合约的token 。那么攻击合约在此之前,一定有其它账户给它转移过token 。不然,它不会有可以sell的token 。

        在追踪攻击者1的token转变历程中,我们发明另外一个攻击者(简称攻击者2,地址为0x2368beb43da49c4323e47399033f5166b5023cda),它挪用了一个攻击合约(地址为0x7f325efc3521088a225de98f82e6dd7d4d2d02f8)给攻击者1转移了20万token:

尊龙凯时 - 人生就是搏!


        攻击者2挪用攻击合约的transfer函数,目的地址为攻击者1,数目为20万 。由于攻击合约并没有开放源码,因此这里的transfer函数仅仅是函数署名匹配的效果(有一定几率是其它名字) 。那么,攻击合约的20万token是从那里获得的?


        继续跟踪攻击者2和攻击合约,发明攻击合约是由攻击者2建设的,且攻击者2对攻击合约的挪用就是在God合约被攻击提现的时间窗口中 。

尊龙凯时 - 人生就是搏!


        从攻击者2的生意行为,可以看出他先给攻击合约转入4.3 token,然后从攻击合约转出4.3 token 。此时,攻击合约的token为0 。随后,攻击合约直接转20万token给攻击者1(还转移了21万给另外一个地址),这批注攻击者在挪用reinvest函数时应该使攻击合约的token爆发了某种转变 。


        接着剖析这个reinvest生意,它是直接挪用不开源的攻击合约,其内部机制我们并不清晰 。可是,这个生意历程会触发God合约的两个事务,onTokenPurchase和onReinvestment:

尊龙凯时 - 人生就是搏!


        通过这个事务的纪录数据,可以看到该reinvest挪用使得合约判断购置token的以太代币数目为一个大数,并且远远凌驾以太币刊行总量 。这个信息也反应出reinvest函数内部逻辑一定爆发了某种非预期的行为 。


        通太过析God合约源码,发明onReinvestment事务仅在God合约的reinvest函数中触发:

尊龙凯时 - 人生就是搏!


        可见,onReinvestment的以太币参数的最终盘算方法为:

尊龙凯时 - 人生就是搏!


        这一行代码显着保存整数溢出的理论可能,由于它没有使用SafeMath等类似清静运算操作 。但这里溢出的值并不是经典0xfffff…等类似的大数,而是一个够大但又远缺乏uint256极大值的数 。


        仔细视察发明,magnitude变量是2的64次方,然后我们做一个等式变换:

尊龙凯时 - 人生就是搏!


        这样我们就找到了经典整数溢出的第一现场指纹,只需要上面减法操作的第一操作数比第二操作数略小即可 。很显然,第一操作数又可划分为两个子操作数的乘法,只要任一个为零即导致效果为零 。此时,第二操作数只要是恣意一个小正数即可爆发上面的经典指纹 。


        继续结构上述的操作数 。首先,tokenBalanceLedger_[_customerAddress]在合约挪用的上下文中体现挪用者持有的token 。因此,只要挪用者不持有合约token,这个值就是零 。此时无论profitPerShare_值为几多,乘法效果都为零 。这样减法的第一操作数为零的条件,就容易结构出来了,即挪用者不持有God合约token 。然后,payoutsTo_是一个mapping工具,合约挪用者的初始值为零,需要使其为一个正数 。

尊龙凯时 - 人生就是搏!


        剖析God合约中修改payoutsTo_的代码有:

尊龙凯时 - 人生就是搏!


        攻击合约在reinvest挪用之前只执行过transfer挪用和withdraw挪用 。其中transfer挪用从攻击合约转token到外部账户,以是不会修改合约的payoutsTo_值,但withdraw函数会直接修改合约的payoutsTo_值 。因此,只要在reinvest之前挪用一次withdraw函数就可以使得减法的第二操作数为一个正数 。


        最后,第一个操作数为零值,第二个操作数为正数,并且减法效果强制转换为无符号整数,在没有运用清静运算库的条件下直接使用减法操作就会导致溢出,效果为一个很大的正数 。至此,攻击者的完整攻击历程如下:


尊龙凯时 - 人生就是搏!

二、Remix复现


        在剖析了完整攻击路径后,我们可以结构出如下的攻击合约:

尊龙凯时 - 人生就是搏!


        在remix中凭证如下办法举行操作:


        (1)安排God合约(为了利便追踪内部数据结构的转变,直接把所有成员和函数都重新界说为public) ;


        (2)用1eth,购置第一次token,引用地址设置为0x00… ;

尊龙凯时 - 人生就是搏!


        (3)再用相同的参数来购置一次(一定要再来一次,由于此时合约的profitPerShare_仍然是零值,这会导致withdraw挪用的函数修饰符失败) ;

尊龙凯时 - 人生就是搏!


        (4)安排攻击合约Test(转达God合约地址给Test) ;


        (5)挪用God合约的Transfer给Test发送Token(这里直接把购置的所有token都发送已往) ;


尊龙凯时 - 人生就是搏!

        (6)挪用攻击合约Test的withdraw函数,攻击合约的payoutsTo_已经被修改为大数 ;


尊龙凯时 - 人生就是搏!

        (7)挪用攻击合约Test的transfer函数把token所有给建设者,Test此时拥有的token为0,payoutsTo_为大数 ;

尊龙凯时 - 人生就是搏!


        (8)挪用攻击合约的reinvest函数,在日志中可以看到纪录购置token的eth为海量,并且乐成购置了大宗token ;

尊龙凯时 - 人生就是搏!


        (9)攻击合约Test通过溢出获得了大宗token,攻击者就可以从这个合约给其它地址转移token,并举行售卖套取eth 。


三、小结


        God合约被攻击的误差点较量简朴,即标准的整数溢出 。它的重大在于整数溢出的使用有多个约束条件,并且是在差别的营业逻辑中:


        (1)在溢出攻击的营业逻辑中,攻击者必需没有God的token,且payoutsTo_值必需为正数 ;


        (2)要使payoutsTo_为正数,攻击者就必需在其它营业逻辑中修改,好比withdraw ;


        (3)要执行withdraw,攻击者就必需持有God的token(最终溢出时又不可持有token) 。


        因此,攻击者需要通过多次触发God合约的差别营业逻辑才华最终造成整数溢出 。


        God合约的代码编写保存多处缺陷:


        (1)给管理员留下恣意地址的token操控能力,并且操控不触发事务 。这意味着修改是悄无声息的,除非有人去轮询监控每个地址的token转变 ;


        (2)Token的某些转移历程没有挪用标准ERC20事务接口,导致etherscan上看到的token转变是极端禁绝确的,倒运于果真透明监视 ;


        (3)代码中不思量限制循环,无意义的gas铺张(这也导致了在Remix调试中经常瓦解) ;


        (4)合约中的营业逻辑没有说明规范,仅开放合约代码并不可等价于项目透明 。