dissect.target 3.20.dev23__py3-none-any.whl → 3.20.dev26__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -181,6 +181,9 @@ class BtrfsFilesystemEntry(FilesystemEntry):
181
181
 
182
182
  # Add block information of the filesystem
183
183
  st_info.st_blksize = entry.btrfs.sector_size
184
- st_info.st_blocks = math.ceil(entry.size / st_info.st_blksize)
184
+
185
+ st_info.st_blocks = 0
186
+ if not self.is_dir():
187
+ st_info.st_blocks = (st_info.st_blksize // 512) * math.ceil(st_info.st_size / st_info.st_blksize)
185
188
 
186
189
  return st_info
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import math
3
4
  import stat
4
5
  from typing import BinaryIO, Iterator, Optional
5
6
 
@@ -151,12 +152,14 @@ class NtfsFilesystemEntry(FilesystemEntry):
151
152
  record = self.dereference()
152
153
 
153
154
  size = 0
155
+ real_size = 0
154
156
  if self.is_symlink():
155
157
  mode = stat.S_IFLNK
156
158
  elif self.is_file():
157
159
  mode = stat.S_IFREG
158
160
  try:
159
161
  size = record.size(self.ads)
162
+ real_size = record.size(self.ads, allocated=True)
160
163
  except NtfsFileNotFoundError as e:
161
164
  # Occurs when it cannot find the the specific ads inside its attributes
162
165
  raise FileNotFoundError from e
@@ -176,16 +179,29 @@ class NtfsFilesystemEntry(FilesystemEntry):
176
179
  0,
177
180
  size,
178
181
  stdinfo.last_access_time.timestamp(),
179
- stdinfo.last_change_time.timestamp(),
182
+ stdinfo.last_modification_time.timestamp(),
183
+ # ctime gets set to creation time for python <3.12 purposes
180
184
  stdinfo.creation_time.timestamp(),
181
185
  ]
182
186
  )
183
187
 
184
188
  # Set the nanosecond resolution separately
185
189
  st_info.st_atime_ns = stdinfo.last_access_time_ns
186
- st_info.st_mtime_ns = stdinfo.last_change_time_ns
190
+ st_info.st_mtime_ns = stdinfo.last_modification_time_ns
191
+
187
192
  st_info.st_ctime_ns = stdinfo.creation_time_ns
188
193
 
194
+ st_info.st_birthtime = stdinfo.creation_time.timestamp()
195
+ st_info.st_birthtime_ns = stdinfo.creation_time_ns
196
+
197
+ # real_size is none if the size is resident
198
+ st_info.st_blksize = record.ntfs.cluster_size
199
+ blocks = 0
200
+ if not record.resident:
201
+ blocks = math.ceil(real_size / 512)
202
+
203
+ st_info.st_blocks = blocks
204
+
189
205
  return st_info
190
206
 
191
207
  def attr(self) -> AttributeMap:
@@ -1,6 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import io
2
4
  import logging
3
5
  import uuid
6
+ from datetime import datetime
7
+ from typing import Any, Iterator
4
8
 
5
9
  from dissect.cstruct import cstruct
6
10
  from dissect.util.ts import dostimestamp
@@ -13,7 +17,9 @@ from dissect.target.helpers.descriptor_extensions import (
13
17
  UserRecordDescriptorExtension,
14
18
  )
15
19
  from dissect.target.helpers.record import create_extended_descriptor
20
+ from dissect.target.helpers.regutil import RegistryKey
16
21
  from dissect.target.plugin import Plugin, export
22
+ from dissect.target.target import Target
17
23
 
18
24
  log = logging.getLogger(__name__)
19
25
 
@@ -170,30 +176,30 @@ struct SHITEM_MTP_VOLUME_GUID {
170
176
  };
171
177
 
172
178
  struct SHITEM_MTP_VOLUME {
173
- uint16 size;
174
- uint8 type;
175
- uint8 unk0;
176
- uint16 data_size;
177
- uint32 data_signature;
178
- uint32 unk1;
179
- uint16 unk2;
180
- uint16 unk3;
181
- uint16 unk4;
182
- uint16 unk5;
183
- uint32 unk6;
184
- uint64 unk7;
185
- uint32 unk8;
186
- uint32 name_size;
187
- uint32 identifier_size;
188
- uint32 filesystem_size;
189
- uint32 num_guid;
190
- wchar name[name_size];
191
- wchar identifier[identifier_size];
192
- wchar filesystem[filesystem_size];
193
- SHITEM_MTP_VOLUME_GUID guids[num_guid];
194
- uint32 unk9;
195
- char class_identifier[16];
196
- uint32 num_properties;
179
+ uint16 size;
180
+ uint8 type;
181
+ uint8 unk0;
182
+ uint16 data_size;
183
+ uint32 data_signature;
184
+ uint32 unk1;
185
+ uint16 unk2;
186
+ uint16 unk3;
187
+ uint16 unk4;
188
+ uint16 unk5;
189
+ uint32 unk6;
190
+ uint64 unk7;
191
+ uint32 unk8;
192
+ uint32 name_size;
193
+ uint32 identifier_size;
194
+ uint32 filesystem_size;
195
+ uint32 num_guid;
196
+ wchar name[name_size];
197
+ wchar identifier[identifier_size];
198
+ wchar filesystem[filesystem_size];
199
+ SHITEM_MTP_VOLUME_GUID guids[num_guid];
200
+ uint32 unk9;
201
+ char class_identifier[16];
202
+ uint32 num_properties;
197
203
  };
198
204
 
199
205
  struct SHITEM_USERS_PROPERTY_VIEW {
@@ -248,267 +254,6 @@ c_bag = cstruct().load(bag_def)
248
254
  DELEGATE_ITEM_IDENTIFIER = b"\x74\x1a\x59\x5e\x96\xdf\xd3\x48\x8d\x67\x17\x33\xbc\xee\x28\xba"
249
255
 
250
256
 
251
- ShellBagRecord = create_extended_descriptor([RegistryRecordDescriptorExtension, UserRecordDescriptorExtension])(
252
- "windows/shellbag",
253
- [
254
- ("path", "path"),
255
- ("datetime", "creation_time"),
256
- ("datetime", "modification_time"),
257
- ("datetime", "access_time"),
258
- ("datetime", "regf_modification_time"),
259
- ],
260
- )
261
-
262
-
263
- class ShellBagsPlugin(Plugin):
264
- """Windows Shellbags plugin.
265
-
266
- References:
267
- - https://github.com/libyal/libfwsi
268
- """
269
-
270
- KEYS = [
271
- "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell",
272
- "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\ShellNoRoam",
273
- "HKEY_CURRENT_USER\\Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\Shell",
274
- "HKEY_CURRENT_USER\\Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\ShellNoRoam",
275
- "HKEY_CURRENT_USER\\Software\\Classes\\Wow6432Node\\Local Settings\\Software\\Microsoft\\Windows\\Shell",
276
- "HKEY_CURRENT_USER\\Software\\Classes\\Wow6432Node\\Local Settings\\Software\\Microsoft\\Windows\\ShellNoRoam",
277
- "HKEY_CURRENT_USER\\Local Settings\\Software\\Microsoft\\Windows\\Shell\\BagMRU",
278
- ]
279
-
280
- def __init__(self, target):
281
- super().__init__(target)
282
- self.bagkeys = list(self.target.registry.keys(self.KEYS))
283
-
284
- def check_compatible(self) -> None:
285
- if not len(self.bagkeys) > 0:
286
- raise UnsupportedPluginError("No shellbags found")
287
-
288
- @export(record=ShellBagRecord)
289
- def shellbags(self):
290
- """Return Windows Shellbags.
291
-
292
- Shellbags are registry keys to improve user experience when using Windows Explorer. It stores information about
293
- for example file/folder creation time and access time.
294
-
295
- References:
296
- - https://www.hackingarticles.in/forensic-investigation-shellbags/
297
- """
298
- for regkey in self.bagkeys:
299
- try:
300
- bagsmru = regkey.subkey("BagMRU")
301
-
302
- for r in self._walk_bags(bagsmru, None):
303
- yield r
304
- except RegistryKeyNotFoundError:
305
- continue
306
- except Exception: # noqa
307
- self.target.log.exception("Exception while parsing shellbags")
308
- continue
309
-
310
- def _walk_bags(self, key, path_prefix):
311
- path_prefix = [] if path_prefix is None else [path_prefix]
312
-
313
- user = self.target.registry.get_user(key)
314
-
315
- for reg_val in key.values():
316
- name, value = reg_val.name, reg_val.value
317
- if not name.isdigit():
318
- continue
319
- path = None
320
-
321
- for item in parse_shell_item_list(value):
322
- path = "\\".join(path_prefix + [item.name])
323
- yield ShellBagRecord(
324
- path=windows_path(path),
325
- creation_time=item.creation_time,
326
- modification_time=item.modification_time,
327
- access_time=item.access_time,
328
- regf_modification_time=key.ts,
329
- _target=self.target,
330
- _user=user,
331
- _key=key,
332
- )
333
-
334
- for r in self._walk_bags(key.subkey(name), path):
335
- yield r
336
-
337
-
338
- def parse_shell_item_list(buf):
339
- offset = 0
340
- end = len(buf)
341
- list_buf = memoryview(buf)
342
-
343
- parent = None
344
- while offset < end:
345
- size = c_bag.uint16(list_buf[offset : offset + 2])
346
-
347
- if size == 0:
348
- break
349
-
350
- item_buf = list_buf[offset : offset + size]
351
-
352
- entry = None
353
- if size >= 8:
354
- signature = c_bag.uint32(item_buf[4:8])
355
- if signature == 0x39DE2184:
356
- entry = CONTROL_PANEL_CATEGORY
357
- elif signature == 0x4D677541:
358
- entry = CDBURN
359
- elif signature == 0x49534647:
360
- entry = GAME_FOLDER
361
- elif signature == 0xFFFFFF38:
362
- entry = CONTROL_PANEL_CPL_FILE
363
-
364
- if size >= 10 and not entry:
365
- signature = c_bag.uint32(item_buf[6:10])
366
- if signature == 0x07192006:
367
- entry = MTP_FILE_ENTRY
368
- elif signature == 0x10312005:
369
- entry = MTP_VOLUME
370
- elif signature in (0x10141981, 0x23A3DFD5, 0x23FEBBEE, 0x3B93AFBB, 0xBEEBEE00):
371
- entry = USERS_PROPERTY_VIEW
372
- elif signature == 0x46534643:
373
- entry = UNKNOWN_0x74
374
-
375
- if size >= 38 and not entry:
376
- if item_buf[size - 32 : size] == DELEGATE_ITEM_IDENTIFIER:
377
- entry = DELEGATE
378
-
379
- if size >= 3 and not entry:
380
- class_type = item_buf[2]
381
- mask_type = class_type & 0x70
382
-
383
- if mask_type == 0x00:
384
- if class_type == 0x00:
385
- entry = UNKNOWN0
386
- elif class_type == 0x01:
387
- entry = UNKNOWN1
388
-
389
- elif mask_type == 0x10:
390
- if class_type == 0x1F:
391
- entry = ROOT_FOLDER
392
-
393
- elif mask_type == 0x20:
394
- if class_type in (0x23, 0x25, 0x29, 0x2A, 0x2E, 0x2F):
395
- entry = VOLUME
396
-
397
- elif mask_type == 0x30:
398
- if class_type in (0x30, 0x31, 0x32, 0x35, 0x36, 0xB1):
399
- entry = FILE_ENTRY
400
-
401
- elif mask_type == 0x40:
402
- if class_type in (0x41, 0x42, 0x46, 0x47, 0x4C, 0xC3):
403
- entry = NETWORK
404
-
405
- elif mask_type == 0x50:
406
- if class_type == 0x52:
407
- entry = COMPRESSED_FOLDER
408
-
409
- elif mask_type == 0x60:
410
- if class_type == 0x61:
411
- entry = URI
412
-
413
- elif mask_type == 0x70:
414
- if class_type == 0x71:
415
- entry = CONTROL_PANEL
416
- else:
417
- if not entry:
418
- log.debug("No supported shell item found for size 0x%04x and type 0x%02x", size, class_type)
419
- entry = UNKNOWN
420
-
421
- if not entry:
422
- log.debug("No supported shell item found for size 0x%04x", size)
423
- entry = UNKNOWN
424
-
425
- entry = entry(item_buf)
426
- entry.parent = parent
427
-
428
- first_extension_block_offset = c_bag.uint16(item_buf[-2:])
429
- if 4 <= first_extension_block_offset < size - 2:
430
- extension_offset = first_extension_block_offset
431
- while extension_offset < size - 2:
432
- extension_size = c_bag.uint16(item_buf[extension_offset : extension_offset + 2])
433
-
434
- if extension_size == 0:
435
- break
436
-
437
- if extension_size > size - extension_offset:
438
- log.debug(
439
- "Extension size exceeds item size: 0x%04x > 0x%04x - 0x%04x",
440
- extension_size,
441
- size,
442
- extension_offset,
443
- )
444
- break # Extension size too large
445
-
446
- extension_buf = item_buf[extension_offset : extension_offset + extension_size]
447
- extension_signature = c_bag.uint32(extension_buf[4:8])
448
-
449
- ext = None
450
-
451
- if extension_signature >> 16 != 0xBEEF:
452
- log.debug("Got unsupported extension signature 0x%08x from item %r", extension_signature, entry)
453
- pass # Unsupported
454
-
455
- elif extension_signature == 0xBEEF0000:
456
- pass
457
-
458
- elif extension_signature == 0xBEEF0001:
459
- pass
460
-
461
- elif extension_signature == 0xBEEF0003:
462
- ext = EXTENSION_BLOCK_BEEF0004
463
-
464
- elif extension_signature == 0xBEEF0004:
465
- ext = EXTENSION_BLOCK_BEEF0004
466
-
467
- elif extension_signature == 0xBEEF0005:
468
- ext = EXTENSION_BLOCK_BEEF0005
469
-
470
- elif extension_signature == 0xBEEF0006:
471
- pass
472
-
473
- elif extension_signature == 0xBEEF000A:
474
- pass
475
-
476
- elif extension_signature == 0xBEEF0013:
477
- pass
478
-
479
- elif extension_signature == 0xBEEF0014:
480
- pass
481
-
482
- elif extension_signature == 0xBEEF0019:
483
- pass
484
-
485
- elif extension_signature == 0xBEEF0025:
486
- pass
487
-
488
- elif extension_signature == 0xBEEF0026:
489
- pass
490
-
491
- else:
492
- log.debug(
493
- "Got unsupported beef extension signature 0x%08x from item %r", extension_signature, entry
494
- )
495
- pass
496
-
497
- if ext is None:
498
- ext = EXTENSION_BLOCK
499
- log.debug("Unimplemented extension signature 0x%08x from item %r", extension_signature, entry)
500
-
501
- ext = ext(extension_buf)
502
-
503
- entry.extensions.append(ext)
504
- extension_offset += extension_size
505
-
506
- parent = entry
507
- yield entry
508
-
509
- offset += size
510
-
511
-
512
257
  class SHITEM:
513
258
  STRUCT = None
514
259
 
@@ -521,43 +266,43 @@ class SHITEM:
521
266
  self.parent = None
522
267
  self.extensions = []
523
268
 
269
+ def __repr__(self) -> str:
270
+ return f"<{self.__class__.__name__}>"
271
+
524
272
  @property
525
- def name(self):
273
+ def name(self) -> str:
526
274
  return f"<SHITEM 0x{self.size:x}>"
527
275
 
528
276
  @property
529
- def creation_time(self):
277
+ def creation_time(self) -> None:
530
278
  return None
531
279
 
532
280
  @property
533
- def modification_time(self):
281
+ def modification_time(self) -> None:
534
282
  return None
535
283
 
536
284
  @property
537
- def access_time(self):
285
+ def access_time(self) -> None:
538
286
  return None
539
287
 
540
288
  @property
541
- def file_size(self):
289
+ def file_size(self) -> None:
542
290
  return None
543
291
 
544
292
  @property
545
- def file_reference(self):
293
+ def file_reference(self) -> None:
546
294
  return None
547
295
 
548
- def extension(self, cls):
296
+ def extension(self, cls: Any) -> Any | None:
549
297
  for ext in self.extensions:
550
298
  if isinstance(ext, cls):
551
299
  return ext
552
300
  return None
553
301
 
554
- def __repr__(self):
555
- return f"<{self.__class__.__name__}>"
556
-
557
302
 
558
303
  class UNKNOWN(SHITEM):
559
304
  @property
560
- def name(self):
305
+ def name(self) -> str:
561
306
  type_number = hex(self.type) if self.type else self.type
562
307
  return f"<UNKNOWN size=0x{self.size:04x} type={type_number}>"
563
308
 
@@ -573,7 +318,7 @@ class UNKNOWN0(SHITEM):
573
318
  self.guid = uuid.UUID(bytes_le=fh.read(16))
574
319
 
575
320
  @property
576
- def name(self):
321
+ def name(self) -> str:
577
322
  if self.guid:
578
323
  GUID_name = shell_folder_ids.DESCRIPTIONS.get(str(self.guid))
579
324
  return GUID_name or f"<UNKNOWN0: {{{self.guid}}}>"
@@ -585,11 +330,11 @@ class UNKNOWN1(SHITEM):
585
330
  STRUCT = c_bag.SHITEM_UNKNOWN1
586
331
 
587
332
  @property
588
- def name(self):
333
+ def name(self) -> str:
589
334
  return f"<UNKNOWN1 0x{self.size:x}>"
590
335
 
591
336
 
592
- class ROOT_FOLDER(SHITEM): # noqa
337
+ class ROOT_FOLDER(SHITEM):
593
338
  STRUCT = c_bag.SHITEM_ROOT_FOLDER
594
339
 
595
340
  def __init__(self, fh):
@@ -601,7 +346,7 @@ class ROOT_FOLDER(SHITEM): # noqa
601
346
  self.extension = None
602
347
 
603
348
  @property
604
- def name(self):
349
+ def name(self) -> str:
605
350
  GUID_name = shell_folder_ids.DESCRIPTIONS.get(str(self.guid))
606
351
  return GUID_name or f"{{{self.item.folder_id.name}: {self.guid}}}"
607
352
 
@@ -616,12 +361,12 @@ class VOLUME(SHITEM):
616
361
  if self.type == 0x2E:
617
362
  self.identifier = uuid.UUID(bytes_le=buf[4:20].tobytes())
618
363
  else:
619
- self.volume_name = self.fh.read(20).rstrip(b"\x00").decode()
364
+ self.volume_name = self.fh.read(20).rstrip(b"\x00").decode(errors="surrogateescape")
620
365
  if self.size >= 41:
621
366
  self.identifier = uuid.UUID(bytes_le=buf[25:41].tobytes())
622
367
 
623
368
  @property
624
- def name(self):
369
+ def name(self) -> str:
625
370
  if self.volume_name:
626
371
  return self.volume_name
627
372
  if self.identifier:
@@ -630,7 +375,7 @@ class VOLUME(SHITEM):
630
375
  return f"<VOLUME 0x{self.type:02x}>"
631
376
 
632
377
 
633
- class FILE_ENTRY(SHITEM): # noqa
378
+ class FILE_ENTRY(SHITEM):
634
379
  STRUCT = c_bag.SHITEM_FILE_ENTRY
635
380
 
636
381
  def __init__(self, buf):
@@ -644,7 +389,7 @@ class FILE_ENTRY(SHITEM): # noqa
644
389
  self.primary_name = c_bag.wchar[None](self.fh)
645
390
  self.is_unicode = True
646
391
  else:
647
- self.primary_name = c_bag.char[None](self.fh).decode()
392
+ self.primary_name = c_bag.char[None](self.fh).decode(errors="surrogateescape")
648
393
  self.is_unicode = False
649
394
 
650
395
  if self.fh.tell() % 2:
@@ -659,17 +404,17 @@ class FILE_ENTRY(SHITEM): # noqa
659
404
  if self.is_unicode:
660
405
  self.secondary_name = c_bag.wchar[None](self.fh)
661
406
  else:
662
- self.secondary_name = c_bag.char[None](self.fh).decode()
407
+ self.secondary_name = c_bag.char[None](self.fh).decode(errors="surrogateescape")
663
408
 
664
409
  @property
665
- def name(self):
410
+ def name(self) -> str:
666
411
  ext = self.extension(EXTENSION_BLOCK_BEEF0004)
667
412
  if ext and ext.long_name:
668
413
  return ext.long_name
669
414
  return self.primary_name
670
415
 
671
416
  @property
672
- def modification_time(self):
417
+ def modification_time(self) -> datetime | None:
673
418
  ts = self.item.modification_time
674
419
  if ts > 0:
675
420
  return dostimestamp(ts, swap=True)
@@ -691,15 +436,15 @@ class NETWORK(SHITEM):
691
436
  self.comments = c_bag.char[None](self.fh)
692
437
 
693
438
  @property
694
- def name(self):
695
- return self.item.location.decode()
439
+ def name(self) -> str:
440
+ return self.item.location.decode(errors="surrogateescape")
696
441
 
697
442
 
698
- class COMPRESSED_FOLDER(SHITEM): # noqa
443
+ class COMPRESSED_FOLDER(SHITEM):
699
444
  STRUCT = c_bag.SHITEM_COMPRESSED_FOLDER
700
445
 
701
446
  @property
702
- def name(self):
447
+ def name(self) -> str:
703
448
  return "<COMPRESSED_FOLDER>"
704
449
 
705
450
 
@@ -714,14 +459,14 @@ class URI(SHITEM):
714
459
  if self.item.flags & 0x80:
715
460
  self.uri = c_bag.wchar[None](self.fh)
716
461
  else:
717
- self.uri = c_bag.char[None](self.fh).decode()
462
+ self.uri = c_bag.char[None](self.fh).decode(errors="surrogateescape")
718
463
 
719
464
  @property
720
- def name(self):
465
+ def name(self) -> str:
721
466
  return self.uri or "<URI>"
722
467
 
723
468
 
724
- class CONTROL_PANEL(SHITEM): # noqa
469
+ class CONTROL_PANEL(SHITEM):
725
470
  STRUCT = c_bag.SHITEM_CONTROL_PANEL
726
471
 
727
472
  def __init__(self, buf):
@@ -729,12 +474,12 @@ class CONTROL_PANEL(SHITEM): # noqa
729
474
  self.guid = uuid.UUID(bytes_le=self.item.guid)
730
475
 
731
476
  @property
732
- def name(self):
477
+ def name(self) -> str:
733
478
  GUID_name = shell_folder_ids.DESCRIPTIONS.get(str(self.guid))
734
479
  return GUID_name or f"<CONTROL_PANEL {self.guid}>"
735
480
 
736
481
 
737
- class CONTROL_PANEL_CATEGORY(SHITEM): # noqa
482
+ class CONTROL_PANEL_CATEGORY(SHITEM):
738
483
  STRUCT = c_bag.SHITEM_CONTROL_PANEL_CATEGORY
739
484
  CATEGORIES = {
740
485
  0: "All Control Panel Items",
@@ -752,7 +497,7 @@ class CONTROL_PANEL_CATEGORY(SHITEM): # noqa
752
497
  }
753
498
 
754
499
  @property
755
- def name(self):
500
+ def name(self) -> str:
756
501
  categ_str = self.CATEGORIES.get(self.item.category)
757
502
  if categ_str:
758
503
  return categ_str
@@ -763,11 +508,11 @@ class CDBURN(SHITEM):
763
508
  STRUCT = c_bag.SHITEM_CDBURN
764
509
 
765
510
  @property
766
- def name(self):
511
+ def name(self) -> str:
767
512
  return "<CDBURN>"
768
513
 
769
514
 
770
- class GAME_FOLDER(SHITEM): # noqa
515
+ class GAME_FOLDER(SHITEM):
771
516
  STRUCT = c_bag.SHITEM_GAME_FOLDER
772
517
 
773
518
  def __init__(self, buf):
@@ -775,43 +520,43 @@ class GAME_FOLDER(SHITEM): # noqa
775
520
  self.guid = uuid.UUID(bytes_le=self.item.identifier)
776
521
 
777
522
  @property
778
- def name(self):
523
+ def name(self) -> str:
779
524
  return f"<GAME_FOLDER {{{self.guid}}}>"
780
525
 
781
526
 
782
- class CONTROL_PANEL_CPL_FILE(SHITEM): # noqa
527
+ class CONTROL_PANEL_CPL_FILE(SHITEM):
783
528
  STRUCT = c_bag.SHITEM_CONTROL_PANEL_CPL_FILE
784
529
 
785
530
  @property
786
- def name(self):
531
+ def name(self) -> str:
787
532
  return f"<CONTROL_PANEL_CPL_FILE path={self.item.cpl_path} name={self.item.name} comments={self.item.comments}>"
788
533
 
789
534
 
790
- class MTP_FILE_ENTRY(SHITEM): # noqa
535
+ class MTP_FILE_ENTRY(SHITEM):
791
536
  STRUCT = c_bag.SHITEM_MTP_FILE_ENTRY
792
537
 
793
538
  @property
794
- def name(self):
539
+ def name(self) -> str:
795
540
  return "<MTP_FILE_ENTRY>"
796
541
 
797
542
  @property
798
- def creation_time(self):
543
+ def creation_time(self) -> datetime:
799
544
  return self.item.creation_time
800
545
 
801
546
  @property
802
- def modification_time(self):
547
+ def modification_time(self) -> datetime:
803
548
  return self.item.modification_time
804
549
 
805
550
 
806
- class MTP_VOLUME(SHITEM): # noqa
551
+ class MTP_VOLUME(SHITEM):
807
552
  STRUCT = c_bag.SHITEM_MTP_FILE_ENTRY
808
553
 
809
554
  @property
810
- def name(self):
555
+ def name(self) -> str:
811
556
  return "<MTP_VOLUME>"
812
557
 
813
558
 
814
- class USERS_PROPERTY_VIEW(SHITEM): # noqa
559
+ class USERS_PROPERTY_VIEW(SHITEM):
815
560
  STRUCT = c_bag.SHITEM_USERS_PROPERTY_VIEW
816
561
 
817
562
  def __init__(self, buf):
@@ -823,13 +568,13 @@ class USERS_PROPERTY_VIEW(SHITEM): # noqa
823
568
  self.guid = uuid.UUID(bytes_le=self.item.identifier)
824
569
 
825
570
  @property
826
- def name(self):
571
+ def name(self) -> str:
827
572
  # As we don't know how to handle identifier_size other than 16 bytes, we fall back to data_signature
828
573
  property_view = self.guid or self.identifier
829
574
  return f"<USERS_PROPERTY_VIEW {{{property_view}}}>"
830
575
 
831
576
 
832
- class UNKNOWN_0x74(SHITEM): # noqa
577
+ class UNKNOWN_0x74(SHITEM):
833
578
  STRUCT = c_bag.SHITEM_UNKNOWN_0x74
834
579
 
835
580
  def __init__(self, buf):
@@ -839,11 +584,11 @@ class UNKNOWN_0x74(SHITEM): # noqa
839
584
  self.subitem = c_bag.SHITEM_UNKNOWN_0x74_SUBITEM(self.fh)
840
585
 
841
586
  @property
842
- def name(self):
843
- return self.subitem.primary_name.decode() if self.subitem else "<UNKNOWN_0x74>"
587
+ def name(self) -> str:
588
+ return self.subitem.primary_name.decode(errors="surrogateescape") if self.subitem else "<UNKNOWN_0x74>"
844
589
 
845
590
  @property
846
- def modification_time(self):
591
+ def modification_time(self) -> datetime | None:
847
592
  if self.subitem.modification_time > 0:
848
593
  return dostimestamp(self.subitem.modification_time, swap=True) if self.subitem else None
849
594
  return None
@@ -858,38 +603,38 @@ class DELEGATE(SHITEM):
858
603
  self.shell_identifier = uuid.UUID(bytes_le=self.item.shell_identifier)
859
604
 
860
605
  @property
861
- def name(self):
606
+ def name(self) -> str:
862
607
  GUID_name = shell_folder_ids.DESCRIPTIONS.get(str(self.shell_identifier))
863
608
  return GUID_name if GUID_name else f"{{{self.shell_identifier}}}"
864
609
 
865
610
 
866
- class EXTENSION_BLOCK: # noqa
611
+ class EXTENSION_BLOCK:
867
612
  def __init__(self, buf):
868
613
  self.buf = buf
869
614
  self.fh = io.BytesIO(buf)
870
615
  self.header = c_bag.EXTENSION_BLOCK_HEADER(self.fh)
871
616
 
617
+ def __repr__(self) -> str:
618
+ return f"<EXTENSION_BLOCK size=0x{self.size:04x} version=0x{self.version:04x} signature=0x{self.signature:08x}>"
619
+
872
620
  @property
873
- def size(self):
621
+ def size(self) -> int:
874
622
  return self.header.size
875
623
 
876
624
  @property
877
- def data_size(self):
625
+ def data_size(self) -> int:
878
626
  return self.size - 8 # minus header
879
627
 
880
628
  @property
881
- def version(self):
629
+ def version(self) -> int:
882
630
  return self.header.version
883
631
 
884
632
  @property
885
- def signature(self):
633
+ def signature(self) -> int:
886
634
  return self.header.signature
887
635
 
888
- def __repr__(self):
889
- return f"<EXTENSION_BLOCK size=0x{self.size:04x} version=0x{self.version:04x} signature=0x{self.signature:08x}>"
890
-
891
636
 
892
- class EXTENSION_BLOCK_BEEF0004(EXTENSION_BLOCK): # noqa
637
+ class EXTENSION_BLOCK_BEEF0004(EXTENSION_BLOCK):
893
638
  def __init__(self, buf):
894
639
  super().__init__(buf)
895
640
  fh = self.fh
@@ -923,8 +668,269 @@ class EXTENSION_BLOCK_BEEF0004(EXTENSION_BLOCK): # noqa
923
668
  self.localized_name = c_bag.wchar[None](fh)
924
669
 
925
670
 
926
- class EXTENSION_BLOCK_BEEF0005(EXTENSION_BLOCK): # noqa
671
+ class EXTENSION_BLOCK_BEEF0005(EXTENSION_BLOCK):
927
672
  def __init__(self, buf):
928
673
  super().__init__(buf)
929
674
  c_bag.char[16](self.fh) # GUID?
930
675
  self.shell_items = self.fh.read(self.data_size - 18)
676
+
677
+
678
+ ShellBagRecord = create_extended_descriptor([RegistryRecordDescriptorExtension, UserRecordDescriptorExtension])(
679
+ "windows/shellbag",
680
+ [
681
+ ("datetime", "ts_mtime"),
682
+ ("datetime", "ts_atime"),
683
+ ("datetime", "ts_btime"),
684
+ ("string", "type"),
685
+ ("path", "path"),
686
+ ("datetime", "regf_mtime"),
687
+ ],
688
+ )
689
+
690
+
691
+ class ShellBagsPlugin(Plugin):
692
+ """Windows Shellbags plugin."""
693
+
694
+ KEYS = [
695
+ "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell",
696
+ "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\ShellNoRoam",
697
+ "HKEY_CURRENT_USER\\Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\Shell",
698
+ "HKEY_CURRENT_USER\\Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\ShellNoRoam",
699
+ "HKEY_CURRENT_USER\\Software\\Classes\\Wow6432Node\\Local Settings\\Software\\Microsoft\\Windows\\Shell",
700
+ "HKEY_CURRENT_USER\\Software\\Classes\\Wow6432Node\\Local Settings\\Software\\Microsoft\\Windows\\ShellNoRoam",
701
+ "HKEY_CURRENT_USER\\Local Settings\\Software\\Microsoft\\Windows\\Shell\\BagMRU",
702
+ ]
703
+
704
+ def __init__(self, target: Target):
705
+ super().__init__(target)
706
+ self.bagkeys: list[RegistryKey] = list(self.target.registry.keys(self.KEYS))
707
+
708
+ def check_compatible(self) -> None:
709
+ if not self.bagkeys:
710
+ raise UnsupportedPluginError("No shellbags found")
711
+
712
+ @export(record=ShellBagRecord)
713
+ def shellbags(self) -> Iterator[ShellBagRecord]:
714
+ """Yields Windows Shellbags.
715
+
716
+ Shellbags are registry keys to improve user experience when using Windows Explorer.
717
+ They contain information such as file and folder creation time and access time.
718
+
719
+ References:
720
+ - https://github.com/libyal/libfwsi
721
+ - https://www.giac.org/paper/gcfa/9576/windows-shellbag-forensics-in-depth/128522
722
+ - https://www.hackingarticles.in/forensic-investigation-shellbags/
723
+ """
724
+ for regkey in self.bagkeys:
725
+ try:
726
+ yield from self._walk_bags(regkey.subkey("BagMRU"), None)
727
+
728
+ except RegistryKeyNotFoundError:
729
+ continue
730
+
731
+ except Exception as e:
732
+ self.target.log.error("Exception while parsing shellbags")
733
+ self.target.log.debug("", exc_info=e)
734
+ continue
735
+
736
+ def _walk_bags(self, key: RegistryKey, path_prefix: str | None) -> Iterator[ShellBagRecord]:
737
+ """Recursively walk shellbags from the given RegistryKey location."""
738
+ path_prefix = [] if path_prefix is None else [path_prefix]
739
+ user = self.target.registry.get_user(key)
740
+
741
+ for reg_val in key.values():
742
+ name, value = reg_val.name, reg_val.value
743
+ if not name.isdigit():
744
+ continue
745
+ path = None
746
+
747
+ for item in parse_shell_item_list(value):
748
+ path = "\\".join(path_prefix + [item.name])
749
+ yield ShellBagRecord(
750
+ ts_mtime=item.modification_time,
751
+ ts_atime=item.access_time,
752
+ ts_btime=item.creation_time,
753
+ type=item.__class__.__name__,
754
+ path=windows_path(path),
755
+ regf_mtime=key.ts,
756
+ _key=key,
757
+ _user=user,
758
+ _target=self.target,
759
+ )
760
+
761
+ yield from self._walk_bags(key.subkey(name), path)
762
+
763
+
764
+ def parse_shell_item_list(buf: bytes) -> Iterator[SHITEM]:
765
+ """Parse a shellbag item from the given bytes."""
766
+
767
+ offset = 0
768
+ end = len(buf)
769
+ list_buf = memoryview(buf)
770
+ parent = None
771
+
772
+ while offset < end:
773
+ size = c_bag.uint16(list_buf[offset : offset + 2])
774
+
775
+ if size == 0:
776
+ break
777
+
778
+ item_buf = list_buf[offset : offset + size]
779
+
780
+ entry = None
781
+ if size >= 8:
782
+ signature = c_bag.uint32(item_buf[4:8])
783
+ if signature == 0x39DE2184:
784
+ entry = CONTROL_PANEL_CATEGORY
785
+ elif signature == 0x4D677541:
786
+ entry = CDBURN
787
+ elif signature == 0x49534647:
788
+ entry = GAME_FOLDER
789
+ elif signature == 0xFFFFFF38:
790
+ entry = CONTROL_PANEL_CPL_FILE
791
+
792
+ if size >= 10 and not entry:
793
+ signature = c_bag.uint32(item_buf[6:10])
794
+ if signature == 0x07192006:
795
+ entry = MTP_FILE_ENTRY
796
+ elif signature == 0x10312005:
797
+ entry = MTP_VOLUME
798
+ elif signature in (0x10141981, 0x23A3DFD5, 0x23FEBBEE, 0x3B93AFBB, 0xBEEBEE00):
799
+ entry = USERS_PROPERTY_VIEW
800
+ elif signature == 0x46534643:
801
+ entry = UNKNOWN_0x74
802
+
803
+ if size >= 38 and not entry:
804
+ if item_buf[size - 32 : size] == DELEGATE_ITEM_IDENTIFIER:
805
+ entry = DELEGATE
806
+
807
+ if size >= 3 and not entry:
808
+ class_type = item_buf[2]
809
+ mask_type = class_type & 0x70
810
+
811
+ if mask_type == 0x00:
812
+ if class_type == 0x00:
813
+ entry = UNKNOWN0
814
+ elif class_type == 0x01:
815
+ entry = UNKNOWN1
816
+
817
+ elif mask_type == 0x10:
818
+ if class_type == 0x1F:
819
+ entry = ROOT_FOLDER
820
+
821
+ elif mask_type == 0x20:
822
+ if class_type in (0x23, 0x25, 0x29, 0x2A, 0x2E, 0x2F):
823
+ entry = VOLUME
824
+
825
+ elif mask_type == 0x30:
826
+ if class_type in (0x30, 0x31, 0x32, 0x35, 0x36, 0xB1):
827
+ entry = FILE_ENTRY
828
+
829
+ elif mask_type == 0x40:
830
+ if class_type in (0x41, 0x42, 0x46, 0x47, 0x4C, 0xC3):
831
+ entry = NETWORK
832
+
833
+ elif mask_type == 0x50:
834
+ if class_type == 0x52:
835
+ entry = COMPRESSED_FOLDER
836
+
837
+ elif mask_type == 0x60:
838
+ if class_type == 0x61:
839
+ entry = URI
840
+
841
+ elif mask_type == 0x70:
842
+ if class_type == 0x71:
843
+ entry = CONTROL_PANEL
844
+ else:
845
+ if not entry:
846
+ log.debug("No supported shell item found for size 0x%04x and type 0x%02x", size, class_type)
847
+ entry = UNKNOWN
848
+
849
+ if not entry:
850
+ log.debug("No supported shell item found for size 0x%04x", size)
851
+ entry = UNKNOWN
852
+
853
+ entry = entry(item_buf)
854
+ entry.parent = parent
855
+
856
+ first_extension_block_offset = c_bag.uint16(item_buf[-2:])
857
+ if 4 <= first_extension_block_offset < size - 2:
858
+ extension_offset = first_extension_block_offset
859
+ while extension_offset < size - 2:
860
+ extension_size = c_bag.uint16(item_buf[extension_offset : extension_offset + 2])
861
+
862
+ if extension_size == 0:
863
+ break
864
+
865
+ if extension_size > size - extension_offset:
866
+ log.debug(
867
+ "Extension size exceeds item size: 0x%04x > 0x%04x - 0x%04x",
868
+ extension_size,
869
+ size,
870
+ extension_offset,
871
+ )
872
+ break # Extension size too large
873
+
874
+ extension_buf = item_buf[extension_offset : extension_offset + extension_size]
875
+ extension_signature = c_bag.uint32(extension_buf[4:8])
876
+
877
+ ext = None
878
+
879
+ if extension_signature >> 16 != 0xBEEF:
880
+ log.debug("Got unsupported extension signature 0x%08x from item %r", extension_signature, entry)
881
+ pass # Unsupported
882
+
883
+ elif extension_signature == 0xBEEF0000:
884
+ pass
885
+
886
+ elif extension_signature == 0xBEEF0001:
887
+ pass
888
+
889
+ elif extension_signature == 0xBEEF0003:
890
+ ext = EXTENSION_BLOCK_BEEF0004
891
+
892
+ elif extension_signature == 0xBEEF0004:
893
+ ext = EXTENSION_BLOCK_BEEF0004
894
+
895
+ elif extension_signature == 0xBEEF0005:
896
+ ext = EXTENSION_BLOCK_BEEF0005
897
+
898
+ elif extension_signature == 0xBEEF0006:
899
+ pass
900
+
901
+ elif extension_signature == 0xBEEF000A:
902
+ pass
903
+
904
+ elif extension_signature == 0xBEEF0013:
905
+ pass
906
+
907
+ elif extension_signature == 0xBEEF0014:
908
+ pass
909
+
910
+ elif extension_signature == 0xBEEF0019:
911
+ pass
912
+
913
+ elif extension_signature == 0xBEEF0025:
914
+ pass
915
+
916
+ elif extension_signature == 0xBEEF0026:
917
+ pass
918
+
919
+ else:
920
+ log.debug(
921
+ "Got unsupported beef extension signature 0x%08x from item %r", extension_signature, entry
922
+ )
923
+ pass
924
+
925
+ if ext is None:
926
+ ext = EXTENSION_BLOCK
927
+ log.debug("Unimplemented extension signature 0x%08x from item %r", extension_signature, entry)
928
+
929
+ ext = ext(extension_buf)
930
+
931
+ entry.extensions.append(ext)
932
+ extension_offset += extension_size
933
+
934
+ parent = entry
935
+ offset += size
936
+ yield entry
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.20.dev23
3
+ Version: 3.20.dev26
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
@@ -23,7 +23,7 @@ dissect/target/containers/vmdk.py,sha256=5fQGkJy4esXONXrKLbhpkQDt8Fwx19YENK2mOm7
23
23
  dissect/target/data/autocompletion/target_bash_completion.sh,sha256=wrOQ_ED-h8WFcjCmY6n4qKl84tWJv9l8ShFHDfJqJyA,3592
24
24
  dissect/target/filesystems/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  dissect/target/filesystems/ad1.py,sha256=nEPzaaRsb6bL4ItFo0uLdmdLvrmK9BjqHeD3FOp3WQI,2413
26
- dissect/target/filesystems/btrfs.py,sha256=X-eMlKkx88xS2iIWRuq9yaQ82fl0DM435F1sdXkDo8g,6469
26
+ dissect/target/filesystems/btrfs.py,sha256=TotOs0-VOmgSijERZb1pOrIH_E7B1J_DRKqws8ttQTk,6569
27
27
  dissect/target/filesystems/cb.py,sha256=6LcoJiwsYu1Han31IUzVpZVDTifhTLTx_gLfNpB_p6k,5329
28
28
  dissect/target/filesystems/config.py,sha256=GQOtixIIt-Jjtpl3IVqUTujcBFfWaAZeAtvxgNUNetU,11963
29
29
  dissect/target/filesystems/cpio.py,sha256=ssVCjkAtLn2FqmNxeo6U5boyUdSYFxLWfXpytHYGPqs,641
@@ -34,7 +34,7 @@ dissect/target/filesystems/fat.py,sha256=ZSw-wS57vo5eIXJndfI1rZkGu_qh-vyioMzCZFZ
34
34
  dissect/target/filesystems/ffs.py,sha256=Wu8sS1jjmD0QXXcAaD2h_zzfvinjco8qvj0hErufZ-4,4555
35
35
  dissect/target/filesystems/itunes.py,sha256=w2lcWv6jlBPm84tsGZehxKBMXXyuW3KlmwVTF4ssQec,6395
36
36
  dissect/target/filesystems/jffs.py,sha256=Ceqa5Em2pepnXMH_XZFmSNjQyWPo1uWTthBFSMWfKRo,3926
37
- dissect/target/filesystems/ntfs.py,sha256=fGgCKjdO5GrPC21DDr0SwIxmwR7KruNIqGUzysboirA,7068
37
+ dissect/target/filesystems/ntfs.py,sha256=Losf35q9aLm-YdwVllT5so99s-GqTF1ZXMbLX0PUNC0,7624
38
38
  dissect/target/filesystems/overlay.py,sha256=d0BNZcVd3SzBcM1SZO5nX2FrEYcdtVH34BPJQ6Oh4x8,4753
39
39
  dissect/target/filesystems/smb.py,sha256=uxfcOWwEoDCw8Qpsa94T5Pn-SKd4WXs4OOrzVVI55d8,6406
40
40
  dissect/target/filesystems/squashfs.py,sha256=ehzlThXB7n96XUvQnsK5tWLsA9HIxYN-Zxl7aO9D7ts,3921
@@ -334,7 +334,7 @@ dissect/target/plugins/os/windows/regf/nethist.py,sha256=QHbG9fmZNmjSVhrgqMvMo12
334
334
  dissect/target/plugins/os/windows/regf/recentfilecache.py,sha256=goS6ajLIh6ZU-Gq4tupoxBoQCfMDp2qJgg-Nn5qFIsY,1850
335
335
  dissect/target/plugins/os/windows/regf/regf.py,sha256=D1GrljF-sV8cWIjWJ3zH7k52i1OWD8poEC_PIeZMEis,3419
336
336
  dissect/target/plugins/os/windows/regf/runkeys.py,sha256=-2HcdnVytzCt1xwgAI8rHDnwk8kwLPWURumvhrGnIHU,4278
337
- dissect/target/plugins/os/windows/regf/shellbags.py,sha256=hXAqThFkHmGPmhNRSXwMNzw25kAyIC6OOZivgpPEwTQ,25679
337
+ dissect/target/plugins/os/windows/regf/shellbags.py,sha256=-8WkdplG0FR37XgpCTd4iDdQvvrgtOk9kZY5qLsW5J8,26984
338
338
  dissect/target/plugins/os/windows/regf/shimcache.py,sha256=TY7GEFnxb8h99q12CzM0SwVlUymi4hFPae3uuM0M6kY,9998
339
339
  dissect/target/plugins/os/windows/regf/trusteddocs.py,sha256=3yvpBDM-Asg0rvGN2TwALGRm9DYogG6TxRau9D6FBbw,3700
340
340
  dissect/target/plugins/os/windows/regf/usb.py,sha256=nSAHB4Cdd0wF2C1EK_XYOfWCyqOgTZCLfDhuSmr7rdM,9709
@@ -368,10 +368,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
368
368
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
369
369
  dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
370
370
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
371
- dissect.target-3.20.dev23.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
372
- dissect.target-3.20.dev23.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
373
- dissect.target-3.20.dev23.dist-info/METADATA,sha256=dGuBoIpEp_hKqGr5hAlD6pnyc70LxqqzpOG40g_EeLE,12897
374
- dissect.target-3.20.dev23.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
375
- dissect.target-3.20.dev23.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
376
- dissect.target-3.20.dev23.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
377
- dissect.target-3.20.dev23.dist-info/RECORD,,
371
+ dissect.target-3.20.dev26.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
372
+ dissect.target-3.20.dev26.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
373
+ dissect.target-3.20.dev26.dist-info/METADATA,sha256=9ta9bS0OqAMhf6vc8zDqGxpXSkY-YRJMAHWRucXo3f8,12897
374
+ dissect.target-3.20.dev26.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
375
+ dissect.target-3.20.dev26.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
376
+ dissect.target-3.20.dev26.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
377
+ dissect.target-3.20.dev26.dist-info/RECORD,,