dissect.target 3.19.dev13__py3-none-any.whl → 3.19.dev15__py3-none-any.whl

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.
@@ -1,91 +1,111 @@
1
- from datetime import datetime
1
+ import re
2
+ from datetime import datetime, timezone
3
+ from typing import Iterator
2
4
 
3
5
  from dissect.target.exceptions import UnsupportedPluginError
6
+ from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
7
+ from dissect.target.helpers.fsutil import TargetPath
8
+ from dissect.target.helpers.record import create_extended_descriptor
4
9
  from dissect.target.plugin import export
5
10
  from dissect.target.plugins.apps.remoteaccess.remoteaccess import (
11
+ GENERIC_LOG_RECORD_FIELDS,
6
12
  RemoteAccessPlugin,
7
- RemoteAccessRecord,
8
13
  )
14
+ from dissect.target.plugins.general.users import UserDetails
9
15
 
10
16
 
11
17
  class AnydeskPlugin(RemoteAccessPlugin):
12
- """
13
- Anydesk plugin.
14
- """
18
+ """Anydesk plugin."""
15
19
 
16
20
  __namespace__ = "anydesk"
17
21
 
18
22
  # Anydesk logs when installed as a service
19
23
  SERVICE_GLOBS = [
20
- "/sysvol/ProgramData/AnyDesk/*.trace", # Standard client >= Windows 7
21
- "/sysvol/ProgramData/AnyDesk/ad_*/*.trace", # Custom client >= Windows 7
22
- "/var/log/anydesk*.trace", # Standard/Custom client Linux/MacOS
24
+ # Standard client >= Windows 7
25
+ "sysvol/ProgramData/AnyDesk/*.trace",
26
+ # Custom client >= Windows 7
27
+ "sysvol/ProgramData/AnyDesk/ad_*/*.trace",
28
+ # Windows XP / 2003
29
+ "sysvol/Documents and Settings/Public/AnyDesk/*.trace",
30
+ "sysvol/Documents and Settings/Public/AnyDesk/ad_*/*.trace",
31
+ # Standard/Custom client Linux/MacOS
32
+ "var/log/anydesk*/*.trace",
23
33
  ]
24
34
 
25
35
  # User specific Anydesk logs
26
36
  USER_GLOBS = [
27
- "appdata/roaming/AnyDesk/*.trace", # Standard client Windows
28
- "appdata/roaming/AnyDesk/ad_*/*.trace", # Custom client Windows
29
- ".anydesk/*.trace", # Standard client Linux/MacOS
30
- ".anydesk_ad_*/*.trace", # Custom client Linux/MacOS
37
+ # Standard client Windows
38
+ "AppData/Roaming/AnyDesk/*.trace",
39
+ # Custom client Windows
40
+ "AppData/Roaming/AnyDesk/ad_*/*.trace",
41
+ # Windows XP / 2003
42
+ "AppData/AnyDesk/*.trace",
43
+ # Standard client Linux/MacOS
44
+ ".anydesk/*.trace",
45
+ # Custom client Linux/MacOS
46
+ ".anydesk_ad_*/*.trace",
31
47
  ]
32
48
 
49
+ RemoteAccessLogRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
50
+ "remoteaccess/anydesk/log", GENERIC_LOG_RECORD_FIELDS
51
+ )
52
+
33
53
  def __init__(self, target):
34
54
  super().__init__(target)
35
55
 
36
- self.logfiles = []
56
+ self.trace_files: set[tuple[TargetPath, UserDetails]] = set()
37
57
 
38
- # Check service globs
58
+ # Service globs
39
59
  user = None
40
- for log_glob in self.SERVICE_GLOBS:
41
- for logfile in self.target.fs.glob(log_glob):
42
- self.logfiles.append([logfile, user])
60
+ for trace_glob in self.SERVICE_GLOBS:
61
+ for trace_file in self.target.fs.path().glob(trace_glob):
62
+ self.trace_files.add((trace_file, user))
43
63
 
44
- # Anydesk logs when as user
64
+ # User globs
45
65
  for user_details in self.target.user_details.all_with_home():
46
- for log_glob in self.USER_GLOBS:
47
- for logfile in user_details.home_path.glob(log_glob):
48
- self.logfiles.append([logfile, user_details.user])
66
+ for trace_glob in self.USER_GLOBS:
67
+ for trace_file in user_details.home_path.glob(trace_glob):
68
+ self.trace_files.add((trace_file, user_details.user))
49
69
 
50
70
  def check_compatible(self) -> None:
51
- if not (len(self.logfiles)):
52
- raise UnsupportedPluginError("No Anydesk logs found")
71
+ if not self.trace_files:
72
+ raise UnsupportedPluginError("No Anydesk trace files found on target")
53
73
 
54
- @export(record=RemoteAccessRecord)
55
- def logs(self):
56
- """Return the content of the AnyDesk logs.
74
+ @export(record=RemoteAccessLogRecord)
75
+ def logs(self) -> Iterator[RemoteAccessLogRecord]:
76
+ """Parse AnyDesk trace files.
57
77
 
58
78
  AnyDesk is a remote desktop application and can be used by adversaries to get (persistent) access to a machine.
59
- Log files (.trace files) are retrieved from various location based on OS and client type.
79
+ Log files (.trace files) can be stored on various locations, based on target OS and client type.
80
+ Timestamps in trace files do not carry a time zone designator (TZD) but are in fact UTC.
60
81
 
61
82
  References:
62
83
  - https://www.inversecos.com/2021/02/forensic-analysis-of-anydesk-logs.html
63
84
  - https://support.anydesk.com/knowledge/trace-files#trace-file-locations
64
85
  """
65
- for logfile, user in self.logfiles:
66
- logfile = self.target.fs.path(logfile)
67
-
68
- for line in logfile.open("rt"):
86
+ for trace_file, user in self.trace_files:
87
+ for line in trace_file.open("rt", errors="backslashreplace"):
69
88
  line = line.strip()
70
89
 
71
- # Skip empty lines
72
- if not line:
73
- continue
74
-
75
- if "* * * * * * * * * * * * * *" in line:
90
+ if not line or "* * * * * * * * * * * * * *" in line:
76
91
  continue
77
92
 
78
- level, ts_day, ts_time, description = line.split(" ", 3)
79
- description = f"{level} {description}"
80
- ts_time = ts_time.split(".")[0]
81
-
82
- timestamp = datetime.strptime(f"{ts_day} {ts_time}", "%Y-%m-%d %H:%M:%S")
83
-
84
- yield RemoteAccessRecord(
85
- ts=timestamp,
86
- tool="anydesk",
87
- logfile=str(logfile),
88
- description=description,
89
- _target=self.target,
90
- _user=user,
91
- )
93
+ try:
94
+ level, ts_date, ts_time, message = line.split(" ", 3)
95
+
96
+ timestamp = datetime.strptime(f"{ts_date} {ts_time}", "%Y-%m-%d %H:%M:%S.%f").replace(
97
+ tzinfo=timezone.utc
98
+ )
99
+ message = re.sub(r"\s\s+", " ", f"{level} {message}")
100
+
101
+ yield self.RemoteAccessLogRecord(
102
+ ts=timestamp,
103
+ message=message,
104
+ source=trace_file,
105
+ _target=self.target,
106
+ _user=user,
107
+ )
108
+
109
+ except ValueError as e:
110
+ self.target.log.warning("Could not parse log line in file %s: '%s'", trace_file, line)
111
+ self.target.log.debug("", exc_info=e)
@@ -2,14 +2,14 @@ from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExt
2
2
  from dissect.target.helpers.record import create_extended_descriptor
3
3
  from dissect.target.plugin import NamespacePlugin
4
4
 
5
- RemoteAccessRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
6
- "application/log/remoteaccess",
7
- [
8
- ("datetime", "ts"),
9
- ("string", "tool"),
10
- ("path", "logfile"),
11
- ("string", "description"),
12
- ],
5
+ GENERIC_LOG_RECORD_FIELDS = [
6
+ ("datetime", "ts"),
7
+ ("string", "message"),
8
+ ("path", "source"),
9
+ ]
10
+
11
+ RemoteAccessLogRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
12
+ "remoteaccess/log", GENERIC_LOG_RECORD_FIELDS
13
13
  )
14
14
 
15
15
 
@@ -1,60 +1,72 @@
1
1
  import re
2
- from datetime import datetime
2
+ from datetime import datetime, timezone
3
+ from typing import Iterator
3
4
 
4
5
  from dissect.target.exceptions import UnsupportedPluginError
6
+ from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
7
+ from dissect.target.helpers.fsutil import TargetPath
8
+ from dissect.target.helpers.record import create_extended_descriptor
5
9
  from dissect.target.plugin import export
6
10
  from dissect.target.plugins.apps.remoteaccess.remoteaccess import (
11
+ GENERIC_LOG_RECORD_FIELDS,
7
12
  RemoteAccessPlugin,
8
- RemoteAccessRecord,
9
13
  )
14
+ from dissect.target.plugins.general.users import UserDetails
10
15
 
11
16
  START_PATTERN = re.compile(r"^(\d{2}|\d{4})/")
12
17
 
13
18
 
14
- class TeamviewerPlugin(RemoteAccessPlugin):
15
- """
16
- Teamviewer plugin.
19
+ class TeamViewerPlugin(RemoteAccessPlugin):
20
+ """TeamViewer client plugin.
21
+
22
+ Resources:
23
+ - https://teamviewer.com/en/global/support/knowledge-base/teamviewer-classic/contact-support/find-your-log-files
24
+ - https://www.systoolsgroup.com/forensics/teamviewer/
25
+ - https://benleeyr.wordpress.com/2020/05/19/teamviewer-forensics-tested-on-v15/
17
26
  """
