Modernização da GHIDRA. Carregadeira para rum Sega Mega Drive


Saudações, camaradas. Eu não ouvi falar do GHIDRA , ainda não aberto, provavelmente apenas um engenheiro reverso surdo / cego / mudo / sem Internet. Seus recursos prontos são surpreendentes: descompiladores para todos os processadores suportados, adição simples de novas arquiteturas (com descompilação ativa imediata devido à conversão competente em IR), um monte de scripts que simplificam a vida, a possibilidade de Undo / Redo ... E essa é apenas uma parte muito pequena de todos os recursos fornecidos. Dizer que fiquei impressionado é dizer quase nada.


Portanto, neste artigo, gostaria de contar como escrevi meu primeiro módulo para o GHIDRA - um carregador de rum para jogos do Sega Mega Drive / Genesis . Para escrever, eu precisava ... apenas algumas horas! Vamos lá


Mas e a IDA?

Passei alguns dias entendendo o processo de criação de downloaders para a IDA . Aparentemente, era a versão 6.5 , mas naquela época havia muitos problemas com a documentação do SDK.


Preparamos o ambiente de desenvolvimento


GHIDRA desenvolvedores da GHIDRA pensaram em quase tudo ( Ilfak , onde você esteve antes?). E, apenas para simplificar a implementação da nova funcionalidade, eles desenvolveram um plug-in para o Eclipse - GhidraDev , que na verdade " ajuda " a escrever código. O plug-in é integrado ao ambiente de desenvolvimento e permite criar modelos de projeto para scripts, carregadores, módulos de processador e extensões para eles, bem como módulos de exportação (como eu entendo, isso é algum tipo de exportação de dados de um projeto) com apenas alguns cliques.


Para instalar o plug-in, faça o download do Eclipse para Java , clique em Help -> Install New Software... , clique no botão Add e abra a caixa de diálogo de seleção de arquivamento com o plug-in usando o botão Archive... O arquivo GhidraDev com GhidraDev está localizado no $(GHIDRA)/Extensions/Eclipse/GhidraDev . Selecione, clique no botão Add .



Na lista exibida, Ghidra uma Ghidra no Ghidra , clique em Next > , aceite os contratos, clique em Install Anyway (porque o plug-in não possui uma assinatura) e reinicie o Eclipse .



No total, um novo item do GhidraDev aparecerá no GhidraDev IDE para criação e distribuição convenientes de seus projetos (é claro, você também pode criar através do assistente usual para novos projetos Eclipse ). Além disso, temos a oportunidade de depurar o plug-in ou script desenvolvido.



E onde está o depurador de aplicativos?

O que realmente enfurece a situação com a GHIDRA são os artigos roucos e GHIDRA contêm quase o mesmo material, o que, além disso, não é verdade. Um exemplo? Sim por favor:


A versão atual da ferramenta é 9.0. e a ferramenta possui opções para incluir funcionalidades adicionais, como análise de criptografia, interação com o OllyDbg, o depurador Ghidra.

E onde está tudo isso? Não!


O segundo ponto: abertura. De fato, está quase lá, mas é praticamente inexistente. A GHIDRA contém códigos-fonte para componentes que foram escritos em Java , mas se você observar os scripts Gradle , poderá ver que existem dependências em vários projetos externos dos secretos laboratórios Repositórios da NSA .
No momento da redação deste documento, não havia SLEIGH decompilador e SLEIGH (este é um utilitário para compilar descrições de módulos de processador e conversões para IR).


Bem, oh bem, estou distraído com alguma coisa.


Então, vamos criar um novo projeto no Eclipse .


Crie um projeto loader


GhidraDev -> New -> Ghidra Module Project...


Indicamos o nome do projeto (levamos em conta que as palavras do tipo Loader serão coladas aos nomes dos arquivos e, para não obter algo como sega_loaderLoader.java , os sega_loaderLoader.java acordo).



Clique em Next > . Aqui, colocamos daws na frente das categorias que precisamos. No meu caso, este é apenas um Loader . Clique em Next > .



Aqui, indicamos o caminho para o diretório com o . Clique em Next > .



GHIDRA permite escrever scripts em python (via Jython ). Vou escrever em Java , por isso não coloco um daw. Eu pressiono Finish .



Escrevendo um código


A árvore vazia do projeto parece impressionante:



Todos os arquivos com código java estão no ramo /src/main/java :



getName ()


Para começar, vamos escolher um nome para o gerenciador de inicialização. O método getName() retorna:


 @Override public String getName() { return "Sega Mega Drive / Genesis Loader"; } 

