Skip to content

Commit

Permalink
Merge branch 'main' into feature/zip-loader
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthijsy authored Jan 29, 2025
2 parents 4072e20 + 385512c commit d3d7f9d
Show file tree
Hide file tree
Showing 38 changed files with 558 additions and 106 deletions.
9 changes: 6 additions & 3 deletions dissect/target/helpers/fsutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,10 +512,12 @@ def open_decompress(
An binary or text IO stream, depending on the mode with which the file was opened.
Example:
bytes_buf = open_decompress(Path("/dir/file.gz")).read()
.. code-block:: python
for line in open_decompress(Path("/dir/file.gz"), "rt"):
print(line)
bytes_buf = open_decompress(Path("/dir/file.gz")).read()
for line in open_decompress(Path("/dir/file.gz"), "rt"):
print(line)
"""
if path and fileobj:
raise ValueError("path and fileobj are mutually exclusive")
Expand All @@ -527,6 +529,7 @@ def open_decompress(
file = path.open("rb")
else:
file = fileobj
file.seek(0)

magic = file.read(5)
file.seek(0)
Expand Down
2 changes: 2 additions & 0 deletions dissect/target/helpers/localeutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ def _get_resource_path(path: str) -> Path:
)
if child.attrib["territory"] == "001"
}

WINDOWS_ZONE_MAP["UTC"] = "UTC" # Change 'Etc/UTC' to 'UTC' to be consistent across operating systems.
2 changes: 2 additions & 0 deletions dissect/target/plugins/apps/other/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ def envfile(self, env_path: str, extension: str = "env") -> Iterator[Environment

if not env_path:
self.target.log.error("No ``--path`` provided!")
return

if not (path := self.target.fs.path(env_path)).exists():
self.target.log.error("Provided path %s does not exist!", path)
return

for file in path.glob("**/*." + extension):
if not file.is_file():
Expand Down
2 changes: 1 addition & 1 deletion dissect/target/plugins/apps/vpn/wireguard.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
("string", "name"), # basename of .conf file if unset
("net.ipaddress", "address"),
("string", "private_key"),
("string", "listen_port"),
("varint", "listen_port"),
("string", "fw_mark"),
("string", "dns"),
("varint", "table"),
Expand Down
7 changes: 3 additions & 4 deletions dissect/target/plugins/os/unix/_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def users(self, sessions: bool = False) -> Iterator[UnixUserRecord]:
# Yield users found in passwd files.
for passwd_file in PASSWD_FILES:
if (path := self.target.fs.path(passwd_file)).exists():
for line in path.open("rt"):
for line in path.open("rt", errors="surrogateescape"):
line = line.strip()
if not line or line.startswith("#"):
continue
Expand All @@ -85,13 +85,12 @@ def users(self, sessions: bool = False) -> Iterator[UnixUserRecord]:
current_user = (pwent.get(0), pwent.get(5), pwent.get(6))
if current_user in seen_users:
continue

seen_users.add(current_user)
yield UnixUserRecord(
name=pwent.get(0),
passwd=pwent.get(1),
uid=pwent.get(2),
gid=pwent.get(3),
uid=pwent.get(2) or None,
gid=pwent.get(3) or None,
gecos=pwent.get(4),
home=posix_path(pwent.get(5)),
shell=pwent.get(6),
Expand Down
4 changes: 2 additions & 2 deletions dissect/target/plugins/os/unix/linux/sockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
("string", "protocol"),
("uint32", "rx_queue"),
("uint32", "tx_queue"),
("string", "local_ip"),
("net.ipaddress", "local_ip"),
("uint16", "local_port"),
("string", "remote_ip"),
("net.ipaddress", "remote_ip"),
("uint16", "remote_port"),
("string", "state"),
("string", "owner"),
Expand Down
13 changes: 8 additions & 5 deletions dissect/target/plugins/os/unix/locale.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@


def timezone_from_path(path: Path) -> str:
"""Return timezone name for given zoneinfo path.
"""Return timezone name for the given zoneinfo path.
/usr/share/zoneinfo/Europe/Amsterdam -> Europe/Amsterdam
.. code-block::
/usr/share/zoneinfo/Europe/Amsterdam -> Europe/Amsterdam
/usr/share/zoneinfo/UTC -> UTC
Etc/UTC -> UTC
"""
zoneinfo_path = str(path).split("/")
return "/".join(zoneinfo_path[-2:])
return "/".join([p for p in path.parts[-2:] if p.lower() not in ["zoneinfo", "etc"]])


class LocalePlugin(Plugin):
Expand All @@ -42,7 +45,7 @@ def timezone(self) -> str | None:
# on most unix systems
if (path := self.target.fs.path("/etc/timezone")).exists():
for line in path.open("rt"):
return line.strip()
return timezone_from_path(Path(line.strip()))

# /etc/localtime can be a symlink, hardlink or a copy of:
# eg. /usr/share/zoneinfo/America/New_York
Expand Down
13 changes: 11 additions & 2 deletions dissect/target/plugins/os/unix/log/journal.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,18 @@ def __iter__(self) -> Iterator[dict[str, int | str]]:
offset = self.header.entry_array_offset
while offset != 0:
self.fh.seek(offset)
object_type = self.fh.read(1)[0]

if self.fh.read(1)[0] != c_journal.ObjectType.OBJECT_ENTRY_ARRAY:
raise ValueError(f"Expected OBJECT_ENTRY_ARRAY at offset {offset}")
if object_type == c_journal.ObjectType.OBJECT_UNUSED:
self.target.log.warning(
"ObjectType OBJECT_UNUSED encountered for next OBJECT_ENTRY_ARRAY offset at 0x%X. "
"This indicates allocated space in the journal file which is not used yet.",
offset,
)
break

elif object_type != c_journal.ObjectType.OBJECT_ENTRY_ARRAY:
raise ValueError(f"Expected OBJECT_ENTRY_ARRAY or OBJECT_UNUSED at offset {offset}")

if self.header.incompatible_flags & c_journal.IncompatibleFlag.HEADER_INCOMPATIBLE_COMPACT:
entry_array_object = c_journal.EntryArrayObject_Compact(self.fh)
Expand Down
68 changes: 57 additions & 11 deletions dissect/target/plugins/os/unix/shadow.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from __future__ import annotations

from datetime import datetime, timedelta, timezone
from typing import Iterator

from dissect.target.exceptions import UnsupportedPluginError
Expand All @@ -13,12 +16,12 @@
("string", "hash"),
("string", "algorithm"),
("string", "crypt_param"),
("string", "last_change"),
("varint", "min_age"),
("varint", "max_age"),
("datetime", "last_change"),
("datetime", "min_age"),
("datetime", "max_age"),
("varint", "warning_period"),
("string", "inactivity_period"),
("string", "expiration_date"),
("varint", "inactivity_period"),
("datetime", "expiration_date"),
("string", "unused_field"),
],
)
Expand All @@ -39,6 +42,7 @@ def passwords(self) -> Iterator[UnixShadowRecord]:
Resources:
- https://manpages.ubuntu.com/manpages/oracular/en/man5/passwd.5.html
- https://linux.die.net/man/5/shadow
"""

seen_hashes = set()
Expand All @@ -64,19 +68,53 @@ def passwords(self) -> Iterator[UnixShadowRecord]:

seen_hashes.add(current_hash)

# improve readability
last_change = None
min_age = None
max_age = None
expiration_date = None

try:
last_change = int(shent.get(2)) if shent.get(2) else None
except ValueError as e:
self.target.log.warning(
"Unable to parse last_change shadow value in %s: %s ('%s')", shadow_file, e, shent.get(2)
)

try:
min_age = int(shent.get(3)) if shent.get(3) else None
except ValueError as e:
self.target.log.warning(
"Unable to parse last_change shadow value in %s: %s ('%s')", shadow_file, e, shent.get(3)
)

try:
max_age = int(shent.get(4)) if shent.get(4) else None
except ValueError as e:
self.target.log.warning(
"Unable to parse last_change shadow value in %s: %s ('%s')", shadow_file, e, shent.get(4)
)

try:
expiration_date = int(shent.get(7)) if shent.get(7) else None
except ValueError as e:
self.target.log.warning(
"Unable to parse last_change shadow value in %s: %s ('%s')", shadow_file, e, shent.get(7)
)

yield UnixShadowRecord(
name=shent.get(0),
crypt=shent.get(1),
algorithm=crypt.get("algo"),
crypt_param=crypt.get("param"),
salt=crypt.get("salt"),
hash=crypt.get("hash"),
last_change=shent.get(2),
min_age=shent.get(3),
max_age=shent.get(4),
warning_period=shent.get(5),
inactivity_period=shent.get(6),
expiration_date=shent.get(7),
last_change=epoch_days_to_datetime(last_change) if last_change else None,
min_age=epoch_days_to_datetime(last_change + min_age) if last_change and min_age else None,
max_age=epoch_days_to_datetime(last_change + max_age) if last_change and max_age else None,
warning_period=shent.get(5) if shent.get(5) else None,
inactivity_period=shent.get(6) if shent.get(6) else None,
expiration_date=epoch_days_to_datetime(expiration_date) if expiration_date else None,
unused_field=shent.get(8),
_target=self.target,
)
Expand Down Expand Up @@ -128,3 +166,11 @@ def extract_crypt_details(shent: dict) -> dict:
crypt["algo"] = algos[crypt["algo"]]

return crypt


def epoch_days_to_datetime(days: int) -> datetime:
"""Convert a number representing the days since 1 January 1970 to a datetime object."""
if not isinstance(days, int):
raise ValueError("days argument should be an integer")

return datetime(1970, 1, 1, 0, 0, tzinfo=timezone.utc) + timedelta(days)
8 changes: 4 additions & 4 deletions dissect/target/plugins/os/windows/amcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
("varint", "reference"),
("path", "path"),
("string", "language_code"),
("digest", "digests"),
("digest", "digest"),
("string", "program_id"),
("string", "pe_header_checksum"),
("string", "pe_size_of_image"),
Expand Down Expand Up @@ -126,7 +126,7 @@
[
("datetime", "mtime_regf"),
("string", "program_id"),
("digest", "digests"),
("digest", "digest"),
("path", "path"),
("string", "hash_path"),
("wstring", "name"),
Expand Down Expand Up @@ -224,7 +224,7 @@ def parse_file(self):
reference=int(subkey.name, 16),
path=self.target.fs.path(subkey_data["full_path"]) if subkey_data.get("full_path") else None,
language_code=subkey_data.get("language_code"),
digests=[None, subkey_data["sha1"][-40:] if subkey_data.get("sha1") else None, None],
digest=(None, subkey_data["sha1"][-40:] if subkey_data.get("sha1") else None, None),
program_id=subkey_data.get("program_id"),
pe_header_checksum=subkey_data.get("pe_header_checksum"),
pe_size_of_image=subkey_data.get("pe_size_of_image"),
Expand Down Expand Up @@ -468,7 +468,7 @@ def parse_inventory_application_file(self):
yield ApplicationFileAppcompatRecord(
mtime_regf=entry.timestamp,
program_id=entry_data.get("ProgramId"),
digests=[None, sha1_digest, None],
digest=(None, sha1_digest, None),
path=self.target.fs.path(entry_data.get("LowerCaseLongPath")),
link_date=parse_win_datetime(entry_data.get("LinkDate")),
hash_path=entry_data.get("LongPathHash"),
Expand Down
2 changes: 1 addition & 1 deletion dissect/target/plugins/os/windows/credential/credhist.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def _parse(self) -> Iterator[CredHistEntry]:
yield CredHistEntry(
version=entry.dwVersion,
guid=UUID(bytes_le=entry.guidLink),
user_sid=read_sid(entry.pSid),
user_sid=read_sid(entry.pSid) if entry.pSid else None,
hash_alg=HashAlgorithm.from_id(entry.algHash),
cipher_alg=cipher_alg,
sha1=None,
Expand Down
9 changes: 5 additions & 4 deletions dissect/target/plugins/os/windows/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"filesystem/registry/ndis",
[
("datetime", "ts"),
("string", "network"),
("string", "network_name"),
("string", "name"),
("string", "pnpinstanceid"),
],
Expand Down Expand Up @@ -113,7 +113,7 @@
("path", "librarypath"),
("string", "displaystring"),
("bytes", "providerid"),
("string", "enabled"),
("boolean", "enabled"),
("string", "version"),
],
)
Expand Down Expand Up @@ -408,7 +408,7 @@ def ndis(self) -> Iterator[NdisRecord]:

yield NdisRecord(
ts=network.ts,
network=sub.name,
network_name=sub.name,
name=name,
pnpinstanceid=pnpinstanceid,
_target=self.target,
Expand Down Expand Up @@ -611,11 +611,12 @@ def sid(self) -> Iterator[ComputerSidRecord]:

try:
key = self.target.registry.key("HKLM\\SECURITY\\Policy\\PolMachineAccountS")
raw_sid = key.value("(Default)").value

yield ComputerSidRecord(
ts=key.timestamp,
sidtype="Domain",
sid=read_sid(key.value("(Default)").value),
sid=read_sid(raw_sid) if raw_sid else None,
_target=self.target,
)
except (RegistryError, struct.error):
Expand Down
10 changes: 5 additions & 5 deletions dissect/target/plugins/os/windows/log/amcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
("string", "bin_file_version"),
("string", "bin_product_version"),
("string", "binary_type"),
("digest", "digests"),
("digest", "digest"),
("wstring", "file_version"),
("wstring", "company_name"),
("wstring", "file_description"),
Expand All @@ -42,7 +42,7 @@
("string", "pe_image"),
("string", "pe_subsystem"),
("string", "crc_checksum"),
("string", "filesize"),
("filesize", "filesize"),
("wstring", "longname"),
("string", "msi"),
]
Expand Down Expand Up @@ -82,16 +82,16 @@ def create_record(
size_of_image=install_properties.get("sizeofimage"),
file_description=install_properties.get("filedescription"),
size=install_properties.get("size"),
digests=[
digest=(
None,
install_properties.get("id")[4:],
None,
], # remove leading zeros from the entry to create a sha1 hash
), # remove leading zeros from the entry to create a sha1 hash
company_name=install_properties.get("companyname"),
binary_type=install_properties.get("binarytype"),
bin_product_version=install_properties.get("binproductversion"),
bin_file_version=install_properties.get("binfileversion"),
filesize=install_properties.get("filesize"),
filesize=int(install_properties.get("filesize", "0"), 16),
pe_image=install_properties.get("peimagetype"),
product_version=install_properties.get("productversion"),
crc_checksum=install_properties.get("crcchecksum"),
Expand Down
Loading

0 comments on commit d3d7f9d

Please sign in to comment.