
تحياتي ، أيها الرفاق. لم أسمع عن 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
كما يلي:
- الوسيطة الأولى هي
languageID
، سلسلة من النموذج " ProcessorName: Endianness: Bits: ExactCpu ". في حالتي ، يجب أن يكون السطر " 68000: BE: 32: MC68020 " (لسوء الحظ ، لم يتم تضمين MC68000
بالضبط في عملية التسليم ، ولكن هذه ليست مشكلة). ExactCpu
قد يكون default
- يمكن العثور على الوسيطة الثانية ،
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
- في بعض الأحيان!
تحميل ()
في هذه الطريقة ، سنقوم بإنشاء شرائح ومعالجة بيانات الإدخال وإنشاء رمز والتسميات المعروفة سابقًا. للقيام بذلك ، يتم تقديم الكائنات التالية لمساعدتنا عند المدخل:
ByteProvider provider
: نحن نعرفه بالفعل. العمل مع بيانات الملف الثنائيLoadSpec loadSpec
: مواصفات البنية التي تم LoadSpec loadSpec
أثناء مرحلة الاستيراد للملف باستخدام طريقة findSupportedLoadSpecs
. من الضروري ، على سبيل المثال ، أن نكون قادرين على العمل مع العديد من تنسيقات البيانات في وحدة واحدة. مريحList<Option> options
: قائمة الخيارات (بما في ذلك العرف). لم أتعلم بعد العمل معهمProgram program
: الكائن الرئيسي الذي يوفر الوصول إلى جميع الوظائف اللازمة: القائمة ، مساحة العنوان ، القطاعات ، التسميات ، إنشاء المصفوفات ، إلخ.MemoryConflictHandler handler
TaskMonitor monitor
: نادراً ما يتعين علينا العمل معهم مباشرة (عادة ، فقط نقل هذه الكائنات إلى أساليب جاهزة)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
:
FlatProgramAPI fpa
لتحويل العناوين long
إلى نوع بيانات Address
Hydra (في الواقع ، يكمل نوع البيانات هذا القيمة العددية البسيطة للعنوان من خلال FlatProgramAPI fpa
أخرى - مساحة العنوان)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
التي تنفذ واجهة 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) |
$ 1B0 | 1B2 دولار | "RA" و $ F8 تمكن SRAM |
1B3 دولار | ---- | غير مستخدم (20 دولارًا) |
1B4 دولار | 1B7 دولار | بداية SRAM (الافتراضي $ 200200) |
$ 1B8 | 1 مليار دولار | نهاية 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()
، والتي تكون ملائمة لإنشاء مناطق الذاكرة. عند الإدخال ، يستغرق الوسائط التالية:
name
: اسم المنطقةaddress
: address
بداية المنطقةstream
: كائن من النوع InputStream
الذي سيكون أساس البيانات في منطقة الذاكرة. إذا حددت null
، فسيتم إنشاء منطقة غير مهيأة (على سبيل المثال ، على 68K RAM
Z80 RAM
أو Z80 RAM
سنحتاج إلى ذلكsize
: حجم المنطقة التي تم إنشاؤها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
. , :
address
: ,numElements
:dataType
:elementSize
:
applyTo(program)
, .
, , BYTE
, WORD
, DWORD
. , FlatProgramAPI
createByte()
, createWord()
, createDword()
..
, , (, VDP
). , :
Program
getSymbolTable()
, , ..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
. :
program
: Program
address
: ,dataType
:dataLength
: . -1
stackPointers
: true
, - . false
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
. .