findSupportedLoadSpecs ()


O método findSupportedLoadSpecs() decide (com base nos dados contidos no arquivo binário) qual módulo do processador deve ser usado para desmontagem (assim como no IDA ). Na terminologia GHIDRA isso é chamado de Compiler Language . Inclui: processador, endianness, bitness e compilador (se conhecido).


Este método retorna uma lista de arquiteturas e idiomas suportados. Se os dados estiverem no formato errado, simplesmente retornamos uma lista vazia.


Portanto, no caso do Sega Mega Drive , a palavra " SEGA " costuma estar presente no deslocamento 0x100 cabeçalho (isso não é um pré-requisito, mas é cumprido em 99% dos casos). Você precisa verificar se esta linha está no arquivo importado. Para fazer isso, o ByteProvider provider é ByteProvider provider findSupportedLoadSpecs() , com a qual trabalharemos com o arquivo.


Criamos um objeto BinaryReader , para a conveniência de ler dados de um arquivo:


 BinaryReader reader = new BinaryReader(provider, false); 

O argumento false neste caso indica o uso de Big Endian ao ler. Agora vamos ler a linha. Para fazer isso, use o readAsciiString(offset, size) do objeto reader :


 reader.readAsciiString(0x100, 4).equals(new String("SEGA")) 

Se equals() retorna true , então estamos lidando com o rum de Segov e na lista List<LoadSpec> loadSpecs = new ArrayList<>(); será possível adicionar o Motorola m68k . Para fazer isso, crie um novo objeto do tipo LoadSpec , cujo construtor aceita um objeto loader (neste caso, this ), ImageBase , no qual a ROM será carregada, um objeto do tipo LanguageCompilerSpecPair e um sinalizador - esse LoadSpec entre os outros na lista (sim, na lista pode haver mais de um LoadSpec ).


O formato do construtor para LanguageCompilerSpecPair o seguinte:


  1. O primeiro argumento é languageID , uma sequência no formato " ProcessorName: Endianness: Bits: ExactCpu ". No meu caso, deve ser a linha " 68000: BE: 32: MC68020 " (infelizmente, exatamente o MC68000 não está incluído na entrega, mas isso não é um problema). ExactCpu pode ser o default
  2. O segundo argumento, compilerSpecID , para encontrar o que você precisa especificar aqui, pode ser encontrado no diretório com as descrições do processador ( $(GHIDRA)/Ghidra/Processors/68000/data/languages ) no arquivo 68000.opinion . Vemos que apenas o default é indicado aqui. Na verdade, nós indicamos isso

Como resultado, temos o seguinte código (como você pode ver, nada complicado até agora):


 @Override public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException { List<LoadSpec> loadSpecs = new ArrayList<>(); BinaryReader reader = new BinaryReader(provider, false); if (reader.readAsciiString(0x100, 4).equals(new String("SEGA"))) { loadSpecs.add(new LoadSpec(this, 0, new LanguageCompilerSpecPair("68000:BE:32:MC68020", "default"), true)); } return loadSpecs; } 

A diferença entre IDA e GHIDRA em termos de módulos

Há uma diferença, e ainda é muito forte. No GHIDRA você pode escrever um projeto que entenderá diferentes arquiteturas, diferentes formatos de dados, seja um carregador, um módulo de processador, expansão da funcionalidade do descompilador e outros itens.


Na IDA , esse é um projeto separado para cada tipo de complemento.


Quanto mais conveniente? Na minha opinião, na GHIDRA - às vezes!


load ()


Nesse método, criaremos segmentos, processaremos dados de entrada, criaremos códigos e rótulos conhecidos anteriormente. Para fazer isso, os seguintes objetos são enviados para nos ajudar na entrada:


  1. ByteProvider provider : já o conhecemos. Trabalhando com dados de arquivos binários
  2. LoadSpec loadSpec : especificação da arquitetura que foi selecionada durante a fase de importação do arquivo usando o método findSupportedLoadSpecs . É necessário que, por exemplo, possamos trabalhar com vários formatos de dados em um módulo. Conveniente
  3. List<Option> options : lista de opções (incluindo personalizadas). Ainda não aprendi a trabalhar com eles
  4. Program program : o principal objeto que fornece acesso a todas as funcionalidades necessárias: listagem, espaço de endereçamento, segmentos, etiquetas, criação de matrizes, etc.
  5. MemoryConflictHandler handler e o TaskMonitor monitor : raramente precisamos trabalhar diretamente com eles (geralmente, apenas passando esses objetos para métodos prontos)
  6. MessageLog log : próprio logger

Portanto, para iniciantes, vamos criar alguns objetos que simplificarão nosso trabalho com entidades GHIDRA e dados existentes. Obviamente, precisaremos definitivamente do BinaryReader :


 BinaryReader reader = new BinaryReader(provider, false); 

Próximo. Será muito útil para nós e simplificará quase todo o objeto da classe FlatProgramAPI (você verá mais tarde o que pode fazer com ele):


 FlatProgramAPI fpa = new FlatProgramAPI(program, monitor); 

Rum cabeçalho


Para começar, determinaremos qual é o título de um rum comum da Sega. Nos primeiros 0x100 bytes, há uma tabela de 64 ponteiros DWORD para vetores, por exemplo: Reset , Trap , DivideByZero , VBLANK e outros.


Em seguida, vem a estrutura com o nome dos Roma, regiões, endereços do início e final dos blocos ROM e RAM , a caixa de seleção (o campo é verificado a pedido dos desenvolvedores, e não o prefixo) e outras informações.


Vamos criar classes java para trabalhar com essas estruturas, bem como implementar os tipos de dados que serão adicionados à lista de estruturas.


VetoresTabela


Criamos uma nova classe VectorsTable e, atenção, indicamos que ela implementa a interface StructConverter . Nesta classe, armazenaremos os endereços dos vetores (para uso futuro) e seus nomes.



Declaramos uma lista de nomes de vetores e seu número:


 private static final int VECTORS_SIZE = 0x100; private static final int VECTORS_COUNT = VECTORS_SIZE / 4; private static final String[] VECTOR_NAMES = { "SSP", "Reset", "BusErr", "AdrErr", "InvOpCode", "DivBy0", "Check", "TrapV", "GPF", "Trace", "Reserv0", "Reserv1", "Reserv2", "Reserv3", "Reserv4", "BadInt", "Reserv10", "Reserv11", "Reserv12", "Reserv13", "Reserv14", "Reserv15", "Reserv16", "Reserv17", "BadIRQ", "IRQ1", "EXT", "IRQ3", "HBLANK", "IRQ5", "VBLANK", "IRQ7", "Trap0", "Trap1", "Trap2", "Trap3", "Trap4", "Trap5", "Trap6", "Trap7", "Trap8", "Trap9", "Trap10", "Trap11", "Trap12", "Trap13","Trap14", "Trap15", "Reserv30", "Reserv31", "Reserv32", "Reserv33", "Reserv34", "Reserv35", "Reserv36", "Reserv37", "Reserv38", "Reserv39", "Reserv3A", "Reserv3B", "Reserv3C", "Reserv3D", "Reserv3E", "Reserv3F" }; 

Criamos uma classe separada para armazenar o endereço e o nome do vetor:


 package sega; import ghidra.program.model.address.Address; public class VectorFunc { private Address address; private String name; public VectorFunc(Address address, String name) { this.address = address; this.name = name; } public Address getAddress() { return address; } public String getName() { return name; } } 

A lista de vetores será armazenada na matriz de vectors :


 private VectorFunc[] vectors; 

O construtor para VectorsTable aceitará:


  1. FlatProgramAPI fpa para converter endereços long no tipo de dados Address do Hydra (na verdade, esse tipo de dado complementa o valor numérico simples do endereço, vinculando-o a outro chip - o espaço de endereço)
  2. BinaryReader reader - estaleiros de leitura

O objeto fpa possui um método toAddr() e o reader setPointerIndex() e readNextUnsignedInt() . Em princípio, nada mais é necessário. Nós obtemos o código:


 public VectorsTable(FlatProgramAPI fpa, BinaryReader reader) throws IOException { if (reader.length() < VECTORS_COUNT) { return; } reader.setPointerIndex(0); vectors = new VectorFunc[VECTORS_COUNT]; for (int i = 0; i < VECTORS_COUNT; ++i) { vectors[i] = new VectorFunc(fpa.toAddr(reader.readNextUnsignedInt()), VECTOR_NAMES[i]); } } 

O método toDataType() , que precisamos substituir para implementar a estrutura, deve retornar um objeto Structure no qual os nomes dos campos da estrutura, seus tamanhos e comentários em cada campo devem ser declarados (você pode usar null ):


 @Override public DataType toDataType() { Structure s = new StructureDataType("VectorsTable", 0); for (int i = 0; i < VECTORS_COUNT; ++i) { s.add(POINTER, 4, VECTOR_NAMES[i], null); } return s; } 

Bem, vamos implementar métodos para obter cada um dos vetores ou a lista inteira (um monte de código padrão):


Outros métodos
  public VectorFunc[] getVectors() { return vectors; } public VectorFunc getSSP() { if (vectors.length < 1) { return null; } return vectors[0]; } public VectorFunc getReset() { if (vectors.length < 2) { return null; } return vectors[1]; } public VectorFunc getBusErr() { if (vectors.length < 3) { return null; } return vectors[2]; } public VectorFunc getAdrErr() { if (vectors.length < 4) { return null; } return vectors[3]; } public VectorFunc getInvOpCode() { if (vectors.length < 5) { return null; } return vectors[4]; } public VectorFunc getDivBy0() { if (vectors.length < 6) { return null; } return vectors[5]; } public VectorFunc getCheck() { if (vectors.length < 7) { return null; } return vectors[6]; } public VectorFunc getTrapV() { if (vectors.length < 8) { return null; } return vectors[7]; } public VectorFunc getGPF() { if (vectors.length < 9) { return null; } return vectors[8]; } public VectorFunc getTrace() { if (vectors.length < 10) { return null; } return vectors[9]; } public VectorFunc getReserv0() { if (vectors.length < 11) { return null; } return vectors[10]; } public VectorFunc getReserv1() { if (vectors.length < 12) { return null; } return vectors[11]; } public VectorFunc getReserv2() { if (vectors.length < 13) { return null; } return vectors[12]; } public VectorFunc getReserv3() { if (vectors.length < 14) { return null; } return vectors[13]; } public VectorFunc getReserv4() { if (vectors.length < 15) { return null; } return vectors[14]; } public VectorFunc getBadInt() { if (vectors.length < 16) { return null; } return vectors[15]; } public VectorFunc getReserv10() { if (vectors.length < 17) { return null; } return vectors[16]; } public VectorFunc getReserv11() { if (vectors.length < 18) { return null; } return vectors[17]; } public VectorFunc getReserv12() { if (vectors.length < 19) { return null; } return vectors[18]; } public VectorFunc getReserv13() { if (vectors.length < 20) { return null; } return vectors[19]; } public VectorFunc getReserv14() { if (vectors.length < 21) { return null; } return vectors[20]; } public VectorFunc getReserv15() { if (vectors.length < 22) { return null; } return vectors[21]; } public VectorFunc getReserv16() { if (vectors.length < 23) { return null; } return vectors[22]; } public VectorFunc getReserv17() { if (vectors.length < 24) { return null; } return vectors[23]; } public VectorFunc getBadIRQ() { if (vectors.length < 25) { return null; } return vectors[24]; } public VectorFunc getIRQ1() { if (vectors.length < 26) { return null; } return vectors[25]; } public VectorFunc getEXT() { if (vectors.length < 27) { return null; } return vectors[26]; } public VectorFunc getIRQ3() { if (vectors.length < 28) { return null; } return vectors[27]; } public VectorFunc getHBLANK() { if (vectors.length < 29) { return null; } return vectors[28]; } public VectorFunc getIRQ5() { if (vectors.length < 30) { return null; } return vectors[29]; } public VectorFunc getVBLANK() { if (vectors.length < 31) { return null; } return vectors[30]; } public VectorFunc getIRQ7() { if (vectors.length < 32) { return null; } return vectors[31]; } public VectorFunc getTrap0() { if (vectors.length < 33) { return null; } return vectors[32]; } public VectorFunc getTrap1() { if (vectors.length < 34) { return null; } return vectors[33]; } public VectorFunc getTrap2() { if (vectors.length < 35) { return null; } return vectors[34]; } public VectorFunc getTrap3() { if (vectors.length < 36) { return null; } return vectors[35]; } public VectorFunc getTrap4() { if (vectors.length < 37) { return null; } return vectors[36]; } public VectorFunc getTrap5() { if (vectors.length < 38) { return null; } return vectors[37]; } public VectorFunc getTrap6() { if (vectors.length < 39) { return null; } return vectors[38]; } public VectorFunc getTrap7() { if (vectors.length < 40) { return null; } return vectors[39]; } public VectorFunc getTrap8() { if (vectors.length < 41) { return null; } return vectors[40]; } public VectorFunc getTrap9() { if (vectors.length < 42) { return null; } return vectors[41]; } public VectorFunc getTrap10() { if (vectors.length < 43) { return null; } return vectors[42]; } public VectorFunc getTrap11() { if (vectors.length < 44) { return null; } return vectors[43]; } public VectorFunc getTrap12() { if (vectors.length < 45) { return null; } return vectors[44]; } public VectorFunc getTrap13() { if (vectors.length < 46) { return null; } return vectors[45]; } public VectorFunc getTrap14() { if (vectors.length < 47) { return null; } return vectors[46]; } public VectorFunc getTrap15() { if (vectors.length < 48) { return null; } return vectors[47]; } public VectorFunc getReserv30() { if (vectors.length < 49) { return null; } return vectors[48]; } public VectorFunc getReserv31() { if (vectors.length < 50) { return null; } return vectors[49]; } public VectorFunc getReserv32() { if (vectors.length < 51) { return null; } return vectors[50]; } public VectorFunc getReserv33() { if (vectors.length < 52) { return null; } return vectors[51]; } public VectorFunc getReserv34() { if (vectors.length < 53) { return null; } return vectors[52]; } public VectorFunc getReserv35() { if (vectors.length < 54) { return null; } return vectors[53]; } public VectorFunc getReserv36() { if (vectors.length < 55) { return null; } return vectors[54]; } public VectorFunc getReserv37() { if (vectors.length < 56) { return null; } return vectors[55]; } public VectorFunc getReserv38() { if (vectors.length < 57) { return null; } return vectors[56]; } public VectorFunc getReserv39() { if (vectors.length < 58) { return null; } return vectors[57]; } public VectorFunc getReserv3A() { if (vectors.length < 59) { return null; } return vectors[58]; } public VectorFunc getReserv3B() { if (vectors.length < 60) { return null; } return vectors[59]; } public VectorFunc getReserv3C() { if (vectors.length < 61) { return null; } return vectors[60]; } public VectorFunc getReserv3D() { if (vectors.length < 62) { return null; } return vectors[61]; } public VectorFunc getReserv3E() { if (vectors.length < 63) { return null; } return vectors[62]; } public VectorFunc getReserv3F() { if (vectors.length < 64) { return null; } return vectors[63]; } 

