BEC合约整数溢出漏洞还原与分析
一、币圈一秒,人间一年
有道是币圈一日,人间一年。这个说法又得升级了,叫币圈一秒,人间一年。
前不久,币圈又出大事啦。BEC智能合约被爆出整数溢出漏洞,导致黑客能无限印币,在一次交易中,也就那么几秒钟的事情,黑客就“无中生有”地给两个账户转了天文数字般的BEC币,而原账户一分BEC币都没损失。大家来围观下这笔交易:
https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f

围观归围观,所谓外行看热闹、内行看门道。
看到这么大笔BEC币,想必大家已经流着口水。。。
别馋,虽然大熊不能教大家在现实中黑一把,但搭个测试网络,还原下漏洞,过过瘾也是可以的,顺便还能学习下智能合约部署、整数溢出漏洞原理。知识是无价的,说不定以后能靠这些知识大赚一笔。好吧,言归正传:
那么,什么是整数溢出呢?
二、整数溢出浅析
首先,什么是溢出?
往空杯里面加水,加到满之后,继续加水,水就会流出来,这就是溢出。然而需要注意的是:尽管溢出,但杯里面依然是满水的状态。
然后,什么是整数溢出?
跟满杯加水溢出的情况不同,整数溢出像水轮车。往里面加水,加满之后,继续加水,水轮车将会失衡并转动,此时水将会被全部倒出来,并重新盛水。这就跟整数溢出类似:当变量累加到超过自身容量范围时,将会溢出,归零。而黑客也是利用了这一点,把运算前的数值作为转账金额,把运算后溢出的0作为检测条件,从而实现绕过。

这次的BEC整数溢出漏洞就出在batchTransfer函数(功能是批量转账)里面,具体来说就是以下这行:
uint256 amount = uint256(cnt) * _value;

简单分析一下:比如张三给李四、王五转账,数额为2^255(2的255次方),那么cnt=2,_value=2^255,而将cnt和_value两者相乘,结果为2^256,刚好超过uint256的范围,溢出之后amount的结果为0,接下来所有条件都能通过,最终结果是张三账户减去0个BEC币,而李四、王五的账户增加2^255个BEC币。

三、环境搭建、漏洞还原与分析(无意中又发现了一个漏洞)
这个漏洞虽然不起眼,逻辑也很简单,但是直接带来的后果相当于无限印BEC币啊,这还了得,下面就开始搭建测试网络,也给自己印几批天文数字BEC币过过瘾。
由于区块链环境搭建、以太坊和BEC智能合约部署过程较长,且并不是本文重点,所以简单阐述,若有兴趣可到本人微信公众号《进击的大熊》,回复“BEC”可见。
(一)环境搭建
geth客户端安装,用来运行以太坊节点、创建和管理账户、发送交易、挖矿、部署智能合约。
具体环境是:Win10 64位、geth-windows-amd64-1.8.6
(二)漏洞还原
1、准备创世块文件、初始化创世块,并启动区块链节点
2、创建3个用户,并分别命名为zhangsan、lisi、wangwu
3、主节点zhangsan挖矿,得到一些ETH,后面部署智能合约等操作都需要ETH。
4、复制BEC智能合约源码,用在线Remix工具编译,并部署到虚拟网络,下图可以看到已经部署成功。

5、溢出转账
这里无意中又发现了一个漏洞,不过不是BEC合约,而是console,先前我一直这样来转账:(绿色数值就是2^255)
>bectoken.batchTransfer([lisi,wangwu],57896044618658097711785492504343953926634992332820282019728792003956564819968,{from:zhangsan})
然而失败了若干次,由于数值较大,每次都怀疑是不是复制错了,为了解决这个问题,还试过:
>bectoken.batchTransfer([lisi,wangwu],Math.pow(2,255),{from:zhangsan})
但还是失败,李四和王五的账号依旧为0.
后来用Remix调试才发现,原来一直不能成功绕过检测(require(_value > 0 && balances[msg.sender] >= amount); ),因为amount根本不为0,张三的账户上也没有那么多BEC币。进一步发现,传进去的_value也不是2^255,而是诡异的0x80000000000000016c889……
怪不得不能通过 balances[msg.sender] >= amount

那么,换个思路吧,直接把_value的值在源代码设为2^255则可实现绕过,并成功实现转账,最少证明了理论是可行,大方向没错。

