11from __future__ import annotations
22
33import re
4- from datetime import datetime
4+ from datetime import datetime , timezone
55from typing import TYPE_CHECKING
66
77from dissect .target .exceptions import UnsupportedPluginError
88from dissect .target .helpers .descriptor_extensions import UserRecordDescriptorExtension
9- from dissect .target .helpers .record import create_extended_descriptor
9+ from dissect .target .helpers .record import TargetRecordDescriptor , create_extended_descriptor
1010from dissect .target .plugin import export
1111from dissect .target .plugins .apps .remoteaccess .remoteaccess import (
1212 GENERIC_LOG_RECORD_FIELDS ,
4949)
5050
5151
52+ TeamviewerIncomingRecord = TargetRecordDescriptor (
53+ "remoteaccess/teamviewer/incoming" ,
54+ [
55+ ("datetime" , "ts" ),
56+ ("datetime" , "end" ),
57+ ("string" , "remote_id" ),
58+ ("string" , "name" ),
59+ ("string" , "user" ),
60+ ("string" , "connection_type" ),
61+ ("string" , "connection_id" ),
62+ ],
63+ )
64+
65+
5266class TeamViewerPlugin (RemoteAccessPlugin ):
5367 """TeamViewer client plugin.
5468
@@ -66,6 +80,11 @@ class TeamViewerPlugin(RemoteAccessPlugin):
6680 "/var/log/teamviewer*/*.log" ,
6781 )
6882
83+ SYSTEM_INCOMING_GLOBS = (
84+ "sysvol/Program Files/TeamViewer/*_incoming.txt" ,
85+ "sysvol/Program Files (x86)/TeamViewer/*_incoming.txt" ,
86+ )
87+
6988 USER_GLOBS = (
7089 "AppData/Roaming/TeamViewer/teamviewer*_logfile.log" ,
7190 "Library/Logs/TeamViewer/teamviewer*_logfile*.log" ,
@@ -79,20 +98,26 @@ def __init__(self, target: Target):
7998 super ().__init__ (target )
8099
81100 self .logfiles : set [tuple [str , UserDetails | None ]] = set ()
101+ self .incoming_logfiles : set [str ] = set ()
82102
83103 # Find system service log files.
84104 for log_glob in self .SYSTEM_GLOBS :
85105 for logfile in self .target .fs .glob (log_glob ):
86106 self .logfiles .add ((logfile , None ))
87107
108+ # Find system incoming connection log files.
109+ for log_glob in self .SYSTEM_INCOMING_GLOBS :
110+ for logfile in self .target .fs .glob (log_glob ):
111+ self .incoming_logfiles .add (logfile )
112+
88113 # Find user log files.
89114 for user_details in self .target .user_details .all_with_home ():
90115 for log_glob in self .USER_GLOBS :
91116 for logfile in user_details .home_path .glob (log_glob ):
92117 self .logfiles .add ((logfile , user_details ))
93118
94119 def check_compatible (self ) -> None :
95- if not len (self .logfiles ):
120+ if not len (self .logfiles ) and not len ( self . incoming_logfiles ) :
96121 raise UnsupportedPluginError ("No Teamviewer logs found on target" )
97122
98123 @export (record = RemoteAccessLogRecord )
@@ -169,6 +194,51 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]:
169194 _user = user_details .user if user_details else None ,
170195 )
171196
197+ @export (record = TeamviewerIncomingRecord )
198+ def incoming (self ) -> Iterator [TeamviewerIncomingRecord ]:
199+ """Yield TeamViewer incoming connection logs.
200+
201+ TeamViewer is a commercial remote desktop application. An adversary may use it to gain persistence on a system.
202+ """
203+ for logfile in self .incoming_logfiles :
204+ logfile = self .target .fs .path (logfile )
205+
206+ for line in logfile .open ("rt" , errors = "replace" ):
207+ if not (line := line .strip ()) or line .startswith ("# " ):
208+ continue
209+
210+ fields = line .split ("\t " )
211+ if len (fields ) < 7 :
212+ self .target .log .warning ("Skipping TeamViewer incoming connection log line %r in %s" , line , logfile )
213+ continue
214+
215+ try :
216+ start = datetime .strptime (fields [2 ], "%d-%m-%Y %H:%M:%S" ).replace (tzinfo = timezone .utc )
217+ end = datetime .strptime (fields [3 ], "%d-%m-%Y %H:%M:%S" ).replace (tzinfo = timezone .utc )
218+ except Exception as e :
219+ self .target .log .warning (
220+ "Unable to parse timestamps in TeamViewer incoming connection log line %r in %s" , line , logfile
221+ )
222+ self .target .log .debug ("" , exc_info = e )
223+ continue
224+
225+ remote_id = fields [0 ]
226+ name = fields [1 ]
227+ user = fields [4 ]
228+ connection_type = fields [5 ]
229+ connection_id = fields [6 ]
230+
231+ yield TeamviewerIncomingRecord (
232+ ts = start ,
233+ end = end ,
234+ remote_id = remote_id ,
235+ name = name ,
236+ user = user ,
237+ connection_type = connection_type ,
238+ connection_id = connection_id ,
239+ _target = self .target ,
240+ )
241+
172242
173243def parse_start (line : str ) -> datetime | None :
174244 """TeamViewer ``Start`` messages can be formatted in different ways
0 commit comments