From 1e13071fd9ff1f82099ba1f56b7bb54a8a4ed9ce Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Mon, 9 Mar 2026 14:25:38 +0000 Subject: [PATCH 01/11] Use the target timezone in the teamviewer plugin if the utcoffsets match Determine whether the timezone found by parsing `Start:` is the same timezone as that of the target. This automatically adjust the timestamp when dst time ends or starts. There is a grey area where it cannot determine whether a timestamp is in dst time or not, so when it is observed that the previous timestamp was less than the current one, it adjust the timezone for those entries (UTC+XXXX) --- .../plugins/apps/remoteaccess/teamviewer.py | 26 +++++++++++++++--- .../apps/remoteaccess/test_teamviewer.py | 27 +++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/dissect/target/plugins/apps/remoteaccess/teamviewer.py b/dissect/target/plugins/apps/remoteaccess/teamviewer.py index 378ddf0687..d8980f99b7 100644 --- a/dissect/target/plugins/apps/remoteaccess/teamviewer.py +++ b/dissect/target/plugins/apps/remoteaccess/teamviewer.py @@ -1,7 +1,7 @@ from __future__ import annotations import re -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from typing import TYPE_CHECKING from dissect.target.exceptions import UnsupportedPluginError @@ -132,6 +132,7 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]: logfile = self.target.fs.path(logfile) start_date = None + prev_timestamp = None for line in logfile.open("rt", errors="replace"): if not (line := line.strip()) or line.startswith("# "): continue @@ -143,6 +144,15 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]: self.target.log.warning("Failed to parse Start message %r in %s", line, logfile) self.target.log.debug("", exc_info=e) + if start_date is None: + continue + + # See whether the utcoffset with the two different timezones are the same + target_start_date = start_date.replace(tzinfo=target_tz) + if target_start_date.utcoffset() == start_date.utcoffset(): + # Adjust the start_date so it uses the timezone known to target throughtout + start_date = target_start_date + continue if not (match := RE_LOG.search(line)): @@ -178,14 +188,22 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]: time += ".000000" try: - timestamp = datetime.strptime(f"{date} {time}", "%Y/%m/%d %H:%M:%S.%f").replace( - tzinfo=start_date.tzinfo if start_date else target_tz - ) + tz_info = start_date.tzinfo if start_date else target_tz + timestamp = datetime.strptime(f"{date} {time}", "%Y/%m/%d %H:%M:%S.%f").replace(tzinfo=tz_info) except Exception as e: self.target.log.warning("Unable to parse timestamp %r in file %s", line, logfile) self.target.log.debug("", exc_info=e) timestamp = 0 + if timestamp: + if prev_timestamp and prev_timestamp > timestamp: + # We might currently be in a grey area where the dst period ended. + # During this time we adjust the timedelta to use the correct time. + delta = (timestamp + timedelta(days=1)).utcoffset() + timestamp = timestamp.replace(tzinfo=timezone(delta)) if delta else timestamp + else: + prev_timestamp = timestamp + yield self.RemoteAccessLogRecord( ts=timestamp, message=log.get("message"), diff --git a/tests/plugins/apps/remoteaccess/test_teamviewer.py b/tests/plugins/apps/remoteaccess/test_teamviewer.py index 0336f6380f..5698002496 100644 --- a/tests/plugins/apps/remoteaccess/test_teamviewer.py +++ b/tests/plugins/apps/remoteaccess/test_teamviewer.py @@ -4,6 +4,7 @@ from io import BytesIO from textwrap import dedent from typing import TYPE_CHECKING +from unittest.mock import patch from dissect.target.plugins.apps.remoteaccess.teamviewer import TeamViewerPlugin from tests._utils import absolute_path @@ -135,3 +136,29 @@ def test_teamviewer_incoming(target_win_users: Target, fs_win: VirtualFilesystem assert records[1].user == "Server" assert records[1].connection_type == "RemoteControl" assert records[1].connection_id == "{4BF22BA7-32BA-4F64-8755-97E6E45F9883}" + + +def test_teamviewer_daylight_savings_time(target_win_tzinfo: Target, fs_win: VirtualFilesystem) -> None: + """Test whether the teamviewer plugin handles dst correctly.""" + + log = """ + Start: 2025/10/26 02:50:32.134 (UTC+2:00) + 2025/10/26 02:50:32.300 1234 5678 G1 Example DST timestamp + 2025/10/26 02:00:03.400 1234 5678 G1 Example non DST timestamp + 2025/10/26 02:30:03.400 1234 5678 G1 Example continued timestamp + Start: 2025/10/27 01:02:03.123 (UTC+1:00) + 2025/10/27 01:02:03.500 1234 5678 G1 Example non DST timestamp + """ + fs_win.map_file_fh("Program Files/TeamViewer/Teamviewer_Log.log", BytesIO(dedent(log).encode())) + # set timezone to something that has a dst time record + eu_timezone = target_win_tzinfo.datetime.tz("W. Europe Standard Time") + target_win_tzinfo.add_plugin(TeamViewerPlugin) + + with patch.object(target_win_tzinfo.datetime, "_tzinfo", eu_timezone): + records = list(target_win_tzinfo.teamviewer.logs()) + assert len(records) == 4 + + assert records[0].ts.astimezone(timezone.utc) == datetime(2025, 10, 26, 0, 50, 32, 300000, tzinfo=timezone.utc) + assert records[1].ts.astimezone(timezone.utc) == datetime(2025, 10, 26, 1, 0, 3, 400000, tzinfo=timezone.utc) + assert records[2].ts.astimezone(timezone.utc) == datetime(2025, 10, 26, 1, 30, 3, 400000, tzinfo=timezone.utc) + assert records[3].ts.astimezone(timezone.utc) == datetime(2025, 10, 27, 0, 2, 3, 500000, tzinfo=timezone.utc) From a7cdb7d063235319324a0ef9b4c2001de3cd923b Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Mon, 9 Mar 2026 14:29:55 +0000 Subject: [PATCH 02/11] Rewrite parse_start to account for only `(UTC)` inside the string And add tests for it --- .../plugins/apps/remoteaccess/teamviewer.py | 21 +++++++++---- .../apps/remoteaccess/test_teamviewer.py | 30 +++++++++++++++++-- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/dissect/target/plugins/apps/remoteaccess/teamviewer.py b/dissect/target/plugins/apps/remoteaccess/teamviewer.py index d8980f99b7..f7bc62087c 100644 --- a/dissect/target/plugins/apps/remoteaccess/teamviewer.py +++ b/dissect/target/plugins/apps/remoteaccess/teamviewer.py @@ -266,6 +266,7 @@ def parse_start(line: str) -> datetime | None: Start: 2021/11/11 12:34:56 Start: 2024/12/31 01:02:03.123 (UTC+2:00) + Start: 2025/01/01 12:28:41.436 (UTC) """ if match := RE_START.search(line): dt = match.groupdict() @@ -275,13 +276,21 @@ def parse_start(line: str) -> datetime | None: dt["time"] = dt["time"].rsplit(".")[0] # Format timezone, e.g. "UTC+2:00" to "UTC+0200" - if dt["timezone"]: - name, operator, amount = re.split(r"(\+|\-)", dt["timezone"]) - amount = int(amount.replace(":", "")) - dt["timezone"] = f"{name}{operator}{amount:0>4d}" + if timezone := dt["timezone"]: + identifier = " %Z%z" + # Handle just UTC timezone + if timezone.lower() == "utc": + timezone = " UTC+00:00" + else: + name, operator, amount = re.split(r"(\+|\-)", timezone) + amount = int(amount.replace(":", "")) + timezone = f" {name}{operator}{amount:0>4d}" + else: + timezone = "" + identifier = "" return datetime.strptime( # noqa: DTZ007 - f"{dt['date']} {dt['time']}" + (f" {dt['timezone']}" if dt["timezone"] else ""), - "%Y/%m/%d %H:%M:%S" + (" %Z%z" if dt["timezone"] else ""), + f"{dt['date']} {dt['time']}{timezone}", + f"%Y/%m/%d %H:%M:%S{identifier}", ) return None diff --git a/tests/plugins/apps/remoteaccess/test_teamviewer.py b/tests/plugins/apps/remoteaccess/test_teamviewer.py index 5698002496..5859f1cc0e 100644 --- a/tests/plugins/apps/remoteaccess/test_teamviewer.py +++ b/tests/plugins/apps/remoteaccess/test_teamviewer.py @@ -1,12 +1,14 @@ from __future__ import annotations -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from io import BytesIO from textwrap import dedent from typing import TYPE_CHECKING from unittest.mock import patch -from dissect.target.plugins.apps.remoteaccess.teamviewer import TeamViewerPlugin +import pytest + +from dissect.target.plugins.apps.remoteaccess.teamviewer import TeamViewerPlugin, parse_start from tests._utils import absolute_path if TYPE_CHECKING: @@ -162,3 +164,27 @@ def test_teamviewer_daylight_savings_time(target_win_tzinfo: Target, fs_win: Vir assert records[1].ts.astimezone(timezone.utc) == datetime(2025, 10, 26, 1, 0, 3, 400000, tzinfo=timezone.utc) assert records[2].ts.astimezone(timezone.utc) == datetime(2025, 10, 26, 1, 30, 3, 400000, tzinfo=timezone.utc) assert records[3].ts.astimezone(timezone.utc) == datetime(2025, 10, 27, 0, 2, 3, 500000, tzinfo=timezone.utc) + + +@pytest.mark.parametrize( + argnames=("line", "expected_date"), + argvalues=[ + pytest.param( + "Start: 2021/11/11 12:34:56", + datetime(2021, 11, 11, 12, 34, 56), # noqa DTZ001 + id="Parse withouth timezone", + ), + pytest.param( + "Start: 2024/12/31 01:02:03.123 (UTC+2:00)", + datetime(2024, 12, 31, 1, 2, 3, tzinfo=timezone(timedelta(seconds=7200))), + id="Parse (UTC+2:00)", + ), + pytest.param( + "Start: 2025/01/01 12:28:41.436 (UTC)", + datetime(2025, 1, 1, 12, 28, 41, tzinfo=timezone.utc), + id="Parse UTC without offset", + ), + ], +) +def test_teamviewer_parse_start(line: str, expected_date: datetime) -> None: + assert parse_start(line) == expected_date From 334d7fa8cf0baf878a3460f17c42e3d5a73ec1f9 Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Mon, 9 Mar 2026 14:49:47 +0000 Subject: [PATCH 03/11] Fix linting --- tests/plugins/apps/remoteaccess/test_teamviewer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/plugins/apps/remoteaccess/test_teamviewer.py b/tests/plugins/apps/remoteaccess/test_teamviewer.py index 5859f1cc0e..4dade5394a 100644 --- a/tests/plugins/apps/remoteaccess/test_teamviewer.py +++ b/tests/plugins/apps/remoteaccess/test_teamviewer.py @@ -142,7 +142,6 @@ def test_teamviewer_incoming(target_win_users: Target, fs_win: VirtualFilesystem def test_teamviewer_daylight_savings_time(target_win_tzinfo: Target, fs_win: VirtualFilesystem) -> None: """Test whether the teamviewer plugin handles dst correctly.""" - log = """ Start: 2025/10/26 02:50:32.134 (UTC+2:00) 2025/10/26 02:50:32.300 1234 5678 G1 Example DST timestamp From 1d7e4c5145fd8d6eae7ebe0e2fd9032ebc1973a8 Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Wed, 11 Mar 2026 12:39:32 +0000 Subject: [PATCH 04/11] Replace fold instead of changing the timezone with a delta --- .../plugins/apps/remoteaccess/teamviewer.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/dissect/target/plugins/apps/remoteaccess/teamviewer.py b/dissect/target/plugins/apps/remoteaccess/teamviewer.py index f7bc62087c..85cfc17175 100644 --- a/dissect/target/plugins/apps/remoteaccess/teamviewer.py +++ b/dissect/target/plugins/apps/remoteaccess/teamviewer.py @@ -1,7 +1,7 @@ from __future__ import annotations import re -from datetime import datetime, timedelta, timezone +from datetime import datetime, timezone from typing import TYPE_CHECKING from dissect.target.exceptions import UnsupportedPluginError @@ -195,14 +195,12 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]: self.target.log.debug("", exc_info=e) timestamp = 0 - if timestamp: - if prev_timestamp and prev_timestamp > timestamp: - # We might currently be in a grey area where the dst period ended. - # During this time we adjust the timedelta to use the correct time. - delta = (timestamp + timedelta(days=1)).utcoffset() - timestamp = timestamp.replace(tzinfo=timezone(delta)) if delta else timestamp - else: - prev_timestamp = timestamp + if timestamp and prev_timestamp and prev_timestamp > timestamp: + # We might currently be in a grey area where the dst period ended. + # During this time we adjust the timedelta to use the correct time. + timestamp = timestamp.replace(fold=1) + + prev_timestamp = timestamp yield self.RemoteAccessLogRecord( ts=timestamp, From 3562f9ff50a22a6a1a6b7d2a5a48f10afd4c20f6 Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Wed, 11 Mar 2026 14:26:18 +0000 Subject: [PATCH 05/11] Persist `fold` between `Start:` --- dissect/target/plugins/apps/remoteaccess/teamviewer.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dissect/target/plugins/apps/remoteaccess/teamviewer.py b/dissect/target/plugins/apps/remoteaccess/teamviewer.py index 85cfc17175..401fd5db61 100644 --- a/dissect/target/plugins/apps/remoteaccess/teamviewer.py +++ b/dissect/target/plugins/apps/remoteaccess/teamviewer.py @@ -133,6 +133,7 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]: start_date = None prev_timestamp = None + fold = 0 for line in logfile.open("rt", errors="replace"): if not (line := line.strip()) or line.startswith("# "): continue @@ -140,6 +141,7 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]: if line.startswith("Start:"): try: start_date = parse_start(line) + fold = 0 except Exception as e: self.target.log.warning("Failed to parse Start message %r in %s", line, logfile) self.target.log.debug("", exc_info=e) @@ -197,8 +199,11 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]: if timestamp and prev_timestamp and prev_timestamp > timestamp: # We might currently be in a grey area where the dst period ended. - # During this time we adjust the timedelta to use the correct time. - timestamp = timestamp.replace(fold=1) + # replace the fold value of the timestamp + fold = 1 + + if timestamp: + timestamp = timestamp.replace(fold=fold) prev_timestamp = timestamp From 2d708e15b69a3d0e55457efbdb98a274bac1a098 Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Thu, 12 Mar 2026 09:06:04 +0000 Subject: [PATCH 06/11] Depend on new flow.record version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e9ead0e5aa..c42f2339ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ dependencies = [ "dissect.regf>=3.13,<4", "dissect.util>=3,<4", "dissect.volume>=3.17,<4", - "flow.record~=3.21.0", + "flow.record>=3.22.dev10", #TODO: Update at release "structlog", ] dynamic = ["version"] From 5e46152742afccad445967fa8e51d6cd42f7886c Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Thu, 12 Mar 2026 09:35:17 +0000 Subject: [PATCH 07/11] Change grouped record definitions to use __name__ and __records__ during tests --- tests/helpers/test_modifier.py | 10 +++++----- tests/plugins/os/windows/test_tasks.py | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/helpers/test_modifier.py b/tests/helpers/test_modifier.py index 0b1e3b9f73..0094ff8781 100644 --- a/tests/helpers/test_modifier.py +++ b/tests/helpers/test_modifier.py @@ -63,11 +63,11 @@ def test_hash_path_records_with_paths( ): hashed_record = hash_function(target_win, record) - assert hashed_record.name == "test" - assert len(hashed_record.records) == expected_records - assert hashed_record.records[0] == record + assert hashed_record.__name__ == "test" + assert len(hashed_record.__records__) == expected_records + assert hashed_record.__records__[0] == record - for name, _record in zip(path_field_names, hashed_record.records[1:], strict=False): + for name, _record in zip(path_field_names, hashed_record.__records__[1:], strict=False): assert getattr(_record, f"{name}_resolved") is not None assert getattr(_record, f"{name}_digest").__dict__ == digest(HASHES).__dict__ @@ -151,6 +151,6 @@ def test_resolved_modifier(record: Record, target_win: Target, resolve_function: resolved_record = resolve_function(target_win, record) - for _record in resolved_record.records[1:]: + for _record in resolved_record.__records__[1:]: assert _record.name_resolved is not None assert not hasattr(_record, "name_digest") diff --git a/tests/plugins/os/windows/test_tasks.py b/tests/plugins/os/windows/test_tasks.py index 4b18d9353a..b1da607234 100644 --- a/tests/plugins/os/windows/test_tasks.py +++ b/tests/plugins/os/windows/test_tasks.py @@ -171,8 +171,8 @@ def assert_at_task_grouped_padding(at_task_grouped: GroupedRecord) -> None: def assert_at_task_grouped_monthlydow(at_task_grouped: GroupedRecord) -> None: - assert at_task_grouped.records[0].enabled - assert at_task_grouped.records[1].trigger_enabled + assert at_task_grouped.__records__[0].enabled + assert at_task_grouped.__records__[1].trigger_enabled assert at_task_grouped.start_boundary == datetime.fromisoformat("2023-05-11 00:00:00+00:00") assert at_task_grouped.end_boundary == datetime.fromisoformat("2023-05-20 00:00:00+00:00") assert at_task_grouped.repetition_interval == "PT1M" @@ -186,8 +186,8 @@ def assert_at_task_grouped_monthlydow(at_task_grouped: GroupedRecord) -> None: def assert_at_task_grouped_weekly(at_task_grouped: GroupedRecord) -> None: - assert at_task_grouped.records[0].enabled - assert at_task_grouped.records[1].trigger_enabled + assert at_task_grouped.__records__[0].enabled + assert at_task_grouped.__records__[1].trigger_enabled assert at_task_grouped.end_boundary == datetime.fromisoformat("2023-05-27 00:00:00+00:00") assert at_task_grouped.execution_time_limit == "P3D" assert at_task_grouped.repetition_duration == "PT1H" @@ -201,8 +201,8 @@ def assert_at_task_grouped_weekly(at_task_grouped: GroupedRecord) -> None: def assert_at_task_grouped_monthly_date(at_task_grouped: GroupedRecord) -> None: - assert at_task_grouped.records[0].enabled - assert at_task_grouped.records[1].trigger_enabled + assert at_task_grouped.__records__[0].enabled + assert at_task_grouped.__records__[1].trigger_enabled assert at_task_grouped.day_of_month == [15] assert at_task_grouped.months_of_year == ["March", "May", "June", "July", "August", "October"] assert at_task_grouped.end_boundary == datetime.fromisoformat("2023-05-29 00:00:00+00:00") @@ -214,8 +214,8 @@ def assert_at_task_grouped_monthly_date(at_task_grouped: GroupedRecord) -> None: def assert_xml_task_trigger_properties(xml_task: GroupedRecord) -> None: - assert xml_task.records[0].enabled - assert xml_task.records[1].trigger_enabled + assert xml_task.__records__[0].enabled + assert xml_task.__records__[1].trigger_enabled assert xml_task.days_between_triggers == 1 assert xml_task.start_boundary == datetime.fromisoformat("2023-05-12 00:00:00+00:00") From f92238d802e124cbbc4e396131e2cbcb8b6e41d4 Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Tue, 17 Mar 2026 11:00:46 +0100 Subject: [PATCH 08/11] Apply suggestions from code review Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/plugins/apps/remoteaccess/teamviewer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dissect/target/plugins/apps/remoteaccess/teamviewer.py b/dissect/target/plugins/apps/remoteaccess/teamviewer.py index 401fd5db61..baf085dc6a 100644 --- a/dissect/target/plugins/apps/remoteaccess/teamviewer.py +++ b/dissect/target/plugins/apps/remoteaccess/teamviewer.py @@ -141,18 +141,18 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]: if line.startswith("Start:"): try: start_date = parse_start(line) - fold = 0 except Exception as e: self.target.log.warning("Failed to parse Start message %r in %s", line, logfile) self.target.log.debug("", exc_info=e) + fold = 0 if start_date is None: continue # See whether the utcoffset with the two different timezones are the same target_start_date = start_date.replace(tzinfo=target_tz) if target_start_date.utcoffset() == start_date.utcoffset(): - # Adjust the start_date so it uses the timezone known to target throughtout + # Adjust the start_date so it uses the timezone known to target throughout start_date = target_start_date continue @@ -195,7 +195,7 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]: except Exception as e: self.target.log.warning("Unable to parse timestamp %r in file %s", line, logfile) self.target.log.debug("", exc_info=e) - timestamp = 0 + continue if timestamp and prev_timestamp and prev_timestamp > timestamp: # We might currently be in a grey area where the dst period ended. From 3c2e67843268c745794818cd967ca009ac63c7f1 Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Tue, 17 Mar 2026 14:13:28 +0000 Subject: [PATCH 09/11] Set timestamp to start_date and set start_date to `None` if an exception occurred during parsing The only case it gets in this exception is when `Start:` gets parsed and the next lines contain some system information. Technically those records have the same time as the start_date In the worst case scenario the `start_date` would be None if there is an error or the line couldn't get parsed. --- dissect/target/plugins/apps/remoteaccess/teamviewer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dissect/target/plugins/apps/remoteaccess/teamviewer.py b/dissect/target/plugins/apps/remoteaccess/teamviewer.py index baf085dc6a..9aa8d0bfe7 100644 --- a/dissect/target/plugins/apps/remoteaccess/teamviewer.py +++ b/dissect/target/plugins/apps/remoteaccess/teamviewer.py @@ -144,6 +144,8 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]: except Exception as e: self.target.log.warning("Failed to parse Start message %r in %s", line, logfile) self.target.log.debug("", exc_info=e) + # Unset start_date if it was already defined + start_date = None fold = 0 if start_date is None: @@ -195,7 +197,7 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]: except Exception as e: self.target.log.warning("Unable to parse timestamp %r in file %s", line, logfile) self.target.log.debug("", exc_info=e) - continue + timestamp = start_date if timestamp and prev_timestamp and prev_timestamp > timestamp: # We might currently be in a grey area where the dst period ended. From 873484834a7541dcaff9896a1c4d3979004fceed Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Wed, 18 Mar 2026 09:49:02 +0000 Subject: [PATCH 10/11] Add a test when parsing invalid datetimes Need to be invalid times that exceed normal datetime behaviour, because if they are not numbers they are already ignored due to the regex not matching --- .../plugins/apps/remoteaccess/teamviewer.py | 2 +- .../apps/remoteaccess/test_teamviewer.py | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/dissect/target/plugins/apps/remoteaccess/teamviewer.py b/dissect/target/plugins/apps/remoteaccess/teamviewer.py index 9aa8d0bfe7..c3e219bdf4 100644 --- a/dissect/target/plugins/apps/remoteaccess/teamviewer.py +++ b/dissect/target/plugins/apps/remoteaccess/teamviewer.py @@ -197,7 +197,7 @@ def logs(self) -> Iterator[RemoteAccessLogRecord]: except Exception as e: self.target.log.warning("Unable to parse timestamp %r in file %s", line, logfile) self.target.log.debug("", exc_info=e) - timestamp = start_date + timestamp = prev_timestamp or start_date if timestamp and prev_timestamp and prev_timestamp > timestamp: # We might currently be in a grey area where the dst period ended. diff --git a/tests/plugins/apps/remoteaccess/test_teamviewer.py b/tests/plugins/apps/remoteaccess/test_teamviewer.py index 4dade5394a..9e64f90e31 100644 --- a/tests/plugins/apps/remoteaccess/test_teamviewer.py +++ b/tests/plugins/apps/remoteaccess/test_teamviewer.py @@ -165,6 +165,26 @@ def test_teamviewer_daylight_savings_time(target_win_tzinfo: Target, fs_win: Vir assert records[3].ts.astimezone(timezone.utc) == datetime(2025, 10, 27, 0, 2, 3, 500000, tzinfo=timezone.utc) +def test_teamviewer_invalid_datetimes(target_win: Target, fs_win: VirtualFilesystem) -> None: + log = """ + Start: 2025/10/26 02:50:32.134 (UTC) + 2025/10/26 02:50:90.300 1234 5678 G1 Should use start timestamp + 2025/10/26 02:50:34.300 1234 5678 G1 Normal timestamp + 2025/10/26 02:50:90.300 1234 5678 G1 Should use previous timestamp + Start: 2025/10/26 02:x:32.134 (UTC) + 2025/10/26 02:50:90.300 1234 5678 G1 Should use previous timestamp + """ + fs_win.map_file_fh("Program Files/TeamViewer/Teamviewer_Log.log", BytesIO(dedent(log).encode())) + + records = list(target_win.teamviewer.logs()) + assert len(records) == 4 + + assert records[0].ts == datetime(2025, 10, 26, 2, 50, 32, tzinfo=timezone.utc) + assert records[1].ts == datetime(2025, 10, 26, 2, 50, 34, 300000, tzinfo=timezone.utc) + assert records[2].ts == datetime(2025, 10, 26, 2, 50, 34, 300000, tzinfo=timezone.utc) + assert records[3].ts == datetime(2025, 10, 26, 2, 50, 34, 300000, tzinfo=timezone.utc) + + @pytest.mark.parametrize( argnames=("line", "expected_date"), argvalues=[ From 5a29da7f32660cc39dab9e39675f292801caa84d Mon Sep 17 00:00:00 2001 From: Miauwkeru Date: Thu, 19 Mar 2026 12:33:48 +0000 Subject: [PATCH 11/11] Remove redundant len checks in check_compatible --- dissect/target/plugins/apps/remoteaccess/teamviewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/plugins/apps/remoteaccess/teamviewer.py b/dissect/target/plugins/apps/remoteaccess/teamviewer.py index 3f49aaa97b..07d5da43cd 100644 --- a/dissect/target/plugins/apps/remoteaccess/teamviewer.py +++ b/dissect/target/plugins/apps/remoteaccess/teamviewer.py @@ -116,7 +116,7 @@ def __init__(self, target: Target): self.logfiles.add((logfile, user_details)) def check_compatible(self) -> None: - if not len(self.logfiles) and not len(self.incoming_logfiles): + if not self.logfiles and not self.incoming_logfiles: raise UnsupportedPluginError("No Teamviewer logs found on target") @export(record=RemoteAccessLogRecord)