
Olá pessoal
uma série de artigos sobre como escrever várias coisas úteis para o IDA Pro continua. Da última vez que consertamos o módulo do processador , hoje focaremos na criação de um módulo carregador (carregador) para um sistema operacional antigo, o AmigaOS . Vamos escrever em Python. Também tentarei revelar algumas sutilezas ao trabalhar com realocações (elas também são relocations ) encontradas em muitos arquivos executáveis ( PE , ELF , MS-DOS , etc.). 
1. Introdução
Aqueles que já trabalharam com o formato Amiga Hunk (no AmigaOS são chamados objetos que contêm código executável: executable -, arquivos de library etc.) e baixaram pelo menos um desses arquivos no IDA, provavelmente viram que o gerenciador de inicialização é ele já existe (além disso, existem fontes no IDA SDK ):


Sim, de fato, tudo já foi escrito antes de nós, mas ... Tudo é implementado tão mal que se torna simplesmente impossível trabalhar com pelo menos algum arquivo executável normal.
Problemas atuais de implementação
Então, aqui está uma lista de problemas:
- Realocações. Nos arquivos Amiga Hunk, a presença deles é prática normal. E na implementação existente, eles até se aplicam ao baixar um arquivo. Mas isso nem sempre é feito corretamente (o link resultante pode não ser calculado corretamente).
 Além disso, você não poderá criar um " programa Rebase ... " Esta função está ausente no carregador.
- O arquivo é baixado no endereço base 0x00000000. Definitivamente, isso está errado, pois várias bibliotecas do sistema são carregadas com deslocamento zero. Como resultado, os links para essas bibliotecas são criados no espaço de endereço do arquivo baixado
- O carregador pode ser configurado com vários sinalizadores que não permitem (ou, inversamente, permitem) ao IDA executar determinadas ações de "reconhecimento": definição de ponteiros, matrizes, instruções do montador.
 Em situações em que não se refere ao x86 / x64 / ARM, geralmente após o download de um arquivo, a listagem do assembler parece que você deseja fechar o IDA rapidamente(e também exclua o arquivo investigado e aprenda a usar o radare2). A razão para isso são os sinalizadores padrão do carregador de inicialização.

Escrevendo um modelo de carregador de inicialização
Na verdade, escrever um gerenciador de inicialização é fácil. Há três retornos de chamada que precisam ser implementados:
1) accept_file(li, filename)
Por meio dessa função, o IDA determina se esse carregador pode ser usado para baixar o filename do filename
 def accept_file(li, filename): li.seek(0) tag = li.read(4) if tag == 'TAG1':  
2) load_file(li, neflags, format)
Aqui, o conteúdo do arquivo é baixado no banco de dados, a criação de segmentos / estruturas / tipos, o uso de relocks e outras ações.
 def load_file(li, neflags, format):  
3) move_segm(frm, to, sz, fileformatname)
Se houver realocações no arquivo baixado, você não poderá simplesmente pegar e alterar o endereço base. Você precisa recontar todas as realocações e corrigir os links. Em geral, o código sempre será o mesmo. Aqui, passamos por todos os relocks criados anteriormente, adicionando um delta a eles e aplicando patches nos bytes do arquivo baixado.
 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 
Modelo do carregador de inicialização import idaapi import ida_idp import ida_fixup def accept_file(li, filename): li.seek(0) tag = li.read(4) if tag == 'TAG1':  
 Escrevendo o código principal do gerenciador de inicialização
Então, com o básico resolvido. Vamos preparar um "local de trabalho" para nós mesmos. Não sei para o que alguém gosta de escrever código em Python, mas gosto de fazê-lo em PyCharm. Vamos criar um novo projeto e adicionar o diretório da IDA nos caminhos para procurar importações:

Pessoas que já enfrentaram o problema de emular arquivos executáveis para o AmigaOS provavelmente já ouviram falar de um projeto como o amitools . Possui um conjunto quase completo de ferramentas para trabalhar com o Amiga Hunk (tanto para emulação quanto apenas para análise). Proponho, com base nisso, fazer o “carregador de inicialização” (a licença do projeto permite, e nosso carregador de inicialização será sem fins lucrativos).
Após uma curta pesquisa em amitools arquivo amitools foi encontrado. Ele implementa a análise de um arquivo, definindo segmentos, realocações e muito mais. Na verdade, o arquivo Relocate.py é responsável pelo uso de bloqueios.
Agora a parte mais difícil: de toda essa árvore de arquivos amitools , você amitools arrastar tudo o que é mencionado em BinFmtHunk.py e Relocate.py para o nosso arquivo do carregador de inicialização, fazendo pequenas correções em alguns lugares.
Outra coisa que quero adicionar é a definição para cada segmento da posição no arquivo do qual os dados foram baixados. Isso é feito adicionando o atributo data_offset a duas classes: HunkSegmentBlock e HunkOverlayBlock . Acontece o seguinte 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) 
 Agora precisamos adicionar esse atributo à classe Segment , que é criada posteriormente a partir dos blocos 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 
 Avançar para a classe BinFmtHunk adicione o uso de data_offset ao criar segmentos. Isso é feito no método create_image_from_load_seg_file em um loop de enumeração de bloco de segmento:
create_image_from_load_seg_file  segs = lsf.get_segments() for seg in segs:  
 Escrevendo um código para retornos de chamada da IDA
Agora que temos tudo o que precisamos, vamos escrever um código para retornos de chamada. O primeiro será 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 
 Tudo é simples: lemos os quatro primeiros bytes, StringIO um arquivo virtual ( StringIO ) a partir deles e passamos para a função is_image_fobj , que retorna True se o arquivo estiver no formato apropriado. Nesse caso, retornamos o dicionário com dois campos: format (descrição do texto do formato carregado) e processor (em qual plataforma o código executável é gravado).
Em seguida, você precisa enviar o arquivo para o BID. É mais complicado. A primeira coisa a fazer é forçar o tipo de processador para o Motorola 68040 necessário:
 idaapi.set_processor_type('68040', ida_idp.SETPROC_LOADER) 
Defina os sinalizadores para o carregador de inicialização para que nenhum jogo seja reconhecido e as matrizes não sejam criadas a partir de tudo em uma linha (uma descrição dos sinalizadores pode ser encontrada aqui ):
 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 o conteúdo do arquivo baixado para o BinFmtHunk (análise e tudo isso):
 li.seek(0) data = li.read(li.size()) bf = BinFmtHunk() fobj = StringIO.StringIO(data) bi = bf.load_image_fobj(fobj) 
Com um endereço de download zero, proponho lidar com a escolha de outro ImageBase . A propósito, os arquivos executáveis no AmigaOS são baixados apenas em endereços acessíveis, não há endereços virtuais lá. Eu escolhi 0x21F000 , é bonito e dificilmente coincide com qualquer constante. Aplique:
 rel = Relocate(bi)  
Adicione o endereço inicial a partir do qual o programa inicia:
 
Chegou a hora de carregar segmentos no banco de dados e criar relocks (na terminologia da 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 
O código move_segm simplesmente move_segm inalterado.
O código de resumo e conclusões do gerenciador de inicialização
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 , . , - , , .
.