diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusFileSystem.java new file mode 100644 index 0000000000..d17b44147f --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusFileSystem.java @@ -0,0 +1,28 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.ios.hfs; + +import ghidra.file.formats.sevenzip.SevenZipFileSystem; +import ghidra.formats.gfilesystem.FSRLRoot; +import ghidra.formats.gfilesystem.FileSystemService; +import ghidra.formats.gfilesystem.annotations.FileSystemInfo; + +@FileSystemInfo(type = "hfsplus", description = "Apple HFS+ Disk Volume", factory = HFSPlusFileSystemFactory.class) +public class HFSPlusFileSystem extends SevenZipFileSystem { + public HFSPlusFileSystem(FSRLRoot fsrl, FileSystemService fsService) { + super(fsrl, fsService); + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusFileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusFileSystemFactory.java new file mode 100644 index 0000000000..5decfd182d --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusFileSystemFactory.java @@ -0,0 +1,53 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.ios.hfs; + +import java.io.IOException; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.formats.gfilesystem.FSRLRoot; +import ghidra.formats.gfilesystem.FileSystemService; +import ghidra.formats.gfilesystem.factory.GFileSystemFactoryByteProvider; +import ghidra.formats.gfilesystem.factory.GFileSystemProbeByteProvider; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class HFSPlusFileSystemFactory + implements GFileSystemFactoryByteProvider, GFileSystemProbeByteProvider { + + @Override + public boolean probe(ByteProvider byteProvider, FileSystemService fsService, + TaskMonitor monitor) throws IOException, CancelledException { + return HFSPlusVolumeHeader.probe(byteProvider); + } + + @Override + public HFSPlusFileSystem create(FSRLRoot targetFSRL, ByteProvider byteProvider, + FileSystemService fsService, TaskMonitor monitor) + throws IOException, CancelledException { + + HFSPlusFileSystem fs = new HFSPlusFileSystem(targetFSRL, fsService); + try { + fs.mount(byteProvider, monitor); + return fs; + } + catch (IOException ioe) { + fs.close(); + throw ioe; + } + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusVolumeHeader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusVolumeHeader.java new file mode 100644 index 0000000000..3acad38e4e --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/ios/hfs/HFSPlusVolumeHeader.java @@ -0,0 +1,145 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.ios.hfs; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.ByteProvider; + +/** + * Apple HFS+ volume header. + *

+ * See https://developer.apple.com/library/archive/technotes/tn/tn1150.html#VolumeHeader + *

+ * Fields are BigEndian + */ +public class HFSPlusVolumeHeader { + //@formatter:off + // Offset (hex) Length Comment + private short signature; // 0 2 48 2b, "H+" + private short version; // 2 2 4=HFS+, 5=HFSX + private int attributes; // 4 4 + private int lastMountedVersion; // 8 4 '10.0' string + private int journalInfoBlock; // C 4 + + private int createDate; // 10 4 + private int modifyDate; // 14 4 + private int backupDate; // 18 4 + private int checkedDate; // 1C 4 + + private int fileCount; // 20 4 + private int folderCount; // 24 4 + + private int blockSize; // 28 4 0x1000=4096 + private int totalBlocks; // 2C 4 blockSize*totalBlocks should equal vol size + private int freeBlocks; // 30 4 + + private int nextAllocation; // 34 4 + private int rsrcClumpSize; // 38 4 + private int dataClumpSize; // 3C 4 + private int nextCatalogID; // 40 4 + + private int writeCount; // 44 4 + private long encodingsBitmap; // 48 8 + + private int[] finderInfo; // 50 32 uint32[8] + + private byte[] rawForkData; // 70 400 + //HFSPlusForkData allocationFile; // 70 80 + //HFSPlusForkData extentsFile; // C0 80 + //HFSPlusForkData catalogFile; // 110 80 + //HFSPlusForkData attributesFile; // 160 80 + //HFSPlusForkData startupFile; // 1B0 80 + //@formatter:on + + private static final int HFSPLUS_SIGNATURE_MAGIC = 0x482b; // "H+" + private static final int HFSX_SIGNATURE_MAGIC = 0x4858; // "HX" + private static final int HFSPLUS_VERSION = 4; + private static final int HFSX_VERSION = 5; + private static final int SIZEOF_HEADER = 512; + private static final int DEFAULT_OFFSET = 1024; + + public static boolean probe(ByteProvider provider) { + try { + if (provider.length() < DEFAULT_OFFSET + SIZEOF_HEADER) { + return false; + } + HFSPlusVolumeHeader header = read(provider); + return header.isValid() && header.hasGoodVolumeInfo(provider); + } + catch (IOException e) { + return false; + } + } + + public static HFSPlusVolumeHeader read(ByteProvider provider) throws IOException { + return read(provider, DEFAULT_OFFSET); + } + + public static HFSPlusVolumeHeader read(ByteProvider provider, long offset) throws IOException { + BinaryReader reader = new BinaryReader(provider, false /*BE*/); + reader.setPointerIndex(offset); + HFSPlusVolumeHeader result = new HFSPlusVolumeHeader(); + result.signature = reader.readNextShort(); + result.version = reader.readNextShort(); + result.attributes = reader.readNextInt(); + result.lastMountedVersion = reader.readNextInt(); + result.journalInfoBlock = reader.readNextInt(); + + result.createDate = reader.readNextInt(); + result.modifyDate = reader.readNextInt(); + result.backupDate = reader.readNextInt(); + result.checkedDate = reader.readNextInt(); + + result.fileCount = reader.readNextInt(); + result.folderCount = reader.readNextInt(); + + result.blockSize = reader.readNextInt(); + result.totalBlocks = reader.readNextInt(); + result.freeBlocks = reader.readNextInt(); + + result.nextAllocation = reader.readNextInt(); + result.rsrcClumpSize = reader.readNextInt(); + result.dataClumpSize = reader.readNextInt(); + result.nextCatalogID = reader.readNextInt(); + + result.writeCount = reader.readNextInt(); + result.encodingsBitmap = reader.readNextLong(); + + result.finderInfo = reader.readNextIntArray(8); + + result.rawForkData = reader.readNextByteArray(400); + + return result; + } + + public boolean isValid() { + return signature == HFSPLUS_SIGNATURE_MAGIC && version == HFSPLUS_VERSION && + isGoodBlockSize(blockSize); + } + + private static boolean isGoodBlockSize(int bs) { + return bs > 0 && bs % 512 == 0; + } + + public boolean hasGoodVolumeInfo(ByteProvider bp) throws IOException { + long calculatedSize = blockSize * totalBlocks; + // NOTE: can't compare with exact equals-to provider size because an extra 16 bytes + // are present in examples extracted from firmware images + return bp.length() >= calculatedSize; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystem.java index bd8992b369..d58d71d808 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystem.java @@ -15,283 +15,15 @@ */ package ghidra.file.formats.iso9660; -import java.io.IOException; -import java.util.*; - -import ghidra.app.util.bin.BinaryReader; -import ghidra.app.util.bin.ByteProvider; -import ghidra.formats.gfilesystem.*; +import ghidra.file.formats.sevenzip.SevenZipFileSystem; +import ghidra.formats.gfilesystem.FSRLRoot; +import ghidra.formats.gfilesystem.FileSystemService; import ghidra.formats.gfilesystem.annotations.FileSystemInfo; -import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory; -import ghidra.util.Msg; -import ghidra.util.exception.*; -import ghidra.util.task.TaskMonitor; -@FileSystemInfo(type = "iso9660", description = "ISO 9660", factory = GFileSystemBaseFactory.class) -public class ISO9660FileSystem extends GFileSystemBase { +@FileSystemInfo(type = "iso9660", description = "ISO 9660", factory = ISO9660FileSystemFactory.class) +public class ISO9660FileSystem extends SevenZipFileSystem { - //Possible locations for magic number - private static final long[] SIGNATURE_PROBE_OFFSETS = new long[] { 0x8000L, 0x8800L, 0x9000L }; - - //Location where the magic number was found - private long signatureOffset; - - //Set true if the root level directory has been processed - private boolean lookedAtRoot = false; - - private short logicalBlockSize; - - private ISO9660Header header; - - private Map fileToDirectoryMap = new HashMap<>(); - - public ISO9660FileSystem(String fileSystemName, ByteProvider provider) { - - super(fileSystemName, provider); + public ISO9660FileSystem(FSRLRoot fsrl, FileSystemService fsService) { + super(fsrl, fsService); } - - @Override - public boolean isValid(TaskMonitor monitor) throws IOException { - for (long probeOffset : SIGNATURE_PROBE_OFFSETS) { - if (isMagicSignatureAt(probeOffset + 1)) { - // signature is at +1 offset from the start of the volume offset - signatureOffset = probeOffset; - return true; - } - } - return false; - } - - private boolean isMagicSignatureAt(long offset) throws IOException { - int magicLen = ISO9660Constants.MAGIC_BYTES.length; - long providerLen = provider.length(); - return (providerLen > offset + magicLen) && - Arrays.equals(provider.readBytes(offset, magicLen), ISO9660Constants.MAGIC_BYTES); - } - - @Override - public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException { - BinaryReader reader = new BinaryReader(provider, true); - - //Set start of pointer index of beginning of primary volume descriptor - reader.setPointerIndex(signatureOffset); - - header = new ISO9660Header(reader); - ISO9660VolumeDescriptor pvd = header.getPrimaryVolumeDescriptor(); - logicalBlockSize = pvd.getLogicalBlockSizeBE(); - ISO9660Directory rootDir = header.getPrimaryDirectory(); - - //Get the list containing all directories at the root level of the file system - List topLevel = - createDirectoryList(reader, rootDir, pvd.getLogicalBlockSizeLE(), monitor); - - try { - - //Entry point for the this recursive function to process all nested - //directories - createDirectories(reader, topLevel, pvd.getLogicalBlockSizeLE(), monitor); - } - - catch (Exception e) { - Msg.showError(this, null, "Directory Creation Error", - "Failed to create archive directories"); - - } - - } - - @Override - public void close() throws IOException { - super.close(); - header = null; - fileToDirectoryMap.clear(); - } - - @Override - public List getListing(GFile directory) throws IOException { - if (directory == null || directory.equals(root)) { - List roots = new ArrayList<>(); - for (GFile file : fileToDirectoryMap.keySet()) { - if (file.getParentFile() == root || file.getParentFile().equals(root)) { - roots.add(file); - } - } - return roots; - } - List tmp = new ArrayList<>(); - for (GFile file : fileToDirectoryMap.keySet()) { - if (file.getParentFile() == null) { - continue; - } - if (file.getParentFile().equals(directory)) { - tmp.add(file); - } - } - return tmp; - } - - @Override - public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) - throws IOException, CancelledException { - ISO9660Directory dir = fileToDirectoryMap.get(file); - return dir.getByteProvider(provider, logicalBlockSize, file.getFSRL()); - } - - /* - * From a given parent directory create each child directory - * under that parent directory and add them to a list - */ - private List createDirectoryList(BinaryReader reader, - ISO9660Directory parentDir, long blockSize, TaskMonitor monitor) throws IOException { - - List directoryList = new ArrayList<>(); - ISO9660Directory childDir = null; - long dirIndex; - long endIndex; - - //Get location from parent into child directory - dirIndex = parentDir.getLocationOfExtentLE() * blockSize; - endIndex = dirIndex + parentDir.getDataLengthLE(); - - //while there is still more data in the current directory level - while (dirIndex < endIndex) { - reader.setPointerIndex(dirIndex); - - //If the next byte is not zero then create the directory - if (reader.peekNextByte() != 0) { - if (!lookedAtRoot) { - childDir = new ISO9660Directory(reader); - addAndStoreDirectory(monitor, directoryList, childDir); - - } - - //Root level has already been looked at - else { - if (parentDir.getName() != null) { - childDir = new ISO9660Directory(reader, parentDir); - addAndStoreDirectory(monitor, directoryList, childDir); - } - } - } - - //Otherwise there is a gap in the data so keep looking forward - //while still under the end index and create directory when data is - //reached - else { - readWhileZero(reader, endIndex); - - //Create the data once the reader finds the next position - //and not reached end index - if (reader.getPointerIndex() < endIndex) { - if (!lookedAtRoot) { - childDir = new ISO9660Directory(reader); - addAndStoreDirectory(monitor, directoryList, childDir); - dirIndex = childDir.getVolumeIndex(); - } - else { - if (parentDir.getName() != null) { - childDir = new ISO9660Directory(reader, parentDir); - addAndStoreDirectory(monitor, directoryList, childDir); - dirIndex = childDir.getVolumeIndex(); - } - } - } - - } - dirIndex += childDir.getDirectoryRecordLength(); - } - - lookedAtRoot = true; - return directoryList; - } - - private void readWhileZero(BinaryReader reader, long endIndex) throws IOException { - while (reader.peekNextByte() == 0) { - - //keep reading if all zeros until non zero is met or - //end index reached - if (reader.getPointerIndex() < endIndex) { - reader.readNextByte(); - } - else { - break; - } - } - } - - private void addAndStoreDirectory(TaskMonitor monitor, List directoryList, - ISO9660Directory childDir) { - - directoryList.add(childDir); - - if (childDir.getName() != null) { - storeDirectory(childDir, monitor); - } - } - - /* - * Recurses though each level of a directory structure - * in a depth-first manner - * and creates each directory also marking them in the binary - */ - private void createDirectories(BinaryReader reader, List directoryList, - long blockSize, TaskMonitor monitor) throws DuplicateNameException, Exception { - - for (ISO9660Directory dir : directoryList) { - - //If the directory is a new level of directories - //recurse down into the next level - if (dir.isDirectoryFlagSet() && dir.getName() != null) { - List dirs; - dirs = createDirectoryList(reader, dir, blockSize, monitor); - createDirectories(reader, dirs, blockSize, monitor); - } - } - return; - } - - /* - * Stores a gFile after finding its parent and its matching directory - */ - private void storeDirectory(ISO9660Directory directory, TaskMonitor monitor) { - - String dirName = directory.getName(); - boolean isDirectory = directory.isDirectoryFlagSet(); - int length = directory.getDataLengthLE(); - GFileImpl gFile = null; - - //Map does not contain entries yet since root level needs to be processed - if (!lookedAtRoot) { - gFile = GFileImpl.fromFilename(this, root, dirName, isDirectory, length, null); - storeFile(gFile, directory); - } - else { - //Root has been processed, all other entries must have a parent - String parentDirName = directory.getParentDirectory().getName(); - for (GFile currGFile : fileToDirectoryMap.keySet()) { - //Find the parent and store the file - if (parentDirName.equals(currGFile.getName())) { - gFile = - GFileImpl.fromFilename(this, currGFile, dirName, isDirectory, length, null); - storeFile(gFile, directory); - break; - } - } - } - } - - private void storeFile(GFile file, ISO9660Directory directory) { - if (file == null) { - return; - } - if (file.equals(root)) { - return; - } - if (!fileToDirectoryMap.containsKey(file) || fileToDirectoryMap.get(file) == null) { - fileToDirectoryMap.put(file, directory); - } - GFile parentFile = file.getParentFile(); - storeFile(parentFile, null); - } - } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystemFactory.java new file mode 100644 index 0000000000..c3c88b4bbb --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/iso9660/ISO9660FileSystemFactory.java @@ -0,0 +1,68 @@ +/* ### + * IP: GHIDRA + * + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.iso9660; + +import java.io.IOException; +import java.util.Arrays; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.formats.gfilesystem.FSRLRoot; +import ghidra.formats.gfilesystem.FileSystemService; +import ghidra.formats.gfilesystem.factory.GFileSystemFactoryByteProvider; +import ghidra.formats.gfilesystem.factory.GFileSystemProbeByteProvider; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class ISO9660FileSystemFactory + implements GFileSystemFactoryByteProvider, GFileSystemProbeByteProvider { + private static final long[] SIGNATURE_PROBE_OFFSETS = new long[] { 0x8000L, 0x8800L, 0x9000L }; + + @Override + public boolean probe(ByteProvider byteProvider, FileSystemService fsService, + TaskMonitor monitor) throws IOException, CancelledException { + + for (long probeOffset : SIGNATURE_PROBE_OFFSETS) { + if (isMagicSignatureAt(byteProvider, probeOffset + 1)) { + return true; + } + } + return false; + } + + private boolean isMagicSignatureAt(ByteProvider provider, long offset) throws IOException { + int magicLen = ISO9660Constants.MAGIC_BYTES.length; + long providerLen = provider.length(); + return (providerLen > offset + magicLen) && + Arrays.equals(provider.readBytes(offset, magicLen), ISO9660Constants.MAGIC_BYTES); + } + + @Override + public ISO9660FileSystem create(FSRLRoot targetFSRL, ByteProvider byteProvider, + FileSystemService fsService, TaskMonitor monitor) + throws IOException, CancelledException { + + ISO9660FileSystem fs = new ISO9660FileSystem(targetFSRL, fsService); + try { + fs.mount(byteProvider, monitor); + return fs; + } + catch (IOException ioe) { + fs.close(); + throw ioe; + } + } + +}