Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Vista idlist parsing #25

Merged
merged 6 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions dissect/shellitem/lnk/lnk.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,16 @@
)
return

struct = c_lnk.typedefs[block_name](block_data)
if block_name == "VISTA_AND_ABOVE_IDLIST_PROPS":
struct = LnkTargetIdList(BytesIO(block_data), read_size)
else:
struct = c_lnk.typedefs[block_name](block_data)

if block_name == "PROPERTY_STORE_PROPS":
# TODO implement actual serialized property parsing
guid = self._parse_guid(struct.format_id)
struct._values.update({"format_id": guid})

elif block_name == "VISTA_AND_ABOVE_IDLIST_PROPS":
struct = LnkTargetIdList(BytesIO(block_data), read_size)

elif block_name == "TRACKER_PROPS":
for name, value in struct._values.items():
if "droid" in name:
Expand All @@ -103,16 +103,16 @@
self.extradata.update({block_name: struct})

else:
log.warning(f"Unknown extra data block encountered with signature 0x{signature:x}")
log.warning("Unknown extra data block encountered with signature %x", signature)

Check warning on line 106 in dissect/shellitem/lnk/lnk.py

View check run for this annotation

Codecov / codecov/patch

dissect/shellitem/lnk/lnk.py#L106

Added line #L106 was not covered by tests

# keep calling parse until the TERMINAL_BLOCK is hit.
self._parse(fh)

def _parse_guid(self, guid: bytes, endianness: str = "<") -> UUID:
if endianness == "<":
return UUID(bytes_le=guid)
else:
return UUID(bytes=guid)

return UUID(bytes=guid)

Check warning on line 115 in dissect/shellitem/lnk/lnk.py

View check run for this annotation

Codecov / codecov/patch

dissect/shellitem/lnk/lnk.py#L115

Added line #L115 was not covered by tests

def __getattr__(self, attr: str) -> Any:
try:
Expand Down Expand Up @@ -208,8 +208,8 @@
# if so the LocalBasePathOffsetUnicode and CommonPathSuffixOffsetUnicode fields are present
if self.linkinfo_header.link_info_header_size >= 0x00000024:
log.error(
"Unicode link_info_header encountered. Size bigger than 0x00000024. Size encountered:"
f"{self.linkinfo_header.link_info_header_size}"
"Unicode link_info_header encountered. Size bigger than 0x00000024. Size encountered: %x",
self.linkinfo_header.link_info_header_size,
)
# TODO parse unicode headers. none encountered yet.

Expand Down Expand Up @@ -435,16 +435,18 @@

if link_clsid == "00021401-0000-0000-c000-000000000046":
return link_header
else:
log.info(f"Encountered invalid link file header: {link_header}. Skipping.")
return None
else:
log.info(
f"Encountered invalid link file with magic header size 0x{header_size:x} - "
f"magic header size should be 0x{LINK_HEADER_SIZE:x}. Skipping."
)

log.info("Encountered invalid link file header: %s. Skipping.", link_header)

Check warning on line 439 in dissect/shellitem/lnk/lnk.py

View check run for this annotation

Codecov / codecov/patch

dissect/shellitem/lnk/lnk.py#L439

Added line #L439 was not covered by tests
return None

log.info(

Check warning on line 442 in dissect/shellitem/lnk/lnk.py

View check run for this annotation

Codecov / codecov/patch

dissect/shellitem/lnk/lnk.py#L442

Added line #L442 was not covered by tests
"Encountered invalid link file with magic header size 0x%x. \
Magic header size should be 0x%x. Skipping.",
header_size,
LINK_HEADER_SIZE,
)
return None

Check warning on line 448 in dissect/shellitem/lnk/lnk.py

View check run for this annotation

Codecov / codecov/patch

dissect/shellitem/lnk/lnk.py#L448

Added line #L448 was not covered by tests

@property
def clsid(self) -> UUID:
"""Returns the class id (clsid) of the LNK file."""
Expand Down
17 changes: 11 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,35 @@
import pytest


def absolute_path(filename):
def absolute_path(filename: str) -> str:
return os.path.join(os.path.dirname(__file__), filename)
Horofic marked this conversation as resolved.
Show resolved Hide resolved


@pytest.fixture
def xp_modified_remote_lnk_file():
def xp_modified_remote_lnk_file() -> Path:
return Path(absolute_path("data/modified_remote.file.xp.lnk"))


@pytest.fixture
def xp_remote_lnk_file():
def xp_remote_lnk_file() -> Path:
return Path(absolute_path("data/remote.file.xp.lnk"))


@pytest.fixture
def xp_remote_lnk_dir():
def xp_remote_lnk_dir() -> Path:
return Path(absolute_path("data/remote.directory.xp.lnk"))


@pytest.fixture
def win7_local_lnk_dir():
def win7_local_lnk_dir() -> Path:
return Path(absolute_path("data/local.directory.seven.lnk"))


@pytest.fixture
def win81_downloads_lnk_dir():
def win81_downloads_lnk_dir() -> Path:
return Path(absolute_path("data/downloads.win81.lnk"))


@pytest.fixture
def vista_idlist_lnk_file() -> Path:
return Path(absolute_path("data/vista.idlist.lnk"))
Binary file added tests/data/vista.idlist.lnk
Binary file not shown.
24 changes: 18 additions & 6 deletions tests/test_lnk.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from pathlib import Path

from dissect.util.ts import uuid1timestamp

from dissect.shellitem.lnk import Lnk, c_lnk


def test_xp_custom_destination_remote_lnk_file(xp_modified_remote_lnk_file):
def test_xp_custom_destination_remote_lnk_file(xp_modified_remote_lnk_file: Path) -> None:
# The first 16 bytes contain the LNK GUID a to simulate a Jumplist CustomDestination file
fh = xp_modified_remote_lnk_file.open("rb")
fh.seek(16)
Expand All @@ -14,7 +16,7 @@ def test_xp_custom_destination_remote_lnk_file(xp_modified_remote_lnk_file):
assert str(lnk_file.clsid) == "00021401-0000-0000-c000-000000000046"


def test_xp_remote_lnk_file(xp_remote_lnk_file):
def test_xp_remote_lnk_file(xp_remote_lnk_file: Path) -> None:
fh = xp_remote_lnk_file.open("rb")
lnk_file = Lnk(fh)

Expand Down Expand Up @@ -68,7 +70,7 @@ def test_xp_remote_lnk_file(xp_remote_lnk_file):
assert uuid1timestamp(tracker_props.file_droid.time).ctime() == "Wed Feb 8 07:52:55 2006"


def test_xp_remote_lnk_dir(xp_remote_lnk_dir):
def test_xp_remote_lnk_dir(xp_remote_lnk_dir: Path) -> None:
fh = xp_remote_lnk_dir.open("rb")
lnk_file = Lnk(fh)

Expand All @@ -81,7 +83,7 @@ def test_xp_remote_lnk_dir(xp_remote_lnk_dir):
idlist = lnk_file.target_idlist.idlist
assert len(idlist.itemid_list) == 7
assert idlist.terminalid == b"\x00\x00"
assert all([entry.itemid_size == len(entry.dumps()) for entry in idlist.itemid_list])
assert all(entry.itemid_size == len(entry.dumps()) for entry in idlist.itemid_list)

assert flags & c_lnk.LINK_FLAGS.has_link_info
link_info = lnk_file.linkinfo.link_info
Expand Down Expand Up @@ -116,7 +118,7 @@ def test_xp_remote_lnk_dir(xp_remote_lnk_dir):
assert uuid1timestamp(tracker_props.file_droid.time).ctime() == "Tue Jul 21 07:31:44 2009"


def test_win7_local_lnk_dir(win7_local_lnk_dir):
def test_win7_local_lnk_dir(win7_local_lnk_dir: Path) -> None:
fh = win7_local_lnk_dir.open("rb")
lnk_file = Lnk(fh)

Expand Down Expand Up @@ -179,7 +181,7 @@ def test_win7_local_lnk_dir(win7_local_lnk_dir):
assert str(property_store_props.format_id) == "b725f130-47ef-101a-a5f1-02608c9eebac"


def test_common_path_suffix(win81_downloads_lnk_dir):
def test_common_path_suffix(win81_downloads_lnk_dir: Path) -> None:
fh = win81_downloads_lnk_dir.open("rb")
downloads = Lnk(fh)

Expand All @@ -190,3 +192,13 @@ def test_common_path_suffix(win81_downloads_lnk_dir):
assert link_info_flags & c_lnk.LINK_INFO_FLAGS.common_network_relative_link_and_pathsuffix == 0
assert link_info.common_path_suffix == b""
assert link_info.common_path_suffix.decode() == ""


def test_vista_and_above_idlist_lnk_props(vista_idlist_lnk_file: Path) -> None:
fh = vista_idlist_lnk_file.open("rb")
lnk_file = Lnk(fh)
vista_props = lnk_file.extradata.extradata["VISTA_AND_ABOVE_IDLIST_PROPS"]

assert vista_props.size == 0x62 # 98
assert vista_props.idlist.itemid_list[0].itemid_size == 0x60 # 96
assert len(vista_props.idlist.itemid_list[0].data) == 0x5E # 94
Loading