
مرحبا بالجميع
تستمر سلسلة من المقالات حول كتابة أشياء مفيدة مختلفة لـ IDA Pro . آخر مرة قمنا بإصلاح وحدة المعالج ، واليوم سنركز على كتابة وحدة تحميل (محمل) لنظام تشغيل واحد قديم ، ألا وهو AmigaOS . سنكتب في بيثون. سأحاول أيضًا الكشف عن بعض الخفايا الدقيقة عند العمل مع إعادة الترحيل (وهي أيضًا relocations
) ، والتي توجد في العديد من الملفات القابلة للتنفيذ ( PE
، ELF
، MS-DOS
، إلخ).
مقدمة
أولئك الذين عملوا سابقًا مع تنسيق Amiga Hunk (في AmigaOS ، هذا هو اسم الكائنات التي تحتوي على رمز قابل للتنفيذ: executable
- ، ملفات library
، وما إلى ذلك) وتحميل ملف واحد على الأقل من هذا القبيل في IDA ، ربما رأوا أن محمل التمهيد موجود بالفعل (علاوة على ذلك ، هناك مصادر حتى في IDA SDK
):


نعم ، بالفعل ، تم كتابة كل شيء أمامنا بالفعل ، ولكن ... كل شيء يتم تنفيذه بشكل سيء لدرجة أنه يصبح من المستحيل ببساطة العمل على الأقل مع بعض الملفات العادية القابلة للتنفيذ.
قضايا التنفيذ الحالية
حتى هنا قائمة القضايا:
- الترحيل. في ملفات Amiga Hunk ، يعد وجودها ممارسة طبيعية. وفي التطبيق الحالي ، يتم تطبيقها عند تنزيل ملف. ولكن هذا لا يتم دائمًا بشكل صحيح (قد لا يتم حساب الارتباط الناتج بشكل صحيح).
بالإضافة إلى ذلك ، لن تتمكن من تنفيذ " برنامج Rebase ... ". هذه الوظيفة غير موجودة في اللودر. - يتم تنزيل الملف على العنوان الأساسي
0x00000000
. هذا خطأ بالتأكيد ، حيث يتم تحميل مكتبات النظام المختلفة بدون تعويض. ونتيجة لذلك ، يتم إنشاء روابط لهذه المكتبات في مساحة العنوان للملف الذي تم تنزيله - يمكن تعيين المُحمل بأعلام متنوعة لا تسمح (أو ، على العكس ، تسمح) للمؤسسة الدولية للتنمية بتنفيذ إجراءات "التعرف" معينة: تحديد المؤشرات ، والمصفوفات ، وتعليمات التجميع.
في المواقف التي لا تتعلق فيها بـ x86 / x64 / ARM ، غالبًا بعد تنزيل ملف ، تبدو قائمة التجميع وكأنك تريد إغلاق IDA من لمحة (وكذلك احذف الملف الذي تم التحقيق فيه وتعلم كيفية استخدام radare2) . والسبب في ذلك هو علامات أداة تحميل التشغيل الافتراضية.