18
27
 
19
28
  __namespace__ = "teamviewer"
20
29
 
21
- # Teamviewer log when service (Windows)
22
- GLOBS = [
30
+ SYSTEM_GLOBS = [
23
31
  "sysvol/Program Files/TeamViewer/*.log",
24
32
  "sysvol/Program Files (x86)/TeamViewer/*.log",
25
33
  ]
26
34
 
35
+ USER_GLOBS = [
36
+ "AppData/Roaming/TeamViewer/teamviewer*_logfile.log",
37
+ ]
38
+
39
+ RemoteAccessLogRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
40
+ "remoteaccess/teamviewer/log", GENERIC_LOG_RECORD_FIELDS
41
+ )
42
+
27
43
  def __init__(self, target):
28
44
  super().__init__(target)
29
45
 
30
- self.logfiles = []
46
+ self.logfiles: list[list[TargetPath, UserDetails]] = []
31
47
 
32
- # Check service globs
33
- user = None
34
- for log_glob in self.GLOBS:
48
+ # Find system service log files.
49
+ for log_glob in self.SYSTEM_GLOBS:
35
50
  for logfile in self.target.fs.glob(log_glob):
36
- self.logfiles.append([logfile, user])
51
+ self.logfiles.append([logfile, None])
37
52
 
38
- # Teamviewer logs when as user (Windows)
53
+ # Find user log files.
39
54
  for user_details in self.target.user_details.all_with_home():
40
- for logfile in user_details.home_path.glob("appdata/roaming/teamviewer/teamviewer*_logfile.log"):
41
- self.logfiles.append([logfile, user_details.user])
55
+ for log_glob in self.USER_GLOBS:
56
+ for logfile in user_details.home_path.glob(log_glob):
57
+ self.logfiles.append([logfile, user_details])
42
58
 
43
59
  def check_compatible(self) -> None:
44
60
  if not len(self.logfiles):
45
61
  raise UnsupportedPluginError("No Teamviewer logs found")
46
62
 
47
- @export(record=RemoteAccessRecord)
48
- def logs(self):
49
- """Return the content of the TeamViewer logs.
50
-
51
- TeamViewer is a commercial remote desktop application. An adversary may use it to gain persistence on a
52
- system.
63
+ @export(record=RemoteAccessLogRecord)
64
+ def logs(self) -> Iterator[RemoteAccessLogRecord]:
65
+ """Yield TeamViewer client logs.
53
66
 
54
- References:
55
- - https://www.teamviewer.com/nl/
67
+ TeamViewer is a commercial remote desktop application. An adversary may use it to gain persistence on a system.
56
68
  """
57
- for logfile, user in self.logfiles:
69
+ for logfile, user_details in self.logfiles:
58
70
  logfile = self.target.fs.path(logfile)
59
71
 
60
72
  start_date = None
@@ -83,7 +95,7 @@ class TeamviewerPlugin(RemoteAccessPlugin):
83
95
  if not re.match(START_PATTERN, line):
84
96
  continue
85
97
 
86
- ts_day, ts_time, description = line.split(" ", 2)
98
+ ts_day, ts_time, message = line.split(" ", 2)
87
99
  ts_time = ts_time.split(".")[0]
88
100
 
89
101
  # Correct for use of : as millisecond separator
@@ -99,13 +111,14 @@ class TeamviewerPlugin(RemoteAccessPlugin):
99
111
  if ts_day.count("/") == 2 and len(ts_day.split("/")[0]) == 2:
100
112
  ts_day = "20" + ts_day
101
113
 
102
- timestamp = datetime.strptime(f"{ts_day} {ts_time}", "%Y/%m/%d %H:%M:%S")
114
+ timestamp = datetime.strptime(f"{ts_day} {ts_time}", "%Y/%m/%d %H:%M:%S").replace(
115
+ tzinfo=timezone.utc
116
+ )
103
117
 
104
- yield RemoteAccessRecord(
105
- tool="teamviewer",
118
+ yield self.RemoteAccessLogRecord(
106
119
  ts=timestamp,
107
- logfile=str(logfile),
108
- description=description,
120
+ message=message,
121
+ source=logfile,
109
122
  _target=self.target,
110
- _user=user,
123
+ _user=user_details.user if user_details else None,
111
124
  )
@@ -79,15 +79,15 @@ class WindowsPlugin(OSPlugin):
79
79
  self.target.log.debug("", exc_info=e)
80
80
 
81
81
  sysvol_drive = self.target.fs.mounts.get("sysvol")
82
- # Fallback mount the sysvol to C: if we didn't manage to mount it to any other drive letter
83
- if sysvol_drive and operator.countOf(self.target.fs.mounts.values(), sysvol_drive) == 1:
82
+ if not sysvol_drive:
83
+ self.target.log.warning("No sysvol drive found")
84
+ elif operator.countOf(self.target.fs.mounts.values(), sysvol_drive) == 1:
85
+ # Fallback mount the sysvol to C: if we didn't manage to mount it to any other drive letter
84
86
  if "c:" not in self.target.fs.mounts:
85
87
  self.target.log.debug("Unable to determine drive letter of sysvol, falling back to C:")
86
88
  self.target.fs.mount("c:", sysvol_drive)
87
89
  else:
88
90
  self.target.log.warning("Unknown drive letter for sysvol")
89
- else:
90
- self.target.log.warning("No sysvol drive found")
91
91
 
92
92
  @export(property=True)
93
93
  def hostname(self) -> Optional[str]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.19.dev13
3
+ Version: 3.19.dev15
4
4
  Summary: This module ties all other Dissect modules together, it provides a programming API and command line tools which allow easy access to various data sources inside disk images or file collections (a.k.a. targets)
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -128,9 +128,9 @@ dissect/target/plugins/apps/browser/iexplore.py,sha256=g_xw0toaiyjevxO8g9XPCOqc-
128
128
  dissect/target/plugins/apps/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
129
  dissect/target/plugins/apps/container/docker.py,sha256=KxQRbKGgxkf3YFBMa7fjeJ7qo8qjFys7zEmfQhDTnLw,15305
130
130
  dissect/target/plugins/apps/remoteaccess/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
131
- dissect/target/plugins/apps/remoteaccess/anydesk.py,sha256=lHtgINWXfVpPuCTRyQmT2ZO-1vkoqiXZ7coj8cZ8p4c,3185
132
- dissect/target/plugins/apps/remoteaccess/remoteaccess.py,sha256=UQDmDC4Y-KxYl_8kaAh6SG_BLJZ6SeGnxG0gyD8tzaE,833
133
- dissect/target/plugins/apps/remoteaccess/teamviewer.py,sha256=SiEH36HM2NvdPuCjfLjQcMDsluwkcHp_3io9SoY8qFk,4032
131
+ dissect/target/plugins/apps/remoteaccess/anydesk.py,sha256=IdijK3F6ppaB_IgKL-xDljlEbb8l9S2U0xSWKqK9xRs,4294
132
+ dissect/target/plugins/apps/remoteaccess/remoteaccess.py,sha256=DWXkRDVUpFr1icK2fYwSXdZD204Xz0yRuO7rcJOwIwc,825
133
+ dissect/target/plugins/apps/remoteaccess/teamviewer.py,sha256=0nGjgbzzrhESr1PeiXlGxYQ66-aOATO59CnVSwcGn4E,4889
134
134
  dissect/target/plugins/apps/shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
135
135
  dissect/target/plugins/apps/shell/powershell.py,sha256=biPSMRWxPI6kRqP0-75yMtrw0Ti2Bzfl_xI3xbmmF48,2641
136
136
  dissect/target/plugins/apps/ssh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -254,7 +254,7 @@ dissect/target/plugins/os/unix/log/lastlog.py,sha256=Wq89wRSFZSBsoKVCxjDofnC4yw9
254
254
  dissect/target/plugins/os/unix/log/messages.py,sha256=CXA-SkMPLaCgnTQg9nzII-7tO8Il_ENQmuYvDxo33rI,4698
255
255
  dissect/target/plugins/os/unix/log/utmp.py,sha256=1nPHIaBUHt_9z6PDrvyqg4huKLihUaWLrMmgMsbaeIo,7755
256
256
  dissect/target/plugins/os/windows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
257
- dissect/target/plugins/os/windows/_os.py,sha256=Iu-xgEqtkycx1yDx4b_GL29pSz1Lew7lUYCByBOmTOE,13127
257
+ dissect/target/plugins/os/windows/_os.py,sha256=uBa0dVkFxDsxHAU3T23UEIOCgAx5R6cIpCgbGq3fflY,13131
258
258
  dissect/target/plugins/os/windows/activitiescache.py,sha256=Q2aILnhJ2rp2AwEbWwyBuSLjMbGqaYJTsavSbfkcFKE,6741
259
259
  dissect/target/plugins/os/windows/adpolicy.py,sha256=fULRFO_I_QxAn6G9SCwlLL-TLVliS13JEGnGotf7lSA,6983
260
260
  dissect/target/plugins/os/windows/amcache.py,sha256=ZZNOs3bILTf0AGkDkhoatndl0j39DXkstN7oOyxJECU,27188
@@ -344,10 +344,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
344
344
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
345
345
  dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
346
346
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
347
- dissect.target-3.19.dev13.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
348
- dissect.target-3.19.dev13.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
349
- dissect.target-3.19.dev13.dist-info/METADATA,sha256=oFZiiry3QZEqrgYijsGOlPjZn1DfUM3GBMdf8WZaIFc,12719
350
- dissect.target-3.19.dev13.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
351
- dissect.target-3.19.dev13.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
352
- dissect.target-3.19.dev13.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
353
- dissect.target-3.19.dev13.dist-info/RECORD,,
347
+ dissect.target-3.19.dev15.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
348
+ dissect.target-3.19.dev15.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
349
+ dissect.target-3.19.dev15.dist-info/METADATA,sha256=S5x1NtWPGpIryy_gl8s0sYJtuGIfVQ1mLytzBXwwDfE,12719
350
+ dissect.target-3.19.dev15.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
351
+ dissect.target-3.19.dev15.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
352
+ dissect.target-3.19.dev15.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
353
+ dissect.target-3.19.dev15.dist-info/RECORD,,