Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
zyrox — Zyrox: LLVMベースの、コンパイル時難読化プラグイン。 | Kitploit
ツール/GitHubGitHub/peterhackz/zyrox
静的分析コード分析リバースエンジニアリングバイナリ解析論文と研究学習と教育学習パスとコース
GitHubpeterhackz/zyrox

zyrox

Zyrox: LLVMベースの、コンパイル時難読化プラグイン。

リポジトリを見る
94116ヶ月前Kitploit レビュー済み

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有
ウェブサイト

Zyrox LLVMオブフスケーター

ネイティブコードを難読化するLLVMコンパイル時およびリンク時プラグイン

なぜ作ったのか

なんでって? ¯\_(ツ)_/¯

私がLLVMの内部、バイナリフォーマット、アセンブリ、難読化技術について多くを学んだ最大のプロジェクトのひとつです。

作ることを通じて学ぶのが最良の学び方だと信じているので、このテーマについてもっと学ぶためにこのプロジェクトを構築しました。

リサーチ

Zyrox の背後にあるコンセプトを解説したブログを4つ書きました:

  • パートI: Zyroxの構築:カスタムLLVMオブフスケーター
  • パートII: 制御フローフラットニング
  • パートIII: 暗号化ジャンプテーブル
  • パートIV: フィナーレ

これらのパートはこのREADMEより深く掘り下げており、このテーマに興味があるなら一読の価値があります。

ビルド

テンプレートから(クイックスタート、推奨)

これはZyroxを_すばやくテスト_したい人、またはcmakeプロジェクトへの統合方法を学びたい人を対象としています。

Zyroxテンプレート リポジトリの手順に従ってください。

ソースから

llvmをインストール:

root@kitploit:~
sudo apt update
sudo apt install llvm-18 llvm-18-dev clang-18

zyroxをクローンしてコンパイル:

root@kitploit:~
git clone --recurse-submodules https://github.com/PeterHackz/zyrox.git
cd zyrox
cmake -S . -B build -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++
cmake --build build --parallel 4

Python(コンパイル後)プラグインのセットアップ

python3 と pip がインストールされていることを確認してください。

環境のセットアップ(推奨)

root@kitploit:~
# 仮想環境を作成
python3 -m venv .venv

# 環境を有効化
source .venv/bin/activate

pip install -r requirements.txt

グローバルにインストール

root@kitploit:~
pip install -r requirements.txt

使用方法

クイック使用法

root@kitploit:~
clang -O0 -flto=full -c main.c -o out/main.o
clang -flto=full -fuse-ld=lld -Wl,--load-pass-plugin=./build/libzyrox.so out/main.o -o out/main

難読化後、PyPlugin.py を実行してジャンプテーブルを暗号化します:

root@kitploit:~
# 依存関係を仮想環境にインストールした場合は、最初に有効化してください:
source .venv/bin/activate
#  その後、以下で実行:
python PyPlugin.py --in=<input_file> [--out=<output_file>] [--tables=<zyrox_tables_file>] [--android]

CMakeの場合

CMake統合の例については、Zyroxテンプレート リポジトリを確認してください。

連絡先

これは複雑なテーマであり、このプロジェクトは主に教育目的であるとともに、BSD Brawl に役立てるためのものであることを理解しています。 質問がある場合や、ただチャットしたい場合でも、お気軽にご連絡ください:

  • Discord: @s.b
  • Email: [email protected] または [email protected]
  • Discordサーバー

プルリクエストやissueによる支援はいつでも歓迎です!

動作の仕組み

ZyroxPlugin.cpp はパスを登録し、siphash をリンクして(詳細は後述)、StringEncryption を呼び出して文字列を暗号化します。

文字列を早期に暗号化するのは、復号ロジックも後で難読化されるようにするためです。

次に、ModuleUtils::ExpandCustomAnnotations と QuickConfig::RegisterPasses を呼び出して、すべての __attribute__((annotate("..."))) 式を解析し、QuickJs構成(ZyroxConfig.js に配置)を実行します。

すべての関数は ZyroxCore.cpp にある Zyrox::RunOnFunction を呼び出すことで難読化されます。これに関する詳細なドキュメントは今後提供される予定です。

追加ユーティリティ

switch文はジャンプテーブルを生成し、PHIノードは扱いにくいため、FunctionUtils と BasicBlockUtils を使用して、それぞれ(if文への)フラット化と降格を行います。

パス

いやはや、どこから始めればいいのか

  • ベーシックブロックスプリッター
  • 制御フローフラットニング
  • 間接分岐
  • シンプル間接分岐
  • 混合ブール演算

すべてのjsプラグイン引数は index.d.ts にあるため、このドキュメントでは説明しません。

アノテーションのドキュメントについては、こちらをクリック

ベーシックブロックスプリッター

このパスは、ベーシックブロックをより小さなブロックに分割してシャッフルします。次のコードがあるとします:

root@kitploit:~
int __test_fn(int x)
{
    if (x == 2) {
        printf("x is 2\n");
    } else {
        printf("x is not 2!, x is: %d\n", x);
    }
    return x + 4 * x - 2 / 4;
}

これは次のようにコンパイルされます:

root@kitploit:~
define internal i32 @__test_fn(i32 noundef %0) #0 !zyrox !8 !obfuscated !11 {
  %2 = alloca i32, align 4
  store i32 %0, ptr %2, align 4
  %3 = load i32, ptr %2, align 4
  %4 = icmp eq i32 %3, 2
  br i1 %4, label %5, label %7

5:                                                ; preds = %1
  %6 = call i32 (ptr, ...) @printf(ptr noundef @.str.1)
  br label %10

7:                                                ; preds = %1
  %8 = load i32, ptr %2, align 4
  %9 = call i32 (ptr, ...) @printf(ptr noundef @.str.2, i32 noundef %8)
  br label %10

10:                                               ; preds = %7, %5
  %11 = load i32, ptr %2, align 4
  %12 = load i32, ptr %2, align 4
  %13 = mul nsw i32 4, %12
  %14 = add nsw i32 %11, %13
  %15 = sub nsw i32 %14, 0
  ret i32 %15
}

Basic Block Splitterをこの構成で使用すると:

root@kitploit:~
z.RegisterPass(ObfuscationType.BasicBlockSplitter, {
    PassIterations: 1,
    "BasicBlockSplitter.SplitBlockChance": 100,
    "BasicBlockSplitter.SplitBlockMinSize": 2,
    "BasicBlockSplitter.SplitBlockMaxSize": 5,
});

次のようになります:

root@kitploit:~
define internal i32 @__test_fn(i32 noundef %0) #0 !zyrox !8 !obfuscated !11 {
  %2 = alloca i32, align 4
  store i32 %0, ptr %2, align 4
  %3 = load i32, ptr %2, align 4
  %4 = icmp eq i32 %3, 2
  br i1 %4, label %5, label %14

5:                                                ; preds = %1
  %6 = call i32 (ptr, ...) @printf(ptr noundef @.str.1)
  br label %7

7:                                                ; preds = %14, %5
  %8 = load i32, ptr %2, align 4
  %9 = load i32, ptr %2, align 4
  %10 = mul nsw i32 4, %9
  %11 = add nsw i32 %8, %10
  br label %12

12:                                               ; preds = %7
  %13 = sub nsw i32 %11, 0
  ret i32 %13

14:                                               ; preds = %1
  %15 = load i32, ptr %2, align 4
  %16 = call i32 (ptr, ...) @printf(ptr noundef @.str.2, i32 noundef %15)
  br label %7
}

このような小さな関数ではあまり違いはありませんが、ベーシックブロックが分割されているのがわかりますか? これは 制御フローフラットニング のような他のパスと組み合わせると役立ちます。

制御フローフラットニング

ああ、このパスは全部の中で最も機能が多いんですよ、笑。 まず動作の仕組みを説明し、次にその構成について説明します。次のコードがあるとします:

root@kitploit:~
LABEL_A: bool b = x == 2;
         IF EQ: goto LABEL_B
         goto LABEL_C
LABEL_B  do_stuff()
LABEL_C  do_other_stuff()
         goto LABEL_A

各ベーシックブロック(A、B、C)には一意のディスパッチャ状態が割り当てられます。例:(簡略化)

root@kitploit:~
states = {
    1: LABEL_A,
    2: LABEL_B,
    3: LABEL_C,
};

次に、すべてを制御するディスパッチャブロックを注入すると、コードは次のようになります:

root@kitploit:~
         int state = 0;
LABEL_D  goto LABEL_CA // ディスパッチャラベルは最初の条件ブロック、ラベル条件Aへジャンプ
LABEL_CA if state == 1: goto LABEL_A
         // 1でなければ、ラベルBかどうかのチェックへ進む(フォールバック)
LABEL_CB if state == 2: goto LABEL_B
LABEL_CC if state == 3: goto LABEL_CC
         // 到達不能
         goto LABEL_D
LABEL_A: bool b = x == 2;
         // IF EQ: goto LABEL_B
         // goto LABEL_C
         state = 2 if b else 3 // 目的のブロックの状態を更新してディスパッチャに戻る
         goto LABEL_D
LABEL_B  do_stuff()
LABEL_C  do_other_stuff()
         state = 1
         goto LABEL_D

これにはオブフスケーターが修正するいくつかの欠陥があります。ご覧のとおり、ディスパッチャ変数がひとつしかないため、ブロックが状態を設定した後にどこへ進むかがわかってしまい、難読化を解除するのは簡単です。修正も簡単です!

root@kitploit:~
z.RegisterPass(ObfuscationType.ControlFlowFlattening, {
    PassIterations: 1,
    "ControlFlowFlattening.UseFunctionResolverChance": 60,
    "ControlFlowFlattening.UseGlobalStateVariablesChance": 60,
    "ControlFlowFlattening.UseOpaqueTransformationChance": 40,
    "ControlFlowFlattening.UseGlobalVariableOpaquesChance": 80,
    "ControlFlowFlattening.UseSipHashedStateChance": 40,
    "ControlFlowFlattening.CloneSipHashChance": 80,
});

オプションを1つずつ見ていきましょう:

  • UseFunctionResolverChance: 状態をチェックする関数を注入します。つまり、if (state == expected_state) の代わりに if (injected_resolver(state)) を行います。例:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_3585(__int64 a1)
    {
        return a1 == 0x288A6154F8A5E3E2LL;
    }
    
  • UseGlobalStateVariablesChance: 比較する状態値をグローバル変数に保存します:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_506(__int64 a1)
    {
        return a1 == qword_1B20D8;
    }
    
  • UseOpaqueTransformationChance: チェックを、特定の状態でのみ true になる何らかの変換に難読化します:
    root@kitploit:~
    bool __fastcall cff_resolve_state_check_7901(__int64 a1)
    {
        return ((((a1 ^ 0xEA9E45BB6099BC6ELL) + qword_1C64D8) << qword_1A63F0)
                   | (((a1 ^ 0xEA9E45BB6099BC6ELL)
                       + qword_1C64D8) >> qword_1ACE98)) == qword_1B0B80;
    }
    
  • UseGlobalVariableOpaquesChance: 上記の例でお気づきのとおり、 を行う際に数値の代わりにグローバル変数を使用します。(、、)

間接分岐

次のコードがあるとします:

root@kitploit:~
if (x == 2) goto LABEL_A
            goto LABEL_B
LABEL_A:    do_stuff()
LABEL_B:    // ...

これは次のように変換されます:

root@kitploit:~
@global jump_table = {0, &LABEL_A, &LABEL_B};
if (x == 2) goto jump_table[0] + @inline(decrypt(jump_table[1]));
            goto jump_table[0] + @inline(decrypt(jump_table[2]));
// ...

このパスを使用すると、プラグインは PyPlugin.py で使用する zyrox_tables.txt ファイルを出力します。

PyPlugin.py はジャンプテーブルを暗号化して再配置エントリをパッチし、リロケータがすべてのテーブルの jump_table[0] を指すようにします。リロケータは基本的に target.writePointer(base.add(value)) を行います。つまり、value を 0 に設定することで、リロケータにベースアドレスを渡させて実行時にジャンプテーブルに入れてもらい、それを goto とともに使用して実行時アドレスを生成します。arm32 Thumb モードでは、パスは復号後に自動的に | 1 を追加します。

PyPlugin.py を使用するには、次のようにするだけです:

(venvを使用している場合は、最初に有効化してください)

root@kitploit:~
python3 PyPlugin.py --in <out_obfuscated_file> --android

arm64 バージョンを対象としている場合は、x86_64 バージョンとはリロケータのシグネチャが異なるため、--android を渡すことが重要です。

--out も渡せます(デフォルトでは --in に渡した同じファイルが使用されます)。また、--tables も渡せます(デフォルトは zyrox_tables.txt です)。

シンプル間接分岐

間接分岐は素晴らしいように見えますが、実行時にポインタを復号化するためパフォーマンスの低下を伴います。これはパフォーマンスに影響しないシンプルなバージョンで、次のコード:

root@kitploit:~
if (x == 2) goto LABEL_A
            goto LABEL_B
LABEL_A:    do_stuff()
LABEL_B:    // ...

は次のようになります:

root@kitploit:~
            @stack jump_table = {&LABEL_B, &LABEL_A}
            goto jump_table[!(x == 2)]
LABEL_A:    do_stuff()
LABEL_B:    // ...

これはシンプルで簡単に破れるように見えますが(同感です)、パフォーマンスに影響を与えずに IDA と Ghidra を壊すには十分です。

混合ブール演算

MBA Sub(Mixed Boolean Arithmetic Substitution、混合ブール演算置換)としても知られ、単純な演算を同じ出力を与える複雑な演算に変換します。事前定義されたセットを使用します。 例:

root@kitploit:~
a ^ b = (~a & b) | (a & ~b)
b * c = (((b | c) * (b & c)) + ((b & ~c) * (c &  ~b)))
r = rand(); c = b + r; a = a + c; a = a - r

興味があれば、完全なリストは Passes/MBASub.cpp で確認できます。

Zyrox アノテーション

index.d.ts を確認してください。アノテーションパーサーも同じ順序を使用します。 関数をマークするには、次のようにするだけです:

root@kitploit:~
__attribute__((annotate("ibr:1,100"))) void hello_world () {
    some_hello ();
}

アノテーションコード:

  • ベーシックブロックスプリッター: bbs
  • 制御フローフラットニング: cff
  • 間接分岐: ibr
  • シンプル間接分岐: sibr
  • 混合ブール演算: mba

例: index.d.ts には次のようにあります:

root@kitploit:~
{
    "BasicBlockSplitter.SplitBlockMinSize"?: number;
    "BasicBlockSplitter.SplitBlockMaxSize"?: number;
    "BasicBlockSplitter.SplitBlockChance"?: number;
};

さて、重要なのは、最初の引数でありすべてのパスに共通する引数が PassIterations なので、それがアノテーションの最初の引数になります。 bbs で何かをアノテーションするには、次のようにします:

root@kitploit:~
__attribute__((annotate("bbs:1,15,30,100"))) void hello_world () {
    some_hello ();
}

これは、hello_world に対して ベーシックブロックスプリッター を、最小サイズ = 15、最大サイズ = 30、確率 = 100 で1回実行することを意味します。

パスを組み合わせることもできます:

root@kitploit:~
__attribute__((annotate("bbs:1,15,30,100 ibr:1,100 sibr:1,100"))) void hello_world () {
    some_hello ();
}

これは、hello_world に対して ベーシックブロックスプリッター → 間接分岐 → シンプル間接分岐 の順に実行することを意味します。 定義順に左から右へ実行されます。

ツールをダウンロード
UseOpaqueTransformationChance
qword_1C64D8
qword_1A63F0
qword_1ACE98
  • UseSipHashedStateChance: 状態をチェックするために、小さなカスタマイズされた siphash 関数を使用します。つまり、if (state == 23872) が if (siphash(state) == 3874872081) のようなものになり、ブロックがどこへジャンプするのかを見つけるのが難しくなります。移動先の条件がハッシュ化されると、ブロックは state = 23872 を行います。各 siphash 呼び出しはランダムな値を使用するため、エミュレートがより困難になります。
  • CloneSipHashChance: siphash 関数をクローンし、可能な場合はインライン化も試みて、その兄弟関数を複数作るため、単一の関数をフックするだけでは不十分になります。バイナリサイズが増えるだけでパフォーマンスには影響しないため、これを使用することが強く推奨されます。