dissect.target 3.13.dev26__py3-none-any.whl → 3.14__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (138) hide show
  1. dissect/target/container.py +9 -1
  2. dissect/target/containers/asdf.py +2 -0
  3. dissect/target/containers/ewf.py +2 -0
  4. dissect/target/containers/hdd.py +2 -0
  5. dissect/target/containers/hds.py +2 -0
  6. dissect/target/containers/qcow2.py +2 -0
  7. dissect/target/containers/raw.py +2 -0
  8. dissect/target/containers/split.py +2 -0
  9. dissect/target/containers/vdi.py +2 -0
  10. dissect/target/containers/vhd.py +2 -0
  11. dissect/target/containers/vhdx.py +2 -0
  12. dissect/target/containers/vmdk.py +2 -0
  13. dissect/target/filesystem.py +108 -15
  14. dissect/target/filesystems/ad1.py +1 -1
  15. dissect/target/filesystems/btrfs.py +180 -0
  16. dissect/target/filesystems/cb.py +4 -4
  17. dissect/target/filesystems/config.py +161 -31
  18. dissect/target/filesystems/dir.py +1 -1
  19. dissect/target/filesystems/exfat.py +1 -1
  20. dissect/target/filesystems/extfs.py +5 -1
  21. dissect/target/filesystems/fat.py +1 -1
  22. dissect/target/filesystems/ffs.py +1 -1
  23. dissect/target/filesystems/itunes.py +1 -1
  24. dissect/target/filesystems/ntfs.py +1 -1
  25. dissect/target/filesystems/smb.py +1 -1
  26. dissect/target/filesystems/squashfs.py +1 -1
  27. dissect/target/filesystems/tar.py +1 -1
  28. dissect/target/filesystems/vmfs.py +1 -1
  29. dissect/target/filesystems/xfs.py +1 -1
  30. dissect/target/filesystems/zip.py +1 -1
  31. dissect/target/helpers/cache.py +2 -2
  32. dissect/target/helpers/configutil.py +283 -83
  33. dissect/target/helpers/fsutil.py +9 -6
  34. dissect/target/helpers/hashutil.py +20 -19
  35. dissect/target/helpers/utils.py +14 -3
  36. dissect/target/loaders/ad1.py +1 -1
  37. dissect/target/loaders/asdf.py +1 -1
  38. dissect/target/loaders/log.py +2 -2
  39. dissect/target/loaders/smb.py +23 -13
  40. dissect/target/loaders/targetd.py +12 -2
  41. dissect/target/loaders/vma.py +1 -1
  42. dissect/target/loaders/xva.py +1 -1
  43. dissect/target/plugin.py +14 -2
  44. dissect/target/plugins/apps/av/sophos.py +1 -2
  45. dissect/target/plugins/apps/av/symantec.py +3 -4
  46. dissect/target/plugins/apps/av/trendmicro.py +2 -3
  47. dissect/target/plugins/{browsers → apps/browser}/chrome.py +6 -3
  48. dissect/target/plugins/{browsers → apps/browser}/chromium.py +18 -13
  49. dissect/target/plugins/{browsers → apps/browser}/edge.py +6 -3
  50. dissect/target/plugins/{browsers → apps/browser}/firefox.py +3 -7
  51. dissect/target/plugins/{browsers → apps/browser}/iexplore.py +14 -4
  52. dissect/target/plugins/apps/remoteaccess/teamviewer.py +55 -27
  53. dissect/target/plugins/apps/ssh/opensshd.py +31 -30
  54. dissect/target/plugins/apps/{webservers → webserver}/apache.py +1 -1
  55. dissect/target/plugins/apps/{webservers → webserver}/caddy.py +1 -1
  56. dissect/target/plugins/apps/{webservers → webserver}/iis.py +1 -1
  57. dissect/target/plugins/apps/{webservers → webserver}/nginx.py +1 -1
  58. dissect/target/plugins/child/hyperv.py +1 -2
  59. dissect/target/plugins/child/vmware_workstation.py +1 -3
  60. dissect/target/plugins/filesystem/acquire_handles.py +2 -0
  61. dissect/target/plugins/filesystem/acquire_hash.py +1 -7
  62. dissect/target/plugins/filesystem/icat.py +5 -5
  63. dissect/target/plugins/filesystem/ntfs/mft.py +2 -2
  64. dissect/target/plugins/filesystem/ntfs/mft_timeline.py +2 -2
  65. dissect/target/plugins/filesystem/ntfs/usnjrnl.py +2 -3
  66. dissect/target/plugins/filesystem/resolver.py +1 -1
  67. dissect/target/plugins/filesystem/unix/capability.py +77 -66
  68. dissect/target/plugins/filesystem/walkfs.py +25 -19
  69. dissect/target/plugins/filesystem/yara.py +20 -19
  70. dissect/target/plugins/general/config.py +28 -11
  71. dissect/target/plugins/os/unix/_os.py +28 -21
  72. dissect/target/plugins/os/unix/bsd/osx/user.py +1 -3
  73. dissect/target/plugins/os/unix/cronjobs.py +4 -16
  74. dissect/target/plugins/os/unix/{linux/esxi → esxi}/_os.py +5 -6
  75. dissect/target/plugins/os/unix/generic.py +5 -1
  76. dissect/target/plugins/os/unix/history.py +2 -1
  77. dissect/target/plugins/os/unix/linux/_os.py +12 -5
  78. dissect/target/plugins/os/unix/linux/services.py +112 -0
  79. dissect/target/plugins/os/unix/linux/suse/zypper.py +4 -4
  80. dissect/target/plugins/os/unix/locale.py +3 -1
  81. dissect/target/plugins/os/unix/log/journal.py +7 -6
  82. dissect/target/plugins/os/unix/packagemanager.py +3 -3
  83. dissect/target/plugins/os/unix/shadow.py +1 -1
  84. dissect/target/plugins/os/windows/_os.py +2 -1
  85. dissect/target/plugins/os/windows/amcache.py +9 -10
  86. dissect/target/plugins/os/windows/catroot.py +2 -2
  87. dissect/target/plugins/os/windows/cim.py +5 -4
  88. dissect/target/plugins/os/windows/datetime.py +4 -1
  89. dissect/target/plugins/os/windows/defender.py +3 -3
  90. dissect/target/plugins/os/windows/generic.py +10 -11
  91. dissect/target/plugins/os/windows/lnk.py +6 -6
  92. dissect/target/plugins/os/windows/log/amcache.py +3 -5
  93. dissect/target/plugins/os/windows/log/pfro.py +1 -3
  94. dissect/target/plugins/os/windows/prefetch.py +5 -6
  95. dissect/target/plugins/os/windows/recyclebin.py +3 -4
  96. dissect/target/plugins/os/windows/regf/7zip.py +2 -4
  97. dissect/target/plugins/os/windows/regf/bam.py +1 -2
  98. dissect/target/plugins/os/windows/regf/cit.py +4 -5
  99. dissect/target/plugins/os/windows/regf/mru.py +6 -2
  100. dissect/target/plugins/os/windows/regf/muicache.py +1 -3
  101. dissect/target/plugins/os/windows/regf/recentfilecache.py +1 -2
  102. dissect/target/plugins/os/windows/regf/shimcache.py +1 -2
  103. dissect/target/plugins/os/windows/regf/trusteddocs.py +1 -1
  104. dissect/target/plugins/os/windows/regf/userassist.py +1 -2
  105. dissect/target/plugins/os/windows/services.py +2 -4
  106. dissect/target/plugins/os/windows/sru.py +4 -4
  107. dissect/target/plugins/os/windows/startupinfo.py +5 -6
  108. dissect/target/plugins/os/windows/syscache.py +2 -3
  109. dissect/target/target.py +65 -32
  110. dissect/target/tools/info.py +2 -1
  111. dissect/target/tools/mount.py +2 -12
  112. dissect/target/tools/shell.py +3 -2
  113. dissect/target/volume.py +10 -9
  114. dissect/target/volumes/bde.py +1 -1
  115. dissect/target/volumes/ddf.py +2 -0
  116. dissect/target/volumes/disk.py +2 -0
  117. dissect/target/volumes/luks.py +1 -1
  118. dissect/target/volumes/lvm.py +2 -0
  119. dissect/target/volumes/md.py +2 -0
  120. dissect/target/volumes/vmfs.py +2 -0
  121. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/METADATA +2 -1
  122. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/RECORD +137 -136
  123. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/WHEEL +1 -1
  124. dissect/target/plugins/os/unix/services.py +0 -151
  125. /dissect/target/plugins/apps/{containers → browser}/__init__.py +0 -0
  126. /dissect/target/plugins/{browsers → apps/browser}/browser.py +0 -0
  127. /dissect/target/plugins/apps/{vpns → container}/__init__.py +0 -0
  128. /dissect/target/plugins/apps/{containers → container}/docker.py +0 -0
  129. /dissect/target/plugins/apps/{webservers → vpn}/__init__.py +0 -0
  130. /dissect/target/plugins/apps/{vpns → vpn}/openvpn.py +0 -0
  131. /dissect/target/plugins/apps/{vpns → vpn}/wireguard.py +0 -0
  132. /dissect/target/plugins/{browsers → apps/webserver}/__init__.py +0 -0
  133. /dissect/target/plugins/apps/{webservers/webservers.py → webserver/webserver.py} +0 -0
  134. /dissect/target/plugins/os/unix/{linux/esxi → esxi}/__init__.py +0 -0
  135. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/COPYRIGHT +0 -0
  136. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/LICENSE +0 -0
  137. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/entry_points.txt +0 -0
  138. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/top_level.txt +0 -0
