dissect.target 3.8.dev31__py3-none-any.whl → 3.8.dev32__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,11 +20,26 @@ GENERIC_HISTORY_RECORD_FIELDS = [
20
20
  ("uri", "from_url"),
21
21
  ("path", "source"),
22
22
  ]
23
+ GENERIC_DOWNLOAD_RECORD_FIELDS = [
24
+ ("datetime", "ts_start"),
25
+ ("datetime", "ts_end"),
26
+ ("string", "browser"),
27
+ ("varint", "id"),
28
+ ("path", "path"),
29
+ ("uri", "url"),
30
+ ("filesize", "size"),
31
+ ("varint", "state"),
32
+ ("path", "source"),
33
+ ]
23
34
 
24
35
  BrowserHistoryRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
25
36
  "browser/history", GENERIC_HISTORY_RECORD_FIELDS
26
37
  )
27
38
 
39
+ BrowserDownloadRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
40
+ "browser/download", GENERIC_DOWNLOAD_RECORD_FIELDS
41
+ )
42
+
28
43
 
29
44
  class BrowserPlugin(Plugin):
30
45
  """General browser plugin.
@@ -51,28 +66,42 @@ class BrowserPlugin(Plugin):
51
66
  for entry in self.BROWSERS:
52
67
  try:
53
68
  self._plugins.append(getattr(self.target, entry))
54
- except Exception: # noqa
69
+ except Exception:
55
70
  target.log.exception("Failed to load browser plugin: %s", entry)
56
71
 
57
- def check_compatible(self):
72
+ def check_compatible(self) -> bool:
73
+ """Perform a compatibility check with the target.
74
+ This function checks if any of the supported browser plugins
75
+ can be used. Otherwise it should raise an ``UnsupportedPluginError``.
76
+ Raises:
77
+ UnsupportedPluginError: If the plugin could not be loaded.
78
+ """
58
79
  if not len(self._plugins):
59
80
  raise UnsupportedPluginError("No compatible browser plugins found")
60
81
 
61
- def _func(self, f):
62
- for p in self._plugins:
82
+ def _func(self, func_name: str):
83
+ """Return the supported browser plugin records.
84
+
85
+ Args:
86
+ func_name: Exported function of the browser plugin to find.
87
+
88
+ Yields:
89
+ Record from the browser function.
90
+ """
91
+ for plugin_name in self._plugins:
63
92
  try:
64
- for entry in getattr(p, f)():
93
+ for entry in getattr(plugin_name, func_name)():
65
94
  yield entry
66
95
  except Exception:
67
- self.target.log.exception("Failed to execute browser plugin: %s.%s", p._name, f)
96
+ self.target.log.exception("Failed to execute browser plugin: %s.%s", plugin_name._name, func_name)
68
97
 
69
98
  @export(record=BrowserHistoryRecord)
70
99
  def history(self):
71
- """Return browser history records from all browsers installed.
100
+ """Return browser history records from all browsers installed and supported.
72
101
 
73
102
  Historical browser records for Chrome, Chromium, Edge (Chromium), Firefox, and Internet Explorer are returned.
74
103
 
75
- Yields BrowserHistoryRecords with the following fields:
104
+ Yields BrowserHistoryRecord with the following fields:
76
105
  hostname (string): The target hostname.
77
106
  domain (string): The target domain.
78
107
  ts (datetime): Visit timestamp.
@@ -91,12 +120,38 @@ class BrowserPlugin(Plugin):
91
120
  from_url (uri): URL of the "from" visit.
92
121
  source: (path): The source file of the history record.
93
122
  """
94
- for e in self._func("history"):
95
- yield e
123
+ yield from self._func("history")
96
124
 
125
+ @export(record=BrowserDownloadRecord)
126
+ def downloads(self):
127
+ """Return browser download records from all browsers installed and supported.
97
128
 
98
- def try_idna(s):
129
+ Yields:
130
+ BrowserDownloadRecord with the following fieds:
131
+ hostname (string): The target hostname.
132
+ domain (string): The target domain.
133
+ ts_start (datetime): Download start timestamp.
134
+ ts_end (datetime): Download end timestamp.
135
+ browser (string): The browser from which the records are generated from.
136
+ id (string): Record ID.
137
+ path (string): Download path.
138
+ url (uri): Download URL.
139
+ size (varint): Download file size.
140
+ state (varint): Download state number.
141
+ source: (path): The source file of the download record.
142
+ """
143
+ yield from self._func("downloads")
144
+
145
+
146
+ def try_idna(url: str) -> bytes:
147
+ """Attempts to convert a possible Unicode url to ASCII using the IDNA standard.
148
+
149
+ Args:
150
+ url: A String containing the url to be converted.
151
+
152
+ Returns: Bytes object with the ASCII version of the url.
153
+ """
99
154
  try:
100
- return s.encode("idna")
101
- except Exception: # noqa
102
- return s
155
+ return url.encode("idna")
156
+ except Exception:
157
+ return url
@@ -1,7 +1,10 @@
1
1
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
2
2
  from dissect.target.helpers.record import create_extended_descriptor
3
3
  from dissect.target.plugin import Plugin, export
4
- from dissect.target.plugins.browsers.browser import GENERIC_HISTORY_RECORD_FIELDS
4
+ from dissect.target.plugins.browsers.browser import (
5
+ GENERIC_DOWNLOAD_RECORD_FIELDS,
6
+ GENERIC_HISTORY_RECORD_FIELDS,
7
+ )
5
8
  from dissect.target.plugins.browsers.chromium import ChromiumMixin
6
9
 
7
10
 
@@ -20,11 +23,19 @@ class ChromePlugin(ChromiumMixin, Plugin):
20
23
  # Macos
21
24
  "Library/Application Support/Google/Chrome/Default",
22
25
  ]
23
- HISTORY_RECORD = create_extended_descriptor([UserRecordDescriptorExtension])(
26
+ BrowserHistoryRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
24
27
  "browser/chrome/history", GENERIC_HISTORY_RECORD_FIELDS
25
28
  )
29
+ BrowserDownloadRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
30
+ "browser/chrome/download", GENERIC_DOWNLOAD_RECORD_FIELDS
31
+ )
26
32
 
27
- @export(record=HISTORY_RECORD)
33
+ @export(record=BrowserHistoryRecord)
28
34
  def history(self):
29
35
  """Return browser history records for Google Chrome."""
30
- yield from ChromiumMixin.history(self, "chrome")
36
+ yield from super().history("chrome")
37
+
38
+ @export(record=BrowserDownloadRecord)
39
+ def downloads(self):
40
+ """Return browser download records for Google Chrome."""
41
+ yield from super().downloads("chrome")
@@ -1,10 +1,11 @@
1
+ from collections import defaultdict
1
2
  from typing import Iterator
2
3
 
3
4
  from dissect.sql import sqlite3
4
5
  from dissect.sql.exceptions import Error as SQLError
5
6
  from dissect.sql.sqlite3 import SQLite3
6
7
  from dissect.util.ts import webkittimestamp
7
- from flow.record import Record
8
+ from flow.record.fieldtypes import path
8
9
 
9
10
  from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
10
11
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
@@ -12,6 +13,7 @@ from dissect.target.helpers.fsutil import TargetPath
12
13
  from dissect.target.helpers.record import create_extended_descriptor
13
14
  from dissect.target.plugin import Plugin, export
14
15
  from dissect.target.plugins.browsers.browser import (
16
+ GENERIC_DOWNLOAD_RECORD_FIELDS,
15
17
  GENERIC_HISTORY_RECORD_FIELDS,
16
18
  try_idna,
17
19
  )
@@ -21,11 +23,14 @@ class ChromiumMixin:
21
23
  """Mixin class with methods for Chromium-based browsers."""
22
24
 
23
25
  DIRS = []
24
- HISTORY_RECORD = create_extended_descriptor([UserRecordDescriptorExtension])(
26
+ BrowserHistoryRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
25
27
  "browser/chromium/history", GENERIC_HISTORY_RECORD_FIELDS
26
28
  )
29
+ BrowserDownloadRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
30
+ "browser/chromium/download", GENERIC_DOWNLOAD_RECORD_FIELDS
31
+ )
27
32
 
28
- def history(self, browser_name: str = None) -> Iterator[Record]:
33
+ def history(self, browser_name: str = None) -> Iterator[BrowserHistoryRecord]:
29
34
  """Return browser history records from supported Chromium-based browsers.
30
35
 
31
36
  Args:
@@ -55,7 +60,7 @@ class ChromiumMixin:
55
60
  for user, db_file, db in self._iter_db("History"):
56
61
  try:
57
62
  urls = {row.id: row for row in db.table("urls").rows()}
58
- visits: dict = {}
63
+ visits = {}
59
64
 
60
65
  for row in db.table("visits").rows():
61
66
  visits[row.id] = row
@@ -67,7 +72,7 @@ class ChromiumMixin:
67
72
  else:
68
73
  from_visit, from_url = None, None
69
74
 
70
- yield self.HISTORY_RECORD(
75
+ yield self.BrowserHistoryRecord(
71
76
  ts=webkittimestamp(row.visit_time),
72
77
  browser=browser_name,
73
78
  id=row.id,
@@ -89,6 +94,66 @@ class ChromiumMixin:
89
94
  except SQLError as e:
90
95
  self.target.log.warning("Error processing history file: %s", db_file, exc_info=e)
91
96
 
97
+ def downloads(self, browser_name: str = None) -> Iterator[BrowserDownloadRecord]:
98
+ """Return browser download records from supported Chromium-based browsers.
99
+
100
+ Args:
101
+ browser_name: The name of the browser as a string.
102
+ Yields:
103
+ Records with the following fields:
104
+ hostname (string): The target hostname.
105
+ domain (string): The target domain.
106
+ ts_start (datetime): Download start timestamp.
107
+ ts_ed (datetime): Download end timestamp.
108
+ browser (string): The browser from which the records are generated from.
109
+ id (string): Record ID.
110
+ path (string): Download path.
111
+ url (uri): Download URL.
112
+ size (varint): Download file size.
113
+ state (varint): Download state number.
114
+ source: (path): The source file of the download record.
115
+ Raises:
116
+ SQLError: If the history file could not be processed.
117
+ """
118
+ for user, db_file, db in self._iter_db("History"):
119
+ try:
120
+ download_chains = defaultdict(list)
121
+ for row in db.table("downloads_url_chains"):
122
+ download_chains[row.id].append(row)
123
+
124
+ for chain in download_chains.values():
125
+ chain.sort(key=lambda row: row.chain_index)
126
+
127
+ for row in db.table("downloads").rows():
128
+ download_path = row.target_path
129
+ if download_path and self.target.os == "windows":
130
+ download_path = path.from_windows(download_path)
131
+ elif download_path:
132
+ download_path = path.from_posix()(download_path)
133
+
134
+ url = None
135
+ download_chain = download_chains.get(row.id)
136
+
137
+ if download_chain:
138
+ url = download_chain[-1].url
139
+ url = try_idna(url)
140
+
141
+ yield self.BrowserDownloadRecord(
142
+ ts_start=webkittimestamp(row.start_time),
143
+ ts_end=webkittimestamp(row.end_time) if row.end_time else None,
144
+ browser=browser_name,
145
+ id=row.get("id"),
146
+ path=download_path,
147
+ url=url,
148
+ size=row.get("total_bytes"),
149
+ state=row.get("state"),
150
+ source=str(db_file),
151
+ _target=self.target,
152
+ _user=user,
153
+ )
154
+ except SQLError as e:
155
+ self.target.log.warning("Error processing history file: %s", db_file, exc_info=e)
156
+
92
157
  def _iter_db(self, filename: str) -> Iterator[SQLite3]:
93
158
  """Generate a connection to a sqlite history database files.
94
159
 
@@ -151,7 +216,12 @@ class ChromiumPlugin(ChromiumMixin, Plugin):
151
216
  "AppData/Local/Chromium/User Data/Default",
152
217
  ]
153
218
 
154
- @export(record=ChromiumMixin.HISTORY_RECORD)
219
+ @export(record=ChromiumMixin.BrowserHistoryRecord)
155
220
  def history(self):
156
221
  """Return browser history records for Chromium browser."""
157
- yield from ChromiumMixin.history(self, "chromium")
222
+ yield from super().history("chromium")
223
+
224
+ @export(record=ChromiumMixin.BrowserDownloadRecord)
225
+ def downloads(self):
226
+ """Return browser download records for Chromium browser."""
227
+ yield from super().downloads("chromium")
@@ -1,7 +1,10 @@
1
1
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
2
2
  from dissect.target.helpers.record import create_extended_descriptor
