mirror of
https://github.com/mvt-project/mvt.git
synced 2026-08-30 06:30:36 +02:00
Merge branch 'feature/fs-timestamps' into local-timeline-fixes
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from .artifact import AndroidArtifact
|
||||||
|
|
||||||
|
|
||||||
|
class FileTimestampsArtifact(AndroidArtifact):
|
||||||
|
def serialize(self, record: dict) -> Union[dict, list]:
|
||||||
|
records = []
|
||||||
|
|
||||||
|
for ts in set(
|
||||||
|
[
|
||||||
|
record.get("access_time"),
|
||||||
|
record.get("changed_time"),
|
||||||
|
record.get("modified_time"),
|
||||||
|
]
|
||||||
|
):
|
||||||
|
if not ts:
|
||||||
|
continue
|
||||||
|
|
||||||
|
macb = ""
|
||||||
|
macb += "M" if ts == record.get("modified_time") else "-"
|
||||||
|
macb += "A" if ts == record.get("access_time") else "-"
|
||||||
|
macb += "C" if ts == record.get("changed_time") else "-"
|
||||||
|
macb += "-"
|
||||||
|
|
||||||
|
msg = record["path"]
|
||||||
|
if record.get("context"):
|
||||||
|
msg += f" ({record['context']})"
|
||||||
|
|
||||||
|
records.append(
|
||||||
|
{
|
||||||
|
"timestamp": ts,
|
||||||
|
"module": self.__class__.__name__,
|
||||||
|
"event": macb,
|
||||||
|
"data": msg,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return records
|
||||||
@@ -42,6 +42,17 @@ class GetProp(AndroidArtifact):
|
|||||||
entry = {"name": matches[0][0], "value": matches[0][1]}
|
entry = {"name": matches[0][0], "value": matches[0][1]}
|
||||||
self.results.append(entry)
|
self.results.append(entry)
|
||||||
|
|
||||||
|
def get_device_timezone(self) -> str:
|
||||||
|
"""
|
||||||
|
Get the device timezone from the getprop results
|
||||||
|
|
||||||
|
Used in other moduels to calculate the timezone offset
|
||||||
|
"""
|
||||||
|
for entry in self.results:
|
||||||
|
if entry["name"] == "persist.sys.timezone":
|
||||||
|
return entry["value"]
|
||||||
|
return None
|
||||||
|
|
||||||
def check_indicators(self) -> None:
|
def check_indicators(self) -> None:
|
||||||
for entry in self.results:
|
for entry in self.results:
|
||||||
if entry["name"] in INTERESTING_PROPERTIES:
|
if entry["name"] in INTERESTING_PROPERTIES:
|
||||||
|
|||||||
@@ -48,6 +48,31 @@ class AndroidQFModule(MVTModule):
|
|||||||
def _get_files_by_pattern(self, pattern: str):
|
def _get_files_by_pattern(self, pattern: str):
|
||||||
return fnmatch.filter(self.files, pattern)
|
return fnmatch.filter(self.files, pattern)
|
||||||
|
|
||||||
|
def _get_device_timezone(self):
|
||||||
|
"""
|
||||||
|
Get the device timezone from the getprop.txt file.
|
||||||
|
|
||||||
|
This is needed to map local timestamps stored in some
|
||||||
|
Android log files to UTC/timezone-aware timestamps.
|
||||||
|
"""
|
||||||
|
get_prop_files = self._get_files_by_pattern("*/getprop.txt")
|
||||||
|
prop_data = self._get_file_content(get_prop_files[0]).decode("utf-8")
|
||||||
|
|
||||||
|
from mvt.android.artifacts.getprop import GetProp
|
||||||
|
|
||||||
|
properties_artifact = GetProp()
|
||||||
|
properties_artifact.parse(prop_data)
|
||||||
|
timezone = properties_artifact.get_device_timezone()
|
||||||
|
if timezone:
|
||||||
|
self.log.debug("Identified local phone timezone: %s", timezone)
|
||||||
|
return timezone
|
||||||
|
|
||||||
|
self.log.warning(
|
||||||
|
"Could not find or determine local device timezone. "
|
||||||
|
"Some timestamps and timeline data may be incorrect."
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
def _get_file_content(self, file_path):
|
def _get_file_content(self, file_path):
|
||||||
if self.archive:
|
if self.archive:
|
||||||
handle = self.archive.open(file_path)
|
handle = self.archive.open(file_path)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.base import AndroidQFModule
|
from mvt.android.modules.androidqf.base import AndroidQFModule
|
||||||
@@ -106,6 +107,12 @@ class Files(AndroidQFModule):
|
|||||||
# TODO: adds SHA1 and MD5 when available in MVT
|
# TODO: adds SHA1 and MD5 when available in MVT
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
|
if timezone := self._get_device_timezone():
|
||||||
|
device_timezone = ZoneInfo(timezone)
|
||||||
|
else:
|
||||||
|
self.log.warning("Unable to determine device timezone, using UTC")
|
||||||
|
device_timezone = ZoneInfo("UTC")
|
||||||
|
|
||||||
for file in self._get_files_by_pattern("*/files.json"):
|
for file in self._get_files_by_pattern("*/files.json"):
|
||||||
rawdata = self._get_file_content(file).decode("utf-8", errors="ignore")
|
rawdata = self._get_file_content(file).decode("utf-8", errors="ignore")
|
||||||
try:
|
try:
|
||||||
@@ -120,11 +127,18 @@ class Files(AndroidQFModule):
|
|||||||
for file_data in data:
|
for file_data in data:
|
||||||
for ts in ["access_time", "changed_time", "modified_time"]:
|
for ts in ["access_time", "changed_time", "modified_time"]:
|
||||||
if ts in file_data:
|
if ts in file_data:
|
||||||
file_data[ts] = convert_datetime_to_iso(
|
utc_timestamp = datetime.datetime.fromtimestamp(
|
||||||
datetime.datetime.fromtimestamp(
|
file_data[ts], tz=datetime.timezone.utc
|
||||||
file_data[ts], tz=datetime.timezone.utc
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
# Convert the UTC timestamp to local tiem on Android device's local timezone
|
||||||
|
local_timestamp = utc_timestamp.astimezone(device_timezone)
|
||||||
|
|
||||||
|
# HACK: We only output the UTC timestamp in convert_datetime_to_iso, we
|
||||||
|
# set the timestamp timezone to UTC, to avoid the timezone conversion again.
|
||||||
|
local_timestamp = local_timestamp.replace(
|
||||||
|
tzinfo=datetime.timezone.utc
|
||||||
|
)
|
||||||
|
file_data[ts] = convert_datetime_to_iso(local_timestamp)
|
||||||
|
|
||||||
self.results.append(file_data)
|
self.results.append(file_data)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.common.utils import convert_datetime_to_iso
|
||||||
|
from .base import AndroidQFModule
|
||||||
|
from mvt.android.artifacts.file_timestamps import FileTimestampsArtifact
|
||||||
|
|
||||||
|
|
||||||
|
class LogsFileTimestamps(FileTimestampsArtifact, AndroidQFModule):
|
||||||
|
"""This module extracts records from battery daily updates."""
|
||||||
|
|
||||||
|
slug = "logfile_timestamps"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_file_modification_time(self, file_path: str) -> dict:
|
||||||
|
if self.archive:
|
||||||
|
file_timetuple = self.archive.getinfo(file_path).date_time
|
||||||
|
return datetime.datetime(*file_timetuple)
|
||||||
|
else:
|
||||||
|
file_stat = os.stat(os.path.join(self.parent_path, file_path))
|
||||||
|
return datetime.datetime.fromtimestamp(file_stat.st_mtime)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
filesystem_files = self._get_files_by_pattern("*/logs/*")
|
||||||
|
|
||||||
|
self.results = []
|
||||||
|
for file in filesystem_files:
|
||||||
|
# Only the modification time is available in the zip file metadata.
|
||||||
|
# The timezone is the local timezone of the machine the phone.
|
||||||
|
modification_time = self._get_file_modification_time(file)
|
||||||
|
self.results.append(
|
||||||
|
{
|
||||||
|
"path": file,
|
||||||
|
"modified_time": convert_datetime_to_iso(modification_time),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"Extracted a total of %d filesystem timestamps from AndroidQF logs directory.",
|
||||||
|
len(self.results),
|
||||||
|
)
|
||||||
@@ -13,6 +13,7 @@ from .getprop import Getprop
|
|||||||
from .packages import Packages
|
from .packages import Packages
|
||||||
from .receivers import Receivers
|
from .receivers import Receivers
|
||||||
from .adb_state import DumpsysADBState
|
from .adb_state import DumpsysADBState
|
||||||
|
from .fs_timestamps import BugReportTimestamps
|
||||||
|
|
||||||
BUGREPORT_MODULES = [
|
BUGREPORT_MODULES = [
|
||||||
Accessibility,
|
Accessibility,
|
||||||
@@ -25,4 +26,5 @@ BUGREPORT_MODULES = [
|
|||||||
Packages,
|
Packages,
|
||||||
Receivers,
|
Receivers,
|
||||||
DumpsysADBState,
|
DumpsysADBState,
|
||||||
|
BugReportTimestamps,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import fnmatch
|
import fnmatch
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.common.utils import convert_datetime_to_iso
|
||||||
|
from .base import BugReportModule
|
||||||
|
from mvt.android.artifacts.file_timestamps import FileTimestampsArtifact
|
||||||
|
|
||||||
|
|
||||||
|
class BugReportTimestamps(FileTimestampsArtifact, BugReportModule):
|
||||||
|
"""This module extracts records from battery daily updates."""
|
||||||
|
|
||||||
|
slug = "bugreport_timestamps"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_file_modification_time(self, file_path: str) -> dict:
|
||||||
|
if self.zip_archive:
|
||||||
|
file_timetuple = self.zip_archive.getinfo(file_path).date_time
|
||||||
|
return datetime.datetime(*file_timetuple)
|
||||||
|
else:
|
||||||
|
file_stat = os.stat(os.path.join(self.extract_path, file_path))
|
||||||
|
return datetime.datetime.fromtimestamp(file_stat.st_mtime)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
filesystem_files = self._get_files_by_pattern("FS/*")
|
||||||
|
|
||||||
|
self.results = []
|
||||||
|
for file in filesystem_files:
|
||||||
|
# Only the modification time is available in the zip file metadata.
|
||||||
|
# The timezone is the local timezone of the machine the phone.
|
||||||
|
modification_time = self._get_file_modification_time(file)
|
||||||
|
self.results.append(
|
||||||
|
{
|
||||||
|
"path": file,
|
||||||
|
"modified_time": convert_datetime_to_iso(modification_time),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"Extracted a total of %d filesystem timestamps from bugreport.",
|
||||||
|
len(self.results),
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user