
Bonjour à tous
une série d'articles sur l'écriture de diverses choses utiles pour IDA Pro se poursuit. La dernière fois que nous avons corrigé le module processeur , nous nous concentrerons aujourd'hui sur l'écriture d'un module de chargeur (chargeur) pour un système d'exploitation vintage, à savoir pour AmigaOS . Nous écrirons en Python. J'essaierai également de révéler certaines subtilités lorsque vous travaillez avec des relocs (ce sont aussi des relocations
), que l'on retrouve dans de nombreux fichiers exécutables ( PE
, ELF
, MS-DOS
, etc.).
Présentation
Ceux qui ont déjà travaillé avec le format Amiga Hunk (dans AmigaOS, ils sont appelés des objets contenant du code exécutable: executable
-, fichiers de library
, etc.) et ont téléchargé au moins un de ces fichiers dans l'IDA, ils ont probablement vu que le chargeur de démarrage est il existe déjà (d'ailleurs, il y a même des sources dans le IDA SDK
):


Oui, en effet, tout a déjà été écrit avant nous, mais ... Tout est si mal implémenté qu'il devient tout simplement impossible de travailler avec au moins un fichier exécutable normal.
Problèmes de mise en œuvre actuels
Voici donc une liste de problèmes:
- Délocalisations. Dans les fichiers Amiga Hunk, leur présence est une pratique normale. Et dans l'implémentation existante, ils s'appliquent même lors du téléchargement d'un fichier. Mais cela n'est pas toujours fait correctement (le lien résultant peut ne pas être calculé correctement).
De plus, vous ne pourrez pas faire de " programme Rebase ... ". Cette fonction est absente du chargeur. - Le fichier est téléchargé à l'adresse de base
0x00000000
. C'est définitivement faux, car diverses bibliothèques système sont chargées avec un décalage nul. Par conséquent, des liens vers ces bibliothèques sont créés dans l'espace d'adressage du fichier téléchargé - Le chargeur peut être défini avec divers indicateurs qui ne permettent pas (ou, inversement, autorisent) l'IDA à effectuer certaines actions de «reconnaissance»: définir des pointeurs, des tableaux, des instructions d'assembleur.
Dans les situations où cela ne concerne pas x86 / x64 / ARM, souvent après avoir téléchargé un fichier, la liste des assembleurs semble que vous souhaitiez fermer l'IDA d'un coup d'œil (et supprimez également le fichier étudié et découvrez comment utiliser radare2) . La raison en est les indicateurs par défaut du chargeur de démarrage.

Écrire un modèle de chargeur de démarrage
En fait, écrire un chargeur de démarrage est facile. Trois rappels doivent être implémentés:
1) accept_file(li, filename)
Grâce à cette fonction, l'IDA détermine si ce chargeur peut être utilisé pour télécharger le filename
def accept_file(li, filename): li.seek(0) tag = li.read(4) if tag == 'TAG1':
2) load_file(li, neflags, format)
Ici, le contenu du fichier est téléchargé dans la base de données, la création de segments / structures / types, l'utilisation de relocks et d'autres actions.
def load_file(li, neflags, format):
3) move_segm(frm, to, sz, fileformatname)
S'il y a des réinstallations dans le fichier téléchargé, vous ne pouvez pas simplement prendre et modifier l'adresse de base. Il est nécessaire de recompter toutes les délocalisations et les liens de correctifs. En général, le code sera toujours le même. Ici, nous passons en revue tous les revalorisations précédemment créées, en y ajoutant un delta et en appliquant des correctifs aux octets du fichier téléchargé.
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
Modèle de chargeur de démarrage import idaapi import ida_idp import ida_fixup def accept_file(li, filename): li.seek(0) tag = li.read(4) if tag == 'TAG1':
Écriture du code principal du chargeur de démarrage
Donc, avec les bases triées. Préparons-nous un «lieu de travail». Je ne sais pas pourquoi quelqu'un aime écrire du code en Python, mais j'aime le faire dans PyCharm. Créons un nouveau projet et ajoutons le répertoire de l'IDA dans les chemins pour rechercher les importations:

Les personnes qui ont déjà rencontré le problème de l'émulation de fichiers exécutables pour AmigaOS ont probablement entendu parler d'un projet comme amitools . Il dispose d'un ensemble presque complet d'outils pour travailler avec Amiga Hunk (à la fois pour l'émulation et uniquement pour l'analyse). Je propose sur sa base de faire le «bootloader» (la licence du projet le permet, et notre bootloader sera à but non lucratif).
Après une courte recherche sur amitools
fichier amitools
été trouvé. Il implémente l'analyse d'un fichier, la définition de segments, les relocalisations et bien plus encore. En fait, le fichier Relocate.py est responsable de l'utilisation des relocks.
Maintenant, la partie la plus difficile: à partir de toute cette arborescence de fichiers amitools
, vous amitools
glisser tout ce qui est référencé dans BinFmtHunk.py
et Relocate.py
dans notre fichier bootloader, apportant des corrections mineures à certains endroits.
Une autre chose que je veux ajouter est la définition de chaque segment de la position dans le fichier à partir duquel les données ont été téléchargées. Cela se fait en ajoutant l'attribut data_offset
à deux classes: HunkSegmentBlock
et HunkOverlayBlock
. Il s'avère que le code suivant:
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)
Maintenant, nous devons ajouter cet attribut à la classe Segment
, qui est créée plus tard à partir des blocs Hunk
:
Segment 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
Ensuite, pour la classe BinFmtHunk
, BinFmtHunk
ajoutons l'utilisation de data_offset
lors de la création de segments. Cela se fait dans la méthode create_image_from_load_seg_file
dans une boucle d'énumération de bloc de segment:
create_image_from_load_seg_file segs = lsf.get_segments() for seg in segs:
Écrire un code pour les rappels IDA
Maintenant que nous avons tout ce dont nous avons besoin, écrivons un code pour les rappels. Le premier sera 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
Tout est simple: nous lisons les quatre premiers octets, en faisons un fichier virtuel ( StringIO
) et le transmettons à la fonction is_image_fobj
, qui renvoie True
si le fichier est au format approprié. Dans ce cas, nous renvoyons le dictionnaire avec deux champs: format
(description textuelle du format chargé) et processor
(sous quelle plateforme le code exécutable est écrit).
Ensuite, vous devez télécharger le fichier sur IDB. C'est plus compliqué. La première chose à faire est de forcer l'installation du type de processeur sur le Motorola 68040
requis:
idaapi.set_processor_type('68040', ida_idp.SETPROC_LOADER)
Définissez les indicateurs pour le chargeur de démarrage afin qu'aucun jeu ne soit reconnu et que les tableaux ne soient pas créés à partir de tout dans une rangée (une description des indicateurs peut être trouvée ici ):
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
Nous transférons le contenu du fichier téléchargé vers BinFmtHunk
(analyse et tout cela):
li.seek(0) data = li.read(li.size()) bf = BinFmtHunk() fobj = StringIO.StringIO(data) bi = bf.load_image_fobj(fobj)
Avec une adresse de téléchargement nulle, je propose de choisir une autre ImageBase
. Soit dit en passant, les fichiers exécutables d'AmigaOS sont téléchargés uniquement à des adresses accessibles, il n'y a aucune adresse virtuelle là-bas. J'ai choisi 0x21F000
, c'est beau et il est peu probable qu'il coïncide avec une constante. Appliquez-le:
rel = Relocate(bi)
Ajoutez l'adresse de départ à partir de laquelle le programme démarre:
Il est temps de charger des segments dans la base de données et de créer des reverrouillages (dans la terminologie IDA: 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())
Résultat final pour load_file
:
return 1
Le code move_segm
prend juste sans modifications.
Le code de résumé du chargeur de démarrage et les conclusions
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
Lien vers la source: https://github.com/lab313ru/amiga_hunk_loader
Écrire un chargeur de démarrage pour l'IDA n'est en fait pas du tout difficile. Si vous êtes un «plus» ou un peu capable de coder en Python et, bien sûr, avez beaucoup de patience (et où sans rétro-ingénierie?), Vous pouvez le faire en une seule soirée.
Le principal problème dans l'écriture de quelque chose sous l'IDA est le manque d'aide normale, vous devez vous occuper de beaucoup de choses vous-même. Heureusement, les développeurs font déjà quelque chose dans ce sens et les quais ne sont plus les mêmes qu'auparavant.
Merci à tous.