3
3
  from dissect.target.plugin import Plugin, export
4
- from dissect.target.plugins.browsers.browser import GENERIC_HISTORY_RECORD_FIELDS
4
+ from dissect.target.plugins.browsers.browser import (
5
+ GENERIC_DOWNLOAD_RECORD_FIELDS,
6
+ GENERIC_HISTORY_RECORD_FIELDS,
7
+ )
5
8
  from dissect.target.plugins.browsers.chromium import ChromiumMixin
6
9
 
7
10
 
@@ -16,11 +19,19 @@ class EdgePlugin(ChromiumMixin, Plugin):
16
19
  # Macos
17
20
  "Library/Application Support/Microsoft Edge/Default",
18
21
  ]
19
- HISTORY_RECORD = create_extended_descriptor([UserRecordDescriptorExtension])(
22
+ BrowserHistoryRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
20
23
  "browser/edge/history", GENERIC_HISTORY_RECORD_FIELDS
21
24
  )
25
+ BrowserDownloadRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
26
+ "browser/edge/download", GENERIC_DOWNLOAD_RECORD_FIELDS
27
+ )
22
28
 
23
- @export(record=HISTORY_RECORD)
29
+ @export(record=BrowserHistoryRecord)
24
30
  def history(self):
25
31
  """Return browser history records for Microsoft Edge."""
26
- yield from ChromiumMixin.history(self, "edge")
32
+ yield from super().history("edge")
33
+
34
+ @export(record=BrowserDownloadRecord)
35
+ def downloads(self):
36
+ """Return browser download records for Microsoft Edge."""
37
+ yield from super().downloads("edge")
@@ -1,20 +1,22 @@
1
+ import json
2
+ from typing import Iterator
3
+
1
4
  from dissect.sql import sqlite3
2
5
  from dissect.sql.exceptions import Error as SQLError
3
- from dissect.util.ts import from_unix_us
6
+ from dissect.sql.sqlite3 import SQLite3
7
+ from dissect.util.ts import from_unix_ms, from_unix_us
8
+ from flow.record.fieldtypes import path
4
9
 
5
10
  from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
6
11
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
7
12
  from dissect.target.helpers.record import create_extended_descriptor
8
13
  from dissect.target.plugin import Plugin, export
9
14
  from dissect.target.plugins.browsers.browser import (
15
+ GENERIC_DOWNLOAD_RECORD_FIELDS,
10
16
  GENERIC_HISTORY_RECORD_FIELDS,
11
17
  try_idna,
12
18
  )
13
19
 
14
- FirefoxBrowserHistoryRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
15
- "browser/firefox/history", GENERIC_HISTORY_RECORD_FIELDS
16
- )
17
-
18
20
 
19
21
  class FirefoxPlugin(Plugin):
20
22
  """Firefox browser plugin."""
@@ -27,6 +29,12 @@ class FirefoxPlugin(Plugin):
27
29
  ".mozilla/firefox",
28
30
  "snap/firefox/common/.mozilla/firefox",
29
31
  ]
32
+ BrowserHistoryRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
33
+ "browser/firefox/history", GENERIC_HISTORY_RECORD_FIELDS
34
+ )
35
+ BrowserDownloadRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
36
+ "browser/firefox/download", GENERIC_DOWNLOAD_RECORD_FIELDS
37
+ )
30
38
 
31
39
  def __init__(self, target):
32
40
  super().__init__(target)
@@ -43,7 +51,14 @@ class FirefoxPlugin(Plugin):
43
51
  if not len(self.users_dirs):
44
52
  raise UnsupportedPluginError("No Firefox directories found")
45
53
 
46
- def _iter_db(self, filename):
54
+ def _iter_db(self, filename: str) -> Iterator[SQLite3]:
55
+ """Yield opened history database files of all users.
56
+
57
+ Args:
58
+ filename: The filename of the database.
59
+ Yields:
60
+ Opened SQLite3 databases.
61
+ """
47
62
  for user, cur_dir in self.users_dirs:
48
63
  for profile_dir in cur_dir.iterdir():
49
64
  if profile_dir.is_dir():
@@ -55,11 +70,11 @@ class FirefoxPlugin(Plugin):
55
70
  except SQLError as e:
56
71
  self.target.log.warning("Could not open %s file: %s", filename, db_file, exc_info=e)
