acquire 3.16.dev5__tar.gz → 3.16.dev7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {acquire-3.16.dev5/acquire.egg-info → acquire-3.16.dev7}/PKG-INFO +1 -1
  2. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/acquire.py +2 -0
  3. acquire-3.16.dev7/acquire/outputs/__init__.py +9 -0
  4. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/outputs/tar.py +9 -2
  5. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/outputs/zip.py +38 -19
  6. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/utils.py +22 -2
  7. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/version.py +2 -2
  8. {acquire-3.16.dev5 → acquire-3.16.dev7/acquire.egg-info}/PKG-INFO +1 -1
  9. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire.egg-info/SOURCES.txt +1 -0
  10. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_outputs_tar.py +5 -3
  11. acquire-3.16.dev7/tests/test_outputs_zip.py +47 -0
  12. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_utils.py +5 -4
  13. acquire-3.16.dev5/acquire/outputs/__init__.py +0 -7
  14. {acquire-3.16.dev5 → acquire-3.16.dev7}/COPYRIGHT +0 -0
  15. {acquire-3.16.dev5 → acquire-3.16.dev7}/LICENSE +0 -0
  16. {acquire-3.16.dev5 → acquire-3.16.dev7}/MANIFEST.in +0 -0
  17. {acquire-3.16.dev5 → acquire-3.16.dev7}/README.md +0 -0
  18. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/__init__.py +0 -0
  19. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/collector.py +0 -0
  20. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/crypt.py +0 -0
  21. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/dynamic/__init__.py +0 -0
  22. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/dynamic/windows/__init__.py +0 -0
  23. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/dynamic/windows/collect.py +0 -0
  24. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/dynamic/windows/exceptions.py +0 -0
  25. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/dynamic/windows/handles.py +0 -0
  26. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/dynamic/windows/named_objects.py +0 -0
  27. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/dynamic/windows/ntdll.py +0 -0
  28. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/dynamic/windows/types.py +0 -0
  29. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/esxi.py +0 -0
  30. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/gui/__init__.py +0 -0
  31. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/gui/base.py +0 -0
  32. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/gui/win32.py +0 -0
  33. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/hashes.py +0 -0
  34. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/log.py +0 -0
  35. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/outputs/base.py +0 -0
  36. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/outputs/dir.py +0 -0
  37. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/tools/__init__.py +0 -0
  38. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/tools/decrypter.py +0 -0
  39. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/uploaders/__init__.py +0 -0
  40. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/uploaders/minio.py +0 -0
  41. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/uploaders/plugin.py +0 -0
  42. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/uploaders/plugin_registry.py +0 -0
  43. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire/volatilestream.py +0 -0
  44. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire.egg-info/dependency_links.txt +0 -0
  45. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire.egg-info/entry_points.txt +0 -0
  46. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire.egg-info/requires.txt +0 -0
  47. {acquire-3.16.dev5 → acquire-3.16.dev7}/acquire.egg-info/top_level.txt +0 -0
  48. {acquire-3.16.dev5 → acquire-3.16.dev7}/pyproject.toml +0 -0
  49. {acquire-3.16.dev5 → acquire-3.16.dev7}/setup.cfg +0 -0
  50. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/__init__.py +0 -0
  51. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/conftest.py +0 -0
  52. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/docs/Makefile +0 -0
  53. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/docs/conf.py +0 -0
  54. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/docs/index.rst +0 -0
  55. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_acquire_command.py +0 -0
  56. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_acquire_modules.py +0 -0
  57. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_collector.py +0 -0
  58. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_decryptor_funcs.py +0 -0
  59. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_esxi_memory.py +0 -0
  60. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_file_sorting.py +0 -0
  61. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_gui.py +0 -0
  62. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_minio_uploader.py +0 -0
  63. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_misc_users.py +0 -0
  64. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_outputs_dir.py +0 -0
  65. {acquire-3.16.dev5 → acquire-3.16.dev7}/tests/test_plugin.py +0 -0
  66. {acquire-3.16.dev5 → acquire-3.16.dev7}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: acquire
3
- Version: 3.16.dev5
3
+ Version: 3.16.dev7
4
4
  Summary: A tool to quickly gather forensic artifacts from disk images or a live system into a lightweight container
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -924,6 +924,7 @@ class Misc(Module):
924
924
  ("glob", "sysvol/Windows/Logs/WindowsUpdate/WindowsUpdate*.etl"),
925
925
  ("glob", "sysvol/Windows/Logs/CBS/CBS*.log"),
926
926
  ("dir", "sysvol/ProgramData/Microsoft/Search/Data/Applications/Windows"),
927
+ ("dir", "sysvol/Windows/SoftwareDistribution/DataStore"),
927
928
  ]
928
929
 
929
930
 
@@ -1782,6 +1783,7 @@ def acquire_target(target: Target, args: argparse.Namespace, output_ts: Optional
1782
1783
  output = OUTPUTS[args.output_type](
1783
1784
  output_path,
1784
1785
  compress=args.compress,
1786
+ compression_method=args.compress_method,
1785
1787
  encrypt=args.encrypt,
1786
1788
  public_key=args.public_key,
1787
1789
  )
@@ -0,0 +1,9 @@
1
+ from acquire.outputs.dir import DirectoryOutput
2
+ from acquire.outputs.tar import TAR_COMPRESSION_METHODS, TarOutput
3
+ from acquire.outputs.zip import ZIP_COMPRESSION_METHODS, ZipOutput
4
+
5
+ __all__ = ["DirectoryOutput", "TarOutput", "ZipOutput"]
6
+
7
+ OUTPUTS = {"tar": TarOutput, "dir": DirectoryOutput, "zip": ZipOutput}
8
+
9
+ COMPRESSION_METHODS = {*TAR_COMPRESSION_METHODS, *ZIP_COMPRESSION_METHODS}
@@ -8,6 +8,8 @@ from dissect.target.filesystem import FilesystemEntry
8
8
  from acquire.crypt import EncryptedStream
9
9
  from acquire.outputs.base import Output
10
10
 
11
+ TAR_COMPRESSION_METHODS = {"gzip": "gz", "bzip2": "bz2", "xz": "xz"}
12
+
11
13
 
12
14
  class TarOutput(Output):
13
15
  """Tar archive acquire output format. Output can be compressed and/or encrypted.
@@ -15,6 +17,7 @@ class TarOutput(Output):
15
17
  Args:
16
18
  path: The path to write the tar archive to.
17
19
  compress: Whether to compress the tar archive.
20
+ compression_method: Compression method to use (Default: gzip). Supports "gzip", "bzip2", "xz".
18
21
  encrypt: Whether to encrypt the tar archive.
19
22
  public_key: The RSA public key to encrypt the header with.
20
23
  """
@@ -23,15 +26,19 @@ class TarOutput(Output):
23
26
  self,
24
27
  path: Path,
25
28
  compress: bool = False,
29
+ compression_method: str = "gzip",
26
30
  encrypt: bool = False,
27
31
  public_key: Optional[bytes] = None,
28
32
  ) -> None:
33
+ self.compression = None
29
34
  ext = ".tar" if ".tar" not in path.suffixes else ""
30
35
  mode = "w|" if encrypt else "w:"
31
36
 
32
37
  if compress:
33
- ext += ".gz" if ".gz" not in path.suffixes else ""
34
- mode += "gz"
38
+ self.compression = TAR_COMPRESSION_METHODS.get(compression_method, "gz")
39
+
40
+ ext += f".{self.compression}" if f".{self.compression}" not in path.suffixes else ""
41
+ mode += self.compression
35
42
 
36
43
  if encrypt:
37
44
  ext += ".enc"
@@ -11,6 +11,8 @@ from dissect.target.filesystem import FilesystemEntry
11
11
  from acquire.crypt import EncryptedStream
12
12
  from acquire.outputs.base import Output
13
13
 
14
+ ZIP_COMPRESSION_METHODS = {"deflate": zipfile.ZIP_DEFLATED, "bzip2": zipfile.ZIP_BZIP2, "lzma": zipfile.ZIP_LZMA}
15
+
14
16
 
