البرنامج المساعد ida_medigate C++ لـ IDA Pro
[جدول المحتويات]
الهندسة العكسية لكود C++ المترجم ليست ممتعة. الهندسة العكسية الثابتة لرمز C++ المترجم أمر محبط. السبب الرئيسي الذي يجعل الأمر صعبًا للغاية هو الوظائف الافتراضية. على النقيض من كود C المترجم، لا يوجد تدفق واضح للتعليمات البرمجية. في كثير من الأحيان يمكن للمرء قضاء الكثير من الوقت في محاولة فهم ما يسمى الوظيفة الافتراضية التالية، بدلا من مجرد رؤية الوظيفة كما هو الحال في كود C المترجم.
عندما يقوم شخص ما بالتحقق من دالة افتراضية، فإن مقدار الوقت الذي يحتاجه أو تحتاج إلى بذل جهد للعثور على xref الخاص بها، ليس منطقيًا.
بعد عدد كبير جدًا من مشاريع C++ RE، استسلمت وقررت أنني بحاجة إلى أداة مرنة (Python) ومستقرة (يمكنني صيانتها بسهولة) لهذا النوع من البحث. تمت كتابة معظم هذا البرنامج الإضافي في يناير 2018، وقررت مؤخرًا إزالة الغبار وإضافة دعم دعم فئات IDA (7.2) الجديدة.
ليس المقصود من هذا البرنامج المساعد أن يعمل دائمًا "خارج الصندوق"، ولكن ليكون أداة أخرى للعكس.
يتكون البرنامج المساعد من جزأين:
لا يعتمد هذا الجزء الأول على الجزء الثاني، لذلك من الممكن استخدام البرنامج الإضافي لإجراء هندسة عكسية لثنائي لا يحتوي على RTTI، عن طريق تحديد تلك الفئات يدويًا استنادًا إلى واجهة برمجة التطبيقات الخاصة بالمكون الإضافي.
ما يجعل البرنامج الإضافي فريدًا هو حقيقة أنه يستخدم نفس البيئة التي يعرفها الباحث بالفعل، ولا يضيف أي قائمة أو كائن جديد، ويعتمد على وحدات بناء IDA المعروفة (الهيكل، الاتحاد، نوع أعضاء الهيكل، إلخ. ) - يتيح ذلك للمكون الإضافي دعم تجريد C++ لكل بنية تدعمها IDA .
ملحوظة: يقوم محلل RTTI بتوزيع x86/x64 g++ RTTI، لكن بنيته تمكن من إضافة دعم لمزيد من البنيات والمترجمين بسهولة.
plugins/
) انسخ medigate_cpp_plugin
إلى دليل plugins
وأضف مسار التعليمات البرمجية المصدر إلى ملف idapythonrc.py
الخاص بك
انسخ plugins/ida-referee/referee.py
إلى نفس الدليل.
بافتراض أن كود المصدر الثنائي الأصلي هو كما يلي ( examples/a.cpp
):
using namespace std ;
class A {
public:
int x_a;
virtual int f_a ()=0;
};
class B : public A {
public:
int x_b;
int f_a (){x_a = 0 ;}
virtual int f_b (){ this -> f_a ();}
};
class Z {
public:
virtual int f_z1 (){cout << " f_z1 " ;}
virtual int f_z2 (){cout << " f_z2 " ;}
};
class C : public B , public Z {
public:
int f_a (){x_a = 5 ;}
int x_c;
int f_c (){x_c = 0 ;}
virtual int f_z1 (){cout << " f_z3 " ;}
};
int main ()
{
C *c = new C ();
c-> f_a ();
c-> f_b ();
c-> f_z1 ();
c-> f_z2 ();
return 0 ;
}
تم تجريد الملف الثنائي ولكنه يحتوي على RTTI.
عندما نقوم فقط بتحميل الملف الثنائي، تبدو الوظيفة main
( sub_84D
في الإصدار 32 بت) كما يلي:
ابدأ تشغيل المحلل اللغوي g++ RTTI وقم بتشغيله باستخدام:
from ida_medigate.rtti_parser import GccRTTIParser
GccRTTIParser.init_parser()
GccRTTIParser.build_all()
الآن قم بتحديث البنية C (راجع قسم الملاحظات)، وقم بتحويل v0
إلى C *
، وقم بفك الترجمة مرة أخرى:
بالنسبة للحالات التي لا يوجد فيها RTTI، فإن بنيتنا التحتية لا تزال قادرة على تحديد فئة c++ يدويًا. لنفس المثال (examples/a32_stripped) يمكنك إنشاء البنية B يدويًا، ثم تحديد جدولها الافتراضي واكتبه
from ida_medigate import cpp_utils
cpp_utils.make_vtable("B")
يمكن لـ make_vtable
أيضًا الحصول على vtable_ea
و vtable_ea_stop
بدلاً من المنطقة المحددة.
ثم قم بإنشاء البنية C، وقم بتطبيق الميراث:
cpp_utils.add_baseclass("C", "B")
يمكنك الآن إعادة إنشاء جدول vtable للفئة C عن طريق تحديده وكتابة:
cpp_utils.make_vtable("C")
أضف البنية Z، وأعد بناء vtable أيضًا، والآن هو الجزء الرائع:
cpp_utils.add_baseclass("C", "Z", 0x0c, to_update=True)
التي تطبق وراثة C لـ Z عند الإزاحة 0x0c وتحديث البنية أيضًا (راجع الملاحظات).
آخر شيء بقي هو تحديث الجدول الثاني من لغة C، والذي يقوم بتنفيذ واجهة Z. قم بتمييز هذا الجدول v واكتب:
cpp_utils.make_vtable("C", offset_in_class=0x0c)
يعرف ida_medigate أن هذا vtable هو vtable من الفئة Z وستكون النتيجة:
النتيجة النهائية هي نفسها كما في حالة RTTI:
عند إنشاء بنية vtable جديدة (بواسطة محلل RTTI يدويًا بواسطة المستخدم) تتم إعادة تسمية كل وظيفة لم تتغير بعد وتفكيكها وتعيين وسيطتها الأولى على this
.
انقر نقرًا مزدوجًا فوق عضو البنية الذي يتوافق مع هذه الوظيفة وسينتقل إلى الوظيفة، بحيث يمكن للمستخدم قراءة تدفق كود C++ بطريقة مريحة!
يتم ربط كل اسم أو نوع تغيير لوظيفة أو عضو مؤشر الوظيفة المقابل لها في vtables ومزامنته فيما بينها جميعًا. هذا يعني على سبيل المثال، أنه يمكن للمستخدم تغيير نوع عضو vtable من خلال نافذة برنامج فك التحويل البرمجي، وسيتم تطبيق هذا النوع الجديد (النموذج الأولي) على الوظيفة المستهدفة أيضًا.
في السطر 15 في الصورة السابقة، هناك استدعاء لـ B::sub_9A8 (B::f_b في الكود المصدري). وسيطة الوظيفة هذه هي B *
:
ولكن يمكن أيضًا استدعاء هذه الوظيفة بواسطة مثيل C
(الترقية). نريد أن نرى الوظيفة الافتراضية التي سيستدعيها مثيلها. افترض أن هناك العديد من الفئات المشتقة المحتملة، لذا فإن إرسال this
إلى C *
ليس ممكنًا دائمًا. لهذا السبب، نقوم بتنفيذ اتحاد لكل فئة أساسية تحتوي على أبناء لديهم جدول افتراضي مختلف. يمكن للمرء اختيار إظهار جدول افتراضي مشتق مختلف لمشتقات B
بالنقر فوق alt+y (الاختصار لاختيار عضو اتحاد مختلف):
لذلك يمكننا في النهاية "إرسال" مكالمات محددة فقط إلى وظيفة افتراضية مختلفة:
الكأس المقدسة للمهندسين العكسيين C++ المحبطين. نحن نحافظ على ملفات xrefs من الوظائف الافتراضية إلى أعضاء بنية vtable التي تمثلهم!
يتيح لنا الجمع بين هذا وبين ida-referee
تتبع جميع ملفات xrefs الخاصة باستدعاءات الوظائف الافتراضية!
القيد: يمكننا فقط تتبع المكالمات الافتراضية التي تم فك ترجمتها بالفعل. لحسن الحظ، يعرف التحليل التلقائي كيفية ملء نوع وسيطة بين الوظائف، لذلك مع العملية التكرارية المتمثلة في إرسال المزيد من الوسائط -> إلغاء ترجمة جميع الوظائف ذات الصلة -> قراءة الكود مرة أخرى وإلقاء المزيد من الوسائط (...) تصبح هذه القدرة حقًا قوي!
الطريقة التي نضع بها علامة على أعضاء البنية كفئات فرعية في IDAPython لا تتم مزامنتها على الفور مع IDB. الاختراق الذي نقوم به هو تعديل البنية بحيث يتم تشغيل المزامنة. يمكنك أيضًا استخدام
utils.refresh_struct(struct_ptr)
الذي يضيف حقلاً وهميًا في نهاية البنية ثم يقوم بإلغاء تعريفه.