dissect.target 3.18.dev16__py3-none-any.whl → 3.19__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. dissect/target/filesystem.py +44 -25
  2. dissect/target/filesystems/config.py +32 -21
  3. dissect/target/filesystems/extfs.py +4 -0
  4. dissect/target/filesystems/itunes.py +1 -1
  5. dissect/target/filesystems/tar.py +1 -1
  6. dissect/target/filesystems/zip.py +81 -46
  7. dissect/target/helpers/config.py +22 -7
  8. dissect/target/helpers/configutil.py +69 -5
  9. dissect/target/helpers/cyber.py +4 -2
  10. dissect/target/helpers/fsutil.py +32 -4
  11. dissect/target/helpers/loaderutil.py +26 -7
  12. dissect/target/helpers/network_managers.py +22 -7
  13. dissect/target/helpers/record.py +37 -0
  14. dissect/target/helpers/record_modifier.py +23 -4
  15. dissect/target/helpers/shell_application_ids.py +732 -0
  16. dissect/target/helpers/utils.py +11 -0
  17. dissect/target/loader.py +1 -0
  18. dissect/target/loaders/ab.py +285 -0
  19. dissect/target/loaders/libvirt.py +40 -0
  20. dissect/target/loaders/mqtt.py +14 -1
  21. dissect/target/loaders/tar.py +8 -4
  22. dissect/target/loaders/utm.py +3 -0
  23. dissect/target/loaders/velociraptor.py +6 -6
  24. dissect/target/plugin.py +60 -3
  25. dissect/target/plugins/apps/browser/chrome.py +1 -0
  26. dissect/target/plugins/apps/browser/chromium.py +7 -5
  27. dissect/target/plugins/apps/browser/edge.py +1 -0
  28. dissect/target/plugins/apps/browser/firefox.py +82 -36
  29. dissect/target/plugins/apps/remoteaccess/anydesk.py +70 -50
  30. dissect/target/plugins/apps/remoteaccess/remoteaccess.py +8 -8
  31. dissect/target/plugins/apps/remoteaccess/teamviewer.py +46 -31
  32. dissect/target/plugins/apps/ssh/openssh.py +1 -1
  33. dissect/target/plugins/apps/ssh/ssh.py +177 -0
  34. dissect/target/plugins/apps/texteditor/__init__.py +0 -0
  35. dissect/target/plugins/apps/texteditor/texteditor.py +13 -0
  36. dissect/target/plugins/apps/texteditor/windowsnotepad.py +340 -0
  37. dissect/target/plugins/child/qemu.py +21 -0
  38. dissect/target/plugins/filesystem/ntfs/mft.py +132 -45
  39. dissect/target/plugins/filesystem/unix/capability.py +102 -87
  40. dissect/target/plugins/filesystem/walkfs.py +32 -21
  41. dissect/target/plugins/filesystem/yara.py +144 -23
  42. dissect/target/plugins/general/network.py +82 -0
  43. dissect/target/plugins/general/users.py +14 -10
  44. dissect/target/plugins/os/unix/_os.py +19 -5
  45. dissect/target/plugins/os/unix/bsd/freebsd/_os.py +3 -5
  46. dissect/target/plugins/os/unix/esxi/_os.py +29 -23
  47. dissect/target/plugins/os/unix/etc/etc.py +5 -8
  48. dissect/target/plugins/os/unix/history.py +3 -7
  49. dissect/target/plugins/os/unix/linux/_os.py +15 -14
  50. dissect/target/plugins/os/unix/linux/android/_os.py +15 -24
  51. dissect/target/plugins/os/unix/linux/redhat/_os.py +1 -1
  52. dissect/target/plugins/os/unix/locale.py +17 -6
  53. dissect/target/plugins/os/unix/shadow.py +47 -31
  54. dissect/target/plugins/os/windows/_os.py +4 -4
  55. dissect/target/plugins/os/windows/adpolicy.py +4 -1
  56. dissect/target/plugins/os/windows/catroot.py +1 -11
  57. dissect/target/plugins/os/windows/credential/__init__.py +0 -0
  58. dissect/target/plugins/os/windows/credential/lsa.py +174 -0
  59. dissect/target/plugins/os/windows/{sam.py → credential/sam.py} +5 -2
  60. dissect/target/plugins/os/windows/defender.py +6 -3
  61. dissect/target/plugins/os/windows/dpapi/blob.py +3 -0
  62. dissect/target/plugins/os/windows/dpapi/crypto.py +61 -23
  63. dissect/target/plugins/os/windows/dpapi/dpapi.py +127 -133
  64. dissect/target/plugins/os/windows/dpapi/keyprovider/__init__.py +0 -0
  65. dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +21 -0
  66. dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +17 -0
  67. dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +20 -0
  68. dissect/target/plugins/os/windows/dpapi/keyprovider/keyprovider.py +8 -0
  69. dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +38 -0
  70. dissect/target/plugins/os/windows/dpapi/master_key.py +3 -0
  71. dissect/target/plugins/os/windows/jumplist.py +292 -0
  72. dissect/target/plugins/os/windows/lnk.py +96 -93
  73. dissect/target/plugins/os/windows/regf/shimcache.py +2 -2
  74. dissect/target/plugins/os/windows/regf/usb.py +179 -114
  75. dissect/target/plugins/os/windows/task_helpers/tasks_xml.py +1 -1
  76. dissect/target/plugins/os/windows/wua_history.py +1073 -0
  77. dissect/target/target.py +4 -3
  78. dissect/target/tools/fs.py +53 -15
  79. dissect/target/tools/fsutils.py +243 -0
  80. dissect/target/tools/info.py +11 -4
  81. dissect/target/tools/query.py +2 -2
  82. dissect/target/tools/shell.py +505 -333
  83. dissect/target/tools/utils.py +23 -2
  84. dissect/target/tools/yara.py +65 -0
  85. dissect/target/volumes/md.py +2 -2
  86. {dissect.target-3.18.dev16.dist-info → dissect.target-3.19.dist-info}/METADATA +11 -7
  87. {dissect.target-3.18.dev16.dist-info → dissect.target-3.19.dist-info}/RECORD +93 -74
  88. {dissect.target-3.18.dev16.dist-info → dissect.target-3.19.dist-info}/WHEEL +1 -1
  89. {dissect.target-3.18.dev16.dist-info → dissect.target-3.19.dist-info}/entry_points.txt +1 -0
  90. dissect/target/helpers/ssh.py +0 -177
  91. /dissect/target/plugins/os/windows/{credhist.py → credential/credhist.py} +0 -0
  92. {dissect.target-3.18.dev16.dist-info → dissect.target-3.19.dist-info}/COPYRIGHT +0 -0
  93. {dissect.target-3.18.dev16.dist-info → dissect.target-3.19.dist-info}/LICENSE +0 -0
  94. {dissect.target-3.18.dev16.dist-info → dissect.target-3.19.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,292 @@
1
+ from __future__ import annotations
2
+
3
+ import io
4
+ import logging
5
+ from struct import error as StructError
6
+ from typing import BinaryIO, Iterator
7
+
8
+ from dissect.cstruct import cstruct
9
+ from dissect.ole import OLE
10
+ from dissect.ole.exceptions import Error as OleError
11
+ from dissect.shellitem.lnk import Lnk
12
+
13
+ from dissect.target import Target
14
+ from dissect.target.exceptions import UnsupportedPluginError
15
+ from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
16
+ from dissect.target.helpers.record import create_extended_descriptor
17
+ from dissect.target.helpers.shell_application_ids import APPLICATION_IDENTIFIERS
18
+ from dissect.target.helpers.utils import findall
19
+ from dissect.target.plugin import Plugin, export
20
+ from dissect.target.plugins.os.windows.lnk import LnkRecord, parse_lnk_file
21
+
22
+ log = logging.getLogger(__name__)
23
+
24
+ LNK_GUID = b"\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46"
25
+
26
+ JumpListRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
27
+ "windows/jumplist",
28
+ [
29
+ ("string", "type"),
30
+ ("string", "application_id"),
31
+ ("string", "application_name"),
32
+ *LnkRecord.target_fields,
33
+ ],
34
+ )
35
+
36
+
37
+ custom_destination_def = """
38
+ struct header {
39
+ int version;
40
+ int unknown1;
41
+ int unknown2;
42
+ int value_type;
43
+ }
44
+
45
+ struct header_end {
46
+ int number_of_entries;
47
+ }
48
+
49
+ struct header_end_0 {
50
+ uint16 name_length;
51
+ wchar name[name_length];
52
+ int number_of_entries;
53
+ }
54
+
55
+ struct footer {
56
+ char magic[4];
57
+ }
58
+ """
59
+
60
+ c_custom_destination = cstruct()
61
+ c_custom_destination.load(custom_destination_def)
62
+
63
+
64
+ class JumpListFile:
65
+ def __init__(self, fh: BinaryIO, file_name: str):
66
+ self.fh = fh
67
+ self.file_name = file_name
68
+
69
+ self.application_id, self.application_type = file_name.split(".")
70
+ self.application_type = self.application_type.split("-")[0]
71
+ self.application_name = APPLICATION_IDENTIFIERS.get(self.application_id)
72
+
73
+ def __iter__(self) -> Iterator[Lnk]:
74
+ raise NotImplementedError
75
+
76
+ @property
77
+ def name(self) -> str:
78
+ """Return the name of the application."""
79
+ return self.application_name
80
+
81
+ @property
82
+ def id(self) -> str:
83
+ """Return the application identifier."""
84
+ return self.application_id
85
+
86
+ @property
87
+ def type(self) -> str:
88
+ """Return the type of the Jump List file."""
89
+ return self.application_type
90
+
91
+
92
+ class AutomaticDestinationFile(JumpListFile):
93
+ """Parse Jump List AutomaticDestination file."""
94
+
95
+ def __init__(self, fh: BinaryIO, file_name: str):
96
+ super().__init__(fh, file_name)
97
+ self.ole = OLE(self.fh)
98
+
99
+ def __iter__(self) -> Iterator[Lnk]:
100
+ for dir_name in self.ole.root.listdir():
101
+ if dir_name == "DestList":
102
+ continue
103
+
104
+ dir = self.ole.get(dir_name)
105
+
106
+ for item in dir.open():
107
+ try:
108
+ yield Lnk(io.BytesIO(item))
109
+ except StructError:
110
+ continue
111
+ except Exception as e:
112
+ log.warning("Failed to parse LNK file from directory %s", dir_name)
113
+ log.debug("", exc_info=e)
114
+ continue
115
+
116
+
117
+ class CustomDestinationFile(JumpListFile):
118
+ """Parse Jump List CustomDestination file."""
119
+
120
+ MAGIC_FOOTER = 0xBABFFBAB
121
+ VERSIONS = [2]
122
+
123
+ def __init__(self, fh: BinaryIO, file_name: str):
124
+ super().__init__(fh, file_name)
125
+
126
+ self.fh.seek(-4, io.SEEK_END)
127
+ self.footer = c_custom_destination.footer(self.fh.read(4))
128
+ self.magic = int.from_bytes(self.footer.magic, "little")
129
+
130
+ self.fh.seek(0, io.SEEK_SET)
131
+ self.header = c_custom_destination.header(self.fh)
132
+ self.version = self.header.version
133
+
134
+ if self.header.value_type == 0:
135
+ self.header_end = c_custom_destination.header_end_0(self.fh)
136
+ elif self.header.value_type in [1, 2]:
137
+ self.header_end = c_custom_destination.header_end(self.fh)
138
+ else:
139
+ raise NotImplementedError(
140
+ f"The value_type ({self.header.value_type}) of the CustomDestination file is not implemented"
141
+ )
142
+
143
+ if self.version not in self.VERSIONS:
144
+ raise NotImplementedError(f"The CustomDestination file has an unsupported version: {self.version}")
145
+
146
+ if not self.MAGIC_FOOTER == self.magic:
147
+ raise ValueError(f"The CustomDestination file has an invalid magic footer: {self.magic}")
148
+
149
+ def __iter__(self) -> Iterator[Lnk]:
150
+ # Searches for all LNK GUID's because the number of entries in the header is not always correct.
151
+ buf = self.fh.read()
152
+
153
+ for offset in findall(buf, LNK_GUID):
154
+ try:
155
+ lnk = Lnk(io.BytesIO(buf[offset + len(LNK_GUID) :]))
156
+ yield lnk
157
+ except EOFError:
158
+ break
159
+ except Exception as e:
160
+ log.warning("Failed to parse LNK file from a CustomDestination file")
161
+ log.debug("", exc_info=e)
162
+ continue
163
+
164
+
165
+ class JumpListPlugin(Plugin):
166
+ """Jump List is a Windows feature introduced in Windows 7.
167
+
168
+ It stores information about recently accessed applications and files.
169
+
170
+ References:
171
+ - https://forensics.wiki/jump_lists
172
+ - https://github.com/libyal/dtformats/blob/main/documentation/Jump%20lists%20format.asciidoc
173
+ """
174
+
175
+ __namespace__ = "jumplist"
176
+
177
+ def __init__(self, target: Target):
178
+ super().__init__(target)
179
+ self.automatic_destinations = []
180
+ self.custom_destinations = []
181
+
182
+ for user_details in self.target.user_details.all_with_home():
183
+ for destination in user_details.home_path.glob(
184
+ "AppData/Roaming/Microsoft/Windows/Recent/CustomDestinations/*.customDestinations-ms"
185
+ ):
186
+ self.custom_destinations.append([destination, user_details.user])
187
+
188
+ for user_details in self.target.user_details.all_with_home():
189
+ for destination in user_details.home_path.glob(
190
+ "AppData/Roaming/Microsoft/Windows/Recent/AutomaticDestinations/*.automaticDestinations-ms"
191
+ ):
192
+ self.automatic_destinations.append([destination, user_details.user])
193
+
194
+ def check_compatible(self) -> None:
195
+ if not any([self.automatic_destinations, self.custom_destinations]):
196
+ raise UnsupportedPluginError("No Jump List files found")
197
+
198
+ @export(record=JumpListRecord)
199
+ def custom_destination(self) -> Iterator[JumpListRecord]:
200
+ """Return the content of CustomDestination Windows Jump Lists.
201
+
202
+ These are created when a user pins an application or a file in a Jump List.
203
+
204
+ Yields JumpListRecord with fields:
205
+
206
+ .. code-block:: text
207
+
208
+ type (string): Type of Jump List.
209
+ application_id (string): ID of the application.
210
+ application_name (string): Name of the application.
211
+ lnk_path (path): Path of the link (.lnk) file.
212
+ lnk_name (string): Name of the link (.lnk) file.
213
+ lnk_mtime (datetime): Modification time of the link (.lnk) file.
214
+ lnk_atime (datetime): Access time of the link (.lnk) file.
215
+ lnk_ctime (datetime): Creation time of the link (.lnk) file.
216
+ lnk_relativepath (path): Relative path of target file to the link (.lnk) file.
217
+ lnk_workdir (path): Path of the working directory the link (.lnk) file will execute from.
218
+ lnk_iconlocation (path): Path of the display icon used for the link (.lnk) file.
219
+ lnk_arguments (string): Command-line arguments passed to the target (linked) file.
220
+ local_base_path (string): Absolute path of the target (linked) file.
221
+ common_path_suffix (string): Suffix of the local_base_path.
222
+ lnk_full_path (string): Full path of the linked file. Made from local_base_path and common_path_suffix.
223
+ lnk_net_name (string): Specifies a server share path; for example, "\\\\server\\share".
224
+ lnk_device_name (string): Specifies a device; for example, the drive letter "D:"
225
+ machine_id (string): The NetBIOS name of the machine where the linked file was last known to reside.
226
+ target_mtime (datetime): Modification time of the target (linked) file.
227
+ target_atime (datetime): Access time of the target (linked) file.
228
+ target_ctime (datetime): Creation time of the target (linked) file.
229
+ """
230
+ yield from self._generate_records(self.custom_destinations, CustomDestinationFile)
231
+
232
+ @export(record=JumpListRecord)
233
+ def automatic_destination(self) -> Iterator[JumpListRecord]:
234
+ """Return the content of AutomaticDestination Windows Jump Lists.
235
+
236
+ These are created automatically when a user opens an application or file.
237
+
238
+ Yields JumpListRecord with fields:
239
+
240
+ .. code-block:: text
241
+
242
+ type (string): Type of Jump List.
243
+ application_id (string): ID of the application.
244
+ application_name (string): Name of the application.
245
+ lnk_path (path): Path of the link (.lnk) file.
246
+ lnk_name (string): Name of the link (.lnk) file.
247
+ lnk_mtime (datetime): Modification time of the link (.lnk) file.
248
+ lnk_atime (datetime): Access time of the link (.lnk) file.
249
+ lnk_ctime (datetime): Creation time of the link (.lnk) file.
250
+ lnk_relativepath (path): Relative path of target file to the link (.lnk) file.
251
+ lnk_workdir (path): Path of the working directory the link (.lnk) file will execute from.
252
+ lnk_iconlocation (path): Path of the display icon used for the link (.lnk) file.
253
+ lnk_arguments (string): Command-line arguments passed to the target (linked) file.
254
+ local_base_path (string): Absolute path of the target (linked) file.
255
+ common_path_suffix (string): Suffix of the local_base_path.
256
+ lnk_full_path (string): Full path of the linked file. Made from local_base_path and common_path_suffix.
257
+ lnk_net_name (string): Specifies a server share path; for example, "\\\\server\\share".
258
+ lnk_device_name (string): Specifies a device; for example, the drive letter "D:"
259
+ machine_id (string): The NetBIOS name of the machine where the linked file was last known to reside.
260
+ target_mtime (datetime): Modification time of the target (linked) file.
261
+ target_atime (datetime): Access time of the target (linked) file.
262
+ target_ctime (datetime): Creation time of the target (linked) file.
263
+ """
264
+ yield from self._generate_records(self.automatic_destinations, AutomaticDestinationFile)
265
+
266
+ def _generate_records(
267
+ self,
268
+ destinations: list,
269
+ destination_file: AutomaticDestinationFile | CustomDestinationFile,
270
+ ) -> Iterator[JumpListRecord]:
271
+ for destination, user in destinations:
272
+ fh = destination.open("rb")
273
+
274
+ try:
275
+ jumplist = destination_file(fh, destination.name)
276
+ except OleError:
277
+ continue
278
+ except Exception as e:
279
+ self.target.log.warning("Failed to parse Jump List file: %s", destination)
280
+ self.target.log.debug("", exc_info=e)
281
+ continue
282
+
283
+ for lnk in jumplist:
284
+ if lnk := parse_lnk_file(self.target, lnk, destination):
285
+ yield JumpListRecord(
286
+ type=jumplist.type,
287
+ application_name=jumplist.name,
288
+ application_id=jumplist.id,
289
+ **lnk._asdict(),
290
+ _user=user,
291
+ _target=self.target,
292
+ )
@@ -34,6 +34,89 @@ LnkRecord = TargetRecordDescriptor(
34
34
  )
35
35
 
36
36
 
37
+ def parse_lnk_file(target: Target, lnk_file: Lnk, lnk_path: TargetPath) -> Iterator[LnkRecord]:
38
+ # we need to get the active codepage from the system to properly decode some values
39
+ codepage = target.codepage or "ascii"
40
+
41
+ lnk_net_name = lnk_device_name = None
42
+
43
+ if lnk_file.link_header:
44
+ lnk_name = lnk_file.stringdata.name_string.string if lnk_file.flag("has_name") else None
45
+
46
+ lnk_mtime = ts.from_unix(lnk_path.stat().st_mtime)
47
+ lnk_atime = ts.from_unix(lnk_path.stat().st_atime)
48
+ lnk_ctime = ts.from_unix(lnk_path.stat().st_ctime)
49
+
50
+ lnk_relativepath = (
51
+ target.fs.path(lnk_file.stringdata.relative_path.string) if lnk_file.flag("has_relative_path") else None
52
+ )
53
+ lnk_workdir = (
54
+ target.fs.path(lnk_file.stringdata.working_dir.string) if lnk_file.flag("has_working_dir") else None
55
+ )
56
+ lnk_iconlocation = (
57
+ target.fs.path(lnk_file.stringdata.icon_location.string) if lnk_file.flag("has_icon_location") else None
58
+ )
59
+ lnk_arguments = lnk_file.stringdata.command_line_arguments.string if lnk_file.flag("has_arguments") else None
60
+ local_base_path = (
61
+ lnk_file.linkinfo.local_base_path.decode(codepage)
62
+ if lnk_file.flag("has_link_info") and lnk_file.linkinfo.flag("volumeid_and_local_basepath")
63
+ else None
64
+ )
65
+ common_path_suffix = (
66
+ lnk_file.linkinfo.common_path_suffix.decode(codepage) if lnk_file.flag("has_link_info") else None
67
+ )
68
+
69
+ if local_base_path and common_path_suffix:
70
+ lnk_full_path = target.fs.path(local_base_path + common_path_suffix)
71
+ elif local_base_path and not common_path_suffix:
72
+ lnk_full_path = target.fs.path(local_base_path)
73
+ else:
74
+ lnk_full_path = None
75
+
76
+ if lnk_file.flag("has_link_info"):
77
+ if lnk_file.linkinfo.flag("common_network_relative_link_and_pathsuffix"):
78
+ lnk_net_name = (
79
+ lnk_file.linkinfo.common_network_relative_link.net_name.decode()
80
+ if lnk_file.linkinfo.common_network_relative_link.net_name
81
+ else None
82
+ )
83
+ lnk_device_name = (
84
+ lnk_file.linkinfo.common_network_relative_link.device_name.decode()
85
+ if lnk_file.linkinfo.common_network_relative_link.device_name
86
+ else None
87
+ )
88
+ try:
89
+ machine_id = lnk_file.extradata.TRACKER_PROPS.machine_id.decode(codepage).strip("\x00")
90
+ except AttributeError:
91
+ machine_id = None
92
+
93
+ target_mtime = ts.wintimestamp(lnk_file.link_header.write_time)
94
+ target_atime = ts.wintimestamp(lnk_file.link_header.access_time)
95
+ target_ctime = ts.wintimestamp(lnk_file.link_header.creation_time)
96
+
97
+ return LnkRecord(
98
+ lnk_path=lnk_path,
99
+ lnk_name=lnk_name,
100
+ lnk_mtime=lnk_mtime,
101
+ lnk_atime=lnk_atime,
102
+ lnk_ctime=lnk_ctime,
103
+ lnk_relativepath=lnk_relativepath,
104
+ lnk_workdir=lnk_workdir,
105
+ lnk_iconlocation=lnk_iconlocation,
106
+ lnk_arguments=lnk_arguments,
107
+ local_base_path=local_base_path,
108
+ common_path_suffix=common_path_suffix,
109
+ lnk_full_path=lnk_full_path,
110
+ lnk_net_name=lnk_net_name,
111
+ lnk_device_name=lnk_device_name,
112
+ machine_id=machine_id,
113
+ target_mtime=target_mtime,
114
+ target_atime=target_atime,
115
+ target_ctime=target_ctime,
116
+ _target=target,
117
+ )
118
+
119
+
37
120
  class LnkPlugin(Plugin):
38
121
  def __init__(self, target: Target) -> None:
39
122
  super().__init__(target)
@@ -45,9 +128,9 @@ class LnkPlugin(Plugin):
45
128
  return None
46
129
  raise UnsupportedPluginError("No folders containing link files found")
47
130
 
48
- @arg("--directory", "-d", dest="directory", default=None, help="Path to .lnk file in target")
131
+ @arg("--path", "-p", dest="path", default=None, help="Path to directory or .lnk file in target")
49
132
  @export(record=LnkRecord)
50
- def lnk(self, directory: Optional[str] = None) -> Iterator[LnkRecord]:
133
+ def lnk(self, path: Optional[str] = None) -> Iterator[LnkRecord]:
51
134
  """Parse all .lnk files in /ProgramData, /Users, and /Windows or from a specified path in record format.
52
135
 
53
136
  Yields a LnkRecord record with the following fields:
@@ -74,101 +157,21 @@ class LnkPlugin(Plugin):
74
157
  target_ctime (datetime): Creation time of the target (linked) file.
75
158
  """
76
159
 
77
- # we need to get the active codepage from the system to properly decode some values
78
- codepage = self.target.codepage or "ascii"
79
-
80
- for entry in self.lnk_entries(directory):
160
+ for entry in self.lnk_entries(path):
81
161
  lnk_file = Lnk(entry.open())
82
- lnk_net_name = lnk_device_name = None
83
-
84
- if lnk_file.link_header:
85
- lnk_path = entry
86
- lnk_name = lnk_file.stringdata.name_string.string if lnk_file.flag("has_name") else None
87
-
88
- lnk_mtime = ts.from_unix(entry.stat().st_mtime)
89
- lnk_atime = ts.from_unix(entry.stat().st_atime)
90
- lnk_ctime = ts.from_unix(entry.stat().st_ctime)
91
-
92
- lnk_relativepath = (
93
- self.target.fs.path(lnk_file.stringdata.relative_path.string)
94
- if lnk_file.flag("has_relative_path")
95
- else None
96
- )
97
- lnk_workdir = (
98
- self.target.fs.path(lnk_file.stringdata.working_dir.string)
99
- if lnk_file.flag("has_working_dir")
100
- else None
101
- )
102
- lnk_iconlocation = (
103
- self.target.fs.path(lnk_file.stringdata.icon_location.string)
104
- if lnk_file.flag("has_icon_location")
105
- else None
106
- )
107
- lnk_arguments = (
108
- lnk_file.stringdata.command_line_arguments.string if lnk_file.flag("has_arguments") else None
109
- )
110
- local_base_path = (
111
- lnk_file.linkinfo.local_base_path.decode(codepage)
112
- if lnk_file.flag("has_link_info") and lnk_file.linkinfo.flag("volumeid_and_local_basepath")
113
- else None
114
- )
115
- common_path_suffix = (
116
- lnk_file.linkinfo.common_path_suffix.decode(codepage) if lnk_file.flag("has_link_info") else None
117
- )
118
-
119
- if local_base_path and common_path_suffix:
120
- lnk_full_path = self.target.fs.path(local_base_path + common_path_suffix)
121
- elif local_base_path and not common_path_suffix:
122
- lnk_full_path = self.target.fs.path(local_base_path)
123
- else:
124
- lnk_full_path = None
125
-
126
- if lnk_file.flag("has_link_info"):
127
- if lnk_file.linkinfo.flag("common_network_relative_link_and_pathsuffix"):
128
- lnk_net_name = (
129
- lnk_file.linkinfo.common_network_relative_link.net_name.decode()
130
- if lnk_file.linkinfo.common_network_relative_link.net_name
131
- else None
132
- )
133
- lnk_device_name = (
134
- lnk_file.linkinfo.common_network_relative_link.device_name.decode()
135
- if lnk_file.linkinfo.common_network_relative_link.device_name
136
- else None
137
- )
138
- try:
139
- machine_id = lnk_file.extradata.TRACKER_PROPS.machine_id.decode(codepage).strip("\x00")
140
- except AttributeError:
141
- machine_id = None
142
-
143
- target_mtime = ts.wintimestamp(lnk_file.link_header.write_time)
144
- target_atime = ts.wintimestamp(lnk_file.link_header.access_time)
145
- target_ctime = ts.wintimestamp(lnk_file.link_header.creation_time)
146
-
147
- yield LnkRecord(
148
- lnk_path=lnk_path,
149
- lnk_name=lnk_name,
150
- lnk_mtime=lnk_mtime,
151
- lnk_atime=lnk_atime,
152
- lnk_ctime=lnk_ctime,
153
- lnk_relativepath=lnk_relativepath,
154
- lnk_workdir=lnk_workdir,
155
- lnk_iconlocation=lnk_iconlocation,
156
- lnk_arguments=lnk_arguments,
157
- local_base_path=local_base_path,
158
- common_path_suffix=common_path_suffix,
159
- lnk_full_path=lnk_full_path,
160
- lnk_net_name=lnk_net_name,
161
- lnk_device_name=lnk_device_name,
162
- machine_id=machine_id,
163
- target_mtime=target_mtime,
164
- target_atime=target_atime,
165
- target_ctime=target_ctime,
166
- _target=self.target,
167
- )
162
+ yield parse_lnk_file(self.target, lnk_file, entry)
168
163
 
169
164
  def lnk_entries(self, path: Optional[str] = None) -> Iterator[TargetPath]:
170
165
  if path:
171
- yield self.target.fs.path(path)
166
+ target_path = self.target.fs.path(path)
167
+ if not target_path.exists():
168
+ self.target.log.error("Provided path %s does not exist on target", target_path)
169
+ return
170
+
171
+ if target_path.is_file():
172
+ yield target_path
173
+ else:
174
+ yield from target_path.rglob("*.lnk")
172
175
  else:
173
176
  for folder in self.folders:
174
177
  yield from self.target.fs.path("sysvol").joinpath(folder).rglob("*.lnk")
@@ -1,6 +1,6 @@
1
1
  import binascii
2
2
  from datetime import datetime
3
- from enum import IntEnum, auto
3
+ from enum import IntEnum
4
4
  from io import BytesIO
5
5
  from typing import Callable, Generator, Optional, Tuple, Union
6
6
 
@@ -116,7 +116,7 @@ class SHIMCACHE_WIN_TYPE(IntEnum):
116
116
  VERSION_NT61 = 0x0601
117
117
  VERSION_NT52 = 0x0502
118
118
 
119
- VERSION_WIN81_NO_HEADER = auto()
119
+ VERSION_WIN81_NO_HEADER = 0x1002 # auto()
120
120
 
121
121
 
122
122
  def win_10_path(ed: Structure) -> str: