
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 , . , - , , .
.