
Fuzzer لبرامج تشغيل نواة لينكس
يحتوي هذا المستودع على جميع المصادر (بما في ذلك نصوص الإعداد) التي تحتاجها لتشغيل difuze.
Ubuntu >= 14.04.5 LTS
راجع ملف readme
كما هو موضح في بحثنا، هناك مكونان رئيسيان لـ difuze: استعادة الواجهة و محرك التعمية
تعتمد آلية استعادة الواجهة على تحليلات LLVM المارة. كل خطوة من خطوات استعادة الواجهة مكتوبة كممرات فردية. اتبع التعليمات أدناه حول كيفية تشغيل استعادة الواجهة.
تتولى هذه الخطوة تثبيت LLVM و c2xml:
أولاً، تأكد من أن لديك libxml (مطلوب لـ c2xml):
sudo apt-get install libxml2-dev
sudo pip install lxml
بعد ذلك، قمنا بإنشاء نص برمجي واحد، يقوم بتنزيل وبناء جميع الأدوات المطلوبة.
cd helper_scripts
python setup_difuze.py --help
usage: setup_difuze.py [-h] [-b TARGET_BRANCH] [-o OUTPUT_FOLDER]
optional arguments:
-h, --help show this help message and exit
-b TARGET_BRANCH Branch (i.e. version) of the LLVM to setup. Default:
release_38 e.g., release_38
-o OUTPUT_FOLDER Folder where everything needs to be setup.
مثال:
python setup_difuze.py -o difuze_deps
لإكمال الإعداد، تحتاج أيضًا إلى تعديلات في متغير البيئة PATH المحلي. سيعطيك نص الإعداد التغييرات الدقيقة التي تحتاجها.
يعتمد هذا على إتمام الإعداد بنجاح الإعداد. لدينا نص برمجي واحد يبني كل شيء، على الرحب والسعة.
cd InterfaceHandlers
./build.sh
يعتمد هذا على إتمام البناء بنجاح البناء. لتشغيل مكونات استعادة الواجهة على برامج تشغيل النواة، نحتاج أولاً إلى تحويل برامج التشغيل إلى كود LLVM bitcode.
أولاً، نحتاج إلى نواة قابلة للبناء. وهذا يعني أنه يجب أن تكون قادرًا على تجميع النواة باستخدام إعداد البناء المعتاد. أي make.
نلتقط أولاً مخرجات أمر make، ومن هذا المخرج نستخرج أمر التجميع الدقيق.
makebear make <جميع خيارات make>
bear make -j8سيؤدي هذا إلى إنشاء ملف compile_commands.json في الدليل الحالي.
ما عليك سوى تمرير V=1 وإعادة توجيه المخرجات إلى الملف.
مثال:
make V=1 O=out ARCH=arm64 > makeout.txt 2>&1
ملاحظة: لا تستخدم عمليات متعددة أي -j. سيؤدي تشغيل وضع المعالجة المتعددة إلى إفساد ملف المخرجات حيث تحاول عمليات متعددة الكتابة إلى ملف المخرجات.
هذا كل شيء. بعد ذلك، في الخطوة التالية، يأخذ نصنا البرمجي ملف makeout.txt الذي تم إنشاؤه ويقوم بتشغيل استعادة الواجهة على جميع برامج التشغيل المعترف بها.
جميع خطوات استعادة الواجهة المختلفة مغلفة في نص برمجي واحد helper_scripts/run_all.py
كيفية التشغيل:
cd helper_scripts
python run_all.py --help
usage: run_all.py [-h] [-l LLVM_BC_OUT] [-a CHIPSET_NUM] [-m MAKEOUT]
[-c COMPJSON] [-g COMPILER_NAME] [-n ARCH_NUM] [-o OUT]
[-k KERNEL_SRC_DIR] [-isclang] [-clangp CLANG_PATH]
[-llvmlinkp LLVMLINK_PATH] [-skb] [-skl] [-skp] [-skP]
[-ske] [-skI] [-ski] [-skv] [-skd] [-f IOCTL_FINDER_OUT]
optional arguments:
-h, --help show this help message and exit
-l LLVM_BC_OUT Destination directory where all the generated bitcode
files should be stored.
-a CHIPSET_NUM Chipset number. Valid chipset numbers are:
1(mediatek)|2(qualcomm)|3(huawei)|4(samsung)
-m MAKEOUT Path to the makeout.txt file.
-c COMPJSON Path to the compile_commands_json generated by Bear.
-g COMPILER_NAME Name of the compiler used in the makeout.txt, This is
needed to filter out compilation commands. Ex: aarch64
-linux-android-gcc
-n ARCH_NUM Destination architecture, 32 bit (1) or 64 bit (2).
-o OUT Path to the out folder. This is the folder, which
could be used as output directory during compiling
some kernels.
-k KERNEL_SRC_DIR Base directory of the kernel sources.
-isclang flag to indicate that clang was used to built the
kernel
-clangp CLANG_PATH Absolute path to the clang binary (if not provided,
the one available in the path will be used)
-llvmlinkp LLVMLINK_PATH
Absolute path to the llvm-link binary (if not
provided, the one available in the path will be used)
-skb Skip LLVM Build (default: not skipped).
-skl Skip Dr Linker (default: not skipped).
-skp Skip Parsing Headers (default: not skipped).
-skP Skip Generating Preprocessed files (default: not
skipped).
-ske Skip Entry point identification (default: not
skipped).
-skI Skip Generate Includes (default: not skipped).
-ski Skip IoctlCmdParser run (default: not skipped).
-skv Skip V4L2 ioctl processing (default: not skipped).
-skd Skip Device name finder (default: not skipped).
-f IOCTL_FINDER_OUT Path to the output folder where the ioctl command
finder output should be stored.
يقوم النص البرمجي ببناء وربط وتشغيل استعادة الواجهة على جميع برامج التشغيل المعترف بها، ونتيجة لذلك قد يستغرق وقتًا طويلاً (45 دقيقة - 90 دقيقة).
يقوم النص البرمجي أعلاه بالمهام التالية في وضع متعدد المعالجات للاستفادة من جميع أنوية وحدة المعالجة المركزية:
سيتم وضع جميع ملفات bitcode التي تم إنشاؤها في المجلد المقدم للوسيطة -l.
تستغرق هذه الخطوة وقتًا طويلاً، اعتمادًا على عدد الأنوية لديك.
لذا، إذا كنت قد أكملت هذه الخطوة بالفعل، يمكنك تخطيها بتمرير -skb.
يقوم هذا بإجراء الربط، حيث يمر عبر جميع ملفات bitcode ويحدد ملفات bitcode ذات الصلة التي يجب ربطها ويربطها (باستخدام llvm-link) في ملف bitcode موحد (سيتم تخزينه بجانب ملف bitcode المقابل).
على غرار الخطوة أعلاه، يمكنك تخطي هذه الخطوة بتمرير -skl.
تبحث هذه الخطوة عن إعلانات نقاط الدخول في ملفات الرأس وتخزن تكوينها في الملف: hdr_file_config.txt تحت دليل بناء LLVM.
للتخطي: -skp
تحدد هذه الخطوة جميع نقاط الدخول عبر جميع ملفات bitcode الموحدة لبرامج التشغيل.
سيتم تخزين المخرجات في ملف: entry_point_out.txt تحت دليل بناء LLVM.
مثال على محتويات ملف entry_point_out.txt:
IOCTL:msm_lsm_ioctl:/home/difuze/kernels/pixel/msm/sound/soc/msm/qdsp6v2/msm-lsm-client.c:msm_lsm_ioctl.txt:/home/difuze/pixel/llvm_out/sound/soc/msm/qdsp6v2/llvm_link_final/final_to_check.bc
IOCTL:msm_pcm_ioctl:/home/difuze/kernels/pixel/msm/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c:msm_pcm_ioctl.txt:/home/difuze/pixel/llvm_out/sound/soc/msm/qdsp6v2/llvm_link_final/final_to_check.bc
للتخطي: -ske
ستقوم هذه الخطوة بتشغيل مكون استعادة الواجهة الرئيسي (IoctlCmdParser) على جميع نقاط الدخول في ملف entry_point_out.txt. سيتم تخزين المخرجات لكل نقطة دخول في المجلد المقدم للخيار -f.
للتخطي: -ski
سنقدم الآن مثالاً من النقطة التي يكون لديك فيها مصادر النواة إلى نقطة الحصول على نتائج استعادة الواجهة.
لقد قمنا بتحميل نواة mediatek 33.2.A.3.123.tar.bz2. قم أولاً بتنزيل وفك ضغط الملف أعلاه.
لنفترض أنك قمت بفك ضغط الملف أعلاه في مجلد يسمى: ~/mediatek_kernel
قم بتثبيت Bear واتبع الخطوات أدناه:
cd ~/mediatek_kernel
source ./env.sh
cd kernel-3.18
# قد لا تكون الخطوة التالية ضرورية اعتمادًا على النواة
mkdir out
make O=out ARCH=arm64 tubads_defconfig
# توليد compile_commands.json
bear make -j8 O=out ARCH=arm64
cd <repo_path>/helper_scripts
python run_all.py -l ~/mediatek_kernel/llvm_bitcode_out -a 1 -c ~/mediatek_kernel/kernel-3.18/compile_commands.json -n 2 -o ~/mediatek_kernel/kernel-3.18/out -k ~/mediatek_kernel/kernel-3.18 -f ~/mediatek_kernel/ioctl_finder_out
يستغرق الأمر أعلاه بعض الوقت (30 دقيقة - ساعة واحدة).
أولاً، ستكون جميع نتائج التحليل في المجلد: ~/mediatek_kernel/ioctl_finder_out (الوسيطة المعطاة للخيار -f)، لكل نقطة دخول سيتم إنشاء ملف .txt يحتوي على جميع المعلومات حول الواجهة المستعادة.
إذا كنت مهتمًا بمعلومات حول الواجهة فقط ولا تهتم بأي شيء آخر، نوصي باستخدام النص البرمجي parse_interface_output.py. يقوم هذا النص البرمجي بتحويل المخرجات الفوضوية لتمرير استعادة الواجهة إلى ملفات json جميلة بتنسيق نظيف ومتسق.
cd <repo_path>/helper_scripts
python parse_interface_output.py <ioctl_finder_out_dir> <output_directory_for_json_files>
هنا <ioctl_finder_out_dir> يجب أن يكون نفس المجلد الذي قدمته للخيار -f و <output_directory_for_json_files> هو المجلد حيث سيتم إنشاء ملفات json.
يمكنك استخدام ملفات json المقابلة لاستعادة الواجهة لـ ioctl المقابل.
-g (فقط إذا كنت تستخدم makeout.txt)لتوفير قيمة للخيار -g تحتاج إلى معرفة اسم الثنائي *-gcc المستخدم لتجميع النواة.
طريقة سهلة لمعرفة ذلك هي البحث عن gcc في makeout.txt وسترى أوامر المترجم التي يمكنك من خلالها معرفة اسم الثنائي *-gcc.
على سبيل المثال، إذا قمت بـ grep gcc makeout.txt للبناء المثال، سترى الكثير من الأسطر مثل:
aarch64-linux-android-gcc -Wp,-MD,fs/jbd2/.transaction.o.d -nostdinc -isystem ...
لذا، يجب أن تكون قيمة -g هي aarch64-linux-android-gcc.
إذا كانت النواة المراد بناؤها 32 بت، فمن المرجح أن يكون الثنائي هو arm-eabi-gcc
بالنسبة لمجموعات شرائح Qualcomm (أو msm)، قد ترى *gcc-wrapper.py بدلاً من *.gcc، وفي هذه الحالة يجب عليك توفير *gcc-wrapper.py.
-aاعتمادًا على نوع مجموعة الشرائح، تحتاج إلى توفير الرقم المقابل.
-oهذا هو مسار المجلد المقدم للخيار O= لأمر make أثناء بناء النواة.
ليست كل النوى تحتاج إلى مسار مخرج منفصل. يمكنك بناء النواة بعدم توفير خيار O، وفي هذه الحالة يجب ألا تقدم قيمة لهذا الخيار أثناء تشغيل run_all.py.
بالنسبة للنوى المبنية باستخدام clang، بالإضافة إلى الخيارات أعلاه، يرجى تحديد الخيارات التالية (بافتراض أنك استخدمت compile_commands.json):
-isclang -clangp <PATH_TO_THE_CLANG_USED_TO_BUILD_THE_KERNEL> -llvmlinkp <PATH_TO_THE_LLVM_LINK (will be in the same folder as clang)>
قبل أن نبدأ في التعمية، نحتاج إلى معالجة المخرجات قليلاً باستخدام محللاتنا ذات الجودة البحثية (نعتذر).
توجد هذه هنا. النص البرمجي الرئيسي للتشغيل سيكون run_all.py:
$ python run_all.py --help
usage: run_all.py [-h] -f F -o O [-n {manual,auto,hybrid}] [-m M]
run_all options
optional arguments:
-h, --help show this help message and exit
-f F Filename of the ioctl analysis output OR the entire
output directory created by the system
-o O Output directory to store the results. If this
directory does not exist it will be created
-n {manual,auto,hybrid}
Specify devname options. You can choose manual
(specify every name manually), auto (skip anything that
we don't identify a name for), or hybrid (if we
detected a name, we use it, else we ask the user)
-m M Enable multi-device output most ioctls only have one
applicable device node, but some may have multiple. (0
to disable)
ستحتاج إلى تمرير -f إلى دليل مخرجات تحليل ioctl مثل ~/mediatek_kernel/ioctl_finder_out.
-o هو المكان الذي تريد تخزين النتائج المعالجة لاحقًا فيه. ستكون هذه ملفات XML سهلة الهضم (jpits).
-n يحدد النظام إلى أي درجة تريد الاعتماد على استعادة اسم الجهاز لدينا.
إذا كنت لا تريد القيام بأي عمل / بحث عن الأسماء، يمكنك تحديد auto.
هذا بالطبع يأتي بتكلفة تخطي أي جهاز لا نستعيد اسمه له. إذا كنت تريد أن تكون مرتابًا ولا تثق في أي من جهود الاستعادة لدينا (معقول تمامًا) يمكنك استخدام الخيار manual لتسمية كل جهاز بنفسك.
hybrid هو مزيج من الاثنين -- سنقوم بتسمية الجهاز لك عندما نستطيع، ونلجأ إليك عندما نفشل.
-m في بعض الأحيان يمكن أن تتوافق ioctls مع أكثر من جهاز (هذا شائع مع ioctls v4l2/subdev على سبيل المثال). الدعم لهذا ممكّن افتراضيًا، لكنه يتطلب تفاعل المستخدم لتحديد عدد الأجهزة لكل جهاز. إذا كان هذا مزعجًا جدًا بالنسبة لك، يمكنك تعطيل المطالبة بتمرير -m 0 (سنفترض جهازًا واحدًا لكل ioctl).
بعد التشغيل، يجب أن يكون لديك، في مجلد المخرجات الخاص بك، مجلد لكل ioctl.
MangoFuzz هو أداة تعمية أولية بسيطة لدينا وهو مبني على Peach (تحديدًا MozPeach).
إنها ليست أداة تعمية متطورة بشكل خاص لكنها تجد الأخطاء. كما تم بناؤها لتكون قابلة للتوسع بسهولة. هناك مكونان لهذه الأداة، محرك التعمية والمنفذ. يمكن العثور على المنفذ هنا، ومحرك التعمية هنا.
يعمل المنفذ على الهاتف، ويستمع للبيانات التي سيرسلها محرك التعمية إليه.
ما عليك سوى تجميعه لهندسة هاتفك، واستخدم adb push لدفعه إلى الهاتف، وتنفيذه مع المنفذ الذي تريد الاستماع عليه!
التفاعل مع MangoFuzz بسيط إلى حد ما. ستحتاج إلى كائن Engine وكائن Parser، ستغذي بهم محركك.
من هنا، تقوم بتحليل jpits باستخدام Parser الخاص بك، ثم تشغيل Engine. سهل!
لقد قدمنا بعض نصوص التشغيل البسيطة لبدء العمل.
لتشغيله ضد برامج تشغيل محددة، يمكنك استخدام runner.py على أحد مجلدات ioctl في دليل المخرجات (التي تم إنشاؤها بواسطة نصوص المعالجة اللاحقة).
مثال: ./runner.py -f honor8/out/chb -num 1000. هذا يخبر MangoFuzz بالتشغيل لمدة 1000 تكرار ضد جميع أزواج قيم أوامر ioctl المتعلقة بـ chb ioctl/driver.
إذا أردنا بدلاً من ذلك تشغيله ضد جهاز كامل (هاتف)، يمكنك استخدام dev_runner.py. مثال: ./dev_runner.py -f honor8/out -num 100.
سيستمر هذا في التكرار عبر ملفات برنامج التشغيل، والتبديل بينها عشوائياً لمدة 100 تكرار لكل منها.
لاحظ أنه قبل أن يتمكن محرك التعمية من الاتصال بالهاتف، ستحتاج إلى استخدام ADB لإعداد إعادة توجيه المنفذ مثل adb forward tcp:2022 tcp:2022