
Zyrox: LLVM आधारित, संकलन-समय अस्पष्टता प्लगइन।
क्यों नहीं ¯\_(ツ)_/¯
मेरी सबसे बड़ी परियोजनाओं में से एक, जहाँ मैंने LLVM के आंतरिक, बाइनरी फ़ॉर्मेट, असेंबली और अस्पष्टीकरण तकनीकों के बारे में बहुत कुछ सीखा।
मेरा मानना है कि निर्माण करते हुए सीखना सबसे अच्छा तरीका है, इसलिए मैंने इन विषयों के बारे में और अधिक जानने के लिए यह परियोजना बनाई।
मैंने Zyrox के पीछे की अवधारणाओं को समझाते हुए 4 ब्लॉग लिखे हैं:
ये भाग इस रीडमे से अधिक गहराई में जाते हैं, और यदि आप इस विषय में रुचि रखते हैं तो निश्चित रूप से पढ़ने लायक हैं।
यह उन लोगों के लिए है जो 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 इंस्टॉल है।
# एक वर्चुअल एनवायरनमेंट बनाएं
python3 -m venv .venv
# एनवायरनमेंट को सक्रिय करें
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 चलाएँ:
# यदि आपने एक वर्चुअल एनवायरनमेंट में डिपेंडेंसी इंस्टॉल की है, तो पहले इसे सक्रिय करें:
source .venv/bin/activate
# फिर इसके साथ चलाएँ:
python PyPlugin.py --in=<input_file> [--out=<output_file>] [--tables=<zyrox_tables_file>] [--android]
CMake एकीकरण के उदाहरण के लिए Zyrox Template रेपो देखें।
मुझे पता है कि यह एक जटिल विषय है, और यह परियोजना अधिकतर शैक्षिक उद्देश्यों के साथ-साथ BSD Brawl की सेवा के लिए थी। यदि आपका कोई प्रश्न है, या बस बातचीत करना चाहते हैं, तो बेझिझक मुझसे संपर्क करें:
@s.b[email protected] या [email protected]पुल रिक्वेस्ट या इश्यू के माध्यम से कोई भी मदद सराहनीय है!
ZyroxPlugin.cpp पास को रजिस्टर करता है, फिर siphash (इस पर बाद में अधिक) को लिंक करता है और स्ट्रिंग्स को एन्क्रिप्ट करने के लिए StringEncryption को कॉल करता है।
हम स्ट्रिंग्स को जल्दी एन्क्रिप्ट करने का कारण यह है कि बाद में डिक्रिप्शन लॉजिक भी अस्पष्ट हो जाए।
फिर यह ModuleUtils::ExpandCustomAnnotations और QuickConfig::RegisterPasses को कॉल करता है ताकि सभी __attribute__((annotate("..."))) एक्सप्रेशन को पार्स किया जा सके और QuickJs कॉन्फ़िग (जो ZyroxConfig.js में स्थित है) चलाया जा सके।
प्रत्येक फ़ंक्शन को ZyroxCore.cpp में स्थित Zyrox::RunOnFunction को कॉल करके अस्पष्ट किया जाता है, भविष्य में इसके बारे में अधिक दस्तावेज़ उपलब्ध कराए जाएंगे।
स्विच जंप टेबल और PHI नोड्स बनाते हैं, इनसे निपटना कष्टप्रद है, इसलिए हम इन्हें क्रमशः समतल करने (if स्टेटमेंट में) और अवनत करने के लिए FunctionUtils और BasicBlockUtils का उपयोग करते हैं।
अरे यार, मैं कहाँ से शुरू करूँ
सभी 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
}
अब यह इतने छोटे फ़ंक्शन के लिए बहुत अलग नहीं होगा, लेकिन ध्यान दें कि इसने एक बेसिक ब्लॉक को कैसे विभाजित किया? यह अन्य पासों जैसे कंट्रोल फ्लो फ़्लैटनिंग के साथ संयुक्त होने पर सहायक है।
अरे, इस पास में सभी की तुलना में सबसे अधिक सुविधाएँ हैं! मैं यह समझाकर शुरू करूँगा कि यह कैसे काम करता है, फिर इसका कॉन्फ़िग। मान लीजिए हमारे पास यह कोड है:
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) को एक अद्वितीय डिस्पैचर स्थिति (state) दी जाती है, उदाहरण: (सरलीकृत)
states = {
1: LABEL_A,
2: LABEL_B,
3: LABEL_C,
};
फिर हम एक डिस्पैचर ब्लॉक इंजेक्ट करते हैं जो सब कुछ नियंत्रित करता है और कोड बन जाता है: