Modernisasi GHIDRA. Loader untuk rum Sega Mega Drive


Salam, kawan. Saya belum pernah mendengar tentang GHIDRA belum open-source, mungkin hanya reverse engineer yang tuli / buta / bisu / tidak ada Internet. Kemampuannya yang luar biasa: dekompiler untuk semua prosesor yang didukung, penambahan sederhana arsitektur baru (dengan dekompilasi aktif langsung karena konversi yang kompeten ke IR), sekelompok skrip yang menyederhanakan kehidupan, kemungkinan Undo / Redo ... Dan ini hanya sebagian kecil dari semua fitur yang disediakan. Mengatakan bahwa saya terkesan sama saja dengan mengatakan apa-apa.


Jadi, dalam artikel ini saya ingin memberi tahu Anda bagaimana saya menulis modul pertama saya untuk GHIDRA - pemuat rum untuk game untuk Sega Mega Drive / Genesis . Untuk menulisnya saya perlu ... hanya beberapa jam! Ayo pergi.


Tapi bagaimana dengan IDA?

Saya menghabiskan beberapa hari memahami proses penulisan pengunduh untuk IDA . Lalu itu versi 6.5 , tampaknya, tetapi pada masa itu ada banyak masalah dengan dokumentasi SDK.


Kami mempersiapkan lingkungan pengembangan


Pengembang GHIDRA memikirkan hampir segalanya ( Ilfak , di mana saja Anda sebelumnya?). Dan, hanya untuk menyederhanakan implementasi fungsi baru, mereka mengembangkan plug-in untuk Eclipse - GhidraDev , yang sebenarnya " membantu " menulis kode. Plugin terintegrasi ke dalam lingkungan pengembangan, dan memungkinkan Anda untuk membuat template proyek untuk skrip, loader, modul prosesor dan ekstensi untuk mereka, serta modul ekspor (seperti yang saya mengerti, ini adalah semacam ekspor data dari suatu proyek) dengan beberapa klik.


Untuk menginstal plugin, unduh Eclipse for Java , klik Help -> Install New Software... , lalu klik tombol Add , dan buka dialog pemilihan arsip dengan plugin menggunakan tombol Archive... Arsip dengan GhidraDev terletak di $(GHIDRA)/Extensions/Eclipse/GhidraDev . Pilih itu, klik tombol Add .



Dalam daftar yang muncul, beri tahu Ghidra , klik Next > , setujui perjanjian, klik Install Anyway (karena plugin tidak memiliki tanda tangan), dan mulai ulang Eclipse .



Secara total, item GhidraDev baru akan muncul di GhidraDev IDE untuk pembuatan dan distribusi proyek Anda yang nyaman (tentu saja, Anda juga dapat membuat melalui wizard yang biasa untuk proyek Eclipse baru). Selain itu, kami memiliki kesempatan untuk men-debug plug-in atau skrip yang dikembangkan.



Dan di mana aplikasi debugger?

Yang benar-benar membuat marah situasi dengan GHIDRA adalah artikel-artikel hype parau yang berisikan materi yang hampir sama, yang, apalagi, tidak benar. Sebuah contoh? Ya tolong:


Versi alat saat ini adalah 9.0. dan alat ini memiliki opsi untuk memasukkan fungsi tambahan seperti Cryptanalysis, interaksi dengan OllyDbg, Ghidra Debugger.

Dan di mana semua ini? Tidak!


Poin kedua: keterbukaan. Sebenarnya, itu hampir ada, tetapi secara praktis tidak ada. GHIDRA berisi kode sumber untuk komponen yang ditulis dalam Java , tetapi jika Anda melihat skrip Gradle , Anda dapat melihat bahwa ada ketergantungan pada sekelompok proyek eksternal dari yang rahasia laboratorium Repositori NSA .
Pada saat penulisan, tidak ada SLEIGH decompiler dan SLEIGH (ini adalah utilitas untuk menyusun deskripsi modul prosesor dan konversi ke IR).


Yah oh well, saya terganggu oleh sesuatu.


Jadi, mari kita buat proyek baru di Eclipse .


Buat proyek pemuat


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


Kami menunjukkan nama proyek (kami memperhitungkan bahwa kata-kata dari jenis Loader akan terpaku pada nama file, dan agar tidak mendapatkan sesuatu seperti sega_loaderLoader.java , kami sega_loaderLoader.java nama yang sesuai).



Klik Next > . Di sini kita menempatkan daw di depan kategori yang kita butuhkan. Dalam kasus saya, ini hanya Loader . Klik Next > .



Di sini kami menunjukkan jalur ke direktori dengan . Klik Next > .



GHIDRA memungkinkan GHIDRA untuk menulis skrip dengan python (via Jython ). Saya akan menulis di Java , jadi saya tidak menaruh daw. Saya tekan Finish .



Menulis kode


Pohon proyek kosong terlihat mengesankan:



Semua file dengan kode java ada di cabang /src/main/java :



getName ()


Untuk memulai, mari pilih nama untuk bootloader. Metode getName() mengembalikannya:


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

findSupportedLoadSpecs ()


Metode findSupportedLoadSpecs() memutuskan (berdasarkan data yang terkandung dalam file biner) modul prosesor mana yang harus digunakan untuk pembongkaran (serta dalam IDA ). Dalam terminologi GHIDRA ini disebut Compiler Language . Ini termasuk: prosesor, endianness, bitness, dan kompiler (jika diketahui).


Metode ini mengembalikan daftar arsitektur dan bahasa yang didukung. Jika data dalam format yang salah, kami cukup mengembalikan daftar kosong.


Jadi, dalam kasus Sega Mega Drive , kata " SEGA " paling sering hadir pada offset 0x100 header (ini bukan prasyarat, tetapi dipenuhi dalam 99% kasus). Anda perlu memeriksa apakah baris ini ada dalam file yang diimpor. Untuk melakukan ini, ByteProvider provider findSupportedLoadSpecs() , yang dengannya kami akan bekerja dengan file tersebut.


Kami membuat objek BinaryReader , untuk kenyamanan membaca data dari file:


 BinaryReader reader = new BinaryReader(provider, false); 

Argumen false dalam hal ini menunjukkan penggunaan Big Endian saat membaca. Sekarang mari kita baca barisnya. Untuk melakukan ini, gunakan metode readAsciiString(offset, size) dari objek reader :


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

Jika equals() mengembalikan true , maka kita berurusan dengan Segov rum, dan dalam daftar List<LoadSpec> loadSpecs = new ArrayList<>(); dimungkinkan untuk menambahkan Motorola m68k . Untuk melakukan ini, buat objek baru tipe LoadSpec , konstruktor yang menerima objek loader (dalam hal ini, this ), ImageBase , ke mana ROM akan dimuat, objek tipe LanguageCompilerSpecPair dan bendera - apakah ini LoadSpec antara yang lain dalam daftar (ya, dalam daftar mungkin ada lebih dari satu LoadSpec ).


Format konstruktor untuk LanguageCompilerSpecPair sebagai berikut:


  1. Argumen pertama adalah languageID , string dari bentuk " ProcessorName: Endianness: Bits: ExactCpu ". Dalam kasus saya, seharusnya baris " 68000: BE: 32: MC68020 " (sayangnya, tepatnya MC68000 tidak termasuk dalam pengiriman, tetapi ini bukan masalah seperti itu). ExactCpu mungkin default
  2. Argumen kedua, compilerSpecID , untuk menemukan apa yang perlu Anda tentukan di sini, dapat ditemukan di direktori dengan deskripsi prosesor ( $(GHIDRA)/Ghidra/Processors/68000/data/languages ) dalam file 68000.opinion . Kami melihat bahwa hanya default yang ditunjukkan di sini. Sebenarnya, kami mengindikasikannya

Akibatnya, kami memiliki kode berikut (seperti yang Anda lihat, tidak ada yang rumit sejauh ini):


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

Perbedaan antara IDA dan GHIDRA dalam hal modul

Ada perbedaan, dan itu masih sangat kuat. Di GHIDRA Anda dapat menulis satu proyek yang akan memahami arsitektur yang berbeda, format data yang berbeda, menjadi loader, modul prosesor, perluasan fungsi dekompiler, dan barang lainnya.


Di IDA , ini adalah proyek terpisah untuk setiap jenis add-on.


Seberapa jauh lebih nyaman? Menurut pendapat saya, di GHIDRA - kadang-kadang!


memuat ()


Dalam metode ini, kami akan membuat segmen, memproses input data, membuat kode, dan label yang sebelumnya dikenal. Untuk melakukan ini, objek berikut dikirimkan untuk membantu kami di pintu masuk:


  1. ByteProvider provider : kami sudah mengenalnya. Bekerja dengan data file biner
  2. LoadSpec loadSpec : spesifikasi arsitektur yang dipilih selama fase impor file menggunakan metode findSupportedLoadSpecs . Diperlukan jika, misalnya, kami dapat bekerja dengan beberapa format data dalam satu modul. Nyaman
  3. List<Option> options : daftar opsi (termasuk kustom). Saya belum belajar bekerja dengan mereka
  4. Program program : objek utama yang menyediakan akses ke semua fungsi yang diperlukan: daftar, ruang alamat, segmen, label, membuat array, dll.
  5. MemoryConflictHandler handler dan TaskMonitor monitor : kita jarang harus bekerja secara langsung dengannya (biasanya, hanya meneruskan objek-objek ini ke metode yang sudah jadi)
  6. MessageLog log : logger itu sendiri

Jadi, sebagai permulaan, mari kita buat beberapa objek yang akan menyederhanakan pekerjaan kita dengan entitas GHIDRA dan data yang ada. Tentu saja, kita pasti akan membutuhkan BinaryReader :


 BinaryReader reader = new BinaryReader(provider, false); 

Selanjutnya Ini akan sangat berguna bagi kami dan akan menyederhanakan hampir seluruh objek kelas FlatProgramAPI (Anda akan melihat nanti apa yang dapat Anda lakukan dengannya):


 FlatProgramAPI fpa = new FlatProgramAPI(program, monitor); 

Judul rum


Untuk mulai dengan, kita akan menentukan apa judul rum Sega biasa. Dalam 0x100 byte pertama ada tabel 64 DWORD-pointer ke vektor, misalnya: Reset , Trap , DivideByZero , VBLANK dan lainnya.


Berikutnya adalah struktur dengan nama Roma, wilayah, alamat awal dan akhir dari blok ROM dan RAM , kotak centang (bidang diperiksa atas permintaan pengembang, dan bukan awalan) dan informasi lainnya.


Mari kita buat kelas java untuk bekerja dengan struktur ini, serta untuk mengimplementasikan tipe data yang akan ditambahkan ke daftar struktur.


Tabel Vektor


Kami membuat VectorsTable kelas baru, dan, perhatikan, kami mengindikasikan bahwa itu mengimplementasikan antarmuka StructConverter . Di kelas ini kita akan menyimpan alamat vektor (untuk penggunaan di masa depan) dan namanya.



Kami mendeklarasikan daftar nama vektor dan jumlahnya:


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

Kami membuat kelas terpisah untuk menyimpan alamat dan nama vektor:


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

Daftar vektor akan disimpan dalam array vectors :


 private VectorFunc[] vectors; 

Konstruktor untuk VectorsTable akan menerima:


  1. FlatProgramAPI fpa untuk mengonversi alamat long ke tipe data Address Hydra (pada kenyataannya, tipe data ini melengkapi nilai numerik sederhana dari alamat dengan mengikatnya ke chip lain - ruang alamat)
  2. BinaryReader reader - halaman baca

Objek toAddr() memiliki metode toAddr() , dan reader memiliki setPointerIndex() dan readNextUnsignedInt() . Pada prinsipnya, tidak ada lagi yang diperlukan. Kami mendapatkan kode:


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

Metode toDataType() , yang perlu kita timpa untuk mengimplementasikan struktur, harus mengembalikan objek Structure di mana nama bidang struktur, ukurannya, dan komentar pada setiap bidang harus dinyatakan (Anda dapat menggunakan 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; } 

Baiklah, mari kita terapkan metode untuk mendapatkan masing-masing vektor, atau seluruh daftar (sekelompok kode boilerplate):


Metode lainnya
  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


Kami akan melakukan hal yang sama dan membuat kelas GameHeader yang mengimplementasikan antarmuka StructConverter .


Struktur header rum game
Mulai offsetEnd offsetDeskripsi
$ 100$ 10FNama konsol (biasanya 'SEGA MEGA DRIVE' atau 'SEGA GENESIS')
$ 110$ 11FTanggal rilis (biasanya '© XXXX YYYY.MMM' di mana XXXX adalah kode perusahaan, YYYY adalah tahun dan MMM - bulan)
$ 120$ 14FNama domestik
$ 150$ 17FNama internasional
$ 180$ 18DVersi ('XX YYYYYYYYYYYY' di mana XX adalah jenis permainan dan YY kode permainan)
$ 18E$ 18FChecksum
$ 190$ 19FDukungan I / O
$ 1A0$ 1A3ROM mulai
$ 1A4$ 1A7ROM berakhir
$ 1A8$ 1ABRAM mulai (biasanya $ 00FF0000)
$ 1AC$ 1AFRAM akhir (biasanya $ 00FFFFFF)
$ 1B0$ 1B2'RA' dan $ F8 memungkinkan SRAM
$ 1B3----tidak digunakan ($ 20)
$ 1B4$ 1B7SRAM mulai (default $ 00200000)
$ 1B8$ 1BBSRAM end (default $ 0020FFFF)
$ 1BC$ 1FFCatatan (tidak digunakan)

Kami menyiapkan bidang, memeriksa panjang yang cukup dari data input, menggunakan dua metode untuk kita readNextByteArray() , readNextUnsignedShort() dari objek reader untuk membaca data, dan membuat struktur. Kode yang dihasilkan adalah sebagai berikut:


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

Buat objek untuk tajuk:


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

Segmen


Sega memiliki peta wilayah memori yang terkenal, yang mungkin tidak akan saya berikan di sini dalam bentuk tabel, tetapi hanya kode yang digunakan untuk membuat segmen.


Jadi, objek kelas FlatProgramAPI memiliki metode createMemoryBlock() , yang nyaman untuk membuat daerah memori. Pada input, dibutuhkan argumen berikut:


  1. name : nama wilayah
  2. address : address mulai dari wilayah tersebut
  3. stream : objek bertipe InputStream yang akan menjadi dasar untuk data di wilayah memori. Jika Anda menetapkan null , wilayah yang tidak diinisialisasi akan dibuat (misalnya, untuk 68K RAM atau Z80 RAM kami hanya perlu
  4. size : ukuran wilayah yang dibuat
  5. isOverlay : menerima true atau false , dan menunjukkan bahwa wilayah memori overlay. Saya tidak tahu di mana dibutuhkan kecuali untuk file yang dapat dieksekusi

Pada output, createMemoryBlock() mengembalikan objek tipe MemoryBlock , yang Anda dapat mengatur flag hak akses secara opsional ( Read , Write , Execute ).


Akibatnya, kami mendapatkan fungsi dari formulir berikut:


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

Di sini kita juga disebut metode error statis kelas Msg untuk menampilkan pesan kesalahan.


Segmen yang berisi rum game dapat memiliki ukuran maksimum 0x3FFFFF (yang lainnya sudah menjadi bagian dari wilayah lain). Mari kita ciptakan:


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


All Articles