From 2fc7ffc474d4e93cbabd1be1a431a96326d7c584 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Tue, 21 Jan 2025 12:59:34 -0500 Subject: [PATCH] GP-5291: Adding getter methods for fields in DyldCacheHeader. Parsing new "tpro mapping" fields. --- .../format/macho/dyld/DyldCacheHeader.java | 652 +++++++++++++++--- .../util/opinion/DyldCacheProgramBuilder.java | 12 +- .../app/util/opinion/DyldCacheUtils.java | 7 +- .../ios/dyldcache/DyldCacheAnalyzer.java | 11 +- 4 files changed, 595 insertions(+), 87 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheHeader.java index 41812a6a49..bf14834a37 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/dyld/DyldCacheHeader.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,7 +27,6 @@ import ghidra.program.model.data.*; import ghidra.program.model.listing.*; import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.util.CodeUnitInsertionException; -import ghidra.util.NumericUtilities; import ghidra.util.exception.CancelledException; import ghidra.util.exception.DuplicateNameException; import ghidra.util.task.TaskMonitor; @@ -37,7 +36,6 @@ import ghidra.util.task.TaskMonitor; * * @see dyld_cache_format.h */ -@SuppressWarnings("unused") public class DyldCacheHeader implements StructConverter { private byte[] magic; @@ -69,13 +67,12 @@ public class DyldCacheHeader implements StructConverter { private long progClosuresTrieAddr; private long progClosuresTrieSize; private int platform; - private int dyld_info; + private int dyldInfo; private int formatVersion; private boolean dylibsExpectedOnDisk; private boolean simulator; private boolean locallyBuiltCache; private boolean builtFromChainedFixups; - private int padding; private long sharedRegionStart; private long sharedRegionSize; private long maxSlide; @@ -116,6 +113,8 @@ public class DyldCacheHeader implements StructConverter { private long cacheAtlasSize; private long dynamicDataOffset; private long dynamicDataMaxSize; + private int tproMappingsOffset; + private int tproMappingsCount; private int headerSize; private BinaryReader reader; @@ -132,6 +131,9 @@ public class DyldCacheHeader implements StructConverter { private List cacheMappingAndSlideInfoList; private MemoryBlock fileBlock; + @SuppressWarnings("unused") + private int padding; + /** * Create a new {@link DyldCacheHeader}. * @@ -212,13 +214,13 @@ public class DyldCacheHeader implements StructConverter { platform = reader.readNextInt(); } if (reader.getPointerIndex() < mappingOffset) { - dyld_info = reader.readNextInt(); - formatVersion = dyld_info & 0xff; - dylibsExpectedOnDisk = (dyld_info >>> 8 & 1) == 1; - simulator = (dyld_info >>> 9 & 1) == 1; - locallyBuiltCache = (dyld_info >> 10 & 1) == 1; - builtFromChainedFixups = (dyld_info >> 11 & 1) == 1; - padding = (dyld_info >> 12) & 0xfffff; + dyldInfo = reader.readNextInt(); + formatVersion = dyldInfo & 0xff; + dylibsExpectedOnDisk = (dyldInfo >>> 8 & 1) == 1; + simulator = (dyldInfo >>> 9 & 1) == 1; + locallyBuiltCache = (dyldInfo >> 10 & 1) == 1; + builtFromChainedFixups = (dyldInfo >> 11 & 1) == 1; + padding = (dyldInfo >> 12) & 0xfffff; } if (reader.getPointerIndex() < mappingOffset) { sharedRegionStart = reader.readNextLong(); @@ -350,6 +352,12 @@ public class DyldCacheHeader implements StructConverter { if (reader.getPointerIndex() < mappingOffset) { dynamicDataMaxSize = reader.readNextLong(); } + if (reader.getPointerIndex() < mappingOffset) { + tproMappingsOffset = reader.readNextInt(); + } + if (reader.getPointerIndex() < mappingOffset) { + tproMappingsCount = reader.readNextInt(); + } headerSize = (int) (reader.getPointerIndex() - startIndex); @@ -385,7 +393,7 @@ public class DyldCacheHeader implements StructConverter { parseSlideInfos(log, monitor); } - private void parseSlideInfos(MessageLog log, TaskMonitor monitor) throws CancelledException { + private void parseSlideInfos(MessageLog log, TaskMonitor monitor) { if (!hasSlideInfo()) { return; } @@ -476,43 +484,563 @@ public class DyldCacheHeader implements StructConverter { } /** - * Gets the base address of the DYLD cache. This is where the cache should be loaded in - * memory. - * - * @return The base address of the DYLD cache - */ - public long getBaseAddress() { - return baseAddress; - } - - /** - * Gets the magic bytes, which contain version information. - * - * @return The magic bytes + * {@return the magic bytes, which contain version information} */ public byte[] getMagic() { return magic; } /** - * Gets the UUID in {@link String} form - * - * @return The UUID in {@link String} form, or null if a UUID is not defined + * {@return the mapping offset} */ - public String getUUID() { - return NumericUtilities.convertBytesToString(uuid); + public int getMappingOffset() { + return mappingOffset; } /** - * Gets the DYLD entry point address (if known) - * - * @return The DYLD entry point address, or null if it is not known + * {@return the mapping count} */ - public Long getEntryPoint() { - if (!hasAccelerateInfo()) { - return accelerateInfoSize_dyldInCacheEntry; - } - return null; + public int getMappingCount() { + return mappingCount; + } + + /** + * {@return the old images offset} + */ + public int getImagesOffsetOld() { + return imagesOffsetOld; + } + + /** + * {@return the old images count} + */ + public int getImagesCountOld() { + return imagesCountOld; + } + + /** + * {@return the dyld base address} + */ + public long getDyldBaseAddress() { + return dyldBaseAddress; + } + + /** + * {@return the code signature offset} + */ + public long getCodeSignatureOffset() { + return codeSignatureOffset; + } + + /** + * {@return the code signature size} + */ + public long getCodeSignatureSize() { + return codeSignatureSize; + } + + /** + * {@return the slide info offset} + */ + public long getSlideInfoOffset() { + return slideInfoOffset; + } + + /** + * {@return the slide info size} + */ + public long getSlideInfoSize() { + return slideInfoSize; + } + + /** + * {@return the local symbols offset} + */ + public long getLocalSymbolsOffset() { + return localSymbolsOffset; + } + + /** + * {@return the local symbols size} + */ + public long getLocalSymbolsSize() { + return localSymbolsSize; + } + + /** + * {@return the UUID, or {@code null} if it is not defined} + */ + public byte[] getUUID() { + return uuid; + } + + /** + * {@return the cache type} + */ + public long getCacheType() { + return cacheType; + } + + /** + * {@return the branch pools offset} + */ + public int getBranchPoolsOffset() { + return branchPoolsOffset; + } + + /** + * {@return the branch pools count} + */ + public int getBranchPoolsCount() { + return branchPoolsCount; + } + + /** + * {@return the old accelerate info address or new address of mach header in dyld cache, or + * {@code null} if it is not defined} + */ + public long getAccelerateInfoAddrOrDyldInCacheMH() { + return accelerateInfoAddr_dyldInCacheMH; + } + + /** + * {@return the old accelerate info size or new address of entry point in dyld cache, or + * {@code null} if it is not defined} + */ + public long getAccelerateInfoSizeOrDyldInCacheEntry() { + return accelerateInfoSize_dyldInCacheEntry; + } + + /** + * {@return the images text offset} + */ + public long getImagesTextOffset() { + return imagesTextOffset; + } + + /** + * {@return the images text count} + */ + public long getImagesTextCount() { + return imagesTextCount; + } + + /** + * {@return the patch info address} + */ + public long getPatchInfoAddr() { + return patchInfoAddr; + } + + /** + * {@return the patch info size} + */ + public long getPatchInfoSize() { + return patchInfoSize; + } + + /** + * {@return the other image group address (unused)} + */ + public long getOtherImageGroupAddrUnused() { + return otherImageGroupAddrUnused; + } + + /** + * {@return the other image group size (unused)} + */ + public long getOtherImageGroupSizeUnused() { + return otherImageGroupSizeUnused; + } + + /** + * {@return the program launch closures address} + */ + public long getProgClosuresAddr() { + return progClosuresAddr; + } + + /** + * {@return the program launch closures size} + */ + public long getProgClosuresSize() { + return progClosuresSize; + } + + /** + * {@return the program launch closures trie address} + */ + public long getProgClosuresTrieAddr() { + return progClosuresTrieAddr; + } + + /** + * {@return the program launch closures trie size} + */ + public long getProgClosuresTrieSize() { + return progClosuresTrieSize; + } + + /** + * {@return the platform} + */ + public int getPlatform() { + return platform; + } + + /** + * {@return the dyld info} + */ + public int getDyldInfo() { + return dyldInfo; + } + + /** + * {@return the format version} + */ + public int getFormatVersion() { + return formatVersion; + } + + /** + * {@return the dylibs expected on disk value} + */ + public boolean getDylibsExpectedOnDisk() { + return dylibsExpectedOnDisk; + } + + /** + * {@return the simulator value} + */ + public boolean getSimulator() { + return simulator; + } + + /** + * {@return the locally built cache value} + */ + public boolean getLocallyBuildCache() { + return locallyBuiltCache; + } + + /** + * {@return the built from chained fixups value} + */ + public boolean getBuiltFromChainedFixups() { + return builtFromChainedFixups; + } + + /** + * {@return the shared region start} + */ + public long getSharedRegionStart() { + return sharedRegionStart; + } + + /** + * {@return the shared region size} + */ + public long getSharedRegionSize() { + return sharedRegionSize; + } + + /** + * {@return the max slide} + */ + public long getMaxSlide() { + return maxSlide; + } + + /** + * {@return the dylibs image array address} + */ + public long getDylibsImageArrayAddr() { + return dylibsImageArrayAddr; + } + + /** + * {@return the dylibs image array size} + */ + public long getDylibsImageArraySize() { + return dylibsImageArraySize; + } + + /** + * {@return the dylibs trie address} + */ + public long getDylibsTriAddr() { + return dylibsTrieAddr; + } + + /** + * {@return the dylibs trie size} + */ + public long getDylibsTrieSize() { + return dylibsTrieSize; + } + + /** + * {@return the other image array address} + */ + public long getOtherImageArrayAddr() { + return otherImageArrayAddr; + } + + /** + * {@return the other image array size} + */ + public long getOtherImageArraySize() { + return otherImageArraySize; + } + + /** + * {@return the other trie address} + */ + public long getOtherTriAddr() { + return otherTrieAddr; + } + + /** + * {@return the other trie size} + */ + public long getOtherTrieSize() { + return otherTrieSize; + } + + /** + * {@return the mapping with slide offset} + */ + public int getMappingWithSlideOffset() { + return mappingWithSlideOffset; + } + + /** + * {@return the mapping with slide count} + */ + public int getMappingWithSlideCount() { + return mappingWithSlideCount; + } + + /** + * {@return the dylibs PrebuildLoaderSet state array address (unused), or {@code null} if it is + * not defined} + */ + public long getDylibsPBLStateArrayAddrUnused() { + return dylibsPBLStateArrayAddrUnused; + } + + /** + * {@return the dylibs PrebuildLoaderSet set address} + */ + public long getDylibsPBLSetAddr() { + return dylibsPBLSetAddr; + } + + /** + * {@return the programs PrebuildLoaderSet set pool address, or {@code null} if it is not + * defined} + */ + public long getProgramsPBLSetPoolAddr() { + return programsPBLSetPoolAddr; + } + + /** + * {@return the programs PrebuildLoaderSet set pool size} + */ + public long getProgramsPBLSetPoolSize() { + return programsPBLSetPoolSize; + } + + /** + * {@return the program trie address} + */ + public long getProgramTrieAddr() { + return programTrieAddr; + } + + /** + * {@return the program trie size} + */ + public int getProgramTrieSize() { + return programTrieSize; + } + + /** + * {@return the OS version} + */ + public int getOsVersion() { + return osVersion; + } + + /** + * {@return the alt platform} + */ + public int getAltPlatform() { + return altPlatform; + } + + /** + * {@return the alt OS version} + */ + public int getAltOsVersion() { + return altOsVersion; + } + + /** + * {@return the swift opts offset} + */ + public long getSwiftOptsOffset() { + return swiftOptsOffset; + } + + /** + * {@return the swift opts size} + */ + public long getSwiftOptsSize() { + return swiftOptsSize; + } + + /** + * {@return the subcache array offset} + */ + public int getSubCacheArrayOffset() { + return subCacheArrayOffset; + } + + /** + * {@return the subcache array count, or {@code null} if it is not defined} + */ + public Integer getSubCacheArrayCount() { + return subCacheArrayCount; + } + + /** + * {@return the symbol file UUID, or {@code null} if it is not defined} + */ + public byte[] getSymbolFileUUID() { + return symbolFileUUID; + } + + /** + * {@return the rosetta read-only address} + */ + public long getRosettaReadOnlyAddr() { + return rosettaReadOnlyAddr; + } + + /** + * {@return the rosetta read-only size} + */ + public long getRosettaReadOnlySize() { + return rosettaReadOnlySize; + } + + /** + * {@return the rosetta read-write address} + */ + public long getRosettaReadWriteAddr() { + return rosettaReadWriteAddr; + } + + /** + * {@return the rosetta read-write size} + */ + public long getRosettaReadWriteSize() { + return rosettaReadWriteSize; + } + + /** + * {@return the images offset} + */ + public int getImagesOffset() { + return imagesOffset; + } + + /** + * {@return the images count} + */ + public int getImagesCount() { + return imagesCount; + } + + /** + * {@return the cache subtype, or {@code null} if it is not defined} + */ + public Integer getCacheSubType() { + return cacheSubType; + } + + /** + * {@return the ObjC opts offset} + */ + public long getObjcOptsOffset() { + return objcOptsOffset; + } + + /** + * {@return the ObjC opts size} + */ + public long getObjcOptsSize() { + return objcOptsSize; + } + + /** + * {@return the cache atlas offset} + */ + public long getCacheAtlasOffset() { + return cacheAtlasOffset; + } + + /** + * {@return the cache atlas size} + */ + public long getCacheAtlasSize() { + return cacheAtlasSize; + } + + /** + * {@return the dynamic data offset} + */ + public long getDynamicDataOffset() { + return dynamicDataOffset; + } + + /** + * {@return the dynamic data max size} + */ + public long getDynamicDataMaxSize() { + return dynamicDataMaxSize; + } + + /** + * {@return the tpro mappings offset} + */ + public int getTproMappingsOffset() { + return tproMappingsOffset; + } + + /** + * {@return the tpro mappings count} + */ + public int getTproMappingsCount() { + return tproMappingsCount; + } + + /** + * {@return the reader associated with the header} + * + */ + public BinaryReader getReader() { + return reader; + } + + /** + * {@return the base address of the DYLD cache} + *

+ * This is where the cache should be loaded in memory. + */ + public long getBaseAddress() { + return baseAddress; } /** @@ -524,30 +1052,6 @@ public class DyldCacheHeader implements StructConverter { return mappingInfoList; } - /** - * Gets the file offset to first {@link DyldCacheImageInfo}. - * - * @return The file offset to first {@link DyldCacheImageInfo} - */ - public int getImagesOffset() { - if (imagesOffset != 0) { - return imagesOffset; - } - return imagesOffsetOld; - } - - /** - * Gets the number of {@link DyldCacheImageInfo}s. - * - * @return The number of {@link DyldCacheImageInfo}s - */ - public int getImagesCount() { - if (imagesOffset != 0) { - return imagesCount; - } - return imagesCountOld; - } - /** * Generates a {@link List} of {@link DyldCacheImage}s that are mapped in by this * {@link DyldCacheHeader}. Requires header to have been parsed. @@ -581,16 +1085,6 @@ public class DyldCacheHeader implements StructConverter { return subcacheEntryList; } - /** - * Gets the symbol file UUID in {@link String} form - * - * @return The symbol file UUID in {@link String} form, or null if a symbol file UUID is not - * defined or is all zeros - */ - public String getSymbolFileUUID() { - return NumericUtilities.convertBytesToString(symbolFileUUID); - } - /** * Gets the {@link List} of {@link DyldCacheMappingAndSlideInfo}s. Requires header to have been parsed. * @@ -718,6 +1212,8 @@ public class DyldCacheHeader implements StructConverter { addHeaderField(struct, QWORD, "cacheAtlasSize", "size of embedded cache atlas"); addHeaderField(struct, QWORD, "dynamicDataOffset", "VM offset from cache_header* to the location of dyld_cache_dynamic_data_header"); addHeaderField(struct, QWORD, "dynamicDataMaxSize", "maximum size of space reserved from dynamic data"); + addHeaderField(struct, DWORD, "tproMappingsOffset", "file offset to first dyld_cache_tpro_mapping_info"); + addHeaderField(struct, DWORD, "tproMappingsCount", "number of dyld_cache_tpro_mapping_info entries"); // @formatter:on struct.setCategoryPath(new CategoryPath(MachConstants.DATA_TYPE_CATEGORY)); @@ -871,7 +1367,7 @@ public class DyldCacheHeader implements StructConverter { } private void markupHeader(Program program, AddressSpace space, TaskMonitor monitor, - MessageLog log) throws CancelledException { + MessageLog log) { monitor.setMessage("Marking up DYLD header..."); monitor.initialize(1); try { @@ -948,7 +1444,7 @@ public class DyldCacheHeader implements StructConverter { } private void markupCodeSignature(Program program, AddressSpace space, TaskMonitor monitor, - MessageLog log) throws CancelledException { + MessageLog log) { monitor.setMessage("Marking up DYLD code signature..."); monitor.initialize(1); try { @@ -965,7 +1461,7 @@ public class DyldCacheHeader implements StructConverter { } private void markupSlideInfo(Program program, AddressSpace space, TaskMonitor monitor, - MessageLog log) throws CancelledException { + MessageLog log) { monitor.setMessage("Marking up DYLD slide info..."); monitor.initialize(1); try { @@ -1012,7 +1508,7 @@ public class DyldCacheHeader implements StructConverter { monitor.initialize(branchPoolList.size()); try { Address addr = fileOffsetToAddr(branchPoolsOffset, program, space); - for (Long element : branchPoolList) { + for (int i = 0; i < branchPoolList.size(); i++) { Data d = DataUtilities.createData(program, addr, Pointer64DataType.dataType, Pointer64DataType.dataType.getLength(), DataUtilities.ClearDataMode.CHECK_FOR_SPACE); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheProgramBuilder.java index e112b8440f..fa3e894d96 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheProgramBuilder.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -35,6 +35,7 @@ import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.symbol.SourceType; import ghidra.program.model.symbol.SymbolUtilities; +import ghidra.util.NumericUtilities; import ghidra.util.exception.CancelledException; import ghidra.util.exception.DuplicateNameException; import ghidra.util.task.TaskMonitor; @@ -143,7 +144,9 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder { */ private void setDyldCacheEntryPoint(SplitDyldCache splitDyldCache) throws Exception { monitor.initialize(1, "Setting entry pointer base..."); - Long entryPoint = splitDyldCache.getDyldCacheHeader(0).getEntryPoint(); + Long entryPoint = !splitDyldCache.getDyldCacheHeader(0).hasAccelerateInfo() + ? splitDyldCache.getDyldCacheHeader(0).getAccelerateInfoSizeOrDyldInCacheEntry() + : null; if (entryPoint != null) { Address entryPointAddr = space.getAddress(entryPoint); program.getSymbolTable().addExternalEntryPoint(entryPointAddr); @@ -186,7 +189,8 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder { if (!bookmarkSet) { program.getBookmarkManager() .setBookmark(block.getStart(), BookmarkType.INFO, "Dyld Cache Header", - name + " - " + dyldCacheHeader.getUUID()); + name + " - " + + NumericUtilities.convertBytesToString(dyldCacheHeader.getUUID())); bookmarkSet = true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheUtils.java index 2410bf2879..c6909cd968 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheUtils.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheUtils.java @@ -27,6 +27,7 @@ import ghidra.formats.gfilesystem.*; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryAccessException; +import ghidra.util.NumericUtilities; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -149,7 +150,8 @@ public class DyldCacheUtils { splitHeader.parseFromFile(shouldProcessLocalSymbols, log, monitor); headers.add(splitHeader); names.add(splitFSRL.getName()); - uuidToFileMap.put(splitHeader.getUUID(), splitFSRL); + uuidToFileMap.put(NumericUtilities.convertBytesToString(splitHeader.getUUID()), + splitFSRL); } // Validate the subcaches @@ -164,7 +166,8 @@ public class DyldCacheUtils { } log.appendMsg("Including subcache: " + fsrl.getName() + " - " + uuid); } - String symbolUUID = baseHeader.getSymbolFileUUID(); + String symbolUUID = + NumericUtilities.convertBytesToString(baseHeader.getSymbolFileUUID()); if (symbolUUID != null) { FSRL symbolFSRL = uuidToFileMap.get(symbolUUID); if (symbolFSRL == null) { diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheAnalyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheAnalyzer.java index 9b49ce0a66..5a4c26d668 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheAnalyzer.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/dyldcache/DyldCacheAnalyzer.java @@ -55,10 +55,15 @@ public class DyldCacheAnalyzer extends FileFormatAnalyzer { createFragment(program, headerDataType.getName(), headerData.getMinAddress(), headerData.getMaxAddress().add(1)); - reader.setPointerIndex(header.getImagesOffset()); - Address address = toAddr(program, header.getImagesOffset()); + int imagesOffset = header.getImagesOffset() != 0 ? header.getImagesOffset() + : header.getImagesOffsetOld(); + int imagesCount = header.getImagesOffset() != 0 ? header.getImagesCount() + : header.getImagesCountOld(); - for (int i = 0; i < header.getImagesCount(); ++i) { + reader.setPointerIndex(imagesOffset); + Address address = toAddr(program, imagesOffset); + + for (int i = 0; i < imagesCount; ++i) { if (monitor.isCancelled()) { break;