Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 73 additions & 3 deletions dissect/target/plugins/apps/remoteaccess/teamviewer.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from __future__ import annotations

import re
from datetime import datetime
from datetime import datetime, timezone
from typing import TYPE_CHECKING

from dissect.target.exceptions import UnsupportedPluginError
from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
from dissect.target.helpers.record import create_extended_descriptor
from dissect.target.helpers.record import TargetRecordDescriptor, create_extended_descriptor
from dissect.target.plugin import export
from dissect.target.plugins.apps.remoteaccess.remoteaccess import (
GENERIC_LOG_RECORD_FIELDS,
Expand Down Expand Up @@ -49,6 +49,20 @@
)


TeamviewerIncomingRecord = TargetRecordDescriptor(
"remoteaccess/teamviewer/incoming",
[
("datetime", "ts"),
("datetime", "end"),
("string", "remote_id"),
("string", "name"),
("string", "user"),
("string", "connection_type"),
("string", "connection_id"),
],
)


class TeamViewerPlugin(RemoteAccessPlugin):
"""TeamViewer client plugin.

Expand All @@ -66,6 +80,11 @@ class TeamViewerPlugin(RemoteAccessPlugin):
"/var/log/teamviewer*/*.log",
)

SYSTEM_INCOMING_GLOBS = (
"sysvol/Program Files/TeamViewer/*_incoming.txt",
"sysvol/Program Files (x86)/TeamViewer/*_incoming.txt",
)

USER_GLOBS = (
"AppData/Roaming/TeamViewer/teamviewer*_logfile.log",
"Library/Logs/TeamViewer/teamviewer*_logfile*.log",
Expand All @@ -79,20 +98,26 @@ def __init__(self, target: Target):
super().__init__(target)

self.logfiles: set[tuple[str, UserDetails | None]] = set()
self.incoming_logfiles: set[str] = set()

# Find system service log files.
for log_glob in self.SYSTEM_GLOBS:
for logfile in self.target.fs.glob(log_glob):
self.logfiles.add((logfile, None))

# Find system incoming connection log files.
for log_glob in self.SYSTEM_INCOMING_GLOBS:
for logfile in self.target.fs.glob(log_glob):
self.incoming_logfiles.add(logfile)

# Find user log files.
for user_details in self.target.user_details.all_with_home():
for log_glob in self.USER_GLOBS:
for logfile in user_details.home_path.glob(log_glob):
self.logfiles.add((logfile, user_details))

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 on target")

@export(record=RemoteAccessLogRecord)
Expand Down Expand Up @@ -169,6 +194,51 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]:
_user=user_details.user if user_details else None,
)

@export(record=TeamviewerIncomingRecord)
def incoming(self) -> Iterator[TeamviewerIncomingRecord]:
"""Yield TeamViewer incoming connection logs.

TeamViewer is a commercial remote desktop application. An adversary may use it to gain persistence on a system.
"""
for logfile in self.incoming_logfiles:
logfile = self.target.fs.path(logfile)

for line in logfile.open("rt", errors="replace"):
if not (line := line.strip()) or line.startswith("# "):
continue

fields = line.split("\t")
if len(fields) < 7:
self.target.log.warning("Skipping TeamViewer incoming connection log line %r in %s", line, logfile)
continue

try:
start = datetime.strptime(fields[2], "%d-%m-%Y %H:%M:%S").replace(tzinfo=timezone.utc)
end = datetime.strptime(fields[3], "%d-%m-%Y %H:%M:%S").replace(tzinfo=timezone.utc)
except Exception as e:
self.target.log.warning(
"Unable to parse timestamps in TeamViewer incoming connection log line %r in %s", line, logfile
)
self.target.log.debug("", exc_info=e)
continue

remote_id = fields[0]
name = fields[1]
user = fields[4]
connection_type = fields[5]
connection_id = fields[6]

yield TeamviewerIncomingRecord(
ts=start,
end=end,
remote_id=remote_id,
name=name,
user=user,
connection_type=connection_type,
connection_id=connection_id,
_target=self.target,
)


def parse_start(line: str) -> datetime | None:
"""TeamViewer ``Start`` messages can be formatted in different ways
Expand Down
30 changes: 30 additions & 0 deletions tests/plugins/apps/remoteaccess/test_teamviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,33 @@ def test_teamviewer_timezone(target_win_users: Target, fs_win: VirtualFilesystem
assert records[0].message == "1234 5678 G1 LanguageControl: device language is 'enUS'"
assert records[0].source == "C:\\Users\\John\\AppData\\Roaming\\TeamViewer\\TeamViewer1337_Logfile.log"
assert records[0].username == "John"


def test_teamviewer_incoming(target_win_users: Target, fs_win: VirtualFilesystem) -> None:
"""Test TeamViewer incoming connection log parsing."""
log = """
1031857653 DESKTOP-CAK7OMO 11-09-2022 14:44:03 11-09-2022 15:27:53 SERVER TV RemoteControl {C2CC2F16-D1F4-4547-9928-EE63891D4CC0}
1031857653 DESKTOP-CAK7OMO 22-12-2022 19:25:22 22-12-2022 19:49:28 Server RemoteControl {4BF22BA7-32BA-4F64-8755-97E6E45F9883}
""" # noqa: E501
fs_win.map_file_fh("Program Files/TeamViewer/Connections_incoming.txt", BytesIO(dedent(log).encode()))

target_win_users.add_plugin(TeamViewerPlugin)

records = list(target_win_users.teamviewer.incoming())
assert len(records) == 2

assert records[0].ts == datetime(2022, 9, 11, 14, 44, 3, tzinfo=timezone.utc)
assert records[0].end == datetime(2022, 9, 11, 15, 27, 53, tzinfo=timezone.utc)
assert records[0].remote_id == "1031857653"
assert records[0].name == "DESKTOP-CAK7OMO"
assert records[0].user == "SERVER TV"
assert records[0].connection_type == "RemoteControl"
assert records[0].connection_id == "{C2CC2F16-D1F4-4547-9928-EE63891D4CC0}"

assert records[1].ts == datetime(2022, 12, 22, 19, 25, 22, tzinfo=timezone.utc)
assert records[1].end == datetime(2022, 12, 22, 19, 49, 28, tzinfo=timezone.utc)
assert records[1].remote_id == "1031857653"
assert records[1].name == "DESKTOP-CAK7OMO"
assert records[1].user == "Server"
assert records[1].connection_type == "RemoteControl"
assert records[1].connection_id == "{4BF22BA7-32BA-4F64-8755-97E6E45F9883}"
Loading