uploaded what I have found
This commit is contained in:
parent
81b6ba30d1
commit
0d355902ab
1
AUTHORS
Executable file
1
AUTHORS
Executable file
@ -0,0 +1 @@
|
||||
fatlister is written and maintained by Patrick Neumann <patrick@neumannsland.de>.
|
21
CHANGES
Executable file
21
CHANGES
Executable file
@ -0,0 +1,21 @@
|
||||
Release 1.0 (in development)
|
||||
============================
|
||||
|
||||
|
||||
(Incompatible) changes
|
||||
----------------------
|
||||
|
||||
* none
|
||||
|
||||
|
||||
Features added
|
||||
--------------
|
||||
|
||||
* none
|
||||
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* none
|
||||
|
BIN
cf64mbwin.dd
Executable file
BIN
cf64mbwin.dd
Executable file
Binary file not shown.
114
cf64mbwin.info
Executable file
114
cf64mbwin.info
Executable file
@ -0,0 +1,114 @@
|
||||
|
||||
GUYMAGER ACQUISITION INFO FILE
|
||||
==============================
|
||||
|
||||
Guymager
|
||||
========
|
||||
|
||||
Version : 0.8.4-1
|
||||
Compilation timestamp: 2017-02-02-16.19.12
|
||||
Compiled with : gcc 4.9.2
|
||||
libewf version : 20140608
|
||||
libguytools version : 2.1.0beta5
|
||||
Host name : host10
|
||||
Domain name : (none)
|
||||
System : Linux host10 3.16.0-4-amd64 #1 SMP Debian 3.16.39-1+deb8u2 (2017-03-07) x86_64
|
||||
|
||||
|
||||
Device information
|
||||
==================
|
||||
Command executed: bash -c "search="`basename /dev/sdc`: H..t P.......d A..a de.....d" && dmesg | grep -A3 "$search" || echo "No kernel HPA messages for /dev/sdc""
|
||||
Information returned:
|
||||
----------------------------------------------------------------------------------------------------
|
||||
No kernel HPA messages for /dev/sdc
|
||||
|
||||
Command executed: bash -c "smartctl -s on /dev/sdc ; smartctl -a /dev/sdc"
|
||||
Information returned:
|
||||
----------------------------------------------------------------------------------------------------
|
||||
smartctl 6.4 2014-10-07 r4002 [x86_64-linux-3.16.0-4-amd64] (local build)
|
||||
Copyright (C) 2002-14, Bruce Allen, Christian Franke, www.smartmontools.org
|
||||
|
||||
/dev/sdc: Unknown USB bridge [0x0bda:0x0184 (0x8413)]
|
||||
Please specify device type with the -d option.
|
||||
|
||||
Use smartctl -h to get a usage summary
|
||||
|
||||
smartctl 6.4 2014-10-07 r4002 [x86_64-linux-3.16.0-4-amd64] (local build)
|
||||
Copyright (C) 2002-14, Bruce Allen, Christian Franke, www.smartmontools.org
|
||||
|
||||
/dev/sdc: Unknown USB bridge [0x0bda:0x0184 (0x8413)]
|
||||
Please specify device type with the -d option.
|
||||
|
||||
Use smartctl -h to get a usage summary
|
||||
|
||||
Command executed: bash -c "hdparm -I /dev/sdc"
|
||||
Information returned:
|
||||
----------------------------------------------------------------------------------------------------
|
||||
SG_IO: bad/missing sense data, sb[]: 70 00 05 00 00 00 00 0a 00 00 00 00 24 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
|
||||
/dev/sdc:
|
||||
|
||||
ATA device, with non-removable media
|
||||
Standards:
|
||||
Likely used: 1
|
||||
Configuration:
|
||||
Logical max current
|
||||
cylinders 0 0
|
||||
heads 0 0
|
||||
sectors/track 0 0
|
||||
--
|
||||
Logical/Physical Sector size: 512 bytes
|
||||
device size with M = 1024*1024: 0 MBytes
|
||||
device size with M = 1000*1000: 0 MBytes
|
||||
cache/buffer size = unknown
|
||||
Capabilities:
|
||||
IORDY not likely
|
||||
Cannot perform double-word IO
|
||||
R/W multiple sector transfer: not supported
|
||||
DMA: not supported
|
||||
PIO: pio0
|
||||
|
||||
Hidden areas: unknown
|
||||
|
||||
|
||||
Acquisition
|
||||
===========
|
||||
|
||||
Linux device : /dev/sdc
|
||||
Device size : 65536000 (65,5MB)
|
||||
Format : Linux split dd raw image - file extension is .xxx
|
||||
Image path and file name: /home/user10/Dokumente/cf64mbwin.xxx
|
||||
Info path and file name: /home/user10/Dokumente/cf64mbwin.info
|
||||
Hash calculation : MD5 and SHA-1
|
||||
Source verification : on
|
||||
Image verification : on
|
||||
|
||||
No bad sectors encountered during acquisition.
|
||||
No bad sectors encountered during verification.
|
||||
State: Finished successfully
|
||||
|
||||
MD5 hash : 9073705f04e50e70790cddf48a1d0413
|
||||
MD5 hash verified source : 9073705f04e50e70790cddf48a1d0413
|
||||
MD5 hash verified image : 9073705f04e50e70790cddf48a1d0413
|
||||
SHA1 hash : 08165d91b9f2d84581ecc087abf36855fb721da3
|
||||
SHA1 hash verified source : 08165d91b9f2d84581ecc087abf36855fb721da3
|
||||
SHA1 hash verified image : 08165d91b9f2d84581ecc087abf36855fb721da3
|
||||
SHA256 hash : --
|
||||
SHA256 hash verified source: --
|
||||
SHA256 hash verified image : --
|
||||
Source verification OK. The device delivered the same data during acquisition and verification.
|
||||
Image verification OK. The image contains exactly the data that was written.
|
||||
|
||||
Acquisition started : 2017-05-08 15:10:06 (ISO format YYYY-MM-DD HH:MM:SS)
|
||||
Verification started: 2017-05-08 15:10:24
|
||||
Ended : 2017-05-08 15:10:43 (0 hours, 0 minutes and 37 seconds)
|
||||
Acquisition speed : 3.47 MByte/s (0 hours, 0 minutes and 18 seconds)
|
||||
Verification speed : 3.47 MByte/s (0 hours, 0 minutes and 18 seconds)
|
||||
|
||||
|
||||
Generated image files and their MD5 hashes
|
||||
==========================================
|
||||
|
||||
No MD5 hashes available (configuration parameter CalcImageFileMD5 is off)
|
||||
MD5 Image file
|
||||
n/a cf64mbwin.000
|
BIN
doc/fatlister.pdf
Executable file
BIN
doc/fatlister.pdf
Executable file
Binary file not shown.
522
fatlister.py
Executable file
522
fatlister.py
Executable file
@ -0,0 +1,522 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""List file and directory names in a FAT16 file system (disk image)."""
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
# FILE:
|
||||
# ./fatlister.py
|
||||
#
|
||||
# BASIC USAGE:
|
||||
# $ ./fatlister.py [-h|--help] [-o|--offset OFFSET] [-f|--file FILENAME] IMAGE
|
||||
# OR
|
||||
# $ python3 fatlister.py [-h|--help] [-o|--offset OFFSET] [-f|--file FILENAME] IMAGE
|
||||
#
|
||||
# OPTIONS:
|
||||
# -h,
|
||||
# --help show help message and exit
|
||||
# -o OFFSET,
|
||||
# --offset OFFSET offset in sectors (default=0)
|
||||
# -f FILENAME, write output to file (default=unset)
|
||||
# --file FILENAME if unset output goes to stdout
|
||||
# IMAGE raw image file (esp. dd)
|
||||
#
|
||||
# EXIT STATES:
|
||||
# 0 = success
|
||||
# 1 = Python version not tested/supported
|
||||
# 2 = states thrown by argparse:
|
||||
# - IMAGE not passed to fatlister
|
||||
# - unknown argument passed to fatlister
|
||||
# - OFFSET is not an integer
|
||||
# 3 = image file does not exist
|
||||
# 4 = file is not readable
|
||||
# 5 = offset is not a positive integer
|
||||
# 6 = output file already exist
|
||||
# 7 = output directory is not writable
|
||||
# 8 = empty image file
|
||||
# 9 = image file smaller than offset
|
||||
# 10 = invalid vbr signatures
|
||||
#
|
||||
# REQUIREMENTS:
|
||||
# python3.4+
|
||||
#
|
||||
# NOTES:
|
||||
# Tested on:
|
||||
# - ArchLinux (64-Bit) + python 3.6.1
|
||||
# - Raspbian GNU/Linux 8.0 (32-Bit) + python 3.4.2
|
||||
# - macOS (10.12.4) + python 3.6.1 (from Homebrew)
|
||||
# - Bash on Ubuntu on Windows 10 "1703" (64-Bit) + python 3.5.2
|
||||
# with:
|
||||
# - raw images
|
||||
# - filesystem(s) in partition(s) <--- TODO!
|
||||
# - filesystem without a partition
|
||||
# - no partition/no filesystem (empty file)
|
||||
# - invalid filesystem
|
||||
# - FAT16
|
||||
#
|
||||
# WARRANTY:
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the LICENSE file for more details.
|
||||
#
|
||||
# HISTORY:
|
||||
# See the CHANGES file for more details.
|
||||
#
|
||||
#===============================================================================
|
||||
|
||||
#=== MODULES ===================================================================
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
#=== INFO ======================================================================
|
||||
|
||||
"""@author: Patrick Neumann
|
||||
@contact: patrick@neumannsland.de
|
||||
@copyright: Copyright (C) 2017, Patrick Neumann
|
||||
@license: GNU General Public License 3.0
|
||||
@date: 2017-03-26
|
||||
@version: 1.0.1
|
||||
@status: Development
|
||||
"""
|
||||
|
||||
#=== CHECKS ====================================================================
|
||||
|
||||
"""Only tested with Python versions 3.4.x - 3.6.x!"""
|
||||
|
||||
if sys.version.startswith("2"):
|
||||
sys.stderr.write("Python version 2.x is not supportet!\n")
|
||||
sys.exit(1)
|
||||
|
||||
if sys.version_info < (3, 4, 0):
|
||||
sys.stderr.write("Sorry, your Python version was not tested but may work?\n")
|
||||
sys.exit(1)
|
||||
|
||||
#=== CLASS =====================================================================
|
||||
|
||||
class Fatlister:
|
||||
def __init__(self, image, offset):
|
||||
self._checkimage(image)
|
||||
self.image = image
|
||||
self._checkoffset(offset)
|
||||
self.offset = offset
|
||||
self._checkvbr()
|
||||
self.output = ['# This RFC4180 compliant file was created with fatlister.py at ' + time.strftime("%Y-%m-%dT%H:%M:%S") + '.',
|
||||
'"id", "path", "name", "LFN", "Allocated", "Size", "Accessed", "Modified", "Created", "Starting Cluster"']
|
||||
self._processvbr()
|
||||
self._readfiles()
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# This is a private method for usage in this class only.
|
||||
# Unfortunately, the underscore does not prevent other developers from using
|
||||
# it directly from outside this class. :-(
|
||||
# sphinx-apidoc does ignore it. :-)
|
||||
def _checkimage(self, _file):
|
||||
"""Check image file.
|
||||
|
||||
Check if the image is a existing and readable file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
_file : str
|
||||
path to the image file.
|
||||
|
||||
Raises
|
||||
------
|
||||
Exits if the image does not exist or is not readable.
|
||||
"""
|
||||
|
||||
if not os.path.isfile(_file):
|
||||
sys.stderr.write("Error: image file does not exist!\n")
|
||||
sys.exit(3)
|
||||
|
||||
size = os.stat(_file)[6]
|
||||
if size == 0:
|
||||
sys.stderr.write("Error: the image file is an empty file!\n")
|
||||
sys.exit(8)
|
||||
|
||||
# Check if image is readable the first time if we need to read it!
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# This is a private method for usage in this class only.
|
||||
# Unfortunately, the underscore does not prevent other developers from using
|
||||
# it directly from outside this class. :-(
|
||||
# sphinx-apidoc does ignore it. :-)
|
||||
def _checkoffset(self, offset):
|
||||
"""Check offset.
|
||||
|
||||
Check if the offset is an integer and is equal or greater than zero.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
offset : int
|
||||
offset in bytes in the image file.
|
||||
|
||||
Raises
|
||||
------
|
||||
Exits if the offset is not equal or greater than zero.
|
||||
"""
|
||||
|
||||
if not offset >= 0:
|
||||
sys.stderr.write("Error: offset is not equal or greater zero!\n")
|
||||
sys.exit(5)
|
||||
|
||||
size = os.stat(self.image)[6]
|
||||
if size < offset + 512:
|
||||
sys.stderr.write("Error: size of the image file is smaller than offset (+ 512 byte)!\n")
|
||||
sys.exit(9)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# This is a private method for usage in this class only.
|
||||
# Unfortunately, the underscore does not prevent other developers from using
|
||||
# it directly from outside this class. :-(
|
||||
# sphinx-apidoc does ignore it. :-)
|
||||
def _checkvbr(self):
|
||||
"""Check for FAT VBR.
|
||||
|
||||
Check for the existence of a valid FAT12/16 VBR.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
self.image : str
|
||||
path to the image file.
|
||||
|
||||
self.offset : int
|
||||
offset in bytes in the image file.
|
||||
|
||||
Raises
|
||||
------
|
||||
Exits if the VBR can not be verified as a FAT12/16 VBR.
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(self.image, "rb") as isreadable:
|
||||
isreadable.seek(self.offset + 38)
|
||||
extended_signature = isreadable.read(1)
|
||||
isreadable.seek(15, 1)
|
||||
file_system_type = isreadable.read(8)
|
||||
isreadable.seek(448, 1)
|
||||
vbr_signature = isreadable.read(2)
|
||||
except IOError:
|
||||
sys.stderr.write("Error: image file is not readable!\n")
|
||||
sys.exit(4)
|
||||
|
||||
condition1 = extended_signature == b"\x29"
|
||||
condition2 = re.search(r'FAT1(2|6) ', file_system_type.decode("ascii"))
|
||||
condition3 = vbr_signature == b"\x55\xaa"
|
||||
|
||||
if not (condition1 and condition2 and condition3):
|
||||
sys.stderr.write("Error: no valid FAT12/FAT16 VBR found!\n")
|
||||
sys.exit(10)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
def printoutput(self):
|
||||
"""Print to stdout.
|
||||
|
||||
Prints every item of self.output to stdout.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
self.output : list
|
||||
List with every line of output as a item.
|
||||
"""
|
||||
|
||||
for line in self.output:
|
||||
print(line)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
def writeoutput(self):
|
||||
"""Write to file.
|
||||
|
||||
Writes every item of self.output to a file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
self.output : list
|
||||
List with every line of output as a item.
|
||||
|
||||
Raises
|
||||
------
|
||||
Exits if output directory is not writable.
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(self.file, "w") as iswritable:
|
||||
for line in self.output:
|
||||
iswritable.write(line + "\n")
|
||||
except IOError:
|
||||
sys.stderr.write("Error: output directory is not writable!\n")
|
||||
sys.exit(7)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# This is a private method for usage in this class only.
|
||||
# Unfortunately, the underscore does not prevent other developers from using
|
||||
# it directly from outside this class. :-(
|
||||
# sphinx-apidoc does ignore it. :-)
|
||||
def _processvbr(self):
|
||||
"""Process FAT12/FAT16 VBR.
|
||||
|
||||
Read, translate and calculate needed information out of the vbr and
|
||||
write it into single attributes.
|
||||
"""
|
||||
|
||||
# We have already checked if we are able to read the image.
|
||||
with open(self.image, "rb") as image:
|
||||
image.seek(self.offset + 11)
|
||||
raw = image.read(2 + 1 + 2 + 1 + 2)
|
||||
parts = struct.unpack_from("<HBHBH", raw)
|
||||
bytesPerSector = parts[0]
|
||||
sectorsPerCluster = parts[1]
|
||||
reservedSectors = parts[2]
|
||||
fatCopies = parts[3]
|
||||
rootDirectoryEntries = parts[4]
|
||||
image.seek(3, 1)
|
||||
sectorsPerFat = struct.unpack_from("<H", image.read(2))[0]
|
||||
self.StartOfFAT0 = reservedSectors * bytesPerSector
|
||||
self.StartOfFAT1 = self.StartOfFAT0 + sectorsPerFat * bytesPerSector
|
||||
# In theory there could be more than two FATs
|
||||
# but I know nobody who has seen more of them in real!
|
||||
self.firstRootDirectoryEntryLocation = ((reservedSectors + fatCopies * sectorsPerFat) * bytesPerSector) + 32
|
||||
self.startOfClusterArea = ((reservedSectors + fatCopies * sectorsPerFat) * bytesPerSector) + (rootDirectoryEntries * 32)
|
||||
self.bytesPerCluster = bytesPerSector * sectorsPerCluster
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
def _calculatedate(unsignedShort):
|
||||
# TODO:
|
||||
# candidate for external use?
|
||||
# add test for unignedShort
|
||||
# add comment
|
||||
year = 1980 + ((0b1111111000000000 & unsignedShort) >> 9)
|
||||
month = "{:02}".format((0b0000000111100000 & unsignedShort) >> 5)
|
||||
day = "{:02}".format(0b0000000000011111 & unsignedShort)
|
||||
return (year, month, day)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
def _calculatetime(unsignedShort):
|
||||
# TODO:
|
||||
# candidate for external use?
|
||||
# add test for unignedShort
|
||||
# add comment
|
||||
hours = "{:02}".format((0b1111100000000000 & unsignedShort) >> 11)
|
||||
minutes = "{:02}".format((0b0000011111100000 & unsignedShort) >> 5)
|
||||
seconds = "{:02}".format((0b0000000000011111 & unsignedShort) * 2)
|
||||
return (hours, minutes, seconds)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# This is a private method for usage in this class only.
|
||||
# Unfortunately, the underscore does not prevent other developers from using
|
||||
# it directly from outside this class. :-(
|
||||
# sphinx-apidoc does ignore it. :-)
|
||||
def _readfiles(self):
|
||||
"""Loop over directories and files.
|
||||
|
||||
Loop over all directories and add each file with SFN and optionally
|
||||
LFN to the list (self.outout).
|
||||
|
||||
TODO
|
||||
----
|
||||
1. Move subloops to separate methods!?
|
||||
"""
|
||||
|
||||
directoryOffsets = [self.offset + self.firstRootDirectoryEntryLocation]
|
||||
directories = {self.offset + self.firstRootDirectoryEntryLocation: "/"}
|
||||
_id = 0
|
||||
longFileName = ""
|
||||
|
||||
# We have already checked if we are able to read the image.
|
||||
with open(self.image, "rb") as image:
|
||||
for directoryOffset in directoryOffsets:
|
||||
fileOffset = directoryOffset
|
||||
for entry in range(int(self.bytesPerCluster / 32)):
|
||||
image.seek(fileOffset)
|
||||
filename = image.read(8)
|
||||
# skip . or ..:
|
||||
if re.search(b"^\x2e\x20{7}|\x2e{2}\x20{6}", filename):
|
||||
fileOffset = fileOffset + 32
|
||||
continue
|
||||
# if unused:
|
||||
elif re.search(b"^\x00", filename):
|
||||
break
|
||||
# if deleted:
|
||||
elif re.search(b"^\xe5", filename):
|
||||
filename = b"\x3f" + filename[1:] # deleted (sigma) to ?
|
||||
allocated = "deleted"
|
||||
else:
|
||||
# if the filename starts with "sigma":
|
||||
if re.search(b"^\x05", filename):
|
||||
filename = b"\xe5" + filename[1:]
|
||||
allocated = "allocated"
|
||||
filenameExtension = image.read(3)
|
||||
fileAttributes = struct.unpack_from("<B", image.read(1))[0]
|
||||
# if we have a LFN:
|
||||
if fileAttributes == 15:
|
||||
cache = b""
|
||||
image.seek(fileOffset + 1)
|
||||
for step in range(5):
|
||||
cache = cache + struct.pack(">H", struct.unpack_from("<H", image.read(2))[0])
|
||||
image.seek(3, 1)
|
||||
for step in range(6):
|
||||
cache = cache + struct.pack(">H", struct.unpack_from("<H", image.read(2))[0])
|
||||
image.seek(2, 1)
|
||||
for step in range(2):
|
||||
cache = cache + struct.pack(">H", struct.unpack_from("<H", image.read(2))[0])
|
||||
cache = re.sub(b"\x00\x00|\xff\xff", b"", cache) # end and unused
|
||||
cache = re.sub(b"^\x03\xc3", b"\x00\x3f", cache) # deleted to ?
|
||||
cache = re.sub(b"\x00", b"", cache) # delete unneeded zerobytes (ugly hack?)
|
||||
longFileName = cache.decode("utf-8", "ignore") + longFileName # ignore undecodable codes
|
||||
fileOffset = fileOffset + 32
|
||||
continue
|
||||
filename = re.sub(" *$", "", filename.decode("cp437")) # remove spaces at the end
|
||||
filenameExtension = filenameExtension.decode("cp437")
|
||||
if not re.search(r'^ *$', filenameExtension):
|
||||
filename = filename + '.' + filenameExtension # add . and ext only if ext not empty
|
||||
image.seek(2, 1)
|
||||
raw = image.read(2 + 2 + 2)
|
||||
parts = struct.unpack_from("<HHH", raw)
|
||||
(createdHours, createdMinutes, createdSeconds) = Fatlister._calculatetime(parts[0])
|
||||
(createdYear, createdMonth, createdDay) = Fatlister._calculatedate(parts[1])
|
||||
(accessedYear, accessedMonth, accessedDay) = Fatlister._calculatedate(parts[2])
|
||||
image.seek(2, 1)
|
||||
raw = image.read(2 + 2 + 2 + 4)
|
||||
parts = struct.unpack_from("<HHHI", raw)
|
||||
(modifiedHours, modifiedMinutes, modifiedSeconds) = Fatlister._calculatetime(parts[0])
|
||||
(modifiedYear, modifiedMonth, modifiedDay) = Fatlister._calculatedate(parts[1])
|
||||
startingCluster = parts[2]
|
||||
# if we have a directory:
|
||||
if fileAttributes == 16:
|
||||
longFileName = ""
|
||||
clusterOffset = startingCluster
|
||||
while True:
|
||||
# just a ugly hack to avoid multiple rounds with the same directoryOffset! :-/
|
||||
if not self.startOfClusterArea + ((clusterOffset - 2) * self.bytesPerCluster) in directoryOffsets:
|
||||
directoryOffsets.append(self.startOfClusterArea + ((clusterOffset - 2) * self.bytesPerCluster))
|
||||
directories[self.startOfClusterArea + ((clusterOffset - 2) * self.bytesPerCluster)] = directories[directoryOffset] + filename + "/"
|
||||
nextCluster = self.offset + self.StartOfFAT0 + 4 + (clusterOffset * 2)
|
||||
image.seek(nextCluster)
|
||||
raw = image.read(2)
|
||||
clusterOffset = struct.unpack_from("<H", raw)[0]
|
||||
if clusterOffset < 2 or clusterOffset > 65526:
|
||||
break
|
||||
fileOffset = fileOffset + 32
|
||||
continue
|
||||
fileSize = parts[3]
|
||||
self.output.append('"' + str(_id) + '", ' \
|
||||
+ '"' + directories[directoryOffset] + '", ' \
|
||||
+ '"' + filename + '", ' \
|
||||
+ '"' + longFileName + '", ' \
|
||||
+ '"' + allocated + '", ' \
|
||||
+ '"' + str(fileSize) + '", ' \
|
||||
+ '"' + str(accessedYear) + '-' + str(accessedMonth) + '-' + str(accessedDay) + '", ' \
|
||||
+ '"' + str(modifiedYear) + '-' + str(modifiedMonth) + '-' + str(modifiedDay) + 'T' + str(modifiedHours) + ':' + str(modifiedMinutes) + ':' + str(modifiedSeconds) + '", ' \
|
||||
+ '"' + str(createdYear) + '-' + str(createdMonth) + '-' + str(createdDay) + 'T' + str(createdHours) + ':' + str(createdMinutes) + ':' + str(createdSeconds) + '", ' \
|
||||
+ '"' + str(startingCluster) + '"')
|
||||
# if we don't have a LFN:
|
||||
if not fileAttributes == 15:
|
||||
longFileName = ""
|
||||
_id = _id + 1
|
||||
fileOffset = fileOffset + 32
|
||||
|
||||
#=== FUNCTIONS =================================================================
|
||||
|
||||
# This is a private module function only for the other module functions.
|
||||
# Unfortunately, the underscore does not prevent other developers from using
|
||||
# it directly from outside this module. :-(
|
||||
# sphinx-apidoc does ignore it. :-)
|
||||
def _getargs():
|
||||
"""Get command line arguments.
|
||||
|
||||
Parses the command line arguments and return them.
|
||||
(Don't forget to check them in the next steps?)
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple
|
||||
A tuple containing the to the image file,
|
||||
the offset in bytes in the image file calculated from sectors
|
||||
and the output file.
|
||||
|
||||
The default offset is 0.
|
||||
|
||||
For example:
|
||||
( "image.dd", 2048, "output.csv" )
|
||||
|
||||
Raises
|
||||
------
|
||||
Exits if argparse detects an error.
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("image",
|
||||
type=str,
|
||||
help="raw image file (esp.: image.dd)")
|
||||
parser.add_argument("-o",
|
||||
"--offset",
|
||||
type=int,
|
||||
default=0,
|
||||
help="offset in sectors (default=0)")
|
||||
parser.add_argument("-f",
|
||||
"--file",
|
||||
type=str,
|
||||
default="",
|
||||
help='output file (default="")')
|
||||
args = parser.parse_args()
|
||||
|
||||
return (args.image, args.offset * 512, args.file)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# This is a private module function only for the other module functions.
|
||||
# Unfortunately, the underscore does not prevent other developers from using
|
||||
# it directly from outside this class. :-(
|
||||
# sphinx-apidoc does ignore it. :-)
|
||||
def _checkfile(_file):
|
||||
"""Check output file.
|
||||
|
||||
Check if the output file is not existing and the target directory is writable.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
_file : str
|
||||
path to the output file.
|
||||
|
||||
Raises
|
||||
------
|
||||
Exits if the image does not exist or is not readable.
|
||||
"""
|
||||
|
||||
if os.path.isfile(_file):
|
||||
sys.stderr.write("Error: output file already exist!\n")
|
||||
sys.exit(6)
|
||||
|
||||
# Check write permissions if we try to write to the file the first time!
|
||||
|
||||
#=== MAIN ======================================================================
|
||||
|
||||
if __name__ == '__main__':
|
||||
(IMAGE, OFFSET, FILE) = _getargs()
|
||||
OBJECT = Fatlister(IMAGE, OFFSET)
|
||||
if not FILE:
|
||||
OBJECT.printoutput()
|
||||
else:
|
||||
_checkfile(FILE)
|
||||
OBJECT.file = FILE
|
||||
OBJECT.writeoutput()
|
||||
sys.exit(0)
|
||||
|
140
fatlister_wrapper.py
Executable file
140
fatlister_wrapper.py
Executable file
@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3.6
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""List file and directory names in a disk image."""
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
# FILE:
|
||||
# ./fatlister_wrapper.py
|
||||
#
|
||||
# BASIC USAGE:
|
||||
# $ ./fatlister_wrapper.py [-h|--help] [-o|--offset OFFSET] [-f|--file FILENAME] image
|
||||
# OR
|
||||
# $ python fatlister_wrapper.py [-h|--help] [-o|--offset OFFSET] [-f|--file FILENAME] image
|
||||
#
|
||||
# OPTIONS:
|
||||
# -h,
|
||||
# --help show help message and exit
|
||||
# -o OFFSET,
|
||||
# --offset OFFSET offset in sectors (default=0)
|
||||
# -f FILENAME, write output to file (default=unset)
|
||||
# --file FILENAME if unset output goes to stdout
|
||||
# image raw image file (esp. dd)
|
||||
#
|
||||
# EXIT STATES:
|
||||
# 0 = success
|
||||
# 1 = Python version not tested/supported
|
||||
# 2 = image file does not exist
|
||||
# 33 = file is not readable
|
||||
# 3 = offset is not a positive integer
|
||||
# 22 = output file already exist
|
||||
# 23 = output directory is not writable
|
||||
# 4 = wrong or missing data in dictionary
|
||||
# 5 = empty image file
|
||||
# 6 = image file smaller than offset
|
||||
# 7 = invalid vbr signatures
|
||||
#
|
||||
# REQUIREMENTS:
|
||||
# python2.7
|
||||
#
|
||||
# NOTES:
|
||||
# Tested on:
|
||||
# - ArchLinux (64-Bit) + python 2.7.13
|
||||
# - Raspbian GNU/Linux 8.0 (32-Bit) + python 2.7.9
|
||||
# - macOS (10.12.3) + python 2.7.10
|
||||
# - Bash on Ubuntu on Windows 10 (64-Bit) + python 2.7.6
|
||||
# with:
|
||||
# - raw images
|
||||
# - filesystem(s) in partition(s)
|
||||
# - filesystem without a partition
|
||||
# - no partition/no filesystem (empty file)
|
||||
# - invalid filesystem
|
||||
# - FAT12, FAT16 and FAT32
|
||||
#
|
||||
# WARRANTY:
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
# See the LICENSE file for more details.
|
||||
#
|
||||
# HISTORY:
|
||||
# See the CHANGES file for more details.
|
||||
#
|
||||
#===============================================================================
|
||||
|
||||
#=== MODULES ===================================================================
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import fatlister
|
||||
|
||||
#=== INFO ======================================================================
|
||||
|
||||
"""@author: Patrick Neumann
|
||||
@contact: patrick@neumannsland.de
|
||||
@copyright: Copyright (C) 2017, Patrick Neumann
|
||||
@license: GNU General Public License 3.0
|
||||
@date: 2017-05-08
|
||||
@version: 1.0.0
|
||||
@status: Development
|
||||
"""
|
||||
|
||||
#=== CHECKS ====================================================================
|
||||
|
||||
"""Only tested with Python versions 3.6.x!"""
|
||||
|
||||
if sys.version_info < (3, 6, 0):
|
||||
sys.stderr.write("Sorry, your Python version was not tested but may work?\n")
|
||||
sys.exit(1)
|
||||
|
||||
#=== FUNCTIONS =================================================================
|
||||
|
||||
# This is a private module function only for the other module functions.
|
||||
# Unfortunately, the underscore does not prevent other developers from using
|
||||
# it directly from outside this module. :-(
|
||||
# sphinx-apidoc does ignore it. :-)
|
||||
def _getargs():
|
||||
"""Get command line arguments.
|
||||
|
||||
Parses the command line arguments and return them.
|
||||
(Don't forget to check them in the next steps?)
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple
|
||||
A tuple containing the to the image file,
|
||||
the offset in bytes in the image file calculated from sectors
|
||||
and the output file.
|
||||
|
||||
The default offset is 0.
|
||||
|
||||
For example:
|
||||
( "image.dd", 1048576, "/dev/stdout" )
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("image",
|
||||
type=str,
|
||||
help="raw image file (esp.: image.dd)")
|
||||
parser.add_argument("-o",
|
||||
"--offset",
|
||||
type=int,
|
||||
default=0,
|
||||
help="offset in sectors (default=0)")
|
||||
parser.add_argument("-f",
|
||||
"--file",
|
||||
type=str,
|
||||
default="",
|
||||
help="output file (default=/dev/stdout)")
|
||||
args = parser.parse_args()
|
||||
|
||||
return (args.image, args.offset * 512, args.file)
|
||||
|
||||
#=== MAIN ======================================================================
|
||||
|
||||
if __name__ == '__main__':
|
||||
(IMAGE, OFFSET, FILE) = _getargs()
|
||||
OBJECT = fatlister.Fatlister(IMAGE, OFFSET, FILE)
|
||||
sys.exit(0)
|
BIN
images/cf8mbwin.dmg
Executable file
BIN
images/cf8mbwin.dmg
Executable file
Binary file not shown.
0
images/empty.dd
Executable file
0
images/empty.dd
Executable file
BIN
images/fat16.dd
Executable file
BIN
images/fat16.dd
Executable file
Binary file not shown.
65
images/generate_fat16_image
Executable file
65
images/generate_fat16_image
Executable file
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
fallocate -l 32M fat16.dd
|
||||
mkfs.vfat -F 16 -s 2 -n "IMG_4_A2" fat16.dd
|
||||
|
||||
mkdir fat16.loop
|
||||
mount -o loop fat16.dd fat16.loop
|
||||
|
||||
cd fat16.loop
|
||||
|
||||
echo "Short file name." > SFN.TXT
|
||||
echo "Short file name with a leading sigma." > X_SIGMA.TXT
|
||||
echo "Short file name that will be deleted soon." > DELSFN.TXT
|
||||
|
||||
echo "Long file name." > Long_file_name.txt
|
||||
echo "Long file name with spaces." > "LFN with spaces.txt"
|
||||
echo "Long file name that will be deleted soon." > Del_long_file_name.txt
|
||||
|
||||
dd if=/dev/urandom of=BIGFILE.BIN bs=1024 count=1024
|
||||
|
||||
mkdir SDN
|
||||
cd SDN
|
||||
|
||||
mkdir SSUBDN
|
||||
cd SSUBDN
|
||||
|
||||
echo "Another short file name." > SFN2.TXT
|
||||
|
||||
cd ../..
|
||||
|
||||
mkdir Long_directory_name
|
||||
cd Long_directory_name
|
||||
|
||||
mkdir Long_subdirectory_name
|
||||
cd Long_subdirectory_name
|
||||
|
||||
echo "Another long file name." > Long_file_name2.txt
|
||||
|
||||
cd ../..
|
||||
|
||||
mkdir BIGDIR
|
||||
cd BIGDIR
|
||||
|
||||
for id in {01..30} ; do
|
||||
echo "I am in the first cluster of the directory." > FILE${id}.TXT
|
||||
done
|
||||
echo "I am in the second cluster of the directory." > FILE31.TXT
|
||||
|
||||
cd ..
|
||||
|
||||
rm DELSFN.TXT
|
||||
rm Del_long_file_name.txt
|
||||
|
||||
cd ..
|
||||
|
||||
umount fat16.loop
|
||||
rmdir fat16.loop
|
||||
|
||||
chmod 444 fat16.dd
|
||||
|
||||
echo
|
||||
echo "Do not forget to change the X in X_SIGMA.TXT to \x05 with a hex editor!"
|
||||
echo
|
||||
|
||||
exit 0
|
BIN
images/invalid.dd
Executable file
BIN
images/invalid.dd
Executable file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user