Merge remote-tracking branch 'origin/GP-807_dev747368_HFS_ISO9660'

This commit is contained in:
Ryan Kurtz
2021-10-14 08:02:13 -04:00
5 changed files with 301 additions and 275 deletions

View File

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

View File

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

View File

@@ -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.
* <p>
* See https://developer.apple.com/library/archive/technotes/tn/tn1150.html#VolumeHeader
* <p>
* 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;
}
}

View File

@@ -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<GFile, ISO9660Directory> 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<ISO9660Directory> 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<GFile> getListing(GFile directory) throws IOException {
if (directory == null || directory.equals(root)) {
List<GFile> roots = new ArrayList<>();
for (GFile file : fileToDirectoryMap.keySet()) {
if (file.getParentFile() == root || file.getParentFile().equals(root)) {
roots.add(file);
}
}
return roots;
}
List<GFile> 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<ISO9660Directory> createDirectoryList(BinaryReader reader,
ISO9660Directory parentDir, long blockSize, TaskMonitor monitor) throws IOException {
List<ISO9660Directory> 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<ISO9660Directory> 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<ISO9660Directory> 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<ISO9660Directory> 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);
}
}

View File

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