كتابة قالب محمل إقلاع
في الواقع ، كتابة محمل الإقلاع أمر سهل. هناك ثلاث عمليات استدعاء يجب تنفيذها:
1) accept_file(li, filename)
من خلال هذه الوظيفة ، تحدد المؤسسة الدولية للتنمية ما إذا كان يمكن استخدام هذا المحمل لتنزيل filename
def accept_file(li, filename): li.seek(0) tag = li.read(4) if tag == 'TAG1':
2) load_file(li, neflags, format)
هنا ، يتم تنزيل محتويات الملف إلى قاعدة البيانات ، وإنشاء الأجزاء / الهياكل / الأنواع ، واستخدام إعادة الترحيل والإجراءات الأخرى.
def load_file(li, neflags, format):
3) move_segm(frm, to, sz, fileformatname)
إذا كانت هناك عمليات نقل في الملف الذي تم تنزيله ، فلا يمكنك فقط أخذ العنوان الأساسي وتغييره. تحتاج إلى إعادة سرد جميع عمليات الترحيل وتصحيح الروابط. بشكل عام ، سيكون الرمز هو نفسه دائمًا. هنا ننتقل فقط إلى جميع عمليات إعادة التكوين التي تم إنشاؤها سابقًا ، ونضيف دلتا إليها ونطبق تصحيحات على وحدات بايت الملف الذي تم تنزيله.
def move_segm(frm, to, sz, fileformatname): delta = to xEA = ida_fixup.get_first_fixup_ea() while xEA != idaapi.BADADDR: fd = ida_fixup.fixup_data_t(idaapi.FIXUP_OFF32) ida_fixup.get_fixup(xEA, fd) fd.off += delta if fd.get_type() == ida_fixup.FIXUP_OFF8: idaapi.put_byte(xEA, fd.off) elif fd.get_type() == ida_fixup.FIXUP_OFF16: idaapi.put_word(xEA, fd.off) elif fd.get_type() == ida_fixup.FIXUP_OFF32: idaapi.put_long(xEA, fd.off) fd.set(xEA) xEA = ida_fixup.get_next_fixup_ea(xEA) idaapi.cvar.inf.baseaddr = idaapi.cvar.inf.baseaddr + delta return 1
قالب Bootloader import idaapi import ida_idp import ida_fixup def accept_file(li, filename): li.seek(0) tag = li.read(4) if tag == 'TAG1':
كتابة كود محمل الإقلاع الرئيسي
لذا ، مع تسوية الأساسيات. دعونا نعد "مكان عمل" لأنفسنا. لا أعرف ما الذي يحب أي شخص كتابة التعليمات البرمجية له في Python ، لكني أحب القيام بذلك في PyCharm. لنقم بإنشاء مشروع جديد وإضافة الدليل من المؤسسة الدولية للتنمية في المسارات للبحث عن عمليات الاستيراد:

