From 8d696e49c9018bf84ac3392c201517582b6b5f57 Mon Sep 17 00:00:00 2001 From: BrunoFischerGermany Date: Thu, 13 Mar 2025 22:16:14 +0100 Subject: [PATCH 1/6] =?UTF-8?q?A=20check=20mark=20with=20the=20designation?= =?UTF-8?q?=20=E2=80=9Ccreate=20an=20un-compressed=20(raw)=20acquisition?= =?UTF-8?q?=20.dmg-file=E2=80=9D=20inserted,=20default=20-=20False=20hdiut?= =?UTF-8?q?il=20convert=20-format=20=E2=80=9CUDRO=E2=80=9D=20if=20ticked,?= =?UTF-8?q?=20otherwise=20=E2=80=9CUDZO=E2=80=9D=20so=20X-Ways=20Forensics?= =?UTF-8?q?=20can=20also=20open=20the=20created=20.dmg=20file.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- acquisition/abstract.py | 7 +++++-- fuji.py | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/acquisition/abstract.py b/acquisition/abstract.py index 8cc845c..8befef2 100644 --- a/acquisition/abstract.py +++ b/acquisition/abstract.py @@ -16,7 +16,6 @@ from meta import AUTHOR, VERSION from shared.utils import command_to_properties, lines_to_properties - @dataclass class Parameters: case: str = "" @@ -306,8 +305,12 @@ def _generate_dmg(self, report: Report) -> bool: print("\nConverting", self.temporary_path, "->", self.output_path) sparseimage = f"{self.temporary_path}" dmg = f"{self.output_path}" + if params.compressed: + dmgformat = "UDRO" + else: + dmgformat = "UDZO" result = self._run_status( - ["hdiutil", "convert", sparseimage, "-format", "UDZO", "-o", dmg] + ["hdiutil", "convert", sparseimage, "-format", dmgformat, "-o", dmg] ) success = result == 0 diff --git a/fuji.py b/fuji.py index cdfb839..8cb82ea 100644 --- a/fuji.py +++ b/fuji.py @@ -349,6 +349,12 @@ def __init__(self): description_text.SetLabelMarkup(description_label) self.description_texts.append(description_text) + # Sound checkbox + self.compressed_checkbox = wx.CheckBox( + panel, label="Create an un-compressed (raw) acquisition .dmg-file" + ) + self.compressed_checkbox.SetValue(False) + # Sound checkbox self.sound_checkbox = wx.CheckBox( panel, label="Play loud sound when acquisition is completed" @@ -402,6 +408,7 @@ def __init__(self): vbox.Add(description_text, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 10) vbox.Add((0, 20)) + vbox.Add(self.compressed_checkbox, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, 10) vbox.Add(self.sound_checkbox, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, 10) vbox.Add(continue_btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, 20) panel.SetSizer(vbox) @@ -449,6 +456,7 @@ def on_continue(self, event): PARAMS.tmp = Path(self.tmp_picker.GetPath().strip()) PARAMS.destination = Path(self.destination_picker.GetPath().strip()) PARAMS.sound = self.sound_checkbox.GetValue() + PARAMS.compressed = self.compressed_checkbox.GetValue() self.method = METHODS[self.method_choice.GetSelection()] self.Hide() @@ -517,6 +525,7 @@ def update_overview(self): "Temp image location": PARAMS.tmp, "DMG destination": PARAMS.destination, "Acquisition method": INPUT_WINDOW.method.name, + "Compressed Acquisition": "No" if PARAMS.compressed else "Yes", "Play sound": "Yes" if PARAMS.sound else "No", } From 730b9896f5843e14d22a1ad73cf2433a3f33ad96 Mon Sep 17 00:00:00 2001 From: Bruno Fischer Date: Thu, 13 Mar 2025 22:22:45 +0100 Subject: [PATCH 2/6] Update fuji.py --- fuji.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuji.py b/fuji.py index 8cb82ea..9a278db 100644 --- a/fuji.py +++ b/fuji.py @@ -349,7 +349,7 @@ def __init__(self): description_text.SetLabelMarkup(description_label) self.description_texts.append(description_text) - # Sound checkbox + # compressed dmg file checkbox self.compressed_checkbox = wx.CheckBox( panel, label="Create an un-compressed (raw) acquisition .dmg-file" ) From 3a96ce54cc4db06c034bed85fd21a07e760869ae Mon Sep 17 00:00:00 2001 From: BrunoFischerGermany Date: Sat, 15 Mar 2025 20:33:37 +0100 Subject: [PATCH 3/6] Add Hook for uncompressed (UDRW) dmg-Imagefile for using dmg in X-Ways Forensics Add Hook for delete temporary image file --- acquisition/abstract.py | 82 +++++++++++++++++++++++++---------------- fuji.py | 15 ++++++-- 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/acquisition/abstract.py b/acquisition/abstract.py index 8befef2..0166dee 100644 --- a/acquisition/abstract.py +++ b/acquisition/abstract.py @@ -84,7 +84,7 @@ def _limited_read(self, file: IO[str], limit: int, encoding: str) -> str: return "" def _create_shell_process( - self, arguments: List[str], awake=True, tee: Path = None + self, arguments: List[str], awake=True, tee: Path = None ) -> Popen[str]: if awake: arguments = ["caffeinate", "-dimsu"] + arguments @@ -111,7 +111,7 @@ def _run_silent(self, arguments: List[str], awake=True) -> Tuple[int, str]: return p.returncode, p.stdout def _run_process( - self, arguments: List[str], awake=True, buffer_size=1024000, tee: Path = None + self, arguments: List[str], awake=True, buffer_size=1024000, tee: Path = None ) -> Tuple[int, str]: # Run a process in plain sight. Return its status code and output. p = self._create_shell_process(arguments, awake=awake, tee=tee) @@ -135,7 +135,7 @@ def _run_process( return p.returncode, output def _run_status( - self, arguments: List[str], awake=True, buffer_size=1024000, tee: Path = None + self, arguments: List[str], awake=True, buffer_size=1024000, tee: Path = None ) -> int: # Run a process in plain sight. Return its status code. p = self._create_shell_process(arguments, awake=awake, tee=tee) @@ -263,11 +263,11 @@ def _create_temporary_image(self, report: Report) -> bool: success = result == 0 and len(volume_lines) > 0 if success: container_line = container_lines[0] - parts = re.split("\s+", container_line, maxsplit=2) + parts = re.split(r"\s+", container_line, maxsplit=2) self.temporary_container = parts[0] mount_line = volume_lines[0] - parts = re.split("\s+", mount_line, maxsplit=2) + parts = re.split(r"\s+", mount_line, maxsplit=2) self.temporary_volume = parts[0] self.temporary_mount = parts[2] @@ -277,6 +277,20 @@ def _create_temporary_image(self, report: Report) -> bool: return success + def _delete_temporary_image(self, report: Report) -> bool: + params = report.parameters + output_directory = params.tmp / params.image_name + self.temporary_path = output_directory / f"{params.image_name}.sparseimage" + if os.path.exists(self.temporary_path): + if params.state_temporary_image: + try: + os.remove(self.temporary_path) + print(f"Temporary image file {self.temporary_path} deleted.") + return True + except Exception as e: + print(f"Error while delete temporary image file {self.temporary_path}: {e}") + print(f"{self.temporary_path}") + def _detach_temporary_image(self, delay=10, interval=5, attempts=20) -> bool: print("\nWaiting to detach temporary image...") time.sleep(delay) @@ -306,7 +320,7 @@ def _generate_dmg(self, report: Report) -> bool: sparseimage = f"{self.temporary_path}" dmg = f"{self.output_path}" if params.compressed: - dmgformat = "UDRO" + dmgformat = "UDRW" else: dmgformat = "UDZO" result = self._run_status( @@ -376,9 +390,9 @@ def _write_report(self, report: Report) -> None: output_files = [] if len(report.output_files): output_files = [ - separator, - "Generated files:", - ] + [f" - {file}" for file in report.output_files] + separator, + "Generated files:", + ] + [f" - {file}" for file in report.output_files] hashes = [] if report.result: @@ -392,28 +406,30 @@ def _write_report(self, report: Report) -> None: with open(self.output_report, "w") as output: for line in ( - [ - "Fuji - Forensic Unattended Juicy Imaging", - f"Version {VERSION} by {AUTHOR}", - "Acquisition log", - separator, - f"Case name: {report.parameters.case}", - f"Examiner: {report.parameters.examiner}", - f"Notes: {report.parameters.notes}", - separator, - f"Start time: {report.start_time}", - f"End time: {report.end_time}", - f"Source: {report.parameters.source}", - f"Acquisition method: {report.method.name}", - separator, - report.hardware_info, - separator, - "Volume:", - "", - report.path_details.disk_info, - ] - + output_files - + hashes + [ + "Fuji - Forensic Unattended Juicy Imaging", + f"Version {VERSION} by {AUTHOR}", + "Acquisition log", + separator, + f"Case name: {report.parameters.case}", + f"Examiner: {report.parameters.examiner}", + f"Notes: {report.parameters.notes}", + separator, + f"Start time: {report.start_time}", + f"End time: {report.end_time}", + f"Source: {report.parameters.source}", + f"Acquisition method: {report.method.name}", + f"Imagefile type: {'uncompressed (UDRW)' if report.parameters.compressed else 'compressed (UDZO)'}", + f"temporary image file: {'delete' if report.parameters.state_temporary_image else 'keep'} after acquisition", + separator, + report.hardware_info, + separator, + "Volume:", + "", + report.path_details.disk_info, + ] + + output_files + + hashes ): output.write(line + "\n") @@ -431,8 +447,10 @@ def _dmg_and_hash(self, report: Report) -> Report: report.success = True report.end_time = datetime.now() - self._write_report(report) + _ = self._delete_temporary_image(report) + self._write_report(report) + print("\nAcquisition completed!") return report diff --git a/fuji.py b/fuji.py index 9a278db..5678202 100644 --- a/fuji.py +++ b/fuji.py @@ -133,7 +133,7 @@ def __init__(self, parent): if not line.startswith("/dev/disk"): continue identifier, size, used, free, _, _, _, _, mount_point = re.split( - "\s+", line, maxsplit=8 + r"\s+", line, maxsplit=8 ) short_identifier = identifier[5:] mount_info[short_identifier] = DiskSpaceInfo( @@ -351,10 +351,16 @@ def __init__(self): # compressed dmg file checkbox self.compressed_checkbox = wx.CheckBox( - panel, label="Create an un-compressed (raw) acquisition .dmg-file" + panel, label="Create an un-compressed (UDRW) acquisition .dmg-file" ) self.compressed_checkbox.SetValue(False) + # keep/delete temporary sparseimage file after acquisition + self.state_temporary_image_checkbox = wx.CheckBox( + panel, label="Delete temporary image file after acquisition" + ) + self.state_temporary_image_checkbox.SetValue(False) + # Sound checkbox self.sound_checkbox = wx.CheckBox( panel, label="Play loud sound when acquisition is completed" @@ -409,6 +415,7 @@ def __init__(self): vbox.Add((0, 20)) vbox.Add(self.compressed_checkbox, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, 10) + vbox.Add(self.state_temporary_image_checkbox, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, 10) vbox.Add(self.sound_checkbox, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, 10) vbox.Add(continue_btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, 20) panel.SetSizer(vbox) @@ -457,6 +464,7 @@ def on_continue(self, event): PARAMS.destination = Path(self.destination_picker.GetPath().strip()) PARAMS.sound = self.sound_checkbox.GetValue() PARAMS.compressed = self.compressed_checkbox.GetValue() + PARAMS.state_temporary_image = self.state_temporary_image_checkbox.GetValue() self.method = METHODS[self.method_choice.GetSelection()] self.Hide() @@ -525,7 +533,8 @@ def update_overview(self): "Temp image location": PARAMS.tmp, "DMG destination": PARAMS.destination, "Acquisition method": INPUT_WINDOW.method.name, - "Compressed Acquisition": "No" if PARAMS.compressed else "Yes", + "Imagefile type": "uncompressed (UDRW)" if PARAMS.compressed else "compressed (UDZO)", + "Temporary image file": f"{'delete' if PARAMS.state_temporary_image else 'keep'} after acquisition", "Play sound": "Yes" if PARAMS.sound else "No", } From 2837f78ec4a8b42946883499dee1926fe8564d21 Mon Sep 17 00:00:00 2001 From: BrunoFischerGermany Date: Sat, 15 Mar 2025 21:18:03 +0100 Subject: [PATCH 4/6] wrong format --- acquisition/abstract.py | 48 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/acquisition/abstract.py b/acquisition/abstract.py index 0166dee..6a23ee9 100644 --- a/acquisition/abstract.py +++ b/acquisition/abstract.py @@ -406,30 +406,30 @@ def _write_report(self, report: Report) -> None: with open(self.output_report, "w") as output: for line in ( - [ - "Fuji - Forensic Unattended Juicy Imaging", - f"Version {VERSION} by {AUTHOR}", - "Acquisition log", - separator, - f"Case name: {report.parameters.case}", - f"Examiner: {report.parameters.examiner}", - f"Notes: {report.parameters.notes}", - separator, - f"Start time: {report.start_time}", - f"End time: {report.end_time}", - f"Source: {report.parameters.source}", - f"Acquisition method: {report.method.name}", - f"Imagefile type: {'uncompressed (UDRW)' if report.parameters.compressed else 'compressed (UDZO)'}", - f"temporary image file: {'delete' if report.parameters.state_temporary_image else 'keep'} after acquisition", - separator, - report.hardware_info, - separator, - "Volume:", - "", - report.path_details.disk_info, - ] - + output_files - + hashes + [ + "Fuji - Forensic Unattended Juicy Imaging", + f"Version {VERSION} by {AUTHOR}", + "Acquisition log", + separator, + f"Case name: {report.parameters.case}", + f"Examiner: {report.parameters.examiner}", + f"Notes: {report.parameters.notes}", + separator, + f"Start time: {report.start_time}", + f"End time: {report.end_time}", + f"Source: {report.parameters.source}", + f"Acquisition method: {report.method.name}", + f"Imagefile type: {'uncompressed (UDRW)' if report.parameters.compressed else 'compressed (UDZO)'}", + f"temporary image file: {'delete' if report.parameters.state_temporary_image else 'keep'} after acquisition", + separator, + report.hardware_info, + separator, + "Volume:", + "", + report.path_details.disk_info, + ] + + output_files + + hashes ): output.write(line + "\n") From 4b9a731023bd042d2b0b98a992af41ee8b458e9b Mon Sep 17 00:00:00 2001 From: BrunoFischerGermany Date: Sat, 15 Mar 2025 21:21:28 +0100 Subject: [PATCH 5/6] wrong format --- acquisition/abstract.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/acquisition/abstract.py b/acquisition/abstract.py index 6a23ee9..8128d51 100644 --- a/acquisition/abstract.py +++ b/acquisition/abstract.py @@ -84,7 +84,7 @@ def _limited_read(self, file: IO[str], limit: int, encoding: str) -> str: return "" def _create_shell_process( - self, arguments: List[str], awake=True, tee: Path = None + self, arguments: List[str], awake=True, tee: Path = None ) -> Popen[str]: if awake: arguments = ["caffeinate", "-dimsu"] + arguments @@ -111,7 +111,7 @@ def _run_silent(self, arguments: List[str], awake=True) -> Tuple[int, str]: return p.returncode, p.stdout def _run_process( - self, arguments: List[str], awake=True, buffer_size=1024000, tee: Path = None + self, arguments: List[str], awake=True, buffer_size=1024000, tee: Path = None ) -> Tuple[int, str]: # Run a process in plain sight. Return its status code and output. p = self._create_shell_process(arguments, awake=awake, tee=tee) @@ -135,7 +135,7 @@ def _run_process( return p.returncode, output def _run_status( - self, arguments: List[str], awake=True, buffer_size=1024000, tee: Path = None + self, arguments: List[str], awake=True, buffer_size=1024000, tee: Path = None ) -> int: # Run a process in plain sight. Return its status code. p = self._create_shell_process(arguments, awake=awake, tee=tee) From 7c30e9d1169af32f3f95ddde69cb376c9f44ee74 Mon Sep 17 00:00:00 2001 From: BrunoFischerGermany Date: Sat, 15 Mar 2025 21:23:54 +0100 Subject: [PATCH 6/6] wrong format --- acquisition/abstract.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/acquisition/abstract.py b/acquisition/abstract.py index 8128d51..a7c3e42 100644 --- a/acquisition/abstract.py +++ b/acquisition/abstract.py @@ -390,9 +390,9 @@ def _write_report(self, report: Report) -> None: output_files = [] if len(report.output_files): output_files = [ - separator, - "Generated files:", - ] + [f" - {file}" for file in report.output_files] + separator, + "Generated files:", + ] + [f" - {file}" for file in report.output_files] hashes = [] if report.result: