为什么不呢 ¯\_(ツ)_/¯
这是我最大的项目之一,我在其中学到了很多关于 LLVM 内部机制、二进制格式、汇编和混淆技术的知识。
我相信通过构建来学习是最好的学习方式,因此我构建了这个项目来更深入地了解这些主题。
我写了 4 篇博客来解释 Zyrox 背后的概念:
这些部分比本 readme 更深入,如果你对这个主题感兴趣,绝对值得一读。
这适合想要 快速测试 Zyrox,或学习如何将其集成到 cmake 项目中的人。
按照 Zyrox Template 仓库中的步骤操作。
安装 llvm:
sudo apt update
sudo apt install llvm-18 llvm-18-dev clang-18
克隆并编译 zyrox:
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
确保你已安装 python3 和 pip。
# Create a virtual environment
python3 -m venv .venv
# Activate the env
source .venv/bin/activate
pip install -r requirements.txt
pip install -r requirements.txt
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 来加密跳转表:
# 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]
请查看 Zyrox Template 仓库,获取 CMake 集成示例。
我明白这是一个复杂的话题,这个项目主要是为了教育目的,同时也服务于 BSD Brawl。 如果你有任何问题,或者只是想聊天,欢迎联系我:
@s.b[email protected] 或 [email protected]任何形式的帮助,无论是通过 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 语句)和降级。
哦天哪,我该从哪里开始呢
所有 js-plugin 参数都在 index.d.ts 中,因此本文档中不再讨论。
关于注解的文档,点击这里
此 pass 将一个基本块拆分并打乱为多个更小的基本块。假设我们有以下代码:
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;
}
它会被编译成:
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 并使用以下配置时:
z.RegisterPass(ObfuscationType.BasicBlockSplitter, {
PassIterations: 1,
"BasicBlockSplitter.SplitBlockChance": 100,
"BasicBlockSplitter.SplitBlockMinSize": 2,
"BasicBlockSplitter.SplitBlockMaxSize": 5,
});
它会变成:
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。
哦天哪,这个 pass 的功能是所有 pass 中最多的,哈哈。 我先解释它的工作原理,然后再介绍它的配置。 假设我们有以下代码:
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)都会被分配一个唯一的分发器状态,例如:(简化版)
states = {
1: LABEL_A,
2: LABEL_B,
3: LABEL_C,
};
然后我们注入一个控制所有内容的分发器块,代码变成:
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
这个方案有一些缺陷,混淆器会修复这些缺陷。如你所见,由于我们只有一个分发器变量,很容易进行反混淆,因为我们知道一个块在设置状态后会跳转到哪里。很容易修复!
z.RegisterPass(ObfuscationType.ControlFlowFlattening, {
PassIterations: 1,
"ControlFlowFlattening.UseFunctionResolverChance": 60,
"ControlFlowFlattening.UseGlobalStateVariablesChance": 60,
"ControlFlowFlattening.UseOpaqueTransformationChance": 40,
"ControlFlowFlattening.UseGlobalVariableOpaquesChance": 80,
"ControlFlowFlattening.UseSipHashedStateChance": 40,
"ControlFlowFlattening.CloneSipHashChance": 80,
});
让我们逐一介绍这些选项: