認証ロジックを少し保護しようと思って動的自動生成で難読化を重ねていたら、 いつの間にか、触れてはいけないものが産まれていました。
再生する際は音量にご注意ください(SAN値的な意味で)。
デバッガを開く勇気がある方は、どうぞ。概念を説明するために簡略化とフォーマットしたトイコードです。実際には変数名・文字列・windowやconsoleなどの人間が読める単語はすべて粉砕されて、もはや静的解析では人間が理解できるレベルではありません。
/**
* 難読化スクリプトの構造再現(強化版)
* * 1. String Decryptor: 全ての文字列を動的に生成
* 2. Virtual Machine: 独自命令セットによるロジック実行
* 3. Environment Guard: 解析妨害
*/
(function() {
// --- 1. 暗号化データセクション ---
// 実際には数千のバイト配列。命令、データ、文字列が渾然一体となっている
let _rawPayload = [0x61, 0x74, 0x33, 0x12, 0x89, 0xAC, 0x55, 0x21 /* ...中略... */];
let _bytecode = new Uint8Array(_rawPayload.length);
// --- 2. 文字列復号関数 (String Scrambler) ---
// 実行時に動的に文字列を組み立てる。静的解析では「どのAPIを叩いているか」すら不明
const _decryptString = (offset, length) => {
let s = "";
for (let i = 0; i < length; i++) {
// ローリング暗号(前の文字を鍵にする等)を模した計算等
// 実際にはこれだけではない
let charCode = _bytecode[offset + i] ^ (i % 255);
s += String.fromCharCode(charCode);
}
return s;
};
// --- 3. VMエンジン (The Dispatcher) ---
let _stack = [];
let _registers = new Int32Array(256);
let _pc = 0; // プログラムカウンタ
const execute = (initialState) => {
while (_pc < _bytecode.length) {
// 命令の取得
let op = _bytecode[_pc++];
// 難読化エグゼキューター特有の巨大な分岐
// 実際にはOPコード・メモリマップ等
// 考えうる限りのパラメータはすべて動的にシャッフルされる
switch(op) {
case 0x81: // PUSH_INT: 次のバイトを数値としてスタックへ
_stack.push(_bytecode[_pc++]);
break;
case 0x10: // STORE: レジスタへの代入
_registers[_bytecode[_pc++]] = _stack.pop();
break;
case 0x95: // LOAD: レジスタからスタックへ
_stack.push(_registers[_bytecode[_pc++]]);
break;
case 0x2A: // MATH_IMUL: 符号付き32bit乗算
let b = _stack.pop();
let a = _stack.pop();
_stack.push(Math.imul(a, b));
break;
case 0x38: // SYS_CALL (DOM/API):
// ここで `_decryptString` を使い、動的にAPIを呼び出す
let apiId = _bytecode[_pc++];
if (apiId === 0x06) {
// 例: "getContext" を復号してCanvasを取得
let methodName = _decryptString(50, 10);
let ctx = document.getElementById('c')[methodName]('2d');
// 描画ロジックへ...
}
break;
case 0x77: // JMP_IF_LESS: 条件付きジャンプ
let target = _bytecode[_pc++];
if (_stack.pop() < _stack.pop()) _pc = target;
break;
case 0xAF: // ANTI_DEBUG: 実行環境のチェック
if (window.outerWidth - window.innerWidth > 100) {
// 開発者ツールが開いていると判断し、偽のパスへ飛ばす
_pc = 0;
}
break;
case 0x00: // EXIT
return _stack.pop();
default:
// 未定義命令が来たら、解析を困難にするために「あえて壊す」
throw new Error(_decryptString(0, 5));
}
}
};
// --- 4. ブートストラップ ---
const init = () => {
// ペイロードの第1段階復号(XOR等)
for (let i = 0; i < _rawPayload.length; i++) {
_bytecode[i] = _rawPayload[i] ^ 0x55;
}
// 解析妨害の仕掛け:定期的に環境をチェック
setInterval(() => {
try {
// デバッガが起動していたら例外を投げたり
// pauseさせたりするような数々の解析を阻むコード
(function(){}).constructor("debugger")();
} catch(e) {}
}, 1000);
// VMの起動
execute([0, 0]);
};
init();
})();
技術紹介記事はこちらへ: JavaScriptを「粉砕」し、論理の迷宮を構築する動的保護エンジン「KinetiCrypt」の設計