-
Notifications
You must be signed in to change notification settings - Fork 51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Plugin for teamviewer incoming connections #701
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension | ||
from dissect.target.helpers.record import create_extended_descriptor | ||
from dissect.target.plugin import NamespacePlugin | ||
from dissect.target.helpers.record import TargetRecordDescriptor | ||
|
||
RemoteAccessRecord = create_extended_descriptor([UserRecordDescriptorExtension])( | ||
"application/log/remoteaccess", | ||
|
@@ -11,7 +12,23 @@ | |
("string", "description"), | ||
], | ||
) | ||
|
||
RemoteAccessIncomingConnectionRecord = TargetRecordDescriptor( | ||
"application/log/remoteaccess", | ||
[ | ||
|
||
("string", "tool"), | ||
("path", "logfile"), | ||
("string", "remote_tvid"), | ||
("string", "tv_user_host"), | ||
("string", "tv_user_host"), | ||
("datetime", "start_time"), | ||
#("string","host"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be removed? |
||
("datetime", "end_time"), | ||
("string", "user_context"), | ||
("string", "connection_type"), | ||
("string", "connection_guid"), | ||
], | ||
) | ||
|
||
class RemoteAccessPlugin(NamespacePlugin): | ||
"""General Remote Access plugin. | ||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -1,11 +1,12 @@ | ||||||||
import re | ||||||||
from datetime import datetime | ||||||||
|
||||||||
#import datetime | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
from dissect.target.exceptions import UnsupportedPluginError | ||||||||
from dissect.target.plugin import export | ||||||||
from dissect.target.plugins.apps.remoteaccess.remoteaccess import ( | ||||||||
RemoteAccessPlugin, | ||||||||
RemoteAccessRecord, | ||||||||
RemoteAccessIncomingConnectionRecord | ||||||||
) | ||||||||
|
||||||||
START_PATTERN = re.compile(r"^(\d{2}|\d{4})/") | ||||||||
|
@@ -23,25 +24,34 @@ class TeamviewerPlugin(RemoteAccessPlugin): | |||||||
"sysvol/Program Files/TeamViewer/*.log", | ||||||||
"sysvol/Program Files (x86)/TeamViewer/*.log", | ||||||||
] | ||||||||
INCOMING_GLOBS = [ | ||||||||
"sysvol/Program Files/TeamViewer/*_incoming.txt", | ||||||||
"sysvol/Program Files (x86)/TeamViewer/*_incoming.txt", | ||||||||
] | ||||||||
|
||||||||
def __init__(self, target): | ||||||||
super().__init__(target) | ||||||||
|
||||||||
self.logfiles = [] | ||||||||
|
||||||||
self.incoming_logfiles = [] | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
# Check service globs | ||||||||
user = None | ||||||||
for log_glob in self.GLOBS: | ||||||||
for logfile in self.target.fs.glob(log_glob): | ||||||||
self.logfiles.append([logfile, user]) | ||||||||
|
||||||||
for log_glob in self.INCOMING_GLOBS: | ||||||||
|
||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
for logfile in self.target.fs.glob(log_glob): | ||||||||
self.incoming_logfiles.append(logfile) | ||||||||
|
||||||||
# Teamviewer logs when as user (Windows) | ||||||||
for user_details in self.target.user_details.all_with_home(): | ||||||||
for logfile in user_details.home_path.glob("appdata/roaming/teamviewer/teamviewer*_logfile.log"): | ||||||||
self.logfiles.append([logfile, user_details.user]) | ||||||||
|
||||||||
def check_compatible(self) -> None: | ||||||||
if not len(self.logfiles): | ||||||||
if not len(self.logfiles) and not len(self.incoming_logfiles): | ||||||||
raise UnsupportedPluginError("No Teamviewer logs found") | ||||||||
|
||||||||
@export(record=RemoteAccessRecord) | ||||||||
|
@@ -109,3 +119,98 @@ def logs(self): | |||||||
_target=self.target, | ||||||||
_user=user, | ||||||||
) | ||||||||
|
||||||||
@export(record=RemoteAccessIncomingConnectionRecord) | ||||||||
def incoming_connections(self): | ||||||||
"""Return the content of the TeamViewer incoming connections logs. | ||||||||
|
||||||||
TeamViewer is a commercial remote desktop application. An adversary may use it to gain persistence on a | ||||||||
system. | ||||||||
|
||||||||
References: | ||||||||
- https://www.teamviewer.com/nl/ | ||||||||
""" | ||||||||
hostname=str(self.target).split("Collection-")[1].split("-")[0] | ||||||||
for logfile in self.incoming_logfiles: | ||||||||
|
||||||||
logfile = self.target.fs.path(logfile) | ||||||||
|
||||||||
with logfile.open("rt",encoding='latin-1') as file: | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Does this not work? |
||||||||
next(file) | ||||||||
while True: | ||||||||
try: | ||||||||
line = file.readline() | ||||||||
|
||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
except UnicodeDecodeError: | ||||||||
continue | ||||||||
|
||||||||
# End of file, quit while loop | ||||||||
if not line: | ||||||||
|
||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
break | ||||||||
|
||||||||
line = line.strip() | ||||||||
|
||||||||
# Skip empty lines | ||||||||
if not line: | ||||||||
continue | ||||||||
|
||||||||
fields = line.split('\t') | ||||||||
if len(fields) < 7: | ||||||||
print("Line does not contain enough fields:", line) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
continue | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
remote_teamviewer_id = fields[0] | ||||||||
username_or_hostname = fields[1] | ||||||||
#print(username_or_hostname) | ||||||||
starttime = datetime.strptime(fields[2], '%d-%m-%Y %H:%M:%S') #.strftime('%Y-%m-%d %H:%M:%S') | ||||||||
endtime = datetime.strptime(fields[3], '%d-%m-%Y %H:%M:%S') #.strftime('%Y/%m/%d %H:%M:%S') | ||||||||
connected_user = fields[4] | ||||||||
connection_type = fields[5] | ||||||||
connection_guid = fields[6].strip() # Remove any trailing whitespace | ||||||||
''' | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this still relevant? Can you remove any commented code? |
||||||||
# Older logs first mention the start time and then leave out the year | ||||||||
if line.startswith("Start:"): | ||||||||
start_date = datetime.strptime(line.split()[1], "%Y/%m/%d") | ||||||||
|
||||||||
# Sometimes there are weird, mult-line/pretty print log messages. | ||||||||
# We only parse the start line which starts with year (%Y/) or month (%m/) | ||||||||
if not re.match(START_PATTERN, line): | ||||||||
continue | ||||||||
|
||||||||
ts_day, ts_time, description = line.split(" ", 2) | ||||||||
ts_time = ts_time.split(".")[0] | ||||||||
|
||||||||
# Correct for use of : as millisecond separator | ||||||||
if ts_time.count(":") > 2: | ||||||||
ts_time = ":".join(ts_time.split(":")[:3]) | ||||||||
# Correct for missing year in date | ||||||||
if ts_day.count("/") == 1: | ||||||||
if not start_date: | ||||||||
self.target.log.debug("Missing year in log line, skipping line.") | ||||||||
continue | ||||||||
ts_day = f"{start_date.year}/{ts_day}" | ||||||||
# Correct for year if short notation for 2000 is used | ||||||||
if ts_day.count("/") == 2 and len(ts_day.split("/")[0]) == 2: | ||||||||
ts_day = "20" + ts_day | ||||||||
|
||||||||
timestamp = datetime.strptime(f"{ts_day} {ts_time}", "%Y/%m/%d %H:%M:%S") | ||||||||
|
||||||||
|
||||||||
''' | ||||||||
#print(starttime) | ||||||||
#print(endtime) | ||||||||
yield RemoteAccessIncomingConnectionRecord( | ||||||||
tool="teamviewer", | ||||||||
logfile=str(logfile), | ||||||||
remote_tvid=remote_teamviewer_id, | ||||||||
tv_user_host=username_or_hostname, | ||||||||
start_time=starttime, | ||||||||
end_time=endtime, | ||||||||
user_context=connected_user, | ||||||||
connection_type=connection_type, | ||||||||
connection_guid=connection_guid, | ||||||||
|
||||||||
_target=self.target, | ||||||||
|
||||||||
) | ||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems pretty specific to TeamViewer right now, so perhaps just place this in
teamviewer.py
and call itTeamViewerIncomingConnectionRecord
.Could you also put the
start_time
andend_time
at the top?