Skip to content

Commit

Permalink
Fix Vista idlist parsing (#25)
Browse files Browse the repository at this point in the history
(DIS-2798)
  • Loading branch information
Horofic authored Jan 30, 2024
1 parent 0e19fdd commit 4e25899
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 36 deletions.
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 @@ def _parse(self, fh: BinaryIO) -> None:
)
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 @@ def _parse(self, fh: BinaryIO) -> None:
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)

# 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)

def __getattr__(self, attr: str) -> Any:
try:
Expand Down Expand Up @@ -208,8 +208,8 @@ def __init__(self, fh: Optional[BinaryIO] = None):
# 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 @@ def _parse_header(self, fh: Optional[BinaryIO]) -> Optional[c_lnk.SHELL_LINK_HEA

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)
return None

log.info(
"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

@property
def clsid(self) -> UUID:
"""Returns the class id (clsid) of the LNK file."""
Expand Down
30 changes: 17 additions & 13 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
import os
from pathlib import Path

import pytest


def absolute_path(filename):
return os.path.join(os.path.dirname(__file__), filename)
def absolute_path(filename: str) -> Path:
return Path(__file__).parent.joinpath(filename)


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


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


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


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


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


@pytest.fixture
def vista_idlist_lnk_file() -> Path:
return 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

0 comments on commit 4e25899

Please sign in to comment.