57
72
 
58
- @export(record=FirefoxBrowserHistoryRecord)
59
- def history(self):
73
+ @export(record=BrowserHistoryRecord)
74
+ def history(self) -> Iterator[BrowserHistoryRecord]:
60
75
  """Return browser history records from Firefox.
61
76
 
62
- Yields FirefoxBrowserHistoryRecord with the following fields:
77
+ Yields BrowserHistoryRecord with the following fields:
63
78
  hostname (string): The target hostname.
64
79
  domain (string): The target domain.
65
80
  ts (datetime): Visit timestamp.
@@ -93,7 +108,7 @@ class FirefoxPlugin(Plugin):
93
108
  else:
94
109
  from_visit, from_place = None, None
95
110
 
96
- yield FirefoxBrowserHistoryRecord(
111
+ yield self.BrowserHistoryRecord(
97
112
  ts=from_unix_us(row.visit_date),
98
113
  browser="firefox",
99
114
  id=row.id,
@@ -114,3 +129,91 @@ class FirefoxPlugin(Plugin):
114
129
  )
115
130
  except SQLError as e:
116
131
  self.target.log.warning("Error processing history file: %s", db_file, exc_info=e)
132
+
133
+ @export(record=BrowserDownloadRecord)
134
+ def downloads(self) -> Iterator[BrowserDownloadRecord]:
135
+ """Return browser download records from Firefox.
136
+
137
+ Yields BrowserDownloadRecord with the following fields:
138
+ hostname (string): The target hostname.
139
+ domain (string): The target domain.
140
+ ts_start (datetime): Download start timestamp.
141
+ ts_end (datetime): Download end timestamp.
142
+ browser (string): The browser from which the records are generated from.
143
+ id (string): Record ID.
144
+ path (string): Download path.
145
+ url (uri): Download URL.
146
+ size (varint): Download file size.
147
+ state (varint): Download state number.
148
+ source: (path): The source file of the download record.
149
+ """
150
+ for user, db_file, db in self._iter_db("places.sqlite"):
151
+ try:
152
+ places = {row.id: row for row in db.table("moz_places").rows()}
153
+ attributes = {row.id: row.name for row in db.table("moz_anno_attributes").rows()}
154
+ annotations = {}
155
+
156
+ for row in db.table("moz_annos"):
157
+ attribute_name = attributes.get(row.anno_attribute_id, row.anno_attribute_id)
158
+
159
+ if attribute_name == "downloads/metaData":
160
+ content = json.loads(row.content)
161
+ else:
162
+ content = row.content
163
+
164
+ if row.place_id not in annotations:
165
+ annotations[row.place_id] = {"id": row.id}
166
+
167
+ annotations[row.place_id][attribute_name] = {
168
+ "content": content,
169
+ "flags": row.flags,
170
+ "expiration": row.expiration,
171
+ "type": row.type,
172
+ "date_added": from_unix_us(row.dateAdded),
173
+ "last_modified": from_unix_us(row.lastModified),
174
+ }
175
+
176
+ for place_id, annotation in annotations.items():
177
+ if "downloads/metaData" not in annotation:
178
+ continue
179
+
180
+ metadata = annotation.get("downloads/metaData", {})
181
+
182
+ ts_end = None
183
+ size = None
184
+ state = None
185
+
186
+ content = metadata.get("content")
187
+ if content:
188
+ ts_end = metadata.get("content").get("endTime")
189
+ ts_end = from_unix_ms(ts_end) if ts_end else None
190
+
191
+ size = content.get("fileSize")
192
+ state = content.get("state")
193
+
194
+ dest_file_info = annotation.get("downloads/destinationFileURI", {})
195
+ download_path = dest_file_info.get("content")
196
+
197
+ if download_path and self.target.os == "windows":
198
+ download_path = path.from_windows(download_path)
199
+ elif download_path:
200
+ download_path = path.from_posix(download_path)
201
+
202
+ place = places.get(place_id)
203
+ url = place.get("url")
204
+ url = try_idna(url) if url else None
205
+
206
+ yield self.BrowserDownloadRecord(
207
+ ts_start=dest_file_info.get("date_added"),
208
+ ts_end=ts_end,
209
+ browser="firefox",
210
+ id=annotation.get("id"),
211
+ path=download_path,
212
+ url=url,
213
+ size=size,
214
+ state=state,
215
+ source=str(db_file),
216
+ _target=self.target,
217
+ )
218
+ except SQLError as e:
219
+ self.target.log.warning("Error processing history file: %s", db_file, exc_info=e)
@@ -9,23 +9,30 @@ from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExt
9
9
  from dissect.target.helpers.record import create_extended_descriptor
10
10
  from dissect.target.plugin import Plugin, export
11
11
  from dissect.target.plugins.browsers.browser import (
12
+ GENERIC_DOWNLOAD_RECORD_FIELDS,
12
13
  GENERIC_HISTORY_RECORD_FIELDS,
13
14
  try_idna,
14
15
  )
15
16
  from dissect.target.plugins.general.users import UserDetails
16
17
  from dissect.target.target import Target
17
18
 
18
- IEBrowserHistoryRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
19
- "browser/ie/history", GENERIC_HISTORY_RECORD_FIELDS
20
- )
21
-
22
19
 
23
20
  class WebCache:
21
+ """Class for opening and pre-processing IE WebCache file."""
22
+
24
23
  def __init__(self, target: Target, fh: BinaryIO):
25
24
  self.target = target
26
25
  self.db = esedb.EseDB(fh)
27
26
 
28
27
  def find_containers(self, name: str) -> table.Table:
28
+ """Look up all ``ContainerId`` values for a given container name.
29
+
30
+ Args:
31
+ name: The container name to look up all container IDs of.
32
+
33
+ Yields:
34
+ All ``ContainerId`` values for the requested container name.
35
+ """
29
36
  try:
30
37
  for container_record in self.db.table("Containers").records():
31
38
  if record_name := container_record.get("Name"):
@@ -37,6 +44,14 @@ class WebCache:
37
44
  pass
38
45
 
39
46
  def _iter_records(self, name: str) -> Iterator[record.Record]:
47
+ """Yield records from a Webcache container.
48
+
49
+ Args:
50
+ name: The container name.
51
+
52
+ Yields:
53
+ Records from specified Webcache container.
54
+ """
40
55
  for container in self.find_containers(name):
41
56
  try:
42
57
  yield from container.records()
@@ -45,8 +60,13 @@ class WebCache:
45
60
  continue
46
61
 
47
62
  def history(self) -> Iterator[record.Record]:
63
+ """Yield records from the history webcache container."""
48
64
  yield from self._iter_records("history")
49
65
 
66
+ def downloads(self) -> Iterator[record.Record]:
67
+ """Yield records from the iedownload webcache container."""
68
+ yield from self._iter_records("iedownload")
69
+
50
70
 
51
71
  class InternetExplorerPlugin(Plugin):
52
72
  """Internet explorer browser plugin."""
@@ -56,8 +76,13 @@ class InternetExplorerPlugin(Plugin):
56
76
  DIRS = [
57
77
  "AppData/Local/Microsoft/Windows/WebCache",
58
78
  ]
59
-
60
79
  CACHE_FILENAME = "WebCacheV01.dat"
80
+ BrowserDownloadRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
81
+ "browser/ie/download", GENERIC_DOWNLOAD_RECORD_FIELDS
82
+ )
83
+ BrowserHistoryRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
84
+ "browser/ie/history", GENERIC_HISTORY_RECORD_FIELDS
85
+ )
61
86
 
62
87
  def __init__(self, target: Target):
63
88
  super().__init__(target)
@@ -74,11 +99,30 @@ class InternetExplorerPlugin(Plugin):
74
99
  if not len(self.users_dirs):
75
100
  raise UnsupportedPluginError("No Internet Explorer directories found")
76
101
 
77
- @export(record=IEBrowserHistoryRecord)
78
- def history(self) -> Iterator[IEBrowserHistoryRecord]:
102
+ def _iter_cache(self) -> Iterator[WebCache]:
103
+ """Yield open IE Webcache files.
104
+
105
+ Args:
106
+ filename: Name of the Webcache file.
107
+
108
+ Yields:
109
+ Open Webcache file.
110
+
111
+ Raises:
112
+ FileNoteFoundError: If the webcache file could not be found.
113
+ """
114
+ for user, cdir in self.users_dirs:
115
+ cache_file = cdir.joinpath(self.CACHE_FILENAME)
116
+ try:
117
+ yield user, cache_file, WebCache(self.target, cache_file.open())
118
+ except FileNotFoundError:
119
+ self.target.log.warning("Could not find %s file: %s", self.CACHE_FILENAME, cache_file)
120
+
121
+ @export(record=BrowserHistoryRecord)
122
+ def history(self) -> Iterator[BrowserHistoryRecord]:
79
123
  """Return browser history records from Internet Explorer.
80
124
 
81
- Yields IEBrowserHistoryRecord with the following fields:
125
+ Yields BrowserHistoryRecord with the following fields:
82
126
  hostname (string): The target hostname.
83
127
  domain (string): The target domain.
84
128
  ts (datetime): Visit timestamp.
@@ -97,12 +141,7 @@ class InternetExplorerPlugin(Plugin):
97
141
  from_url (uri): URL of the "from" visit.
98
142
  source: (path): The source file of the history record.
99
143
  """
100
- for user, cdir in self.users_dirs:
101
- cache_file = cdir.joinpath(self.CACHE_FILENAME)
102
- if not cache_file.exists():
103
- continue
104
-
105
- cache = WebCache(self.target, cache_file.open())
144
+ for user, cache_file, cache in self._iter_cache():
106
145
  for container_record in cache.history():
107
146
  if not container_record.get("Url"):
108
147
  continue
@@ -113,7 +152,7 @@ class InternetExplorerPlugin(Plugin):
113
152
  if accessed_time := container_record.get("AccessedTime"):
114
153
  ts = wintimestamp(accessed_time)
115
154
 
116
- yield IEBrowserHistoryRecord(
155
+ yield self.BrowserHistoryRecord(
117
156
  ts=ts,
118
157
  browser="iexplore",
119
158
  id=container_record.get("EntryId"),
@@ -132,3 +171,39 @@ class InternetExplorerPlugin(Plugin):
132
171
  _target=self.target,
133
172
  _user=user,
134
173
  )
174
+
175
+ @export(record=BrowserDownloadRecord)
176
+ def downloads(self) -> Iterator[BrowserDownloadRecord]:
177
+ """Return browser downloads records from Internet Explorer.
178
+
179
+ Yields BrowserDownloadRecord with the following fields:
180
+ hostname (string): The target hostname.
181
+ domain (string): The target domain.
182
+ ts_start (datetime): Download start timestamp.
183
+ ts_end (datetime): Download end timestamp.
184
+ browser (string): The browser from which the records are generated from.
185
+ id (string): Record ID.
186
+ path (string): Download path.
187
+ url (uri): Download URL.
188
+ size (varint): Download file size.
189
+ state (varint): Download state number.
190
+ source: (path): The source file of the download record.
191
+ """
192
+ for user, cache_file, cache in self._iter_cache():
193
+ for container_record in cache.downloads():
194
+ response_headers = container_record.ResponseHeaders.decode("utf-16-le", errors="ignore")
195
+ ref_url, mime_type, temp_download_path, down_url, down_path = response_headers.split("\x00")[-6:-1]
196
+
197
+ yield self.BrowserDownloadRecord(
198
+ ts_start=None,
199
+ ts_end=wintimestamp(container_record.AccessedTime),
200
+ browser="iexplore",
201
+ id=container_record.EntryId,
202
+ path=down_path,
203
+ url=down_url,
204
+ size=None,
205
+ state=None,
206
+ source=str(cache_file),
207
+ _target=self.target,
208
+ _user=user,
209
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.8.dev31
3
+ Version: 3.8.dev32
4
4
  Summary: This module ties all other Dissect modules together, it provides a programming API and command line tools which allow easy access to various data sources inside disk images or file collections (a.k.a. targets)
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -93,12 +93,12 @@ dissect/target/plugins/apps/webservers/iis.py,sha256=RIS_1w9hcQSfF3-83MsRsT8EJeY
93
93
  dissect/target/plugins/apps/webservers/nginx.py,sha256=P6lkAJwf4U8pyaIHt37DKyGYhJ2a-Mb7I-msZNRuuG8,4006
94
94
  dissect/target/plugins/apps/webservers/webservers.py,sha256=00a0GWWK_9CLuZnXJEyHitHxHaGK-HUrmHOK6jpeiXg,2156
95
95
  dissect/target/plugins/browsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
- dissect/target/plugins/browsers/browser.py,sha256=hCDAjiezYaulNRE9Hmy1B8VgB0zvWAevLkUXdqzBt24,3411
97
- dissect/target/plugins/browsers/chrome.py,sha256=Yq2bV6RNFAp-xkq6ymwFSX5HMr8nyl51WSAa7nalQJQ,1173
98
- dissect/target/plugins/browsers/chromium.py,sha256=P3Z_pW-3sUe6FtRco-gPK9KnpNiuNKakt_X-WIIR3VY,6365
99
- dissect/target/plugins/browsers/edge.py,sha256=znep-McomUvtAFVn6un5RJm7D_iloixAbz0AMuxJksU,959
100
- dissect/target/plugins/browsers/firefox.py,sha256=Qm9LgYlQAIiFGk5c95S5abZG-JXr38NkDZRNGvjzVpQ,4899
101
- dissect/target/plugins/browsers/iexplore.py,sha256=Qj7sVz14hvodlmTxK7165WMhtsVps0xCWMbyZl8Jruk,5144
96
+ dissect/target/plugins/browsers/browser.py,sha256=Bm-MuRkEi-hyaQnB02t2bz3EghRoTPOtkW5jtrw6LWU,5491
97
+ dissect/target/plugins/browsers/chrome.py,sha256=0qy_IvQ6hFxslClpfmlyTH3VOokB6VP8fnukXrbNDU4,1559
98
+ dissect/target/plugins/browsers/chromium.py,sha256=3lVEbJl1jmd3kjioWzrYkfHUNzdpzX9KZ7nr1BNJLrg,9570
99
+ dissect/target/plugins/browsers/edge.py,sha256=uVJqHN5CD0oLuJ8D4PIfIao4iui4HEekbs5el8CDwx8,1342
100
+ dissect/target/plugins/browsers/firefox.py,sha256=BPC8M4OWkw9-bWz1LYEdVS78XfQQugi1OoA8WiL6r84,9347
101
+ dissect/target/plugins/browsers/iexplore.py,sha256=pzboThhidWubxiR5d82wheINT1bMfa-66jxEbDlw66g,8251
102
102
  dissect/target/plugins/child/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
103
103
  dissect/target/plugins/child/esxi.py,sha256=RzfrnFWPYL-y67zU-vRJzzTGYT1MBNWrFh4so0L7dR0,554
104
104
  dissect/target/plugins/child/hyperv.py,sha256=lmhLQD1JSn-EpGKc7RNUAzESmfosz7RwGCm2JHWK-pU,2185
@@ -251,10 +251,10 @@ dissect/target/volumes/bde.py,sha256=gYGg5yF9MNARwNzEkrEfZmKkxyZW4rhLkpdnPJCbhGk
251
251
  dissect/target/volumes/disk.py,sha256=95grSsPt1BLVpKwTclwQYzPFGKTkFFqapIk0RoGWf38,968
252
252
  dissect/target/volumes/lvm.py,sha256=zXAfszxNR6tOGrKAtAa_E-JhjI-sXQyR4VYLXD-kqCw,1616
253
253
  dissect/target/volumes/vmfs.py,sha256=mlAJ8278tYaoRjk1u6tFFlCaDQUrVu5ZZE4ikiFvxi8,1707
254
- dissect.target-3.8.dev31.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
255
- dissect.target-3.8.dev31.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
256
- dissect.target-3.8.dev31.dist-info/METADATA,sha256=IDpi5WC9S-ruUyaqZLa6yEuEHCK69HudHw0fRk7LM0I,9752
257
- dissect.target-3.8.dev31.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
258
- dissect.target-3.8.dev31.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
259
- dissect.target-3.8.dev31.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
260
- dissect.target-3.8.dev31.dist-info/RECORD,,
254
+ dissect.target-3.8.dev32.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
255
+ dissect.target-3.8.dev32.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
256
+ dissect.target-3.8.dev32.dist-info/METADATA,sha256=Xxk_a-9Tb-0_dvcelF-C5KTFviYGegIYrD76c9l0JVA,9752
257
+ dissect.target-3.8.dev32.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
258
+ dissect.target-3.8.dev32.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
259
+ dissect.target-3.8.dev32.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
260
+ dissect.target-3.8.dev32.dist-info/RECORD,,