
أداة تطعيم ثنائي ثابتة للملفات التنفيذية لويندوز بمعمارية x64
peafl64 هي أداة إدراج برمجي ثابت (static instrumentation) لملفات PE بصيغة x64 في ويندوز.
الإدراج البرمجي الثابت هو ممارسة تعديل الملفات التنفيذية وإضافة كود إلى مواقع محددة فيها.
يضيف الإدراج البرمجي كودًا إلى بداية كل كتلة أساسية (basic block) في الملف الثنائي، مسجلًا تسلسل التنفيذ بطريقة متوافقة مع AFL.
يتيح لنا ذلك اختبار التشويش (fuzzing) على الملفات الثنائية في وضع المستخدم (باستخدام WinAFL) وفي وضع النواة (باستخدام kAFL) دون الوصول إلى الكود المصدري لها.
هناك طرق أخرى لاختبار التشويش على ملفات ويندوز الثنائية؛ لكننا اخترنا في هذا المشروع التركيز على الإدراج البرمجي الثابت لأنه الأسرع.
يعتمد هذا المشروع على أداة pe-afl من wmliang، مع إضافة دعم x64.
يتطلب سكربت الإدراج البرمجي مخرجات تحليل من IDA.
لإنشائها، قم بتشغيل السكربت المرفق ida_dumper.py داخل IDA.
يتطلب السكربت IDA 7+ و python3.8+.
usage: pe_afl.py [-h] [-n] [-cb] [-tf] [-te THREAD_ENTRY] [-nt NTOSKRNL] [-e ENTRY] [-l ENLARGE] [-v] [-lf] pefile ida_dump
positional arguments:
pefile Target PE file for instrumentation
ida_dump dump.json from IDA (created by ida_dumper.py)
optional arguments:
-h, --help show this help message and exit
-n, --nop Instrument with NOPs for testing
-cb, --callback Instrument with a callback, which is in the helper driver that's written in C
-tf, --thread-filter Driver instrumentation that filters only on thread ID (must use "-te" with this option)
-te THREAD_ENTRY, --thread-entry THREAD_ENTRY
The address (RVA) of the thread's initialization function
-nt NTOSKRNL, --ntoskrnl NTOSKRNL
ntoskrnl.exe path for offset extraction (non-optional if instrumenting a driver)
-e ENTRY, --entry ENTRY
Inject code on entry point, ie. -e9090
-l ENLARGE, --enlarge ENLARGE
Enlarge factor for sections, default=4
-v, --verbose Print debug log
-lf, --logfile Print log to pe-afl64.log rather than stream
تجهيز ملف ثنائي في وضع المستخدم باستخدام NOPs
PS pe-afl-64> python .\pe_afl.py -n C:\Work\cmd.exe C:\Work\cmd.exe.dump.json
[*] User-mode binary is being instrumented
[*] Single-thread instrument is on
[*] Preparing new sections
[*] Added section .text^
[*] Added section .cov
[*] Expanding relative jumps
[*] Expanded 3874 of 14353 branches
[*] Building address map
[*] Updating relative instructions
[*] Updating relocations...
[*] Updating Export table
[*] Updating load config
[*] Updating exception records
[*] Updating the PE headers
[*] Finalizing...
[*] Creating instrumented code
[*] Writing address mapping to C:\Work\cmd.exe.mapping.txt
[*] Updating jump tables
[*] Updated .text
...
[*] Removing temporary PE files
[*] Fixing PE checksum
[*] Instrumented binary saved to: C:\Work\cmd.instrumented.exe
تجهيز ملف ثنائي في وضع النواة مع التصفية حسب معرف العملية
python .\pe_afl.py -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\driver.sys" "C:\Work\driver.sys.dump.json"
تجهيز ملف ثنائي في وضع النواة مع التصفية حسب معرف الخيط وإخراج مفصّل
python .\pe_afl.py -v -tf -te 0x40000 -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe.dump.json"
أولًا، ستحتاج إلى التحقق مما إذا كان جهازك يُقلع باستخدام BIOS أو UEFI.
بالنسبة لأجهزة Hyper-V: تعتمد أجهزة الجيل الأول (Gen 1) على BIOS، بينما تعتمد أجهزة الجيل الثاني (Gen 2) على UEFI.
إذا كان جهازك يعتمد على BIOS، فستحتاج إلى تصحيح winload.exe:
ImgpValidateImageHashmov eax, edi بـ xor eax, eax في آخر كتلة كود من الدالةbcdedit /set path \Windows\system32\winload2.exeإذا كان جهازك يُقلع باستخدام UEFI، فاستخدم أداة EfiGuard لتصحيح winload.efi.
ورقة أوامر سريعة إذا كنت تستخدم Hyper-V Manager:
FS1:
cd EFI/Boot
Load EfiGuard.Dxe.efi
FS0:
\EFI\Boot\bootx64.efi
ثم قم بتجهيز برنامج التشغيل الذي تختاره. لتحميل برنامج تشغيل مُجهَّز على جهاز ويندوز، يجب أن يكون موقّعًا،
ويكفي شهادة توقيع ذاتي لتلبية متطلبات النظام:
# In elevated powershell terminal
$c = New-SelfSignedCertificate -Type CodeSigningCert -KeyUsage DigitalSignature -Subject 'CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US'
Set-AuthenticodeSignature .\driver.instrumented.sys -Certificate $c -Force
إذا كان برنامج التشغيل الذي قمت بتجهيزه مستخدمًا بالفعل من قبل النظام، فاستخدم هذه الأوامر (بصلاحيات المدير) لاستبدال ملف برنامج التشغيل:
set NAME=mydriver.sys
icacls %NAME% /save C:\windows\temp\%NAME%.icacls
takeown /F %NAME%
icacls %NAME% /grant Everyone:F
move %NAME% %NAME%.bak
move instrumented_driver.sys %NAME%
icacls . /restore C:\windows\temp\%NAME%.icacls
ثم شغّل:
bcdedit /set recoveryenabled no
bcdedit /set nointegritychecks on
shutdown -t 0 -r
يتم التكامل مع WinAFL عن طريق ترجمة برنامج ربط (harness) باستخدام ملفات الرأس المرفقة.
إلى جانب ملفات الرأس يوجد example.c، وهو برنامج نموذجي يوضح كيفية استخدامها.
ملفات الرأس المرفقة هي تعديل طفيف على ملفات الرأس التي يوفرها WinAFL أصلًا للتكامل مع أداة إدراج برمجي ثابت أخرى تُسمى Syzygy.
طريقة تكاملنا مع kAFL بسيطة جدًا.
عادةً، يعمل برنامج ربط (harness) خاص بـ kAFL على جهاز افتراضي ويتواصل مع الواجهة الأمامية للمُشوِّش باستخدام "استدعاءات مفرطة" (hypercalls) خاصة.
تُخبر هذه الاستدعاءات المفرطة المُشوِّش بالقيام بأشياء كثيرة، من بينها تحميل بيانات التغطية من IntelPT وتحليلها كخريطة بتات (bitmap) بصيغة AFL.
ولأن peafl64 يجعل تتبع IntelPT غير ضروري، يجب علينا إعداد طريقة لنقل بيانات التغطية إلى المُشوِّش.
لذلك، قمنا بتوسيع qemu و kvm باستدعاءات مفرطة تسمح لبرنامج الربط (الذي يعمل في وضع المستخدم) داخل الجهاز الافتراضي بإرسال بيانات التغطية التي جمعها باستخدام برنامج التشغيل المساعد.
يتعلق هذا بشكل خاص بالإعداد على ESXi، لكنه ينطبق أيضًا على منصات المحاكاة الافتراضية الأخرى مثل AWS.
الإعداد بسيط جدًا:
install.sh qemu، شغّل install.sh qemu_sbiلاختبار التشويش باستخدام kAFL و peafl64، نحتاج إلى إعداد جهاز مخصص للتشويش:
بخلاف ذلك، فإن التشويش باستخدام فرعنا من kAFL هو نفسه التشويش العادي مع kAFL.
نظرة عامة على الخطوات:
أولًا، نستخدم IDA للعثور على التعليمات والمواقع المهمة وتحليلها.
ينتج عن التحليل ملف dump.json يحتوي على كل هذه المعلومات بصيغة json.
يحتوي instrument.py على منطق الإدراج البرمجي، والتسلسل نفسه موضح في الدالة process_pe.
لتجهيز الملف الثنائي، نُنسخ أقسامه القابلة للتنفيذ، حيث سيتواجد كل الكود المُجهَّز.
بعد ذلك، نعالج جميع التعليمات النسبية ونحدد كيف وما إذا كنا بحاجة إلى التعامل معها.
لنفترض أن لدينا قفزة قصيرة (short jmp) من العنوان X إلى العنوان Y. نقوم بإدراج كود الإدراج البرمجي بين X و Y، وعندها يصبح الهدف عند العنوان Z (Z = Y + len(instrumentation)).
هذا يعني أنه يجب علينا تعديل القفزة.
إذا أصبح العنوان Z الآن خارج نطاق القفزات القصيرة - فسنحتاج إلى تحويل القفزة من short إلى far. (instrument.py:expand_relative_instructions)
على سبيل المثال:
short jmp 0x57 (eb 55) -> near jmp 0x604 (e9 ff 05 00 00)وينطبق الشيء نفسه على تعليمات مثل call و loop والقفزات الشرطية.
هناك حالة شائعة أخرى تحتاج إلى معالجة وهي التعليمات النسبية إلى سجل rip التي أُدخلت في لغة التجميع x64:
call [rip+0x1000]نقوم بتحديث العناصر التالية لتصبح مُشيرة إلى الكود المُجهَّز: جدول إعادة التوطين (relocation table)، جدول الصادرات، إعداد التحميل (load configuration)، دليل TLS، وسجلات الاستثناءات.
تتم التحديثات بتغيير جميع العناوين من الأقسام الأصلية إلى نظيراتها المُجهَّزة. (instrument.py:update_addr)
يتم تحديث الترويسات لتعكس التغييرات في بنية PE - الأقسام المضافة، ونقطة الدخول المعدَّلة، وحجم PE. (instrument.py:update_pe_headers)
بالإضافة إلى ذلك، يمكن لـ peafl64 تجهيز نواة ويندوز. لتحقيق ذلك، يقوم بتحليل وتحديث جدول إعادة توطين القيم الديناميكية (DVRT) وجدول SSDT، ويتعامل مع خصوصيات PatchGuard.
DVRT هو جدول مولّد من المترجم يصف مواقع العناوين التي يجب تغييرها عند تحميل PE.
يُستخدم لتحسين KASLR ويساعد في تخفيف ثغرة Spectre (1, 2, 3).
يتم تحليل DVRT باستخدام مجموعة من الفئات (classes) التي تحاكي بنية الجدول (drt.py; instrument.py:get_updated_dynamic_relocs)
لم يكن التعامل مع SSDT وتحديثه بالأمر المباشر.
عنوانه غير مُصدَّر، لذا كان علينا إما الاعتماد على الرموز (symbols) أو على الاستدلالات (heuristics) لتحديد موقعه.
يستخدم حلنا أسلوبًا استدلاليًا، بالاعتماد على NtWaitForSingleObject كعلامة ثابتة لجميع إصدارات ويندوز 10 والعثور على عنوان SSDT نسبيًا إليه. (instrument.py:ntoskrnl_update_KiServiceTable)
يعمل هذا الأسلوب مع جميع نوى ويندوز 10، لكنه سيحتاج إلى تعديل لإصدارات النواة الأخرى.