Source code for adi_lg_plugins.drivers.imageextractor
import os
import pytsk3
[docs]
class IMGFileExtractor:
"""Extract files from disk image (.img) files using pytsk3.
This utility class provides methods to inspect partitions, list files,
and extract individual files or directories from disk image files without
mounting them. It's primarily used by KuiperDLDriver to extract boot files
from Kuiper Linux release images.
Args:
img_path (str): Path to the disk image file.
logger (Logger, optional): Logger instance for debug output. If None,
prints to stdout.
Example:
>>> extractor = IMGFileExtractor("kuiper.img")
>>> partitions = extractor.get_partitions()
>>> fs = extractor.open_filesystem(partitions[0]["start"])
>>> extractor.extract_file(fs, "/Image", "./output/Image")
>>> extractor.close()
"""
[docs]
def __init__(self, img_path, logger=None):
self.img_path = img_path
self.img_handle = pytsk3.Img_Info(img_path)
self.logger = logger
[docs]
def get_partitions(self):
"""List all partitions in the IMG file"""
try:
volume = pytsk3.Volume_Info(self.img_handle)
partitions = []
for partition in volume:
if partition.len > 2048: # Filter out small/empty partitions
partitions.append(
{
"tag": partition.tag,
"index": partition.addr,
"start": partition.start * 512, # Convert sectors to bytes
"length": partition.len * 512,
"description": partition.desc.decode("utf-8")
if partition.desc
else "Unknown",
}
)
return partitions
except Exception as e:
print(f"Error getting partitions: {e}")
# If volume info fails, try to detect filesystem at offset 0
return [{"index": 0, "start": 0, "length": 0, "description": "Single partition"}]
[docs]
def open_filesystem(self, partition_offset):
"""Open filesystem at a specific partition offset"""
try:
return pytsk3.FS_Info(self.img_handle, offset=partition_offset)
except Exception as e:
raise Exception(f"Could not open filesystem at offset {partition_offset}: {e}") from e
[docs]
def list_files(self, fs, path="/"):
"""Recursively list all files in a directory"""
try:
directory = fs.open_dir(path)
files = []
for entry in directory:
name = entry.info.name.name.decode("utf-8")
# Skip . and ..
if name in [".", ".."]:
continue
full_path = f"{path}/{name}".replace("//", "/")
# Check if it's a directory
if entry.info.meta and entry.info.meta.type == pytsk3.TSK_FS_META_TYPE_DIR:
files.append({"path": full_path, "type": "dir", "size": 0})
# Recursively list subdirectory
files.extend(self.list_files(fs, full_path))
elif entry.info.meta:
files.append({"path": full_path, "type": "file", "size": entry.info.meta.size})
return files
except Exception as e:
print(f"Error listing {path}: {e}")
return []
[docs]
def extract_file(self, fs, file_path, output_path):
"""Extract a single file"""
try:
# Open the file in the filesystem
file_entry = fs.open(file_path)
# Read file data
file_size = file_entry.info.meta.size
data = file_entry.read_random(0, file_size)
# Create output directory if needed
os.makedirs(os.path.dirname(output_path), exist_ok=True)
# Write to output file
with open(output_path, "wb") as f:
f.write(data)
self.log(f"Extracted: {file_path} -> {output_path}")
return True
except Exception as e:
self.log(f"Error extracting {file_path}: {e}")
return False
[docs]
def extract_directory(self, fs, source_path, output_dir):
"""Extract an entire directory recursively"""
files = self.list_files(fs, source_path)
for file_info in files:
if file_info["type"] == "file":
# Create relative path for output
rel_path = file_info["path"].lstrip("/")
output_path = os.path.join(output_dir, rel_path)
self.extract_file(fs, file_info["path"], output_path)
[docs]
def close(self):
"""Close the IMG file handle"""
# pytsk3.Img_Info doesn't require explicit closing
# The resources will be released when the object is garbage collected
pass
# Example usage:
def main():
extractor = IMGFileExtractor("disk.img")
# 1. List all partitions
print("Available partitions:")
partitions = extractor.get_partitions()
for i, part in enumerate(partitions):
print(f" {i}: {part['description']} - Offset: {part['start']} bytes")
# 2. Choose a partition (e.g., partition 0)
partition_index = 0
partition_offset = partitions[partition_index]["start"]
# 3. Open the filesystem
fs = extractor.open_filesystem(partition_offset)
# 4. List files in a specific directory
print("\nFiles in root:")
files = extractor.list_files(fs, "/")
for f in files[:10]: # Show first 10
print(f" {f['type']}: {f['path']} ({f['size']} bytes)")
# 5. Extract specific file
extractor.extract_file(fs, "/path/to/file.txt", "./output/file.txt")
# 6. Or extract entire directory
extractor.extract_directory(fs, "/etc", "./output/etc")
extractor.close()
if __name__ == "__main__":
main()