كتابة محاكي x86 بسيط مع IDAPython اا Writing a simple x86 emulator with IDAPython
في كثير من الأحيان، عندما أجد سكربتات IDAPython، لاحظت أنهم يستخدمون الـ API الخاصة بـ IDAPython بطريقة غير فعالة/غير صحيحة لتفكيك-disassemble أو فك ترميز-decode التعليمات [على سبيل المثال باستخدام ()idc.GetMnem أو ()idc.GetDisasm ]. لذلك في هذه التدوينة، سأوضح كيفية استخدام دوال فك ترميز التعليمات التي توفرها اداة IDA من خلال IDAPython لكتابة محاكي x86 بسيط للغاية. الهدف من ذلك هو توضيح الاستخدام الصحيح للـ API التي تختص بفك ترميز التعليمات (او instruction decoding) من خلال IDAPython. بحلول نهاية هذه التدوينة، يجب أن تكون قادرًا على حل مشكلات مماثلة باستخدام IDAPython.
دعنا نبدأ مع عينة البرنامج التالي الذي يحتوي على جدول يضم 12 دالة يتم استدعاءها في loop ويتم عرض النتيجة. هدفنا هو كتابة محاكي يمكنه حساب قيمة التحدي بشكل ستاتيكي دون استخدام محاكي طرف ثالث او محاكي خارجي.
[/align]
في كثير من الأحيان، عندما أجد سكربتات IDAPython، لاحظت أنهم يستخدمون الـ API الخاصة بـ IDAPython بطريقة غير فعالة/غير صحيحة لتفكيك-disassemble أو فك ترميز-decode التعليمات [على سبيل المثال باستخدام ()idc.GetMnem أو ()idc.GetDisasm ]. لذلك في هذه التدوينة، سأوضح كيفية استخدام دوال فك ترميز التعليمات التي توفرها اداة IDA من خلال IDAPython لكتابة محاكي x86 بسيط للغاية. الهدف من ذلك هو توضيح الاستخدام الصحيح للـ API التي تختص بفك ترميز التعليمات (او instruction decoding) من خلال IDAPython. بحلول نهاية هذه التدوينة، يجب أن تكون قادرًا على حل مشكلات مماثلة باستخدام IDAPython.
- جدول المحتويات
- نظرة عامة
- عمل Disassembly للبرنامج
- كتابة المحاكي
- مقدمة سريعة لفك ترميز التعليمات - instruction decoding
- تحديد نطاق دوال البرنامج
- محاكاة التعليمات
- نظرة عامة
في الحياة الواقعية، يمكن للمرء أن يجد نفسه في موقف مماثل يرغب في التعامل مع دالة كانها صندوق أسود بدلاً من عكسها مرة أخرى إلى pseudo-code. في مثل هذه الحالات، الشخص لديه العديد من الخيارات:
دعنا نعمل compile لهذا البرنامج ونعمل عليه باستخدام c1 = 123 و c2 = 456:
لنقم بعمل Disassembly للبرنامج الذي نختبرة وتحديد موقع جدول challenge_funcs و أول دالة للبرنامج:
- استخدم Appcall أثناء وقت التشغيل والاستعلام عن الدالة مباشرة.
- استخدم decompiler لاستخراج الخوارزمية على شكل كود C زائف (C pseudo-code) وعمل recompile له ثم استخدمه.
- استخراج تعليمات الاسمبلي للدالة وإعادة تجميعها-reassemble من ثم استخدامها.
- استخدم برنامج للمحاكاة ( Unicorn engine على سبيل المثال) لعمل محاكاة للدالة.
دعنا نعمل compile لهذا البرنامج ونعمل عليه باستخدام c1 = 123 و c2 = 456:
- عمل Disassembly للبرنامج
يمكننا تحديد موقع جدول challenge_funcs بسهولة لأنه يتم الرجوع إليه من ()main . تتميز دالة البرنامج الأولى، مثلها مثل كل الدوال الأخرى، بتنسيق/نمط متميز تمامًا والذي سنبني عليه تصميم المحاكي.
يمكننا أن نرى بوضوح تعليمة pusha (في 0x401009)، متبوعًا بتعليمتين يقومان بتحميل القيم الأولية (في 0x40100A و 0x40100D) ، ثم سلسلة من العمليات (بين 0x401010 و 0x401076) على تلك المسجلات وأخيراً نرى النتائج يتم نسخها مرة أخرى إلى المتغيرات المحلية (في 0x401078 و 0x40107B) قبل استخدام popa لاستعادة جميع المسجلات.
سوف نستخدم نمط الكود هذا لكتابة دالة صغيرة تحدد حدود التعليمات التي تقوم بالحساب-computation. سنقوم بعد ذلك بكتابة دالة أخرى تحاكي الكود البرمجي في نطاق معين وتعيد النتيجة.
في هذا القسم، سنقوم بعمل دالتين:
قبل البدء، دعونا نحدد بعض المتغيرات العامة التي يحتاجها السكربت:
لا أعرف السبب ،ولكن يتم استخدام البادئة NN_ prefix لجميع أنواع التعليمات على معالج x86/x64.
إليك مثال بسيط على كيفية فك الترميز والتحقق من نوع التعليمات:
يمكننا أن نرى بوضوح تعليمة pusha (في 0x401009)، متبوعًا بتعليمتين يقومان بتحميل القيم الأولية (في 0x40100A و 0x40100D) ، ثم سلسلة من العمليات (بين 0x401010 و 0x401076) على تلك المسجلات وأخيراً نرى النتائج يتم نسخها مرة أخرى إلى المتغيرات المحلية (في 0x401078 و 0x40107B) قبل استخدام popa لاستعادة جميع المسجلات.
سوف نستخدم نمط الكود هذا لكتابة دالة صغيرة تحدد حدود التعليمات التي تقوم بالحساب-computation. سنقوم بعد ذلك بكتابة دالة أخرى تحاكي الكود البرمجي في نطاق معين وتعيد النتيجة.
- كتابة المحاكي
- ()scope_challenge_function: تعثر هذه الدالة على حدود التعليمات المراد محاكاتها.
- ()emulate_challenge_function: تحاكي هذه الدالة التعليمات ضمن نطاق معين.
استنتجنا جدول دوال البرنامج وحجمه من خلال عملية الـdisassembly أعلاه. نحدد أيضًا متغير RESULTS الذي يحتوي على ناتج استدعاء كل دالة باستخدام c1 = 123 و c2 = 456. سنستخدم هذا الجدول للتحقق من عملية المحاكاة بعد الانتهاء.
لتعداد جميع دوال البرنامج في الجدول، يمكننا القيام بشيء مثل:
لتعداد جميع دوال البرنامج في الجدول، يمكننا القيام بشيء مثل:
والمخرجات هي:
- مقدمة سريعة لفك ترميز التعليمات - instruction decoding
لفك ترميز التعليمات باستخدام IDAPython ، استخدم الدالة ()idautils.DecodeInstruction:
إذا عملية فك الترميز فشلت، فستُرجع هذه الوظيفة None. في حالة نجاح فك الترميز، نحصل على كائن تعليمي يحتوي على معلومات حول التعليمة ومعاملاتها.
هذه هي ميزات التعليمات الهامة:
قد تتساءل ما هي العلاقة بين opcode و itype؟ الجواب بسيط. في IDA، تكون وحدة قاعدة البيانات المفتوحة للمعالجات ( او the open database’s processor module ) مسؤولة عن ملء حقل itype استنادًا إلى opcode. في IDA SDK يمكنك العثور على ملف header يسمى allins.hpp. يحتوي ملف الـ header هذا على تعدادات لكافة وحدات المعالجات المدعومة مع enum members لكل تعليمة مدعومة:
هذه هي ميزات التعليمات الهامة:
- inst.itype: هذا عدد صحيح يمثل نوع التعليمات. ألـopcodes المختلفة لها نفس الـ itype وبالتالي opcode != itype.
- inst.size: هذا هو حجم التعليمات البرمجية المراد فك ترميزها.
- inst.Operands: هذه مصفوفة تستند إلى صفر تحتوي على معلومات المعاملات.
- inst.Op1 .. OpN: هذه هي الأسماء المستعارة المستندة إلى 1 في مصفوفة Operands.
- inst.ea : العنوان الخطي للتعليمات البرمجية المراد فك ترميزها
لا أعرف السبب ،ولكن يتم استخدام البادئة NN_ prefix لجميع أنواع التعليمات على معالج x86/x64.
إليك مثال بسيط على كيفية فك الترميز والتحقق من نوع التعليمات:
يمكن للمرء أن يتحقق بشكل حدسي من التعليمة التي تم فك ترميزها عن طريق المقارنة مع أحد ثوابت idaapi.NN_xxxx.
بالنسبة للمعاملات، يمكن للمرء الوصول إليها عبر inst.Operands أو inst.OpN. للحصول على عدد المعامِلات المستخدمة بواسطة التعليمة البرمجية التي تم فك ترميزها، يجب ألا تعتمد على طول Operands array لأنه سيتم تثبيتها دائمًا على UA_MAXOP == 8 (راجع ida.hpp). بدلاً من ذلك، قم بالنظر مراراً على كل معامل ومعرفة ما إذا كان نوعه o_void.
يتم تعريف معامل التعليمة باستخدام نوع بنية op_t المحدد في ملف الهيدر ua.hpp.
بعض أعضاء المعاملات هي:
]هذه هي أنواع المعاملات المدعومة (o_xxx):
هناك معاملات إضافية تختلف معانيها بناءً على نوع المعامل:
عندما يكون نوع المعامل o_reg أو o_phrase ، فإن قيم op.reg / op.phrase تحتوي على قيمة التعداد الخاصة بالمسجل. مثل مصطلحات NN_xxx، يوفر IDA SDK أيضًا أسماء المسجلات الثابتة وقيمها؛ ولكن هذا صحيح فقط بالنسبة إلى وحدة المعالج x86/x64. إليك مقتطف من ملف الهيدر intel.hpp:
بالنسبة للمعاملات، يمكن للمرء الوصول إليها عبر inst.Operands أو inst.OpN. للحصول على عدد المعامِلات المستخدمة بواسطة التعليمة البرمجية التي تم فك ترميزها، يجب ألا تعتمد على طول Operands array لأنه سيتم تثبيتها دائمًا على UA_MAXOP == 8 (راجع ida.hpp). بدلاً من ذلك، قم بالنظر مراراً على كل معامل ومعرفة ما إذا كان نوعه o_void.
يتم تعريف معامل التعليمة باستخدام نوع بنية op_t المحدد في ملف الهيدر ua.hpp.
بعض أعضاء المعاملات هي:
- op.flags: معاملات الرايات.
- op.dtype: نوع المعامل. أحد ثوابت dt_xxx. يمكن للشخص استخدام هذا الحقل لمعرفة حجم المعامل (1 == dt_byte ، 2 == dt_word ، إلخ).
- op.type: نوع المعامل. أحد الثوابت.
- o_xxx. specflag1 .. specflag4: رايات محددة للمعالج.
- o_void: لا يوجد مُعامل.
- o_reg: المعامل هو مسجل (al، ax، es، ds ...).
- o_mem: مرجع الذاكرة المباشر (DATA).
- o_phrase: مرجع الذاكرة [Reg Reg + Index Reg].
- o_displ: ذاكرة مسجل [Base Reg + Index Reg + Displacement].
- o_imm: قيمة فورية.
- o_far: العنوان البعيد الفوري (CODE).
- o_near: العنوان القريب الفوري (CODE).
- o_idpspec0 .. o_idpspec5: رايات محددة للمعالج.
- op.reg: رقم المسجل (o_reg).
- op.phrase: مسجل الفهرسة مع معاملات الوصول الى الذاكرة (o_phrase).
- op.value: القيمة الفورية (o_imm) أو الإزاحة الخارجية (o_displ).
- op.addr: عنوان الذاكرة المستخدم من قبل المعامل ( o_mem, o_far, o_displ, o_near).
لسوء الحظ، هذه الـ enums غير متوفرة لـ IDAPython ، لكننا على الأقل نعرف ما يكفي لتحديد شيء مثل التالي:
إليك مثال آخر على كيفية "disassemble" تعليمة بالكامل:
هكذا تم تغطية مبادئ فك ترميز التعليمات. يرجى الرجوع إلى ملفات الهيدر intel.hpp و allins.hpp و ua.hpp و idp.hpp لمزيد من المعلومات.
- تحديد نطاق دوال البرنامج
في وقت سابق، اكتشفنا كيفية تجاوز جدول دوال البرنامج واسترداد عنوان كل ودالة. دعنا الآن نكتب دالة تستخدم وحدة فك ترميز التعليمات للعثور على حدود التعليمات التي يجب محاكاتها.
يرجى ملاحظة أنه يمكنني استخدام ()FindBinary في IDAPython ولكن هذا ينافي الغرض من هذه الدوينة. لأغراض العرض التوضيحي، أرغب في العثور على نمط الكود البرمجي المعني عن
طريق استخدام فك ترميز التعليمات فقط:
يرجى ملاحظة أنه يمكنني استخدام ()FindBinary في IDAPython ولكن هذا ينافي الغرض من هذه الدوينة. لأغراض العرض التوضيحي، أرغب في العثور على نمط الكود البرمجي المعني عن
طريق استخدام فك ترميز التعليمات فقط:
يتمثل النمط الأساسي عند فك ترميزالتعليمات في تقديم عنوان فك الترميز (متغير ea في هذه الحالة) من خلال inst.size بعد كل عملية فك ترميز ناجحة. بعد ذلك، ينبغي للمرء التحقق من نوع التعليمات، ثم تفقد المعاملات وفقًا لذلك.
لاحظ أنه في المرحلة رقم 2، انا بدأت في فك التشفير للخلف. للرجوع إلى الخلف في قائمة الـ disassembly بشكل مناسب، يمكن استخدام دالة ()idc.PrevHead للحصول على عنوان البدء للتعليمات المحددة مسبقًا (راجع السطر 37). دعنا نختبر هذه الدالة:
لاحظ أنه في المرحلة رقم 2، انا بدأت في فك التشفير للخلف. للرجوع إلى الخلف في قائمة الـ disassembly بشكل مناسب، يمكن استخدام دالة ()idc.PrevHead للحصول على عنوان البدء للتعليمات المحددة مسبقًا (راجع السطر 37). دعنا نختبر هذه الدالة:
- محاكاة التعليمات
في الخطوة السابقة، تمكنا من الحصول على عنوان البداية والنهاية للحدود المراد محاكاتها. الآن دعنا نكتب دالة محاكاة بسيطة تدعم فقط
مجموعة محدودة من التعليمات (NOT ، DEC ، INC ، XOR ، SUB و ADD):
مجموعة محدودة من التعليمات (NOT ، DEC ، INC ، XOR ، SUB و ADD):
عندما تبدأ الدالة، يتم ملء قاموس regs بالقيم الأولية للمسجلات. نحن نستخدم op.reg كمفتاح في هذا القاموس. سيحتوي أي مسجل غير مهيأ على القيمة صفر. دالة المحاكاة بعد ذلك تدخل في loop وتفك ترميز كل تعليمة. لكل تعليمة، تقوم بفحص نوعها (لمعرفة العملية التي يجب محاكاتها) ومعاملاتها (لمعرفة كيفية استرداد القيم المطلوبة). في نهاية الحلقة ، يتم إرجاع قيمة 64 بت.
يمكننا التحقق من صحة المحاكاة من خلال مقارنة النتائج التي يتم إرجاعها من المحاكاة بالنتائج التي حصلنا عليها سابقًا:
يمكننا التحقق من صحة المحاكاة من خلال مقارنة النتائج التي يتم إرجاعها من المحاكاة بالنتائج التي حصلنا عليها سابقًا:
آمل أن تكونوا قد وجدتم هذه التدوينه مفيدة. لا تتردد في طرح الأسئلة و/أو الإشارة إلى الأخطاء في هذه التدونة. يمكنك تنزيل الملفات المستخدمة في هذه المقالة من الملفات المرفقة
باس الملف المرفق
Password: 123
-----------------------------------
المقالة مترجمة من http://0xeb.net/2018/02/writing-a-simple...idapython/
لا تتردد في طرح الأسئلة و/أو الإشارة إلى الأخطاء في هذه الترجمة
لا تنسوني ووالدي من الدعاء
باس الملف المرفق
Password: 123
-----------------------------------
المقالة مترجمة من http://0xeb.net/2018/02/writing-a-simple...idapython/
لا تتردد في طرح الأسئلة و/أو الإشارة إلى الأخطاء في هذه الترجمة
لا تنسوني ووالدي من الدعاء

سبحان الله وبحمده، سبحان الله العظيم