ربما سمع الأشخاص الذين واجهوا بالفعل مشكلة محاكاة الملفات القابلة للتنفيذ لـ AmigaOS بمشروع مثل amitools . يحتوي على مجموعة كاملة تقريبًا من الأدوات للعمل مع Amiga Hunk (لكل من المحاكاة والتحليل فقط). أقترح على أساسها القيام "محمل الإقلاع" (يسمح ترخيص المشروع ، وسيكون محمل الإقلاع غير ربحي).
بعد بحث قصير عن amitools
تم العثور على ملف amitools
. يطبق تحليل ملف ، وتحديد الأجزاء ، وعمليات النقل ، وأكثر من ذلك بكثير. في الواقع ، ملف Relocate.py مسؤول عن استخدام التراكبات.
الآن الجزء الأصعب: من كل شجرة ملفات amitools
، amitools
سحب كل ما هو مشار إليه في BinFmtHunk.py
و Relocate.py
إلى ملف برنامج تحميل التشغيل لدينا ، وإجراء تصحيحات طفيفة في بعض الأماكن.
شيء آخر أريد إضافته هو تعريف كل جزء من الموضع في الملف الذي تم تنزيل البيانات منه. يتم ذلك عن طريق إضافة سمة data_offset
إلى فئتين: HunkSegmentBlock
و HunkOverlayBlock
. اتضح الرمز التالي:
HunkSegmentBlock class HunkSegmentBlock(HunkBlock): """HUNK_CODE, HUNK_DATA, HUNK_BSS""" def __init__(self, blk_id=None, data=None, data_offset=0, size_longs=0): HunkBlock.__init__(self) if blk_id is not None: self.blk_id = blk_id self.data = data self.data_offset = data_offset self.size_longs = size_longs def parse(self, f): size = self._read_long(f) self.size_longs = size if self.blk_id != HUNK_BSS: size *= 4 self.data_offset = f.tell() self.data = f.read(size)
HunkOverlayBlock class HunkOverlayBlock(HunkBlock): """HUNK_OVERLAY""" blk_id = HUNK_OVERLAY def __init__(self): HunkBlock.__init__(self) self.data_offset = 0 self.data = None def parse(self, f): num_longs = self._read_long(f) self.data_offset = f.tell() self.data = f.read(num_longs * 4)
نحتاج الآن إلى إضافة هذه السمة إلى فئة Segment
، والتي تم إنشاؤها لاحقًا من كتل Hunk
:
الجزء class Segment: def __init__(self, seg_type, size, data=None, data_offset=0, flags=0): self.seg_type = seg_type self.size = size self.data_offset = data_offset self.data = data self.flags = flags self.relocs = {} self.symtab = None self.id = None self.file_data = None self.debug_line = None
بعد ذلك ، بالنسبة لفئة BinFmtHunk
، نضيف استخدام data_offset
عند إنشاء الشرائح. يتم ذلك في طريقة create_image_from_load_seg_file
في حلقة تعداد كتلة المقطع:
create_image_from_load_seg_file segs = lsf.get_segments() for seg in segs:
كتابة رمز لاسترجاعات IDA
الآن بعد أن أصبح لدينا كل ما نحتاجه ، دعنا نكتب رمزًا لرد الاتصال. الملف الأول سيتم accept_file
:
Accept_file def accept_file(li, filename): li.seek(0) bf = BinFmtHunk() tag = li.read(4) tagf = StringIO.StringIO(tag) if bf.is_image_fobj(tagf): return {'format': 'Amiga Hunk executable', 'processor': '68040'} else: return 0
كل شيء بسيط: نقرأ البايتات الأربعة الأولى StringIO
منها ملفًا افتراضيًا ( StringIO
) StringIO
إلى دالة is_image_fobj
، التي تُرجع True
إذا كان الملف بالتنسيق المناسب. في هذه الحالة ، نعيد القاموس مع حقلين: format
(الوصف النصي للتنسيق المحمل) processor
(الذي يتم بموجبه كتابة الرمز القابل للتنفيذ).
بعد ذلك ، تحتاج إلى تحميل الملف إلى IDB. إنه أكثر تعقيدًا. أول شيء يجب فعله هو فرض نوع المعالج على Motorola 68040
المطلوب:
idaapi.set_processor_type('68040', ida_idp.SETPROC_LOADER)
قم بتعيين الإشارات إلى أداة تحميل التشغيل بحيث لا يتم التعرف على أي لعبة ولا يتم عمل الصفائف من كل شيء على التوالي (يمكن العثور على وصف للأعلام هنا ):
idaapi.cvar.inf.af = idaapi.AF_CODE | idaapi.AF_JUMPTBL | idaapi.AF_USED | idaapi.AF_UNK | \ idaapi.AF_PROC | idaapi.AF_LVAR | idaapi.AF_STKARG | idaapi.AF_REGARG | \ idaapi.AF_TRACE | idaapi.AF_VERSP | idaapi.AF_ANORET | idaapi.AF_MEMFUNC | \ idaapi.AF_TRFUNC | idaapi.AF_FIXUP | idaapi.AF_JFUNC | idaapi.AF_NULLSUB | \ idaapi.AF_NULLSUB | idaapi.AF_IMMOFF | idaapi.AF_STRLIT
ننقل محتويات الملف الذي تم تنزيله إلى BinFmtHunk
(التحليل وكل ذلك):
li.seek(0) data = li.read(li.size()) bf = BinFmtHunk() fobj = StringIO.StringIO(data) bi = bf.load_image_fobj(fobj)
مع عدم وجود عنوان تنزيل ، أقترح التعامل مع اختيار ImageBase
آخر. بالمناسبة ، يتم تنزيل الملفات القابلة للتنفيذ في AmigaOS فقط في عناوين يمكن الوصول إليها ، ولا توجد عناوين افتراضية هناك. اخترت 0x21F000
، إنه جميل ومن غير المحتمل أن يتزامن مع أي ثابت. تطبيقه:
rel = Relocate(bi)
أضف عنوان البداية الذي يبدأ منه البرنامج:
حان الوقت لتحميل المقاطع في قاعدة البيانات وإنشاء تراكبات (في مصطلحات المؤسسة الدولية للتنمية: reloc == fixup
):
for seg in bi.get_segments(): offset = addrs[seg.id] size = seg.size to_segs = seg.get_reloc_to_segs() for to_seg in to_segs: reloc = seg.get_reloc(to_seg) for r in reloc.get_relocs(): offset2 = r.get_offset() rel_off = Relocate.read_long(datas[seg.id], offset2) addr = offset + rel_off + r.addend fd = idaapi.fixup_data_t(idaapi.FIXUP_OFF32) fd.off = addr fd.set(offset + offset2) idaapi.mem2base(str(datas[seg.id]), offset, seg.data_offset) idaapi.add_segm(0, offset, offset + size, 'SEG_%02d' % seg.id, seg.get_type_name())
النتيجة النهائية load_file
:
return 1
move_segm
رمز move_segm
فقط دون تغييرات.
رمز ملخص برنامج bootloader والاستنتاجات
amiga_hunk.py import idaapi import ida_idp import ida_fixup import StringIO import struct HUNK_UNIT = 999 HUNK_NAME = 1000 HUNK_CODE = 1001 HUNK_DATA = 1002 HUNK_BSS = 1003 HUNK_ABSRELOC32 = 1004 HUNK_RELRELOC16 = 1005 HUNK_RELRELOC8 = 1006 HUNK_EXT = 1007 HUNK_SYMBOL = 1008 HUNK_DEBUG = 1009 HUNK_END = 1010 HUNK_HEADER = 1011 HUNK_OVERLAY = 1013 HUNK_BREAK = 1014 HUNK_DREL32 = 1015 HUNK_DREL16 = 1016 HUNK_DREL8 = 1017 HUNK_LIB = 1018 HUNK_INDEX = 1019 HUNK_RELOC32SHORT = 1020 HUNK_RELRELOC32 = 1021 HUNK_ABSRELOC16 = 1022 HUNK_PPC_CODE = 1257 HUNK_RELRELOC26 = 1260 hunk_names = { HUNK_UNIT: "HUNK_UNIT", HUNK_NAME: "HUNK_NAME", HUNK_CODE: "HUNK_CODE", HUNK_DATA: "HUNK_DATA", HUNK_BSS: "HUNK_BSS", HUNK_ABSRELOC32: "HUNK_ABSRELOC32", HUNK_RELRELOC16: "HUNK_RELRELOC16", HUNK_RELRELOC8: "HUNK_RELRELOC8", HUNK_EXT: "HUNK_EXT", HUNK_SYMBOL: "HUNK_SYMBOL", HUNK_DEBUG: "HUNK_DEBUG", HUNK_END: "HUNK_END", HUNK_HEADER: "HUNK_HEADER", HUNK_OVERLAY: "HUNK_OVERLAY", HUNK_BREAK: "HUNK_BREAK", HUNK_DREL32: "HUNK_DREL32", HUNK_DREL16: "HUNK_DREL16", HUNK_DREL8: "HUNK_DREL8", HUNK_LIB: "HUNK_LIB", HUNK_INDEX: "HUNK_INDEX", HUNK_RELOC32SHORT: "HUNK_RELOC32SHORT", HUNK_RELRELOC32: "HUNK_RELRELOC32", HUNK_ABSRELOC16: "HUNK_ABSRELOC16", HUNK_PPC_CODE: "HUNK_PPC_CODE", HUNK_RELRELOC26: "HUNK_RELRELOC26", } EXT_SYMB = 0 EXT_DEF = 1 EXT_ABS = 2 EXT_RES = 3 EXT_ABSREF32 = 129 EXT_ABSCOMMON = 130 EXT_RELREF16 = 131 EXT_RELREF8 = 132 EXT_DEXT32 = 133 EXT_DEXT16 = 134 EXT_DEXT8 = 135 EXT_RELREF32 = 136 EXT_RELCOMMON = 137 EXT_ABSREF16 = 138 EXT_ABSREF8 = 139 EXT_RELREF26 = 229 TYPE_UNKNOWN = 0 TYPE_LOADSEG = 1 TYPE_UNIT = 2 TYPE_LIB = 3 HUNK_TYPE_MASK = 0xffff SEGMENT_TYPE_CODE = 0 SEGMENT_TYPE_DATA = 1 SEGMENT_TYPE_BSS = 2 BIN_IMAGE_TYPE_HUNK = 0 segment_type_names = [ "CODE", "DATA", "BSS" ] loadseg_valid_begin_hunks = [ HUNK_CODE, HUNK_DATA, HUNK_BSS, HUNK_PPC_CODE ] loadseg_valid_extra_hunks = [ HUNK_ABSRELOC32, HUNK_RELOC32SHORT, HUNK_DEBUG, HUNK_SYMBOL, HUNK_NAME ] class HunkParseError(Exception): def __init__(self, msg): self.msg = msg def __str__(self): return self.msg class HunkBlock: """Base class for all hunk block types""" def __init__(self): pass blk_id = 0xdeadbeef sub_offset = None
: https://github.com/lab313ru/amiga_hunk_loader
IDA, , . «» Python , , ( -?), .
- IDA , . , - , , .
.