تحليل الكود القديم عند فقدان الكود المصدري: القيام به أم لا؟

إن تحليل الكود الثنائي ، أي الكود الذي يتم تنفيذه مباشرة بواسطة الجهاز ، هو مهمة غير تافهة. في معظم الحالات ، إذا كنت بحاجة إلى تحليل الشفرة الثنائية ، فسيتم استعادتها أولاً عن طريق تفكيكها ، ثم تفكيكها إلى تمثيل عالي المستوى ، ثم تحليل ما حدث.

هنا يجب أن يقال أن الكود الذي تم استعادته ، وفقًا لتمثيل النص ، ليس لديه الكثير من القواسم المشتركة مع الكود الذي كتبه المبرمج في الأصل وتم تجميعه في ملف قابل للتنفيذ. من المستحيل استرداد الملف الثنائي الذي تم تلقيه من لغات البرمجة المترجمة مثل C / C ++ ، Fortran ، نظرًا لأن هذه مهمة غير معدلة من الناحية الحسابية. في عملية تحويل كود المصدر الذي كتبه المبرمج إلى البرنامج الذي ينفذه الجهاز ، يقوم المترجم بتحويلات لا رجعة فيها.

في التسعينات من القرن الماضي ، كان يعتقد على نطاق واسع أن المترجم ، مثل مفرمة اللحم ، يطحن البرنامج الأصلي ، ومهمة استعادته تشبه مهمة استعادة الكبش من النقانق.


ومع ذلك ، ليس سيئا للغاية. في عملية الحصول على النقانق ، تفقد الأغنام وظيفتها ، بينما يقوم البرنامج الثنائي بحفظها. إذا أمكن تشغيل السجق الناتج والقفز ، فستكون المهام متشابهة.

لذلك ، بمجرد أن يحتفظ البرنامج الثنائي بوظيفته ، يمكننا القول أنه من الممكن استعادة التعليمات البرمجية القابلة للتنفيذ إلى تمثيل عالي المستوى بحيث تكون وظائف البرنامج الثنائي ، الذي لا يوجد تمثيله الأصلي ، والبرنامج الذي تلقينا تمثيله النصي ، متكافئين.

بحكم التعريف ، هناك برنامجان متكافئان من الناحية الوظيفية إذا ، على نفس بيانات الإدخال ، أكملوا أو فشلوا في إكمال التنفيذ ، وإذا تم التنفيذ ، أنتجوا نفس النتيجة.

وعادة ما يتم حلها تفكيك مشكلة في وضع شبه التلقائي، وهذا هو، أخصائي يجعل الانتعاش يدويا باستخدام أدوات تفاعلية، مثل المجمع التفاعلية IDAPro ، radare أو أداة أخرى. علاوة على ذلك ، يتم أيضًا إجراء فك التجميع في الوضع شبه التلقائي. يتم استخدام HexRays أو SmartDecompiler أو أي برنامج فك شفرات آخر مناسب لحل مهمة التفكيك هذه كأداة لفك الشفرة لمساعدة أخصائي.

يمكن جعل استعادة التمثيل النصي الأصلي للبرنامج من كود البايت دقيقًا تمامًا. بالنسبة للغات المفسرة مثل Java أو لغات عائلة .NET ، التي تتم ترجمتها في كود بايت ، يتم حل مهمة فك التجميع بشكل مختلف. نحن لا نفكر في هذه المشكلة في هذه المقالة.

لذلك ، يمكنك تحليل البرامج الثنائية من خلال فك التجميع. عادة ، يتم إجراء مثل هذا التحليل لفهم سلوك البرنامج من أجل استبداله أو تعديله.

من ممارسة العمل مع البرامج القديمة


تتحكم بعض البرامج ، المكتوبة قبل 40 عامًا في عائلة اللغات منخفضة المستوى C و Fortran ، في معدات إنتاج النفط. يمكن أن يكون فشل هذه المعدات أمرًا بالغ الأهمية للإنتاج ، لذا فإن تغيير البرنامج أمر غير مرغوب فيه للغاية. ومع ذلك ، على مدى السنوات الماضية ، فقدت رموز المصدر.

