acquire 3.5.dev8__tar.gz → 3.6__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 (54) hide show
  1. {acquire-3.5.dev8/acquire.egg-info → acquire-3.6}/PKG-INFO +9 -3
  2. {acquire-3.5.dev8 → acquire-3.6}/README.md +8 -2
  3. {acquire-3.5.dev8 → acquire-3.6}/acquire/acquire.py +109 -10
  4. {acquire-3.5.dev8 → acquire-3.6}/acquire/collector.py +9 -3
  5. {acquire-3.5.dev8 → acquire-3.6}/acquire/utils.py +36 -1
  6. acquire-3.6/acquire/version.py +4 -0
  7. {acquire-3.5.dev8 → acquire-3.6/acquire.egg-info}/PKG-INFO +9 -3
  8. {acquire-3.5.dev8 → acquire-3.6}/acquire.egg-info/SOURCES.txt +5 -1
  9. {acquire-3.5.dev8 → acquire-3.6}/pyproject.toml +0 -1
  10. acquire-3.6/tests/conftest.py +30 -0
  11. acquire-3.6/tests/docs/Makefile +24 -0
  12. acquire-3.6/tests/docs/conf.py +34 -0
  13. acquire-3.6/tests/docs/index.rst +8 -0
  14. {acquire-3.5.dev8 → acquire-3.6}/tests/test_collector.py +72 -34
  15. {acquire-3.5.dev8 → acquire-3.6}/tests/test_utils.py +90 -0
  16. {acquire-3.5.dev8 → acquire-3.6}/tox.ini +21 -0
  17. acquire-3.5.dev8/acquire/version.py +0 -4
  18. {acquire-3.5.dev8 → acquire-3.6}/COPYRIGHT +0 -0
  19. {acquire-3.5.dev8 → acquire-3.6}/LICENSE +0 -0
  20. {acquire-3.5.dev8 → acquire-3.6}/MANIFEST.in +0 -0
  21. {acquire-3.5.dev8 → acquire-3.6}/acquire/__init__.py +0 -0
  22. {acquire-3.5.dev8 → acquire-3.6}/acquire/crypt.py +0 -0
  23. {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/__init__.py +0 -0
  24. {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/__init__.py +0 -0
  25. {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/collect.py +0 -0
  26. {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/exceptions.py +0 -0
  27. {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/handles.py +0 -0
  28. {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/named_objects.py +0 -0
  29. {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/ntdll.py +0 -0
  30. {acquire-3.5.dev8 → acquire-3.6}/acquire/dynamic/windows/types.py +0 -0
  31. {acquire-3.5.dev8 → acquire-3.6}/acquire/esxi.py +0 -0
  32. {acquire-3.5.dev8 → acquire-3.6}/acquire/hashes.py +0 -0
  33. {acquire-3.5.dev8 → acquire-3.6}/acquire/log.py +0 -0
  34. {acquire-3.5.dev8 → acquire-3.6}/acquire/outputs/__init__.py +0 -0
  35. {acquire-3.5.dev8 → acquire-3.6}/acquire/outputs/base.py +0 -0
  36. {acquire-3.5.dev8 → acquire-3.6}/acquire/outputs/dir.py +0 -0
  37. {acquire-3.5.dev8 → acquire-3.6}/acquire/outputs/tar.py +0 -0
  38. {acquire-3.5.dev8 → acquire-3.6}/acquire/tools/__init__.py +0 -0
  39. {acquire-3.5.dev8 → acquire-3.6}/acquire/tools/decrypter.py +0 -0
  40. {acquire-3.5.dev8 → acquire-3.6}/acquire/uploaders/__init__.py +0 -0
  41. {acquire-3.5.dev8 → acquire-3.6}/acquire/uploaders/minio.py +0 -0
  42. {acquire-3.5.dev8 → acquire-3.6}/acquire/uploaders/plugin.py +0 -0
  43. {acquire-3.5.dev8 → acquire-3.6}/acquire/uploaders/plugin_registry.py +0 -0
  44. {acquire-3.5.dev8 → acquire-3.6}/acquire.egg-info/dependency_links.txt +0 -0
  45. {acquire-3.5.dev8 → acquire-3.6}/acquire.egg-info/entry_points.txt +0 -0
  46. {acquire-3.5.dev8 → acquire-3.6}/acquire.egg-info/requires.txt +0 -0
  47. {acquire-3.5.dev8 → acquire-3.6}/acquire.egg-info/top_level.txt +0 -0
  48. {acquire-3.5.dev8 → acquire-3.6}/setup.cfg +0 -0
  49. {acquire-3.5.dev8 → acquire-3.6}/tests/__init__.py +0 -0
  50. {acquire-3.5.dev8 → acquire-3.6}/tests/test_acquire_command.py +0 -0
  51. {acquire-3.5.dev8 → acquire-3.6}/tests/test_esxi_memory.py +0 -0
  52. {acquire-3.5.dev8 → acquire-3.6}/tests/test_file_sorting.py +0 -0
  53. {acquire-3.5.dev8 → acquire-3.6}/tests/test_minio_uploader.py +0 -0
  54. {acquire-3.5.dev8 → acquire-3.6}/tests/test_plugin.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: acquire
3
- Version: 3.5.dev8
3
+ Version: 3.6
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
@@ -36,6 +36,12 @@ However, there are some options available to use the operating system as a fallb
36
36
 
37
37
  For more information, please see [the documentation](https://docs.dissect.tools/en/latest/projects/acquire/index.html).
38
38
 
39
+ ## Requirements
40
+
41
+ This project is part of the Dissect framework and requires Python.
42
+
43
+ Information on the supported Python versions can be found in the Getting Started section of [the documentation](https://docs.dissect.tools/en/latest/index.html#getting-started).
44
+
39
45
  ## Installation
40
46
 
41
47
  `acquire` is available on [PyPI](https://pypi.org/project/acquire/).
@@ -63,12 +69,12 @@ tox
63
69
  ```
64
70
 
65
71
  For a more elaborate explanation on how to build and test the project, please see [the
66
- documentation](https://docs.dissect.tools/en/latest/contributing/developing.html#building-testing).
72
+ documentation](https://docs.dissect.tools/en/latest/contributing/tooling.html).
67
73
 
68
74
  ## Contributing
69
75
 
70
76
  The Dissect project encourages any contribution to the codebase. To make your contribution fit into the project, please
71
- refer to [the style guide](https://docs.dissect.tools/en/latest/contributing/style-guide.html).
77
+ refer to [the development guide](https://docs.dissect.tools/en/latest/contributing/developing.html).
72
78
 
73
79
  ## Copyright and license
74
80
 
@@ -20,6 +20,12 @@ However, there are some options available to use the operating system as a fallb
20
20
 
21
21
  For more information, please see [the documentation](https://docs.dissect.tools/en/latest/projects/acquire/index.html).
22
22
 
23
+ ## Requirements
24
+
25
+ This project is part of the Dissect framework and requires Python.
26
+
27
+ Information on the supported Python versions can be found in the Getting Started section of [the documentation](https://docs.dissect.tools/en/latest/index.html#getting-started).
28
+
23
29
  ## Installation
24
30
 
25
31
  `acquire` is available on [PyPI](https://pypi.org/project/acquire/).
@@ -47,12 +53,12 @@ tox
47
53
  ```
48
54
 
49
55
  For a more elaborate explanation on how to build and test the project, please see [the
50
- documentation](https://docs.dissect.tools/en/latest/contributing/developing.html#building-testing).
56
+ documentation](https://docs.dissect.tools/en/latest/contributing/tooling.html).
51
57
 
52
58
  ## Contributing
53
59
 
54
60
  The Dissect project encourages any contribution to the codebase. To make your contribution fit into the project, please
55
- refer to [the style guide](https://docs.dissect.tools/en/latest/contributing/style-guide.html).
61
+ refer to [the development guide](https://docs.dissect.tools/en/latest/contributing/developing.html).
56
62
 
57
63
  ## Copyright and license
58
64
 
@@ -48,6 +48,7 @@ from acquire.utils import (
48
48
  get_utc_now,
49
49
  get_utc_now_str,
50
50
  is_user_admin,
51
+ normalize_path,
51
52
  parse_acquire_args,
52
53
  persist_execution_report,
53
54
  )
@@ -313,11 +314,10 @@ class NTFS(Module):
313
314
 
314
315
  @classmethod
315
316
  def collect_usnjrnl(cls, collector: Collector, fs, name: str) -> None:
316
- usnjrnl_path = fs.path("$Extend/$Usnjrnl")
317
-
318
317
  try:
318
+ usnjrnl_path = fs.path("$Extend/$Usnjrnl:$J")
319
319
  entry = usnjrnl_path.get()
320
- journal = entry.entry.open("$J")
320
+ journal = entry.open()
321
321
 
322
322
  i = 0
323
323
  while journal.runlist[i][0] is None:
@@ -344,10 +344,10 @@ class NTFS(Module):
344
344
 
345
345
  @classmethod
346
346
  def collect_ntfs_secure(cls, collector: Collector, fs, name: str) -> None:
347
- secure_path = fs.path("$Secure")
348
347
  try:
348
+ secure_path = fs.path("$Secure:$SDS")
349
349
  entry = secure_path.get()
350
- sds = entry.entry.open("$SDS")
350
+ sds = entry.open()
351
351
  collector.output.write(
352
352
  f"{collector.base}/{name}/$Secure:$SDS",
353
353
  sds,
@@ -826,6 +826,7 @@ class Misc(Module):
826
826
  ("dir", "sysvol/Windows/System32/WDI/LogFiles/StartupInfo"),
827
827
  ("dir", "sysvol/windows/sysvol/domain/policies/"),
828
828
  ("dir", "sysvol/windows/system32/GroupPolicy/DataStore/"),
829
+ ("dir", "sysvol/ProgramData/Microsoft/Group Policy/History/"),
829
830
  ("glob", "sysvol/Windows/System32/LogFiles/SUM/*.mdb"),
830
831
  ]
831
832
 
@@ -1101,6 +1102,51 @@ class History(Module):
1101
1102
  ("glob", "/Users/*/Library/Application Support/Chromium/*/Archived History"),
1102
1103
  ("glob", "/Users/*/Library/Application Support/Chromium/*/Last Session"),
1103
1104
  ("glob", "/Users/*/Library/Application Support/Chromium/*/Last Tabs"),
1105
+ # Chrome - RHEL/Ubuntu - DNF
1106
+ ("glob", ".config/google-chrome/*/Bookmarks", from_user_home),
1107
+ ("glob", ".config/google-chrome/*/Favicons", from_user_home),
1108
+ ("glob", ".config/google-chrome/*/History", from_user_home),
1109
+ ("glob", ".config/google-chrome/*/Login Data", from_user_home),
1110
+ ("glob", ".config/google-chrome/*/Login Data For Account", from_user_home),
1111
+ ("glob", ".config/google-chrome/*/Shortcuts", from_user_home),
1112
+ ("glob", ".config/google-chrome/*/Top Sites", from_user_home),
1113
+ ("glob", ".config/google-chrome/*/Web Data", from_user_home),
1114
+ # Chrome - RHEL/Ubuntu - Flatpak
1115
+ ("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Bookmarks", from_user_home),
1116
+ ("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Favicons", from_user_home),
1117
+ ("glob", ".var/app/com.google.Chrome/config/google-chrome/*/History", from_user_home),
1118
+ ("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Login Data", from_user_home),
1119
+ ("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Login Data For Account", from_user_home),
1120
+ ("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Shortcuts", from_user_home),
1121
+ ("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Top Sites", from_user_home),
1122
+ ("glob", ".var/app/com.google.Chrome/config/google-chrome/*/Web Data", from_user_home),
1123
+ # Chromium - RHEL/Ubuntu - DNF/apt
1124
+ ("glob", ".config/chromium/*/Bookmarks", from_user_home),
1125
+ ("glob", ".config/chromium/*/Favicons", from_user_home),
1126
+ ("glob", ".config/chromium/*/History", from_user_home),
1127
+ ("glob", ".config/chromium/*/Login Data", from_user_home),
1128
+ ("glob", ".config/chromium/*/Login Data For Account", from_user_home),
1129
+ ("glob", ".config/chromium/*/Shortcuts", from_user_home),
1130
+ ("glob", ".config/chromium/*/Top Sites", from_user_home),
1131
+ ("glob", ".config/chromium/*/Web Data", from_user_home),
1132
+ # Chromium - RHEL/Ubuntu - Flatpak
1133
+ ("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Bookmarks", from_user_home),
1134
+ ("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Favicons", from_user_home),
1135
+ ("glob", ".var/app/org.chromium.Chromium/config/chromium/*/History", from_user_home),
1136
+ ("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Login Data", from_user_home),
1137
+ ("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Login Data For Account", from_user_home),
1138
+ ("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Shortcuts", from_user_home),
1139
+ ("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Top Sites", from_user_home),
1140
+ ("glob", ".var/app/org.chromium.Chromium/config/chromium/*/Web Data", from_user_home),
1141
+ # Chromium - RHEL/Ubuntu - snap
1142
+ ("glob", "snap/chromium/common/chromium/*/Bookmarks", from_user_home),
1143
+ ("glob", "snap/chromium/common/chromium/*/Favicons", from_user_home),
1144
+ ("glob", "snap/chromium/common/chromium/*/History", from_user_home),
1145
+ ("glob", "snap/chromium/common/chromium/*/Login Data", from_user_home),
1146
+ ("glob", "snap/chromium/common/chromium/*/Login Data For Account", from_user_home),
1147
+ ("glob", "snap/chromium/common/chromium/*/Shortcuts", from_user_home),
1148
+ ("glob", "snap/chromium/common/chromium/*/Top Sites", from_user_home),
1149
+ ("glob", "snap/chromium/common/chromium/*/Web Data", from_user_home),
1104
1150
  # Edge
1105
1151
  ("glob", "AppData/Local/Microsoft/Edge/User Data/*/Bookmarks", from_user_home),
1106
1152
  ("glob", "AppData/Local/Microsoft/Edge/User Data/*/Extension Cookies", from_user_home),
@@ -1165,12 +1211,37 @@ class History(Module):
1165
1211
  ("glob", "/Users/*/Library/Application Support/Microsoft Edge/*/Shortcuts"),
1166
1212
  ("glob", "/Users/*/Library/Application Support/Microsoft Edge/*/Top Sites"),
1167
1213
  ("glob", "/Users/*/Library/Application Support/Microsoft Edge/*/Web Data"),
1168
- # Firefox
1214
+ # Edge - RHEL/Ubuntu - DNF/apt
1215
+ ("glob", ".config/microsoft-edge/*/Bookmarks", from_user_home),
1216
+ ("glob", ".config/microsoft-edge/*/Favicons", from_user_home),
1217
+ ("glob", ".config/microsoft-edge/*/History", from_user_home),
1218
+ ("glob", ".config/microsoft-edge/*/Login Data", from_user_home),
1219
+ ("glob", ".config/microsoft-edge/*/Login Data For Account", from_user_home),
1220
+ ("glob", ".config/microsoft-edge/*/Shortcuts", from_user_home),
1221
+ ("glob", ".config/microsoft-edge/*/Top Sites", from_user_home),
1222
+ ("glob", ".config/microsoft-edge/*/Web Data", from_user_home),
1223
+ # Edge - RHEL/Ubuntu - Flatpak
1224
+ ("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Bookmarks", from_user_home),
1225
+ ("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Favicons", from_user_home),
1226
+ ("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/History", from_user_home),
1227
+ ("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Login Data", from_user_home),
1228
+ ("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Login Data For Account", from_user_home),
1229
+ ("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Shortcuts", from_user_home),
1230
+ ("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Top Sites", from_user_home),
1231
+ ("glob", ".var/app/com.microsoft.Edge/config/microsoft-edge/*/Web Data", from_user_home),
1232
+ # Firefox - Windows
1169
1233
  ("glob", "AppData/Local/Mozilla/Firefox/Profiles/*/*.sqlite*", from_user_home),
1170
1234
  ("glob", "AppData/Roaming/Mozilla/Firefox/Profiles/*/*.sqlite*", from_user_home),
1171
1235
  ("glob", "Application Data/Mozilla/Firefox/Profiles/*/*.sqlite*", from_user_home),
1236
+ # Firefox - macOS
1172
1237
  ("glob", "/Users/*/Library/Application Support/Firefox/Profiles/*/*.sqlite*"),
1173
- # Safari
1238
+ # Firefox - RHEL/Ubuntu - Flatpak
1239
+ ("glob", ".var/app/org.mozilla.firefox/.mozilla/firefox/*/*.sqlite", from_user_home),
1240
+ # Firefox - RHEL/Ubuntu - DNF/apt
1241
+ ("glob", ".mozilla/firefox/*/*.sqlite", from_user_home),
1242
+ # Firefox - RHEL/Ubuntu - snap
1243
+ ("glob", "snap/firefox/common/.mozilla/firefox/*/*.sqlite", from_user_home),
1244
+ # Safari - macOS
1174
1245
  ("glob", "/Users/*/Library/Safari/Bookmarks.plist"),
1175
1246
  ("glob", "/Users/*/Library/Safari/Downloads.plist"),
1176
1247
  ("glob", "/Users/*/Library/Safari/Extensions/Extensions.plist"),
@@ -1551,17 +1622,34 @@ def print_volumes_overview(target):
1551
1622
  log.info("")
1552
1623
 
1553
1624
 
1625
+ def print_acquire_warning(target: Target) -> None:
1626
+ if target.os != "windows":
1627
+ log.warning("========================================== WARNING ==========================================")
1628
+ log.warning("")
1629
+ log.warning(
1630
+ "The support for operating system '%s' is experimental. Some artifacts may not yet be included and some ",
1631
+ target.os,
1632
+ )
1633
+ log.warning("features may not work as expected. Please notify upstream for any missing artifacts or features.")
1634
+ log.warning("")
1635
+ log.warning("========================================== WARNING ==========================================")
1636
+
1637
+
1554
1638
  def acquire_target(target: Target, args: argparse.Namespace, output_ts: Optional[str] = None):
1555
1639
  output_ts = output_ts or get_utc_now_str()
1556
1640
  if args.log_to_dir:
1557
1641
  log_file = args.log_path.joinpath(format_output_name("Unknown", output_ts, "log"))
1642
+ # This will also rename the log file on disk, which was opened in main(), if the name is different
1558
1643
  reconfigure_log_file(log, log_file, delay=True)
1559
1644
  else:
1560
1645
  log_file = args.log_path
1561
1646
 
1562
1647
  files = []
1648
+ skip_list = set()
1563
1649
  if log_file:
1564
1650
  files.append(log_file)
1651
+ if target.path.name == "local":
1652
+ skip_list.add(normalize_path(target, log_file, resolve=True))
1565
1653
 
1566
1654
  print_disks_overview(target)
1567
1655
  print_volumes_overview(target)
@@ -1595,6 +1683,8 @@ def acquire_target(target: Target, args: argparse.Namespace, output_ts: Optional
1595
1683
  if target.os == "windows":
1596
1684
  mount_all_ntfs_filesystems(target)
1597
1685
 
1686
+ print_acquire_warning(target)
1687
+
1598
1688
  modules_selected = {}
1599
1689
  modules_successful = []
1600
1690
  modules_failed = {}
@@ -1638,14 +1728,19 @@ def acquire_target(target: Target, args: argparse.Namespace, output_ts: Optional
1638
1728
  # Prepare log file and output file names
1639
1729
  if log_file_handler and args.log_to_dir:
1640
1730
  log_file = format_output_name(target.name, output_ts, "log")
1731
+ # This will also rename the log file on disk, which was opened and written previously.
1641
1732
  log_file_handler.set_filename(log_file)
1642
- log.info("Logging to file %s", Path(log_file_handler.baseFilename).resolve())
1733
+ log_path = Path(log_file_handler.baseFilename).resolve()
1734
+ log.info("Logging to file %s", log_path)
1643
1735
  files = [log_file_handler.baseFilename]
1736
+ if target.path.name == "local":
1737
+ skip_list = {normalize_path(target, log_path, resolve=True)}
1644
1738
 
1645
1739
  output_path = args.output
1646
1740
  if output_path.is_dir():
1647
1741
  output_dir = format_output_name(target.name, output_ts)
1648
- output_path = output_path.joinpath(output_dir).resolve()
1742
+ output_path = output_path.joinpath(output_dir)
1743
+ output_path = output_path.resolve()
1649
1744
 
1650
1745
  output = OUTPUTS[args.output_type](
1651
1746
  output_path,
@@ -1654,15 +1749,19 @@ def acquire_target(target: Target, args: argparse.Namespace, output_ts: Optional
1654
1749
  public_key=args.public_key,
1655
1750
  )
1656
1751
  files.append(output.path)
1752
+ if target.path.name == "local":
1753
+ skip_list.add(normalize_path(target, output.path, resolve=True))
1657
1754
 
1658
1755
  log.info("Writing output to %s", output.path)
1756
+ if skip_list:
1757
+ log.info("Skipping own files: %s", ", ".join(skip_list))
1659
1758
  log.info("")
1660
1759
 
1661
1760
  dir_base = "fs"
1662
1761
  if target.os != "windows":
1663
1762
  dir_base = "fs/$rootfs$"
1664
1763
 
1665
- with Collector(target, output, base=dir_base) as collector:
1764
+ with Collector(target, output, base=dir_base, skip_list=skip_list) as collector:
1666
1765
  # Acquire specified files
1667
1766
  if args.file or args.directory or args.glob:
1668
1767
  log.info("*** Acquiring specified paths")
@@ -20,7 +20,7 @@ from dissect.target.exceptions import (
20
20
  )
21
21
  from dissect.target.helpers import fsutil
22
22
 
23
- from acquire.utils import StrEnum, get_formatted_exception
23
+ from acquire.utils import StrEnum, get_formatted_exception, normalize_path
24
24
 
25
25
  if TYPE_CHECKING:
26
26
  from acquire.outputs.base import Output
@@ -179,10 +179,11 @@ class Collector:
179
179
  METADATA_BASE = "$metadata$"
180
180
  COMMAND_OUTPUT_BASE = f"{METADATA_BASE}/command-output"
181
181
 
182
- def __init__(self, target: Target, output: Output, base="fs"):
182
+ def __init__(self, target: Target, output: Output, base: str = "fs", skip_list: Optional[set] = None):
183
183
  self.target = target
184
184
  self.output = output
185
185
  self.base = base
186
+ self.skip_list = skip_list or set()
186
187
 
187
188
  self.report = CollectionReport()
188
189
  self.bound_module_name = None
@@ -358,7 +359,7 @@ class Collector:
358
359
  glob_is_empty = True
359
360
  for entry in self.target.fs.path("/").glob(pattern.lstrip("/")):
360
361
  glob_is_empty = False
361
- self.collect_path(entry)
362
+ self.collect_path(entry, module_name=module_name)
362
363
  except Exception:
363
364
  log.error("- Failed to collect glob %s", pattern, exc_info=True)
364
365
  self.report.add_glob_failed(module_name, pattern)
@@ -383,6 +384,11 @@ class Collector:
383
384
  if not isinstance(path, fsutil.TargetPath):
384
385
  path = self.target.fs.path(path)
385
386
 
387
+ if self.skip_list and normalize_path(self.target, path) in self.skip_list:
388
+ self.report.add_path_failed(module_name, path)
389
+ log.error("- Skipping collection of %s, path is on the skip list", path)
390
+ return
391
+
386
392
  try:
387
393
  # If a path does not exist, is_dir(), is_file() and is_symlink() will return False (and not raise an
388
394
  # exception), so we need to explicitly trigger an exception for this using path.get().
@@ -4,6 +4,8 @@ import datetime
4
4
  import getpass
5
5
  import json
6
6
  import os
7
+ import pathlib
8
+ import re
7
9
  import sys
8
10
  import textwrap
9
11
  import traceback
@@ -13,6 +15,7 @@ from pathlib import Path
13
15
  from stat import S_IRGRP, S_IROTH, S_IRUSR
14
16
  from typing import Any, Optional
15
17
 
18
+ from dissect.target import Target
16
19
  from dissect.util.stream import AlignedStream
17
20
 
18
21
  from acquire.outputs import OUTPUTS
@@ -63,7 +66,20 @@ class VolatileStream(AlignedStream):
63
66
  return False
64
67
 
65
68
  def _read(self, offset: int, length: int) -> bytes:
66
- return os.read(self.fd, min(length, self.size - offset))
69
+ result = []
70
+ while length:
71
+ try:
72
+ buf = os.read(self.fd, min(length, self.size - offset))
73
+ except BlockingIOError:
74
+ break
75
+
76
+ if not buf:
77
+ break
78
+
79
+ result.append(buf)
80
+ offset += len(buf)
81
+ length -= len(buf)
82
+ return b"".join(result)
67
83
 
68
84
 
69
85
  class StrEnum(str, Enum):
@@ -379,3 +395,22 @@ def format_output_name(prefix: str, postfix: Optional[str] = None, ext: Optional
379
395
  def persist_execution_report(path: Path, report_data: dict) -> Path:
380
396
  with open(path, "w") as f:
381
397
  f.write(json.dumps(report_data, sort_keys=True, indent=4))
398
+
399
+
400
+ SYSVOL_SUBST = re.compile(r"^(/\?\?/)?[cC]:")
401
+
402
+
403
+ def normalize_path(target: Target, path: pathlib.Path, resolve: bool = False) -> str:
404
+ if resolve:
405
+ path = path.resolve()
406
+
407
+ path = path.as_posix()
408
+
409
+ if not target.fs.case_sensitive:
410
+ path = path.lower()
411
+
412
+ if target.os == "windows":
413
+ # As dissect.target always maps c: onto sysvol, we can do this substitution here.
414
+ path = SYSVOL_SUBST.sub("sysvol", path)
415
+
416
+ return path
@@ -0,0 +1,4 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ __version__ = version = '3.6'
4
+ __version_tuple__ = version_tuple = (3, 6)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: acquire
3
- Version: 3.5.dev8
3
+ Version: 3.6
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
@@ -36,6 +36,12 @@ However, there are some options available to use the operating system as a fallb
36
36
 
37
37
  For more information, please see [the documentation](https://docs.dissect.tools/en/latest/projects/acquire/index.html).
38
38
 
39
+ ## Requirements
40
+
41
+ This project is part of the Dissect framework and requires Python.
42
+
43
+ Information on the supported Python versions can be found in the Getting Started section of [the documentation](https://docs.dissect.tools/en/latest/index.html#getting-started).
44
+
39
45
  ## Installation
40
46
 
41
47
  `acquire` is available on [PyPI](https://pypi.org/project/acquire/).
@@ -63,12 +69,12 @@ tox
63
69
  ```
64
70
 
65
71
  For a more elaborate explanation on how to build and test the project, please see [the
66
- documentation](https://docs.dissect.tools/en/latest/contributing/developing.html#building-testing).
72
+ documentation](https://docs.dissect.tools/en/latest/contributing/tooling.html).
67
73
 
68
74
  ## Contributing
69
75
 
70
76
  The Dissect project encourages any contribution to the codebase. To make your contribution fit into the project, please
71
- refer to [the style guide](https://docs.dissect.tools/en/latest/contributing/style-guide.html).
77
+ refer to [the development guide](https://docs.dissect.tools/en/latest/contributing/developing.html).
72
78
 
73
79
  ## Copyright and license
74
80
 
@@ -38,10 +38,14 @@ acquire/uploaders/minio.py
38
38
  acquire/uploaders/plugin.py
39
39
  acquire/uploaders/plugin_registry.py
40
40
  tests/__init__.py
41
+ tests/conftest.py
41
42
  tests/test_acquire_command.py
42
43
  tests/test_collector.py
43
44
  tests/test_esxi_memory.py
44
45
  tests/test_file_sorting.py
45
46
  tests/test_minio_uploader.py
46
47
  tests/test_plugin.py
47
- tests/test_utils.py
48
+ tests/test_utils.py
49
+ tests/docs/Makefile
50
+ tests/docs/conf.py
51
+ tests/docs/index.rst
@@ -53,5 +53,4 @@ license-files = ["LICENSE", "COPYRIGHT"]
53
53
  include = ["acquire", "acquire.*"]
54
54
 
55
55
  [tool.setuptools_scm]
56
- local_scheme = "no-local-version"
57
56
  write_to = "acquire/version.py"
@@ -0,0 +1,30 @@
1
+ from unittest.mock import Mock
2
+
3
+ import pytest
4
+ from dissect.target import Target
5
+ from dissect.target.filesystem import VirtualFile, VirtualFilesystem, VirtualSymlink
6
+
7
+
8
+ @pytest.fixture
9
+ def mock_file() -> Mock:
10
+ return Mock()
11
+
12
+
13
+ @pytest.fixture
14
+ def mock_fs(mock_file) -> VirtualFilesystem:
15
+ fs = VirtualFilesystem(case_sensitive=False)
16
+ fs.makedirs("/foo/bar")
17
+ fs.map_file_entry("/foo/bar/some-file", VirtualFile(fs, "some-file", mock_file))
18
+ fs.map_file_entry("/foo/bar/own-file", VirtualFile(fs, "own-file", mock_file))
19
+ fs.map_file_entry("/foo/bar/some-symlink", VirtualSymlink(fs, "some-symlink", "/foo/bar/some-file"))
20
+ fs.map_file_entry("/foo/own-symlink", VirtualSymlink(fs, "own-symlink", "/foo/bar/own-file"))
21
+ return fs
22
+
23
+
24
+ @pytest.fixture
25
+ def mock_target(mock_fs) -> Target:
26
+ target = Target()
27
+ target.fs.mount("/", mock_fs)
28
+ target.filesystems.add(mock_fs)
29
+ target.os = "mock"
30
+ return target
@@ -0,0 +1,24 @@
1
+ # Minimal makefile for Sphinx documentation
2
+ #
3
+
4
+ # You can set these variables from the command line, and also
5
+ # from the environment for the first two.
6
+ SPHINXOPTS ?= -jauto
7
+ SPHINXBUILD ?= sphinx-build
8
+ SOURCEDIR = .
9
+ BUILDDIR = build
10
+
11
+ # Put it first so that "make" without argument is like "make help".
12
+ help:
13
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14
+
15
+ .PHONY: clean help Makefile
16
+
17
+ clean: Makefile
18
+ rm -rf api
19
+ @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
20
+
21
+ # Catch-all target: route all unknown targets to Sphinx using the new
22
+ # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
23
+ %: Makefile
24
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@@ -0,0 +1,34 @@
1
+ extensions = [
2
+ "autoapi.extension",
3
+ "sphinx.ext.autodoc",
4
+ "sphinx.ext.autosectionlabel",
5
+ "sphinx.ext.doctest",
6
+ "sphinx.ext.napoleon",
7
+ "sphinx_argparse_cli",
8
+ ]
9
+
10
+ exclude_patterns = []
11
+
12
+ html_theme = "furo"
13
+
14
+ autoapi_type = "python"
15
+ autoapi_dirs = ["../../acquire/"]
16
+ autoapi_ignore = ["*tests*", "*.tox*", "*venv*", "*examples*"]
17
+ autoapi_python_use_implicit_namespaces = True
18
+ autoapi_add_toctree_entry = False
19
+ autoapi_root = "api"
20
+ autoapi_options = [
21
+ "members",
22
+ "undoc-members",
23
+ "show-inheritance",
24
+ "show-module-summary",
25
+ "special-members",
26
+ "imported-members",
27
+ ]
28
+ autoapi_keep_files = True
29
+ autoapi_template_dir = "_templates/autoapi"
30
+
31
+ autodoc_typehints = "signature"
32
+ autodoc_member_order = "groupwise"
33
+
34
+ autosectionlabel_prefix_document = True
@@ -0,0 +1,8 @@
1
+ API Reference
2
+ =============
3
+
4
+ .. toctree::
5
+ :maxdepth: 1
6
+ :glob:
7
+
8
+ /api/*/*/index
@@ -9,7 +9,7 @@ from dissect.target.exceptions import (
9
9
  NotASymlinkError,
10
10
  SymlinkRecursionError,
11
11
  )
12
- from dissect.target.filesystem import VirtualFile, VirtualFilesystem, VirtualSymlink
12
+ from dissect.target.filesystem import VirtualFilesystem
13
13
 
14
14
  from acquire.collector import Collector
15
15
 
@@ -36,28 +36,6 @@ def test_collector() -> None:
36
36
  assert not mock_log.info.call_args.args[0] == "- Collecting file %s: Skipped (DEDUP)"
37
37
 
38
38
 
39
- @pytest.fixture
40
- def mock_file() -> Mock:
41
- return Mock()
42
-
43
-
44
- @pytest.fixture
45
- def mock_fs(mock_file) -> VirtualFilesystem:
46
- fs = VirtualFilesystem(case_sensitive=False)
47
- fs.makedirs("/foo/bar")
48
- fs.map_file_entry("/foo/bar/some-file", VirtualFile(fs, "some-file", mock_file))
49
- fs.map_file_entry("/foo/bar/some-symlink", VirtualSymlink(fs, "some-symlink", "/foo/bar/some_file"))
50
- return fs
51
-
52
-
53
- @pytest.fixture
54
- def mock_target(mock_fs) -> Target:
55
- target = Target()
56
- target.fs.mount("/", mock_fs)
57
- target.filesystems.add(mock_fs)
58
- return target
59
-
60
-
61
39
  @pytest.fixture
62
40
  def mock_collector(mock_target) -> Collector:
63
41
  collector = Collector(mock_target, Mock())
@@ -106,24 +84,84 @@ def test_collector_collect_path_file(mock_collector) -> None:
106
84
 
107
85
  def test_collector_collect_path_symlink(mock_collector) -> None:
108
86
  with patch.object(mock_collector, "collect_symlink", autospec=True):
109
- mock_collector.collect_path(
110
- "/foo/bar/some-symlink",
111
- follow=False,
112
- seen_paths=MOCK_SEEN_PATHS,
113
- module_name=MOCK_MODULE_NAME,
114
- )
115
- mock_collector.collect_symlink.assert_called()
87
+ with patch.object(mock_collector, "collect_file", autospec=True):
88
+ mock_collector.collect_path(
89
+ "/foo/bar/some-symlink",
90
+ follow=False,
91
+ seen_paths=MOCK_SEEN_PATHS,
92
+ module_name=MOCK_MODULE_NAME,
93
+ )
94
+ mock_collector.collect_symlink.assert_called()
95
+ mock_collector.collect_file.assert_not_called()
116
96
 
117
97
 
118
98
  def test_collector_collect_path_symlink_follow(mock_collector) -> None:
119
99
  with patch.object(mock_collector, "collect_symlink", autospec=True):
120
- mock_collector.collect_path(
100
+ with patch.object(mock_collector, "collect_file", autospec=True):
101
+ mock_collector.collect_path(
102
+ "/foo/bar/some-symlink",
103
+ follow=True,
104
+ seen_paths=MOCK_SEEN_PATHS,
105
+ module_name=MOCK_MODULE_NAME,
106
+ )
107
+ mock_collector.collect_symlink.assert_called()
108
+ mock_collector.collect_file.assert_called()
109
+
110
+
111
+ @pytest.mark.parametrize(
112
+ "path, symlink_called, file_called",
113
+ [
114
+ (
115
+ "/foo/bar/own-file",
116
+ False,
117
+ False,
118
+ ),
119
+ (
120
+ "/foo/own-symlink",
121
+ True,
122
+ False,
123
+ ),
124
+ (
125
+ "/foo/bar/some-file",
126
+ False,
127
+ True,
128
+ ),
129
+ (
121
130
  "/foo/bar/some-symlink",
122
- follow=True,
123
- seen_paths=MOCK_SEEN_PATHS,
131
+ True,
132
+ True,
133
+ ),
134
+ ],
135
+ )
136
+ def test_collector_collect_path_skip_list(mock_collector, path, symlink_called, file_called) -> None:
137
+ with patch.object(mock_collector, "skip_list", new={"/foo/bar/own-file"}):
138
+ with patch.object(mock_collector, "collect_symlink", autospec=True):
139
+ with patch.object(mock_collector, "collect_file", autospec=True):
140
+ mock_collector.collect_path(
141
+ path,
142
+ follow=True,
143
+ seen_paths=MOCK_SEEN_PATHS,
144
+ module_name=MOCK_MODULE_NAME,
145
+ )
146
+ if symlink_called:
147
+ mock_collector.collect_symlink.assert_called()
148
+ else:
149
+ mock_collector.collect_symlink.assert_not_called()
150
+
151
+ if file_called:
152
+ mock_collector.collect_file.assert_called()
153
+ else:
154
+ mock_collector.collect_file.assert_not_called()
155
+
156
+
157
+ def test_collector_collect_glob(mock_collector) -> None:
158
+ with patch.object(mock_collector, "collect_file", autospec=True):
159
+ mock_collector.collect_glob(
160
+ "/foo/bar/*",
124
161
  module_name=MOCK_MODULE_NAME,
125
162
  )
126
- mock_collector.collect_symlink.assert_called()
163
+ assert len(mock_collector.collect_file.mock_calls) == 3
164
+ assert mock_collector.collect_file.call_args.kwargs.get("module_name", None) == MOCK_MODULE_NAME
127
165
 
128
166
 
129
167
  def test_collector_collect_path_non_existing_file(mock_collector) -> None:
@@ -3,12 +3,14 @@ import pathlib
3
3
  from unittest.mock import MagicMock, patch
4
4
 
5
5
  import pytest
6
+ from dissect.target import Target
6
7
 
7
8
  from acquire.acquire import MODULES, PROFILES
8
9
  from acquire.utils import (
9
10
  check_and_set_acquire_args,
10
11
  check_and_set_log_args,
11
12
  create_argument_parser,
13
+ normalize_path,
12
14
  )
13
15
 
14
16
 
@@ -290,3 +292,91 @@ def test_check_and_set_acquire_args_cagent():
290
292
 
291
293
  assert args.cagent_key == cagent_key
292
294
  assert args.cagent_certificate == cagent_certificate
295
+
296
+
297
+ @pytest.mark.parametrize(
298
+ "path, resolve, norm_path, case_sensitive, os",
299
+ [
300
+ (
301
+ pathlib.Path("/foo/bar"),
302
+ False,
303
+ "/foo/bar",
304
+ True,
305
+ "dummy",
306
+ ),
307
+ (
308
+ pathlib.Path("/foo/BAR"),
309
+ False,
310
+ "/foo/bar",
311
+ False,
312
+ "dummy",
313
+ ),
314
+ (
315
+ pathlib.Path("/foo/BAR"),
316
+ False,
317
+ "/foo/BAR",
318
+ True,
319
+ "dummy",
320
+ ),
321
+ (
322
+ pathlib.Path("/foo/../bar"),
323
+ False,
324
+ "/foo/../bar",
325
+ True,
326
+ "dummy",
327
+ ),
328
+ (
329
+ pathlib.Path("/foo/../foo/bar"),
330
+ True,
331
+ "/foo/bar",
332
+ True,
333
+ "dummy",
334
+ ),
335
+ (
336
+ pathlib.PureWindowsPath("c:\\foo\\bar"),
337
+ False,
338
+ "sysvol/foo/bar",
339
+ False,
340
+ "windows",
341
+ ),
342
+ (
343
+ pathlib.PureWindowsPath("C:\\foo\\bar"),
344
+ False,
345
+ "sysvol/foo/bar",
346
+ False,
347
+ "windows",
348
+ ),
349
+ (
350
+ pathlib.PureWindowsPath("\\??\\C:\\foo\\bar"),
351
+ False,
352
+ "sysvol/foo/bar",
353
+ False,
354
+ "windows",
355
+ ),
356
+ (
357
+ pathlib.PureWindowsPath("\\??\\c:\\foo\\bar"),
358
+ False,
359
+ "sysvol/foo/bar",
360
+ False,
361
+ "windows",
362
+ ),
363
+ (
364
+ pathlib.PureWindowsPath("D:\\foo\\bar"),
365
+ False,
366
+ "d:/foo/bar",
367
+ False,
368
+ "windows",
369
+ ),
370
+ ],
371
+ )
372
+ def test_utils_normalize_path(
373
+ mock_target: Target,
374
+ path: pathlib.Path,
375
+ resolve: bool,
376
+ norm_path: str,
377
+ case_sensitive: bool,
378
+ os: str,
379
+ ) -> None:
380
+ with patch.object(mock_target, "os", new=os):
381
+ with patch.object(mock_target.fs, "_case_sensitive", new=case_sensitive):
382
+ assert normalize_path(mock_target, path, resolve=resolve) == norm_path
@@ -56,3 +56,24 @@ extend-ignore =
56
56
  # See https://github.com/PyCQA/pycodestyle/issues/373
57
57
  E203,
58
58
  statistics = True
59
+ exclude = acquire/version.py
60
+
61
+ [testenv:docs-build]
62
+ allowlist_externals = make
63
+ deps =
64
+ sphinx
65
+ sphinx-autoapi
66
+ sphinx_argparse_cli
67
+ sphinx-copybutton
68
+ sphinx-design
69
+ furo
70
+ commands =
71
+ make -C tests/docs clean
72
+ make -C tests/docs html
73
+
74
+ [testenv:docs-linkcheck]
75
+ allowlist_externals = make
76
+ deps = {[testenv:docs-build]deps}
77
+ commands =
78
+ make -C tests/docs clean
79
+ make -C tests/docs linkcheck
@@ -1,4 +0,0 @@
1
- # file generated by setuptools_scm
2
- # don't change, don't track in version control
3
- __version__ = version = '3.5.dev8'
4
- __version_tuple__ = version_tuple = (3, 5, 'dev8')
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
File without changes
File without changes
File without changes
File without changes