
Hola a todos
continúa una serie de artículos sobre cómo escribir varias cosas útiles para IDA Pro . La última vez que arreglamos el módulo del procesador , y hoy nos centraremos en escribir un módulo cargador (cargador) para un sistema operativo antiguo, es decir, para AmigaOS . Escribiremos en Python. También intentaré revelar algunas sutilezas al trabajar con relocations
(también son relocations
), que se encuentran en muchos archivos ejecutables ( PE
, ELF
, MS-DOS
, etc.).
Introduccion
Aquellos que previamente trabajaron con el formato Amiga Hunk (en AmigaOS, este es el nombre de los objetos que contienen código ejecutable: executable
, archivos de library
, etc.) y cargaron al menos uno de esos archivos en la IDA, probablemente vieron que el gestor de arranque es ya existe (además, incluso hay fuentes en el IDA SDK
):


Sí, de hecho, todo ya ha sido escrito antes que nosotros, pero ... Todo está implementado tan mal que se vuelve simplemente imposible trabajar con al menos algún archivo ejecutable normal.
Problemas de implementación actuales
Así que aquí hay una lista de problemas:
- Reubicaciones En los archivos de Amiga Hunk, su presencia es una práctica normal. Y en la implementación existente, incluso se aplican al descargar un archivo. Pero esto no siempre se hace correctamente (el enlace resultante puede no calcularse correctamente).
Además, no podrá hacer un " programa Rebase ... ". Esta función está ausente en el cargador. - El archivo se descarga en la dirección base
0x00000000
. Esto definitivamente es incorrecto, ya que varias bibliotecas del sistema se cargan con desplazamiento cero. Como resultado, los enlaces a estas bibliotecas se crean en el espacio de direcciones del archivo descargado - El cargador se puede configurar con varios indicadores que no permiten (o, por el contrario, permiten) que la IDA realice ciertas acciones de "reconocimiento": definir punteros, matrices, instrucciones de ensamblador.
En situaciones en las que no se trata de x86 / x64 / ARM, a menudo después de descargar un archivo, la lista de ensambladores parece que desea cerrar la IDA de un vistazo (y también elimine el archivo investigado y aprenda a usar radare2) . La razón de esto son las banderas predeterminadas del gestor de arranque.

Escribir una plantilla de gestor de arranque
En realidad, escribir un gestor de arranque es fácil. Hay tres devoluciones de llamada que deben implementarse:
1) accept_file(li, filename)
A través de esta función, la IDA determina si este cargador se puede usar para descargar el filename
def accept_file(li, filename): li.seek(0) tag = li.read(4) if tag == 'TAG1':
2) load_file(li, neflags, format)
Aquí, el contenido del archivo se descarga a la base de datos, la creación de segmentos / estructuras / tipos, el uso de reubicaciones y otras acciones.
def load_file(li, neflags, format):
3) move_segm(frm, to, sz, fileformatname)
Si hay reubicaciones en el archivo descargado, entonces no puede simplemente tomar y cambiar la dirección base. Debe contar todas las reubicaciones y parchear los enlaces. En general, el código siempre será el mismo. Aquí solo repasaremos todos los bloqueos creados previamente, añadiéndoles un delta y aplicando parches a los bytes del archivo descargado.
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
Plantilla de cargador de arranque import idaapi import ida_idp import ida_fixup def accept_file(li, filename): li.seek(0) tag = li.read(4) if tag == 'TAG1':
Escribir el código del gestor de arranque principal
Entonces, con lo básico resuelto. Vamos a preparar un "lugar de trabajo" para nosotros mismos. No sé para qué le gusta a nadie escribir código en Python, pero me gusta hacerlo en PyCharm. Creemos un nuevo proyecto y agreguemos el directorio de IDA en las rutas para buscar importaciones:

Las personas que ya se han enfrentado al problema de emular archivos ejecutables para AmigaOS probablemente hayan oído hablar de un proyecto como amitools . Tiene un conjunto casi completo de herramientas para trabajar con Amiga Hunk (tanto para emulación como solo para análisis). Propongo sobre la base de hacer el "gestor de arranque" (la licencia del proyecto lo permite, y nuestro gestor de arranque será sin fines de lucro).
Después de una breve búsqueda en amitools
se encontró el archivo amitools
. Implementa el análisis de un archivo, la definición de segmentos, reubicaciones y mucho más. En realidad, el archivo Relocate.py es responsable del uso de reubicaciones.
Ahora la parte más difícil: de todo este árbol de archivos de amitools
, amitools
arrastrar todo lo que se hace referencia en BinFmtHunk.py
y Relocate.py
a nuestro archivo del gestor de arranque, haciendo correcciones menores en algunos lugares.
Otra cosa que quiero agregar es la definición de cada segmento de la posición en el archivo desde el que se descargaron los datos. Esto se hace agregando el atributo data_offset
a dos clases: HunkSegmentBlock
y HunkOverlayBlock
. Resulta el siguiente código:
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)
Ahora necesitamos agregar este atributo a la clase Segment
, que se crea más tarde a partir de los bloques Hunk
:
Segmento 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
A continuación, para la clase BinFmtHunk
, agregamos el uso de data_offset
al crear segmentos. Esto se hace en el método create_image_from_load_seg_file
en un ciclo de enumeración de bloque de segmento:
create_image_from_load_seg_file segs = lsf.get_segments() for seg in segs:
Escribir un código para devoluciones de llamada IDA
Ahora que tenemos todo lo que necesitamos, escribamos un código para devoluciones de llamada. El primero será accept_file
:
aceptar_archivo 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
Todo es simple: leemos los primeros cuatro bytes, StringIO
un archivo virtual ( StringIO
) y lo pasamos a la función is_image_fobj
, que devuelve True
si el archivo está en el formato apropiado. En este caso, devolvemos el diccionario con dos campos: format
(descripción de texto del formato cargado) y processor
(bajo qué plataforma se escribe el código ejecutable).
A continuación, debe cargar el archivo en el BID. Es mas complicado. Lo primero que debe hacer es forzar la instalación del tipo de procesador en el Motorola 68040
requerido:
idaapi.set_processor_type('68040', ida_idp.SETPROC_LOADER)
Establezca las banderas para el gestor de arranque para que no se reconozca ningún juego y no se hagan matrices de todo en una fila (puede encontrar una descripción de las banderas aquí ):
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
Transferimos el contenido del archivo descargado a BinFmtHunk
(análisis y todo eso):
li.seek(0) data = li.read(li.size()) bf = BinFmtHunk() fobj = StringIO.StringIO(data) bi = bf.load_image_fobj(fobj)
Con una dirección de descarga cero, propongo tratar de elegir otra ImageBase
. Por cierto, los archivos ejecutables en AmigaOS se descargan solo en direcciones accesibles, no hay direcciones virtuales allí. Elegí 0x21F000
, es hermoso y es poco probable que coincida con ninguna constante. Aplicarlo:
rel = Relocate(bi)
Agregue la dirección de inicio desde la cual se inicia el programa:
Es hora de cargar segmentos en la base de datos y crear reubicaciones (en terminología 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())
Resultado final para load_file
:
return 1
El código move_segm
simplemente se move_segm
sin cambios.
El código de resumen del cargador de arranque y las conclusiones
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 , . , - , , .
Gracias a todos.