Modernisierung von GHIDRA. Lader für Rum Sega Mega Drive


Grüße, Genossen. Ich habe noch nichts von dem noch nicht Open-Source- GHIDRA , wahrscheinlich nur einem taub / blind / dumm / kein Internet-Reverse Engineer. Die sofort einsatzbereiten Funktionen sind erstaunlich: Dekompilierer für alle unterstützten Prozessoren, einfaches Hinzufügen neuer Architekturen (mit sofortiger aktiver Dekompilierung aufgrund kompetenter Konvertierung in IR), eine Reihe von Skripten, die das Leben vereinfachen, die Möglichkeit des Undo / Redo ... Und dies ist nur ein sehr kleiner Teil aller bereitgestellten Funktionen. Zu sagen, dass ich beeindruckt war, bedeutet fast nichts zu sagen.


In diesem Artikel möchte ich Ihnen erzählen, wie ich mein erstes Modul für GHIDRA - einen GHIDRA für Spiele für Sega Mega Drive / Genesis . Um es zu schreiben, brauchte ich ... nur ein paar Stunden! Lass uns gehen.


Aber was ist mit der IDA?

Ich habe einige Tage damit verbracht, den Prozess des Schreibens von Downloadern für die IDA verstehen. Dann war es anscheinend Version 6.5 , aber damals gab es viele Probleme mit der SDK-Dokumentation.


Wir bereiten die Entwicklungsumgebung vor


GHIDRA Entwickler haben fast alles durchdacht ( Ilfak , wo waren Sie vorher?). Um die Implementierung der neuen Funktionalität zu vereinfachen, haben sie ein Plug-In für Eclipse - GhidraDev , das tatsächlich beim Schreiben von Code " hilft ". Das Plugin ist in die Entwicklungsumgebung integriert und ermöglicht es Ihnen, Projektvorlagen für Skripte, Loader, Prozessormodule und Erweiterungen für diese sowie Exportmodule (so wie ich es verstehe, ist dies eine Art Export von Daten aus einem Projekt) mit wenigen Klicks zu erstellen.


Um das Plugin zu installieren, laden Sie Eclipse für Java herunter, klicken Sie auf Help -> Install New Software... , klicken Sie auf die Schaltfläche Add und öffnen Sie den Archivauswahldialog mit dem Plugin über die Schaltfläche Archive... Das Archiv mit GhidraDev befindet sich im $(GHIDRA)/Extensions/Eclipse/GhidraDev . Wählen Sie es aus und klicken Sie auf die Schaltfläche Add .



Legen Sie in der angezeigten Liste eine Morgendämmerung für Ghidra , klicken Sie auf Next > , stimmen Sie den Vereinbarungen zu, klicken Install Anyway auf Trotzdem Install Anyway (da das Plugin keine Signatur hat) und starten Sie Eclipse .



Insgesamt wird im IDE- GhidraDev ein neues GhidraDev Element GhidraDev , mit dem Sie Ihre Projekte bequem erstellen und verteilen können (natürlich können Sie es auch über den üblichen Assistenten für neue Eclipse Projekte erstellen). Darüber hinaus haben wir die Möglichkeit, das entwickelte Plug-In oder Skript zu debuggen.



Und wo ist der Anwendungsdebugger?

Was die Situation bei GHIDRA wirklich wütend GHIDRA sind die verdammt heiseren Hype-Artikel, die fast das gleiche Material enthalten, was im Übrigen nicht stimmt. Ein Beispiel? Ja, bitte:


Die aktuelle Version des Tools ist 9.0. Das Tool bietet Optionen für zusätzliche Funktionen wie Kryptoanalyse und Interaktion mit OllyDbg, dem Ghidra-Debugger.

Und wo ist das alles? Nein!


Der zweite Punkt: Offenheit. In der Tat ist es fast da, aber es ist praktisch nicht vorhanden. Die GHIDRA enthält Quellcodes für Komponenten, die in Java wurden. Wenn Sie sich jedoch Gradle Skripte ansehen, können Sie feststellen, dass Abhängigkeiten von einer Reihe externer Projekte von geheimen bestehen Laboratorien NSA Repositories.
Zum Zeitpunkt des Schreibens gibt es keine Dekompiler- und SLEIGH (dies ist ein Dienstprogramm zum Kompilieren von Beschreibungen von Prozessormodulen und Konvertierungen in IR).


Na ja, ich bin von etwas abgelenkt.


Erstellen wir also ein neues Projekt in Eclipse .


Erstellen Sie ein Loader-Projekt


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


Wir geben den Namen des Projekts an (wir berücksichtigen, dass Wörter vom Typ Loader an die Dateinamen geklebt werden, und um so etwas wie sega_loaderLoader.java nicht zu erhalten, sega_loaderLoader.java wir sie entsprechend).



Klicken Sie auf Next > . Hier stellen wir die Morgendämmerung vor die Kategorien, die wir brauchen. In meinem Fall ist dies nur ein Loader . Klicken Sie auf Next > .



Hier geben wir den Pfad zum Verzeichnis mit . Klicken Sie auf Next > .



GHIDRA können GHIDRA Skripte in Python schreiben (über Jython ). Ich werde in Java schreiben, damit ich keine Morgenröte mache. Ich drücke auf Finish .



Einen Code schreiben


Der leere Projektbaum sieht beeindruckend aus:



Alle Dateien mit java Code befinden sich im Zweig /src/main/java :



getName ()


Wählen Sie zunächst einen Namen für den Bootloader. Die Methode getName() gibt getName() zurück:


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

findSupportedLoadSpecs ()


Die Methode findSupportedLoadSpecs() entscheidet (basierend auf den in der Binärdatei enthaltenen Daten), welches Prozessormodul für die Demontage (sowie in der IDA ) verwendet werden soll. In der GHIDRA Terminologie GHIDRA dies als Compiler Language . Es enthält: Prozessor, Endianness, Bitness und Compiler (falls bekannt).


Diese Methode gibt eine Liste der unterstützten Architekturen und Sprachen zurück. Wenn die Daten im falschen Format vorliegen, geben wir einfach eine leere Liste zurück.


Im Fall von Sega Mega Drive ist das Wort " SEGA " am häufigsten am Offset 0x100 Headers vorhanden (dies ist keine Voraussetzung, wird aber in 99% der Fälle erfüllt). Sie müssen überprüfen, ob diese Zeile in der importierten Datei enthalten ist. Zu diesem ByteProvider provider wird der ByteProvider provider findSupportedLoadSpecs() , mit der wir mit der Datei arbeiten.


Wir erstellen ein BinaryReader Objekt, um das Lesen von Daten aus einer Datei zu BinaryReader :


 BinaryReader reader = new BinaryReader(provider, false); 

Das false Argument in diesem Fall weist auf die Verwendung von Big Endian beim Lesen hin. Nun lesen wir die Zeile. Verwenden Sie dazu die Methode readAsciiString(offset, size) des reader Objekts:


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

Wenn equals() true zurückgibt, handelt es sich um Segov rum und in der Liste List<LoadSpec> loadSpecs = new ArrayList<>(); Es wird möglich sein, Motorola m68k hinzuzufügen. Erstellen Sie dazu ein neues Objekt vom Typ LoadSpec , dessen Konstruktor ein Loader-Objekt (in diesem Fall this ), ImageBase , in das das ROM geladen wird, ein Objekt vom Typ LanguageCompilerSpecPair und ein Flag LoadSpec dieses LoadSpec unter den anderen in der Liste LoadSpec (ja, in der Liste) LoadSpec Es kann mehr als eine LoadSpec .


Das Konstruktorformat für LanguageCompilerSpecPair wie folgt:


  1. Das erste Argument ist languageID , eine Zeichenfolge der Form " ProcessorName: Endianness: Bits: ExactCpu ". In meinem Fall sollte es die Zeile " 68000: BE: 32: MC68020 " sein (leider ist genau der MC68000 nicht im Lieferumfang enthalten, aber dies ist kein solches Problem). ExactCpu möglicherweise default
  2. Das zweite Argument, compilerSpecID , um zu finden, was Sie hier angeben müssen, befindet sich im Verzeichnis mit den Prozessorbeschreibungen ( $(GHIDRA)/Ghidra/Processors/68000/data/languages ) in der Datei 68000.opinion . Wir sehen, dass hier nur die default angegeben ist. Eigentlich geben wir es an

