
التفحيم العشوائي للأنظمة المضمنة باستخدام نقاط التوقف العتادية
هذا هو الكود المصاحب للورقة: '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
يجب أن يظهر مجلد إخراج في دليل العمل الحالي بالهيكل الموضح أعلاه.
ينقسم تقييمنا إلى جزئين.
يمكن لـ GDBFuzz العمل مع أي خادم GDB وبالتالي معظم مسببات التصحيح للمتحكمات الدقيقة.
فيما يتعلق بـ RQ1 من الورقة، نقوم بتنفيذ GDBFuzz على متحكمات دقيقة مختلفة مع برامج ثابتة مختلفة موجودة في example_firmware.
لكل تجربة نقوم بتشغيل GDBFuzz باستراتيجية RandomBasicBlock واستراتيجية RandomBasicBlockNoCorpus. تتصرف الأخيرة مثل التعتيم بدون ملاحظات، لكننا لا نزال نستطيع قياس التغطية المحققة.
للإجابة على RQ1، نقارن التغطية المحققة لاستراتيجية RandomBasicBlock واستراتيجية RandomBasicBlockNoCorpus.
ملفات التكوين الخاصة موجودة في المجلدات الفرعية المقابلة وسنشرح الآن كيفية إعداد التعتيم على لوحات التطوير الأربعة.
يتطلب GDBFuzz الوصول إلى خادم GDB. في هذه الحالة، يتم استخدام B-L4S5I-IOT01A ومصحح الأخطاء المدمج على اللوحة. يقوم مصحح الأخطاء المدمج هذا بإعداد خادم GDB عبر برنامج 'st-util'، ويتيح الوصول إلى خادم GDB هذا عبر localhost:4242.
sudo apt-get install stlink-tools gdb-multiarch
قم ببناء وبرمجة برنامج ثابت للوحة STM32 B-L4S5I-IOT01A، على سبيل المثال مشروع arduinojson.
المتطلب السابق: قم بتثبيت platformio (pio)
cd ./example_firmware/stm32_disco_arduinojson/
pio run --target upload
لمعلوماتك: قام platformio بتخزين ملف .elf للـ SUT هنا: ./example_firmware/stm32_disco_arduinojson/.pio/build/disco_l4s5i_iot01a/firmware.elf سيتم استخدام ملف .elf هذا لاحقاً في تكوين المستخدم لـ Ghidra.
ابدأ محطة طرفية جديدة، وقم بتشغيل الأمر التالي لبدء خادم GDB:
st-util
قم بتشغيل GDBFuzz مع تكوين مستخدم لـ arduinojson. يمكننا إرسال البيانات عبر منفذ USB إلى المتحكم الدقيق. يقوم المتحكم الدقيق بإعادة توجيه هذه البيانات عبر المنفذ التسلسلي إلى الـ SUT'. في حالتنا، /dev/ttyACM0 هو جهاز USB للوحة المتحكم الدقيق. إذا قام نظامك بتعيين جهاز آخر للوحة المتحكم الدقيق، قم بتغيير /dev/ttyACM0 في ملف التكوين إلى جهازك.
./src/GDBFuzz/main.py --config ./example_firmware/stm32_disco_arduinojson/fuzz_serial_json.cfg
إحصائيات وسجلات المُعتّم موجودة في دليل ./output/...
قم بتثبيت pyocd:
pip install --upgrade pip 'mbed-ls>=1.7.1' 'pyocd>=0.16'
تأكد من وجود 'KitProg v3' على الجهاز ووضع اللوحة في وضع 'Arm DAPLink' بالضغط على الزر المناسب. ابدأ خادم GDB:
pyocd gdbserver --persist
قم ببرمجة برنامج ثابت وابدأ التعتيم على سبيل المثال بـ
gdb-multiarch
target remote :3333
load ./example_firmware/CY8CKIT_json/mtb-example-psoc6-uart-transmit-receive.elf
monitor reset
./src/GDBFuzz/main.py --config ./example_firmware/CY8CKIT_json/fuzz_serial_json.cfg
قم ببناء وبرمجة برنامج ثابت لـ ESP32، على سبيل المثال مثال arduinojson باستخدام platformio.
cd ./example_firmware/esp32_arduinojson/
pio run --target upload
أضف السطر التالي إلى ملف تكوين openocd لمصحح J-Link: jlink.cfg
adapter speed 10000
ابدأ محطة طرفية جديدة، وقم بتشغيل الأمر التالي لبدء خادم GDB:
get_idf
openocd -f interface/jlink.cfg -f target/esp32.cfg -c "telnet_port 7777" -c "gdb_port 8888"
قم بتشغيل GDBFuzz مع تكوين مستخدم لـ arduinojson. يمكننا إرسال البيانات عبر منفذ USB إلى المتحكم الدقيق. يقوم المتحكم الدقيق بإعادة توجيه هذه البيانات عبر المنفذ التسلسلي إلى الـ SUT'. في حالتنا، /dev/ttyUSB0 هو جهاز USB للوحة المتحكم الدقيق. إذا قام نظامك بتعيين جهاز آخر للوحة المتحكم الدقيق، قم بتغيير /dev/ttyUSB0 في ملف التكوين إلى جهازك.
./src/GDBFuzz/main.py --config ./example_firmware/esp32_arduinojson/fuzz_serial.cfg
إحصائيات وسجلات المُعتّم موجودة في دليل ./output/...
قم بتثبيت TI MSP430 GCC من https://www.ti.com/tool/MSP430-GCC-OPENSOURCE
ابدأ خادم GDB
./gdb_agent_console libmsp430.so
أو (أكثر استقراراً). قم ببناء mspdebug من https://github.com/dlbeer/mspdebug/ واستخدم:
until mspdebug --fet-skip-close --force-reset tilib "opt gdb_loop True" gdb ; do sleep 1 ; done
يفشل Ghidra في تحليل الثنائيات لوحدة التحكم TI MSP430 بشكل افتراضي. لإصلاح ذلك، نقوم باستيراد الملف في واجهة المستخدم الرسومية لـ Ghidra، واختيار MSP430X كبنية وتخطي التحليل التلقائي. بعد ذلك، نفتح 'جدول الرموز'، ونرتبها حسب الاسم ونحذف جميع الرموز بأسماء مثل $C$L*. الآن يمكن تنفيذ التحليل التلقائي. بعد التحليل، ابدأ جسر ghidra من واجهة المستخدم الرسومية لـ Ghidra يدوياً ثم ابدأ GDBFuzz.
./src/GDBFuzz/main.py --config ./example_firmware/msp430_arduinojson/fuzz_serial.cfg
للوصول إلى أجهزة USB كمستخدم غير جذري باستخدام pyusb نضيف قواعد مناسبة إلى udev. ألصق الأسطر التالية في /etc/udev/rules.d/50-myusb.rules:
SUBSYSTEM=="usb", ATTRS{idVendor}=="1234", ATTRS{idProduct}=="5678" GROUP="usbusers", MODE="666"
أعد تحميل udev:
sudo udevadm control --reload
sudo udevadm trigger
في RQ2 من الورقة، نقارن GDBFuzz مع النهج القائم على المحاكاة Fuzzware. أولاً نقوم بتنفيذ GDBFuzz و Fuzzware كما هو موصوف سابقاً على ملفات البرامج الثابتة المرفقة. لكل تجربة GDBFuzz، نقوم بإنشاء ملف بالكتل الأساسية الصالحة من ملفات رسم بياني لتدفق التحكم كما يلي:
cut -d " " -f1 ./cfg > valid_bbs.txt
الآن يمكننا إعادة تشغيل التغطية مقابل نتيجة fuzzware fuzzware genstats --valid-bb-file valid_bbs.txt
عند العثور على مُدخلات متسببة في تعطل أو تعليق، يتم تخزينها في مجلد crashes. أثناء التقييم، وجدنا الأخطاء الثلاثة التالية:
يمكن لـ GDBFuzz أيضاً العمل على مضيف Raspberry Pi مع تعديلات طفيفة:
في الملف ./dependencies/ghidra/support/launch.sh:125 يجب ترميز متغير JAVA_HOME بشكل ثابت، على سبيل المثال إلى JAVA_HOME="/usr/lib/jvm/default-java"
لتعتيم البرامج على لوحات أخرى، يتطلب GDBFuzz
src/GDBFuzz/connections) تؤدي إلى تنفيذ الكود عند نقطة الدخول، على سبيل المثال اتصال تسلسلي.يجب تحديد كل هذه الخصائص في ملف التكوين.
بالنسبة لـ RQ 4 إلى 8 نقوم بتشغيل معيار واسع النطاق.
أولاً، قم ببناء صورة Docker كما هو موصوف سابقاً وقم بتجميع التطبيقات من مجموعة اختبار المُعتّم من Google باستخدام أداة ربط التعتيم الخاصة بنا في benchmark/benchSUTs/GDBFuzz_wrapper/common.
cd ./benchmark/benchSUTs
chmod a+x setup_benchmark_SUTs.py
make dockerbenchmarkimage
بعد ذلك، قم بتعديل إعدادات المعيار في benchmark/scripts/benchmark.py و benchmark/scripts/benchmark_aflpp.py حسب متطلباتك (خاصة number_of_cores و trials و seconds_per_trial) وابدأ المعيار بـ:
cd ./benchmark/scripts
./benchmark.py $(pwd)/../benchSUTs/SUTs/ SUTs.json
./benchmark_aflpp.py $(pwd)/../benchSUTs/SUTs/ SUTs.json
يظهر مجلد في ./benchmark/scripts يحتوي على ملفات رسم بياني (التغطية بمرور الوقت)، وملفات إحصائيات المُعتّم، وملفات رسم بياني لتدفق التحكم لكل تجربة كما في evaluation/fuzzer_test_suite_qemu_runs.
يحتوي GDBFuzz على ميزة اختيارية حيث يرسم رسم بياني لتدفق التحكم للعقد المغطاة. هذا معطل بشكل افتراضي. يمكنك تمكينه باتباع تعليمات هذا القسم وتعيين 'enable_UI' إلى 'True' في تكوين المستخدم.
على المضيف:
قم بالتثبيت
sudo apt-get install graphviz
قم بتثبيت إصدار حديث من node، على سبيل المثال الخيار 2 من هنا. استخدم الخيار 2 وليس الخيار 1. يجب أن يقوم هذا بتثبيت كل من node و npm. كمرجع، أرقام الإصدارات لدينا هي (لكن الإصدارات الأحدث يجب أن تعمل أيضاً):
➜ node --version
v16.9.1
➜ npm --version
7.21.1
قم بتثبيت تبعيات واجهة المستخدم على الويب:
cd ./src/webui
npm install
قم بتثبيت وسيط MQTT mosquitto، على سبيل المثال راجع هنا
قم بتحديث تكوين وسيط mosquitto: استبدل الملف /etc/mosquitto/conf.d/mosquitto.conf بالمحتوى التالي:
listener 1883
allow_anonymous true
listener 9001
protocol websockets
أعد تشغيل وسيط mosquitto:
sudo service mosquitto restart
تحقق من أن وسيط mosquitto قيد التشغيل:
sudo service mosquitto status
يجب أن يتضمن الإخراج النص 'Active: active (running)'
ابدأ واجهة المستخدم على الويب:
cd ./src/webui
npm start
يجب أن يفتح متصفح الويب الخاص بك تلقائياً على 'http://localhost:3000/'.
ابدأ GDBFuzz واستخدم ملف تكوين مستخدم حيث يكون enable_UI مضبوطاً على True. يمكنك استخدام حاوية Docker و SUT arduinojson من أعلاه. لكن تأكد من تعيين 'enable_UI' على 'True'.
العقد المغطاة باللون 'الأزرق' هي المغطاة. العقد البيضاء غير مغطاة. نعرض فقط العقد غير المغطاة إذا كانت العقدة الأم مغطاة (رسم رسم بياني كامل لتدفق التحكم يستغرق وقتاً طويلاً إذا كان الرسم البياني كبيراً).
GDBFuzz مفتوح المصدر بموجب ترخيص AGPL-3.0. راجع ملف LICENSE للحصول على التفاصيل.
للحصول على قائمة بالمكونات مفتوحة المصدر الأخرى المضمنة في GDBFuzz، راجع الملف 3rd-party-licenses.txt.