GHIDRA ، الملفات التنفيذية لـ Playstation 1 ، توقيعات FLIRT ، و PsyQ

مرحبا بالجميع



لا أعلم عنك ، لكنني كنت أرغب دائمًا في عكس ألعاب وحدة التحكم القديمة ، مع وجود أداة فك تشفير متوفرة أيضًا. والآن ، جاءت هذه اللحظة البهيجة في حياتي - لقد خرجت GHIDRA . لن أكتب ما هو عليه ، يمكنك بسهولة جوجل. والمراجعات مختلفة جدًا (خاصة عن الوراء) ، حيث سيكون من الصعب على الوافد الجديد أن يقرر إطلاق هذه المعجزة ... وإليك مثال لك: " لقد عملت لأفكار لمدة 20 عامًا ، وأنا أنظر إلى هيدرا لديك بريبة كبيرة ، لأن وكالة الأمن القومي. لكن عندما - سأقوم بتشغيله والتحقق منه في الواقع ".


باختصار - تشغيل هيدرا ليست مخيفة. وما نحصل عليه بعد الإطلاق سيمنع كل خوفك من الإشارات المرجعية والخلفيات من وكالة الأمن القومي في كل مكان.


لذلك ، ما أتحدث عنه ... هناك بادئة مثل: Sony Playstation 1 ( PS1 ، PSX ، Curling iron ). تم إنشاء العديد من الألعاب الرائعة لذلك ، ظهرت مجموعة من الامتيازات التي لا تزال شعبية. وذات يوم أردت أن أعرف كيف تعمل: ما هي تنسيقات البيانات الموجودة ، سواء تم استخدام ضغط الموارد ، حاول ترجمة شيء ما إلى اللغة الروسية (سأقول على الفور أنني لم أترجم أي ألعاب حتى الآن).


بدأت بكتابة أداة مساعدة رائعة للعمل مع تنسيق TIM مع صديق على Delphi (هذا شيء مثل BMP من عالم Playstation): Tim2View . في وقت واحد ، تمتعت بالنجاح (وربما تتمتع الآن). ثم أردت أن أعمق في الضغط.


صورة


ثم بدأت المشاكل. لم أكن على دراية MIPS ذلك الحين. استغرق الدراسة. لم أكن على دراية بـ IDA Pro أيضًا (جئت لعكس الألعاب على Sega Mega Drive لاحقًا من Playstation ). ولكن بفضل الإنترنت ، اكتشفت أن IDA Pro يدعم تنزيل وتحليل ملفات PS1 : PS-X EXE القابلة للتنفيذ . حاولت تحميل ملف لعبة (يبدو أنهم Lemmings ) يحمل اسمًا SLUS_123.45 ، مثل SLUS_123.45 في Ida ، وحصلت على مجموعة من سطور التعليمات البرمجية المجمعة (لحسن الحظ ، كانت لدي فكرة عما كانت عليه ، بفضل برامج تشغيل Windows exe إلى x86) ، وبدأت في فهم.



كان أول مكان يصعب فهمه هو خط التجميع. على سبيل المثال ، ترى مكالمة لبعض الوظائف ، وبعد تحميلها مباشرة في السجل ، يجب استخدام المعلمة التي يجب استخدامها في هذه الوظيفة. باختصار ، قبل أي القفزات والمكالمات الوظيفية ، يتم تنفيذ التعليمات التي تلي القفزة / المكالمة لأول مرة ، وعندها فقط المكالمة أو القفز نفسه.


بعد كل الصعوبات التي مررت بها ، تمكنت من كتابة العديد من أدوات تعبئة / فك حزم موارد اللعبة. لكنني لم أدرس الكود مطلقًا. لماذا؟ حسنًا ، كل شيء عادي: كان هناك الكثير من التعليمات البرمجية ، والوصول إلى BIOS والوظائف التي كان من المستحيل تقريبًا فهمها (كانت مكتبة ، ولم يكن لديّ SDK لتجعيد الشعر بعد ذلك) ، تعليمات العمل مع ثلاث سجلات في نفس الوقت ، عدم وجود برنامج فك تشفير.


وهكذا ، بعد سنوات عديدة ، GHIDRA . من بين المنصات التي يدعمها decompiler هو MIPS . يا فرح! دعونا نحاول فك تشفير شيء قريبًا! ولكن ... كنت أنتظر المشكله. لا PS-X EXE دعم PS-X EXE بواسطة Hydra. لا مشكلة ، اكتب بنفسك!


في الواقع رمز


يكفي انحدار غنائي ، دعنا نكتب الكود. كيف أقوم بإنشاء برامج تنزيل خاصة بي لـ Ghidra ، كان لدي بالفعل فكرة عما كتبته سابقًا . لذلك ، يبقى فقط العثور على خريطة الذاكرة لأول مكواة تجعيد ، وعناوين السجلات ، ويمكنك جمع الثنائيات وتحميلها. لم يقل قال من القيام به.


كانت الشفرة جاهزة ، وتمت إضافة التسجيلات والمناطق والتعرف عليها ، ولكن لا يزال هناك بقعة فارغة كبيرة في الأماكن التي تم استدعاء وظائف المكتبة ووظائف BIOS فيها. ولسوء الحظ ، لم تحصل FLIRT دعم FLIRT . إذا لم يكن كذلك ، دعنا نضيف.


تنسيق تواقيع FLIRT معروف pat.txt ملف pat.txt ، والذي يمكن العثور عليه في Ida SDK. لدى Ida أيضًا أداة مساعدة لإنشاء هذه التواقيع على وجه التحديد من ملفات مكتبة Playstation ، وتسمى: ppsx . قمت بتنزيل SDK PsyQ Playstation Development Kit المسمى PsyQ Playstation Development Kit ، ووجدت ملفات lib هناك وحاولت إنشاء بعض التواقيع منها على الأقل - بنجاح. اتضح نصًا صغيرًا يحتوي كل سطر فيه على تنسيق محدد. يبقى أن تكتب الكود الذي سيحلل هذه السطور ويطبقها على الكود.



PatParser


نظرًا لأن كل سطر له تنسيق محدد ، سيكون من المنطقي كتابة تعبير عادي. اتضح مثل هذا:


 private static final Pattern linePat = Pattern.compile("^((?:[0-9A-F\\.]{2})+) ([0-9A-F]{2}) ([0-9A-F]{4}) ([0-9A-F]{4}) ((?:[:\\^][0-9A-F]{4}@? [\\.\\w]+ )+)((?:[0-9A-F\\.]{2})+)?$"); 

حسنًا ، لتسليط الضوء على قائمة الإزاحة والنوع واسم الوظيفة في قائمة الوحدات بشكل منفصل ، نكتب إعادة تسجيل منفصلة:


 private static final Pattern modulePat = Pattern.compile("([:\\^][0-9A-F]{4}@?) ([\\.\\w]+) "); 

الآن دعنا نذهب من خلال الاطلاع على مكونات كل توقيع على حدة:


  1. يأتي أولاً تسلسل ست عشري من وحدات البايت ( 0-9A-F ) ، حيث يمكن أن يكون بعضها (أي حرف نقطة "."). لذلك ، نقوم بإنشاء فصل يخزن مثل هذا التسلسل. دعوتها MaskedBytes :

MaskedBytes.java
 package pat; public class MaskedBytes { private final byte[] bytes, masks; public final byte[] getBytes() { return bytes; } public final byte[] getMasks() { return masks; } public final int getLength() { return bytes.length; } public MaskedBytes(byte[] bytes, byte[] masks) { this.bytes = bytes; this.masks = masks; } public static MaskedBytes extend(MaskedBytes src, MaskedBytes add) { return extend(src, add.getBytes(), add.getMasks()); } public static MaskedBytes extend(MaskedBytes src, byte[] addBytes, byte[] addMasks) { int length = src.getBytes().length; byte[] tmpBytes = new byte[length + addBytes.length]; byte[] tmpMasks = new byte[length + addMasks.length]; System.arraycopy(src.getBytes(), 0, tmpBytes, 0, length); System.arraycopy(addBytes, 0, tmpBytes, length, addBytes.length); System.arraycopy(src.getMasks(), 0, tmpMasks, 0, length); System.arraycopy(addMasks, 0, tmpMasks, length, addMasks.length); return new MaskedBytes(tmpBytes, tmpMasks); } } 

  1. طول الكتلة التي يتم حساب CRC16 منها.
  2. CRC16 ، الذي يستخدم 0x8408 الحدود الخاص به ( 0x8408 ):

كود العد CRC16
 public static boolean checkCrc16(byte[] bytes, short resCrc) { if ( bytes.length == 0 ) return true; int crc = 0xFFFF; for (int i = 0; i < bytes.length; ++i) { int a = bytes[i]; for (int x = 0; x < 8; ++x) { if (((crc ^ a) & 1) != 0) { crc = (crc >> 1) ^ 0x8408; } else { crc >>= 1; } a >>= 1; } } crc = ~crc; int x = crc; crc = (crc << 8) | ((x >> 8) & 0xFF); crc &= 0xFFFF; return (short)crc == resCrc; } 

  1. إجمالي طول "الوحدة النمطية" بالبايت.
  2. قائمة الأسماء العالمية (ما نحتاجه).
  3. قائمة الروابط إلى أسماء أخرى (هناك حاجة أيضا).
  4. ذيل بايت.

يحتوي كل اسم في الوحدة النمطية على نوع وإزاحة معينين بالنسبة إلى البداية. يمكن الإشارة إلى النوع بواسطة أحد الأحرف :: ، ^ ، @ ، اعتمادًا على النوع:


  • " : NAME ": اسم عالمي. من أجل هذه الأسماء بدأت كل شيء.
  • " : NAME @ ": الاسم / التسمية المحلية. قد لا تكون مبينة ، ولكن فليكن ؛
  • " ^ NAME ": رابط إلى الاسم.

من ناحية ، كل شيء بسيط ، ولكن يمكن بسهولة ألا يكون الرابط إشارة إلى دالة (وبالتالي ، ستكون القفزة نسبية) ، ولكن إلى متغير عمومي. ما المشكلة كما تقول؟ وهو أنه في PSX لا يمكنك دفع DWORD بالكامل إلى السجل بتعليمات واحدة. للقيام بذلك ، قم بتنزيله في شكل نصفي. الحقيقة ، في MIPS يقتصر حجم التعليمات على أربعة بايت. ويبدو أنك تحتاج فقط إلى الحصول على نصف من التعليمات ، ثم تفكيك التالي - والحصول على النصف الثاني. لكن ليس بهذه البساطة. يمكن تحميل النصف الأول من التعليمات 5 مرة أخرى ، وسيتم إعطاء الرابط في الوحدة النمطية فقط بعد تحميل النصف الثاني. اضطررت إلى كتابة محلل متطور (ربما يمكن تعديله).


نتيجة لذلك ، نقوم بإنشاء enum لثلاثة أنواع من الأسماء:


ModuleType.java
 package pat; public enum ModuleType { GLOBAL_NAME, LOCAL_NAME, REF_NAME; public boolean isGlobal() { return this == GLOBAL_NAME; } public boolean isLocal() { return this == LOCAL_NAME; } public boolean isReference() { return this == REF_NAME; } @Override public String toString() { if (isGlobal()) { return "Global"; } else if (isLocal()) { return "Local"; } else { return "Reference"; } } } 

MaskedBytes يحول تسلسل النص السداسي عشري ونقاط الكتابة إلى MaskedBytes :


hexStringToMaskedBytesArray ()
 private MaskedBytes hexStringToMaskedBytesArray(String s) { MaskedBytes res = null; if (s != null) { int len = s.length(); byte[] bytes = new byte[len / 2]; byte[] masks = new byte[len / 2]; for (int i = 0; i < len; i += 2) { char c1 = s.charAt(i); char c2 = s.charAt(i + 1); masks[i / 2] = (byte) ( (((c1 == '.') ? 0x0 : 0xF) << 4) | (((c2 == '.') ? 0x0 : 0xF) << 0) ); bytes[i / 2] = (byte) ( (((c1 == '.') ? 0x0 : Character.digit(c1, 16)) << 4) | (((c2 == '.') ? 0x0 : Character.digit(c2, 16)) << 0) ); } res = new MaskedBytes(bytes, masks); } return res; } 

يمكنك بالفعل التفكير في فصل يقوم بتخزين معلومات حول كل وظيفة فردية: اسم الوظيفة ، والإزاحة في الوحدة النمطية ، والنوع:


ModuleData.java
 package pat; public class ModuleData { private final long offset; private final String name; private final ModuleType type; public ModuleData(long offset, String name, ModuleType type) { this.offset = offset; this.name = name; this.type = type; } public final long getOffset() { return offset; } public final String getName() { return name; } public final ModuleType getType() { return type; } } 

وأخيرًا: فئة تخزن كل ما هو موضح في كل سطر من ملف pat ، وهي: بايت ، crc ، قائمة بالأسماء ذات الإزاحة:


SignatureData.java
 package pat; import java.util.Arrays; import java.util.List; public class SignatureData { private final MaskedBytes templateBytes, tailBytes; private MaskedBytes fullBytes; private final int crc16Length; private final short crc16; private final int moduleLength; private final List<ModuleData> modules; public SignatureData(MaskedBytes templateBytes, int crc16Length, short crc16, int moduleLength, List<ModuleData> modules, MaskedBytes tailBytes) { this.templateBytes = this.fullBytes = templateBytes; this.crc16Length = crc16Length; this.crc16 = crc16; this.moduleLength = moduleLength; this.modules = modules; this.tailBytes = tailBytes; if (this.tailBytes != null) { int addLength = moduleLength - templateBytes.getLength() - tailBytes.getLength(); byte[] addBytes = new byte[addLength]; byte[] addMasks = new byte[addLength]; Arrays.fill(addBytes, (byte)0x00); Arrays.fill(addMasks, (byte)0x00); this.fullBytes = MaskedBytes.extend(this.templateBytes, addBytes, addMasks); this.fullBytes = MaskedBytes.extend(this.fullBytes, tailBytes); } } public MaskedBytes getTemplateBytes() { return templateBytes; } public MaskedBytes getTailBytes() { return tailBytes; } public MaskedBytes getFullBytes() { return fullBytes; } public int getCrc16Length() { return crc16Length; } public short getCrc16() { return crc16; } public int getModuleLength() { return moduleLength; } public List<ModuleData> getModules() { return modules; } } 

الشيء الرئيسي الآن: نكتب الكود لإنشاء كل هذه الفئات:


تحليل سطر واحد من ملف بات
 private List<ModuleData> parseModuleData(String s) { List<ModuleData> res = new ArrayList<ModuleData>(); if (s != null) { Matcher m = modulePat.matcher(s); while (m.find()) { String __offset = m.group(1); ModuleType type = __offset.startsWith(":") ? ModuleType.GLOBAL_NAME : ModuleType.REF_NAME; type = (type == ModuleType.GLOBAL_NAME && __offset.endsWith("@")) ? ModuleType.LOCAL_NAME : type; String _offset = __offset.replaceAll("[:^@]", ""); long offset = Integer.parseInt(_offset, 16); String name = m.group(2); res.add(new ModuleData(offset, name, type)); } } return res; } 

تحليل جميع خطوط ملف بات
 private void parse(List<String> lines) { modulesCount = 0L; signatures = new ArrayList<SignatureData>(); int linesCount = lines.size(); monitor.initialize(linesCount); monitor.setMessage("Reading signatures..."); for (int i = 0; i < linesCount; ++i) { String line = lines.get(i); Matcher m = linePat.matcher(line); if (m.matches()) { MaskedBytes pp = hexStringToMaskedBytesArray(m.group(1)); int ll = Integer.parseInt(m.group(2), 16); short ssss = (short)Integer.parseInt(m.group(3), 16); int llll = Integer.parseInt(m.group(4), 16); List<ModuleData> modules = parseModuleData(m.group(5)); MaskedBytes tail = null; if (m.group(6) != null) { tail = hexStringToMaskedBytesArray(m.group(6)); } signatures.add(new SignatureData(pp, ll, ssss, llll, modules, tail)); modulesCount += modules.size(); } monitor.incrementProgress(1); } } 

رمز إنشاء الوظيفة حيث تم التعرف على أحد التواقيع:


إنشاء وظيفة
 private static void disasmInstruction(Program program, Address address) { DisassembleCommand cmd = new DisassembleCommand(address, null, true); cmd.applyTo(program, TaskMonitor.DUMMY); } public static void setFunction(Program program, FlatProgramAPI fpa, Address address, String name, boolean isFunction, boolean isEntryPoint, MessageLog log) { try { if (fpa.getInstructionAt(address) == null) disasmInstruction(program, address); if (isFunction) { fpa.createFunction(address, name); } if (isEntryPoint) { fpa.addEntryPoint(address); } if (isFunction && program.getSymbolTable().hasSymbol(address)) { return; } program.getSymbolTable().createLabel(address, name, SourceType.IMPORTED); } catch (InvalidInputException e) { log.appendException(e); } } 

أصعب مكان ، كما ذكرنا سابقًا ، هو حساب رابط إلى اسم / متغير آخر (ربما يلزم تحسين الشفرة):


عدد الروابط
 public static void setInstrRefName(Program program, FlatProgramAPI fpa, PseudoDisassembler ps, Address address, String name, MessageLog log) { ReferenceManager refsMgr = program.getReferenceManager(); Reference[] refs = refsMgr.getReferencesFrom(address); if (refs.length == 0) { disasmInstruction(program, address); refs = refsMgr.getReferencesFrom(address); if (refs.length == 0) { refs = refsMgr.getReferencesFrom(address.add(4)); if (refs.length == 0) { refs = refsMgr.getFlowReferencesFrom(address.add(4)); Instruction instr = program.getListing().getInstructionAt(address.add(4)); if (instr == null) { disasmInstruction(program, address.add(4)); instr = program.getListing().getInstructionAt(address.add(4)); if (instr == null) { return; } } FlowType flowType = instr.getFlowType(); if (refs.length == 0 && !(flowType.isJump() || flowType.isCall() || flowType.isTerminal())) { return; } refs = refsMgr.getReferencesFrom(address.add(8)); if (refs.length == 0) { return; } } } } try { program.getSymbolTable().createLabel(refs[0].getToAddress(), name, SourceType.IMPORTED); } catch (InvalidInputException e) { log.appendException(e); } } 

واللمس النهائي - تطبيق التوقيعات:


طبق التوقيعات ()
 public void applySignatures(ByteProvider provider, Program program, Address imageBase, Address startAddr, Address endAddr, MessageLog log) throws IOException { BinaryReader reader = new BinaryReader(provider, false); PseudoDisassembler ps = new PseudoDisassembler(program); FlatProgramAPI fpa = new FlatProgramAPI(program); monitor.initialize(getAllModulesCount()); monitor.setMessage("Applying signatures..."); for (SignatureData sig : signatures) { MaskedBytes fullBytes = sig.getFullBytes(); MaskedBytes tmpl = sig.getTemplateBytes(); Address addr = program.getMemory().findBytes(startAddr, endAddr, fullBytes.getBytes(), fullBytes.getMasks(), true, TaskMonitor.DUMMY); if (addr == null) { monitor.incrementProgress(sig.getModules().size()); continue; } addr = addr.subtract(imageBase.getOffset()); byte[] nextBytes = reader.readByteArray(addr.getOffset() + tmpl.getLength(), sig.getCrc16Length()); if (!PatParser.checkCrc16(nextBytes, sig.getCrc16())) { monitor.incrementProgress(sig.getModules().size()); continue; } addr = addr.add(imageBase.getOffset()); List<ModuleData> modules = sig.getModules(); for (ModuleData data : modules) { Address _addr = addr.add(data.getOffset()); if (data.getType().isGlobal()) { setFunction(program, fpa, _addr, data.getName(), data.getType().isGlobal(), false, log); } monitor.setMessage(String.format("%s function %s at 0x%08X", data.getType(), data.getName(), _addr.getOffset())); monitor.incrementProgress(1); } for (ModuleData data : modules) { Address _addr = addr.add(data.getOffset()); if (data.getType().isReference()) { setInstrRefName(program, fpa, ps, _addr, data.getName(), log); } monitor.setMessage(String.format("%s function %s at 0x%08X", data.getType(), data.getName(), _addr.getOffset())); monitor.incrementProgress(1); } } } 

هنا يمكنك التحدث عن وظيفة واحدة مثيرة للاهتمام: findBytes() . باستخدامه ، يمكنك البحث عن تسلسلات محددة من البايتات ، مع أقنعة البت المحددة لكل بايت. تسمى الطريقة مثل هذا:


 Address addr = program.getMemory().findBytes(startAddr, endAddr, bytes, masks, forward, TaskMonitor.DUMMY); 

نتيجة لذلك ، يتم إرجاع العنوان الذي تبدأ منه وحدات البايت أو null .


كتابة محلل


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