Gameheader


Faremos o mesmo e criaremos uma classe GameHeader que implementa a interface StructConverter .


Estrutura de cabeçalho de rum de jogo
Iniciar deslocamentoDeslocamento finalDescrição do produto
$ 100$ 10FNome do console (geralmente 'SEGA MEGA DRIVE' ou 'SEGA GENESIS')
$ 110$ 11FData de lançamento (geralmente '© XXXX YYYY.MMM', em que XXXX é a empresa, YYYY é o ano e MMM - mês)
$ 120$ 14FNome nacional
$ 150$ 17FNome internacional
$ 180$ 18DVersão ('XX AAAAAAAAAAA', onde XX é o tipo de jogo e AA o código do jogo)
$ 18E$ 18FSoma de verificação
$ 190$ 19FSuporte de E / S
$ 1A0$ 1A3Início da ROM
$ 1A4$ 1A7Final da ROM
$ 1A8$ 1ABInício da RAM (geralmente $ 00FF0000)
$ 1AC$ 1AFFinal da RAM (geralmente $ 00FFFFFF)
US $ 1 bilhão$ 1B2'RA' e $ F8 habilitam SRAM
$ 1B3----não utilizado (US $ 20)
$ 1B4$ 1B7Início da SRAM (padrão $ 00200000)
$ 1B8US $ 1 bilhãoFim da SRAM (padrão $ 0020FFFF)
$ 1BC$ 1FFNotas (não utilizadas)

Configuramos os campos, verificamos um comprimento suficiente dos dados de entrada, usamos dois métodos para readNextByteArray() , readNextUnsignedShort() do objeto reader para ler dados e criar uma estrutura. O código resultante é o seguinte:


Gameheader
 package sega; import java.io.IOException; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.StructConverter; import ghidra.program.flatapi.FlatProgramAPI; import ghidra.program.model.address.Address; import ghidra.program.model.data.DataType; import ghidra.program.model.data.Structure; import ghidra.program.model.data.StructureDataType; public class GameHeader implements StructConverter { private byte[] consoleName = null; private byte[] releaseDate = null; private byte[] domesticName = null; private byte[] internationalName = null; private byte[] version = null; private short checksum = 0; private byte[] ioSupport = null; private Address romStart = null, romEnd = null; private Address ramStart = null, ramEnd = null; private byte[] sramCode = null; private byte unused = 0; private Address sramStart = null, sramEnd = null; private byte[] notes = null; FlatProgramAPI fpa; public GameHeader(FlatProgramAPI fpa, BinaryReader reader) throws IOException { this.fpa = fpa; if (reader.length() < 0x200) { return; } reader.setPointerIndex(0x100); consoleName = reader.readNextByteArray(0x10); releaseDate = reader.readNextByteArray(0x10); domesticName = reader.readNextByteArray(0x30); internationalName = reader.readNextByteArray(0x30); version = reader.readNextByteArray(0x0E); checksum = (short) reader.readNextUnsignedShort(); ioSupport = reader.readNextByteArray(0x10); romStart = fpa.toAddr(reader.readNextUnsignedInt()); romEnd = fpa.toAddr(reader.readNextUnsignedInt()); ramStart = fpa.toAddr(reader.readNextUnsignedInt()); ramEnd = fpa.toAddr(reader.readNextUnsignedInt()); sramCode = reader.readNextByteArray(0x03); unused = reader.readNextByte(); sramStart = fpa.toAddr(reader.readNextUnsignedInt()); sramEnd = fpa.toAddr(reader.readNextUnsignedInt()); notes = reader.readNextByteArray(0x44); } @Override public DataType toDataType() { Structure s = new StructureDataType("GameHeader", 0); s.add(STRING, 0x10, "ConsoleName", null); s.add(STRING, 0x10, "ReleaseDate", null); s.add(STRING, 0x30, "DomesticName", null); s.add(STRING, 0x30, "InternationalName", null); s.add(STRING, 0x0E, "Version", null); s.add(WORD, 0x02, "Checksum", null); s.add(STRING, 0x10, "IoSupport", null); s.add(POINTER, 0x04, "RomStart", null); s.add(POINTER, 0x04, "RomEnd", null); s.add(POINTER, 0x04, "RamStart", null); s.add(POINTER, 0x04, "RamEnd", null); s.add(STRING, 0x03, "SramCode", null); s.add(BYTE, 0x01, "Unused", null); s.add(POINTER, 0x04, "SramStart", null); s.add(POINTER, 0x04, "SramEnd", null); s.add(STRING, 0x44, "Notes", null); return s; } public byte[] getConsoleName() { return consoleName; } public byte[] getReleaseDate() { return releaseDate; } public byte[] getDomesticName() { return domesticName; } public byte[] getInternationalName() { return internationalName; } public byte[] getVersion() { return version; } public short getChecksum() { return checksum; } public byte[] getIoSupport() { return ioSupport; } public Address getRomStart() { return romStart; } public Address getRomEnd() { return romEnd; } public Address getRamStart() { return ramStart; } public Address getRamEnd() { return ramEnd; } public byte[] getSramCode() { return sramCode; } public byte getUnused() { return unused; } public Address getSramStart() { return sramStart; } public Address getSramEnd() { return sramEnd; } public boolean hasSRAM() { if (sramCode == null) { return false; } return sramCode[0] == 'R' && sramCode[1] == 'A' && sramCode[2] == 0xF8; } public byte[] getNotes() { return notes; } } 

Crie objetos para o cabeçalho:


 vectors = new VectorsTable(fpa, reader); header = new GameHeader(fpa, reader); 

Segmentos


A Sega possui um mapa bem conhecido de regiões de memória, que talvez eu não cite aqui na forma de tabela, mas darei apenas o código usado para criar os segmentos.


Portanto, o objeto da classe FlatProgramAPI possui um método createMemoryBlock() , com o qual é conveniente criar regiões de memória. Na entrada, são utilizados os seguintes argumentos:


  1. name : nome da região
  2. address : address inicial da região
  3. stream : um objeto do tipo InputStream que será a base para os dados na região da memória. Se você especificar null , uma região não inicializada será criada (por exemplo, para 68K RAM ou Z80 RAM , precisaremos exatamente disso
  4. size : tamanho da região criada
  5. isOverlay : aceita true ou false e indica que a região da memória está sobreposta. Não sei onde é necessário, exceto arquivos executáveis

Na saída, createMemoryBlock() retorna um objeto do tipo MemoryBlock , no qual você pode definir opcionalmente sinalizadores de direitos de acesso ( Read , Write , Execute ).


Como resultado, obtemos uma função da seguinte forma:


 private void createSegment(FlatProgramAPI fpa, InputStream stream, String name, Address address, long size, boolean read, boolean write, boolean execute) { MemoryBlock block = null; try { block = fpa.createMemoryBlock(name, address, stream, size, false); block.setRead(read); block.setWrite(read); block.setExecute(execute); } catch (Exception e) { Msg.error(this, String.format("Error creating %s segment", name)); } } 

Aqui também chamamos o método de error estático da classe Msg para exibir uma mensagem de erro.


Um segmento que contém rum de jogo pode ter um tamanho máximo de 0x3FFFFF (todo o resto já pertencerá a outras regiões). Vamos criá-lo:


 InputStream romStream = provider.getInputStream(0); createSegment(fpa, romStream, "ROM", fpa.toAddr(0x000000), Math.min(romStream.available(), 0x3FFFFF), true, false, true); 

InputStream , 0.


, ( SegaCD Sega32X ). OptionDialog . , showYesNoDialogWithNoAsDefaultButton() YES NO - NO .


:


 if (OptionDialog.YES_OPTION == OptionDialog.showYesNoDialogWithNoAsDefaultButton(null, "Question", "Create Sega CD segment?")) { if (romStream.available() > 0x3FFFFF) { InputStream epaStream = provider.getInputStream(0x400000); createSegment(fpa, epaStream, "EPA", fpa.toAddr(0x400000), 0x400000, true, true, false); } else { createSegment(fpa, null, "EPA", fpa.toAddr(0x400000), 0x400000, true, true, false); } } if (OptionDialog.YES_OPTION == OptionDialog.showYesNoDialogWithNoAsDefaultButton(null, "Question", "Create Sega 32X segment?")) { createSegment(fpa, null, "32X", fpa.toAddr(0x800000), 0x200000, true, true, false); } 

:


 createSegment(fpa, null, "Z80", fpa.toAddr(0xA00000), 0x10000, true, true, false); createSegment(fpa, null, "SYS1", fpa.toAddr(0xA10000), 16 * 2, true, true, false); createSegment(fpa, null, "SYS2", fpa.toAddr(0xA11000), 2, true, true, false); createSegment(fpa, null, "Z802", fpa.toAddr(0xA11100), 2, true, true, false); createSegment(fpa, null, "Z803", fpa.toAddr(0xA11200), 2, true, true, false); createSegment(fpa, null, "FDC", fpa.toAddr(0xA12000), 0x100, true, true, false); createSegment(fpa, null, "TIME", fpa.toAddr(0xA13000), 0x100, true, true, false); createSegment(fpa, null, "TMSS", fpa.toAddr(0xA14000), 4, true, true, false); createSegment(fpa, null, "VDP", fpa.toAddr(0xC00000), 2 * 9, true, true, false); createSegment(fpa, null, "RAM", fpa.toAddr(0xFF0000), 0x10000, true, true, true); if (header.hasSRAM()) { Address sramStart = header.getSramStart(); Address sramEnd = header.getSramEnd(); if (sramStart.getOffset() >= 0x200000 && sramEnd.getOffset() <= 0x20FFFF && sramStart.getOffset() < sramEnd.getOffset()) { createSegment(fpa, null, "SRAM", sramStart, sramEnd.getOffset() - sramStart.getOffset() + 1, true, true, false); } } 

,


CreateArrayCmd . , :


  1. address : ,
  2. numElements :
  3. dataType :
  4. elementSize :

applyTo(program) , .


, , BYTE , WORD , DWORD . , FlatProgramAPI createByte() , createWord() , createDword() ..


, , (, VDP ). , :


  1. Program getSymbolTable() , , ..
  2. createLabel() , , . , , SourceType.IMPORTED

, :


 private void createNamedByteArray(FlatProgramAPI fpa, Program program, Address address, String name, int numElements) { if (numElements > 1) { CreateArrayCmd arrayCmd = new CreateArrayCmd(address, numElements, ByteDataType.dataType, ByteDataType.dataType.getLength()); arrayCmd.applyTo(program); } else { try { fpa.createByte(address); } catch (Exception e) { Msg.error(this, "Cannot create byte. " + e.getMessage()); } } try { program.getSymbolTable().createLabel(address, name, SourceType.IMPORTED); } catch (InvalidInputException e) { Msg.error(this, String.format("%s : Error creating array %s", getName(), name)); } } private void createNamedWordArray(FlatProgramAPI fpa, Program program, Address address, String name, int numElements) { if (numElements > 1) { CreateArrayCmd arrayCmd = new CreateArrayCmd(address, numElements, WordDataType.dataType, WordDataType.dataType.getLength()); arrayCmd.applyTo(program); } else { try { fpa.createWord(address); } catch (Exception e) { Msg.error(this, "Cannot create word. " + e.getMessage()); } } try { program.getSymbolTable().createLabel(address, name, SourceType.IMPORTED); } catch (InvalidInputException e) { Msg.error(this, String.format("%s : Error creating array %s", getName(), name)); } } private void createNamedDwordArray(FlatProgramAPI fpa, Program program, Address address, String name, int numElements) { if (numElements > 1) { CreateArrayCmd arrayCmd = new CreateArrayCmd(address, numElements, DWordDataType.dataType, DWordDataType.dataType.getLength()); arrayCmd.applyTo(program); } else { try { fpa.createDWord(address); } catch (Exception e) { Msg.error(this, "Cannot create dword. " + e.getMessage()); } } try { program.getSymbolTable().createLabel(address, name, SourceType.IMPORTED); } catch (InvalidInputException e) { Msg.error(this, String.format("%s : Error creating array %s", getName(), name)); } } 

 createNamedDwordArray(fpa, program, fpa.toAddr(0xA04000), "Z80_YM2612", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10000), "IO_PCBVER", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10002), "IO_CT1_DATA", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10004), "IO_CT2_DATA", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10006), "IO_EXT_DATA", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10008), "IO_CT1_CTRL", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA1000A), "IO_CT2_CTRL", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA1000C), "IO_EXT_CTRL", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA1000E), "IO_CT1_RX", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10010), "IO_CT1_TX", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10012), "IO_CT1_SMODE", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10014), "IO_CT2_RX", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10016), "IO_CT2_TX", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA10018), "IO_CT2_SMODE", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA1001A), "IO_EXT_RX", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA1001C), "IO_EXT_TX", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA1001E), "IO_EXT_SMODE", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA11000), "IO_RAMMODE", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA11100), "IO_Z80BUS", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xA11200), "IO_Z80RES", 1); createNamedByteArray(fpa, program, fpa.toAddr(0xA12000), "IO_FDC", 0x100); createNamedByteArray(fpa, program, fpa.toAddr(0xA13000), "IO_TIME", 0x100); createNamedDwordArray(fpa, program, fpa.toAddr(0xA14000), "IO_TMSS", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC00000), "VDP_DATA", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC00002), "VDP__DATA", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC00004), "VDP_CTRL", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC00006), "VDP__CTRL", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC00008), "VDP_CNTR", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC0000A), "VDP__CNTR", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC0000C), "VDP___CNTR", 1); createNamedWordArray(fpa, program, fpa.toAddr(0xC0000E), "VDP____CNTR", 1); createNamedByteArray(fpa, program, fpa.toAddr(0xC00011), "VDP_PSG", 1); 


createData() DataUtilities . :


  1. program : Program
  2. address : ,
  3. dataType :
  4. dataLength : . -1
  5. stackPointers : true , - . false
  6. clearDataMode : , (, )

: .. (, ), . FlatProgramAPI createFunction() , .


:


 private void markVectorsTable(Program program, FlatProgramAPI fpa) { try { DataUtilities.createData(program, fpa.toAddr(0), vectors.toDataType(), -1, false, ClearDataMode.CLEAR_ALL_UNDEFINED_CONFLICT_DATA); for (VectorFunc func : vectors.getVectors()) { fpa.createFunction(func.getAddress(), func.getName()); } } catch (CodeUnitInsertionException e) { Msg.error(this, "Vectors mark conflict at 0x000000"); } } private void markHeader(Program program, FlatProgramAPI fpa) { try { DataUtilities.createData(program, fpa.toAddr(0x100), header.toDataType(), -1, false, ClearDataMode.CLEAR_ALL_UNDEFINED_CONFLICT_DATA); } catch (CodeUnitInsertionException e) { Msg.error(this, "Vectors mark conflict at 0x000100"); } } 

load()


load() setMessage() TaskMonitor , .


 monitor.setMessage(String.format("%s : Start loading", getName())); 

, :


 @Override protected void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options, Program program, MemoryConflictHandler handler, TaskMonitor monitor, MessageLog log) throws CancelledException, IOException { monitor.setMessage(String.format("%s : Start loading", getName())); BinaryReader reader = new BinaryReader(provider, false); FlatProgramAPI fpa = new FlatProgramAPI(program, monitor); vectors = new VectorsTable(fpa, reader); header = new GameHeader(fpa, reader); createSegments(fpa, provider, program, monitor); markVectorsTable(program, fpa); markHeader(program, fpa); monitor.setMessage(String.format("%s : Loading done", getName())); } 

getDefaultOptions validateOptions


,



Run -> Debug As -> 1 Ghidra . .



GHIDRA


, - . Eclipse extension.properties , :


 description=Loader for Sega Mega Drive / Genesis ROMs author=Dr. MefistO createdOn=20.03.2019 

GhidraDev -> Export -> Ghidra Module Extension... :





dist zip- (- ghidra_9.0_PUBLIC_20190320_Sega.zip ) GHIDRA .


. , File -> Install Extensions... , , . ...





github- , .


: IDA GHIDRA . .

Source: https://habr.com/ru/post/pt444562/


All Articles