炼数成金 门户 量化投资 区块链 查看内容

一行代码蒸发了 ¥6,447,277,680 人民币!

2018-4-24 10:04| 发布者: 炼数成金_小数| 查看: 23804| 评论: 0|原作者: 高金|来自: Python 爬虫分享

摘要: 现在进入你还是先行者,最后观望者进场才是韭菜。美图董事长蔡文胜曾在三点钟群,高调的说出了这句话,随即被大众疯传。在他发表完言论没多久,2 月美链(BEC)上交易所会暴涨 4000%,后又暴跌。尽管他多次否认,聪 ...

Python 安全 测试 金融 量化投资

现在进入你还是先行者,最后观望者进场才是韭菜。

美图董事长蔡文胜曾在三点钟群,高调的说出了这句话,随即被大众疯传。

在他发表完言论没多久,2 月美链(BEC)上交易所会暴涨 4000%,后又暴跌。尽管他多次否认,聪明的网友早已扒出,他与 BEC 千丝万缕的关系。


庄家坐庄操控币价,美图的股价随之暴涨,蔡文胜顺利完成了他的韭菜收割大计。

但在币圈,割人者,人恒割之。

随着 BEC 智能合约的漏洞的爆出,被黑客利用,瞬间套现抛售大额 BEC,60亿在瞬间归零。

而这一切,竟然是因为一个简单至极的程序 Bug。



背景
今天有人在群里说,Beauty Chain 美蜜代码里面有 bug,已经有人利用该 bug 获得了 57,896,044,618,658,100,000,000,000,000,000,000,000,000,000,000,000,000,000,000.792003956564819968 个 BEC。

那笔操作记录是 0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f(https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f)


我们可以看到执行的方法是 batchTransfer。

那这个方法是干嘛的呢?(给指定的几个地址,发送相同数量的代币)。

整体逻辑是
你传几个地址给我(receivers),然后再传给我你要给每个人多少代币(value);

然后你要发送的总金额 = 发送的人数* 发送的金额;

然后 要求你当前的余额大于 发送的总金额;

然后扣掉你发送的总金额;

然后 给 receivers 里面的每个人发送 指定的金额(value)。

从逻辑上看,这边是没有任何问题的,你想给别人发送代币,那么你本身的余额一定要大于发送的总金额的!

但是这段代码却犯了一个很傻的错!

代码解释

这个方法会传入两个参数:
_receivers

_value

_receivers 的值是个列表,里面有两个地址:

0x0e823ffe018727585eaf5bc769fa80472f76c3d7

0xb4d30cac5124b46c2df0cf3e3e1be05f42119033

_value 的值是:

8000000000000000000000000000000000000000000000000000000000000000

我们再查看代码(如下图):

我们一行一行地来解释:

uint cnt = _receivers.length;
是获取 _receivers 里面有几个地址,我们从上面可以看到 参数里面只有两个地址,所以 cnt=2,也就是 给两个地址发送代币。

uint256 amount = uint256(cnt) * _value;

uint256
首先 uint256(cnt) 是把 cnt 转成了 uint256 类型。那么,什么是 uint256 类型?或者说 uint256 类型的取值范围是多少?

uintx 类型的取值范围是 0 到 2 的 x 次方 -1。也就是,假如是 uint8 的话,则 uint8 的取值范围是 0 到 2 的 8 次方 -1,即 0 到 255。

那么,uint256 的取值范围是:

0 - 2 的 256 次方 -1 ,也就是 0 到 115792089237316195423570985008687907853269984665640564039457584007913129639935

Python 算 2 的 256 次方是多少?


那么假如说 设置的值超过了 取值范围怎么办?这种情况称为“溢出”。

举个例子来说明:

因为 uint256 的取值太大了,所以用 uint8 来 举例。

从上面我们已经知道了 uint8 最小是 0,较大是 255。

那么当我 255 + 1 的时候,结果是啥呢?结果会变成 0。

那么当我 255 + 2 的时候,结果是啥呢?结果会变成 1。

那么当我 0 - 1 的时候,结果是啥呢?结果会变成 255。

那么当我 0 - 2 的时候,结果是啥呢?结果会变成 254。

那么,我们回到上面的代码中:

amount = uint256(cnt) * _value
则 amount = 2* _value。

但是此时 _value 是 16 进制的,我们把它转成 10 进制:

(Python 16 进制转 10 进制)

