
american fuzzy lop - أداة تشويش موجَّهة للأمان
طُوِّر في الأصل بواسطة Michal Zalewski [email protected].
انظر QuickStartGuide.txt إذا لم يكن لديك الوقت لقراءة هذا الملف.
الاختبار العشوائي (Fuzzing) هو أحد أقوى الاستراتيجيات وأكثرها إثباتًا لتحديد المشكلات الأمنية في البرمجيات الواقعية؛ وهو مسؤول عن الغالبية العظمى من ثغرات تنفيذ التعليمات البرمجية عن بُعد وثغرات تصعيد الامتيازات المكتشفة حتى الآن في البرمجيات الحساسة أمنيًا.
لسوء الحظ، فإن الاختبار العشوائي ضحل نسبيًا أيضًا؛ فالطفرات العشوائية العمياء تجعل من غير المرجح للغاية الوصول إلى مسارات برمجية معيّنة في الكود المُختبر، تاركةً بعض الثغرات بعيدة تمامًا عن متناول هذه التقنية.
كانت هناك محاولات عديدة لحل هذه المشكلة. أحد الأساليب المبكرة - التي ابتكرها تافيس أورماندي (Tavis Ormandy) - هو تقطير المجموعة (corpus distillation). تعتمد هذه الطريقة على إشارات التغطية لاختيار مجموعة فرعية من البذور المثيرة للاهتمام من مجموعة ضخمة عالية الجودة من الملفات المرشحة، ثم اختبارها عشوائيًا بالوسائل التقليدية. يعمل هذا النهج بشكل استثنائي، لكنه يتطلب أن تكون هذه المجموعة متاحة بسهولة. بالإضافة إلى ذلك، فإن قياسات تغطية الكتل لا توفر سوى فهمًا مبسطًا للغاية لحالة البرنامج، وتكون أقل فائدة لتوجيه جهود الاختبار العشوائي على المدى الطويل.
أما الأبحاث الأخرى الأكثر تطورًا فقد ركّزت على تقنيات مثل تحليل تدفق البرنامج (التنفيذ المختلط "concolic execution")، والتنفيذ الرمزي (symbolic execution)، أو التحليل الثابت (static analysis). جميع هذه الأساليب واعدة للغاية في البيئات التجريبية، لكنها تميل إلى المعاناة من مشاكل في الموثوقية والأداء في الاستخدامات العملية - ولا تقدم حاليًا بديلًا قابلاً للتطبيق عن تقنيات الاختبار العشوائي "الغبية" (dumb fuzzing).
American Fuzzy Lop هي أداة اختبار عشوائي تعمل بالقوة الغاشمة، مقترنة بخوارزمية جينية موجّهة بأدوات القياس بسيطة للغاية ولكنها صلبة كالصخر. تستخدم شكلاً معدلاً من تغطية الحواف (edge coverage) لالتقاط التغييرات الدقيقة على المستوى المحلي في تدفق تحكم البرنامج بسهولة.
بتبسيط الأمور قليلاً، يمكن تلخيص الخوارزمية العامة على النحو التالي:
قم بتحميل حالات الاختبار الأولية التي يوفرها المستخدم إلى قائمة الانتظار،
خذ ملف الإدخال التالي من قائمة الانتظار،
حاول اقتطاع حالة الاختبار إلى أصغر حجم لا يغيّر السلوك المُقاس للبرنامج،
قم بتغيير الملف بشكل متكرر باستخدام مجموعة متوازنة ومدروسة جيدًا من استراتيجيات الاختبار العشوائي التقليدية،
إذا نتج عن أي من الطفرات المُولّدة انتقال حالة جديد تم تسجيله بواسطة أدوات القياس، أضف المخرجات المطفّرة كإدخال جديد في قائمة الانتظار.
انتقل إلى الخطوة 2.
كما يتم أيضًا استبعاد حالات الاختبار المكتشفة بشكل دوري للتخلص من الحالات التي أصبحت قديمة بسبب الاكتشافات الأحدث ذات التغطية الأعلى؛ وتخضع للعديد من خطوات تقليل الجهد الأخرى الموجّهة بأدوات القياس.
كنتيجة جانبية لعملية الاختبار العشوائي، تنشئ الأداة مجموعة صغيرة مكتفية ذاتيًا من حالات الاختبار المثيرة للاهتمام. هذه الحالات مفيدة للغاية لتغذية أنظمة اختبار أخرى مكلفة من حيث الجهد أو الموارد - على سبيل المثال، لاختبار الإجهاد (stress-testing) للمتصفحات، وتطبيقات المكاتب، وحزم الرسوميات، أو الأدوات مغلقة المصدر.
تم اختبار أداة الاختبار العشوائي بدقة لتقديم أداء فوري يتفوق بكثير على الاختبار العشوائي الأعمى أو أدوات التغطية فقط.
عند توافر الكود المصدري، يمكن حقن أدوات القياس بواسطة أداة مساعدة تعمل كبديل مباشر لـ gcc أو clang في أي عملية بناء قياسية لكود من طرف ثالث.
تأثير أدوات القياس على الأداء متواضع إلى حد ما؛ وبالتزامن مع التحسينات الأخرى التي تنفذها afl-fuzz، يمكن اختبار معظم البرامج عشوائيًا بسرعة مماثلة أو حتى أسرع مما هو ممكن باستخدام الأدوات التقليدية.
قد تختلف الطريقة الصحيحة لإعادة ترجمة البرنامج المستهدف وفقًا لتفاصيل عملية البناء، لكن النهج شبه الشامل سيكون:```shell $ CC=/path/to/afl/afl-gcc ./configure $ make clean all
بالنسبة لبرامج C++، قد ترغب أيضًا في تعيين `CXX=/path/to/afl/afl-g++`.
يمكن استخدام أغلفة clang (afl-clang وafl-clang++) بنفس الطريقة؛
قد يختار مستخدمو clang أيضًا الاستفادة من وضع قياس عالي الأداء،
كما هو موصوف في llvm_mode/README.llvm.
عند اختبار المكتبات، تحتاج إلى إيجاد أو كتابة برنامج بسيط يقرأ
البيانات من stdin أو من ملف ويمررها إلى المكتبة المختبرة. في مثل هذه
الحالة، من الضروري ربط هذا الملف التنفيذي بنسخة ثابتة من
المكتبة المُجهَّزة، أو التأكد من تحميل ملف .so الصحيح في
وقت التشغيل (عادةً عن طريق تعيين `LD_LIBRARY_PATH`). الخيار الأبسط هو بناء
ثابت، وعادةً ما يكون ممكنًا عبر:```shell
$ CC=/path/to/afl/afl-gcc ./configure --disable-shared
عند استدعاء make، يؤدي ضبط AFL_HARDEN=1 إلى جعل غلاف المترجم (CC wrapper) يفعّل تلقائيًا خيارات تحصين الكود التي تسهّل اكتشاف أخطاء الذاكرة البسيطة. كما يمكن أن تساعد مكتبة Libdislocator، وهي مكتبة مساعدة مرفقة مع AFL (انظر libdislocator/README.dislocator)، في كشف مشاكل تلف الكومة (heap corruption) أيضًا.
ملاحظة: يُنصح مستخدمو ASAN بمراجعة ملف notes_for_asan.txt للاطلاع على تحذيرات مهمة.
عندما لا يكون الكود المصدري متوفرًا، يوفّر المُختبر (fuzzer) دعمًا تجريبيًا لإضافة أجهزة القياس بسرعة وفورية على الثنائيات ذات الصندوق الأسود (black-box binaries). ويتم ذلك عبر نسخة من QEMU تعمل في وضع "محاكاة فضاء المستخدم" (user space emulation) الأقل شهرة.
QEMU مشروع مستقل عن AFL، لكن يمكنك بسهولة بناء هذه الميزة عبر تنفيذ ما يلي:```shell $ cd qemu_mode $ ./build_qemu_support.sh
للحصول على تعليمات وملاحظات إضافية، راجع qemu_mode/README.qemu.
يكون هذا الوضع أبطأ بنحو 2-5x من التنفيذ الآلي في وقت الترجمة (compile-time instrumentation)، وهو أقل ملاءمة للتوازي، وقد يكون له بعض الخصائص الأخرى.
## 5) اختيار حالات الاختبار الأولية
لكي يعمل المزيل (fuzzer) بشكل صحيح، يتطلب ملف بداية واحدًا أو أكثر يحتوي على مثال جيد لبيانات الإدخال التي يتوقعها التطبيق المستهدف عادةً. هناك قاعدتان أساسيتان:
- اجعل الملفات صغيرة. أقل من 1 كيلوبايت هو المثالي، وإن لم يكن ضروريًا تمامًا.
لمناقشة سبب أهمية الحجم، راجع [perf_tips.txt](https://github.com/google/afl/blob/master/docs/perf_tips.txt).
- استخدم حالات اختبار متعددة فقط إذا كانت مختلفة وظيفيًا عن بعضها البعض.
لا فائدة من استخدام خمسين صورة إجازة مختلفة لاختبار مكتبة صور.
يمكنك العثور على العديد من الأمثلة الجيدة لملفات البداية في المجلد الفرعي testcases/ المرفق مع هذه الأداة.
ملاحظة: إذا كانت لديك مجموعة كبيرة من البيانات المتاحة للفحص، فقد ترغب في استخدام أداة afl-cmin لتحديد مجموعة فرعية من الملفات المختلفة وظيفيًا التي تمر بمسارات برمجية مختلفة في البرنامج الهدف.
## 6) اختبار الملفات الثنائية (Fuzzing binaries)
تتم عملية الاختبار نفسها بواسطة أداة afl-fuzz. يتطلب هذا البرنامج دليلًا للقراءة فقط يحتوي على حالات الاختبار الأولية، ومكانًا منفصلًا لتخزين النتائج، بالإضافة إلى مسار إلى الملف الثنائي المراد اختباره.
بالنسبة للملفات الثنائية الهدف التي تقبل الإدخال مباشرة من stdin، تكون الصيغة المعتادة كما يلي:```shell
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program [...params...]
بالنسبة للبرامج التي تأخذ المدخلات من ملف، استخدم '@@' لتحديد الموضع في سطر أوامر الهدف حيث يجب وضع اسم ملف الإدخال. ستقوم أداة fuzzer باستبدال هذا نيابة عنك:```shell $ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@
يمكنك أيضًا استخدام الخيار -f لكتابة البيانات المتحولة إلى ملف محدد. هذا مفيد إذا كان البرنامج يتوقع امتداد ملف معين أو ما شابه.
يمكن فحص الملفات الثنائية غير المُجهزة (non-instrumented) في وضع QEMU (أضف -Q في سطر الأوامر) أو في وضع المُشوش الأعمى التقليدي (حدد -n).
يمكنك استخدام الخيارين -t و -m لتجاوز المهلة الزمنية الافتراضية وحد الذاكرة للعملية المنفذة؛ من الأمثلة النادرة على الأهداف التي قد تحتاج إلى تعديل هذه الإعدادات: المترجمات (compilers) ومفككات الفيديو (video decoders).
نصائح تحسين أداء التشويش مذكورة في [perf_tips.txt](https://github.com/google/afl/blob/master/docs/perf_tips.txt).