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 内部机制、二进制格式、汇编和混淆技术的知识。

我相信通过构建来学习是最好的学习方式,因此我构建了这个项目来更深入地了解这些主题。

研究

我写了 4 篇博客来解释 Zyrox 背后的概念:

  • 第一部分:构建 Zyrox:自定义 LLVM 混淆器
  • 第二部分:控制流平坦化
  • 第三部分:加密跳转表
  • 第四部分:终章

这些部分比本 readme 更深入,如果你对这个主题感兴趣,绝对值得一读。

构建

使用模板(快速开始,推荐)

这适合想要 快速测试 Zyrox,或学习如何将其集成到 cmake 项目中的人。

按照 Zyrox Template 仓库中的步骤操作。

从源码构建

安装 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:~
# Create a virtual environment
python3 -m venv .venv

# Activate the env
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:~
# if you installed dependencies in a virtual environment, activate it first:
source .venv/bin/activate
#  then run with:
python PyPlugin.py --in=<input_file> [--out=<output_file>] [--tables=<zyrox_tables_file>] [--android]

使用 CMake

请查看 Zyrox Template 仓库,获取 CMake 集成示例。

联系方式

我明白这是一个复杂的话题,这个项目主要是为了教育目的,同时也服务于 BSD Brawl。 如果你有任何问题,或者只是想聊天,欢迎联系我:

  • Discord:@s.b
  • 邮箱:[email protected] 或 [email protected]
  • Discord 服务器

任何形式的帮助,无论是通过 pull request 还是 issue,都非常感谢!

工作原理

ZyroxPlugin.cpp 注册 pass,然后链接 siphash(稍后详细介绍)并调用 StringEncryption 来加密字符串。

我们提前加密字符串的原因是,这样解密逻辑之后也会被混淆。

然后它调用 ModuleUtils::ExpandCustomAnnotations 和 QuickConfig::RegisterPasses 来解析所有 __attribute__((annotate("..."))) 表达式,并运行 QuickJs 配置(位于 ZyroxConfig.js)。

每个函数都通过调用位于 ZyroxCore.cpp 中的 Zyrox::RunOnFunction 进行混淆, 未来会提供更多相关文档。

额外工具

switch 会创建跳转表,而 PHI 节点处理起来很烦人,因此我们分别使用 FunctionUtils 和 BasicBlockUtils 将其扁平化(转换为 if 语句)和降级。

Passes

哦天哪,我该从哪里开始呢

  • Basic Block Splitter
  • Control Flow Flattening
  • Indirect Branching
  • Simple Indirect Branching
  • Mixed Boolean Arithmetic

所有 js-plugin 参数都在 index.d.ts 中,因此本文档中不再讨论。

关于注解的文档,点击这里

Basic Block Splitter

此 pass 将一个基本块拆分并打乱为多个更小的基本块。假设我们有以下代码:

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
}

现在对于这么小的函数来说,差别不会太大,但注意它是如何拆分一个基本块的?这与其他 pass 结合使用时很有帮助,例如 Control Flow Flattening。

Control Flow Flattening

哦天哪,这个 pass 的功能是所有 pass 中最多的,哈哈。 我先解释它的工作原理,然后再介绍它的配置。 假设我们有以下代码:

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 // dispatcher label jumps to first condition block, label condition A
LABEL_CA if state == 1: goto LABEL_A
         // if not 1, go to check if it is label B (fallback)
LABEL_CB if state == 2: goto LABEL_B
LABEL_CC if state == 3: goto LABEL_CC
         // unreachable
         goto LABEL_D
LABEL_A: bool b = x == 2;
         // IF EQ: goto LABEL_B
         // goto LABEL_C
         state = 2 if b else 3 // update state for the block we want and back to dispatcher
         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,
});

让我们逐一介绍这些选项:

  • 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:将检查混淆为某种变换,该变换只会在特定状态下返回真:
    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:在执行 UseOpaqueTransformationChance 时使用全局变量而不是数字,正如你在上面的示例中注意到的那样。(、、)

Indirect Branching

假设我们有以下代码:

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]));
// ...

使用此 pass 时,插件会输出一个 zyrox_tables.txt 文件,供 PyPlugin.py 使用。

PyPlugin.py 会加密跳转表并修补重定位条目,然后让重定位器指向每个表的 jump_table[0]。重定位器基本上执行以下操作: target.writePointer(base.add(value)),因此通过将 value 设为 0,我们让重定位器返回基地址并在运行时将其放入跳转表,然后我们将其与 goto 一起用于生成运行时地址。在 arm32 thumb 模式下,此 pass 会在解密后自动添加 | 1。

要使用 PyPlugin.py,只需执行以下操作:

(如果使用 venv,请先激活它)

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

如果你针对的是 arm64 版本,传入 --android 很重要,因为 x86_64 版本的重定位器签名不同。

你还可以传入 --out(默认使用传给 --in 的同一文件),也可以传入 --tables(默认为 zyrox_tables.txt)。

Simple Indirect Branching

虽然 indirect branching 看起来很棒,但它也有性能开销,因为它在运行时解密指针。这是一个不影响性能的简单版本,像这样:

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 失效,而且不影响性能。

Mixed Boolean Arithmetic

也称为 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 Annotations

只需查看 index.d.ts。注解解析器使用相同的顺序。 要标记一个函数,只需执行以下操作:

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

注解代码:

  • Basic Block Splitter: bbs
  • Control Flow Flattening: cff
  • Indirect Branching: ibr
  • Simple Indirect Branching: sibr
  • Mixed Boolean Arithmetic: mba

示例: 在 index.d.ts 中我们看到:

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

现在重点是,PassIterations 是第一个参数,也是所有 pass 共有的参数,因此它将是注解中的第一个参数。 要用 bbs 注解某个函数,我们这样做:

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

这意味着:对 hello_world 运行 Basic Block Splitter 1 次,最小大小 = 15,最大大小 = 30,概率 = 100。

你还可以组合多个 pass:

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

这意味着对 hello_world 先运行 Basic Block Splitter,_然后_运行 Indirect Branching,_再然后_运行 Simple Indirect Branching。它们将按从左到右的定义顺序运行。

下载工具
qword_1C64D8
qword_1A63F0
qword_1ACE98
  • UseSipHashedStateChance:使用一个微型的自定义 siphash 函数来检查状态。因此 if (state == 23872) 会变成类似 if (siphash(state) == 3874872081) 的样子,使查找一个块跳转到哪里变得更加困难。当块的去向条件被哈希后,块会执行 state = 23872。每次 siphash 调用都会使用随机值,使其更难被模拟。
  • CloneSipHashChance:克隆 siphash 函数,并在可能时尝试内联它,从而生成多个兄弟副本,这使得只 hook 一个函数是不够的。非常推荐使用此选项,因为它只会增加二进制大小,而不会影响性能。