可以看到 _value = 57896044618658097711785492504343953926634992332820282019728792003956564819968

那么 amount = _value*2 = 115792089237316195423570985008687907853269984665640564039457584007913129639936

可以在查看上面看到 uint256 取值范围较大为 115792089237316195423570985008687907853269984665640564039457584007913129639935

此时,amout 已经超过了较大值,溢出 则 amount = 0

下一行代码 require(cnt > 0 && cnt <= 20); require 语句是表示该语句一定要是正确的,也就是 cnt 必须大于 0 且 小于等于 20

我们的 cnt 等于 2,通过!

require(_value > 0 && balances[msg.sender] >= amount);
这句要求 value 大于 0,我们的 value 是大于 0 的 且,当前用户拥有的代币余额大于等于 amount,因为 amount 等于 0,所以 就算你一个代币没有,也是满足的!

balances[msg.sender] = balances[msg.sender].sub(amount);
这句是当前用户的余额 - amount

当前 amount 是 0,所以当前用户代币的余额没有变动。

for (uint i = 0; i < cnt; i++) {
 balances[_receivers[i]] = balances[_receivers[i]].add(_value);
 Transfer(msg.sender, _receivers[i], _value);
}
这句是遍历 _receivers 中的地址, 对每个地址做以下操作:

balances[_receivers[i]] = balances[_receivers[i]].add(_value); _receivers 中的地址 的余额 = 原本余额+value

所以 _receivers 中地址的余额 则加了 57896044618658097711785492504343953926634992332820282019728792003956564819968 个代币!!!

Transfer(msg.sender, _receivers[i], _value); } 这句则只是把赠送代币的记录存下来!!!

总结
就一个简单的溢出漏洞,导致 BEC 代币的市值接近归 0。

那么,开发者有没有考虑到溢出问题呢?

其实他考虑了,可以看如下的截图:


除了 amount 的计算外, 其他的给用户转钱都用了 safeMath 的方法(sub,add)。

那么,为啥就偏偏这一句没有用 safeMath 的方法呢。。。

这就要问写代码的人了。。。

啥是 safeMath

safeMath 是为了计算安全 而写的一个 library。

我们看看它干了啥?为啥能保证计算安全。

function mul(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
如上面的乘法。他在计算后,用 assert 验证了下结果是否正确!

如果在上面计算 amount 的时候,用了 mul 的话, 则 c / a == b 也就是 验证 amount / cnt == _value。

这句会执行报错的,因为 0 / cnt 不等于 _value。

所以程序会报错!

也就不会发生溢出了...

那么,还有一个小问题,这里的 assert 好 require 好像是干的同一件事 —— 都是为了验证某条语句是否正确!

那么它俩有啥区别呢?

用了 assert 的话,则程序的 gas limit 会消耗完毕;

而 require 的话,则只是消耗掉当前执行的 gas。

总结
那么 我们如何避免这种问题呢?

我个人看法是:
只要涉及到计算,一定要用 safeMath

代码一定要测试!

代码一定要 review!

必要时,要请专门做代码审计的公司来测试代码。

这件事后需要如何处理呢?

目前,该方法已经暂停了(还好可以暂停)所以看过文章的朋友 不要去测试了...

不过已经发生了的事情咋办呢?

我的想法是,快照在漏洞之前,所有用户的余额情况,然后发行新的 token,给之前的用户发送等额的代币...

作者简介:高金,pycon 2017上海站讲师,首届 block.city taipei hackathon champion,累计参与全球 10 余场黑客马拉松,参与过多款区块链游戏开发。

欢迎加入本站公开兴趣群
量化投资群
兴趣范围包括:量化投资,算法交易,金融建模,统计套利等等
QQ群 697033743
如果大家不明白什么是量化投资,在百度谷歌搜索一下“西蒙斯”就知道了,最近这哥们火极了!这套东西在国外的金融机构已经大量使用,随着中国金融市场规模日益扩大和趋于成熟,这套玩法最终肯定也能在国内转起来,我们一起学习切磋,寻求项目机会做一下,提升自己在这方面的技能,将来一起发财

鲜花

握手

雷人

路过

鸡蛋

相关阅读

最新评论

热门频道

  • 大数据
  • 商业智能
  • 量化投资
  • 科学探索
  • 创业

即将开课

 

GMT+8, 2018-9-19 13:19 , Processed in 0.148905 second(s), 24 queries .