بسم الله الرحمن الرحيم.
في هذا المقال، سوف نحل التحدي الأول من تحديات Flare-On (2014). سنعتمد في هذا الحل — وفي غالبية التحديات القادمة — على منهجية التعلم في الوقت المناسب (Just-in-Time Learning) أو ما يُعرف بالتعلم بالممارسة (Learning-by-Doing).
لماذا بدأنا بتحديات Flare-On؟
لأن هذه التحديات مبنية على تقنيات واقعية من العالم الحقيقي (Real-World)، وليست مجرد تحديات CTF مصطنعة تُقدَّم عادةً في المسابقات.
ملاحظة مهمة: تحديات الـ CTF هي فقط جسرٌ بين التعلم النظري والتطبيق العملي، ولا تدل بأي شكل من الأشكال على أنك شخص قوي في المجال. الخبرة الحقيقية تُكتسب من التعامل مع البرامج الواقعية (Real-World) — سواء كانت برمجيات خبيثة (Malware) أو ألعاباً أو برامج نقوم بهندستها عكسياً.
أي اقتراح لتطوير هذه المقالات، أو أي خطأ، أو أي استفسار، فالتواصل معي متاح دائماً إن شاء الله.
إن أحسنت فمن الله، وإن أسأت فمن نفسي والشيطان.
1. تحميل التحدي والفحص الأولي
أولاً، نقوم بتحميل التحدي من الموقع الرسمي: flare-on.com
كلمة سر الملفات: إما flare أو infected.
بدايةً، نقوم بفحص البرنامج بأداة Detect It Easy (DIE) لنعرف معلومات عنه، فنحصل على المعلومات التالية:
شكل (1): معلومات الملف كما تظهرها أداة DIE
كما هو واضح، إنه برنامج بصيغة PE32 ومكتوب بلغة C#.
2. أساسيات مهمة عن برامج .NET
قبل البدء بالحل، من الضروري أن نعرف بعض المعلومات عن برامج .NET، لأنها تختلف تماماً عن برامج C/C++.
برامج C/C++
عند كتابة برامج بلغة C/C++، نستخدم مترجماً (Compiler) مثل GCC. يمر البرنامج بمرحلتين بشكل مختصر:
- الترجمة المباشرة (Ahead-of-Time Compilation): يأخذ المترجم الكود المصدري (Source Code) ويحوّله مباشرة إلى كود الآلة (Machine Code).
- الربط (Linking): يتم دمج المكتبات الخارجية مع الكود ليصبح ملفاً تنفيذياً (.exe).
برامج C# (.NET)
بينما تمر برامج C#/.NET بعملية مختلفة يمكننا تسميتها البرمجة المُدارة (Managed Code): لا تُترجم برامج .NET مباشرة إلى لغة الآلة، بل تمر بمرحلتين وتعمل داخل بيئة تشغيل تُسمى CLR - Common Language Runtime.
المرحلة الأولى: وقت الترجمة (Compile Time)
عند عمل Build لبرنامج C#، لا يتم تحويله إلى لغة آلة مباشرة، بل يحدث التالي:
- يقوم مترجم .NET (مثل Roslyn) بتحويل الكود إلى وسيطة تُسمى IL - Intermediate Language، وهي لغة تشبه لغة التجميع (Assembly) لكنها غير مرتبطة بمعمارية معالجة (CPU Architecture) معينة.
- يتم تجميع هذا الكود الوسيط مع البيانات الوصفية (Metadata) داخل ملف Assembly بامتداد
.dllأو.exe. - يمكن أخذ هذا الملف نفسه وتشغيله على لينكس وويندوز وماك.
المرحلة الثانية: وقت التشغيل (Runtime)
عند فتح البرنامج، يتدخل الـ CLR (محرك تشغيل .NET):
- يقرأ الـ CLR ملف الـ Assembly الذي يحتوي على الـ IL.
- يستخدم مترجماً يُسمى JIT - Just-in-Time Compiler.
- يقوم الـ JIT بتحويل الـ IL إلى لغة الآلة الأصلية (Native) في نفس لحظة تشغيل البرنامج، ويحوّل فقط الأجزاء التي يحتاجها البرنامج في تلك اللحظة.
3. تشغيل البرنامج وملاحظة السلوك
نقوم بتشغيل البرنامج (Run) لنلاحظ سلوكه:
شكل (2): الواجهة الأولية للبرنامج
كما هو واضح في الصورة، توجد شخصية مكتوب فوقها “Let’s start with something easy!"، وأسفلها زر Decode. عند الضغط عليه، تتغير الصورة وتظهر بالشكل التالي:
شكل (3): الواجهة بعد الضغط على زر Decode
كما هو مبين، تظهر الفروق بوضوح في الصورة. المطلوب منا هنا هو معرفة النص المشفر الظاهر في الصورة.
4. التفكيك باستخدام ILSpy
كما أوضحنا أعلاه كيفية ترجمة برامج .NET، فإن هذا يسهل علينا الكثير من الأمور، مثل العمل على مُفكِّك (Decompiler) مع التأكد من أن الكود الذي يُخرجه هو نفسه الكود الأصلي. لذلك سنستخدم مُفكِّك ILSpy للتعامل مع هذا البرنامج.
شكل (4): فتح البرنامج داخل ILSpy
بما أن أمامنا نافذة (Form)، سنركز على الموارد (Resources) الموجودة داخلها:
شكل (5): الموارد الموجودة داخل الـ Form
كما هو واضح، يوجد ثلاثة ملفات، وأحدها مشبوه لأنه يحمل اللاحقة .encode، وهذا شيء يتعلق بالتشفير.
الآن، ننتقل إلى المنطق الأساسي (Core Logic) الذي يحتوي على خوارزمية التشفير، ولذلك نبحث في كود الـ Form:
شكل (6): استعراض كود الـ Form
يظهر لنا الكود التالي:
شكل (7): الكود الذي يظهر في نافذة ILSpy
دالة btnDecode_Click: هذا النمط هو كود زر Decode (ماذا يحدث عند الضغط عليه):
- أولاً، تُستبدل الصورة بصورة اسمها
bob_roge. - يُعرّف مصفوفة بايتات اسمها
dat_secret— وهي نفسها الموجودة في الـ Resources — وغالباً ما تكون هذه المصفوفة هي النص الأصلي الذي يتم تشفيره. - تُطبَّق خوارزمية التشفير.
هنا نلاحظ استخدام ما يُسمى بالتمويه (Obfuscation). سنركز فقط على المتغير text والخوارزمية المرتبطة به:
شكل (8): الخوارزمية في الكود بعد إزالة التمويه
تتألف الخوارزمية من تبديل الأنصاف (Nipple Swap) وعملية XOR:
- Nipple Swap: مثلاً، إذا كان لدينا البايت
0011 1100، بعد تطبيق عملية التبديل عليه يصبح1100 0011؛ أي أنه يقوم بتبديل النصف الأيمن بالنصف الأيسر. - بعد التبديل، يقوم بعملية XOR مع القيمة
0x29.
وما تبقى من الكود نتجاهله.
5. كتابة سكريبت الحل (Solver)
الآن، نقوم بتصدير ملف dat_secret من الـ Resources:
شكل (9): تصدير ملف dat_secret
سنطلب من أي نموذج ذكاء اصطناعي كتابة سكريبت بايثون (Solver) لفك تشفير ملف dat_secret:
def decode_dat_secret(file_path):
try:
with open(file_path, 'rb') as f:
dat_secret = f.read()
except FileNotFoundError:
print("لم يتم العثور على الملف. تأكد من أن ملف 'dat_secret' موجود في نفس المسار.")
return None
text = []
for b in dat_secret:
# (b >> 4) يجلب الجزء العلوي (High Nibble)
# ((b << 4) & 0xF0) يجلب الجزء السفلي (Low Nibble) ويرفعه للأعلى
dec_val = ((b >> 4) | ((b << 4) & 0xF0)) ^ 0x29
if dec_val == 0x00: # توقف عند المحرف الفارغ (Null Terminator)
break
text.append(chr(dec_val))
return "".join(text)
if __name__ == "__main__":
file_name = "dat_secret" # ضع مسار الملف هنا
result = decode_dat_secret(file_name)
print("--- النص بعد فك التشفير ---")
print(result)
يجب التأكد من أن ملف البايثون في نفس مسار ملف dat_secret.
نقوم بتشغيل سكريبت البايثون:
شكل (10): النص بعد فك التشفير
وبهذا نكون قد أنهينا حل التحدي بنجاح، ونستمر إن شاء الله.