这种情况下,我想只能是console传递数值的时候出现问题了,但找了很多资料没能解决,最后在Github上发帖咨询Geth团队,才知道原来也是个溢出漏洞,真是“洞中洞”啊。
发帖没过多久就收到了回复,感谢这位小哥!
大概意思是:所用的控制台基于JavaScript,而JavaScipt使用float-s来表示数字,直接传递2^255这样大的数值,将会产生溢出,从而使数值发生变化。解决方法是,需要使用一个大数字库,这样才能使用任意精度的整数。

因此我改变打法,用web3.toBigNumber来表示2^255,最终也实现了还原,成功给李四、王五账户上转了天文数字般的BEC币。
var value = web3.toBigNumber(’57896044618658097711785492504343953926634992332820282019728792003956564819968′);


另外,可能眼尖的朋友会留意到,为什么zhangsan账户上也有一笔BEC,那是因为智能合约上规定,创造者会拥有一些初始的Token,请见怪不怪。

(三)安全代码的作用
已经证明了整数溢出的危害性,那么该如何解决这个问题呢?其实BEC智能合约里面已经回答了这个问题,可以发现除了漏洞那行代码之外,其它运算都用了安全运算库里面的函数,那么试试吧。
更改BEC智能合约代码,把*改成库里面的mul函数,再重复上面的过程。
可以看到,这时已不能绕过检测,转账失败。


分析下这个Mul函数,代入具体数值说明一下:
假如:a = 2,b = 2^255
那么:c = a * b = 0
但: c / a = 0 != b
因此也就绕不过安全运算函数了。

四、整数溢出的原因
水轮车溢出之后归零,并重新开始盛水,是因为自身失衡所致。
那整数溢出的原理又是什么呢,当uint256的参数为2^256时,为什么会等于0,而不是继续等于2^256-1。
下面尝试用C++程序实验来解释:

从下图实验结果可以发现,当int参数赋给unsigned short参数之后,unsigned short只取后16的数值,而前面的数值一概不理,所以就会出现当输入为65536,赋给输出值后,产生溢出,只保留最后的0000,最终输出值为零。
另外,当输入为65537,-1时,按照溢出的规则,则输出也就自然为1、65535。

用OD调试这个C++程序,以进一步说明:
单步调试来到这里输入数值前,可以从堆栈发现输入值将会保存到 地址为0x004FF918的内存空间中。
于是输入65535,可以发现0x FFFF(65535)已经被存储到 0x004FF918的内存空间中。
接下来,程序会将该值用movzx指令赋给ecx寄存器,以实现C++中的output=input。

继续单步调试,可以看到ecx寄存器的寄存器值为0x FFFF,也就是说,当输入为65535,输出也为65535。

再来一次,这次输入65536,可以发现0x 0001 0000已经被存储到 地址为0x00CFF8CC的内存空间中。
接下来,程序会将该值用movzx指令赋给ecx寄存器。

单步调试,继续运行程序,可以看到ecx寄存器的寄存器值为0x 0000。也就是说当输入为65536时,输出为0。
所以关键在于:MOVZX指令。
这是汇编语言数据传送指令MOV的变体,无符号扩展并传送。
简单来说,movzx将源操作数取出,置于目的操作数,而目的操作数其余位用0填充。于是就出现了溢出时,只留下低位的16位数值。
五、写在最后
BEC智能合约也不是小众产品,但都能出现如此明显且致命的漏洞,可见币圈安全问题真的不会少。
想必大家都想着产品早日上线,早日盈利,所以基本的代码审计、系统测试这类基本的安全检查都能省则省,或者简单带过,带来的后果可想而知。这次的安全事件给区块链开发者和黑客们都提了醒,开发者得加大精力考虑代码安全,而黑客们则是将注意力从持币者身上转移到源码审计。
信息安全问题,归根结底是人与人之间的利益博弈,只要利益存在,攻与防、矛与盾就会一直持续。
希望这次的事件,能引起大家对信息安全的重视。
时刻牢记安全无小事,防微杜渐是关键!
BEC合约整数溢出漏洞还原与分析的更多相关文章
- CVE-2013-2551:Internet Explore VML COALineDashStyleArray 整数溢出漏洞简单调试分析
0x01 2013 Pwn2Own 黑客大赛 在 Pwn2Own 的黑客大赛上,来自法国的 VUPEN 安全团队再一次利用 0day 漏洞攻破 Windows8 环境下的 IE10 浏览器,这一次问题 ...
- Solidity合约中的整数溢出漏洞事件
事件 2018年4月23日 BEC 一夜被偷64亿 2018年4月25日 SMT 再爆类似漏洞,火币Pro和OKEx相继暂停了SMT交易 2018年4月25日 BEC.SMT现重大漏洞,这8个智能合约 ...
- CVE-2019-11477:Linux 内核中TCP协议栈整数溢出漏洞详细分析 代码卫士 今天
CVE-2019-11477:Linux 内核中TCP协议栈整数溢出漏洞详细分析 代码卫士 今天
- Nginx整数溢出漏洞 Writeup
Nginx整数溢出漏洞 CVE-2017-7529 题目URL:http://www.whalwl.site:8040/ 漏洞描述 在 Nginx 的 range filter 中存在整数溢出漏洞,可 ...
- Linux Kernel 整数溢出漏洞
漏洞名称: Linux Kernel 整数溢出漏洞 CNNVD编号: CNNVD-201311-062 发布时间: 2013-11-07 更新时间: 2013-11-07 危害等级: 漏洞类型: ...
- CVE-2012-0774:Adobe Reader TrueType 字体整数溢出漏洞调试分析
0x01 TrueType 字体 TTF 字体是 Apple 和 Microsoft 两家公司共同推出的字体格式,现在已经广泛的运用于 Windows 操作系统,其中 PDF 文档也可以嵌入 TTF ...
- gdk-pixbuf tiff_image_parse函数整数溢出漏洞
受影响系统:gdk-pixbuf gdk-pixbuf 2.36.6描述:CVE(CAN) ID: CVE-2017-2870 gdk-pixbuf是一个用于以各种格式加载图像和像素缓冲处理的库. 使 ...
- 美链BEC合约漏洞技术分析
这两天币圈链圈被美链BEC智能合约的漏洞导致代币价值几乎归零的事件刷遍朋友圈.这篇文章就来分析下BEC智能合约的漏洞 漏洞攻击交易 我们先来还原下攻击交易,这个交易可以在这个链接查询到. 我截图给大家 ...
- CVE2016-8863libupnp缓冲区溢出漏洞原理分析及Poc
1.libupnp问题分析: (1)问题简述: 根据客户给出的报告,通过设备安装的libupnp软件版本来判断,存在缓冲区溢出漏洞:CVE-2016-8863. (2)漏洞原理分析: 该漏洞发生在up ...
随机推荐
- Web--Response
using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using ...
- acm数论之旅(转载)---最大公约数与最小公倍数
gcd(a, b),就是求a和b的最大公约数 lcm(a, b),就是求a和b的最小公倍数 然后有个公式 a*b = gcd * lcm ( gcd就是gcd(a, b), ( •̀∀•́ ) ...
- java.lang.NoClassDefFoundError异常处理
1.异常信息: Caused by: java.lang.NoClassDefFoundError: com/pingan/cfss/monitor/user/controller/UserInfoC ...
- css属性书写顺序(重点)
- nm
nm是names的缩写,功能是列出目标文件的符号清单,常用来查看动态链接库中的函数. nm选项 -a 按照man手册,仅列出调试信息,实际上却是调试信息+正常信息 -A 增加一列显示目标文件, ...
- 【SSM 】导出excel含图片
ExprotRentUtils package com.kikyo.stat.utils; import java.awt.image.BufferedImage; import java.io.By ...
- Vue项目的准备
1.下载nodejs 检查是否安装成功 2.使用gitee作为线上仓库 3.使用脚手架工具--命令行工具 能在8080里显示出以下画面即为成功
- wpf表单验证
在做表单的,需要对User提交数据做验证,wpf与silverlight 都提供自带的验证机制,但是只是验证,并不能在提交时提供详细的信息,此时可使用 依赖属性将错误信息整合进自定义错误集合中,即可在 ...
- PaperReading20200227
CanChen ggchen@mail.ustc.edu.cn Neural Predictor for Neural Architecture Search Motivation: Curren ...
- Pytorch本人疑问(2)model.train()和model.eval()的区别
我们在训练时如果使用了BN层和Dropout层,我们需要对model进行标识: model.train():在训练时使用BN层和Dropout层,对模型进行更改. model.eval():在评价时将 ...