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

Allow multiple accessors in Velociraptor loader #576

Merged
merged 9 commits into from
Apr 18, 2024
28 changes: 23 additions & 5 deletions dissect/target/loaders/dir.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from __future__ import annotations

import zipfile
from collections import defaultdict
from pathlib import Path
from typing import TYPE_CHECKING

from dissect.target.filesystem import LayerFilesystem
from dissect.target.filesystems.dir import DirectoryFilesystem
from dissect.target.filesystems.zip import ZipFilesystem
from dissect.target.helpers import loaderutil
Expand Down Expand Up @@ -48,6 +50,7 @@ def map_dirs(target: Target, dirs: list[Path | tuple[str, Path]], os_type: str,
alt_separator = "\\"
case_sensitive = False

drive_letter_map = defaultdict(list)
for path in dirs:
drive_letter = None
if isinstance(path, tuple):
Expand All @@ -59,13 +62,28 @@ def map_dirs(target: Target, dirs: list[Path | tuple[str, Path]], os_type: str,
dfs = ZipFilesystem(path.root.fp, path.at, alt_separator=alt_separator, case_sensitive=case_sensitive)
else:
dfs = DirectoryFilesystem(path, alt_separator=alt_separator, case_sensitive=case_sensitive)
target.filesystems.add(dfs)

if os_type == OperatingSystem.WINDOWS:
loaderutil.add_virtual_ntfs_filesystem(target, dfs, **kwargs)
drive_letter_map[drive_letter].append(dfs)

fs_to_add = []
for drive_letter, dfs in drive_letter_map.items():
if drive_letter is not None:
if len(dfs) > 1:
vfs = LayerFilesystem()
for fs in dfs:
vfs.append_fs_layer(fs)
else:
vfs = dfs[0]

if drive_letter is not None:
target.fs.mount(drive_letter.lower() + ":", dfs)
fs_to_add.append(vfs)
target.fs.mount(drive_letter.lower() + ":", vfs)
else:
fs_to_add.extend(dfs)

for fs in fs_to_add:
target.filesystems.add(fs)
if os_type == OperatingSystem.WINDOWS:
loaderutil.add_virtual_ntfs_filesystem(target, fs, **kwargs)


def find_and_map_dirs(target: Target, path: Path, **kwargs) -> None:
Expand Down
9 changes: 5 additions & 4 deletions dissect/target/loaders/velociraptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ def extract_drive_letter(name: str) -> Optional[str]:
if len(name) == 14 and name.startswith("%5C%5C.%5C") and name.endswith("%3A"):
return name[10].lower()

# X: in URL encoding
if len(name) == 4 and name.endswith("%3A"):
return name[0].lower()


class VelociraptorLoader(DirLoader):
"""Load Rapid7 Velociraptor forensic image files.
Expand All @@ -71,10 +75,7 @@ class VelociraptorLoader(DirLoader):
{"Generic.Collectors.File":{"Root":"/","collectionSpec":"Glob\\netc/**\\nvar/log/**"}}

Generic.Collectors.File (Windows) and Windows.KapeFiles.Targets (Windows) uses the accessors mft, ntfs, lazy_ntfs,
ntfs_vss and auto. The loader only supports a collection where a single accessor is used, which can be forced by
using the following configuration::

{"Windows.KapeFiles.Targets":{"VSSAnalysisAge":"1000","_SANS_Triage":"Y"}}
ntfs_vss and auto. The loader supports a collection where multiple accessors were used.

References:
- https://www.rapid7.com/products/velociraptor/
Expand Down
21 changes: 15 additions & 6 deletions tests/loaders/test_velociraptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,19 @@ def create_root(sub_dir: str, tmp_path: Path) -> Path:


@pytest.mark.parametrize(
"sub_dir",
["mft", "ntfs", "ntfs_vss", "lazy_ntfs", "auto"],
"sub_dir, other_dir",
[
("mft", "auto"),
("ntfs", "auto"),
("ntfs_vss", "auto"),
("lazy_ntfs", "auto"),
("auto", "ntfs"),
],
)
def test_velociraptor_loader_windows_ntfs(sub_dir: str, target_bare: Target, tmp_path: Path) -> None:
def test_windows_ntfs(sub_dir: str, other_dir: str, target_bare: Target, tmp_path: Path) -> None:
root = create_root(sub_dir, tmp_path)
root.joinpath(f"uploads/{other_dir}/C%3A").mkdir(parents=True, exist_ok=True)
root.joinpath(f"uploads/{other_dir}/C%3A/other.txt").write_text("my first file")

assert VelociraptorLoader.detect(root) is True

Expand All @@ -65,13 +73,14 @@ def test_velociraptor_loader_windows_ntfs(sub_dir: str, target_bare: Target, tmp
assert usnjrnl_records == 2
assert len(target_bare.filesystems) == 4
assert target_bare.fs.path("sysvol/C-DRIVE.txt").exists()
assert target_bare.fs.path("sysvol/other.txt").read_text() == "my first file"


@pytest.mark.parametrize(
"sub_dir",
["mft", "ntfs", "ntfs_vss", "lazy_ntfs", "auto"],
)
def test_velociraptor_loader_windows_ntfs_zip(sub_dir: str, target_bare: Target, tmp_path: Path) -> None:
def test_windows_ntfs_zip(sub_dir: str, target_bare: Target, tmp_path: Path) -> None:
create_root(sub_dir, tmp_path)

shutil.make_archive(tmp_path.joinpath("test_ntfs"), "zip", tmp_path)
Expand Down Expand Up @@ -106,7 +115,7 @@ def test_velociraptor_loader_windows_ntfs_zip(sub_dir: str, target_bare: Target,
(["uploads/auto/Library", "uploads/auto/Applications"]),
],
)
def test_dir_loader_unix(paths: list[str], target_bare: Target, tmp_path: Path) -> None:
def test_unix(paths: list[str], target_bare: Target, tmp_path: Path) -> None:
root = tmp_path
mkdirs(root, paths)

Expand All @@ -131,7 +140,7 @@ def test_dir_loader_unix(paths: list[str], target_bare: Target, tmp_path: Path)
(["uploads/auto/Library", "uploads/auto/Applications"]),
],
)
def test_dir_loader_unix_zip(paths: list[str], target_bare: Target, tmp_path: Path) -> None:
def test_unix_zip(paths: list[str], target_bare: Target, tmp_path: Path) -> None:
root = tmp_path
mkdirs(root, paths)

Expand Down