لقد بدأت في كتابة الملاحظات على مقاطع الفيديو ذات الصلة بالأمان التي أشاهدها (كوسيلة للتذكير السريع).
قد تكون هذه أكثر فائدة للمبتدئين.
ترتيب الملاحظات هنا ليس في ترتيب الصعوبة ، ولكن بالترتيب الزمني العكسي لكيفية كتابتها (أي أحدث).
تم ترخيص هذا العمل بموجب ترخيص Creative Commons Noncommercial-Sharealike 4.0 الدولي.
مكتوبة في 12 أغسطس 2017
تأثرت بثقة Gynvael CTF 2017 هنا وهنا ؛ ومن خلال Google CTF Quals 2017 Livestream هنا
في بعض الأحيان ، قد ينفذ التحدي مهمة معقدة من خلال تنفيذ VM. ليس من الضروري دائمًا عكس هندسة VM تمامًا والعمل على حل التحدي. في بعض الأحيان ، يمكنك إعادة قليلاً ، وبمجرد معرفة ما يجري ، يمكنك ربط VM ، والوصول إلى الأشياء التي تحتاجها. بالإضافة إلى ذلك ، تصبح هجمات القناة الجانبية القائمة على التوقيت أسهل في VMS (ويرجع ذلك أساسًا إلى عدد أكبر من التعليمات "الحقيقية" التي تم تنفيذها.
يمكن التعرف على وظائف مثيرة للاهتمام في الثنائيات وسرعان ما تتم ببساطة من خلال البحث عن الثوابت والبحث عنها عبر الإنترنت. بالنسبة لوظائف التشفير القياسية ، فإن هذه الثوابت كافية للتخمين بسرعة في وظيفة ما. يمكن التعرف على وظائف التشفير البسيطة بسهولة أكبر. إذا رأيت الكثير من xors وأشياء من هذا القبيل يحدث ، ولا توجد ثوابت يمكن التعرف عليها بسهولة ، فمن المحتمل أن تكون مشفرة مدفوعة يدويًا (وربما مكسورة أيضًا).
في بعض الأحيان ، عند استخدام IDA مع HexRays ، قد تكون وجهة نظر التفكيك أفضل من عرض إزالة التجميع. هذا صحيح بشكل خاص إذا لاحظت أنه يبدو أن هناك الكثير من المضاعفات التي تحدث في عرض فك ، لكنك لاحظت أنماطًا متكررة في عرض التفكيك. (يمكنك تبديل B/W بسرعة باستخدام شريط الفضاء). على سبيل المثال ، إذا كان هناك مكتبة (حجم ثابت) تم تنفيذ مكتبة كبيرة ، فإن طريقة إزالة الإلغاء أمر فظيع ، ولكن من السهل فهم عرض التفكيك (ويمكن التعرف عليه بسهولة بسبب الإرشادات المتكررة "مع الحمل" مثل adc ). بالإضافة إلى ذلك ، عند التحليل مثل هذا ، يعد استخدام ميزة "عقد المجموعة" في عرض الرسم البياني لـ IDA مفيدًا للغاية لتقليل تعقيد الرسم البياني الخاص بك بسرعة ، كما تفهم ما تفعله كل عقدة.
بالنسبة للبنى الغريبة ، فإن وجود محاكي جيد أمر مفيد للغاية. على وجه الخصوص ، يمكن استخدام المحاكي الذي يمكن أن يمنحك تفريغًا من الذاكرة لمعرفة ما يجري بسرعة ، والتعرف على الأجزاء المثيرة للاهتمام ، بمجرد خروجك من المحاكي. بالإضافة إلى ذلك ، فإن استخدام محاكي يتم تنفيذه بلغة مريحة (مثل Python) ، يعني أنه يمكنك تشغيل الأشياء بالضبط كيف تحب. على سبيل المثال ، إذا كان هناك جزء مثير للاهتمام من الكود الذي قد ترغب في تشغيله عدة مرات (على سبيل المثال ، إلى القوة الغاشمة أو شيء ما) ، ثم باستخدام المحاكي ، يمكنك بسرعة ترميز شيء لا يفعل سوى هذا الجزء من الكود ، بدلاً من الاضطرار إلى تشغيل البرنامج الكامل.
كونك كسولًا جيدًا ، عندما يكون رينغ. لا تضيع الوقت في هندسة كل شيء ، ولكن تقضي وقتًا كافيًا في القيام بالتشويه (حتى في تحدي إعادة!) ، حتى تتمكن من تقليل الوقت الذي يقضيه في فعل المهمة الأكثر صعوبة في Reing. ما يعنيه ، في مثل هذا الموقف ، هو مجرد إلقاء نظرة سريعة على وظائف مختلفة ، دون قضاء الكثير من الوقت في تحليل كل وظيفة بدقة. يمكنك فقط قياس ما قد تكون عليه الوظيفة (على سبيل المثال "يبدو وكأنه شيء مشفر" ، أو "يبدو وكأنه شيء إدارة الذاكرة" ، وما إلى ذلك)
بالنسبة للأجهزة أو الهندسة المعمارية غير المعروفة ، تقضي وقتًا كافيًا في البحث عنها على Google ، فقد تكون محظوظًا مع مجموعة من الأدوات أو المستندات المفيدة التي قد تساعدك على بناء أدوات بشكل أسرع. في كثير من الأحيان ، ستجد تطبيقات محاكي الألعاب وما إلى ذلك قد تكون مفيدة كنقطة سريعة للبدء منها. بدلاً من ذلك ، قد تحصل على بعض المعلومات المثيرة للاهتمام (مثل كيفية تخزين خرائط الصورة النقطية ، أو كيفية تخزين السلاسل ، أو شيء ما) يمكنك من خلاله كتابة نص "إصلاح" سريع ، ثم استخدام الأدوات العادية لمعرفة ما إذا كانت هناك أشياء مثيرة للاهتمام.
GIMP (أداة معالجة الصور) ، لها وظيفة مفتوحة/تحميل رائعة للغاية لرؤية بيانات البكسل الخام. يمكنك استخدام هذا للبحث بسرعة عن الأصول أو الهياكل المتكررة في البيانات الثنائية الخام. قضاء بعض الوقت في العبث مع الإعدادات لمعرفة ما إذا كان يمكن الحصول على مزيد من المعلومات منه.
مكتوب في 2 يوليو 2017
تأثرت بمناقشة مع @p4n74 و @h3rcul35 على infoseciitr #bin chat. كنا نناقش كيف يكافح المبتدئون في بعض الأحيان من أجل البدء بتحدي أكبر ثنائي ، خاصة عندما يتم تجريده.
إما لحل تحدي Re ، أو أن يكون قادرًا على pwn ، يجب على المرء أولاً تحليل الثنائي المعطى ، من أجل أن يكون قادرًا على استغلاله بشكل فعال. نظرًا لأن الثنائي قد يتم تجريده وما إلى ذلك (تم العثور عليه باستخدام file ) يجب على المرء أن يعرف من أين تبدأ التحليل ، للحصول على موطئ قدم للتراكم منه.
هناك عدد قليل من أنماط التحليل ، عند البحث عن نقاط الضعف في الثنائيات (ومن ما جمعته ، فإن فرق CTF مختلفة لها تفضيلات مختلفة):
1.1. Transpiling Complete Code to C
هذا النوع من التحليل أمر نادر الحدوث ، ولكنه مفيد للغاية للثنائيات الأصغر. الفكرة هي الذهاب في هندسة عكسية مجمل الكود. يتم فتح كل وظيفة في IDA (باستخدام طريقة عرض decompiler) ، ويتم استخدام إعادة تسمية (اختصار: n) وإعادة إعادة التنفيذ (الاختصار: y) لجعل الكود المبلور أكثر قابلية للقراءة بسرعة. بعد ذلك ، يتم نسخ/تصدير جميع الكود إلى ملف .c منفصل ، والذي يمكن تجميعه للحصول على ثنائي مكافئ (ولكن ليس نفسه) إلى الأصل. بعد ذلك ، يمكن إجراء تحليل مستوى رمز المصدر ، للعثور على الفقرة وما إلى ذلك. بمجرد العثور على نقطة الضعف ، يتم بناء الاستغلال على الثنائي الأصلي ، من خلال متابعته في المصدر المقطوع بشكل جيد في IDA ، جنبًا إلى جنب مع عرض التفكيك (استخدم علامة التبويب بسرعة بين الاثنين ؛ واستخدام المساحة بسرعة بين الرسم البياني وعرض النص لعرضها على التحميل).
1.2. الحد الأدنى من التحليل لإزالة التجميع
يتم ذلك في كثير من الأحيان ، لأن معظم الثنائي غير مجدي نسبيًا (من منظور المهاجم). تحتاج فقط إلى تحليل الوظائف المشبوهة أو قد تقودك إلى الفولن. للقيام بذلك ، هناك بعض الأساليب للبدء:
1.2.1. ابدأ من الرئيسي
الآن عادةً ، بالنسبة للثنائي المُجرّب ، حتى لا يتم تصنيف Main (IDA 6.9 فصاعدًا ، يميزها بالنسبة لك رغم ذلك) ، ولكن بمرور الوقت ، تتعلم التعرف على كيفية الوصول إلى الرئيسي من نقطة الدخول (حيث تفتح IDA بشكل افتراضي). تقفز إلى ذلك وتبدأ في التحليل من هناك.
1.2.2. العثور على السلاسل ذات الصلة
في بعض الأحيان ، تعرف بعض الأوتار المحددة التي قد يتم إخراجها وما إلى ذلك ، والتي تعرف أنها قد تكون مفيدة (على سبيل المثال "تهانينا ، علمك ٪ S" لتحدي إعادة). يمكنك القفز إلى عرض السلاسل (اختصار: Shift+F12) ، والعثور على السلسلة ، والعمل للخلف باستخدام XREFS (الاختصار: X). تتيح لك XREF أن تجد مسار الوظائف لتلك السلسلة ، باستخدام XREFs على جميع الوظائف في تلك السلسلة ، حتى تصل إلى الرئيسية (أو نقطة تعرفها).
1.2.3. من بعض الوظائف العشوائية
في بعض الأحيان ، قد لا تكون سلسلة محددة مفيدة ، ولا تريد أن تبدأ من الرئيسي. لذا بدلاً من ذلك ، يمكنك الالتفاف بسرعة عبر قائمة الوظائف بأكملها ، وتبحث عن وظائف تبدو مشبوهة (مثل وجود الكثير من الثوابت ، أو الكثير من xors ، إلخ) أو تستدعي وظائف مهمة (xrefs من malloc ، مجانًا ، إلخ) ، وتبدأ من هناك ، وتذهب إلى الأمام (تتبع الوظائف التي تستدعي) (xrefs من الوظيفة)
1.3. تحليل التفكيك الخالص
في بعض الأحيان ، لا يمكنك استخدام عرض إلغاء التثبيت (بسبب الهندسة المعمارية الغريبة ، أو تقنيات مكافحة الالتفاف ، أو التجميع المكتوب يدويًا ، أو فك الإلغاء المعقدة بشكل غير ضروري). في هذه الحالة ، من الصحيح تمامًا أن ننظر بحتة في عرض التفكيك. من المفيد للغاية (بالنسبة للبنيات الجديدة) تشغيل تعليقات تلقائية ، مما يدل على تعليق يشرح كل تعليمات. بالإضافة إلى ذلك ، فإن وظائف تلوين العقدة وعقد المجموعة مفيدة للغاية. حتى لو لم تستخدم أيًا من هذه الأشياء ، فإن تعليقات العلامات بانتظام في التفكيك تساعد كثيرًا. إذا كنت أقوم بذلك شخصياً ، فأنا أفضل تدوين التعليقات الشبيهة بالثعبان ، حتى أتمكن بسرعة من الانتقال إلى Python يدويًا (مفيد بشكل خاص لتحديات Re ، حيث قد تضطر إلى استخدام Z3 وما إلى ذلك).
1.4. باستخدام منصات مثل BAP ، إلخ.
هذا النوع من التحليل (شبه) آلي ، وعادة ما يكون أكثر فائدة للبرامج الأكبر بكثير ، ونادرا ما يستخدم مباشرة في CTFs.
يمكن أن يكون الضباب تقنية فعالة للوصول بسرعة إلى الفولن ، دون الحاجة إلى فهمها فعليًا في البداية. باستخدام fuzzer ، يمكن للمرء الحصول على الكثير من نمط الفاكهة المنخفضة من Vulns ، والتي تحتاج بعد ذلك إلى تحليلها والثلاثي للوصول إلى Vuln الفعلي. شاهد ملاحظاتي حول أساسيات الغموض والغزى الوراثي لمزيد من المعلومات.
يمكن استخدام التحليل الديناميكي بعد إيجاد Vuln باستخدام تحليل ثابت ، للمساعدة في بناء مآثر بسرعة. بدلا من ذلك ، يمكن استخدامه للعثور على فولن نفسه. عادةً ما يبدأ المرء من القابل للتنفيذ داخل مصحح أخطاء ، ويحاول السير على طول مسارات التعليمات البرمجية التي تؤدي إلى الخلل. من خلال وضع نقاط التوقف في المواقع المناسبة ، وتحليل حالة السجلات/الكومة/المكدس/إلخ ، يمكن للمرء أن يحصل على فكرة جيدة عما يجري. يمكن للمرء أيضًا استخدام Defuggers لتحديد الوظائف المثيرة للاهتمام بسرعة. يمكن القيام بذلك ، على سبيل المثال ، عن طريق تعيين نقاط توقف مؤقتة على جميع الوظائف في البداية ؛ ثم المتابعة للقيام 2 من المشي - واحد من خلال جميع مسارات الكود غير المهتمة ؛ وواحد من خلال مسار واحد مثير للاهتمام فقط. تقوم المشي الأول برحلات جميع وظائف غير مهتم وتؤدي إلى تعطيل نقاط التوقف هذه ، مما يترك تلك المثيرة للاهتمام تظهر كنقاط توقف خلال المشي الثاني.
أسلوبي الشخصي للتحليل ، هو البدء بالتحليل الثابت ، عادةً من التطبيقات الرئيسية (أو للتطبيقات غير القائمة على القساوسة ، من الأوتار) ، والعمل على إيجاد وظيفة تبدو غريبة بسرعة. ثم أقضي الوقت وأتفوق على الأمام والخلف من هنا ، وأكتب بانتظام التعليقات ، وإعادة تسمية المتغيرات باستمرار وإعادة تنفيذها لتحسين عملية فك. مثل الآخرين ، أستخدم أسماء مثل Apple و Banana و Carrot ، إلخ من أجل مفيدة على ما يبدو ، ولكن حتى الآن وظائف/متغيرات غير معروفة/إلخ ، لتسهيل التحليل (تتبع نمط FUNC_123456 من الأسماء أمر صعب للغاية بالنسبة لي). كما أنني أستخدم طريقة عرض الهياكل بانتظام في IDA لتحديد الهياكل (والتعداد) لجعل فك الإرهاق حتى أجمل. بمجرد أن أجد الفولن ، عادةً ما أنتقل إلى كتابة برنامج نصي باستخدام pwntools (واستخدم ذلك للاتصال بـ gdb.attach() ). بهذه الطريقة ، يمكنني السيطرة على ما يجري. داخل GDB ، عادةً ما أستخدم GDB العادي ، على الرغم من أنني أضفت أمرًا peda الذي يقوم بتحميل PEDA على الفور إذا لزم الأمر.
إن أسلوبي يتطور بالتأكيد ، حيث حصلت على مزيد من الراحة مع أدواتي ، وأيضًا مع الأدوات المخصصة التي كتبتها لتسريع الأمور. يسعدني أن أسمع عن أنماط تحليل أخرى ، بالإضافة إلى التغييرات المحتملة على أسلوبي التي قد تساعدني في الحصول على أسرع. لأي تعليقات/انتقادات/مدح لديك ، كما هو الحال دائمًا ، يمكن الوصول إلي على Twitter @jay_f0xtr0t.
كتب في 4 يونيو 2017
متأثرًا بهذا البث المباشر الرائع من Gynvael Coldwind ، حيث يناقش أساسيات ROP ، ويعطي بعض النصائح والحيل
تعد البرمجة الموجهة نحو الإرجاع (ROP) واحدة من تقنيات الاستغلال الكلاسيكية ، والتي يتم استخدامها لتجاوز حماية NX (الذاكرة غير القابلة للتنفيذ). قامت Microsoft بدمج NX كـ DEP (الوقاية من تنفيذ البيانات). حتى Linux وما إلى ذلك ، يكون لها فعال ، مما يعني أنه مع هذه الحماية ، لم يعد بإمكانك وضع رمز الصدفة على كومة/كومة وتنفيذها فقط عن طريق القفز إليها. حتى الآن ، لكي تكون قادرًا على تنفيذ التعليمات البرمجية ، يمكنك القفز إلى الكود الموجود مسبقًا (الثنائي الرئيسي ، أو مكتباتها-LIBC ، LDD ETC على Linux ؛ Kernel32 ، NTDLL إلخ على Windows). يأتي ROP إلى الوجود من خلال إعادة استخدام أجزاء من هذا الرمز الموجود بالفعل ، ومعرفة طريقة للجمع بين تلك الشظايا في القيام بما تريد القيام به (وهو بالطبع ، اختراق الكوكب !!!).
في الأصل ، بدأت ROP بـ RET2LIBC ، ثم أصبحت أكثر تقدماً بمرور الوقت باستخدام العديد من القطع الصغيرة من التعليمات البرمجية. قد يقول البعض أن ROP قد أصبح الآن "ميتًا" ، نظرًا للحماية الإضافية للتخفيف من ذلك ، ولكن لا يزال يمكن استغلالها في الكثير من السيناريوهات (وبالتأكيد ضرورية للعديد من CTFs).
الجزء الأكثر أهمية في ROP ، هو الأدوات. الأدوات هي "أجزاء قابلة للاستخدام من الكود لـ ROP". هذا يعني عادة أجزاء من التعليمات البرمجية التي تنتهي بـ ret (ولكن قد تكون أنواع الأدوات الأخرى مفيدة أيضًا ؛ مثل تلك التي تنتهي بـ pop eax; jmp eax إلخ). نرسل هذه الأدوات معًا لتشكيل الاستغلال ، والتي تعرف باسم سلسلة ROP .
واحدة من أهم افتراضات ROP هي أن لديك التحكم في المكدس (أي ، يشير مؤشر المكدس إلى مخزن مؤقت تتحكم فيه). إذا لم يكن هذا صحيحًا ، فستحتاج إلى تطبيق الحيل الأخرى (مثل محور المكدس) لاكتساب هذا التحكم قبل بناء سلسلة ROP.
كيف تستخرج الأدوات؟ استخدم أدوات التنزيل (مثل RopGadget) أو أداة عبر الإنترنت (مثل Ropshell) أو كتابة أدواتك الخاصة (قد تكون أكثر فائدة لتحديات أكثر صعوبة في بعض الأحيان ، حيث يمكنك تعديلها إلى التحدي المحدد إذا لزم الأمر). في الأساس ، نحتاج فقط إلى العناوين التي يمكننا القفز إليها لهذه الأدوات. هذا هو المكان الذي قد تكون هناك مشكلة مع ASLR وما إلى ذلك (وفي هذه الحالة ، تحصل على تسرب للعنوان ، قبل الانتقال إلى ROP بالفعل).
والآن ، كيف نستخدم هذه الأدوات لصنع Ropchain؟ نبحث أولاً عن "الأدوات الأساسية". هذه أدوات يمكنها القيام بمهام بسيطة بالنسبة لنا (مثل pop ecx; ret ، والتي يمكن استخدامها لتحميل قيمة في ECX عن طريق وضع الأداة ، تليها القيمة المراد تحميلها ، تليها بقية السلسلة ، والتي يتم إرجاعها بعد تحميل القيمة). الأدوات الأساسية الأكثر فائدة ، عادة ما تكون "تعيين سجل" ، "قيمة سجل المتجر على العنوان المشار إليها بواسطة التسجيل" ، إلخ.
يمكننا أن نتراكم من هذه الوظائف البدائية لاكتساب وظائف أعلى مستوى (على غرار منشورتي بعنوان تجريد الاستغلال). على سبيل المثال ، باستخدام أدوات SET-LEGISTER ، وأدوات تخزين القيمة المدمرة ، يمكننا التوصل إلى وظيفة "Poke" ، والتي تتيح لنا تعيين أي عنوان محدد ذي قيمة محددة. باستخدام هذا ، يمكننا إنشاء وظيفة "boke-bork" تتيح لنا تخزين أي سلسلة معينة في أي موقع معين في الذاكرة. الآن وبعد أن نكون لدينا سلسلة من السلسلة ، تم إنجازنا بشكل أساسي ، حيث يمكننا إنشاء أي هياكل نريدها في الذاكرة ، ويمكننا أيضًا استدعاء أي وظائف نريدها بالمعلمات التي نريدها (نظرًا لأننا يمكننا تعيين المسجلة ، ويمكننا وضع القيم على المكدس).
أحد أهم الأسباب للبناء من هذه البدائية ذات الترتيب الأدنى إلى الوظائف الأكبر التي تقوم بأشياء أكثر تعقيدًا ، هو تقليل فرص ارتكاب الأخطاء (وهو أمر شائع في ROP خلاف ذلك).
هناك أفكار وتقنيات ونصائح أكثر تعقيدًا لـ ROP ، ولكن ربما يكون هذا موضوعًا لمذكرة منفصلة ، لفترة مختلفة :)
PS: Gyn لديه مدونة على الاستغلال الموجهة نحو العودة والتي قد تستحق القراءة.
كتب في 27 مايو 2017 ؛ امتدت في 29 مايو 2017
متأثرًا بهذا البث المباشر المذهل من Gynvael Coldwind ، حيث يتحدث عن النظرية الأساسية وراء الغموض الوراثي ، ويبدأ في بناء زورق وراثي أساسي. ثم يشرع في إكمال التنفيذ في هذا البث المباشر.
"المتقدم" الضخمة (مقارنة مع fuzzer الأعمى ، الموصوفة في "أساسيات" المذكرة ". كما أنه يعدل/يتحول بايت وما إلى ذلك ، لكنه يفعل ذلك أكثر ذكاءً قليلاً من الفوتزر "الغبي" الأعمى.
لماذا نحتاج إلى fuzzer وراثي؟
قد تكون بعض البرامج "سيئة" تجاه الزواحف الغبية ، حيث أنه من الممكن أن تتطلب الضعف مجموعة كاملة من الظروف للوصول إليها. في زورق غبي ، لدينا احتمال منخفض للغاية لحدوث هذا لأنه لا يوجد لديه أي فكرة إذا كان يحقق أي تقدم أم لا. كمثال محدد ، إذا كان لدينا الرمز if a: if b: if c: if d: crash! (دعنا نسميها رمز Crasher) ، ثم في هذه الحالة ، نحتاج إلى 4 شروط لتلبية البرنامج. ومع ذلك ، قد لا يتمكن زخرفة غبية من تجاوز الشرط a ، لمجرد وجود فرصة منخفضة للغاية لأن جميع الطفرات الأربعة a ، b ، c ، d ، تحدث في نفس الوقت. في الواقع ، حتى لو تتقدم من خلال القيام به a ، فقد تعود الطفرة التالية إلى !a لمجرد أنها لا تعرف أي شيء عن البرنامج.
انتظر ، متى يظهر هذا النوع من برنامج "Case Case"؟
من الشائع جدًا في محلات تنسيق الملفات ، أن تأخذ مثالًا واحدًا. للوصول إلى بعض مسارات التعليمات البرمجية المحددة ، قد يحتاج المرء إلى تجاوز عمليات الشيكات المتعددة "هذه القيمة يجب أن تكون هذه ، ويجب أن تكون هذه القيمة هي تلك ، ويجب أن تكون بعض القيمة الأخرى شيئًا آخر" وما إلى ذلك. بالإضافة إلى ذلك ، لا يوجد برنامج في العالم الحقيقي تقريبًا "غير معقد" ، ومعظم البرامج لديها العديد من مسارات التعليمات البرمجية الممكنة ، والتي لا يمكن الوصول إليها إلا بعد إعداد العديد من الأشياء في الحالة بشكل صحيح. وبالتالي ، لا يمكن الوصول إلى العديد من مسارات رمز هذه البرامج بشكل أساسي للضربات الغبية. بالإضافة إلى ذلك ، في بعض الأحيان ، قد لا يمكن الوصول إلى بعض المسارات تمامًا (بدلاً من مجرد غير محتملة بجنون) بسبب عدم وجود طفرات كافية على الإطلاق. إذا كان لدى أي من هذه المسارات أخطاء ، فلن يتمكن زورق غبي أبدًا من العثور عليها.
فكيف نفعل أفضل من الفزاء الغبي؟
النظر في الرسم البياني لتدفق التحكم (CFG) لرمز التصادم المذكور أعلاه. إذا كان من الصدفة على الصدفة فجأة a ، فلن يدرك أيضًا أنها وصلت إلى عقدة جديدة ، لكنها ستستمر في تجاهل ذلك ، مع التخلص من العينة. من ناحية أخرى ، فإن ما يفعله AFL (وغيرها من الزوارق الوراثية أو "الذكية") ، هل يتعرفون على هذا كقطعة جديدة من المعلومات ("مسار تم الوصول إليه حديثًا") وتخزين هذه العينة كنقطة أولية جديدة في المجموعة. ما يعنيه هذا هو أنه الآن يمكن أن يبدأ fuzzer من a والتحرك أبعد من ذلك. بالطبع ، في بعض الأحيان ، قد يعود إلى !a من العينة a ، ولكن في معظم الوقت ، لن يكون ، وبدلاً من ذلك قد يكون قادرًا على الوصول إلى كتلة b هذا مرة أخرى هي عقدة جديدة تم الوصول إليها ، لذلك يضيف عينة جديدة في المجموعة. يستمر هذا ، مما يسمح بفحص المزيد والمزيد من المسارات الممكنة ، وأخيراً يصل إلى crash! .
لماذا هذا العمل؟
من خلال إضافة عينات متحولة إلى الجسم ، تستكشف الرسم البياني أكثر (أي أجزاء الوصول التي لم يتم استكشافها من قبل) ، يمكننا الوصول إلى المناطق التي لم يتم الوصول إليها مسبقًا ، وبالتالي يمكن أن تغضب مثل هذه المناطق. نظرًا لأننا نستطيع أن نغضب مثل هذه المناطق ، فقد نتمكن من اكتشاف الحشرات في تلك المناطق.
لماذا يطلق عليه fuzzing الوراثية؟
هذا النوع من الضخمة "الذكية" هو نوع من الخوارزميات الوراثية. الطفرة والتقاطع من العينات تسبب عينات جديدة. نحتفظ بالعينات التي تناسب الظروف التي يتم اختبارها. في هذه الحالة ، فإن الشرط هو "كم عدد العقد في الرسم البياني الذي وصلت إليه؟". تلك التي تجتاز المزيد يمكن الاحتفاظ بها. هذا لا يشبه تمامًا الطحالب الوراثية ، ولكنه اختلاف (نظرًا لأننا نحتفظ بجميع العينات التي تجتاز الأراضي غير المستكشفة ، ونحن لا نفعل كروس) ولكننا مشابهة بما يكفي للحصول على نفس الاسم. في الأساس ، الاختيار من السكان الموجودين مسبقًا ، تليها طفرة ، تليها اختبار اللياقة (سواء رأت مناطق جديدة) ، وتكرار.
انتظر ، لذلك نحن فقط تتبع العقد التي لم يتم الوصول إليها؟
كلا ، ليس حقا. AFL يتبع تتبع الحافة في الرسم البياني ، بدلاً من العقد. بالإضافة إلى ذلك ، لا يقول فقط "حافة سافر أم لا" ، بل تتتبع عدد المرات التي تم فيها اجتياز الحافة. إذا تم اجتياز الحافة 0 ، 1 ، 2 ، 4 ، 8 ، 16 ، مرات ... يعتبر "مسارًا جديدًا" ويؤدي إلى إضافة إلى المجموعة. يتم ذلك لأن النظر إلى الحواف بدلاً من العقد هو وسيلة أفضل للتمييز بين حالات التطبيق ، واستخدام عدد متزايد بشكل كبير في تعبيرية الحافة يعطي المزيد من المعلومات (الحافة التي تم اجتيازها مرة واحدة تختلف تمامًا عن اجتيازها مرتين ، لكن اجتياز 10 لا يختلف عن 11 مرة).
إذن ، ما الذي تحتاجه في فوزر وراثي؟
نحتاج إلى شيئين ، ويسمى الجزء الأول التتبع (أو أجهزة تتبع). إنه يخبرك بشكل أساسي بالتعليمات التي تم تنفيذها في التطبيق. يقوم AFL بهذا بطريقة بسيطة عن طريق القفز بين مراحل التجميع. بعد توليد التجميع ، ولكن قبل تجميع البرنامج ، يبحث عن الكتل الأساسية (من خلال البحث عن النهايات ، من خلال التحقق من نوع التعليمات/الفرع) ، ويضيف رمزًا إلى كل كتلة تمثل الكتلة/الحافة كما تم تنفيذها (ربما في بعض ذاكرة الظل أو شيء ما). إذا لم يكن لدينا رمز مصدر ، فيمكننا استخدام تقنيات أخرى للتتبع (مثل PIN ، Debugger ، إلخ). تبين ، حتى ASAN يمكن أن يقدم معلومات التغطية (انظر المستندات لهذا).
بالنسبة للجزء الثاني ، نستخدم بعد ذلك معلومات التغطية التي قدمها Tracer لتتبع مسارات جديدة عند ظهورها ، ونضيف تلك العينات التي تم إنشاؤها إلى المجموعة للاختيار العشوائي في المستقبل.
هناك آليات متعددة لجعل التتبع. يمكن أن تكون على أساس البرمجيات ، أو قائمة على الأجهزة. بالنسبة للأجهزة ، هناك ، على سبيل المثال ، بعض ميزات وحدة المعالجة المركزية Intel موجودة حيث يتم إعطاء مخزن مؤقت في الذاكرة ، فإنه يسجل معلومات عن جميع الكتل الأساسية التي تم اجتيازها إلى هذا المخزن المؤقت. إنها ميزة kernel ، لذلك يتعين على kernel أن يدعمها وتقديمها كواجهة برمجة تطبيقات (التي تفعلها Linux). بالنسبة للبرمجيات ، يمكننا القيام بذلك عن طريق إضافة رمز ، أو استخدام مصحح تصحيح (باستخدام نقاط التوقف المؤقتة ، أو من خلال التنقل الفردي) ، أو استخدام قدرات تتبع مطهر العناوين ، أو استخدام السنانير ، أو المحاكيات ، أو مجموعة كاملة من الطرق الأخرى.
هناك طريقة أخرى للتمييز بين الآليات إما عن طريق تتبع الصندوق الأسود (حيث يمكنك فقط استخدام الثنائي غير المعدل) ، أو تتبع صندوق البرامج الأبيض (حيث يمكنك الوصول إلى الكود المصدري ، وتعديل الرمز نفسه لإضافة رمز التتبع).
يستخدم AFL أجهزة البرنامج أثناء التجميع كطريقة للتتبع (أو من خلال مضاهاة QEMU). يدعم Honggfuzz كل من طرق التتبع القائمة على البرامج والأجهزة. قد تكون الفوزير الذكية الأخرى مختلفة. الشخص الذي يبنيه Gyn يستخدم التتبع/التغطية التي يوفرها مطهر العنوان (ASAN).
تستخدم بعض fuzzers "speedhacks" (أي زيادة سرعة الغموض) مثل صنع فورسرفر أو غيرها من الأفكار. قد يكون من المفيد النظر في هذه في مرحلة ما :)
كتب في 20 أبريل 2017
متأثرًا بهذا البث المباشر الرائع من Gynvael Coldwind ، حيث يتحدث عن ما يدور حوله ، ويبني أيضًا فوزًا أساسيًا من الصفر!
ما هو fuzzer ، في المقام الأول؟ ولماذا نستخدمه؟
النظر في أن لدينا مكتبة/برنامج يأخذ بيانات الإدخال. قد يتم تنظيم المدخلات بطريقة أو بأخرى (قل PDF ، أو PNG ، أو XML ، إلخ ؛ ولكنه لا يحتاج إلى أن يكون أي تنسيق "قياسي"). من منظور الأمان ، من المثير للاهتمام إذا كان هناك حدود أمان بين المدخلات والعملية / المكتبة / البرنامج ، ويمكننا تمرير بعض "المدخلات الخاصة" التي تسبب سلوكًا غير مقصود وراء تلك الحدود. Fuzzer هو أحد هذه الطرق للقيام بذلك. يقوم بذلك عن طريق "تحور" الأشياء في المدخلات ( وبالتالي إفساده) ، من أجل أن يؤدي إما إلى تنفيذ طبيعي (بما في ذلك الأخطاء التي تمت معالجتها بأمان) أو تصادم. يمكن أن يحدث هذا بسبب عدم معالجة منطق حالة الحافة بشكل جيد.
التعطل هو أسهل طريقة لظروف الخطأ. قد يكون هناك آخرين كذلك. على سبيل المثال ، قد يؤدي استخدام ASAN (Advice Thitizer) وما إلى ذلك إلى اكتشاف المزيد من الأشياء أيضًا ، والتي قد تكون مشكلات أمنية. على سبيل المثال ، قد لا يتسبب تدفق بايت واحد في المخزن المؤقت في حدوث حادث من تلقاء نفسه ، ولكن باستخدام ASAN ، قد نكون قادرين على التقاط هذا مع fuzzer.
هناك استخدام آخر محتمل لـ Fuzzer وهو أن المدخلات التي تم إنشاؤها عن طريق غزو برنامج واحد يمكن أيضًا استخدامها في مكتبة/برنامج آخر ومعرفة ما إذا كانت هناك اختلافات. على سبيل المثال ، لوحظت بعض أخطاء مكتبة الرياضيات عالية الدقة مثل هذا. هذا لا يؤدي عادة إلى مشكلات أمنية ، لذلك لن نركز على هذا كثيرًا.
كيف يعمل fuzzer؟
يعد Fuzzer في الأساس حلقة متكررة متحولة تنفيذي تستكشف مساحة الحالة للتطبيق لمحاولة "العثور على حالات" بشكل عشوائي "عن حدوث عرقلة / أمان. لا يجد استغلال ، مجرد فولن. الجزء الرئيسي من fuzzer هو متحور نفسه. المزيد عن هذا لاحقًا.
مخرجات من fuzzer؟
في Fuzzer ، يرتبط مصحح الأخطاء (أحيانًا) بالتطبيق للحصول على نوع من التقرير من الحادث ، ليكون قادرًا على تحليله لاحقًا باعتباره تحطمًا أمانًا (ولكن ربما يكون مهمًا).
كيف تحدد مجالات البرامج الأفضل لزخرفة أولاً؟
عند الغموض ، نريد عادة التركيز على قطعة واحدة أو مجموعة صغيرة من قطعة البرنامج. عادة ما يتم ذلك بشكل أساسي لتقليل مقدار التنفيذ الذي يتعين القيام به. عادة ، نركز على التحليل والمعالجة فقط. مرة أخرى ، تهم حدود الأمن الكثير في تحديد الأجزاء التي تهمنا.
أنواع الفزات؟
وتسمى عينات الإدخال المعطاة إلى fuzzer المجموعة . في oldschool fuzzers (المعروف أيضًا باسم "أعمى"/"غبي" ، كانت هناك ضرورة لجسم كبير. لا تحتاج الفوزير الأحدث (المعروفة أيضًا باسم "الوراثة" ، على سبيل المثال AFL) ، بالضرورة إلى مثل هذه المجموعة الكبيرة ، لأنها تستكشف الدولة بمفردها.
كيف مفيدة الزوارق؟
الفزاء مفيدة بشكل أساسي "فاكهة منخفضة معلقة". لن يجد الأخطاء المنطقية المعقدة ، ولكن يمكن أن تجد سهلة العثور على الأخطاء (والتي من السهل في بعض الأحيان تفويتها أثناء التحليل اليدوي). على الرغم من أنني قد أقول مدخلات خلال هذه الملاحظة ، وعادة ما أشير إلى ملف الإدخال ، إلا أنه لا يجب أن يكون ذلك. يمكن لـ fuzzers التعامل مع المدخلات التي قد تكون stdin أو ملف إدخال أو مأخذ توصيل الشبكة أو العديد من الآخرين. بدون فقدان الكثير من العمومية ، يمكننا أن نفكر في الأمر مجرد ملف في الوقت الحالي.
كيف تكتب fuzzer (الأساسي)؟
مرة أخرى ، تحتاج فقط إلى أن تكون حلقة متكررة متفرقة. يجب أن نكون قادرين على استدعاء الهدف في كثير من الأحيان ( subprocess.Popen . نحتاج أيضًا إلى أن نكون قادرين على نقل المدخلات إلى البرنامج (على سبيل المثال: الملفات) واكتشاف التعطل ( SIGSEGV وما إلى ذلك تسبب استثناءات يمكن اكتشافها). الآن ، علينا فقط كتابة متحورة لملف الإدخال ، والاستمرار في الاتصال بالهدف على الملفات المتحورة.
طوابير؟ ماذا؟!؟
يمكن أن يكون هناك عدة طفرات ممكنة. قد تكون تلك السهلة (أي بسيطة للتنفيذ) هي تحوير البتات ، أو تحوير البايت ، أو التحول إلى قيم "سحرية". لزيادة فرصة التعطل ، بدلاً من تغيير بت واحد فقط أو شيء من هذا القبيل ، يمكننا تغيير متعددة (ربما بعض النسبة المئوية المعلمة منها؟). يمكننا أيضًا (بدلاً من الطفرات العشوائية) ، تغيير البايت/الكلمات/dwords/إلخ إلى بعض القيم "السحرية". قد تكون القيم السحرية 0 ، 0xff ، 0xffff ، 0xffffffff ، 0x80000000 (32 بت INT_MIN ) ، 0x7fffffff (32 بت INT_MAX ) إلخ. يمكننا كتابة طفرات أكثر ذكاءً إذا كنا نعرف المزيد من المعلومات حول البرنامج (على سبيل المثال ، للأعداد الصحيحة المستندة إلى السلسلة ، قد نكتب شيئًا يغير سلسلة عدد صحيح إلى "65536" أو -1 إلخ). قد تتحرك الطفرات القائمة على القطع القطع (بشكل أساسي ، إعادة تنظيم المدخلات). تعمل طفرات الإضافة/الإلحقة أيضًا (على سبيل المثال ، مما يتسبب في إدخال أكبر في المخزن المؤقت). قد تعمل المباهدات أيضًا (على سبيل المثال ، قد لا يتم التعامل مع EOF في بعض الأحيان بشكل جيد). في الأساس ، جرب مجموعة كاملة من الطرق الإبداعية لالتقاط الأشياء. كلما زادت الخبرة فيما يتعلق بالبرنامج (والاستغلال بشكل عام) ، قد تكون المسطات الأكثر فائدة ممكنة.
ولكن ما هو هذا الغموض "الوراثي"؟
ربما يكون هذا مناقشة لفترة لاحقة. ومع ذلك ، فإن اثنين من الروابط لبعض الفزات الحديثة (مفتوحة المصدر) هي AFL و Honggfuzz.
كتب في 7 أبريل 2017
تأثرت من تحدٍ لطيف في Picoctf 2017 (تم حجب اسم التحدي ، لأن المسابقة لا تزال جارية)
تحذير: قد تبدو هذه المذكرة بسيطة/واضحة لبعض القراء ، لكنها تستلزم القول ، لأن الطبقات لم تكن واضحة بالنسبة لي حتى وقت قريب جدًا.
بالطبع ، عند البرمجة ، نستخدم جميعًا التجريدات ، سواء كانت فصولًا وأشياء أو وظائف أو وظائف تلوي أو تعدد الأشكال أو الموناد أو المسلحين ، أو كل هذه الجاز. ومع ذلك ، هل يمكن أن يكون لدينا شيء من هذا القبيل أثناء الاستغلال؟ من الواضح أنه يمكننا استغلال الأخطاء التي ارتكبت في تنفيذ التجريدات المذكورة أعلاه ، لكن هنا ، أتحدث عن شيء مختلف.
عبر العديد من CTFs ، كلما كتبت استغلالًا سابقًا ، كان نصًا مخصصًا للاستغلال يسقط قذيفة. أستخدم pwntools المذهلة كإطار (للاتصال بالخدمة ، وتحويل الأشياء ، ودينف ، وما إلى ذلك) ، ولكن هذا يتعلق به. تميل كل استغلال إلى أن يكون وسيلة مخصصة للعمل لتحقيق هدف تنفيذ الكود التعسفي. ومع ذلك ، فإن هذا التحدي الحالي ، وكذلك التفكير في ملاحظتي السابقة حول استغلال سلسلة التنسيق "المتقدم" ، جعلني أدرك أنه يمكنني وضع مآثراتي بطريقة متسقة ، والانتقال عبر طبقات التجريد المختلفة للوصول أخيرًا إلى الهدف المطلوب.
على سبيل المثال ، دعنا نعتبر أن الضعف خطأ منطقي ، والذي يتيح لنا القيام بقراءة/كتابة من 4 بايت ، في مكان ما في نطاق صغير بعد المخزن المؤقت. نريد إساءة استخدام هذا طوال الطريق لاكتساب تنفيذ التعليمات البرمجية ، وأخيراً العلم.
في هذا السيناريو ، سأعتبر هذا التجريد بدائيًا short-distance-write-anything . مع هذا نفسه ، من الواضح أننا لا نستطيع أن نفعل الكثير. ومع ذلك ، أقوم بعمل وظيفة Python صغيرة vuln(offset, val) . ومع ذلك ، نظرًا لأن هناك بعض البيانات/البيانات الوصفية التي قد تكون مفيدة ، يمكننا إساءة استخدام هذا لبناء أي read-anywhere write-anything-anywhere . هذا يعني أنني أكتب وظائف Python القصيرة التي تستدعي وظيفة vuln() المحددة مسبقًا. يتم إجراء وظائف get_mem(addr) و set_mem(addr, val) ببساطة (في هذا المثال الحالي) ببساطة باستخدام وظيفة vuln() للكتابة فوق مؤشر ، والتي يمكن بعد ذلك تخزينها في مكان آخر في الثنائي.
الآن ، بعد أن يكون لدينا تجريدات get_mem() و set_mem() ، أقوم بإنشاء تجريد مضاد لـ ASLR ، من خلال تسربان عناوين من خلال get_mem() والمقارنة مع قاعدة بيانات LIBC (شكرًا @niklasb لصنع قاعدة البيانات). تعطيني التعويضات من هذه libc_base بشكل موثوق ، والتي تسمح لي باستبدال أي وظيفة في GOT مع آخر من LIBC.
لقد منحني هذا بشكل أساسي السيطرة على EIP (في اللحظة التي يمكنني فيها "تشغيل" إحدى هذه الوظائف بالضبط عندما أريد ذلك). الآن ، كل ما تبقى بالنسبة لي هو استدعاء الزناد مع المعلمات الصحيحة. لذلك قمت بإعداد المعلمات كتجريد منفصل ، ثم استدعاء trigger() ولدي الوصول إلى النظام على النظام.
TL ؛ DR: يمكن للمرء أن يبني بدائل الاستغلال الصغيرة (التي لا تملك الكثير من القوة) ، ومن خلال دمجها وبناء تسلسل هرمي من البدائية الأقوى ، يمكننا الحصول على تنفيذ كامل.
كتب في 6 أبريل 2017
يتأثر بهذا البث المباشر الرائع من Gynvael Coldwind ، حيث يتحدث عن استغلال سلسلة التنسيق
مآثر سلسلة التنسيق البسيط:
يمكنك استخدام %p لمعرفة ما هو موجود على المكدس. إذا كانت سلسلة التنسيق نفسها على المكدس ، فيمكن للمرء أن يضع عنوانًا (على سبيل المثال FOO ) على المكدس ، ثم ابحث عنه باستخدام محدد الموضع n$ (على سبيل المثال ، قد يعيد AAAA %7$p AAAA 0x41414141 ، إذا كان 7 هو الموضع على المكدس). We can then use this to build a read-where primitive, using the %s format specifier instead (for example, AAAA %7$s would return the value at the address 0x41414141, continuing the previous example). We can also use the %n format specifier to make it into a write-what-where primitive. Usually instead, we use %hhn (a glibc extension, iirc), which lets us write one byte at a time.
We use the above primitives to initially beat ASLR (if any) and then overwrite an entry in the GOT (say exit() or fflush() or ...) to then raise it to an arbitrary-eip-control primitive, which basically gives us arbitrary-code-execution .
Possible difficulties (that make it "advanced" exploitation):
If we have partial ASLR , then we can still use format strings and beat it, but this becomes much harder if we only have one-shot exploit (ie, our exploit needs to run instantaneously, and the addresses are randomized on each run, say). The way we would beat this is to use addresses that are already in the memory, and overwrite them partially (since ASLR affects only higher order bits). This way, we can gain reliability during execution.
If we have a read only .GOT section, then the "standard" attack of overwriting the GOT will not work. In this case, we look for alternative areas that can be overwritten (preferably function pointers). Some such areas are: __malloc_hook (see man page for the same), stdin 's vtable pointer to write or flush , etc. In such a scenario, having access to the libc sources is extremely useful. As for overwriting the __malloc_hook , it works even if the application doesn't call malloc , since it is calling printf (or similar), and internally, if we pass a width specifier greater than 64k (say %70000c ), then it will call malloc, and thus whatever address was specified at the global variable __malloc_hook .
If we have our format string buffer not on the stack , then we can still gain a write-what-where primitive, though it is a little more complex. First off, we need to stop using the position specifiers n$ , since if this is used, then printf internally copies the stack (which we will be modifying as we go along). Now, we find two pointers that point ahead into the stack itself, and use those to overwrite the lower order bytes of two further ahead pointing pointers on the stack, so that they now point to x+0 and x+2 where x is some location further ahead on the stack. Using these two overwrites, we are able to completely control the 4 bytes at x , and this becomes our where in the primitive. Now we just have to ignore more positions on the format string until we come to this point, and we have a write-what-where primitive.
Written on 1st April 2017
Influenced by this amazing live stream by Gynvael Coldwind, where he explains about race conditions
If a memory region (or file or any other resource) is accessed twice with the assumption that it would remain same, but due to switching of threads, we are able to change the value, we have a race condition.
Most common kind is a TOCTTOU (Time-of-check to Time-of-use), where a variable (or file or any other resource) is first checked for some value, and if a certain condition for it passes, then it is used. In this case, we can attack it by continuously "spamming" this check in one thread, and in another thread, continuously "flipping" it so that due to randomness, we might be able to get a flip in the middle of the "window-of-opportunity" which is the (short) timeframe between the check and the use.
Usually the window-of-opportunity might be very small. We can use multiple tricks in order to increase this window of opportunity by a factor of 3x or even up to ~100x. We do this by controlling how the value is being cached, or paged. If a value (let's say a long int ) is not aligned to a cache line, then 2 cache lines might need to be accessed and this causes a delay for the same instruction to execute. Alternatively, breaking alignment on a page, (ie, placing it across a page boundary) can cause a much larger time to access. This might give us higher chance of the race condition being triggered.
Smarter ways exist to improve this race condition situation (such as clearing TLB etc, but these might not even be necessary sometimes).
Race conditions can be used, in (possibly) their extreme case, to get ring0 code execution (which is "higher than root", since it is kernel mode execution).
It is possible to find race conditions "automatically" by building tools/plugins on top of architecture emulators. For further details, http://vexillium.org/pub/005.html
Written on 31st Mar 2017
Influenced by this amazing live stream by Gynvael Coldwind, where he is experimenting on the heap
Use-after-free:
Let us say we have a bunch of pointers to a place in heap, and it is freed without making sure that all of those pointers are updated. This would leave a few dangling pointers into free'd space. This is exploitable by usually making another allocation of different type into the same region, such that you control different areas, and then you can abuse this to gain (possibly) arbitrary code execution.
Double-free:
Free up a memory region, and the free it again. If you can do this, you can take control by controlling the internal structures used by malloc. This can get complicated, compared to use-after-free, so preferably use that one if possible.
Classic buffer overflow on the heap (heap-overflow):
If you can write beyond the allocated memory, then you can start to write into the malloc's internal structures of the next malloc'd block, and by controlling what internal values get overwritten, you can usually gain a read-what-where primitive, that can usually be abused to gain higher levels of access (usually arbitrary code execution, via the GOT PLT , or __fini_array__ or similar).