تحديث غيدرا. محمل رم سيجا ميجا محرك


تحياتي ، أيها الرفاق. لم أسمع عن GHIDRA لم يتم فتحها بعد ، وربما فقط مهندس عكسي للصم / أعمى / أخرس / لا GHIDRA الإنترنت. تعتبر إمكاناتها مذهلة للغاية: فك المحولات لجميع المعالجات المدعومة ، إضافة بسيطة إلى بنيات جديدة (مع إزالة نشطة نشطة بسبب التحويل المؤهل إلى IR) ، مجموعة من البرامج النصية التي تبسط الحياة ، إمكانية Undo / Redo ... وهذا جزء صغير جدًا من جميع الميزات المتوفرة. أن أقول إن أعجبت هو أن أقول شيئًا تقريبًا.


لذا ، أود في هذه المقالة أن أخبرك كيف كتبت أول وحدة نمطية لـ GHIDRA - محمل الروم للألعاب لـ Sega Mega Drive / Genesis . لكتابة ذلك كنت بحاجة ... فقط بضع ساعات! دعنا نذهب.


ولكن ماذا عن المؤسسة الدولية للتنمية؟

قضيت بعض الأيام في فهم عملية كتابة برامج التنزيل لـ IDA . يبدو أنه كان الإصدار 6.5 ، ولكن في تلك الأيام كان هناك الكثير من المشاكل مع وثائق SDK.


نحن نستعد بيئة التطوير


فكر مطورو GHIDRA كل شيء تقريبًا ( Ilfak ، أين كنت من قبل؟). ولتبسيط تنفيذ الوظيفة الجديدة ، قاموا بتطوير مكون إضافي لـ Eclipse - GhidraDev ، والذي " يساعد " بالفعل في كتابة التعليمات البرمجية. تم دمج المكوّن الإضافي في بيئة التطوير ، ويسمح لك بإنشاء قوالب مشروع للبرامج النصية واللوادر والوحدات النمطية للمعالج وملحقاتها ، وكذلك وحدات التصدير (كما أفهمها ، هذا نوع من تصدير البيانات من مشروع) ببضع نقرات.


لتثبيت المكوّن الإضافي ، قم بتنزيل Eclipse for Java ، وانقر فوق Help -> Install New Software... ، ثم انقر فوق الزر " Add ، وافتح مربع حوار تحديد الأرشيف مع المكون الإضافي باستخدام الزر " Archive... . يوجد الأرشيف مع GhidraDev في دليل $(GHIDRA)/Extensions/Eclipse/GhidraDev . حدده ، انقر فوق الزر " Add .



في القائمة التي تظهر ، ضع مؤشر Ghidra على Ghidra ، انقر فوق Next > ، والموافقة على الاتفاقيات ، وانقر فوق Install Anyway (لأن الملحق ليس له توقيع) ، وأعد تشغيل Eclipse .



في المجموع ، GhidraDev عنصر GhidraDev جديد في GhidraDev IDE لإنشاء مشاريعك وتوزيعها بطريقة مريحة (بالطبع ، يمكنك أيضًا إنشاء المعالج المعتاد لمشاريع Eclipse الجديدة). بالإضافة إلى ذلك ، لدينا الفرصة لتصحيح المكون الإضافي أو البرنامج النصي الذي تم تطويره.



وأين هو مصحح أخطاء التطبيق؟

إن ما يثير حفيظة الموقف في GHIDRA هو مقالات الضجيج GHIDRA التي تحتوي على نفس المادة تقريبًا ، والتي ، علاوة على ذلك ، ليست صحيحة. مثال؟ نعم من فضلك:


الإصدار الحالي للأداة هو 9.0. وللأداة خيارات لتضمين وظائف إضافية مثل Cryptanalysis ، والتفاعل مع OllyDbg ، Ghidra Debugger.

وأين كل هذا؟ كلا!


النقطة الثانية: الانفتاح. في الواقع ، هناك تقريبا ، لكنه غير موجود عمليا. يحتوي GHIDRA على أكواد مصدر للمكونات التي تمت كتابتها في Java ، ولكن إذا نظرت إلى البرامج النصية Gradle ، يمكنك أن ترى أن هناك تبعية على مجموعة من المشاريع الخارجية من تلك السرية مختبر مستودعات NSA .
في وقت كتابة هذا التقرير ، لم تكن هناك SLEIGH و SLEIGH (هذه أداة مساعدة لتجميع أوصاف وحدات المعالج والتحويلات إلى IR).


حسنًا ، حسنًا ، لقد صرفت انتباهي عن شيء ما.


لذلك ، دعونا ننشئ مشروعًا جديدًا في Eclipse .


إنشاء مشروع محمل


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


نشير إلى اسم المشروع (نأخذ في الاعتبار أن كلمات نوع Loader سيتم لصقها بأسماء الملفات ، ومن أجل عدم الحصول على شيء مثل sega_loaderLoader.java ، فإننا sega_loaderLoader.java وفقًا لذلك).



انقر فوق Next > . هنا نضع daws أمام الفئات التي نحتاجها. في حالتي ، هذه ليست سوى Loader . انقر فوق Next > .



هنا نشير إلى الطريق إلى الدليل مع . انقر فوق Next > .



GHIDRA يسمح GHIDRA بكتابة البرامج النصية في بيثون (عبر Jython ). سأكتب Java ، لذلك أنا لا أضع داو. أنا اضغط Finish .



كتابة رمز


تبدو شجرة المشروع الفارغة مثيرة للإعجاب:



جميع الملفات التي تحتوي على كود java موجودة في فرع /src/main/java :



getName ()


للبدء ، دعنا نختار اسمًا لمحمل الإقلاع. إرجاع الأسلوب getName() :


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

findSupportedLoadSpecs ()


يقرر أسلوب findSupportedLoadSpecs() (بناءً على البيانات الموجودة في الملف الثنائي) وحدة المعالج التي يجب استخدامها للتفكيك (وكذلك في IDA ). في المصطلحات GHIDRA وهذا ما يسمى Compiler Language . ويشمل: المعالج ، endianness ، bitness ومترجم (إذا كانت معروفة).


تقوم هذه الطريقة بإرجاع قائمة بنيات ولغات معتمدة. إذا كانت البيانات بتنسيق خاطئ ، فسنقوم ببساطة بإرجاع قائمة فارغة.


لذلك ، في حالة Sega Mega Drive ، غالبًا ما تكون كلمة " SEGA " موجودة عند الإزاحة 0x100 للرأس (هذا ليس شرطًا أساسيًا ، ولكن يتم الوفاء به في 99٪ من الحالات). تحتاج إلى التحقق مما إذا كان هذا الخط في الملف المستورد. للقيام بذلك ، يتم ByteProvider provider findSupportedLoadSpecs() ، والذي findSupportedLoadSpecs() معه مع الملف.


نقوم بإنشاء كائن BinaryReader ، لراحة قراءة البيانات من ملف:


 BinaryReader reader = new BinaryReader(provider, false); 

تشير الحجة false في هذه الحالة إلى استخدام Big Endian عند القراءة. الآن دعونا نقرأ السطر. للقيام بذلك ، استخدم طريقة readAsciiString(offset, size) للكائن reader :


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

إذا كانت قيمة equals() true ، فإننا نتعامل مع Segov rum ، وفي القائمة List<LoadSpec> loadSpecs = new ArrayList<>(); سيكون من الممكن إضافة موتورولا m68k . للقيام بذلك ، قم بإنشاء كائن جديد من نوع LoadSpec ، يقبل ImageBase كائن أداة تحميل (في هذه الحالة ، this ) ، ImageBase ، حيث سيتم تحميل ROM ، كائن من نوع LanguageCompilerSpecPair LoadSpec - هو هذا LoadSpec بين الآخرين في القائمة (نعم ، في القائمة قد يكون هناك أكثر من LoadSpec ).


تنسيق المنشئ لـ LanguageCompilerSpecPair كما يلي:


  1. الوسيطة الأولى هي languageID ، سلسلة من النموذج " ProcessorName: Endianness: Bits: ExactCpu ". في حالتي ، يجب أن يكون السطر " 68000: BE: 32: MC68020 " (لسوء الحظ ، لم يتم تضمين MC68000 بالضبط في عملية التسليم ، ولكن هذه ليست مشكلة). ExactCpu قد يكون default
  2. يمكن العثور على الوسيطة الثانية ، compilerSpecID ، للعثور على ما تحتاج إلى تحديده هنا ، في الدليل مع وصف معالج ( $(GHIDRA)/Ghidra/Processors/68000/data/languages ) في الملف 68000.opinion . نرى أن default هو الوحيد المشار إليه هنا. في الواقع ، فإننا نشير إلى ذلك

نتيجة لذلك ، لدينا الكود التالي (كما ترون ، لا يوجد شيء معقد حتى الآن):


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

