
Zyrox: LLVM-basiertes, kompilierzeitiges Obfuscator-Plugin.
why not ¯\_(ツ)_/¯
Eines meiner größten Projekte, bei dem ich viel über LLVM-Interna, Binärformate, Assembly und Obfuskierungstechniken gelernt habe.
Ich glaube, dass Lernen durch Bauen der beste Weg ist, zu lernen, daher habe ich dieses Projekt gebaut, um mehr über diese Themen zu erfahren.
Ich habe 4 Blogbeiträge geschrieben, die die Konzepte hinter Zyrox erklären:
Diese Teile gehen tiefer als diese README und sind auf jeden Fall lesenswert, wenn Sie sich für das Thema interessieren.
Dies ist für diejenigen gedacht, die Zyrox schnell testen möchten oder lernen möchten, wie man es in ein CMake-Projekt integriert.
Folgen Sie den Schritten im Zyrox Template-Repository.
llvm installieren:
sudo apt update
sudo apt install llvm-18 llvm-18-dev clang-18
Zyrox klonen und kompilieren:
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
Stellen Sie sicher, dass python3 und pip installiert sind.
# 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
Nach der Obfuskierung führen Sie PyPlugin.py aus, um Sprungtabellen zu verschlüsseln:
# 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]
Schauen Sie sich das Zyrox Template-Repository für ein Beispiel einer CMake-Integration an.
Mir ist klar, dass dies ein komplexes Thema ist, und dieses Projekt diente hauptsächlich Bildungszwecken sowie dem BSD Brawl. Wenn Sie Fragen haben oder einfach nur plaudern möchten, können Sie mich gerne kontaktieren:
@s.b[email protected] or [email protected]Jede Hilfe, sei es durch Pull Requests oder Issues, ist willkommen!
ZyroxPlugin.cpp registriert den Pass, verlinkt dann siphash (mehr dazu später) und ruft StringEncryption auf, um Strings zu verschlüsseln.
Der Grund, warum wir Strings früh verschlüsseln, ist, dass die Entschlüsselungslogik später ebenfalls obfuskiert wird.
dann ruft es ModuleUtils::ExpandCustomAnnotations und QuickConfig::RegisterPasses auf, um alle __attribute__((annotate("...")))-Ausdrücke zu parsen und die QuickJs-Konfiguration (in ZyroxConfig.js) auszuführen.
Jede Funktion wird durch Aufruf von Zyrox::RunOnFunction in ZyroxCore.cpp obfuskiert; weitere Dokumentation dazu wird in Zukunft bereitgestellt.
Switch-Anweisungen erzeugen Sprungtabellen und PHI-Knoten sind lästig zu handhaben, daher verwenden wir FunctionUtils und BasicBlockUtils, um diese zu glätten (in if-Anweisungen) bzw. herabzustufen.
oh Mann, wo soll ich anfangen
Alle js-Plugin-Argumente sind in index.d.ts und werden daher in dieser Dokumentation nicht behandelt.
für die Annotationsdokumentation klicken Sie hier
Dieser Pass teilt und mischt einen Basisblock in kleinere Blöcke auf. Angenommen, wir haben Folgendes:
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;
}
was zu Folgendem kompiliert wird:
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
}
bei Verwendung des Basic Block Splitters mit dieser Konfiguration:
z.RegisterPass(ObfuscationType.BasicBlockSplitter, {
PassIterations: 1,
"BasicBlockSplitter.SplitBlockChance": 100,
"BasicBlockSplitter.SplitBlockMinSize": 2,
"BasicBlockSplitter.SplitBlockMaxSize": 5,
});
wird daraus:
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
}
Jetzt wird es für eine so kleine Funktion nicht viel anders aussehen, aber beachten Sie, wie es einen Basisblock aufgeteilt hat? Dies ist hilfreich in Kombination mit anderen Passes wie Control Flow Flattening.
Oh, Mann, dieser Pass hat die meisten Funktionen von allen lol. Ich werde zuerst erklären, wie es funktioniert, dann seine Konfiguration. Angenommen, wir haben diesen Code:
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
jeder Basisblock (A, B und C) erhält einen eindeutigen Dispatcher-Status, Beispiel: (vereinfacht)
states = {
1: LABEL_A,
2: LABEL_B,
3: LABEL_C,
};
dann fügen wir einen Dispatcher-Block ein, der alles steuert, und der Code wird zu: