Skip to content

Commit 0316358

Browse files
author
Tim Walsh
authored
Merge pull request #36 from timothyryanwalsh/dev-0.6.1
Version 0.6.1
2 parents f700ec8 + 3ce3d9b commit 0316358

File tree

6 files changed

+119
-87
lines changed

6 files changed

+119
-87
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Disk Image Processor
22

33
Analyze disk images and/or create ready-to-ingest SIPs from a directory of disk images and related files.
4-
Version: 0.6.0 (beta)
4+
Version: 0.6.1 (beta)
55

66
## Usage
77

@@ -28,6 +28,8 @@ The destination directory also contains a "reports" directory containing a sub-d
2828
* Text output from "disktype"
2929
* Brunnhilde reports (including logs and reports from clamAV and bulk_extractor)
3030

31+
Optionally, the destination directory may also contain a "files" directory, containing exported logical files from each recognized disk image in the source.
32+
3133
Because "Analysis" mode runs bulk_extractor against each disk, this process can take a while.
3234

3335
### Processing
@@ -94,7 +96,7 @@ Disk Image Processor recognizes which files are disk images by their file extens
9496
* .dd
9597
* .iso
9698

97-
*To add extensions to this list, add them as elements in the tuple inside `file.endswith((".E01", ".000", ".001", ".raw", ".img", ".dd", ".iso"))` on line 369 of `diskimageprocessor.py` and/or line 276 of `diskimageanalyzer.py`.*
99+
*To add extensions to this list, add them as elements in the tuple inside `file.endswith((".E01", ".000", ".001", ".raw", ".img", ".dd", ".iso"))` on line 353 of `diskimageprocessor.py` and/or line 261 of `diskimageanalyzer.py`.*
98100

99101
## Installation and dependencies
100102

design.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ def setupUi(self, MainWindow):
6262
self.label = QtGui.QLabel(self.centralwidget)
6363
self.label.setObjectName(_fromUtf8("label"))
6464
self.verticalLayout.addWidget(self.label)
65+
self.label_8 = QtGui.QLabel(self.centralwidget)
66+
self.label_8.setObjectName(_fromUtf8("label_8"))
67+
self.verticalLayout.addWidget(self.label_8)
68+
self.checkBox_2 = QtGui.QCheckBox(self.centralwidget)
69+
self.checkBox_2.setObjectName(_fromUtf8("checkBox_2"))
70+
self.verticalLayout.addWidget(self.checkBox_2)
71+
self.label_9 = QtGui.QLabel(self.centralwidget)
72+
self.label_9.setObjectName(_fromUtf8("label_9"))
73+
self.verticalLayout.addWidget(self.label_9)
6574
self.checkBox = QtGui.QCheckBox(self.centralwidget)
6675
self.checkBox.setObjectName(_fromUtf8("checkBox"))
6776
self.verticalLayout.addWidget(self.checkBox)
@@ -120,11 +129,14 @@ def retranslateUi(self, MainWindow):
120129
self.label_6.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:600;\">Mode</span></p></body></html>", None))
121130
self.analysisBtn.setText(_translate("MainWindow", "Analysis", None))
122131
self.processingBtn.setText(_translate("MainWindow", "Processing", None))
123-
self.label.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:600;\">General options (Processing mode)</span></p></body></html>", None))
132+
self.label.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:600;\">General options</span></p></body></html>", None))
133+
self.label_8.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:600; font-style:italic;\">Analysis</span></p></body></html>", None))
134+
self.checkBox_2.setText(_translate("MainWindow", "Retain logical files at end of process", None))
135+
self.label_9.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:600; font-style:italic;\">Processing</span></p></body></html>", None))
124136
self.checkBox.setText(_translate("MainWindow", "Bag SIPs", None))
125137
self.filesonlyBtn.setText(_translate("MainWindow", "Make SIPs from logical files only (no disk images)", None))
126138
self.checkBox_3.setText(_translate("MainWindow", "Run bulk_extractor", None))
127-
self.label_5.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:600;\">File export options (Processing mode)</span></p></body></html>", None))
139+
self.label_5.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:600;\">File export options</span></p></body></html>", None))
128140
self.exportAllBtn.setText(_translate("MainWindow", "Include deleted/unallocated files (except HFS-formatted disks)", None))
129141
self.resforksBtn.setText(_translate("MainWindow", "Include AppleDouble resource forks (HFS-formatted disks)", None))
130142
self.label_7.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-weight:600;\">Detailed output</span></p></body></html>", None))

design.ui

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,28 @@
8787
<item>
8888
<widget class="QLabel" name="label">
8989
<property name="text">
90-
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;General options (Processing mode)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
90+
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;General options&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
91+
</property>
92+
</widget>
93+
</item>
94+
<item>
95+
<widget class="QLabel" name="label_8">
96+
<property name="text">
97+
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; font-style:italic;&quot;&gt;Analysis&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
98+
</property>
99+
</widget>
100+
</item>
101+
<item>
102+
<widget class="QCheckBox" name="checkBox_2">
103+
<property name="text">
104+
<string>Retain logical files at end of process</string>
105+
</property>
106+
</widget>
107+
</item>
108+
<item>
109+
<widget class="QLabel" name="label_9">
110+
<property name="text">
111+
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; font-style:italic;&quot;&gt;Processing&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
91112
</property>
92113
</widget>
93114
</item>
@@ -115,7 +136,7 @@
115136
<item>
116137
<widget class="QLabel" name="label_5">
117138
<property name="text">
118-
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;File export options (Processing mode)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
139+
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;File export options&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
119140
</property>
120141
</widget>
121142
</item>

diskimageanalyzer.py

Lines changed: 68 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def time_to_int(str_time):
4141
dt = time.mktime(datetime.datetime.strptime(str_time, "%Y-%m-%dT%H:%M:%S").timetuple())
4242
return dt
4343

44-
def write_to_spreadsheet(disk_result, spreadsheet_path):
44+
def write_to_spreadsheet(disk_result, spreadsheet_path, exportall):
4545
"""append info for current disk to analysis CSV"""
4646

4747
# open description spreadsheet
@@ -52,7 +52,6 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
5252
number_files = 0
5353
total_bytes = 0
5454
mtimes = []
55-
atimes = []
5655
ctimes = []
5756
crtimes = []
5857

@@ -71,6 +70,12 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
7170
# skip directories and links
7271
if obj.name_type != "r":
7372
continue
73+
74+
# skip unallocated if args.exportall is False
75+
if exportall == False:
76+
if obj.unalloc:
77+
if obj.unalloc == 1:
78+
continue
7479

7580
# gather info
7681
number_files += 1
@@ -82,13 +87,6 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
8287
except:
8388
pass
8489

85-
try:
86-
atime = obj.atime
87-
atime = str(atime)
88-
atimes.append(atime)
89-
except:
90-
pass
91-
9290
try:
9391
ctime = obj.ctime
9492
ctime = str(ctime)
@@ -106,7 +104,7 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
106104
total_bytes += obj.filesize
107105

108106
# filter 'None' values from date lists
109-
for date_list in mtimes, atimes, ctimes, crtimes:
107+
for date_list in mtimes, ctimes, crtimes:
110108
while 'None' in date_list:
111109
date_list.remove('None')
112110

@@ -122,8 +120,6 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
122120
# determine earliest and latest MAC dates from lists
123121
date_earliest_m = ""
124122
date_latest_m = ""
125-
date_earliest_a = ""
126-
date_latest_a = ""
127123
date_earliest_c = ""
128124
date_latest_c = ""
129125
date_earliest_cr = ""
@@ -133,9 +129,6 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
133129
if mtimes:
134130
date_earliest_m = min(mtimes)
135131
date_latest_m = max(mtimes)
136-
if atimes:
137-
date_earliest_a = min(atimes)
138-
date_latest_a = max(atimes)
139132
if ctimes:
140133
date_earliest_c = min(ctimes)
141134
date_latest_c = max(ctimes)
@@ -144,7 +137,6 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
144137
date_latest_cr = max(crtimes)
145138

146139
# determine which set of dates to use (logic: use set with earliest start date)
147-
use_atimes = False
148140
use_ctimes = False
149141
use_crtimes = False
150142

@@ -153,29 +145,19 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
153145
date_latest_m = "N/A"
154146
date_to_use = date_earliest_m # default to date modified
155147

156-
if date_earliest_a:
157-
if date_earliest_a < date_to_use:
158-
date_to_use = date_earliest_a
159-
use_atimes = True
160148
if date_earliest_c:
161149
if date_earliest_c < date_to_use:
162150
date_to_use = date_earliest_c
163-
use_atimes = False
164151
use_ctimes = True
165152
if date_earliest_cr:
166153
if date_earliest_cr < date_to_use:
167154
date_to_use = date_earliest_cr
168-
use_atimes = False
169155
use_ctimes = False
170156
use_crtimes = True
171157

172158
# store date_earliest and date_latest values based on datetype & record datetype
173159
date_type = 'Modified'
174-
if use_atimes == True:
175-
date_earliest = date_earliest_a[:10]
176-
date_latest = date_latest_a[:10]
177-
date_type = 'Accessed'
178-
elif use_ctimes == True:
160+
if use_ctimes == True:
179161
date_earliest = date_earliest_c[:10]
180162
date_latest = date_latest_c[:10]
181163
date_type = 'Created'
@@ -250,7 +232,9 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
250232

251233
# parse arguments
252234
parser = argparse.ArgumentParser()
253-
parser.add_argument("-f", "--forensic", help="Use fiwalk and tsk_recover", action="store_true")
235+
parser.add_argument("-e", "--exportall", help="Export all (not only allocated) with tsk_recover", action="store_true")
236+
parser.add_argument("-k", "--keepfiles", help="Retain exported logical files from each disk", action="store_true")
237+
parser.add_argument("-r", "--resforks", help="Export AppleDouble resource forks from HFS-formatted disks", action="store_true")
254238
parser.add_argument("source", help="Path to folder containing disk images")
255239
parser.add_argument("destination", help="Output destination")
256240
args = parser.parse_args()
@@ -262,8 +246,9 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
262246
if not os.path.exists(destination):
263247
os.makedirs(destination)
264248
diskimages_dir = os.path.join(destination, 'diskimages')
249+
files_dir = os.path.join(destination, 'files')
265250
results_dir = os.path.join(destination, 'reports')
266-
for new_dir in diskimages_dir, results_dir:
251+
for new_dir in diskimages_dir, files_dir, results_dir:
267252
os.makedirs(new_dir)
268253

269254
# make list for unanalyzed disks
@@ -334,16 +319,23 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
334319
try:
335320
subprocess.check_output(['fiwalk', '-X', fiwalk_file, diskimage])
336321
except subprocess.CalledProcessError as e:
337-
logandprint('ERROR: Fiwalk could not create DFXML for disk. STDERR: %s' % (e.output))
322+
print('ERROR: Fiwalk could not create DFXML for disk. STDERR: %s' % (e.output))
338323

339324
# carve files
340-
temp_dir = os.path.join(disk_dir, 'temp')
341-
if not os.path.exists(temp_dir):
342-
os.makedirs(temp_dir)
343-
try:
344-
subprocess.check_output(['tsk_recover', '-a', diskimage, temp_dir])
345-
except subprocess.CalledProcessError as e:
346-
logandprint('ERROR: tsk_recover could not carve allocated files from disk. STDERR: %s' % (e.output))
325+
disk_files_dir = os.path.join(files_dir, file)
326+
if not os.path.exists(disk_files_dir):
327+
os.makedirs(disk_files_dir)
328+
# carve allocated or all files depending on option selected
329+
if args.exportall == True:
330+
try:
331+
subprocess.check_output(['tsk_recover', '-e', diskimage, disk_files_dir])
332+
except subprocess.CalledProcessError as e:
333+
print('ERROR: tsk_recover could not carve all files from disk. STDERR: %s' % (e.output))
334+
else:
335+
try:
336+
subprocess.check_output(['tsk_recover', '-a', diskimage, disk_files_dir])
337+
except subprocess.CalledProcessError as e:
338+
print('ERROR: tsk_recover could not carve allocated files from disk. STDERR: %s' % (e.output))
347339

348340
# rewrite last modified dates of carved files based on values in DFXML
349341
for (event, obj) in Objects.iterparse(fiwalk_file):
@@ -385,15 +377,16 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
385377
continue
386378

387379
# rewrite last modified date of corresponding file in objects/files
388-
exported_filepath = os.path.join(temp_dir, dfxml_filename)
380+
exported_filepath = os.path.join(disk_files_dir, dfxml_filename)
389381
if os.path.isfile(exported_filepath):
390382
os.utime(exported_filepath, (dfxml_filedate, dfxml_filedate))
391383

392384
# run brunnhilde
393-
subprocess.call("brunnhilde.py -zwb '%s' '%s' brunnhilde" % (temp_dir, disk_dir), shell=True)
385+
subprocess.call("brunnhilde.py -zwb '%s' '%s' brunnhilde" % (disk_files_dir, disk_dir), shell=True)
394386

395-
# remove tmpdir
396-
shutil.rmtree(temp_dir)
387+
# remove disk_files_dir unless keepfiles option selected
388+
if args.keepfiles == False:
389+
shutil.rmtree(disk_files_dir)
397390

398391
elif ('hfs' in disk_fs.lower()) and ('hfs+' not in disk_fs.lower()):
399392
# mount disk image
@@ -404,14 +397,32 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
404397
try:
405398
subprocess.call("cd /mnt/diskid/ && python3 /usr/share/ccatools/diskimageprocessor/walk_to_dfxml.py > '%s'" % (dfxml_file), shell=True)
406399
except:
407-
logandprint('ERROR: walk_to_dfxml.py unable to generate DFXML for disk %s' % (diskimage))
400+
print('ERROR: walk_to_dfxml.py unable to generate DFXML for disk %s' % (diskimage))
408401

409402
# run brunnhilde
410403
subprocess.call("brunnhilde.py -zwb /mnt/diskid/ '%s' brunnhilde" % (disk_dir), shell=True)
411404

412405
# unmount disk image
413406
subprocess.call('sudo umount /mnt/diskid', shell=True)
414407

408+
# export files to disk_files_dir if keepfiles selected
409+
if args.keepfiles == True:
410+
disk_files_dir = os.path.join(files_dir, file)
411+
if not os.path.exists(disk_files_dir):
412+
os.makedirs(disk_files_dir)
413+
# carve with or without resource forks depending on option selected
414+
if args.resforks == True:
415+
try:
416+
subprocess.check_output(['bash', '/usr/share/hfsexplorer/bin/unhfs', '-v', '-resforks', 'APPLEDOUBLE', '-o', disk_files_dir, diskimage])
417+
except subprocess.CalledProcessError as e:
418+
print('ERROR: HFS Explorer could not carve the following files from image: %s' % (e.output))
419+
else:
420+
try:
421+
subprocess.check_output(['bash', '/usr/share/hfsexplorer/bin/unhfs', '-v', '-o', disk_files_dir, diskimage])
422+
except subprocess.CalledProcessError as e:
423+
print('ERROR: HFS Explorer could not carve the following files from image: %s' % (e.output))
424+
425+
415426
elif 'udf' in disk_fs.lower():
416427
# mount image
417428
subprocess.call("sudo mount -t udf -o loop '%s' /mnt/diskid/" % (diskimage), shell=True)
@@ -421,32 +432,34 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
421432
try:
422433
subprocess.call("cd /mnt/diskid/ && python3 /usr/share/ccatools/diskimageprocessor/walk_to_dfxml.py > '%s'" % (dfxml_file), shell=True)
423434
except:
424-
logandprint('ERROR: walk_to_dfxml.py unable to generate DFXML for disk %s' % (diskimage))
435+
print('ERROR: walk_to_dfxml.py unable to generate DFXML for disk %s' % (diskimage))
425436

426437
# write files to tempdir
427-
temp_dir = os.path.join(disk_dir, 'temp')
428-
shutil.copytree('/mnt/diskid/', temp_dir, symlinks=False, ignore=None)
438+
disk_files_dir = os.path.join(files_dir, file)
439+
shutil.copytree('/mnt/diskid/', disk_files_dir, symlinks=False, ignore=None)
429440

430-
# change file permissions in temp_dir
431-
subprocess.call("find '%s' -type d -exec chmod 755 {} \;" % (temp_dir), shell=True)
432-
subprocess.call("find '%s' -type f -exec chmod 644 {} \;" % (temp_dir), shell=True)
441+
# change file permissions in disk_files_dir
442+
subprocess.call("find '%s' -type d -exec chmod 755 {} \;" % (disk_files_dir), shell=True)
443+
subprocess.call("find '%s' -type f -exec chmod 644 {} \;" % (disk_files_dir), shell=True)
433444

434445
# unmount disk image
435446
subprocess.call('sudo umount /mnt/diskid', shell=True)
436447

437448
# run brunnhilde
438-
subprocess.call("brunnhilde.py -zwb '%s' '%s' brunnhilde" % (temp_dir, disk_dir), shell=True)
449+
subprocess.call("brunnhilde.py -zwb '%s' '%s' brunnhilde" % (disk_files_dir, disk_dir), shell=True)
439450

440-
# delete tempdir
441-
shutil.rmtree(temp_dir)
451+
# remove disk_files_dir unless keepfiles option selected
452+
if args.keepfiles == False:
453+
shutil.rmtree(disk_files_dir)
442454

443455
else:
444456
# add disk to unanalyzed list
445-
unanalyzed.append(diskimage)
446-
457+
unanalyzed.append(diskimage)
447458

448-
# delete disk images
459+
# delete temp directories
449460
shutil.rmtree(diskimages_dir)
461+
if args.keepfiles == False:
462+
shutil.rmtree(files_dir)
450463

451464
# create analysis spreadsheet
452465
spreadsheet_path = os.path.join(destination, 'analysis.csv')
@@ -462,7 +475,7 @@ def write_to_spreadsheet(disk_result, spreadsheet_path):
462475
# add info to description spreadsheet
463476
for item in sorted(os.listdir(results_dir)):
464477
disk_result = os.path.join(results_dir, item)
465-
write_to_spreadsheet(disk_result, spreadsheet_path)
478+
write_to_spreadsheet(disk_result, spreadsheet_path, args.exportall)
466479

467480
# write closing message
468481
if unanalyzed:

0 commit comments

Comments
 (0)