الفرق بين المؤسسة الدولية للتنمية و GHIDRA من حيث الوحدات

هناك فرق ، وما زال قويا جدا. في GHIDRA يمكنك كتابة مشروع واحد من شأنه أن يفهم GHIDRA مختلفة ، وتنسيقات بيانات مختلفة ، ويكون GHIDRA ووحدة معالج ، وتوسيع وظيفة أداة فك التشفير ، وغيرها من الأشياء الجيدة.


في IDA ، هذا مشروع منفصل لكل نوع من الإضافات.


كم أكثر ملاءمة؟ في رأيي ، في GHIDRA - في بعض الأحيان!


تحميل ()


في هذه الطريقة ، سنقوم بإنشاء شرائح ومعالجة بيانات الإدخال وإنشاء رمز والتسميات المعروفة سابقًا. للقيام بذلك ، يتم تقديم الكائنات التالية لمساعدتنا عند المدخل:


  1. ByteProvider provider : نحن نعرفه بالفعل. العمل مع بيانات الملف الثنائي
  2. LoadSpec loadSpec : مواصفات البنية التي تم LoadSpec loadSpec أثناء مرحلة الاستيراد للملف باستخدام طريقة findSupportedLoadSpecs . من الضروري ، على سبيل المثال ، أن نكون قادرين على العمل مع العديد من تنسيقات البيانات في وحدة واحدة. مريح
  3. List<Option> options : قائمة الخيارات (بما في ذلك العرف). لم أتعلم بعد العمل معهم
  4. Program program : الكائن الرئيسي الذي يوفر الوصول إلى جميع الوظائف اللازمة: القائمة ، مساحة العنوان ، القطاعات ، التسميات ، إنشاء المصفوفات ، إلخ.
  5. MemoryConflictHandler handler TaskMonitor monitor : نادراً ما يتعين علينا العمل معهم مباشرة (عادة ، فقط نقل هذه الكائنات إلى أساليب جاهزة)
  6. MessageLog log : MessageLog log نفسه

لذلك ، بالنسبة للمبتدئين ، دعونا ننشئ بعض الكائنات التي من شأنها تبسيط عملنا مع كيانات GHIDRA والبيانات الموجودة. بالطبع ، سنحتاج بالتأكيد إلى BinaryReader :


 BinaryReader reader = new BinaryReader(provider, false); 

التالي. سيكون مفيدًا للغاية بالنسبة لنا وسيعمل على تبسيط الكائن بالكامل تقريبًا من فئة FlatProgramAPI (سترى لاحقًا ما يمكنك فعله به):


 FlatProgramAPI fpa = new FlatProgramAPI(program, monitor); 

رم رم


بادئ ذي بدء ، سوف نحدد عنوان عنوان Sega rum العادي. في البايتات 0x100 الأولى ، يوجد جدول 64 مؤشر DWORD إلى المتجهات ، على سبيل المثال: Reset و Trap و DivideByZero و VBLANK وغيرها.


يأتي بعد ذلك الهيكل الذي يحمل اسم روما ، والمناطق ، وعناوين بداية ونهاية كتل ROM RAM ، وخانة الاختيار (يتم تحديد الحقل بناءً على طلب المطورين ، وليس البادئة) وغيرها من المعلومات.


لنقم بإنشاء فئات java للعمل مع هذه الهياكل ، وكذلك لتنفيذ أنواع البيانات التي سيتم إضافتها إلى قائمة الهياكل.


VectorsTable


نقوم بإنشاء VectorsTable فئة جديدة ، والانتباه ، فإننا نشير إلى أنه ينفذ واجهة StructConverter . في هذه الفئة ، سنقوم بتخزين عناوين المتجهات (للاستخدام في المستقبل) وأسمائهم.



نعلن قائمة بأسماء المتجهات وعددهم:


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

نقوم بإنشاء فصل منفصل لتخزين العنوان واسم المتجه:


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

سيتم تخزين قائمة المتجهات في صفيف vectors :


 private VectorFunc[] vectors; 

VectorsTable مُنشئ VectorsTable :


  1. FlatProgramAPI fpa لتحويل العناوين long إلى نوع بيانات Address Hydra (في الواقع ، يكمل نوع البيانات هذا القيمة العددية البسيطة للعنوان من خلال FlatProgramAPI fpa أخرى - مساحة العنوان)
  2. BinaryReader reader - ساحات القراءة

كائن fpa له أسلوب toAddr() ، reader لديه setPointerIndex() و readNextUnsignedInt() . من حيث المبدأ ، لا شيء مطلوب. نحصل على الكود:


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

يجب أن toDataType() طريقة toDataType() ، التي نحتاج إلى تجاوزها لتنفيذ الهيكل ، كائن Structure الذي يجب أن تعلن فيه أسماء حقول الهيكل وأحجامها وتعليقاتها على كل حقل (يمكنك استخدام قيمة 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; } 

حسنًا ، دعنا ننفذ طرقًا للحصول على كل من المتجهات ، أو القائمة الكاملة (مجموعة من التعليمات البرمجية لـ boilerplate):


طرق أخرى
  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


سنفعل نفس الشيء GameHeader فئة GameHeader التي تنفذ واجهة StructConverter .


لعبة هيكل رأس الروم
إزاحة البدءنهاية الإزاحةوصف
100 دولار$ 10Fاسم وحدة التحكم (عادةً "SEGA MEGA DRIVE" أو "SEGA GENESIS")
110 دولار$ 11Fتاريخ الإصدار (عادة "© XXXX YYYY.MMM" حيث XXXX هي رمز الشركة ، YYYY هي السنة و MMM - شهر)
120 دولار14F دولارالاسم المحلي
150 دولار$ 17Fالاسم الدولي
180 دولار18 دولارالإصدار ('XX YYYYYYYYYYYY' حيث XX هو نوع اللعبة و YY رمز اللعبة)
18E دولار$ 18Fاختباري
190 دولار19F دولارأنا / س الدعم
1A0 دولار1A3 دولاربدء ROM
1A4 دولار1A7 دولارنهاية ROM
1A8 دولار$ 1ABبدء RAM (عادةً 00FF0000 دولار)
1AC دولار$ 1AFذاكرة الوصول العشوائي نهاية (عادة $ 00FFFFFF)
$ 1B01B2 دولار"RA" و $ F8 تمكن SRAM
1B3 دولار----غير مستخدم (20 دولارًا)
1B4 دولار1B7 دولاربداية SRAM (الافتراضي $ 200200)
$ 1B81 مليار دولارنهاية SRAM (الافتراضي $ 0020FFFF)
1 مليار دولار$ 1FFملاحظات (غير مستخدمة)

لقد أنشأنا الحقول ، readNextByteArray() طولًا كافيًا من بيانات الإدخال ، readNextByteArray() طريقتين جديدتين لنا readNextByteArray() ، readNextUnsignedShort() لكائن reader لقراءة البيانات ، وإنشاء بنية. الكود الناتج هو كما يلي:


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

إنشاء كائنات للرأس:


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

شرائح


لدى Sega خريطة معروفة لمناطق الذاكرة ، والتي ربما لن أقدمها هنا في شكل جدول ، لكنني سأقدم فقط الكود المستخدم في إنشاء الشرائح.


لذلك ، يحتوي كائن فئة createMemoryBlock() طريقة createMemoryBlock() ، والتي تكون ملائمة لإنشاء مناطق الذاكرة. عند الإدخال ، يستغرق الوسائط التالية:


  1. name : اسم المنطقة
  2. address : address بداية المنطقة
  3. stream : كائن من النوع InputStream الذي سيكون أساس البيانات في منطقة الذاكرة. إذا حددت null ، فسيتم إنشاء منطقة غير مهيأة (على سبيل المثال ، على 68K RAM Z80 RAM أو Z80 RAM سنحتاج إلى ذلك
  4. size : حجم المنطقة التي تم إنشاؤها
  5. isOverlay : يقبل true أو false ، ويشير إلى أن منطقة الذاكرة isOverlay . لا أعرف مكان الحاجة إليه باستثناء الملفات القابلة للتنفيذ

في الإخراج ، تقوم createMemoryBlock() بإرجاع كائن من النوع MemoryBlock ، حيث يمكنك اختيارياً اختيار إشارات الوصول ( Read ، Write ، Execute ).


نتيجة لذلك ، نحصل على وظيفة من النموذج التالي:


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

هنا قمنا بالإضافة إلى ذلك تسمى طريقة error ثابت للفئة Msg لعرض رسالة خطأ.


يمكن أن يكون الحد الأقصى لحجم مقطع يحتوي على روم اللعبة 0x3FFFFF (كل شيء آخر سوف ينتمي بالفعل إلى مناطق أخرى). لنقم بإنشائه:


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


All Articles