
Zyrox: إضافة تشويش أثناء التجميع مبنية على LLVM.
لم لا ¯\_(ツ)_/¯
أحد أكبر مشاريعي، حيث تعلمت الكثير عن بنية LLVM الداخلية، وصيغ الملفات الثنائية، والتجميع، وتقنيات التشويه.
أعتقد أن التعلم من خلال البناء هو أفضل طريقة للتعلم، لذلك بنيت هذا المشروع لتعلم المزيد عن هذه المواضيع.
لقد كتبت 4 مدونات تشرح المفاهيم وراء Zyrox:
تتعمق هذه الأجزاء أكثر من هذا الملف، وهي تستحق القراءة بالتأكيد إذا كنت مهتمًا بالموضوع.
هذا مخصص لمن يريد اختبار سريع لـ 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]أي مساعدة، من خلال طلبات السحب أو المشكلات موضع تقدير!
يسجل ZyroxPlugin.cpp المرور، ثم يربط siphash (المزيد عن هذا لاحقًا) ويستدعي StringEncryption لتشفير السلاسل النصية.
سبب تشفير السلاسل النصية مبكرًا هو أن منطق فك التشفير سيتعرض للتشويه لاحقًا أيضًا.
ثم يستدعي ModuleUtils::ExpandCustomAnnotations و QuickConfig::RegisterPasses لتحليل جميع تعبيرات __attribute__((annotate("..."))) وتشغيل تكوين QuickJs (الموجود في ZyroxConfig.js)
يتم تشويه كل دالة عن طريق استدعاء Zyrox::RunOnFunction الموجود في ZyroxCore.cpp، وسيتم توفير مزيد من التوثيق حول هذا في المستقبل.
المفاتيح (switch) تنشئ جداول قفز وعُقد PHI وهي مزعجة في التعامل معها، لذلك نستخدم FunctionUtils و BasicBlockUtils لتسطيحها (إلى عبارات if) وإرجاعها إلى مستوى أدنى على التوالي.
يا رجل، من أين أبدأ
جميع وسائط إضافة js موجودة في index.d.ts لذلك لن يتم الحديث عنها في هذا التوثيق.
لتوثيق التعليقات التوضيحية، اضغط هنا
هذا الممر يقسم ويعيد ترتيب كتلة أساسية إلى كتل أصغر. لنفترض أن لدينا هذا:
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
}
عند استخدام مقسِّم الكتلة الأساسية مع هذا التكوين:
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
}
الآن لن يكون مختلفًا كثيرًا لمثل هذه الدالة الصغيرة، لكن لاحظ كيف قسم كتلة أساسية؟ هذا مفيد عند دمجه مع مرور أخرى مثل تسطيح تدفق التحكم
يا رجل، هذا الممر يحتوي على أكبر عدد من الميزات بين الجميع lol. سأبدأ بشرح كيف يعمل ثم تكوينه لنفترض أن لدينا هذه الشيفرة:
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
الآن هذا به بعض العيوب التي يصلحها المشوِّه. كما ترى، نظرًا لأن لدينا متغير موزع واحد، فمن السهل إزالة التشويه عن هذا لأننا نعرف أين ستذهب الكتلة بعد أن تعين الحالة. من السهل إصلاحه!