15
17
  class ZipOutput(Output):
16
18
  """Zip archive acquire output format. Output can be compressed and/or encrypted.
@@ -18,6 +20,7 @@ class ZipOutput(Output):
18
20
  Args:
19
21
  path: The path to write the zip archive to.
20
22
  compress: Whether to compress the zip archive.
23
+ compression_method: Compression method to use (Default: Deflate). Supports "deflate", "bzip2", "lzma".
21
24
  encrypt: Whether to encrypt the zip archive.
22
25
  public_key: The RSA public key to encrypt the header with.
23
26
  """
@@ -26,6 +29,7 @@ class ZipOutput(Output):
26
29
  self,
27
30
  path: Path,
28
31
  compress: bool = False,
32
+ compression_method: str = "deflate",
29
33
  encrypt: bool = False,
30
34
  public_key: Optional[bytes] = None,
31
35
  ) -> None:
@@ -38,7 +42,7 @@ class ZipOutput(Output):
38
42
  self.path = path.with_suffix(path.suffix + ext)
39
43
 
40
44
  if compress:
41
- self.compression = zipfile.ZIP_DEFLATED
45
+ self.compression = ZIP_COMPRESSION_METHODS.get(compression_method, zipfile.ZIP_DEFLATED)
42
46
  else:
43
47
  self.compression = zipfile.ZIP_STORED
44
48
 
@@ -78,32 +82,19 @@ class ZipOutput(Output):
78
82
  info.compress_type = self.compression
79
83
 
80
84
  if entry:
85
+ info.external_attr = self._get_external_attr(entry)
86
+
81
87
  if entry.is_symlink():
82
88
  # System which created ZIP archive, 3 = Unix; 0 = Windows
83
89
  # Windows does not have symlinks, so this must be a unixoid system
84
90
  info.create_system = 3
85
91
 
86
- # The Python zipfile module accepts the 16-bit "Mode" field (that stores st_mode field from
87
- # struct stat, containing user/group/other permissions, setuid/setgid and symlink info, etc) of the
88
- # ASi extra block for Unix as bits 16-31 of the external_attr
89
- unix_st_mode = (
90
- stat.S_IFLNK
91
- | stat.S_IRUSR
92
- | stat.S_IWUSR
93
- | stat.S_IXUSR
94
- | stat.S_IRGRP
95
- | stat.S_IWGRP
96
- | stat.S_IXGRP
97
- | stat.S_IROTH
98
- | stat.S_IWOTH
99
- | stat.S_IXOTH
100
- )
101
- info.external_attr = unix_st_mode << 16
102
-
103
92
  lstat = entry.lstat()
104
93
  if lstat:
94
+ # Python zipfile module does not support timestamps before 1980
105
95
  dt = datetime.fromtimestamp(lstat.st_mtime)
106
- info.date_time = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
96
+ year = max(dt.year, 1980)
97
+ info.date_time = (year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
107
98
 
108
99
  with self.archive.open(info, "w") as zfh:
109
100
  shutil.copyfileobj(fh, zfh)
@@ -113,3 +104,31 @@ class ZipOutput(Output):
113
104
  self.archive.close()
114
105
  if self._fh:
115
106
  self._fh.close()
107
+
108
+ def _get_external_attr(self, entry: FilesystemEntry) -> int:
109
+ """Return the appropriate external attributes of the entry."""
110
+
111
+ # The Python zipfile module accepts the 16-bit "Mode" field (that stores st_mode field from
112
+ # struct stat, containing user/group/other permissions, setuid/setgid and symlink info, etc) of the
113
+ # ASi extra block for Unix as bits 16-31 of the external_attr
114
+ unix_st_mode = stat.S_IFREG
115
+
116
+ if entry.is_symlink():
117
+ unix_st_mode = stat.S_IFLNK
118
+ elif entry.is_dir():
119
+ unix_st_mode = stat.S_IFDIR
120
+
121
+ unix_st_mode = (
122
+ unix_st_mode
123
+ | stat.S_IRUSR
124
+ | stat.S_IWUSR
125
+ | stat.S_IXUSR
126
+ | stat.S_IRGRP
127
+ | stat.S_IWGRP
128
+ | stat.S_IXGRP
129
+ | stat.S_IROTH
130
+ | stat.S_IWOTH
131
+ | stat.S_IXOTH
132
+ ) << 16
133
+
134
+ return unix_st_mode
@@ -16,7 +16,12 @@ from typing import Any, Optional
16
16
 
17
17
  from dissect.target import Target
18
18
 
19
- from acquire.outputs import OUTPUTS
19
+ from acquire.outputs import (
20
+ COMPRESSION_METHODS,
21
+ OUTPUTS,
22
+ TAR_COMPRESSION_METHODS,
23
+ ZIP_COMPRESSION_METHODS,
24
+ )
20
25
  from acquire.uploaders.plugin_registry import UploaderRegistry
21
26
 
22
27
 
@@ -75,7 +80,7 @@ def create_argument_parser(profiles: dict, volatile: dict, modules: dict) -> arg
75
80
  parser.add_argument(
76
81
  "-ot",
77
82
  "--output-type",
78
- choices=OUTPUTS.keys(),
83
+ choices=OUTPUTS,
79
84
  default="tar",
80
85
  help="output type (default: tar)",
81
86
  )
@@ -84,6 +89,11 @@ def create_argument_parser(profiles: dict, volatile: dict, modules: dict) -> arg
84
89
  action=argparse.BooleanOptionalAction,
85
90
  help="compress output (if supported by the output type)",
86
91
  )
