
مولد اختبارات اختراق للصيغ الثنائية يعتمد على القوالب، يقوم بتوليد وتحليل مدخلات اختبار صالحة بسرعة عالية، مع تكامل مع AFL++ لاختبار الاختراق الموجه بالتغطية.
FormatFuzzer هو إطار عمل لتوليد وتحليل المدخلات الثنائية عالية الكفاءة وعالية الجودة.
يأخذ قالبًا ثنائيًا يصف تنسيق مدخل ثنائي وينتج ملفًا تنفيذيًا يقوم بإنتاج وتحليل التنسيق الثنائي المُعطى.
على سبيل المثال، من قالب ثنائي لـ GIF، يُنتج FormatFuzzer مولدًا لـ GIF - يُعرف أيضًا باسم مُدقق GIF.
المولدات التي ينتجها FormatFuzzer عالية الكفاءة، حيث تنتج آلاف المدخلات الصالحة في الثانية - وهذا يتناقض بشكل حاد مع المُدققات القائمة على الطفرات، حيث تكون الغالبية العظمى من المدخلات غير صالحة. المدخلات التي يولدها FormatFuzzer مستقلة عن البرنامج قيد الاختبار (أو في الواقع، أي برنامج)، لذا يمكنك استخدامها في الإعدادات ذات الصندوق الأسود أيضًا. ومع ذلك، يتكامل FormatFuzzer أيضًا مع AFL++ لإنتاج مدخلات صالحة تهدف أيضًا إلى أقصى تغطية. في تجاربنا، يتفوق نهج "أفضل عالَمَين" هذا على جميع الإعدادات الأخرى؛ انظر ورقتنا للتفاصيل.
القوالب الثنائية التي يستخدمها FormatFuzzer تأتي من 010 editor.
يوجد أكثر من 170 قالبًا ثنائيًا، والتي يمكن استخدامها مباشرة مع FormatFuzzer أو تعديلها لاستخدامه. خارج الصندوق، ينتج FormatFuzzer تنسيقات مثل AVI و BMP و GIF و JPG و MIDI و MP3 و MP4 و PCAP و PNG و WAV و ZIP؛ ونواصل توسيع هذه القائمة كل أسبوع.
المساهمون مرحب بهم! زر صفحة مشروع FormatFuzzer لتقديم الأفكار والمشكلات، أو إضافة طلبات السحب. للحصول على تفاصيل حول كيفية عمل FormatFuzzer وكيفية مقارنته، اقرأ ورقتنا لمزيد من المعلومات.
FormatFuzzer متاح من صفحة مشروع FormatFuzzer. يمكنك تنزيل وفك ضغط أحدث إصدار من صفحة الإصدارات.
للحصول على أحدث وأفضل الإصدارات، يمكنك أيضًا استنساخ مستودع git الخاص به:
git clone https://github.com/uds-se/FormatFuzzer.git
جميع الإجراءات الإضافية تتم في مجلده الرئيسي:
cd FormatFuzzer
لتشغيل FormatFuzzer، تحتاج إلى ما يلي:
getopt_long()) مثل clang أو gccpy010parser و six و intervaltreezlib (لدوال الضغط)boost (لدوال المجموع الاختباري)إذا كنت تخطط لتحرير نصوص البناء والتكوين (ملفات .ac و .am)، فستحتاج أيضًا إلى:
sudo apt install git g++ make automake python3-full zlib1g-dev libboost-dev
python3 -m venv ~/fuzz
source ~/fuzz/bin/activate
pip3 install py010parser six intervaltree
xcode-select --install
brew install python3 automake boost
pip3 install py010parser six intervaltree
على جميع الأنظمة، باستخدام pip:
pip install py010parser
pip install six
pip install intervaltree
ملاحظة: جميع أوامر البناء تتطلب منك أن تكون في نفس المجلد الذي يوجد به ملف README هذا. بناء مُدقق خارج هذا المجلد غير مدعوم بعد.
يوجد نص build.sh الذي يقوم بأتمتة جميع خطوات البناء.
ببساطة قم بتشغيل
./build.sh gif
لإنشاء مُدقق GIF.
يعمل هذا لجميع تنسيقات الملفات المتوفرة في templates/؛ إذا كان هناك ملف templates/FOO.bt، فإن ./build.sh FOO سيبني مُدققًا.
يوجد Makefile (المصدر في Makefile.am) الذي يقوم بأتمتة جميع خطوات البناء.
(يتطلب GNU make.)
أولاً قم بتشغيل
touch configure Makefile.in
ثم
./configure
ثم
make gif-fuzzer
لإنشاء مُدقق GIF.
يعمل هذا لجميع تنسيقات الملفات المتوفرة في templates/؛ إذا كان هناك ملف templates/FOO.bt، فإن make FOO-fuzzer سيبني مُدققًا.
إذا لم تعمل طريقة make أعلاه، أو إذا كنت تريد تحكمًا أكبر، فقد تضطر إلى المتابعة يدويًا.
قم بتشغيل مُجمّع ffcompile لتجميع القالب الثنائي إلى كود C++. يأخذ وسيطين: القالب الثنائي .bt، وملف .cpp C++ الذي سيتم إنشاؤه.
./ffcompile templates/gif.bt gif.cpp
استخدم الأوامر التالية لإنشاء مُدقق gif-fuzzer.
أولاً، قم بتجميع مُحرك سطر الأوامر العام:
g++ -c -I . -std=c++17 -g -O3 -Wall fuzzer.cpp
(-I . يشير إلى موقع ملف bt.h؛ -std=c++17 يحدد معيار C++.)
ثم، قم بتجميع المحلل/المُجمّع الثنائي:
g++ -c -I . -std=c++17 -g -O3 -Wall gif.cpp
أخيرًا، قم بربط المحلل/المُجمّع الثنائي مع مُحرك سطر الأوامر للحصول على ملف تنفيذي. إذا كنت تستخدم أي مكتبات إضافية (مثل -lz)، فتأكد من تحديدها هنا أيضًا.
g++ -O3 gif.o fuzzer.o -o gif-fuzzer -lz
يمكن تشغيل FormatFuzzer كمُحلل مستقل، أو مولد، أو مُطفر لتنسيقات محددة. بالإضافة إلى ذلك، يمكن استدعاؤه بواسطة مُدققات عامة مثل AFL++ لدمج هذه القدرات الخاصة بالتنسيق في عملية التدقيق (انظر القسم أدناه حول التكامل مع AFL++).
يأخذ المُدقق المُنشأ أمرًا كوسيط أول، متبوعًا بخيارات ووسائط لهذا الأمر.
الأمر الأكثر أهمية هو fuzz، لإنتاج المخرجات. وسائطه هي الملفات التي سيتم إنشاؤها بالتنسيق المناسب.
قم بتشغيل المولد كالتالي:
./gif-fuzzer fuzz output.gif
لإنشاء ملف ثنائي عشوائي output.gif، أو
./gif-fuzzer fuzz out1.gif out2.gif out3.gif
لإنشاء ثلاثة ملفات GIF: out1.gif و out2.gif و out3.gif.
لاحظ أن القالب gif.bt الذي نقدمه قد تم تحسينه بوظائف خاصة لتسهيل إنشاء ملفات صالحة. إذا كنت تستخدم ملفات .bt أصلية دون تعديلات، فقد تحصل على تحذيرات أثناء الإنشاء وقد تُنشئ ملفات غير صالحة.
يمكنك أيضًا تشغيل المُدقق كـ محلل للملفات الثنائية، باستخدام الأمر parse. هذا مفيد إذا كنت تريد اختبار دقة القالب الثنائي، أو إذا كنت تريد طفر مدخل (انظر 'ملفات القرار' أدناه).
لتشغيل المحلل، استخدم:
./gif-fuzzer parse input.gif
سترى رسائل خطأ إذا تعذر تحليل input.gif بنجاح.
أثناء التحليل، يمكنك أيضًا تخزين جميع قرارات التحليل (أي أي بدائل تحليل تم اتخاذها) في ملف قرار. هذا هو سلسلة من البايتات التي تُعدّد القرارات المتخذة.
كل بايت يمثل قرار تحليل واحد. قيمة بايت 0 تعني أنه تم اتخاذ البديل الأول، وقيمة بايت 1 تعني أنه تم اتخاذ البديل الثاني، وهكذا.
يمكنك إنشاء ملف قرار كهذا عند تحليل مدخل:
./gif-fuzzer parse --decisions input.dec input.gif
هنا، يقوم input.dec بتخزين القرارات المتخذة لتحليل input.gif.
يمكنك أيضًا استخدام ملف قرار كهذا عند إنشاء المدخلات. سيتخذ المُدقق بعد ذلك نفس القرارات تمامًا التي تم العثور عليها أثناء التحليل. الأمر التالي يُنشئ ملف GIF جديد باستخدام القرارات المحددة أثناء تحليل input.gif:
./gif-fuzzer fuzz --decisions input.dec input2.gif
إذا كان كل شيء يعمل بشكل جيد، يجب أن يكون كلا الملفين متطابقين:
cmp input.gif input2.gif
عن طريق تطويع ملف قرار (مثل استبدال بايتات فردية)، يمكنك إنشاء مدخلات مشابهة للملف الأصلي الذي تم تحليله. هذا مفيد للتفاعل مع استراتيجيات اختبار محددة ومُدققات مثل AFL، حيث يمكنك استخدام gif-fuzzer وما شابه ذلك كـ مُترجمين من ملفات القرار إلى ملفات ثنائية والعكس: سيقوم AFL بتطويع ملفات القرار، وسيعمل البرنامج قيد الاختبار على الملفات الثنائية المُترجمة. على عكس تطويع الملفات الثنائية مباشرة (كما يفعل AFL عادةً)، فإن هذا سيكون له ميزة وجود مدخلات صالحة دائمًا - وبالتالي التقدم بشكل أسرع نحو التغطية.
بالإضافة إلى المُدققات الخاصة بالتنسيق، مثل gif-fuzzer، يمكن أيضًا تجميع FormatFuzzer في مكتبات مشتركة خاصة بالتنسيق، مثل gif.so (لهذا، قم بتشغيل ./build.sh gif أو make gif.so).
يمكن تحميل هذه المكتبات المشتركة بواسطة مُدققات عامة، مثل AFL++.
لتشغيل AFL++ مع FormatFuzzer، ما عليك سوى اتباع التعليمات الموجودة على نسختنا المعدلة من AFL++. ندعم استراتيجيات تدقيق مختلفة، بما في ذلك:
AFL+FFMut: يُشغّل AFL++ باستخدام FormatFuzzer لتوفير طفرات ذكية خاصة بالتنسيق.
AFL+FFGen: يستخدم FormatFuzzer كمولد خاص بالتنسيق، بينما يُطوّع AFL++ بذور قراراته.
لكتابة قوالب .bt الثنائية الخاصة بك (وبالتالي إنشاء مُدقق/محلل عالي الكفاءة لهذا التنسيق)، اقرأ القسم مقدمة في القوالب والنصوص من دليل محرر 010.
في كثير من الحالات، قد يكون قالب التنسيق الذي تبحث عنه (أو قالب مشابه) موجودًا بالفعل. ألقِ نظرة على مجموعة القوالب الثنائية لمحرر 010 لمعرفة ما إذا كان هناك شيء يمكنك استخدامه أو الارتكاز عليه.
لاحظ أن ملفات .bt المتوفرة في المستودع تستهدف بشكل عام تحليل الملفات. يمكن استخدامها أيضًا لإنشاء الملفات؛ لكنها غالبًا ما تفتقر إلى المعلومات الدقيقة حول الأجزاء المطلوبة من المدخل.
في هذا القسم، نناقش بعض الطرق التي يمكنك من خلالها تخصيص ملفات .bt لتعمل بشكل جيد مع FormatFuzzer.
على سبيل المثال، بالنسبة لتنسيق GIF، يُظهر الملف templates/gif-orig.bt القالب الثنائي الأصلي، الذي تم تصميمه فقط للتحليل، بينما ملف templates/gif.bt هو نسخة معدلة قادرة على إنشاء صور GIF صالحة. بمقارنة الملفين، نرى أنه كانت هناك حاجة إلى عدد قليل من التغييرات لتحقيق ذلك.
إذا كنت قد أنشأت gif-fuzzer، إما عن طريق تشغيل make gif-fuzzer أو باستخدام أداة ffcompile، فقد حصلت بالفعل على ملف C++ gif.cpp الذي يحتوي على تنفيذ لمولد ومحلل GIF. هذا مفيد لرؤية كيف يتم ترجمة التغييرات التي تجريها على القالب الثنائي إلى كود قابل للتنفيذ. مزيد من التفاصيل حول كود C++ معروضة في القسم التالي.
يستخدم القالب الثنائي لـ GIF دوال النظر المسبق ReadUByte() و ReadUShort() للنظر مسبقًا إلى قيم البايتات التالية في الملف قبل تحليلها فعليًا إلى حقل هيكل. في وقت الإنشاء، نسمح لهذه الدوال بتلقي وسيط إضافي يحدد مجموعة من القيم المعروفة الجيدة لاختيار البايتات التي ننظر إليها مسبقًا. بالإضافة إلى ذلك، نسمح أيضًا بتحديد مجموعة عالمية من القيم المعروفة الجيدة لاستخدامها دائمًا عند استدعاء دالة نظر مسبق معينة، مثل ReadUByte(). يتم تخزين هذه في المتجه ReadUByteInitValues.
افتراضيًا، يحاول إجراء الترجمة الخاص بنا ffcompile استخراج القيم المثيرة للاهتمام التي تم استخدامها في المقارنات ضد بايتات النظر المسبق واستخدامها كمجموعة عالمية من القيم المعروفة. عند تشغيل
./ffcompile templates/gif.bt gif.cpp
تُظهر رسالة مطبوعة دوال النظر المسبق التي تم التعرف عليها، بالإضافة إلى القيم المثيرة للاهتمام المستخرجة:
Finished creating cpp generator.
Lookahead functions found:
ReadUByte
ReadUShort
Mined interesting values:
GlobalColorTableFlag: ['1']
LocalColorTableFlag: ['1']
ReadUByte: ['0x3B', '0x2C']
ReadUShort: ['0xF921', '0xFE21', '0x0121', '0xFF21']
Signature: ['"GIF"']
ومع ذلك، بالنسبة لإنشاء GIF، من الأفضل تحديد مجموعة القيم المعروفة الجيدة لـ ReadUByte() بشكل فردي في كل استدعاء للدالة. لذلك نحدد مصفوفة فارغة (حجم 0)
const local UBYTE ReadUByteInitValues[0];
لتجاوز مجموعة ReadUByteInitValues العالمية ولكل استدعاء لـ ReadUByte()، نستخدم وسيطًا إضافيًا لتحديد مجموعة القيم الجيدة لاستخدامها في ذلك الموقع المحدد.
لغة القالب الثنائي قوية بما يكفي أيضًا للسماح باتخاذ هذا الاختيار بناءً على شروط وقت التشغيل. على سبيل المثال، في الكود التالي نوضح كيف يمكن أن يعتمد اختيار القيم المناسبة لاستدعاء ReadUByte() على إصدار GIF الحالي الذي ننشئه. إصدار GIF 89a يسمح بقيمة إضافية واحدة محتملة للبايت (0x21).
if(GifHeader.Version == "89a")
local UBYTE values[] = { 0x3B, 0x2C, 0x21 };
else
local UBYTE values[] = { 0x3B, 0x2C };
while (ReadUByte(FTell(), values) != 0x3B) {
...
}
التعديلات المتبقية المطلوبة للقالب الثنائي لـ GIF متشابهة. على سبيل المثال، لكل حقل هيكل يمكن أيضًا تحديد مجموعة من القيم المعروفة الجيدة. على سبيل المثال، يحدد هذا القيم الصحيحة لحقل Version: 87a و 89a.
char Version[3] = { {"87a"}, {"89a"} };
لأغراض التصحيح، وكذلك لفهم كيفية إجراء التغييرات المناسبة لتحسين المُولدات والمحللات، قد يكون من المفيد فهم بعض الأعمال الداخلية لكود C++ المُنشأ. من الناحية المثالية، يجب أن تكون قادرًا على تعديل ملفات القالب الثنائي حتى يمكن استخدامها لإنشاء ملفات صالحة باحتمالية عالية، وبالتالي لن تضطر إلى تعديل كود C++ المُنشأ.
يقوم كود C++ بإنشاء فئة لكل struct و union مُعرّف في القالب الثنائي، وكذلك للأنواع الأصلية، مثل int.
في وقت البناء، عند تهيئة متغير، يمكننا تحديد مجموعة من القيم المعروفة الجيدة التي يمكن أن يتخذها هذا المتغير. على سبيل المثال، استدعاء المُنشئ
char_array_class cname(cname_element, { "IHDR", "tEXt", "PLTE", "cHRM", "sRGB", "iEXt", "zEXt", "tIME", "pHYs", "bKGD", "sBIT", "sPLT", "acTL", "fcTL", "fdAT", "IHDR", "IEND" });
سيحدد 17 قيمة جيدة لاستخدامها للمتغير cname. لكن هذا غالبًا لا يكفي، لأن اختيار أنواع القطع المناسبة يعتمد على السياق.
لذلك نسمح أيضًا بتحديد مجموعة من القيم الجيدة في وقت الإنشاء عند إنشاء قطعة جديدة.
على سبيل المثال، يمكن استخدام هذا الاستدعاء لإنشاء مثيل لـ chunk لأول قطعة، والتي يجب أن يكون نوعها IHDR.
GENERATE(chunk, ::g->chunk.generate({ "IHDR" }, false));
عند إنشاء القطعة الثانية، قد نستخدم هذه القائمة الطويلة من القطع المحتملة التي يمكن أن تأتي بين قطعة IHDR وقطعة PLTE:
GENERATE(chunk, ::g->chunk.generate({ "iCCP", "sRGB", "sBIT", "gAMA", "cHRM", "pHYs", "sPLT", "tIME", "zTXt", "tEXt", "iTXt", "eXIf", "oFFs", "pCAL", "sCAL", "acTL", "fcTL", "fdAT", "fRAc", "gIFg", "gIFt", "gIFx", "sTER" }, true));
سيختار المُولد بعد ذلك بشكل منتظم واحدة من القيم المعروفة الجيدة لاستخدامها للمثيل الجديد. نسمح أيضًا باختيار قيمة شريرة ليست واحدة من القيم المعروفة الجيدة باحتمال صغير 1/128.
يمكن تمكين هذه الميزة أو تعطيلها في أي وقت باستخدام طريقة set_evil_bit.
جميع الاختيارات العشوائية التي يتخذها المُولد تتم عن طريق استدعاء طريقة rand_int().
long long rand_int(unsigned long long x, std::function<long long (unsigned char*)> parse);
عند تشغيل البرنامج كمُولد، تقوم هذه الطريقة بأخذ عينة من عدد صحيح من 0 إلى x-1 عن طريق قراءة البايتات من المخزن المؤقت العشوائي.
عند تشغيل البرنامج كمُحلل، تستخدم هذه الطريقة دالة parse() لمعرفة البايتات العشوائية التي يجب أن تكون موجودة في المخزن المؤقت العشوائي لتوليد الملف المستهدف، ثم تكتب تلك البايتات إلى المخزن المؤقت العشوائي.
تتلقى دالة parse كوسيط المخزن المؤقت في الموضع الحالي للملف ويجب أن تُرجع القيمة التي يجب أن تُرجعها الاستدعاء الحالي لـ rand_int() من أجل توليد هذا التكوين الدقيق للملف.
تم تصميم وكتابة FormatFuzzer بواسطة Rafael Dutra <[email protected]>.
تم تقديم مفهوم مُدقق المُجمّع بواسطة Rahul Gopinath <[email protected]> و Andreas Zeller <[email protected]>.
حقوق الطبع والنشر لـ FormatFuzzer © 2020, 2021 محفوظة لصالح CISPA Helmholtz Center for Information Security. تنطبق التراخيص التالية:
كود FormatFuzzer (بشكل ملحوظ، جميع كود C++ والكود المتعلق بتوليده) يخضع لـ GNU GENERAL PUBLIC LICENSE، كما هو موجود في COPYING.
كاستثناء لما سبق، كود C++ المُنشأ بواسطة FormatFuzzer (أي المُدققات والمحللات للتنسيقات المحددة) هو في الملكية العامة.
الكود الأصلي pfp ، الذي يعتمد عليه FormatFuzzer، يخضع لترخيص MIT، كما هو موجود في LICENSE-pfp.