اكتشف موظف جديد في قسم أمن المعلومات ، كانت مسؤوليته فهم كيفية عمله ، أن برنامج مراقبة المستشعر يكتب شيئًا على القرص ببعض الانتظام ، وأنه يكتب وكيف أن هذه المعلومات يمكن استخدامها ليست واضحة. كان لديه أيضًا فكرة أن مراقبة تشغيل المعدات يمكن عرضها على شاشة واحدة كبيرة. للقيام بذلك ، كان من الضروري فهم كيفية عمل البرنامج ، وما هو وبأي تنسيق يكتب على القرص ، وكيف يمكن تفسير هذه المعلومات.

لحل المشكلة ، تم تطبيق تقنية فك التجميع ، متبوعًا بتحليل الكود المستعاد. قمنا أولاً بتفكيك مكونات البرنامج واحدًا تلو الآخر ، ثم قمنا بترجمة الكود المسؤول عن إدخال / إخراج المعلومات ، وبدأنا في التعافي تدريجياً من هذا الكود ، بالنظر إلى التبعيات. بعد ذلك ، تمت استعادة منطق البرنامج ، مما جعل من الممكن الإجابة على جميع أسئلة خدمة الأمن المتعلقة بالبرنامج الذي تم تحليله.

إذا كنت بحاجة إلى تحليل برنامج ثنائي من أجل استعادة منطق تشغيله ، أو استعادة منطق تحويل البيانات المدخلة إلى بيانات الإخراج ، جزئيًا أو كليًا ، وما إلى ذلك ، فمن الملائم القيام بذلك باستخدام مترجم.

بالإضافة إلى هذه المهام ، هناك في الواقع مشاكل في تحليل البرامج الثنائية لمتطلبات أمن المعلومات. علاوة على ذلك ، لا يدرك العميل دائمًا أن هذا التحليل يستغرق وقتًا طويلاً. يبدو ، قم بفك الشفرة وتشغيل الشفرة الناتجة باستخدام محلل ثابت. ولكن نتيجة التحليل النوعي ، فإنه لم ينجح أبدًا.

أولاً ، يجب أن تكون نقاط الضعف المكتشفة غير قادرة على العثور عليها فحسب ، بل يجب تفسيرها أيضًا. إذا تم العثور على الثغرة الأمنية في برنامج بلغة عالية المستوى ، فإن المحلل أو أداة تحليل الشفرة توضح فيه أجزاء التعليمات البرمجية التي تحتوي على عيوب معينة ، والتي تسبب وجودها في الثغرة. ماذا لو لم يكن هناك كود مصدر؟ كيفية إظهار الرمز الذي تسبب في الثغرة الأمنية؟

يسترجع المترجم الشفرة "المبعثرة" بآثار الاسترداد ، ولا جدوى من تحديد الثغرة التي تم الكشف عنها لمثل هذا الرمز ، ولكن لا يوجد شيء واضح. علاوة على ذلك ، فإن التعليمات البرمجية المستعادة غير منظمة بشكل سيء وبالتالي فهي غير صالحة لأدوات تحليل التعليمات البرمجية. من الصعب أيضًا شرح الضعف من حيث البرنامج الثنائي ، لأن الشخص الذي تم شرحه يجب أن يكون على دراية جيدة في التمثيل الثنائي للبرامج.

ثانيًا ، يجب إجراء تحليل ثنائي وفقًا لمتطلبات أمن المعلومات مع فهم ما يجب فعله بالنتيجة الناتجة ، نظرًا لأنه من الصعب جدًا إصلاح ثغرة أمنية في رمز ثنائي ، ولكن لا يوجد رمز مصدر.

على الرغم من جميع الميزات والصعوبات في إجراء تحليل ثابت للبرامج الثنائية وفقًا لمتطلبات أمن المعلومات ، فهناك العديد من الحالات التي يكون فيها هذا التحليل ضروريًا. إذا لم يكن هناك رمز مصدر لسبب ما ، وكان البرنامج الثنائي ينفذ وظائف مهمة لمتطلبات أمن المعلومات ، فيجب التحقق منه. إذا تم العثور على نقاط ضعف ، يجب إرسال مثل هذا التطبيق للمراجعة ، إن أمكن ، أو يجب عمل "غلاف" إضافي له ، مما سيسمح بالتحكم في حركة المعلومات الحساسة.

عندما تم إخفاء الثغرة الأمنية في ملف ثنائي