Als Ergebnis haben wir den folgenden Code (wie Sie sehen können, bisher nichts Kompliziertes):


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

Der Unterschied zwischen IDA und GHIDRA in Bezug auf Module

Es gibt einen Unterschied und es ist immer noch sehr stark. In GHIDRA Sie ein Projekt schreiben, das unterschiedliche Architekturen, unterschiedliche Datenformate, einen Loader, ein Prozessormodul, eine Erweiterung der Decompiler-Funktionalität und andere Extras versteht.


In der IDA ist dies ein separates Projekt für jeden Add-On-Typ.


Wie viel bequemer? Meiner Meinung nach bei GHIDRA - manchmal!


load ()


Bei dieser Methode erstellen wir Segmente, verarbeiten Eingabedaten, erstellen Code und zuvor bekannte Beschriftungen. Dazu werden folgende Objekte eingereicht, um uns am Eingang zu helfen:


  1. ByteProvider provider : Wir kennen ihn bereits. Arbeiten mit Binärdateidaten
  2. LoadSpec loadSpec : Angabe der Architektur, die während der findSupportedLoadSpecs der Datei mit der Methode findSupportedLoadSpecs . Es ist notwendig, wenn wir beispielsweise mit mehreren Datenformaten in einem Modul arbeiten können. Bequem
  3. List<Option> options : Liste der Optionen (einschließlich benutzerdefinierter Optionen). Ich habe noch nicht gelernt, mit ihnen zu arbeiten
  4. Program program : Das Hauptobjekt, das Zugriff auf alle erforderlichen Funktionen bietet: Auflistung, Adressraum, Segmente, Beschriftungen, Erstellen von Arrays usw.
  5. MemoryConflictHandler handler und TaskMonitor monitor : Wir müssen selten direkt mit ihnen arbeiten (normalerweise übergeben wir diese Objekte nur an vorgefertigte Methoden).
  6. MessageLog log : Logger selbst

Lassen Sie uns zunächst einige Objekte erstellen, die unsere Arbeit mit GHIDRA Entitäten und vorhandenen Daten vereinfachen. Natürlich brauchen wir auf jeden Fall BinaryReader :


 BinaryReader reader = new BinaryReader(provider, false); 

Weiter. Es wird für uns sehr nützlich sein und fast das gesamte Objekt der FlatProgramAPI Klasse vereinfachen (Sie werden später sehen, was Sie damit tun können):


 FlatProgramAPI fpa = new FlatProgramAPI(program, monitor); 

Rum Überschrift


Zunächst werden wir bestimmen, wie die Überschrift eines gewöhnlichen Sega-Rums lautet. In den ersten 0x100 Bytes gibt es eine Tabelle mit 64 DWORD-Zeigern auf Vektoren, zum Beispiel: Reset , Trap , DivideByZero , VBLANK und andere.


Als nächstes folgt die Struktur mit dem Namen der Roma, Regionen, Adressen des Anfangs und Endes der ROM und RAM Blöcke, dem Kontrollkästchen (das Feld wird auf Anfrage der Entwickler und nicht dem Präfix aktiviert) und anderen Informationen.


Erstellen wir java Klassen, um mit diesen Strukturen zu arbeiten und die Datentypen zu implementieren, die der Liste der Strukturen hinzugefügt werden.


VectorsTable


Wir erstellen eine neue Klasse VectorsTable und weisen darauf hin, dass sie die StructConverter Schnittstelle implementiert. In dieser Klasse speichern wir die Adressen von Vektoren (für die zukünftige Verwendung) und deren Namen.



Wir deklarieren eine Liste von Vektornamen und deren Nummer:


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

Wir erstellen eine separate Klasse zum Speichern der Adresse und des Vektornamens:


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

Die Liste der Vektoren wird im Vektorarray gespeichert:


 private VectorFunc[] vectors; 

Der Konstruktor für VectorsTable akzeptiert:


  1. FlatProgramAPI fpa zum Konvertieren long Adressen in den Address von Hydra (tatsächlich ergänzt dieser Datentyp den einfachen numerischen Wert der Adresse, indem er sie an einen anderen Chip bindet - den Adressraum).
  2. BinaryReader reader - Lesehöfe

Das fpa Objekt hat eine toAddr() -Methode, und der reader hat setPointerIndex() und readNextUnsignedInt() . Grundsätzlich ist nichts mehr erforderlich. Wir bekommen den 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]); } } 

Die toDataType() -Methode, die wir überschreiben müssen, um die Struktur zu implementieren, sollte ein Structure , in dem die Namen der Strukturfelder, ihre Größen und Kommentare zu jedem Feld deklariert werden sollten (Sie können 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; } 

Lassen Sie uns Methoden implementieren, um jeden der Vektoren oder die gesamte Liste (eine Reihe von Boilerplate-Code) abzurufen:


Andere Methoden
  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


Wir werden dasselbe tun und eine GameHeader Klasse erstellen, die die StructConverter Schnittstelle implementiert.


Game Rum Header Struktur
Offset startenEndversatzBeschreibung
100 $$ 10FKonsolenname (normalerweise 'SEGA MEGA DRIVE' oder 'SEGA GENESIS')
$ 110$ 11FErscheinungsdatum (normalerweise '© XXXX YYYY.MMM', wobei XXXX der Buchungskreis ist, YYYY das Jahr und MMM - Monat)
120 $$ 14FInländischer Name
$ 150$ 17FInternationaler Name
$ 180$ 18DVersion ('XX JJJJJJJJJJ' 'wobei XX der Spieltyp und JJ der Spielcode ist)
$ 18E$ 18FPrüfsumme
190 $$ 19FE / A-Unterstützung
$ 1A0$ 1A3ROM-Start
$ 1A4$ 1A7ROM-Ende
$ 1A8$ 1ABRAM-Start (normalerweise $ 00FF0000)
$ 1AC$ 1AFRAM-Ende (normalerweise $ 00FFFFFF)
$ 1B0$ 1B2'RA' und $ F8 aktivieren SRAM
$ 1B3----unbenutzt ($ 20)
$ 1B4$ 1B7SRAM-Start (Standard $ 00200000)
$ 1B8$ 1BBSRAM-Ende (Standard $ 0020FFFF)
$ 1BC$ 1FFNotizen (unbenutzt)

Wir richten die Felder ein, prüfen, ob die Eingabedaten ausreichend lang sind, verwenden die beiden neuen Methoden readNextByteArray() , readNextUnsignedShort() des reader Objekts, um Daten zu lesen, und erstellen eine Struktur. Der resultierende Code lautet wie folgt:


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

Erstellen Sie Objekte für den Header:


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

Segmente


Sega hat eine bekannte Karte von Speicherbereichen, die ich hier vielleicht nicht in Form einer Tabelle angeben werde, sondern nur den Code, der zum Erstellen der Segmente verwendet wird.


Das Objekt der FlatProgramAPI Klasse verfügt also über eine createMemoryBlock() -Methode, mit der bequem Speicherbereiche erstellt werden können. Bei der Eingabe werden die folgenden Argumente verwendet:


  1. name : Name der Region
  2. address : Startadresse der Region
  3. stream : Ein Objekt vom Typ InputStream , das die Basis für die Daten im Speicherbereich bildet. Wenn Sie null angeben, wird ein nicht initialisierter Bereich erstellt (für 68K RAM oder Z80 RAM benötigen wir genau das
  4. size : Größe der erstellten Region
  5. isOverlay : isOverlay true oder false und zeigt an, dass der Speicherbereich überlagert ist. Wo es neben ausführbaren Dateien benötigt wird

In der Ausgabe gibt createMemoryBlock() ein Objekt vom Typ MemoryBlock , für das Sie optional Zugriffsrechte-Flags ( Read , Write , Execute ) setzen können.


Als Ergebnis erhalten wir eine Funktion der folgenden Form:


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

Hier haben wir zusätzlich die statische error der Msg Klasse aufgerufen, um eine Fehlermeldung anzuzeigen.


Ein Segment mit 0x3FFFFF kann eine maximale Größe von 0x3FFFFF (alles andere gehört bereits zu anderen Regionen). Lassen Sie es uns erstellen:


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


All Articles