Modernisation de GHIDRA. Chargeur pour rhum Sega Mega Drive


Salutations, camarades. Je n'ai pas entendu parler du GHIDRA , qui n'est pas encore open-source, probablement seulement un ingénieur inversé sourd / aveugle / muet / sans Internet. Ses capacités prêtes à l'emploi sont incroyables: décompilateurs pour tous les processeurs pris en charge, simple ajout de nouvelles architectures (avec décompilation active immédiate due à une conversion compétente en IR), un tas de scripts qui simplifient la vie, la possibilité d' Undo / Redo ... Et ce n'est qu'une très petite partie de toutes les fonctionnalités fournies. Dire que j'ai été impressionné, c'est ne rien dire.


Donc, dans cet article, je voudrais vous dire comment j'ai écrit mon premier module pour GHIDRA - un chargeur de rhum pour les jeux pour Sega Mega Drive / Genesis . Pour l'écrire, il me fallait ... juste quelques heures! Allons-y.


Mais qu'en est-il de l'IDA?

J'ai passé quelques jours à comprendre le processus d'écriture des téléchargeurs pour l' IDA . Il s'agissait alors de la version 6.5 , semble-t-il, mais à l'époque il y avait beaucoup de problèmes avec la documentation du SDK.


Nous préparons l'environnement de développement