92
+ parser.add_argument(
93
+ "--compress-method",
94
+ choices=COMPRESSION_METHODS,
95
+ help="compression method (if supported by the output type)",
96
+ )
87
97
  parser.add_argument(
88
98
  "--encrypt",
89
99
  action=argparse.BooleanOptionalAction,
@@ -320,6 +330,16 @@ def check_and_set_acquire_args(
320
330
  if not args.children and args.skip_parent:
321
331
  raise ValueError("--skip-parent can only be set with --children")
322
332
 
333
+ if args.compress:
334
+ if (args.output_type == "zip" and args.compress_method) and args.compress_method not in ZIP_COMPRESSION_METHODS:
335
+ raise ValueError(
336
+ f"Invalid compression method for zip, allowed are: {', '.join(ZIP_COMPRESSION_METHODS.keys())}"
337
+ )
338
+ if (args.output_type == "tar" and args.compress_method) and args.compress_method not in TAR_COMPRESSION_METHODS:
339
+ raise ValueError(
340
+ f"Invalid compression method for tar, allowed are: {', '.join(TAR_COMPRESSION_METHODS.keys())}"
341
+ )
342
+
323
343
 
324
344
  def get_user_name() -> str:
325
345
  try:
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '3.16.dev5'
16
- __version_tuple__ = version_tuple = (3, 16, 'dev5')
15
+ __version__ = version = '3.16.dev7'
16
+ __version_tuple__ = version_tuple = (3, 16, 'dev7')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: acquire
3
- Version: 3.16.dev5
3
+ Version: 3.16.dev7
4
4
  Summary: A tool to quickly gather forensic artifacts from disk images or a live system into a lightweight container
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -55,6 +55,7 @@ tests/test_minio_uploader.py
55
55
  tests/test_misc_users.py
56
56
  tests/test_outputs_dir.py
57
57
  tests/test_outputs_tar.py
58
+ tests/test_outputs_zip.py
58
59
  tests/test_plugin.py
59
60
  tests/test_utils.py
60
61
  tests/docs/Makefile
@@ -7,9 +7,10 @@ from dissect.target.filesystem import VirtualFilesystem
7
7
  from acquire.outputs import TarOutput
8
8
 
9
9
 
10
- @pytest.fixture
11
- def tar_output(tmp_path: Path) -> TarOutput:
12
- return TarOutput(tmp_path)
10
+ @pytest.fixture(params=[(True, "gzip"), (True, "bzip2"), (True, "xz"), (False, None)])
11
+ def tar_output(tmp_path: Path, request: pytest.FixtureRequest) -> TarOutput:
12
+ compress, compression_method = request.param
13
+ return TarOutput(tmp_path, compress=compress, compression_method=compression_method)
13
14
 
14
15
 
15
16
  @pytest.mark.parametrize(
@@ -28,6 +29,7 @@ def test_tar_output_write_entry(mock_fs: VirtualFilesystem, tar_output: TarOutpu
28
29
  tar_file = tarfile.open(tar_output.path)
29
30
  files = tar_file.getmembers()
30
31
 
32
+ assert tar_output.path.suffix == f".{tar_output.compression}" if tar_output.compression else ".tar"
31
33
  assert len(files) == 1
32
34
 
33
35
  file = files[0]
@@ -0,0 +1,47 @@
1
+ import stat
2
+ import zipfile
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+ from dissect.target.filesystem import VirtualFilesystem
7
+
8
+ from acquire.outputs import ZipOutput
9
+
10
+
11
+ @pytest.fixture(params=[(True, "deflate"), (True, "bzip2"), (True, "lzma"), (False, None)])
12
+ def zip_output(tmp_path: Path, request: pytest.FixtureRequest) -> ZipOutput:
13
+ compress, compression_method = request.param
14
+ return ZipOutput(tmp_path, compress=compress, compression_method=compression_method)
15
+
16
+
17
+ @pytest.mark.parametrize(
18
+ "entry_name",
19
+ [
20
+ "/foo/bar/some-file",
21
+ "/foo/bar/some-symlink",
22
+ "/foo/bar/some-dir",
23
+ ],
24
+ )
25
+ def test_zip_output_write_entry(mock_fs: VirtualFilesystem, zip_output: ZipOutput, entry_name: str) -> None:
26
+ entry = mock_fs.get(entry_name)
27
+
28
+ assert zip_output.compression == zip_output.archive.compression
29
+ zip_output.write_entry(entry_name, entry)
30
+ zip_output.close()
31
+
32
+ zip_file = zipfile.ZipFile(zip_output.path, mode="r")
33
+ files = zip_file.filelist
34
+ assert len(files) == 1
35
+
36
+ file = files[0]
37
+ assert file.filename == entry_name
38
+
39
+ file_type = file.external_attr >> 16
40
+
41
+ # zipfile only supports is_dir(). we have all the information we need to determine the file type in 'external_attr'
42
+ if entry.is_dir():
43
+ assert stat.S_ISDIR(file_type)
44
+ elif entry.is_symlink():
45
+ assert stat.S_ISLNK(file_type)
46
+ elif entry.is_file():
47
+ assert stat.S_ISREG(file_type)
@@ -491,10 +491,11 @@ def test_utils_normalize_path(
491
491
  if os == "windows":
492
492
  case_sensitive = False
493
493
 
494
- with patch.object(mock_target, "os", new=os), patch.object(
495
- mock_target.fs, "_case_sensitive", new=case_sensitive
496
- ), patch.object(mock_target.fs, "_alt_separator", new=("\\" if os == "windows" else "/")), patch.dict(
497
- mock_target.props, {"sysvol_drive": sysvol}
494
+ with (
495
+ patch.object(mock_target, "os", new=os),
496
+ patch.object(mock_target.fs, "_case_sensitive", new=case_sensitive),
497
+ patch.object(mock_target.fs, "_alt_separator", new=("\\" if os == "windows" else "/")),
498
+ patch.dict(mock_target.props, {"sysvol_drive": sysvol}),
498
499
  ):
499
500
  if as_path:
500
501
  path = TargetPath(mock_target.fs, path)
@@ -1,7 +0,0 @@
1
- from acquire.outputs.dir import DirectoryOutput
2
- from acquire.outputs.tar import TarOutput
3
- from acquire.outputs.zip import ZipOutput
4
-
5
- __all__ = ["DirectoryOutput", "TarOutput", "ZipOutput"]
6
-
7
- OUTPUTS = {"tar": TarOutput, "dir": DirectoryOutput, "zip": ZipOutput}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes