
التفحيم العشوائي للأنظمة المضمنة باستخدام نقاط التوقف العتادية
هذا هو الكود المصاحب للورقة: 'Fuzzing Embedded Systems using Debugger Interfaces'. يمكن العثور على نسخة مسبقة من الورقة هنا https://publications.cispa.saarland/3950/. يسمح الكود للمستخدمين بإعادة إنتاج وتوسيع النتائج المذكورة في الورقة. يرجى الاستشهاد بالورقة أعلاه عند الإبلاغ عن النتائج أو إعادة إنتاجها أو توسيعها.
.
├── benchmark # نصوص لبناء مجموعة اختبار المُعتّم من Google وتشغيل التجارب
├── dependencies # يحتوي على Makefile لتثبيت تبعيات GDBFuzz
├── evaluation # بيانات التجارب الأولية، المعروضة في الورقة
├── example_firmware # تطبيقات مضمنة مثال، مستخدمة للتقييم
├── example_programs # يحتوي على برنامج مترجم مثال وإعدادات لاختبار GDBFuzz
├── src # يحتوي على تنفيذ GDBFuzz
├── Dockerfile # لإنشاء صورة Docker مع تثبيت جميع تبعيات GDBFuzz
├── LICENSE # الترخيص
├── Makefile # Makefile لإنشاء صورة docker أو تثبيت GDBFuzz محلياً
└── README.md # ملف README هذا
فكرة GDBFuzz هي استغلال نقاط التوقف العتادية من المتحكمات الدقيقة كملاحظات للتعتيم الموجه بالتغطية. لذلك، يتم استخدام GDB كواجهة عامة لتمكين قابلية تطبيق واسعة. لتحليل الثنائي للبرامج الثابتة، يتم استخدام Ghidra. يحتوي الكود على إعداد معياري لتقييم الطريقة. بالإضافة إلى ذلك، يتم تضمين ملفات برامج ثابتة مثال.
يتيح GDBFuzz التعتيم الموجه بالتغطية للأنظمة المضمنة، ولكن - لأغراض التقييم - يمكنه أيضاً تعتيم تطبيقات المستخدم العشوائية. لتعتيم المتحكمات الدقيقة، نوصي بتثبيت محلي لـ GDBFuzz لتكون قادراً على إرسال بيانات التعتيم إلى الجهاز قيد الاختبار بسلاسة.
تم اختبار GDBFuzz على Ubuntu 20.04 LTS و Raspberry Pi OS 32-bit. المتطلبات الأساسية هي java و python3. أولاً، قم بإنشاء بيئة افتراضية جديدة وتثبيت جميع التبعيات.
virtualenv .venv
source .venv/bin/activate
make
chmod a+x ./src/GDBFuzz/main.py
يقرأ GDBFuzz الإعدادات من ملف تكوين بالمفاتيح التالية.
[SUT]
# المسار إلى ملف ثنائي للـ SUT.
# يمكن أن يكون، على سبيل المثال، ملف .elf أو ملف .bin.
binary_file_path = <path>
# عنوان العقدة الجذرية لـ CFG.
# يتم وضع نقاط التوقف عند عقد هذا CFG.
# على سبيل المثال: 'LLVMFuzzerTestOneInput' أو 'main'
entrypoint = <entrypoint>
# عدد المُدخلات التي يجب تنفيذها دون الوصول إلى نقطة توقف حتى
# يتم تدوير نقاط التوقف.
until_rotate_breakpoints = <number>
# أقصى عدد لنقاط التوقف التي يمكن وضعها في أي وقت معين.
max_breakpoints = <number>
# قائمة الوظائف التي يجب تجاهلها باللون الأسود.
# ignore_functions هي قائمة بأسماء الوظائف مفصولة بمسافات، على سبيل المثال: 'malloc free'.
ignore_functions = <space separated list>
# واحد من {Hardware, QEMU, SUTRunsOnHost}
# Hardware: مكون خارجي يبدأ خادم gdb ويمكن لـ GDBFuzz الاتصال بخادم gdb هذا.
# QEMU: يبدأ GDBFuzz QEMU. يقوم QEMU بمحاكاة binary_file_path ويبدأ gdbserver.
# SUTRunsOnHost: يبدأ GDBFuzz البرنامج المستهدف داخل GDB.
target_mode = <mode>
# اضبط هذا على False إذا كنت تريد بدء ghidra، وتحليل الـ SUT،
# وبدء جسر ghidra يدوياً.
start_ghidra = True
# قائمة مفصولة بمسافات من العناوين حيث يتم تعيين نقاط التوقف البرمجية (لكود
# معالجة الأخطاء). يعتبر تنفيذ هذه بمثابة تعطل.
# مثال: software_breakpoint_addresses = 0x123 0x432
software_breakpoint_addresses =
# ما إذا كانت جميع نقاط التوقف البرمجية التي تم تشغيلها تعتبر تعطلاً
consider_sw_breakpoint_as_error = False
[SUTConnection]
# الفئة 'SUT_connection_class' في الملف 'SUT_connection_path' تنفذ
# كيفية إرسال المُدخلات إلى الـ SUT.
# يمكن إرسال المُدخلات، على سبيل المثال، عبر Wi-Fi أو Serial أو Bluetooth أو ...
# يجب أن ترث هذه الفئة من ./connections/SUTConnection.py.
# راجع ./connections/SUTConnection.py لمزيد من المعلومات.
SUT_connection_file = FIFOConnection.py
[GDB]
path_to_gdb = gdb-multiarch
# مكتوب بالشكل address:port
gdb_server_address = localhost:4242
[Fuzzer]
# بالبايت
maximum_input_length = 100000
# بالثواني
single_run_timeout = 20
# بالثواني
total_runtime = 3600
# اختياري
# المسار إلى دليل يحتوي كل ملف فيه على بذرة واحدة. إذا كنت لا ترغب في
# استخدام البذور، اترك القيمة فارغة.
seeds_directory =
[BreakpointStrategy]
# الاستراتيجيات لاختيار الكتل الأساسية موجودة في
# 'src/GDBFuzz/breakpoint_strategies/'
# بالنسبة للورقة نستخدم الاستراتيجيات التالية
# 'RandomBasicBlockStrategy.py' - اختيار عشوائي للكتل الأساسية غير الموصولة
# 'RandomBasicBlockNoDomStrategy.py' - مثل السابقة، لكن لا تستخدم علاقات الهيمنة لاشتقاق العقد الموصولة بشكل متعدي.
# 'RandomBasicBlockNoCorpusStrategy.py' - مثل الأولى، لكن تمنع نمو مجموعة المُدخلات وبالتالي تتصرف مثل التعتيم الصندوق الأسود مع قياس التغطية.
# 'BlackboxStrategy.py' - لا تحدد أي نقاط توقف
breakpoint_strategy_file = RandomBasicBlockStrategy.py
[Dependencies]
path_to_qemu = dependencies/qemu/build/x86_64-linux-user/qemu-x86_64
path_to_ghidra = dependencies/ghidra
[LogsAndVisualizations]
# واحد من {DEBUG, INFO, WARNING, ERROR, CRITICAL}
loglevel = INFO
# المسار إلى دليل حيث يتم تخزين ملفات الإخراج (مثل الرسوم البيانية، سجلات).
output_directory = ./output
# إذا تم ضبطه على True، يرسل عميل MQTT عناصر واجهة المستخدم (مثل الرسوم البيانية)
enable_UI = False
يوجد ملف تكوين مثال في ./example_programs/ مع برنامج مثال تم تجميعه باستخدام أداة ربط التعتيم الخاصة بنا في benchmark/benchSUTs/GDBFuzz_wrapper/common/.
ابدأ التعتيم لمدة ساعة بالأمر التالي.
chmod a+x ./example_programs/json-2017-02-12
./src/GDBFuzz/main.py --config ./example_programs/fuzz_json.cfg
نرى أولاً مخرجات من Ghidra وهي تحلل الملف التنفيذي الثنائي ثم رسائل عندما يتم إعادة وضع نقاط التوقف أو الوصول إليها.
اعتماداً على output_directory المحدد في ملف التكوين، يجب أن يظهر الآن مجلد trial-0 بالهيكل التالي
.
├── corpus # مجلد يحتوي على مجموعة المُدخلات.
├── crashes # مجلد يحتوي على المُدخلات المتسببة في تعطل - إن وجدت.
├── cfg # رسم بياني لتدفق التحكم كقائمة تجاور.
├── fuzzer_stats # إحصائيات حملة التعتيم.
├── plot_data # جدول يوضح في أي وقت نسبي في حملة التعتيم تم الوصول إلى أي كتلة أساسية.
├── reverse_cfg # رسم بياني عكسي لتدفق التحكم.
بتعيين start_ghidra = False في ملف التكوين، يتصل GDBFuzz بمثيل Ghidra يعمل في وضع واجهة المستخدم الرسومية. لذلك، يجب بدء تشغيل إضافة ghidra_bridge يدوياً من مدير البرامج النصية. أثناء التعتيم، يتم تمييز كتل البرنامج التي تم الوصول إليها باللون الأخضر.
لتعتيم تطبيقات مستخدم Linux، يستفيد GDBFuzz من نقطة الدخول القياسية LLVMFuzzOneInput المستخدمة من قبل معظم المعتمين مثل AFL و AFL++ و libFuzzer و...
في benchmark/benchSUTs/GDBFuzz_wrapper/common يوجد غلاف يمكن استخدامه لتجميع أي أداة ربط تعتيم متوافقة في برنامج مستقل يجلب المُدخلات عبر أنبوب مسمى في /tmp/fromGDBFuzz.
يسمح هذا بمحاكاة جهاز مضمن يستهلك البيانات عبر واجهة إدخال محددة جيداً وبالتالي تشغيل GDBFuzz على أي تطبيق. للراحة، أنشأنا نصاً في benchmark/benchSUTs يقوم بتجميع جميع البرامج من تقييمنا مع غلافنا كما هو موضح لاحقاً.
ملاحظة: ليس المقصود من GDBFuzz تعتيم تطبيقات مستخدم Linux. استخدم AFL++ أو معتمين آخرين لذلك. الغلاف موجود فقط لأغراض التقييم لتمكين تشغيل المعايير والمقارنات على نطاق واسع!
يتم عرض الفعالية العامة لنهجنا في معيار واسع النطاق يتم نشره كحاويات docker.
make dockerimage
لتشغيل التجربة أعلاه في حاوية docker (لمدة ساعة كما هو محدد في ملف التكوين)، قم بتعيين example_programs و output كوحدات تخزين وابدأ GDBFuzz كما يلي.
chmod a+x ./example_programs/json-2017-02-12
docker run -it --env CONFIG_FILE=/example_programs/fuzz_json_docker_qemu.cfg -v $(pwd)/example_programs:/example_programs -v $(pwd)/output:/output gdbfuzz:1.0
يجب أن يظهر مجلد إخراج في دليل العمل الحالي بالهيكل الموضح أعلاه.
ينقسم تقييمنا إلى جزئين.