GHIDRA développeurs de GHIDRA ont pensé à presque tout ( Ilfak , où étiez-vous avant?). Et, juste pour simplifier la mise en œuvre de la nouvelle fonctionnalité, ils ont développé un plug-in pour Eclipse - GhidraDev , qui " aide " en fait à écrire du code. Le plugin est intégré dans l'environnement de développement et vous permet de créer des modèles de projet pour les scripts, les chargeurs, les modules de processeur et les extensions pour eux, ainsi que d'exporter des modules (si je comprends bien, c'est une sorte d'exportation des données d'un projet) en quelques clics.


Pour installer le plugin, téléchargez Eclipse pour Java , cliquez sur Help -> Install New Software... , puis cliquez sur le bouton Add et ouvrez la boîte de dialogue de sélection d'archive avec le plugin à l'aide du bouton Archive... L'archive avec GhidraDev se trouve dans le $(GHIDRA)/Extensions/Eclipse/GhidraDev . Sélectionnez-le, cliquez sur le bouton Add .



Dans la liste qui apparaît, mettez un daw sur Ghidra , cliquez sur Next > , acceptez les accords, cliquez sur Install Anyway (car le plugin n'a pas de signature) et redémarrez Eclipse .



Au total, un nouvel élément GhidraDev apparaîtra dans le GhidraDev IDE pour la création et la distribution pratiques de vos projets (bien sûr, vous pouvez également créer via l'assistant habituel pour les nouveaux projets Eclipse ). De plus, nous avons la possibilité de déboguer le plug-in ou le script développé.



Et où est le débogueur d'application?

Ce qui GHIDRA vraiment la situation avec GHIDRA sont les putains d'articles hype rauques contenant presque le même matériel, ce qui, d'ailleurs, n'est pas vrai. Un exemple? Oui, s'il vous plaît:


La version actuelle de l'outil est 9.0. et l'outil a des options pour inclure des fonctionnalités supplémentaires telles que la cryptanalyse, l'interaction avec OllyDbg, le débogueur Ghidra.

Et où est tout ça? Non!


Le deuxième point: l'ouverture. En fait, il est presque là, mais il est pratiquement inexistant. La GHIDRA contient des codes sources pour les composants qui ont été écrits en Java , mais si vous regardez les scripts Gradle , vous pouvez voir qu'il existe des dépendances sur un tas de projets externes à partir de projets secrets laboratoires NSA .
Au moment de l'écriture, il n'y a pas de décompilateur et de SLEIGH (c'est un utilitaire pour compiler les descriptions des modules de processeur et les conversions en IR).


Eh bien, eh bien, je suis distrait par quelque chose.


Créons donc un nouveau projet dans Eclipse .


Créer un projet de chargeur


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


Nous indiquons le nom du projet (nous prenons en compte que les mots de type Loader seront collés aux noms de fichiers, et afin de ne pas obtenir quelque chose comme sega_loaderLoader.java , nous les sega_loaderLoader.java en conséquence).



Cliquez sur Next > . Ici, nous mettons les griffes devant les catégories dont nous avons besoin. Dans mon cas, ce n'est qu'un Loader . Cliquez sur Next > .



Ici, nous indiquons le chemin d'accès au répertoire avec . Cliquez sur Next > .



GHIDRA permet d'écrire des scripts en python (via Jython ). J'écrirai en Java , donc je ne mets pas de bâton. J'appuie sur Finish .



Écrire un code


L'arbre de projet vide semble impressionnant:



Tous les fichiers avec du code java trouvent dans la branche /src/main/java :



getName ()


Pour commencer, choisissons un nom pour le chargeur de démarrage. La méthode getName() renvoie:


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

findSupportedLoadSpecs ()


La méthode findSupportedLoadSpecs() décide (sur la base des données contenues dans le fichier binaire) quel module processeur doit être utilisé pour le démontage (ainsi que dans l' IDA ). Dans la terminologie GHIDRA cela s'appelle le Compiler Language . Il comprend: processeur, endianness, bitness et compilateur (si connu).


Cette méthode renvoie une liste des architectures et langages pris en charge. Si les données sont au mauvais format, nous renvoyons simplement une liste vide.


Ainsi, dans le cas de Sega Mega Drive , le mot " SEGA " est le plus souvent présent à l'offset 0x100 tête (ce n'est pas une condition préalable, mais il est rempli dans 99% des cas). Vous devez vérifier si cette ligne se trouve dans le fichier importé. Pour ce faire, le ByteProvider provider est ByteProvider provider findSupportedLoadSpecs() , avec laquelle nous travaillerons avec le fichier.


Nous créons un objet BinaryReader , pour la commodité de lire les données d'un fichier:


 BinaryReader reader = new BinaryReader(provider, false); 

Le false argument dans ce cas indique l'utilisation de Big Endian lors de la lecture. Maintenant, lisons la ligne. Pour ce faire, utilisez la readAsciiString(offset, size) de l'objet reader :


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

Si equals() renvoie true , alors nous avons affaire au rhum Segov, et dans la liste List<LoadSpec> loadSpecs = new ArrayList<>(); il sera possible d'ajouter le Motorola m68k . Pour ce faire, créez un nouvel objet de type LoadSpec , dont le constructeur accepte un objet chargeur (dans ce cas, this ), ImageBase , dans lequel la ROM sera chargée, un objet de type LanguageCompilerSpecPair et un drapeau - est-ce que LoadSpec parmi les autres de la liste (oui, dans la liste) il peut y avoir plusieurs LoadSpec ).


Le format constructeur pour LanguageCompilerSpecPair le suivant:


  1. Le premier argument est languageID , une chaîne de la forme " ProcessorName: Endianness: Bits: ExactCpu ". Dans mon cas, il devrait s'agir de la ligne " 68000: BE: 32: MC68020 " (malheureusement, exactement le MC68000 n'est pas inclus dans la livraison, mais ce n'est pas un tel problème). ExactCpu peut être default
  2. Le deuxième argument, compilerSpecID , pour trouver ce que vous devez spécifier ici, se trouve dans le répertoire avec les descriptions du processeur ( $(GHIDRA)/Ghidra/Processors/68000/data/languages ) dans le fichier 68000.opinion . Nous voyons que seul le default est indiqué ici. En fait, nous l'indiquons

En conséquence, nous avons le code suivant (comme vous pouvez le voir, rien de compliqué jusqu'à présent):


 @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; } 

La différence entre IDA et GHIDRA en termes de modules

Il y a une différence, et elle est toujours très forte. Dans GHIDRA vous pouvez écrire un projet qui comprendra différentes architectures, différents formats de données, être un chargeur, un module de processeur, l'expansion de la fonctionnalité de décompilation et d'autres goodies.


Dans IDA , il s'agit d'un projet distinct pour chaque type de module complémentaire.


Combien plus pratique? À mon avis, chez GHIDRA - parfois!


charge ()


Dans cette méthode, nous allons créer des segments, traiter les données d'entrée, créer du code et des étiquettes précédemment connues. Pour ce faire, les objets suivants sont soumis pour nous aider à l'entrée:


  1. ByteProvider provider : nous le connaissons déjà. Utilisation de données de fichiers binaires
  2. LoadSpec loadSpec : spécification de l'architecture sélectionnée lors de la phase d'importation du fichier à l'aide de la méthode findSupportedLoadSpecs . Cela est nécessaire si, par exemple, nous sommes capables de travailler avec plusieurs formats de données dans un module. Pratique
  3. List<Option> options : liste des options (y compris personnalisées). Je n'ai pas encore appris à travailler avec eux
  4. Program program : l'objet principal qui donne accès à toutes les fonctionnalités nécessaires: listage, espace d'adressage, segments, étiquettes, création de tableaux, etc.
  5. MemoryConflictHandler handler et TaskMonitor monitor : nous devons rarement travailler directement avec eux (généralement, simplement en passant ces objets à des méthodes prêtes à l'emploi)
  6. MessageLog log : enregistreur lui-même

Donc, pour commencer, créons des objets qui simplifieront notre travail avec les entités GHIDRA et les données existantes. Bien sûr, nous aurons certainement besoin de BinaryReader :


 BinaryReader reader = new BinaryReader(provider, false); 

Ensuite. Il nous sera très utile et simplifiera presque tout l'objet de la classe FlatProgramAPI (vous verrez plus loin ce que vous pouvez en faire):


 FlatProgramAPI fpa = new FlatProgramAPI(program, monitor); 

Rubrique rhum


Pour commencer, nous déterminerons la position d'un rhum Sega ordinaire. Dans les premiers 0x100 octets, il y a une table de 64 pointeurs DWORD vers des vecteurs, par exemple: Reset , Trap , DivideByZero , VBLANK et autres.


Vient ensuite la structure avec le nom des Roms, les régions, les adresses du début et de la fin des blocs ROM et RAM , la case à cocher (le champ est vérifié à la demande des développeurs, et non le préfixe) et d'autres informations.


Créons des classes java pour travailler avec ces structures, ainsi que pour implémenter les types de données qui seront ajoutés à la liste des structures.


VecteursTable


Nous créons une nouvelle classe VectorsTable et, attention, nous indiquons qu'elle implémente l'interface StructConverter . Dans cette classe, nous allons stocker les adresses des vecteurs (pour une utilisation future) et leurs noms.



Nous déclarons une liste de noms de vecteurs et leur nombre:


 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" }; 

Nous créons une classe distincte pour stocker l'adresse et le nom du vecteur:


 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; } } 

La liste des vecteurs sera stockée dans le tableau des vectors :


 private VectorFunc[] vectors; 

Le constructeur de VectorsTable acceptera:


  1. FlatProgramAPI fpa pour convertir long adresses long en type de données d' Address Hydra (en fait, ce type de données complète la simple valeur numérique de l'adresse en la liant à une autre puce - l'espace d'adressage)
  2. BinaryReader reader - lecture des chantiers

L'objet fpa a une méthode toAddr() et le reader a setPointerIndex() et readNextUnsignedInt() . En principe, rien de plus n'est requis. Nous obtenons le code:


 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]); } } 

La méthode toDataType() , que nous devons remplacer pour implémenter la structure, doit renvoyer un objet Structure dans lequel les noms des champs de structure, leurs tailles et commentaires sur chaque champ doivent être déclarés (vous pouvez utiliser 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; } 

Eh bien, implémentons des méthodes pour obtenir chacun des vecteurs, ou la liste entière (un tas de code passe-partout):


Autres méthodes
  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


Nous ferons de même et créerons une classe GameHeader qui implémente l'interface StructConverter .


Structure de l'en-tête du rhum de jeu
Décalage de départDécalage de finLa description
100 $10 F $Nom de la console (généralement «SEGA MEGA DRIVE» ou «SEGA GENESIS»)
110 $11 F $Date de sortie (généralement «© XXXX YYYY.MMM» où XXXX est le code de l'entreprise, YYYY est l'année et MMM - mois)
120 $14 F $Nom domestique
150 $17 F $Nom international
180 $18D $Version ('XX YYYYYYYYYYYY' où XX est le type de jeu et YY le code du jeu)
18E $18 F $Somme de contrôle
190 $19 F $Prise en charge des E / S
1A0 $1A3 $Démarrage de la ROM
1A4 $1A7 $Fin de la ROM
1A8 $1AB $Démarrage de la RAM (généralement 00FFFF0000 $)
1 $ CA1AF $Fin de la RAM (généralement 00FFFFFF $)
1 G $1 G $ 2'RA' et $ F8 permettent SRAM
1 G $ 3----inutilisé (20 $)
1 G $ 41 G $ 7Démarrage SRAM (par défaut 00200000 $)
1 G $ 81 milliard de dollarsFin SRAM (par défaut $ 0020FFFF)
1BC $1FF $Notes (non utilisées)

Nous configurons les champs, vérifions une longueur suffisante des données d'entrée, utilisons pour nous deux méthodes readNextByteArray() , readNextUnsignedShort() de l'objet reader pour lire les données et créer une structure. Le code résultant est le suivant:


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; } } 

Créez des objets pour l'en-tête:


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

Segments


Sega a une carte bien connue des régions de mémoire, que je ne donnerai peut-être pas ici sous forme de tableau, mais seulement le code qui est utilisé pour créer les segments.


Ainsi, l'objet de la classe FlatProgramAPI a une méthode createMemoryBlock() , avec laquelle il est pratique de créer des régions de mémoire. Il faut les arguments suivants pour entrer:


  1. name : nom de la région
  2. address : adresse de départ de la région
  3. stream : un objet de type InputStream qui sera la base des données dans la zone mémoire. Si vous spécifiez null , une région non initialisée sera créée (par exemple, pour 68K RAM ou Z80 RAM nous en aurons besoin
  4. size : taille de la région créée
  5. isOverlay : accepte true ou false et indique que la région mémoire est en superposition. Où cela est nécessaire en plus des exécutables

Sur la sortie, createMemoryBlock() renvoie un objet de type MemoryBlock , auquel vous pouvez en outre définir des indicateurs de droits d'accès ( Read , Write , Execute ).


En conséquence, nous obtenons une fonction de la forme suivante:


 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)); } } 

Ici, nous avons également appelé la méthode d' error statique de la classe Msg pour afficher un message d'erreur.


Un segment contenant du rhum de jeu peut avoir une taille maximale de 0x3FFFFF (tout le reste appartiendra déjà à d'autres régions). Créons-le:


 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/fr444562/


All Articles