@@ -21,7 +21,7 @@ class AsdfLoader(Loader):
21
21
  path = path.resolve()
22
22
 
23
23
  super().__init__(path)
24
- self.asdf = AsdfSnapshot(open(path, "rb"))
24
+ self.asdf = AsdfSnapshot(path.open("rb"))
25
25
 
26
26
  @staticmethod
27
27
  def detect(path) -> bool:
@@ -26,12 +26,12 @@ class LogLoader(Loader):
26
26
 
27
27
  def map(self, target: Target) -> None:
28
28
  self.target = target
29
- vfs = VirtualFilesystem(case_sensitive=False)
29
+ vfs = VirtualFilesystem(case_sensitive=False, alt_separator=target.fs.alt_separator)
30
30
  for entry in self.path.parent.glob(self.path.name):
31
31
  ext = self.options.get("hint", entry.suffix.lower()).strip(".")
32
32
  if (mapping := self.LOGS_DIRS.get(ext, None)) is None:
33
33
  continue
34
- mapping = str(Path(mapping).joinpath(entry.name))
34
+ mapping = str(vfs.path(mapping).joinpath(entry.name))
35
35
  vfs.map_file(mapping, str(entry))
36
36
  target.filesystems.add(vfs)
37
37
  target.fs = vfs
@@ -11,6 +11,7 @@ from urllib.parse import ParseResult, parse_qsl
11
11
  from dissect.regf import regf