إذا كانت الشفرة التي يقوم البرنامج بتشغيلها ذات مستوى عالٍ من الأهمية ، حتى إذا كان رمز المصدر للبرنامج بلغة عالية المستوى ، فمن المفيد تدقيق الملف الثنائي. سيساعد هذا على التخلص من الميزات التي قد يقدمها المحول البرمجي عن طريق إجراء تحسينات التحويل. لذلك ، في سبتمبر 2017 ، تمت مناقشة تحويل التحسين الذي أجراه مترجم Clang على نطاق واسع. وكانت نتيجته استدعاء دالة لا يجب استدعاؤها أبدًا.

#include <stdlib.h> typedef int (*Function)(); static Function Do; static int EraseAll() { return system("rm -rf /"); } void NeverCalled() { Do = EraseAll; } int main() { return Do(); } 

نتيجة لتحويلات التحسين ، سيحصل المترجم على رمز المجمع هذا. تم تجميع المثال تحت Linux X86 بعلامة -O2.

  .text .globl NeverCalled .align 16, 0x90 .type NeverCalled,@function NeverCalled: # @NeverCalled retl .Lfunc_end0: .size NeverCalled, .Lfunc_end0-NeverCalled .globl main .align 16, 0x90 .type main,@function main: # @main subl $12, %esp movl $.L.str, (%esp) calll system addl $12, %esp retl .Lfunc_end1: .size main, .Lfunc_end1-main .type .L.str,@object # @.str .section .rodata.str1.1,"aMS",@progbits,1 .L.str: .asciz "rm -rf /" .size .L.str, 9 

يوجد سلوك غير محدد في التعليمات البرمجية المصدر. يتم استدعاء الدالة NeverCalled () بسبب تحويلات التحسين التي يقوم بها المحول البرمجي. أثناء عملية التحسين ، على الأرجح يقوم بإجراء تحليل للحساسية ، ونتيجة لذلك ، تتلقى الدالة Do () عنوان الدالة NeverCalled (). وبما أن الأسلوب main () يستدعي الدالة Do () ، التي لم يتم تعريفها ، وهي سلوك غير معرّف (سلوك غير معرّف) ، تكون النتيجة كما يلي: يتم استدعاء الدالة EraseAll () ، التي تنفذ الأمر rm -rf /.

المثال التالي: نتيجة للتحويلات المثلى للمترجم ، فقدنا الاختيار لمؤشر إلى NULL قبل إلغاء الإشارة إليه.

 #include <cstdlib> void Checker(int *P) { int deadVar = *P; if (P == 0) return; *P = 8; } 

نظرًا لأن السطر 3 يحرف المؤشر عن المؤشر ، فإن المترجم يفترض أن المؤشر ليس صفراً. على خط 4 تتم إزالة عن طريق أداء الأمثل "إزالة كود قابلة للوصول" ، حيث تعتبر المقارنة زيادة الوزن، وبعد أن تم إزالة الصف 3 من قبل المجمع لتعظيم الاستفادة "القضاء كود ميت" (ميت القضاء كود). يبقى السطر 5. فقط. يظهر رمز التجميع الناتج عن تجميع gcc 7.3 تحت Linux x86 بعلامة -O2 أدناه.

  .text .p2align 4,,15 .globl _Z7CheckerPi .type _Z7CheckerPi, @function _Z7CheckerPi: movl 4(%esp), %eax movl $8, (%eax) ret 

الأمثلة أعلاه لعمل تحسين المترجم هي نتيجة لسلوك غير معرف لـ UB في الكود. ومع ذلك ، يعد هذا رمزًا عاديًا تمامًا يعتبره معظم المبرمجين آمنًا. يقضي المبرمجون اليوم وقتًا في القضاء على السلوك غير المحدد في البرنامج ، بينما قبل 10 سنوات لم ينتبهوا إليه. نتيجة لذلك ، قد يحتوي الرمز القديم على ثغرات UB.

لا تكتشف معظم أجهزة تحليل شفرة المصدر الثابتة الحديثة الأخطاء المتعلقة بـ UB. لذلك ، إذا كانت الشفرة تؤدي وظيفة حاسمة لمتطلبات أمن المعلومات ، فمن الضروري التحقق من كل من شفرة المصدر والشفرة التي سيتم تنفيذها.

Source: https://habr.com/ru/post/ar415475/


All Articles