dissect.target 3.16.dev23__py3-none-any.whl → 3.16.dev25__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.
- dissect/target/filesystem.py +2 -0
- dissect/target/filesystems/cpio.py +18 -0
- dissect/target/helpers/fsutil.py +37 -6
- dissect/target/plugin.py +2 -0
- dissect/target/plugins/os/unix/locate/__init__.py +0 -0
- dissect/target/plugins/os/unix/locate/gnulocate.py +92 -0
- dissect/target/plugins/os/unix/locate/locate.py +5 -0
- dissect/target/plugins/os/unix/locate/mlocate.py +150 -0
- dissect/target/plugins/os/unix/locate/plocate.py +189 -0
- {dissect.target-3.16.dev23.dist-info → dissect.target-3.16.dev25.dist-info}/METADATA +1 -1
- {dissect.target-3.16.dev23.dist-info → dissect.target-3.16.dev25.dist-info}/RECORD +16 -10
- {dissect.target-3.16.dev23.dist-info → dissect.target-3.16.dev25.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.16.dev23.dist-info → dissect.target-3.16.dev25.dist-info}/LICENSE +0 -0
- {dissect.target-3.16.dev23.dist-info → dissect.target-3.16.dev25.dist-info}/WHEEL +0 -0
- {dissect.target-3.16.dev23.dist-info → dissect.target-3.16.dev25.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.16.dev23.dist-info → dissect.target-3.16.dev25.dist-info}/top_level.txt +0 -0
dissect/target/filesystem.py
CHANGED
@@ -1588,5 +1588,7 @@ register("btrfs", "BtrfsFilesystem")
|
|
1588
1588
|
register("exfat", "ExfatFilesystem")
|
1589
1589
|
register("squashfs", "SquashFSFilesystem")
|
1590
1590
|
register("zip", "ZipFilesystem")
|
1591
|
+
register("tar", "TarFilesystem")
|
1592
|
+
register("cpio", "CpioFilesystem")
|
1591
1593
|
register("ad1", "AD1Filesystem")
|
1592
1594
|
register("jffs", "JFFSFilesystem")
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from typing import BinaryIO, Optional
|
2
|
+
|
3
|
+
from dissect.util import cpio
|
4
|
+
|
5
|
+
from dissect.target.filesystems.tar import TarFilesystem
|
6
|
+
from dissect.target.helpers.fsutil import open_decompress
|
7
|
+
|
8
|
+
|
9
|
+
class CpioFilesystem(TarFilesystem):
|
10
|
+
__type__ = "cpio"
|
11
|
+
|
12
|
+
def __init__(self, fh: BinaryIO, base: Optional[str] = None, *args, **kwargs):
|
13
|
+
super().__init__(open_decompress(fileobj=fh), base, tarinfo=cpio.CpioInfo, *args, **kwargs)
|
14
|
+
|
15
|
+
@staticmethod
|
16
|
+
def _detect(fh: BinaryIO) -> bool:
|
17
|
+
"""Detect a cpio file on a given file-like object."""
|
18
|
+
return cpio.detect_header(open_decompress(fileobj=fh)) != cpio.FORMAT_CPIO_UNKNOWN
|
dissect/target/helpers/fsutil.py
CHANGED
@@ -20,6 +20,13 @@ try:
|
|
20
20
|
except ImportError:
|
21
21
|
HAVE_BZ2 = False
|
22
22
|
|
23
|
+
try:
|
24
|
+
import zstandard
|
25
|
+
|
26
|
+
HAVE_ZSTD = True
|
27
|
+
except ImportError:
|
28
|
+
HAVE_ZSTD = False
|
29
|
+
|
23
30
|
import dissect.target.filesystem as filesystem
|
24
31
|
from dissect.target.exceptions import FileNotFoundError, SymlinkRecursionError
|
25
32
|
from dissect.target.helpers.polypath import (
|
@@ -445,17 +452,22 @@ def resolve_link(
|
|
445
452
|
|
446
453
|
|
447
454
|
def open_decompress(
|
448
|
-
path: TargetPath,
|
455
|
+
path: Optional[TargetPath] = None,
|
449
456
|
mode: str = "rb",
|
457
|
+
*,
|
458
|
+
fileobj: Optional[BinaryIO] = None,
|
450
459
|
encoding: Optional[str] = "UTF-8",
|
451
460
|
errors: Optional[str] = "backslashreplace",
|
452
461
|
newline: Optional[str] = None,
|
453
462
|
) -> Union[BinaryIO, TextIO]:
|
454
|
-
"""Open and decompress a file. Handles gz and
|
463
|
+
"""Open and decompress a file. Handles gz, bz2 and zstd files. Uncompressed files are opened as-is.
|
464
|
+
|
465
|
+
When passing in an already opened ``fileobj``, the mode, encoding, errors and newline arguments are ignored.
|
455
466
|
|
456
467
|
Args:
|
457
468
|
path: The path to the file to open and decompress. It is assumed this path exists.
|
458
469
|
mode: The mode in which to open the file.
|
470
|
+
fileobj: The file-like object to open and decompress. This is mutually exclusive with path.
|
459
471
|
encoding: The decoding for text streams. By default UTF-8 encoding is used.
|
460
472
|
errors: The error handling for text streams. By default we're more lenient and use ``backslashreplace``.
|
461
473
|
newline: How newlines are handled for text streams.
|
@@ -469,7 +481,17 @@ def open_decompress(
|
|
469
481
|
for line in open_decompress(Path("/dir/file.gz"), "rt"):
|
470
482
|
print(line)
|
471
483
|
"""
|
472
|
-
|
484
|
+
if path and fileobj:
|
485
|
+
raise ValueError("path and fileobj are mutually exclusive")
|
486
|
+
|
487
|
+
if not path and not fileobj:
|
488
|
+
raise ValueError("path or fileobj is required")
|
489
|
+
|
490
|
+
if path:
|
491
|
+
file = path.open("rb")
|
492
|
+
else:
|
493
|
+
file = fileobj
|
494
|
+
|
473
495
|
magic = file.read(4)
|
474
496
|
file.seek(0)
|
475
497
|
|
@@ -480,13 +502,22 @@ def open_decompress(
|
|
480
502
|
|
481
503
|
if magic[:2] == b"\x1f\x8b":
|
482
504
|
return gzip.open(file, mode, encoding=encoding, errors=errors, newline=newline)
|
483
|
-
|
484
|
-
|
505
|
+
|
506
|
+
if HAVE_BZ2 and magic[:3] == b"BZh" and 0x31 <= magic[3] <= 0x39:
|
507
|
+
# In a valid bz2 header the 4th byte is in the range b'1' ... b'9'.
|
485
508
|
return bz2.open(file, mode, encoding=encoding, errors=errors, newline=newline)
|
486
|
-
|
509
|
+
|
510
|
+
if HAVE_ZSTD and magic[:4] in [b"\xfd\x2f\xb5\x28", b"\x28\xb5\x2f\xfd"]:
|
511
|
+
# stream_reader is not seekable, so we have to resort to the less
|
512
|
+
# efficient decompressor which returns bytes.
|
513
|
+
return io.BytesIO(zstandard.decompress(file.read()))
|
514
|
+
|
515
|
+
if path:
|
487
516
|
file.close()
|
488
517
|
return path.open(mode, encoding=encoding, errors=errors, newline=newline)
|
489
518
|
|
519
|
+
return file
|
520
|
+
|
490
521
|
|
491
522
|
def reverse_readlines(fh: TextIO, chunk_size: int = 1024 * 1024 * 8) -> Iterator[str]:
|
492
523
|
"""Like iterating over a ``TextIO`` file-like object, but starting from the end of the file.
|
dissect/target/plugin.py
CHANGED
@@ -932,6 +932,8 @@ class NamespacePlugin(Plugin):
|
|
932
932
|
try:
|
933
933
|
subplugin = getattr(self.target, entry)
|
934
934
|
self._subplugins.append(subplugin)
|
935
|
+
except UnsupportedPluginError:
|
936
|
+
target.log.warning("Subplugin %s is not compatible with target.", entry)
|
935
937
|
except Exception:
|
936
938
|
target.log.exception("Failed to load subplugin: %s", entry)
|
937
939
|
|
File without changes
|
@@ -0,0 +1,92 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import BinaryIO, Iterable
|
4
|
+
|
5
|
+
from dissect.cstruct import cstruct
|
6
|
+
|
7
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
8
|
+
from dissect.target.helpers.record import TargetRecordDescriptor
|
9
|
+
from dissect.target.plugin import export
|
10
|
+
from dissect.target.plugins.os.unix.locate.locate import BaseLocatePlugin
|
11
|
+
|
12
|
+
gnulocate_def = """
|
13
|
+
#define MAGIC 0x004c4f43415445303200 /* b'/x00LOCATE02/x00' */
|
14
|
+
|
15
|
+
struct entry {
|
16
|
+
int8 offset;
|
17
|
+
char path[];
|
18
|
+
}
|
19
|
+
"""
|
20
|
+
|
21
|
+
GNULocateRecord = TargetRecordDescriptor(
|
22
|
+
"linux/locate/gnulocate",
|
23
|
+
[
|
24
|
+
("path", "path"),
|
25
|
+
("string", "source"),
|
26
|
+
],
|
27
|
+
)
|
28
|
+
|
29
|
+
c_gnulocate = cstruct()
|
30
|
+
c_gnulocate.load(gnulocate_def)
|
31
|
+
|
32
|
+
|
33
|
+
class GNULocateFile:
|
34
|
+
"""locate file parser
|
35
|
+
|
36
|
+
Multiple formats exist for the locatedb file. This class only supports the most recent version ``LOCATE02``.
|
37
|
+
|
38
|
+
The file is encoded with front compression (incremental encoding). This is a form of compression
|
39
|
+
which takes a number of characters of the previous encoded entries. Entries are separated with a null byte.
|
40
|
+
|
41
|
+
Resources:
|
42
|
+
- https://manpages.ubuntu.com/manpages/trusty/en/man5/locatedb.5.html
|
43
|
+
"""
|
44
|
+
|
45
|
+
def __init__(self, fh: BinaryIO):
|
46
|
+
self.fh = fh
|
47
|
+
self.count = 0
|
48
|
+
self.previous_path = ""
|
49
|
+
|
50
|
+
magic = int.from_bytes(self.fh.read(10), byteorder="big")
|
51
|
+
if magic != c_gnulocate.MAGIC:
|
52
|
+
raise ValueError(f"Invalid Locate file magic. Expected /x00LOCATE02/x00, got {magic}")
|
53
|
+
|
54
|
+
def __iter__(self) -> Iterable[GNULocateFile]:
|
55
|
+
try:
|
56
|
+
while True:
|
57
|
+
# NOTE: The offset could be negative, which indicates
|
58
|
+
# that we decrease the number of characters of the previous path.
|
59
|
+
entry = c_gnulocate.entry(self.fh)
|
60
|
+
current_filepath_end = entry.path.decode(errors="backslashreplace")
|
61
|
+
offset = entry.offset
|
62
|
+
|
63
|
+
self.count += offset
|
64
|
+
|
65
|
+
path = self.previous_path[0 : self.count] + current_filepath_end
|
66
|
+
self.previous_path = path
|
67
|
+
yield path
|
68
|
+
except EOFError:
|
69
|
+
return
|
70
|
+
|
71
|
+
|
72
|
+
class GNULocatePlugin(BaseLocatePlugin):
|
73
|
+
__namespace__ = "gnulocate"
|
74
|
+
|
75
|
+
path = "/var/cache/locate/locatedb"
|
76
|
+
|
77
|
+
def check_compatible(self) -> None:
|
78
|
+
if not self.target.fs.path(self.path).exists():
|
79
|
+
raise UnsupportedPluginError(f"No locatedb file found at {self.path}")
|
80
|
+
|
81
|
+
@export(record=GNULocateRecord)
|
82
|
+
def locate(self) -> GNULocateRecord:
|
83
|
+
"""Yield file and directory names from GNU findutils' locatedb file.
|
84
|
+
|
85
|
+
Resources:
|
86
|
+
- https://manpages.debian.org/testing/locate/locatedb.5.en.html
|
87
|
+
"""
|
88
|
+
locate_fh = self.target.fs.path(self.path).open()
|
89
|
+
locate_file = GNULocateFile(locate_fh)
|
90
|
+
|
91
|
+
for path in locate_file:
|
92
|
+
yield GNULocateRecord(path=self.target.fs.path(path), source=self.path)
|
@@ -0,0 +1,150 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from datetime import datetime
|
5
|
+
from typing import BinaryIO, Iterable, Iterator
|
6
|
+
|
7
|
+
from dissect.cstruct import cstruct
|
8
|
+
from dissect.util.ts import from_unix
|
9
|
+
|
10
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
11
|
+
from dissect.target.helpers.record import TargetRecordDescriptor
|
12
|
+
from dissect.target.plugin import export
|
13
|
+
from dissect.target.plugins.os.unix.locate.locate import BaseLocatePlugin
|
14
|
+
|
15
|
+
# Resources: https://linux.die.net/man/5/locate.db
|
16
|
+
mlocate_def = """
|
17
|
+
#define MAGIC 0x006d6c6f63617465 /* b'/x00mlocate' */
|
18
|
+
|
19
|
+
struct header_config {
|
20
|
+
int32 conf_size;
|
21
|
+
int8 version; /* file format version */
|
22
|
+
int8 require_visibility;
|
23
|
+
int8 pad[2]; /* 32-bit total alignment */
|
24
|
+
char root_database;
|
25
|
+
char config_block[conf_size];
|
26
|
+
int8 pad;
|
27
|
+
};
|
28
|
+
|
29
|
+
enum DBE_TYPE: uint8 { /* database entry type */
|
30
|
+
FILE = 0x0, /* file */
|
31
|
+
DIRECTORY = 0x1, /* directory */
|
32
|
+
END = 0x2 /* end of directory */
|
33
|
+
};
|
34
|
+
|
35
|
+
struct directory_entry {
|
36
|
+
/* time is the 'maximum of st_ctime and
|
37
|
+
st_mtime of the directory' according to docs */
|
38
|
+
int64 time_seconds;
|
39
|
+
int32 time_nanoseconds;
|
40
|
+
int32 padding;
|
41
|
+
char path[];
|
42
|
+
};
|
43
|
+
|
44
|
+
struct entry {
|
45
|
+
char path[];
|
46
|
+
};
|
47
|
+
"""
|
48
|
+
|
49
|
+
|
50
|
+
@dataclass
|
51
|
+
class MLocate:
|
52
|
+
ts: datetime
|
53
|
+
ts_ns: int
|
54
|
+
parent: str
|
55
|
+
path: str
|
56
|
+
dbe_type: str
|
57
|
+
|
58
|
+
|
59
|
+
MLocateRecord = TargetRecordDescriptor(
|
60
|
+
"linux/locate/mlocate",
|
61
|
+
[
|
62
|
+
("datetime", "ts"),
|
63
|
+
("varint", "ts_ns"),
|
64
|
+
("path", "parent"),
|
65
|
+
("path", "path"),
|
66
|
+
("string", "type"),
|
67
|
+
("string", "source"),
|
68
|
+
],
|
69
|
+
)
|
70
|
+
|
71
|
+
c_mlocate = cstruct(endian=">")
|
72
|
+
c_mlocate.load(mlocate_def)
|
73
|
+
|
74
|
+
|
75
|
+
class MLocateFile:
|
76
|
+
"""mlocate file parser
|
77
|
+
|
78
|
+
Resources:
|
79
|
+
- https://manpages.debian.org/testing/mlocate/mlocate.db.5.en.html
|
80
|
+
"""
|
81
|
+
|
82
|
+
def __init__(self, fh: BinaryIO):
|
83
|
+
self.fh = fh
|
84
|
+
|
85
|
+
magic = int.from_bytes(self.fh.read(8), byteorder="big")
|
86
|
+
if magic != c_mlocate.MAGIC:
|
87
|
+
raise ValueError(f"Invalid mlocate file magic. Expected b'x00mlocate', got {magic}")
|
88
|
+
|
89
|
+
self.header = c_mlocate.header_config(self.fh)
|
90
|
+
|
91
|
+
def _parse_directory_entries(self) -> Iterator[str, c_mlocate.entry]:
|
92
|
+
while (dbe_type := c_mlocate.DBE_TYPE(self.fh)) != c_mlocate.DBE_TYPE.END:
|
93
|
+
entry = c_mlocate.entry(self.fh)
|
94
|
+
dbe_type = "file" if dbe_type == c_mlocate.DBE_TYPE.FILE else "directory"
|
95
|
+
|
96
|
+
yield dbe_type, entry
|
97
|
+
|
98
|
+
def __iter__(self) -> Iterable[MLocateFile]:
|
99
|
+
while True:
|
100
|
+
try:
|
101
|
+
directory_entry = c_mlocate.directory_entry(self.fh)
|
102
|
+
parent = directory_entry.path.decode()
|
103
|
+
|
104
|
+
for dbe_type, file_entry in self._parse_directory_entries():
|
105
|
+
file_path = file_entry.path.decode()
|
106
|
+
|
107
|
+
yield MLocate(
|
108
|
+
ts=from_unix(directory_entry.time_seconds),
|
109
|
+
ts_ns=directory_entry.time_nanoseconds,
|
110
|
+
parent=parent,
|
111
|
+
path=file_path,
|
112
|
+
dbe_type=dbe_type,
|
113
|
+
)
|
114
|
+
except EOFError:
|
115
|
+
return
|
116
|
+
|
117
|
+
|
118
|
+
class MLocatePlugin(BaseLocatePlugin):
|
119
|
+
__namespace__ = "mlocate"
|
120
|
+
|
121
|
+
path = "/var/lib/mlocate/mlocate.db"
|
122
|
+
|
123
|
+
def check_compatible(self) -> None:
|
124
|
+
if not self.target.fs.path(self.path).exists():
|
125
|
+
raise UnsupportedPluginError(f"No mlocate.db file found at {self.path}")
|
126
|
+
|
127
|
+
@export(record=MLocateRecord)
|
128
|
+
def locate(self) -> Iterator[MLocateRecord]:
|
129
|
+
"""Yield file and directory names from mlocate.db file.
|
130
|
+
|
131
|
+
``mlocate`` is a new implementation of GNU locate,
|
132
|
+
but has been deprecated since Ubuntu 22.
|
133
|
+
|
134
|
+
Resources:
|
135
|
+
- https://manpages.debian.org/testing/mlocate/mlocate.db.5.en.html
|
136
|
+
"""
|
137
|
+
mlocate_fh = self.target.fs.path(self.path).open()
|
138
|
+
mlocate_file = MLocateFile(mlocate_fh)
|
139
|
+
|
140
|
+
for item in mlocate_file:
|
141
|
+
parent = self.target.fs.path(item.parent)
|
142
|
+
yield MLocateRecord(
|
143
|
+
ts=item.ts,
|
144
|
+
ts_ns=item.ts_ns,
|
145
|
+
parent=parent,
|
146
|
+
path=parent.joinpath(item.path),
|
147
|
+
type=item.dbe_type,
|
148
|
+
source=self.path,
|
149
|
+
_target=self.target,
|
150
|
+
)
|
@@ -0,0 +1,189 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import platform
|
4
|
+
import sys
|
5
|
+
from io import BytesIO
|
6
|
+
from typing import BinaryIO, Iterable
|
7
|
+
|
8
|
+
from dissect.cstruct import cstruct
|
9
|
+
from dissect.util.stream import RangeStream
|
10
|
+
|
11
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
12
|
+
from dissect.target.helpers.record import TargetRecordDescriptor
|
13
|
+
from dissect.target.plugin import export
|
14
|
+
from dissect.target.plugins.os.unix.locate.locate import BaseLocatePlugin
|
15
|
+
|
16
|
+
try:
|
17
|
+
import zstandard # noqa
|
18
|
+
|
19
|
+
HAS_ZSTD = True
|
20
|
+
except ImportError:
|
21
|
+
HAS_ZSTD = False
|
22
|
+
|
23
|
+
# Resource: https://git.sesse.net/?p=plocate @ db.h
|
24
|
+
plocate_def = """
|
25
|
+
#define MAGIC 0x00706c6f63617465 /* b'/x00plocate' */
|
26
|
+
|
27
|
+
struct header {
|
28
|
+
uint32_t version;
|
29
|
+
uint32_t hashtable_size;
|
30
|
+
uint32_t extra_ht_slots;
|
31
|
+
uint32_t num_docids;
|
32
|
+
uint64_t hash_table_offset_bytes;
|
33
|
+
uint64_t filename_index_offset_bytes;
|
34
|
+
|
35
|
+
/* Version 1 and up only. */
|
36
|
+
uint32_t max_version;
|
37
|
+
uint32_t zstd_dictionary_length_bytes;
|
38
|
+
uint64_t zstd_dictionary_offset_bytes;
|
39
|
+
|
40
|
+
/* Only if max_version >= 2, and only relevant for updatedb. */
|
41
|
+
uint64_t directory_data_length_bytes;
|
42
|
+
uint64_t directory_data_offset_bytes;
|
43
|
+
uint64_t next_zstd_dictionary_length_bytes;
|
44
|
+
uint64_t next_zstd_dictionary_offset_bytes;
|
45
|
+
uint64_t conf_block_length_bytes;
|
46
|
+
uint64_t conf_block_offset_bytes;
|
47
|
+
|
48
|
+
uint8_t check_visibility;
|
49
|
+
char padding[7]; /* padding for alignment */
|
50
|
+
};
|
51
|
+
|
52
|
+
struct file {
|
53
|
+
char path[];
|
54
|
+
};
|
55
|
+
"""
|
56
|
+
|
57
|
+
PLocateRecord = TargetRecordDescriptor(
|
58
|
+
"linux/locate/plocate",
|
59
|
+
[
|
60
|
+
("path", "path"),
|
61
|
+
("path", "source"),
|
62
|
+
],
|
63
|
+
)
|
64
|
+
|
65
|
+
c_plocate = cstruct()
|
66
|
+
c_plocate.load(plocate_def)
|
67
|
+
|
68
|
+
|
69
|
+
class PLocateFile:
|
70
|
+
"""plocate file parser
|
71
|
+
|
72
|
+
The ``plocate.db`` file contains a hashtable and trigrams to enable quick lookups of filenames.
|
73
|
+
|
74
|
+
We've implemented a few methods to gather those for possible future use, but for the PLocatePlugin
|
75
|
+
we're only interested in the filepaths stored in the database. Hence we don't use these methods.
|
76
|
+
|
77
|
+
Roughly speaking, the plocate.db file has the following structure:
|
78
|
+
- ``header`` (0x70 bytes)
|
79
|
+
- zstd compressed ``filename``s (until start of ``filename_index_offset_bytes``),
|
80
|
+
possibly including a dictionary
|
81
|
+
- hashtables (offset and length in ``header``)
|
82
|
+
- directory data (offset and length in ``header``)
|
83
|
+
- possible zstd dictionary (offset and length in ``header``)
|
84
|
+
- configuration block (offset and length in ``header``)
|
85
|
+
|
86
|
+
No documentation other than the source code is available on the format of this file.
|
87
|
+
|
88
|
+
Resources:
|
89
|
+
- https://git.sesse.net/?p=plocate
|
90
|
+
"""
|
91
|
+
|
92
|
+
HEADER_SIZE = 0x70 # 0x8 bytes magic + 0x68 bytes header
|
93
|
+
NUM_OVERFLOW_SLOTS = 16
|
94
|
+
TRIGRAM_SIZE_BYTES = 16
|
95
|
+
DOCID_SIZE_BYTES = 8
|
96
|
+
|
97
|
+
def __init__(self, fh: BinaryIO):
|
98
|
+
self.fh = fh
|
99
|
+
|
100
|
+
magic = int.from_bytes(self.fh.read(8), byteorder="big")
|
101
|
+
if magic != c_plocate.MAGIC:
|
102
|
+
raise ValueError(f"Invalid plocate file magic. Expected b'/x00plocate', got {magic}")
|
103
|
+
|
104
|
+
self.header = c_plocate.header(self.fh)
|
105
|
+
self.dict_data = None
|
106
|
+
|
107
|
+
if self.header.zstd_dictionary_offset_bytes:
|
108
|
+
self.dict_data = zstandard.ZstdCompressionDict(self.fh.read(self.header.zstd_dictionary_length_bytes))
|
109
|
+
|
110
|
+
self.compressed_length_bytes = (
|
111
|
+
self.header.filename_index_offset_bytes - self.HEADER_SIZE - self.header.zstd_dictionary_length_bytes
|
112
|
+
)
|
113
|
+
self.ctx = zstandard.ZstdDecompressor(dict_data=self.dict_data)
|
114
|
+
self.buf = RangeStream(self.fh, self.fh.tell(), self.compressed_length_bytes)
|
115
|
+
|
116
|
+
def __iter__(self) -> Iterable[PLocateFile]:
|
117
|
+
# NOTE: This is a workaround for a PyPy 3.9 bug
|
118
|
+
# We don't know what breaks, but PyPy + zstandard = unhappy times
|
119
|
+
# You just get random garbage data back instead of the decompressed data
|
120
|
+
# This weird dance of using a decompressobj and unused data is the only way that seems to work
|
121
|
+
# It's more expensive on memory, but at least it doesn't break
|
122
|
+
if platform.python_implementation() == "PyPy" and sys.version_info < (3, 10):
|
123
|
+
obj = self.ctx.decompressobj()
|
124
|
+
buf = self.buf.read()
|
125
|
+
|
126
|
+
tmp = obj.decompress(buf)
|
127
|
+
while unused_data := obj.unused_data:
|
128
|
+
obj = self.ctx.decompressobj()
|
129
|
+
tmp += obj.decompress(unused_data)
|
130
|
+
|
131
|
+
reader = BytesIO(tmp)
|
132
|
+
else:
|
133
|
+
reader = self.ctx.stream_reader(self.buf)
|
134
|
+
|
135
|
+
with reader:
|
136
|
+
try:
|
137
|
+
while True:
|
138
|
+
file = c_plocate.file(reader)
|
139
|
+
yield file.path.decode(errors="surrogateescape")
|
140
|
+
except EOFError:
|
141
|
+
return
|
142
|
+
|
143
|
+
def filename_index(self) -> bytes:
|
144
|
+
"""Return the filename index of the plocate.db file."""
|
145
|
+
self.fh.seek(self.header.filename_index_offset_bytes)
|
146
|
+
num_docids = self.header.num_docids
|
147
|
+
filename_index_size = num_docids * self.DOCID_SIZE_BYTES
|
148
|
+
return self.fh.read(filename_index_size)
|
149
|
+
|
150
|
+
def hashtable(self) -> bytes:
|
151
|
+
"""Return the hashtable of the plocate.db file."""
|
152
|
+
self.fh.seek(self.header.hash_table_offset_bytes)
|
153
|
+
hashtable_size = (self.header.hashtable_size + self.NUM_OVERFLOW_SLOTS + 1) * self.TRIGRAM_SIZE_BYTES
|
154
|
+
return self.fh.read(hashtable_size)
|
155
|
+
|
156
|
+
|
157
|
+
class PLocatePlugin(BaseLocatePlugin):
|
158
|
+
__namespace__ = "plocate"
|
159
|
+
|
160
|
+
path = "/var/lib/plocate/plocate.db"
|
161
|
+
|
162
|
+
def check_compatible(self) -> None:
|
163
|
+
if not self.target.fs.path(self.path).exists():
|
164
|
+
raise UnsupportedPluginError(f"No plocate.db file found at {self.path}")
|
165
|
+
|
166
|
+
if not HAS_ZSTD:
|
167
|
+
raise UnsupportedPluginError(
|
168
|
+
"Please install `python-zstandard` or `pip install zstandard` to use the PLocatePlugin"
|
169
|
+
)
|
170
|
+
|
171
|
+
@export(record=PLocateRecord)
|
172
|
+
def locate(self) -> PLocateRecord:
|
173
|
+
"""Yield file and directory names from the plocate.db.
|
174
|
+
|
175
|
+
``plocate`` is the default package on Ubuntu 22 and newer to locate files.
|
176
|
+
It replaces ``mlocate`` and GNU ``locate``.
|
177
|
+
|
178
|
+
Resources:
|
179
|
+
- https://manpages.debian.org/testing/plocate/plocate.1.en.html
|
180
|
+
- https://git.sesse.net/?p=plocate
|
181
|
+
"""
|
182
|
+
plocate = self.target.fs.path(self.path)
|
183
|
+
plocate_file = PLocateFile(plocate.open())
|
184
|
+
|
185
|
+
for path in plocate_file:
|
186
|
+
yield PLocateRecord(
|
187
|
+
path=self.target.fs.path(path),
|
188
|
+
source=self.path,
|
189
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dissect.target
|
3
|
-
Version: 3.16.
|
3
|
+
Version: 3.16.dev25
|
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
|
@@ -1,9 +1,9 @@
|
|
1
1
|
dissect/target/__init__.py,sha256=Oc7ounTgq2hE4nR6YcNabetc7SQA40ldSa35VEdZcQU,63
|
2
2
|
dissect/target/container.py,sha256=9ixufT1_0WhraqttBWwQjG80caToJqvCX8VjFk8d5F0,9307
|
3
3
|
dissect/target/exceptions.py,sha256=VVW_Rq_vQinapz-2mbJ3UkxBEZpb2pE_7JlhMukdtrY,2877
|
4
|
-
dissect/target/filesystem.py,sha256=
|
4
|
+
dissect/target/filesystem.py,sha256=VuWp_nxaG5WI9xvhEjl0M6X37_sVdclzgt3pZvtZ3hE,53944
|
5
5
|
dissect/target/loader.py,sha256=0-LcZNi7S0qsXR7XGtrzxpuCh9BsLcqNR1T15O7SnBM,7257
|
6
|
-
dissect/target/plugin.py,sha256=
|
6
|
+
dissect/target/plugin.py,sha256=bwtTPASgJBUHBZ7Nr8eb5eXDOHMWfumduWj7loB0FP0,48605
|
7
7
|
dissect/target/report.py,sha256=06uiP4MbNI8cWMVrC1SasNS-Yg6ptjVjckwj8Yhe0Js,7958
|
8
8
|
dissect/target/target.py,sha256=xNJdecZSt2oHcZwf775kOSTFRA-c_hKoScXaDuK-8FI,32155
|
9
9
|
dissect/target/volume.py,sha256=aQZAJiny8jjwkc9UtwIRwy7nINXjCxwpO-_UDfh6-BA,15801
|
@@ -25,6 +25,7 @@ dissect/target/filesystems/ad1.py,sha256=nEPzaaRsb6bL4ItFo0uLdmdLvrmK9BjqHeD3FOp
|
|
25
25
|
dissect/target/filesystems/btrfs.py,sha256=5MBi193ZvclkEQcxDr_sDHfj_FYU_hyYNRL4YqpDu4M,6243
|
26
26
|
dissect/target/filesystems/cb.py,sha256=6LcoJiwsYu1Han31IUzVpZVDTifhTLTx_gLfNpB_p6k,5329
|
27
27
|
dissect/target/filesystems/config.py,sha256=C2JnzBzMqbAjchGFDwURItCeUY7uxkhw1Gen-6cGkAc,11432
|
28
|
+
dissect/target/filesystems/cpio.py,sha256=ssVCjkAtLn2FqmNxeo6U5boyUdSYFxLWfXpytHYGPqs,641
|
28
29
|
dissect/target/filesystems/dir.py,sha256=7GRvojL151_Vk9e3vqgZbWE3I8IL9bU6LUKc_xjk6D4,4050
|
29
30
|
dissect/target/filesystems/exfat.py,sha256=PRkZPUVN5NlgB1VetFtywdNgF6Yj5OBtF5a25t-fFvw,5917
|
30
31
|
dissect/target/filesystems/extfs.py,sha256=9Cke-H0CL-SPd3-xvdAgfc3YA5hYso0sq6hm0C9vGII,4640
|
@@ -46,7 +47,7 @@ dissect/target/helpers/configutil.py,sha256=t_UNvcWuMMT5C1tut_PgTwCnVUodf6RjhfXP
|
|
46
47
|
dissect/target/helpers/cyber.py,sha256=Ki5oSU0GgQxjgC_yWoeieGP7GOY5blQCzNX7vy7Pgas,16782
|
47
48
|
dissect/target/helpers/descriptor_extensions.py,sha256=uT8GwznfDAiIgMM7JKKOY0PXKMv2c0GCqJTCkWFgops,2605
|
48
49
|
dissect/target/helpers/docs.py,sha256=J5U65Y3yOTqxDEZRCdrEmO63XQCeDzOJea1PwPM6Cyc,5146
|
49
|
-
dissect/target/helpers/fsutil.py,sha256=
|
50
|
+
dissect/target/helpers/fsutil.py,sha256=NKwpAq_NlbFDvGGV9ahC5flPda_5jUBDqtF-Ch5ASDs,19543
|
50
51
|
dissect/target/helpers/hashutil.py,sha256=SD24rcV_y0sBEl7M9T-isjm-VzJvCiTN2BoWMqAOAVI,2160
|
51
52
|
dissect/target/helpers/keychain.py,sha256=wYH0sf7eaxP0bZTo80RF_BQMWulCWmIQ8Tzt9K5TSNQ,3611
|
52
53
|
dissect/target/helpers/lazy.py,sha256=823VtmdWsbJyVZvNWopDhQdqq2i1xtj6b8IKfveboKw,1771
|
@@ -230,6 +231,11 @@ dissect/target/plugins/os/unix/linux/redhat/yum.py,sha256=kEvB-C2CNoqxSbgGRZiuo6
|
|
230
231
|
dissect/target/plugins/os/unix/linux/suse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
231
232
|
dissect/target/plugins/os/unix/linux/suse/_os.py,sha256=eaqgnkbunBJ2Hf_GE96THjfT3ybVIZvtWId-dx3JMV4,575
|
232
233
|
dissect/target/plugins/os/unix/linux/suse/zypper.py,sha256=amepAWivvbHFt2AoJUHC8lIeuD5Iy8MFXTWKqTYAEqE,4142
|
234
|
+
dissect/target/plugins/os/unix/locate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
235
|
+
dissect/target/plugins/os/unix/locate/gnulocate.py,sha256=P-YbMFw901p2EBgTaZH6axShfIRRDrCx3APBy6Ii3lE,2934
|
236
|
+
dissect/target/plugins/os/unix/locate/locate.py,sha256=uXFcWAqoz_3eNWHhsGoEtkkhmT5J3F1GYvr4uQxi308,122
|
237
|
+
dissect/target/plugins/os/unix/locate/mlocate.py,sha256=DhrFgxDQF-fMZaA0WK8Z-5o9i9iDsuTHW7MHJtWwz6o,4485
|
238
|
+
dissect/target/plugins/os/unix/locate/plocate.py,sha256=jUIqG-vD66hFn14SkQpJ2rfJnbtwOBMkJcaduWLtnlw,6591
|
233
239
|
dissect/target/plugins/os/unix/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
234
240
|
dissect/target/plugins/os/unix/log/atop.py,sha256=UmaqdnSmE8AO8bEj4drGSc1HH2n4Pdlxpwfa7RgraIY,16314
|
235
241
|
dissect/target/plugins/os/unix/log/audit.py,sha256=OjorWTmCFvCI5RJq6m6WNW0Lhb-poB2VAggKOGZUHK4,3722
|
@@ -325,10 +331,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
|
|
325
331
|
dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
|
326
332
|
dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
|
327
333
|
dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
|
328
|
-
dissect.target-3.16.
|
329
|
-
dissect.target-3.16.
|
330
|
-
dissect.target-3.16.
|
331
|
-
dissect.target-3.16.
|
332
|
-
dissect.target-3.16.
|
333
|
-
dissect.target-3.16.
|
334
|
-
dissect.target-3.16.
|
334
|
+
dissect.target-3.16.dev25.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
|
335
|
+
dissect.target-3.16.dev25.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
336
|
+
dissect.target-3.16.dev25.dist-info/METADATA,sha256=dlfz79OtrmH132ugeqPIQsRDdu-FgcSqIbt0i1AdSgo,11113
|
337
|
+
dissect.target-3.16.dev25.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
338
|
+
dissect.target-3.16.dev25.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
|
339
|
+
dissect.target-3.16.dev25.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
340
|
+
dissect.target-3.16.dev25.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.16.dev23.dist-info → dissect.target-3.16.dev25.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|