12
12
  from dissect.util import ts
13
13
  from impacket.dcerpc.v5 import rpcrt, rrp, scmr, transport
14
+ from impacket.dcerpc.v5.rpcrt import DCERPCException
14
15
  from impacket.smbconnection import SessionError, SMBConnection
15
16
 
16
17
  from dissect.target import Target
@@ -189,18 +190,24 @@ class SmbRegistry(RegistryPlugin):
189
190
  return False
190
191
 
191
192
  def _init_registry(self) -> None:
192
- self._svcctl = _connect_rpc(self.conn, "ncacn_np:445[\\pipe\\svcctl]", scmr.MSRPC_UUID_SCMR)
193
- self._check_service_status()
193
+ try:
194
+ self._svcctl = _connect_rpc(self.conn, "ncacn_np:445[\\pipe\\svcctl]", scmr.MSRPC_UUID_SCMR)
195
+ self._check_service_status()
194
196
 
195
- self._winreg = _connect_rpc(self.conn, "ncacn_np:445[\\pipe\\winreg]", rrp.MSRPC_UUID_RRP)
197
+ self._winreg = _connect_rpc(self.conn, "ncacn_np:445[\\pipe\\winreg]", rrp.MSRPC_UUID_RRP)
196
198
 
197
- hklm_hive = SmbRegistryHive(self._winreg, "HKEY_LOCAL_MACHINE", rrp.hOpenLocalMachine(self._winreg)["phKey"])
198
- hku_hive = SmbRegistryHive(self._winreg, "HKEY_USERS", rrp.hOpenUsers(self._winreg)["phKey"])
199
+ hklm_hive = SmbRegistryHive(
200
+ self._winreg, "HKEY_LOCAL_MACHINE", rrp.hOpenLocalMachine(self._winreg)["phKey"]
201
+ )
202
+ hku_hive = SmbRegistryHive(self._winreg, "HKEY_USERS", rrp.hOpenUsers(self._winreg)["phKey"])
199
203
 
200
- self._add_hive("HKLM", hklm_hive, TargetPath(self.target.fs, "HKLM"))
201
- self._add_hive("HKU", hku_hive, TargetPath(self.target.fs, "HKU"))
202
- self._map_hive("HKEY_LOCAL_MACHINE", hklm_hive)
203
- self._map_hive("HKEY_USERS", hku_hive)
204
+ self._add_hive("HKLM", hklm_hive, TargetPath(self.target.fs, "HKLM"))
205
+ self._add_hive("HKU", hku_hive, TargetPath(self.target.fs, "HKU"))
206
+ self._map_hive("HKEY_LOCAL_MACHINE", hklm_hive)
207
+ self._map_hive("HKEY_USERS", hku_hive)
208
+ except SessionError:
209
+ self.target.log.info("Failed to open remote registry, registry will not be available")
210
+ return # no registry access, probably no access rights
204
211
 
205
212
  def _init_users(self) -> None:
206
213
  pass
@@ -212,15 +219,18 @@ class SmbRegistry(RegistryPlugin):
212
219
  if hasattr(self, "_was_disabled") and self._was_disabled:
213
220
  scmr.hRChangeServiceConfigW(self._svcctl, self._svc_handle, dwStartType=0x4)
214
221
 
215
- if hasattr(self, "_svcctl"):
222
+ if getattr(self, "_svcctl", None):
216
223
  self._svcctl.disconnect()
217
224
 
218
- if hasattr(self, "_winreg"):
225
+ if getattr(self, "_winreg", None):
219
226
  self._winreg.disconnect()
220
227
 
221
228
  def _check_service_status(self) -> None:
222
- manager_handle = scmr.hROpenSCManagerW(self._svcctl)["lpScHandle"]
223
- self._svc_handle = scmr.hROpenServiceW(self._svcctl, manager_handle, "RemoteRegistry")["lpServiceHandle"]
229
+ try:
230
+ manager_handle = scmr.hROpenSCManagerW(self._svcctl)["lpScHandle"]
231
+ self._svc_handle = scmr.hROpenServiceW(self._svcctl, manager_handle, "RemoteRegistry")["lpServiceHandle"]
232
+ except DCERPCException:
233
+ return
224
234
 
225
235
  current_state = scmr.hRQueryServiceStatus(self._svcctl, self._svc_handle)["lpServiceStatus"]["dwCurrentState"]
226
236
  if current_state == scmr.SERVICE_STOPPED:
@@ -186,13 +186,23 @@ if TARGETD_AVAILABLE:
186
186
  if namespace := kwargs.get("namespace", None):
187
187
  obj = getattr(obj, namespace)
188
188
 
189
+ caller.has_output = True
189
190
  if targetd.command == "init":
190
- caller.has_output = True
191
+ caller.output = True
192
+ return
193
+
194
+ if targetd.command == "select":
195
+ targetd.rpcs.select(*args)
196
+ caller.output = True
197
+ return
198
+
199
+ if targetd.command == "deselect":
200
+ targetd.rpcs.deselect()
191
201
  caller.output = True
192
202
  return
193
203
 
194
204
  func = getattr(obj, targetd.command)
195
- caller.has_output = True
205
+
196
206
  result = func(*args, **kwargs)
197
207
  if result is not None:
198
208
  if targetd.command == "get":
@@ -14,7 +14,7 @@ class VmaLoader(Loader):
14
14
  def __init__(self, path, **kwargs):
15
15
  path = path.resolve()
16
16
  super().__init__(path)
17
- self.vma = vma.VMA(open(path, "rb"))
17
+ self.vma = vma.VMA(path.open("rb"))
18
18
 
19
19
  @staticmethod
20
20
  def detect(path):
@@ -14,7 +14,7 @@ class XvaLoader(Loader):
14
14
  def __init__(self, path, **kwargs):
15
15
  path = path.resolve()
16
16
  super().__init__(path)
17
- self.xva = xva.XVA(open(path, "rb"))
17
+ self.xva = xva.XVA(path.open("rb"))
18
18
 
19
19
  @staticmethod
20
20
  def detect(path):
dissect/target/plugin.py CHANGED
@@ -1038,6 +1038,7 @@ def find_plugin_functions(
1038
1038
 
1039
1039
  invalid_funcs = set()
1040
1040
  show_hidden = kwargs.get("show_hidden", False)
1041
+ ignore_load_errors = kwargs.get("ignore_load_errors", False)
1041
1042
 
1042
1043
  for pattern in patterns.split(","):
1043
1044
  # backward compatibility fix for namespace-level plugins (i.e. chrome)
@@ -1071,7 +1072,12 @@ def find_plugin_functions(
1071
1072
  func = functions[index_name]
1072
1073
 
1073
1074
  method_name = index_name.split(".")[-1]
1074
- loaded_plugin_object = load(func)
1075
+ try:
1076
+ loaded_plugin_object = load(func)
1077
+ except Exception:
1078
+ if ignore_load_errors:
1079
+ continue
1080
+ raise
1075
1081
 
1076
1082
  # Skip plugins that don't want to be found by wildcards
1077
1083
  if not show_hidden and not loaded_plugin_object.__findable__:
@@ -1120,7 +1126,13 @@ def find_plugin_functions(
1120
1126
  invalid_funcs.add(pattern)
1121
1127
 
1122
1128
  for description in plugin_descriptions:
1123
- loaded_plugin_object = load(description)
1129
+ try:
1130
+ loaded_plugin_object = load(description)
1131
+ except Exception:
1132
+ if ignore_load_errors:
1133
+ continue
1134
+ raise
1135
+
1124
1136
  fobject = inspect.getattr_static(loaded_plugin_object, funcname)
1125
1137
 
1126
1138
  if compatibility and not loaded_plugin_object(target).is_compatible():
@@ -3,7 +3,6 @@ from typing import Iterator
3
3
 
4
4
  from dissect.sql import sqlite3
5
5
  from dissect.util.ts import wintimestamp
6
- from flow.record.fieldtypes import path
7
6
 
8
7
  from dissect.target import Target
9
8
  from dissect.target.exceptions import UnsupportedPluginError
@@ -105,7 +104,7 @@ class SophosPlugin(Plugin):
105
104
  yield SophosLogRecord(
106
105
  ts=ts,
107
106
  description=details.get("threat_name", details),
108
- path=path.from_windows(path_to_infected_file),
107
+ path=self.target.fs.path(path_to_infected_file),
109
108
  _target=self.target,
110
109
  )
111
110
  except Exception as error:
@@ -199,10 +199,9 @@ class SymantecPlugin(Plugin):
199
199
 
200
200
  def check_compatible(self) -> None:
201
201
  for log_file in self.LOGS:
202
- if self.target.fs.glob(log_file):
203
- break
204
- else:
205
- raise UnsupportedPluginError("No Symantec SEP logs found")
202
+ if list(self.target.fs.glob(log_file)):
203
+ return
204
+ raise UnsupportedPluginError("No Symantec SEP logs found")
206
205
 
207
206
  def _fw_cell(self, line: list, cell_id: int) -> str:
208
207
  return line[cell_id].decode("utf-8")
@@ -2,7 +2,6 @@ from typing import Iterator
2
2
 
3
3
  from dissect import cstruct
4
4
  from dissect.util.ts import from_unix
5
- from flow.record.fieldtypes import path
6
5
 
7
6
  from dissect.target import Target
8
7
  from dissect.target.exceptions import UnsupportedPluginError
@@ -86,7 +85,7 @@ class TrendMicroPlugin(Plugin):
86
85
  yield TrendMicroWFLogRecord(
87
86
  ts=from_unix(int(cells[9])),
88
87
  threat=cells[2],
89
- path=path.from_windows(cells[6] + cells[7]),
88
+ path=self.target.fs.path(cells[6] + cells[7]),
90
89
  lineno=lineno,
91
90
  )
92
91
 
@@ -115,7 +114,7 @@ class TrendMicroPlugin(Plugin):
115
114
  remote_ip=entry.remote_ip.strip(b"\x00").decode(self.codepage),
116
115
  port=entry.port,
117
116
  direction=("out" if entry.direction == b"\x01" else "in"),
118
- path=path.from_windows(entry.path.strip(b"\x00").decode(self.codepage)),
117
+ path=self.target.fs.path(entry.path.strip(b"\x00").decode(self.codepage)),
119
118
  description=entry.description.strip("\x00"),
120
119
  )
121
120
  except EOFError:
@@ -1,13 +1,16 @@
1
1
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
2
2
  from dissect.target.helpers.record import create_extended_descriptor
3
3
  from dissect.target.plugin import export
4
- from dissect.target.plugins.browsers.browser import (
4
+ from dissect.target.plugins.apps.browser.browser import (
5
5
  GENERIC_DOWNLOAD_RECORD_FIELDS,
6
6
  GENERIC_EXTENSION_RECORD_FIELDS,
7
7
  GENERIC_HISTORY_RECORD_FIELDS,
8
8
  BrowserPlugin,
9
9
  )
10
- from dissect.target.plugins.browsers.chromium import ChromiumMixin
10
+ from dissect.target.plugins.apps.browser.chromium import (
11
+ CHROMIUM_DOWNLOAD_RECORD_FIELDS,
12
+ ChromiumMixin,
13
+ )
11
14
 
12
15
 
13
16
  class ChromePlugin(ChromiumMixin, BrowserPlugin):
@@ -27,7 +30,7 @@ class ChromePlugin(ChromiumMixin, BrowserPlugin):
27
30
  "Library/Application Support/Google/Chrome/Default",
28
31
  ]
29
32
  BrowserDownloadRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
30
- "browser/chrome/download", GENERIC_DOWNLOAD_RECORD_FIELDS
33
+ "browser/chrome/download", GENERIC_DOWNLOAD_RECORD_FIELDS + CHROMIUM_DOWNLOAD_RECORD_FIELDS
31
34
  )
32
35
  BrowserExtensionRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
33
36
  "browser/chrome/extension", GENERIC_EXTENSION_RECORD_FIELDS
@@ -6,14 +6,13 @@ from dissect.sql import sqlite3
6
6
  from dissect.sql.exceptions import Error as SQLError
7
7
  from dissect.sql.sqlite3 import SQLite3
8
8
  from dissect.util.ts import webkittimestamp
9
- from flow.record.fieldtypes import path
10
9
 
11
10
  from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
12
11
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
13
12
  from dissect.target.helpers.fsutil import TargetPath
14
13
  from dissect.target.helpers.record import create_extended_descriptor
15
14
  from dissect.target.plugin import export
16
- from dissect.target.plugins.browsers.browser import (
15
+ from dissect.target.plugins.apps.browser.browser import (
17
16
  GENERIC_DOWNLOAD_RECORD_FIELDS,
18
17
  GENERIC_EXTENSION_RECORD_FIELDS,
19
18
  GENERIC_HISTORY_RECORD_FIELDS,
@@ -22,13 +21,19 @@ from dissect.target.plugins.browsers.browser import (
22
21
  )
23
22
  from dissect.target.plugins.general.users import UserDetails
24
23
 
24
+ CHROMIUM_DOWNLOAD_RECORD_FIELDS = [
25
+ ("uri", "tab_url"),
26
+ ("uri", "tab_referrer_url"),
27
+ ("string", "mime_type"),
28
+ ]
29
+
25
30
 
26
31
  class ChromiumMixin:
27
32
  """Mixin class with methods for Chromium-based browsers."""
28
33
 
29
34
  DIRS = []
30
35
  BrowserDownloadRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
31
- "browser/chromium/download", GENERIC_DOWNLOAD_RECORD_FIELDS
36
+ "browser/chromium/download", GENERIC_DOWNLOAD_RECORD_FIELDS + CHROMIUM_DOWNLOAD_RECORD_FIELDS
32
37
  )
33
38
  BrowserExtensionRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
34
39
  "browser/chromium/extension", GENERIC_EXTENSION_RECORD_FIELDS
@@ -117,7 +122,10 @@ class ChromiumMixin:
117
122
  id (string): Record ID.
118
123
  path (string): Download path.
119
124
  url (uri): Download URL.
125
+ tab_url (string): Tab URL.
126
+ tab_referrer_url (string): Referrer URL.
120
127
  size (varint): Download file size.
128
+ mime_type (string): MIME type.
121
129
  state (varint): Download state number.
122
130
  source: (path): The source file of the download record.
123
131
  Raises:
@@ -133,11 +141,8 @@ class ChromiumMixin:
133
141
  chain.sort(key=lambda row: row.chain_index)
134
142
 
135
143
  for row in db.table("downloads").rows():
136
- download_path = row.target_path
137
- if download_path and self.target.os == "windows":
138
- download_path = path.from_windows(download_path)
139
- elif download_path:
140
- download_path = path.from_posix(download_path)
144
+ if download_path := row.target_path:
145
+ download_path = self.target.fs.path(download_path)
141
146
 
142
147
  url = None
143
148
  download_chain = download_chains.get(row.id)
@@ -151,9 +156,12 @@ class ChromiumMixin:
151
156
  ts_end=webkittimestamp(row.end_time) if row.end_time else None,
152
157
  browser=browser_name,
153
158
  id=row.get("id"),
159
+ tab_url=try_idna(row.get("tab_url")),
160
+ tab_referrer_url=try_idna(row.get("tab_referrer_url")),
154
161
  path=download_path,
155
162
  url=url,
156
163
  size=row.get("total_bytes"),
164
+ mime_type=row.get("mime_type"),
157
165
  state=row.get("state"),
158
166
  source=db_file,
159
167
  _target=self.target,
@@ -204,11 +212,8 @@ class ChromiumMixin:
204
212
  if ts_update:
205
213
  ts_update = webkittimestamp(ts_update)
206
214
 
207
- ext_path = extension_data.get("path")
208
- if ext_path and self.target.os == "windows":
209
- ext_path = path.from_windows(ext_path)
210
- elif ext_path:
211
- ext_path = path.from_posix(ext_path)
215
+ if ext_path := extension_data.get("path"):
216
+ ext_path = self.target.fs.path(ext_path)
212
217
 
213
218
  manifest = extension_data.get("manifest")
214
219
  if manifest:
@@ -1,13 +1,16 @@
1
1
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
2
2
  from dissect.target.helpers.record import create_extended_descriptor
3
3
  from dissect.target.plugin import export
4
- from dissect.target.plugins.browsers.browser import (
4
+ from dissect.target.plugins.apps.browser.browser import (
5
5
  GENERIC_DOWNLOAD_RECORD_FIELDS,
6
6
  GENERIC_EXTENSION_RECORD_FIELDS,
7
7
  GENERIC_HISTORY_RECORD_FIELDS,
8
8
  BrowserPlugin,
9
9
  )
10
- from dissect.target.plugins.browsers.chromium import ChromiumMixin
10
+ from dissect.target.plugins.apps.browser.chromium import (
11
+ CHROMIUM_DOWNLOAD_RECORD_FIELDS,
12
+ ChromiumMixin,
13
+ )
11
14
 
12
15
 
13
16
  class EdgePlugin(ChromiumMixin, BrowserPlugin):
@@ -25,7 +28,7 @@ class EdgePlugin(ChromiumMixin, BrowserPlugin):
25
28
  "Library/Application Support/Microsoft Edge/Default",
26
29
  ]
27
30
  BrowserDownloadRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
28
- "browser/edge/download", GENERIC_DOWNLOAD_RECORD_FIELDS
31
+ "browser/edge/download", GENERIC_DOWNLOAD_RECORD_FIELDS + CHROMIUM_DOWNLOAD_RECORD_FIELDS
29
32
  )
30
33
  BrowserExtensionRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
31
34
  "browser/edge/extension", GENERIC_EXTENSION_RECORD_FIELDS
@@ -5,13 +5,12 @@ from dissect.sql import sqlite3
5
5
  from dissect.sql.exceptions import Error as SQLError
6
6
  from dissect.sql.sqlite3 import SQLite3
7
7
  from dissect.util.ts import from_unix_ms, from_unix_us
8
- from flow.record.fieldtypes import path
9
8
 
10
9
  from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
11
10
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
12
11
  from dissect.target.helpers.record import create_extended_descriptor
13
12
  from dissect.target.plugin import export
14
- from dissect.target.plugins.browsers.browser import (
13
+ from dissect.target.plugins.apps.browser.browser import (
15
14
  GENERIC_DOWNLOAD_RECORD_FIELDS,
16
15
  GENERIC_HISTORY_RECORD_FIELDS,
17
16
  BrowserPlugin,
@@ -197,12 +196,9 @@ class FirefoxPlugin(BrowserPlugin):
197
196
  state = content.get("state")
198
197
 
199
198
  dest_file_info = annotation.get("downloads/destinationFileURI", {})
200
- download_path = dest_file_info.get("content")
201
199
 
202
- if download_path and self.target.os == "windows":
203
- download_path = path.from_windows(download_path)
204
- elif download_path:
205
- download_path = path.from_posix(download_path)
200
+ if download_path := dest_file_info.get("content"):
201
+ download_path = self.target.fs.path(download_path)
206
202
 
207
203
  place = places.get(place_id)
208
204
  url = place.get("url")
@@ -2,13 +2,14 @@ from pathlib import Path
2
2
  from typing import BinaryIO, Iterator, Tuple
3
3
 
4
4
  from dissect.esedb import esedb, record, table
5
+ from dissect.esedb.exceptions import KeyNotFoundError
5
6
  from dissect.util.ts import wintimestamp
6
7
 
7
8
  from dissect.target.exceptions import UnsupportedPluginError
8
9
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
9
10
  from dissect.target.helpers.record import create_extended_descriptor
10
11
  from dissect.target.plugin import export
11
- from dissect.target.plugins.browsers.browser import (
12
+ from dissect.target.plugins.apps.browser.browser import (
12
13
  GENERIC_DOWNLOAD_RECORD_FIELDS,
13
14
  GENERIC_HISTORY_RECORD_FIELDS,
14
15
  BrowserPlugin,
@@ -192,12 +193,21 @@ class InternetExplorerPlugin(BrowserPlugin):
192
193
  """
193
194
  for user, cache_file, cache in self._iter_cache():
194
195
  for container_record in cache.downloads():
195
- response_headers = container_record.ResponseHeaders.decode("utf-16-le", errors="ignore")
196
- ref_url, mime_type, temp_download_path, down_url, down_path = response_headers.split("\x00")[-6:-1]
196
+ down_path = None
197
+ down_url = None
198
+ ts_end = wintimestamp(container_record.AccessedTime) if container_record.AccessedTime else None
199
+
200
+ try:
201
+ response_headers = container_record.ResponseHeaders.decode("utf-16-le", errors="ignore")
202
+ # Not used here, but [-6:-3] should give: ref_url, mime_type, temp_download_path
203
+ down_url, down_path = response_headers.split("\x00")[-3:-1]
204
+ except (AttributeError, KeyNotFoundError) as e:
205
+ self.target.log.error("Error parsing response headers: %s", e)
206
+ self.target.log.debug("", exc_info=e)
197
207
 
198
208
  yield self.BrowserDownloadRecord(
199
209
  ts_start=None,
200
- ts_end=wintimestamp(container_record.AccessedTime),
210
+ ts_end=ts_end,
201
211
  browser="iexplore",
202
212
  id=container_record.EntryId,
203
213
  path=down_path,
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from datetime import datetime
2
3
 
3
4
  from dissect.target.exceptions import UnsupportedPluginError
@@ -7,6 +8,8 @@ from dissect.target.plugins.apps.remoteaccess.remoteaccess import (
7
8
  RemoteAccessRecord,
8
9
  )
9
10
 
11
+ START_PATTERN = re.compile(r"^(\d{2}|\d{4})/")
12
+
10
13
 
11
14
  class TeamviewerPlugin(RemoteAccessPlugin):
12
15
  """
@@ -54,30 +57,55 @@ class TeamviewerPlugin(RemoteAccessPlugin):
54
57
  for logfile, user in self.logfiles:
55
58
  logfile = self.target.fs.path(logfile)
56
59
 
57
- for line in logfile.open("rt"):
58
- line = line.strip()
59
-
60
- # Skip empty lines
61
- if not line:
62
- continue
63
-
64
- # Sometimes there are weird, mult-line/pretty print log messages.
65
- try:
66
- # should be year (%Y)
67
- int(line[0])
68
- except ValueError:
69
- continue
70
-
71
- ts_day, ts_time, description = line.split(" ", 2)
72
- ts_time = ts_time.split(".")[0]
73
-
74
- timestamp = datetime.strptime(f"{ts_day} {ts_time}", "%Y/%m/%d %H:%M:%S")
75
-
76
- yield RemoteAccessRecord(
77
- tool="teamviewer",
78
- ts=timestamp,
79
- logfile=str(logfile),
80
- description=description,
81
- _target=self.target,
82
- _user=user,
83
- )
60
+ start_date = None
61
+ with logfile.open("rt") as file:
62
+ while True:
63
+ try:
64
+ line = file.readline()
65
+ except UnicodeDecodeError:
66
+ continue
67
+
68
+ # End of file, quit while loop
69
+ if not line:
70
+ break
71
+
72
+ line = line.strip()
73
+
74
+ # Skip empty lines
75
+ if not line:
76
+ continue
77
+ # Older logs first mention the start time and then leave out the year
78
+ if line.startswith("Start:"):
79
+ start_date = datetime.strptime(line.split()[1], "%Y/%m/%d")
80
+
81
+ # Sometimes there are weird, mult-line/pretty print log messages.
82
+ # We only parse the start line which starts with year (%Y/) or month (%m/)
83
+ if not re.match(START_PATTERN, line):
84
+ continue
85
+
86
+ ts_day, ts_time, description = line.split(" ", 2)
87
+ ts_time = ts_time.split(".")[0]
88
+
89
+ # Correct for use of : as millisecond separator
90
+ if ts_time.count(":") > 2:
91
+ ts_time = ":".join(ts_time.split(":")[:3])
92
+ # Correct for missing year in date
93
+ if ts_day.count("/") == 1:
94
+ if not start_date:
95
+ self.target.log.debug("Missing year in log line, skipping line.")
96
+ continue
97
+ ts_day = f"{start_date.year}/{ts_day}"
98
+ # Correct for year if short notation for 2000 is used
99
+ if ts_day.count("/") == 2 and len(ts_day.split("/")[0]) == 2:
100
+ ts_day = "20" + ts_day
101
+
102
+ timestamp = datetime.strptime(f"{ts_day} {ts_time}", "%Y/%m/%d %H:%M:%S")
103
+
104
+ yield RemoteAccessRecord(
105
+ tool="teamviewer",
106
+ ts=timestamp,
107
+ logfile=str(logfile),
108
+ description=description,
109
+ _target=self.target,
110
+ _user=user,
111
+ )
@@ -128,45 +128,46 @@ class SSHServerPlugin(Plugin):
128
128
  config = {}
129
129
  for key, value in sshd_config.items():
130
130
  if isinstance(value, dict):
131
- # A match statment, ignore for now
131
+ # A match statement, ignore for now
132
132
  continue
133
- _type, value = self._determine_type_and_value(key, value)
133
+ _type, _value = _determine_type_and_value(key, value)
134
134
 
135
- config[key] = value
135
+ config[key] = _value
136
136
  record_fields.append((_type, key))
137
137
 
138
138
  yield TargetRecordDescriptor("application/openssh/sshd_config", record_fields)(
139
139
  mtime=self.sshd_config_path.stat().st_mtime, source=self.sshd_config_path, **config, _target=self.target
140
140
  )
141
141
 
142
- def _determine_type_and_value(self, key: str, value: str) -> tuple[str, Any]:
142
+
143
+ def _determine_type_and_value(key: str, value: str) -> tuple[str, Any]:
144
+ _type = "string"
145
+ _value = value
146
+
147
+ _unpack = None
148
+ if key in SSHD_INTEGER_FIELDS:
149
+ _type = "varint"
150
+ _unpack = int
151
+ elif key in SSHD_BOOLEAN_FIELDS and _value.lower() in SSHD_BOOLEAN_VALUES:
152
+ _type = "boolean"
153
+ _unpack = _convert_bool
154
+ else:
143
155
  _type = "string"
144
- _value = value
145
-
146
- _unpack = None
147
- if key in SSHD_INTEGER_FIELDS:
148
- _type = "varint"
149
- _unpack = int
150
- elif key in SSHD_BOOLEAN_FIELDS and _value.lower() in SSHD_BOOLEAN_VALUES:
151
- _type = "boolean"
152
- _unpack = _convert_bool
153
- else:
154
- _type = "string"
155
-
156
- if multiple_fields := (key in SSHD_MULTIPLE_DEFINITIONS_ALLOWED_FIELDS):
157
- _value = _value if isinstance(_value, list) else [value]
158
-
159
- try:
160
- _value = _convert_function(value, _unpack)
161
- except Exception:
162
- # Something went wrong, restore default type
163
- _type = "string"
164
-
165
- if multiple_fields:
166
- # The type can still be a list, so in that case, we append `[]`
167
- _type = f"{_type}[]"
168
-
169
- return _type, _value
156
+
157
+ if multiple_fields := (key in SSHD_MULTIPLE_DEFINITIONS_ALLOWED_FIELDS):
158
+ _value = _value if isinstance(_value, list) else [_value]
159
+
160
+ try:
161
+ _value = _convert_function(_value, _unpack)
162
+ except Exception:
163
+ # Something went wrong, restore default type
164
+ _type = "string"
165
+
166
+ if multiple_fields:
167
+ # The type can still be a list, so in that case, we append `[]`
168
+ _type = f"{_type}[]"
169
+
170
+ return _type, _value
170
171
 
171
172
 
172
173
  def _convert_function(value: Union[str, list], unpack_function: Optional[Callable]) -> Union[list[Any], Any]:
@@ -8,7 +8,7 @@ from typing import Iterator, Optional
8
8
  from dissect.target import plugin
9
9
  from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
10
10
  from dissect.target.helpers.fsutil import open_decompress
11
- from dissect.target.plugins.apps.webservers.webservers import WebserverAccessLogRecord
11
+ from dissect.target.plugins.apps.webserver.webserver import WebserverAccessLogRecord
12
12
  from dissect.target.target import Target
13
13
 
14
14
  COMMON_REGEX = r'(?P<remote_ip>.*?) (?P<remote_logname>.*?) (?P<remote_user>.*?) \[(?P<ts>.*)\] "(?P<method>.*?) (?P<uri>.*?) ?(?P<protocol>HTTP\/.*?)?" (?P<status_code>\d{3}) (?P<bytes_sent>-|\d+)' # noqa: E501
@@ -9,7 +9,7 @@ from dissect.util.ts import from_unix
9
9
  from dissect.target import plugin
10
10
  from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
11
11
  from dissect.target.helpers.fsutil import basename, open_decompress
12
- from dissect.target.plugins.apps.webservers.webservers import WebserverAccessLogRecord
12
+ from dissect.target.plugins.apps.webserver.webserver import WebserverAccessLogRecord
13
13
  from dissect.target.target import Target
14
14
 
15
15
  LOG_FILE_REGEX = re.compile(r"(log|output file) (?P<log_file>.*)( \{)?$")