-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathwebkit_resource_load_statistics.py
137 lines (118 loc) · 4.75 KB
/
webkit_resource_load_statistics.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# 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 logging
import os
import sqlite3
from typing import Optional, Union
from mvt.common.utils import convert_unix_to_iso
from ..base import IOSExtraction
WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH = (
"Library/WebKit/WebsiteData/ResourceLoadStatistics/observations.db" # pylint: disable=line-too-long
)
WEBKIT_RESOURCELOADSTATICS_ROOT_PATHS = [
"private/var/mobile/Containers/Data/Application/*/Library/WebKit/WebsiteData/ResourceLoadStatistics/observations.db", # pylint: disable=line-too-long
"private/var/mobile/Containers/Data/Application/*/SystemData/com.apple.SafariViewService/Library/WebKit/WebsiteData/observations.db", # pylint: disable=line-too-long
]
class WebkitResourceLoadStatistics(IOSExtraction):
"""This module extracts records from WebKit ResourceLoadStatistics
observations.db."""
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,
)
self.results = [] if not results else results
def serialize(self, record: dict) -> Union[dict, list]:
msg = f"Webkit resource loaded from {record['registrable_domain']}"
if record["domain"] != "":
msg += f" by app in domain {record['domain']}"
return {
"timestamp": record["last_seen_isodate"],
"module": self.__class__.__name__,
"event": "visit",
"data": msg,
}
def check_indicators(self) -> None:
if not self.indicators:
return
self.detected = []
for result in self.results:
ioc = self.indicators.check_url(result["registrable_domain"])
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
def _process_observations_db(self, db_path: str, domain: str, path: str) -> None:
self.log.info(
"Found WebKit ResourceLoadStatistics observations.db file at path %s",
db_path,
)
self._recover_sqlite_db_if_needed(db_path)
conn = self._open_sqlite_db(db_path)
cur = conn.cursor()
try:
# FIXME: table contains extra fields with timestamp here
cur.execute(
"""
SELECT
domainID,
registrableDomain,
lastSeen,
hadUserInteraction
from ObservedDomains;
"""
)
except sqlite3.OperationalError:
return
for row in cur:
self.results.append(
{
"domain_id": row[0],
"registrable_domain": row[1],
"last_seen": row[2],
"had_user_interaction": bool(row[3]),
"last_seen_isodate": convert_unix_to_iso(row[2]),
"domain": domain,
"path": path,
}
)
if len(self.results) > 0:
self.log.info(
"Extracted a total of %d records from %s", len(self.results), db_path
)
def run(self) -> None:
if self.is_backup:
try:
for backup_file in self._get_backup_files_from_manifest(
relative_path=WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH
):
db_path = self._get_backup_file_from_id(backup_file["file_id"])
if db_path:
self._process_observations_db(
db_path=db_path,
domain=backup_file["domain"],
path=WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH,
)
except Exception as exc:
self.log.info("Unable to find WebKit observations.db: %s", exc)
elif self.is_fs_dump:
for db_path in self._get_fs_files_from_patterns(
WEBKIT_RESOURCELOADSTATICS_ROOT_PATHS
):
db_rel_path = os.path.relpath(db_path, self.target_path)
self._process_observations_db(
db_path=db_path, domain="", path=db_rel_path
)