لذلك ، لدخول هذه القائمة ، ستحتاج إلى أن ترث من فئة AbstractAnalyzer وتجاوز بعض الأساليب:


  1. مصمم. سيتعين عليها الاتصال بمنشئ الفئة الأساسية بالاسم ووصف المحلل ونوعه (المزيد حول ذلك لاحقًا). يبدو لي مثل هذا:

 public PsxAnalyzer() { super("PSYQ Signatures", "PSX signatures applier", AnalyzerType.INSTRUCTION_ANALYZER); } 

  1. getDefaultEnablement() . يحدد ما إذا كان محللنا متاحًا دائمًا ، أو فقط إذا تم استيفاء شروط معينة (على سبيل المثال ، في حالة استخدام أداة التحميل الخاصة بنا).
  2. canAnalyze() . هل من الممكن استخدام هذا المحلل على الإطلاق في ملف ثنائي قابل للتنزيل؟
    يمكن التحقق من الفقرتين 2 و 3 ، من حيث المبدأ ، من خلال وظيفة واحدة:

 public static boolean isPsxLoader(Program program) { return program.getExecutableFormat().equalsIgnoreCase(PsxLoader.PSX_LOADER); } 

حيث يقوم PsxLoader.PSX_LOADER بتخزين اسم محمل الإقلاع ، ويتم تعريفه مسبقًا.


المجموع ، لدينا:


 @Override public boolean getDefaultEnablement(Program program) { return isPsxLoader(program); } @Override public boolean canAnalyze(Program program) { return isPsxLoader(program); } 

  1. registerOptions() . ليس من الضروري على الإطلاق إعادة تعريف هذه الطريقة ، ولكن إذا كنا بحاجة إلى أن نسأل المستخدم شيئًا ، على سبيل المثال ، المسار إلى ملف pat قبل التحليل ، فمن الأفضل القيام بذلك في هذه الطريقة. نحصل على:

 private static final String OPTION_NAME = "PSYQ PAT-File Path"; private File file = null; @Override public void registerOptions(Options options, Program program) { try { file = Application.getModuleDataFile("psyq4_7.pat").getFile(false); } catch (FileNotFoundException e) { } options.registerOption(OPTION_NAME, OptionType.FILE_TYPE, file, null, "PAT-File (FLAIR) created from PSYQ library files"); } 

هنا لا بد من توضيح. الأسلوب الثابت getModuleDataFile() لفئة Application بإرجاع المسار الكامل للملف في دليل data ، الموجود في شجرة الوحدة النمطية لدينا ، ويمكن تخزين أي ملفات ضرورية نريد الإشارة إليها لاحقًا.


حسنًا ، registerOption() طريقة registerOption() خيارًا بالاسم المحدد في OPTION_NAME ، ونوع File (أي ، سيتمكن المستخدم من تحديد الملف من خلال مربع حوار عادي) ، والقيمة الافتراضية والوصف.


التالي. لأن ليس لدينا فرصة طبيعية للإشارة إلى الخيار المسجل لاحقًا ، وسنحتاج إلى إعادة تحديد optionsChanged() :


 @Override public void optionsChanged(Options options, Program program) { super.optionsChanged(options, program); file = options.getFile(OPTION_NAME, file); } 

هنا نقوم ببساطة بتحديث المتغير العام وفقًا للقيمة الجديدة.


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


الطريقة المضافة ()
 @Override public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) throws CancelledException { if (file == null) { return true; } Memory memory = program.getMemory(); AddressRangeIterator it = memory.getLoadedAndInitializedAddressSet().getAddressRanges(); while (!monitor.isCancelled() && it.hasNext()) { AddressRange range = it.next(); try { MemoryBlock block = program.getMemory().getBlock(range.getMinAddress()); if (block.isInitialized() && block.isExecute() && block.isLoaded()) { PatParser pat = new PatParser(file, monitor); RandomAccessByteProvider provider = new RandomAccessByteProvider(new File(program.getExecutablePath())); pat.applySignatures(provider, program, block.getStart(), block.getStart(), block.getEnd(), log); } } catch (IOException e) { log.appendException(e); return false; } } return true; } 

هنا نذهب إلى قائمة العناوين القابلة للتنفيذ ، ونحاول تطبيق التوقيعات هناك.



الاستنتاجات والنهاية


مثل كل شيء. في الواقع ، لا يوجد شيء معقد للغاية هنا. هناك أمثلة ، المجتمع حيوي ، يمكنك أن تسأل بأمان عما هو غير واضح أثناء كتابة الكود. خلاصة القول: محمل الإقلاع ومحلل الملفات القابلة للتنفيذ في Playstation 1 .



جميع أكواد المصدر متاحة هنا: ghidra_psx_ldr
الإصدارات هنا: الإصدارات

Source: https://habr.com/ru/post/ar448098/


All Articles