Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8425 homebrew #7730

Draft
wants to merge 36 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ff68786
new branch
gdicristofaro Oct 14, 2022
2c4f2d7
env vars
gdicristofaro Oct 14, 2022
f7aef5a
updates
gdicristofaro Oct 14, 2022
4b9cdf6
fixes for jython loading
gdicristofaro Oct 17, 2022
d8c4953
updates and fixes
gdicristofaro Oct 17, 2022
2ce70f0
updates
gdicristofaro Oct 17, 2022
eb3dd90
snap updates; needs work especially for classic environment
gdicristofaro Oct 18, 2022
61e0f02
updates for photorec
gdicristofaro Oct 18, 2022
cc43a28
working classic snap
gdicristofaro Oct 18, 2022
c6b2aed
homebrew
gdicristofaro Oct 18, 2022
0d0c2ab
work towards homebrew
gdicristofaro Oct 19, 2022
0625220
fixes
gdicristofaro Oct 19, 2022
f5b4b2c
updated homebrew formula
gdicristofaro Oct 19, 2022
3830ea7
sha256 fix
gdicristofaro Oct 19, 2022
34b247b
debug code
gdicristofaro Oct 21, 2022
1f74d98
got gstreamer working with homebrew
gdicristofaro Oct 27, 2022
b3d752d
work on libheif
gdicristofaro Oct 27, 2022
77bd89b
updates
gdicristofaro Oct 28, 2022
f4ae2db
snap gstreamer integration working
gdicristofaro Oct 28, 2022
c99d8e7
improvements to mac recipe
gdicristofaro Oct 28, 2022
96191c4
changes to unix_setup for execution
gdicristofaro Oct 28, 2022
1799619
getting libheif working on mac and linux
gdicristofaro Oct 31, 2022
b0e3034
snapcraft script update
gdicristofaro Nov 22, 2022
4ceab0e
script to update homebrew file
gdicristofaro Nov 22, 2022
8ca5a5d
fix to get right tsk libs
gdicristofaro Nov 22, 2022
948f7d3
readme updates
gdicristofaro Nov 22, 2022
6a9c0eb
Merge branch 'develop' of github.com:sleuthkit/autopsy into 8425_linu…
gdicristofaro Jan 29, 2023
e5e80ef
updates for 4.20.0
gdicristofaro Jan 29, 2023
c751cf4
updates to snapcraft
gdicristofaro Jan 30, 2023
2a439b4
remove homebrew
gdicristofaro Jan 30, 2023
731a0e6
added homebrew to branch
gdicristofaro Jan 30, 2023
bc239fe
Merge branch 'develop' of github.com:sleuthkit/autopsy into 8425-snap
gdicristofaro Jun 27, 2023
2a70d97
Merge branch 'develop' of github.com:sleuthkit/autopsy into 8425-home…
gdicristofaro Jun 27, 2023
304883b
updates for snap and javafx
gdicristofaro Jun 27, 2023
3ab11f3
Merge branch '8425-snap' of github.com:gdicristofaro/autopsy into 842…
gdicristofaro Jun 27, 2023
a9f82df
work towards homebrew
gdicristofaro Jun 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -632,23 +632,18 @@ private synchronized Path createOutputDirectoryForCase(Path providedPath) throws
*/
public static File locateExecutable() throws IngestModule.IngestModuleException {
File exeFile;
Path execName;
String photorec_linux_directory = "/usr/bin";
if (PlatformUtil.isWindowsOS()) {
execName = Paths.get(PHOTOREC_DIRECTORY, PHOTOREC_SUBDIRECTORY, PHOTOREC_EXECUTABLE);
Path execName = Paths.get(PHOTOREC_DIRECTORY, PHOTOREC_SUBDIRECTORY, PHOTOREC_EXECUTABLE);
exeFile = InstalledFileLocator.getDefault().locate(execName.toString(), PhotoRecCarverFileIngestModule.class.getPackage().getName(), false);
} else {
File usrBin = new File("/usr/bin/photorec");
File usrLocalBin = new File("/usr/local/bin/photorec");
if (usrBin.canExecute() && usrBin.exists() && !usrBin.isDirectory()) {
photorec_linux_directory = "/usr/bin";
} else if (usrLocalBin.canExecute() && usrLocalBin.exists() && !usrLocalBin.isDirectory()) {
photorec_linux_directory = "/usr/local/bin";
} else {
throw new IngestModule.IngestModuleException("Photorec not found");
exeFile = null;
for (String dirName: System.getenv("PATH").split(File.pathSeparator)) {
File testExe = new File(dirName, PHOTOREC_LINUX_EXECUTABLE);
if (testExe.exists()) {
exeFile = testExe;
break;
}
}
execName = Paths.get(photorec_linux_directory, PHOTOREC_LINUX_EXECUTABLE);
exeFile = new File(execName.toString());
}

if (null == exeFile) {
Expand Down
Expand Up @@ -18,6 +18,8 @@
*/
package org.sleuthkit.autopsy.modules.pictureanalyzer.impls;

import com.sun.javafx.PlatformUtil;

/**
*
* Interop with libheif native dependencies.
Expand All @@ -32,11 +34,13 @@ public class HeifJNI {
*/
public static HeifJNI getInstance() throws UnsatisfiedLinkError {
if (instance == null) {
System.loadLibrary("vcruntime140_1");
System.loadLibrary("libx265");
System.loadLibrary("libde265");
System.loadLibrary("heif");
System.loadLibrary("jpeg62");
if (PlatformUtil.isWindows()) {
System.loadLibrary("vcruntime140_1");
System.loadLibrary("libx265");
System.loadLibrary("libde265");
System.loadLibrary("heif");
System.loadLibrary("jpeg62");
}
System.loadLibrary("heifconvert");
instance = new HeifJNI();
}
Expand Down
48 changes: 43 additions & 5 deletions Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java
Expand Up @@ -44,7 +44,10 @@
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Comparator;
import org.apache.commons.io.FileUtils;

/**
* Finds and loads Autopsy modules written using the Jython variant of the
Expand All @@ -53,7 +56,8 @@
public final class JythonModuleLoader {

private static final Logger logger = Logger.getLogger(JythonModuleLoader.class.getName());

private static final String INTERNAL_PYTHON_MODULES_FOLDER = "InternalPythonModules";

/**
* Get ingest module factories implemented using Jython.
*
Expand Down Expand Up @@ -84,9 +88,42 @@ public static synchronized List<DataSourceProcessor> getDataSourceProcessorModul
return getInterfaceImplementations(new DataSourceProcessorDefFilter(), DataSourceProcessor.class);
}

/**
* @return Folder in user config directory where internal python modules
* will be copied and compiled.
*/
private static File getUserDirInternalPython() {
return new File(PlatformUtil.getUserDirectory(), INTERNAL_PYTHON_MODULES_FOLDER);
}

/**
* If user directory internal python modules does not exist, create it and
* copy contents internal python modules in installation directory to that
* location. This avoids creating files in the installation directory when
* compiling the python files.
*/
private static synchronized void copyInternalInstallToUserDir() {
File userDirInternalPython = getUserDirInternalPython();
if (!userDirInternalPython.exists()) {
userDirInternalPython.mkdirs();

File installInternalPython = InstalledFileLocator.getDefault().locate(INTERNAL_PYTHON_MODULES_FOLDER, "org.sleuthkit.autopsy.core", false);
if (installInternalPython.exists()) {
try {
FileUtils.copyDirectory(installInternalPython, userDirInternalPython);
} catch (IOException ex) {
logger.log(Level.WARNING, MessageFormat.format("There was an error copying internal python modules from {0} to {1}.",
installInternalPython, userDirInternalPython), ex);
}
}
}
}

@Messages({"JythonModuleLoader.pythonInterpreterError.title=Python Modules",
"JythonModuleLoader.pythonInterpreterError.msg=Failed to load python modules, See log for more details"})
private static <T> List<T> getInterfaceImplementations(LineFilter filter, Class<T> interfaceClass) {
copyInternalInstallToUserDir();

List<T> objects = new ArrayList<>();
Set<File> pythonModuleDirs = new HashSet<>();
PythonInterpreter interpreter = null;
Expand All @@ -100,11 +137,12 @@ private static <T> List<T> getInterfaceImplementations(LineFilter filter, Class<
}
return objects;
}

// add python modules from 'autospy/build/cluster/InternalPythonModules' folder
// which are copied from 'autopsy/*/release/InternalPythonModules' folders.
for (File f : InstalledFileLocator.getDefault().locateAll("InternalPythonModules", "org.sleuthkit.autopsy.core", false)) { //NON-NLS
Collections.addAll(pythonModuleDirs, f.listFiles());
}
// which are copied from 'autopsy/*/release/InternalPythonModules' folders,
// and then copied to 'testuserdir/InternalPythonModules'
Collections.addAll(pythonModuleDirs, getUserDirInternalPython().listFiles());

// add python modules from 'testuserdir/python_modules' folder
Collections.addAll(pythonModuleDirs, new File(PlatformUtil.getUserPythonModulesPath()).listFiles());

Expand Down
1 change: 1 addition & 0 deletions KeywordSearch/solr/bin/autopsy-solr
Expand Up @@ -2240,6 +2240,7 @@ function start_solr() {
exec "$JAVA" "${SOLR_START_OPTS[@]}" $SOLR_ADDL_ARGS -jar start.jar "${SOLR_JETTY_CONFIG[@]}" $SOLR_JETTY_ADDL_CONFIG
else
# run Solr in the background
echo "Starting server with \"$JAVA\" \"${SOLR_START_OPTS[@]}\" $SOLR_ADDL_ARGS -jar start.jar \"${SOLR_JETTY_CONFIG[@]}\" $SOLR_JETTY_ADDL_CONFIG in pwd: $(pwd)"
nohup "$JAVA" "${SOLR_START_OPTS[@]}" $SOLR_ADDL_ARGS -Dsolr.log.muteconsole \
"-XX:OnOutOfMemoryError=$SOLR_TIP/bin/oom_solr.sh $SOLR_PORT $SOLR_LOGS_DIR" \
-jar start.jar "${SOLR_JETTY_CONFIG[@]}" $SOLR_JETTY_ADDL_CONFIG \
Expand Down
2 changes: 1 addition & 1 deletion KeywordSearch/solr/bin/autopsy-solr.in.sh
Expand Up @@ -93,7 +93,7 @@
#SOLR_OPTS="$SOLR_OPTS -Dsolr.autoSoftCommit.maxTime=3000"
#SOLR_OPTS="$SOLR_OPTS -Dsolr.autoCommit.maxTime=60000"
#SOLR_OPTS="$SOLR_OPTS -Dsolr.clustering.enabled=true"
SOLR_OPTS=$SOLR_OPTS -Dbootstrap_confdir=../solr/configsets/AutopsyConfig/conf -Dcollection.configName=AutopsyConfig -Dsolr.default.confdir=../solr/configsets/AutopsyConfig/conf
SOLR_OPTS="$SOLR_OPTS -Dbootstrap_confdir=../solr/configsets/AutopsyConfig/conf -Dcollection.configName=AutopsyConfig -Dsolr.default.confdir=../solr/configsets/AutopsyConfig/conf "


# Location where the bin/solr script will save PID files for running instances
Expand Down
16 changes: 16 additions & 0 deletions homebrew/README.md
@@ -0,0 +1,16 @@
## Installing from Homebrew

Autopsy can be installed from homebrew by running brew with [`autopsy4.rb`](./autopsy4.rb) in this directory. Before you run the command, you may want to remove any previous versions of Autopsy and The Sleuth Kit, especially if they are installed `/usr/local/` as homebrew installs binaries and libraries in the same directory, which may cause conflicts. So the full path would be something like: `brew install --debug --build-from-source --verbose ./autopsy4.rb` when executing from this directory. After that point, you should be able to run autopsy from the command line by calling `autopsy`.

## Updating Versions for Homebrew

The version of Autopsy in the homebrew script can be updated by calling [`version_update.py`](./version_update/version_update.py) with a command like `python version_update.py -s https://path/to/sleuthkit-version.tar.gz -a https://path/to/autopsy-version.zip`. You will likely need to install the python dependencies in the [requirements.txt](./version_update/requirements.txt) with a command like: `pip install -r requirements.txt`.

The version of Autopsy can be updated manually by modifying the following variables in [`autopsy4.rb`](./autopsy4.rb):
- `AUTOPSY_RESOURCE_URL`: the location of the autopsy platform zip like `https://github.com/sleuthkit/autopsy/releases/download/autopsy-4.19.2/autopsy-4.19.2.zip`
- `AUTOPSY_RESOURCE_SHA256`: the SHA-256 of the autopsy platform zip. This can be calculated by running `curl <url> | sha256sum`
- `TSK_RESOURCE_URL`: the location of the sleuthkit compressed files like `https://github.com/sleuthkit/sleuthkit/releases/download/sleuthkit-4.11.1/sleuthkit-4.11.1.tar.gz`
- `TSK_RESOURCE_SHA256`: the SHA-256 of the sleuthkit compressed files.


*There is more information in Jira 8425.*
108 changes: 108 additions & 0 deletions homebrew/autopsy4.rb
@@ -0,0 +1,108 @@
# Documentation: https://docs.brew.sh/Formula-Cookbook
# https://rubydoc.brew.sh/Formula

# Named Autopsy4 to avoid conflict with the autopsy formula in repos
# A package installer can be generated using brew-pkg: https://github.com/timsutton/brew-pkg
# Can be run locally with `brew install --debug --build-from-source --verbose <path_to_this_file>`
# sha256 calculated using curl <url> | sha256sum
class Autopsy4 < Formula
AUTOPSY_RESOURCE_URL = "https://github.com/sleuthkit/autopsy/releases/download/autopsy-4.20.0/autopsy-4.20.0.zip".freeze
AUTOPSY_RESOURCE_SHA256 = "60964AB135429C2636AB8A1B0DA5EE18D232D4323DB6EDE1B6A9CFBF7E3500CE".freeze
TSK_RESOURCE_URL = "https://github.com/sleuthkit/sleuthkit/releases/download/sleuthkit-4.12.0/sleuthkit-4.12.0.tar.gz".freeze
TSK_RESOURCE_SHA256 = "0FAE8DBCCA69316A92212374272B8F81EFD0A669FB93D61267CFD855B06ED23B".freeze

desc "Autopsy® is a digital forensics platform and graphical interface to The Sleuth Kit® and other digital forensics tools. It can be used by law enforcement, military, and corporate examiners to investigate what happened on a computer. You can even use it to recover photos from your camera's memory card. "
homepage "http://www.sleuthkit.org/autopsy/"

url AUTOPSY_RESOURCE_URL
sha256 AUTOPSY_RESOURCE_SHA256
license "Apache-2.0"

depends_on "afflib"
depends_on "libewf"

depends_on "testdisk"

depends_on "libheif"

depends_on "openjdk@17"
depends_on "gst-libav"
depends_on "gst-plugins-bad"
depends_on "gst-plugins-base"
depends_on "gst-plugins-good"
depends_on "gst-plugins-ugly"
depends_on "gstreamer"

depends_on "libtool" => :build
depends_on "autoconf" => :build
depends_on "automake" => :build
depends_on "zip" => :build
depends_on "gnu-tar" => :build
depends_on "ant" => :build

resource "sleuthkit" do
url TSK_RESOURCE_URL
sha256 TSK_RESOURCE_SHA256
end

# sha256 calculated using curl <url> | sha256sum
# TODO could create separate for build and run
on_linux do
depends_on "sqlite"
end
on_macos do
uses_from_macos "sqlite"
end

conflicts_with "ffind", because: "both install a `ffind` executable"
conflicts_with "sleuthkit", because: "both install sleuthkit items"
conflicts_with "autopsy", because: "both install sleuthkit items and have autopsy executables"

def install
ENV.deparallelize
install_dir = File.join(prefix, "install")

# ----- SETUP JVM -----
java_home_path = "#{Formula["gstreamer"].prefix}/opt/openjdk@17"
ENV["JAVA_HOME"] = java_home_path
ENV["PATH"] = "#{java_home_path}/bin:#{ENV["PATH"]}"
ENV["ANT_FOUND"] = Formula["ant"].opt_bin/"ant"

# ----- SETUP TSK -----
sleuthkit_bin_path = File.join(install_dir, "sleuthkit")
system "mkdir", "-p", sleuthkit_bin_path
resource("sleuthkit").stage(sleuthkit_bin_path)
cd sleuthkit_bin_path do
system "./configure", "--disable-dependency-tracking", "--prefix=#{prefix}"
system "make"
system "make", "install"
end

# ----- RUN UNIX SETUP SCRIPT -----
autopsy_tmp_path = `find $(pwd) -maxdepth 1 -type d -name "autopsy-*.*.*"`.strip()
autopsy_install_path = File.join(install_dir, "autopsy")
system "cp", "-a", "#{autopsy_tmp_path}/.", autopsy_install_path

unix_setup_script = File.join(autopsy_install_path, "unix_setup.sh")
system "chmod", "a+x", unix_setup_script

ENV["TSK_JAVA_LIB_PATH"] = File.join(prefix, "share", "java")
system unix_setup_script, "-j", "#{java_home_path}"

open(File.join(autopsy_install_path, "etc", "autopsy.conf"), 'a') { |f|
# gstreamer needs the 'gst-plugin-scanner' to locate gstreamer plugins like the ones that allow gstreamer to play videos in autopsy
# so, the jreflags allow the initial gstreamer lib to be loaded and the 'GST_PLUGIN_SYSTEM_PATH' along with 'GST_PLUGIN_SCANNER'
# allows gstreamer to find plugin dependencies
f.puts("export jreflags=\"-Djna.library.path=/usr/local/lib $jreflags\"")
f.puts("export GST_PLUGIN_SYSTEM_PATH=\"/usr/local/lib/gstreamer-1.0\"")
f.puts("export GST_PLUGIN_SCANNER=\"#{Formula["gstreamer"].prefix}/libexec/gstreamer-1.0/gst-plugin-scanner\"")
}

bin_autopsy = File.join(bin, "autopsy")
system "ln", "-s", File.join(autopsy_install_path, "bin", "autopsy"), bin_autopsy
end

test do
system "#{bin}/autopsy", "--help"
end
end
2 changes: 2 additions & 0 deletions homebrew/version_update/.gitignore
@@ -0,0 +1,2 @@
/.idea
/venv
1 change: 1 addition & 0 deletions homebrew/version_update/requirements.txt
@@ -0,0 +1 @@
argparse==1.4.0
82 changes: 82 additions & 0 deletions homebrew/version_update/version_update.py
@@ -0,0 +1,82 @@
import sys
import argparse
from typing import Union
from os.path import join, dirname, abspath, realpath
import hashlib
from urllib.request import urlopen
import re

HOMEBREW_RUBY_PATH = join(dirname(dirname(abspath(realpath(__file__)))), 'autopsy4.rb')
TSK_URL_KEY = "TSK_RESOURCE_URL"
TSK_SHA256_KEY = "TSK_RESOURCE_SHA256"
AUTOPSY_URL_KEY = "AUTOPSY_RESOURCE_URL"
AUTOPSY_SHA256_KEY = "AUTOPSY_RESOURCE_SHA256"

MAX_FILE_SIZE = 100 * 1024 * 1024 * 1024


def hash_url(url: str) -> str:
remote = urlopen(url)
total_read = 0
hasher = hashlib.sha256()

while total_read < MAX_FILE_SIZE:
data = remote.read(4096)
total_read += 4096
hasher.update(data)

return hasher.hexdigest()


def replace_variable(file_contents: str, var_key: str, var_value: str) -> str:
search_regex = rf'^(\s*{re.escape(var_key)}\s*=\s*").+?("[^"]*)$'
replacement = rf'\g<1>{var_value}\g<2>'
return re.sub(search_regex, replacement, file_contents, flags=re.M)


def update_versions(tsk_resource_url: str, autopsy_resource_url: str, file_path: Union[str, None]):
tsk_sha256 = hash_url(tsk_resource_url)
autopsy_sha256 = hash_url(autopsy_resource_url)

file_path = file_path if file_path is not None and len(file_path.strip()) > 0 else HOMEBREW_RUBY_PATH

with open(file_path, 'r') as f:
content = f.read()

for k, v in [
(TSK_URL_KEY, tsk_resource_url),
(TSK_SHA256_KEY, tsk_sha256),
(AUTOPSY_URL_KEY, autopsy_resource_url),
(AUTOPSY_SHA256_KEY, autopsy_sha256)
]:
content = replace_variable(content, k, v)

with open(file_path, 'w') as f:
f.write(content)


def main():
parser = argparse.ArgumentParser(
description="Updates homebrew file with current versions of autopsy and sleuthkit",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)

parser.add_argument('-s', '--sleuthkit_resource_url', required=True, dest='sleuthkit_resource_url', type=str,
help='The compressed build file system of the sleuthkit release ' +
'(i.e. https://github.com/sleuthkit/sleuthkit/releases/download/sleuthkit-4.11.1/sleuthkit-4.11.1.tar.gz)')
parser.add_argument('-a', '--autopsy_resource_url', required=True, dest='autopsy_resource_url', type=str,
help='The compressed build file system of the autopsy release ' +
'(i.e. https://github.com/sleuthkit/autopsy/releases/download/autopsy-4.19.2/autopsy-4.19.2.zip)')

parser.add_argument('-p', '--homebrew_path', dest='homebrew_path', type=str, default=HOMEBREW_RUBY_PATH,
help='Path to homebrew file.')

args = parser.parse_args()
update_versions(
tsk_resource_url=args.sleuthkit_resource_url,
autopsy_resource_url=args.autopsy_resource_url,
file_path=args.homebrew_path
)


if __name__ == '__main__':
main()
15 changes: 15 additions & 0 deletions snap/README.md
@@ -0,0 +1,15 @@
## Installing Snap

An Autopsy [snap package](https://snapcraft.io/) file can be installed by running `sudo snap install autopsy.snap --classic --dangerous`. The `--classic` flag gives the snap package access to necessary system resources (see [confinement](https://snapcraft.io/docs/snap-confinement) for more information) and `--dangerous` needs to be specified because the snap package isn't signed (see [install modes](https://snapcraft.io/docs/install-modes#heading--dangerous) for more information).

## Generating The Snap Package

A [snap package](https://snapcraft.io/) of Autopsy can be generated using the [`snapcraft.yml`](./snapcraft.yaml) file. You will need [snapcraft](https://snapcraft.io/) on your system and [lxd](https://snapcraft.io/lxd) works well for virtualization while building the snap package. Since snapcraft needs virtualization to create the snap package, your computer's hardware will need to support virtualization and any relevant settings will need to be enabled. From testing as of November 2022, VirtualBox and WSL are not good build environments. Once the development environment has been set up, a snap package can be built with this command: `snapcraft --use-lxd --debug` run from this directory.

## Updating Versions for Snap

The version of Autopsy in the [`snapcraft.yml`](./snapcraft.yaml) can be updated by calling [`version_update.py`](./version_update/version_update.py) with a command like `python version_update.py -s sleuthkit_release_tag -a autopsy_release_tag -v snapcraft_version_name`. You will likely need to install the python dependencies in the [requirements.txt](./version_update/requirements.txt) with a command like: `pip install -r requirements.txt`.

The version of Autopsy can be updated manually by modifying fields relating to git repositories and commits in [`snapcraft.yml`](./snapcraft.yaml) under `parts.autopsy` and `parts.sleuthkit`. Specifically `source`, `source-branch`, and `source-tag`. More information can be found [here](https://snapcraft.io/docs/snapcraft-yaml-reference).

*There is more information in Jira 8425.*
9 changes: 9 additions & 0 deletions snap/gui/autopsy.desktop
@@ -0,0 +1,9 @@
[Desktop Entry]
Name=Autopsy
Comment=A graphical interface to The Sleuth Kit and other digital forensics tools.
GenericName=DFIR Tool.
Exec=autopsy
Icon=${SNAP}/meta/gui/autopsy.png
Type=Application
Categories=Forensics;DFIR
Keywords=autopsy;sleuth;kit;dfir;forensics
Binary file added snap/gui/autopsy.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.