Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions acquire/acquire.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
from acquire.log import get_file_handler, reconfigure_log_file, setup_logging
from acquire.outputs import OUTPUTS
from acquire.uploaders.minio import MinIO
from acquire.uploaders.plugin import UploaderPlugin, upload_files_using_uploader
from acquire.uploaders.plugin import upload_files_using_uploader
from acquire.uploaders.plugin_registry import UploaderRegistry
from acquire.utils import (
check_and_set_acquire_args,
Expand All @@ -71,6 +71,8 @@

from dissect.target.filesystem import Filesystem

from acquire.uploaders.plugin import UploaderPlugin

try:
from acquire.version import version
except ImportError:
Expand Down Expand Up @@ -245,7 +247,7 @@ def wrapper(module_cls: type[Module]) -> type[Module]:


def local_module(cls: type[object]) -> object:
"""A decorator that sets property `__local__` on a module class to mark it for local target only"""
"""A decorator that sets property `__local__` on a module class to mark it for local target only."""
cls.__local__ = True
return cls

Expand Down
7 changes: 2 additions & 5 deletions acquire/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,8 +681,7 @@ def write_bytes(self, destination_path: str, data: bytes) -> None:


def get_report_summary(report: CollectionReport) -> str:
"""Create a table-view report summary with success/failure/missing/empty counters per module"""

"""Create a table-view report summary with success/failure/missing/empty counters per module."""
record_counts = report.get_counts_per_module_per_outcome()

if not record_counts:
Expand Down Expand Up @@ -748,11 +747,9 @@ def get_report_summary(report: CollectionReport) -> str:


def get_full_formatted_report(report: CollectionReport, record_indent: int = 4) -> str:
"""
Create a full list of successful / failed / missing / empty artifacts collected,
"""Create a full list of successful / failed / missing / empty artifacts collected,
broken down by module.
"""

record_line_template = "{record.outcome}: {record.artifact_type}:{record.artifact_value}"
blocks = []

Expand Down
10 changes: 6 additions & 4 deletions acquire/dynamic/windows/collect.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from typing import TYPE_CHECKING

from acquire.dynamic.windows.exceptions import AccessDeniedError
from acquire.dynamic.windows.handles import Handle, get_handles
from acquire.dynamic.windows.named_objects import NamedObject, NamedObjectType
from acquire.dynamic.windows.handles import get_handles
from acquire.dynamic.windows.named_objects import NamedObjectType
from acquire.dynamic.windows.ntdll import (
close_handle,
open_directory_object,
Expand All @@ -15,6 +15,9 @@
if TYPE_CHECKING:
from collections.abc import Iterator

from acquire.dynamic.windows.handles import Handle
from acquire.dynamic.windows.named_objects import NamedObject

log = getLogger(__name__)


Expand All @@ -24,7 +27,6 @@ def collect_named_objects(path: str = "\\") -> list[NamedObject]:
Parameters:
path: point to start searching from
"""

try:
dir_handle = open_directory_object(dir_name=path, root_handle=None)
except AccessDeniedError:
Expand All @@ -45,7 +47,7 @@ def collect_named_objects(path: str = "\\") -> list[NamedObject]:


def collect_open_handles(handle_types: list[NamedObject] | None = None) -> Iterator[Handle]:
"""Collect open handles
"""Collect open handles.

Collect open handles and optionally provide a list to explicitly collect specific types of handles.

Expand Down
3 changes: 3 additions & 0 deletions acquire/dynamic/windows/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from __future__ import annotations


class WindowsDynamicError(Exception):
pass

Expand Down
5 changes: 2 additions & 3 deletions acquire/dynamic/windows/handles.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import gzip
import io
import threading
from logging import Filter, LogRecord, getLogger
from logging import Filter, getLogger
from queue import Empty, Queue
from typing import TYPE_CHECKING

Expand Down Expand Up @@ -39,6 +39,7 @@

if TYPE_CHECKING:
from collections.abc import Iterator
from logging import LogRecord

log = getLogger(__name__)

Expand Down Expand Up @@ -164,7 +165,6 @@ def _get_file_name_thread(h_file: HANDLE, queue: Queue) -> None:

def get_handle_name(pid: int, handle: SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX) -> str | None:
"""Return handle name."""

remote = pid != GetCurrentProcessId()

if remote:
Expand Down Expand Up @@ -268,7 +268,6 @@ def serialize_handles_into_csv(rows: Iterator[Handle], compress: bool = True) ->
Serialize provided rows into normal or gzip-compressed CSV, and return a tuple
containing the result bytes.
"""

raw_buffer = io.BytesIO()

buffer = gzip.GzipFile(fileobj=raw_buffer, mode="wb") if compress else raw_buffer
Expand Down
11 changes: 6 additions & 5 deletions acquire/dynamic/windows/ntdll.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,11 @@ class ACCESS_MASK(IntFlag):


class OBJ_ATTR(IntFlag):
"""
https://github.com/tpn/winsdk-10/blob/master/Include/10.0.10240.0/shared/ntdef.h
https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/object-handles
"""Drivers object handles values.

References:
- https://github.com/tpn/winsdk-10/blob/master/Include/10.0.10240.0/shared/ntdef.h
- https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/object-handles
"""

OBJ_INHERIT = 0x00000002
Expand Down Expand Up @@ -169,7 +171,7 @@ def close_handle(handle: HANDLE) -> None:


def validate_ntstatus(status: NTSTATUS) -> None:
"""Validates the result status of a Nt call
"""Validates the result status of a Nt call.

Parameters:
status: the return value of a ntcall
Expand All @@ -189,7 +191,6 @@ def open_directory_object(dir_name: str, root_handle: HANDLE = None) -> HANDLE:
dir_name: Specific directory we want to try and open.
root_handle: From which point we want to start querying the object.
"""

object_name = UNICODE_STRING.from_str(dir_name)
p_name = ctypes.pointer(object_name)

Expand Down
2 changes: 1 addition & 1 deletion acquire/dynamic/windows/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ class TOKEN_PRIVILEGES(ctypes.Structure):


class Handle:
"""Handle object"""
"""Handle object."""

def __init__(self, handle: SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX, handle_type: str, handle_name: str) -> None:
self.name = handle_name
Expand Down
2 changes: 2 additions & 0 deletions acquire/gui/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from acquire.gui.base import GUI, GUIError

__all__ = ["GUI", "GUIError"]
24 changes: 8 additions & 16 deletions acquire/hashes.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ def get_paths_from_dir(
glob: str,
extensions: set[str] | None = None,
) -> Iterator[Path]:
"""Yield paths that match provided `glob` pattern and `extensions` values"""

"""Yield paths that match provided `glob` pattern and `extensions` values."""
extension_suffixes = {f".{ext}" for ext in extensions} if extensions else None

for path in target.fs.path("/").glob(glob):
Expand All @@ -57,13 +56,11 @@ def get_paths_from_dir(


def get_path_details(path: TargetPath, hash_funcs: Iterator[HashFunc] | None = None) -> tuple:
"""
Calculate and return the details for specified path.
"""Calculate and return the details for specified path.

The details include file size and hashes, calculated for
hash functions provided in `hash_funcs`.
"""

hash_funcs = hash_funcs or []
if hash_funcs:
provided_hash_funcs_sorted = sorted(set(hash_funcs))
Expand All @@ -84,7 +81,7 @@ def get_path_details(path: TargetPath, hash_funcs: Iterator[HashFunc] | None = N


def filter_out_nonfiles(paths: Iterator[Path]) -> Iterator[Path]:
"""Filter out paths that are not files"""
"""Filter out paths that are not files."""
for path in paths:
try:
is_file = path.is_file()
Expand All @@ -99,7 +96,7 @@ def filter_out_nonfiles(paths: Iterator[Path]) -> Iterator[Path]:


def filter_out_huge_files(paths: Iterator[Path], *, max_size_bytes: int) -> Iterator[Path]:
"""Filter out paths that are larger than `max_size_bytes` value"""
"""Filter out paths that are larger than `max_size_bytes` value."""
for path in paths:
try:
file_size = path.stat().st_size
Expand All @@ -119,8 +116,7 @@ def filter_out_by_value_match(
value: bytes,
offsets: Iterator[int] = (0,),
) -> Iterator[Path]:
"""Filter out paths where file data matches the provided `value` at the specified offsets"""

"""Filter out paths where file data matches the provided `value` at the specified offsets."""
if not offsets:
raise ValueError("No offsets provided")

Expand Down Expand Up @@ -148,7 +144,7 @@ def filter_out_by_path_match(
re_pattern: str,
re_flags: re.RegexFlag = re.IGNORECASE,
) -> Iterator[Path]:
"""Filter out paths that match provided regex pattern"""
"""Filter out paths that match provided regex pattern."""
pattern = re.compile(re_pattern, flags=re_flags)
return filter(lambda p: not pattern.match(str(p)), paths)

Expand All @@ -158,8 +154,7 @@ def collect_hashes(
specs: Iterator[Iterator[tuple]],
path_filters: Iterator[Callable[[Iterator[Path]], Iterator[Path]]] | None = None,
) -> Iterator[tuple]:
"""
Walk through the paths, calculate hashes and return details per path.
"""Walk through the paths, calculate hashes and return details per path.

Spec contains a path selector and a list of hash functions to compute against the paths.
For example::
Expand All @@ -169,7 +164,6 @@ def collect_hashes(
(HashFunc.MD5, HashFunc.SHA1),
]
"""

log.info("Starting to collect hashes for spec: %s", specs)

stream_hash_func_pairs = []
Expand Down Expand Up @@ -225,11 +219,9 @@ def collect_hashes(


def serialize_into_csv(rows: Iterator[list], compress: bool = True) -> tuple[int, bytes]:
"""
Serialize provided rows into normal or gzip-compressed CSV, and return a tuple
"""Serialize provided rows into normal or gzip-compressed CSV, and return a tuple
containing the number of rows processed and the result bytes.
"""

raw_buffer = io.BytesIO()

counter = 0
Expand Down
2 changes: 2 additions & 0 deletions acquire/outputs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from acquire.outputs.dir import DirectoryOutput
from acquire.outputs.tar import TAR_COMPRESSION_METHODS, TarOutput
from acquire.outputs.zip import ZIP_COMPRESSION_METHODS, ZipOutput
Expand Down
1 change: 0 additions & 1 deletion acquire/outputs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ def write_bytes(
entry: The optional filesystem entry to write.
size: The optional file size in bytes of the entry to write.
"""

stream = io.BytesIO(data)
self.write(output_path, stream, entry=entry, size=size)

Expand Down
1 change: 0 additions & 1 deletion acquire/outputs/zip.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ def close(self) -> None:

def _get_external_attr(self, entry: FilesystemEntry) -> int:
"""Return the appropriate external attributes of the entry."""

# The Python zipfile module accepts the 16-bit "Mode" field (that stores st_mode field from
# struct stat, containing user/group/other permissions, setuid/setgid and symlink info, etc) of the
# ASi extra block for Unix as bits 16-31 of the external_attr
Expand Down
2 changes: 1 addition & 1 deletion acquire/tools/decrypter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from datetime import datetime, timezone
from pathlib import Path
from queue import Empty as QueueEmptyError
from queue import Queue
from typing import TYPE_CHECKING, BinaryIO
from urllib import request
from urllib.error import HTTPError
Expand Down Expand Up @@ -62,6 +61,7 @@

if TYPE_CHECKING:
from collections.abc import Iterator
from queue import Queue
from threading import Event

log = logging.getLogger(__name__)
Expand Down
1 change: 0 additions & 1 deletion acquire/uploaders/minio.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ def __init__(self, upload: dict[str, str], **kwargs: dict[str, Any]) -> None:
Raises:
ValueError: When the configuration is invalid.
"""

self.endpoint = upload.get("endpoint")
self.access_id = upload.get("access_id")
self.access_key = upload.get("access_key")
Expand Down
2 changes: 1 addition & 1 deletion acquire/uploaders/plugin_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def remove(self, name: str) -> None:
self.plugins.pop(name)

def items(self) -> ItemsView[str, T]:
"""Returns all the items inside the ``plugins`` dictionary"""
"""Returns all the items inside the ``plugins`` dictionary."""
return self.plugins.items()

def get(self, name: str) -> T:
Expand Down
2 changes: 1 addition & 1 deletion acquire/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@


class StrEnum(str, Enum):
"""Sortable and serializible string-based enum"""
"""Sortable and serializible string-based enum."""


def _create_profile_information(profiles: dict) -> str:
Expand Down
25 changes: 23 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ acquire-decrypt = "acquire.tools.decrypter:main"
line-length = 120
required-version = ">=0.13.1"

# Exclude setuptools_scm autogenerated file from linting
extend-exclude = ["acquire/version.py"]

[tool.ruff.format]
docstring-code-format = true

Expand Down Expand Up @@ -107,22 +110,40 @@ select = [
"SLOT",
"SIM",
"TID",
"TCH",
"TC",
"PTH",
"PLC",
"TRY",
"FLY",
"PERF",
"FURB",
"RUF",
"D"
]
ignore = [
"E203", "B904", "UP024", "ANN002", "ANN003", "ANN204", "ANN401", "SIM105", "TRY003",
# Ignore some pydocstyle rules for now as they require a larger cleanup
"D1",
"D205",
"D301",
"D417",
# Seems bugged: https://github.com/astral-sh/ruff/issues/16824
"D402",
]
ignore = ["E203", "B904", "UP024", "ANN002", "ANN003", "ANN204", "ANN401", "SIM105", "TRY003"]
future-annotations = true

[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.ruff.lint.flake8-type-checking]
strict = true

[tool.ruff.lint.per-file-ignores]
"tests/_docs/**" = ["INP001"]

[tool.ruff.lint.isort]
known-first-party = ["acquire"]
required-imports = ["from __future__ import annotations"]

[tool.setuptools.packages.find]
include = ["acquire", "acquire.*"]
Expand Down
2 changes: 2 additions & 0 deletions tests/_docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

project = "acquire"

extensions = [
Expand Down
Loading
Loading