dissect.target 3.19.dev13__py3-none-any.whl → 3.19.dev15__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- dissect/target/plugins/apps/remoteaccess/anydesk.py +70 -50
- dissect/target/plugins/apps/remoteaccess/remoteaccess.py +8 -8
- dissect/target/plugins/apps/remoteaccess/teamviewer.py +44 -31
- dissect/target/plugins/os/windows/_os.py +4 -4
- {dissect.target-3.19.dev13.dist-info → dissect.target-3.19.dev15.dist-info}/METADATA +1 -1
- {dissect.target-3.19.dev13.dist-info → dissect.target-3.19.dev15.dist-info}/RECORD +11 -11
- {dissect.target-3.19.dev13.dist-info → dissect.target-3.19.dev15.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.19.dev13.dist-info → dissect.target-3.19.dev15.dist-info}/LICENSE +0 -0
- {dissect.target-3.19.dev13.dist-info → dissect.target-3.19.dev15.dist-info}/WHEEL +0 -0
- {dissect.target-3.19.dev13.dist-info → dissect.target-3.19.dev15.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.19.dev13.dist-info → dissect.target-3.19.dev15.dist-info}/top_level.txt +0 -0
@@ -1,91 +1,111 @@
|
|
1
|
-
|
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
|
-
|
21
|
-
"
|
22
|
-
|
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
|
-
|
28
|
-
"
|
29
|
-
|
30
|
-
"
|
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.
|
56
|
+
self.trace_files: set[tuple[TargetPath, UserDetails]] = set()
|
37
57
|
|
38
|
-
#
|
58
|
+
# Service globs
|
39
59
|
user = None
|
40
|
-
for
|
41
|
-
for
|
42
|
-
self.
|
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
|
-
#
|
64
|
+
# User globs
|
45
65
|
for user_details in self.target.user_details.all_with_home():
|
46
|
-
for
|
47
|
-
for
|
48
|
-
self.
|
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
|
52
|
-
raise UnsupportedPluginError("No Anydesk
|
71
|
+
if not self.trace_files:
|
72
|
+
raise UnsupportedPluginError("No Anydesk trace files found on target")
|
53
73
|
|
54
|
-
@export(record=
|
55
|
-
def logs(self):
|
56
|
-
"""
|
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)
|
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
|
66
|
-
|
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
|
-
|
72
|
-
if not line:
|
73
|
-
continue
|
74
|
-
|
75
|
-
if "* * * * * * * * * * * * * *" in line:
|
90
|
+
if not line or "* * * * * * * * * * * * * *" in line:
|
76
91
|
continue
|
77
92
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
6
|
-
"
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
15
|
-
"""
|
16
|
-
|
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
|
-
|
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
|
-
#
|
33
|
-
|
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,
|
51
|
+
self.logfiles.append([logfile, None])
|
37
52
|
|
38
|
-
#
|
53
|
+
# Find user log files.
|
39
54
|
for user_details in self.target.user_details.all_with_home():
|
40
|
-
for
|
41
|
-
|
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=
|
48
|
-
def logs(self):
|
49
|
-
"""
|
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
|
-
|
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,
|
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,
|
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
|
105
|
-
tool="teamviewer",
|
118
|
+
yield self.RemoteAccessLogRecord(
|
106
119
|
ts=timestamp,
|
107
|
-
|
108
|
-
|
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
|
-
|
83
|
-
|
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.
|
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=
|
132
|
-
dissect/target/plugins/apps/remoteaccess/remoteaccess.py,sha256=
|
133
|
-
dissect/target/plugins/apps/remoteaccess/teamviewer.py,sha256=
|
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=
|
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.
|
348
|
-
dissect.target-3.19.
|
349
|
-
dissect.target-3.19.
|
350
|
-
dissect.target-3.19.
|
351
|
-
dissect.target-3.19.
|
352
|
-
dissect.target-3.19.
|
353
|
-
dissect.target-3.19.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.19.dev13.dist-info → dissect.target-3.19.dev15.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|