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

Reset offset of file-object #20

Merged
merged 2 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
34 changes: 23 additions & 11 deletions dissect/shellitem/lnk/lnk.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@


class LnkExtraData:
"""Class that represents the a LNK file's EXTRA_DATA stucture
"""Class that represents the a LNK file's EXTRA_DATA structure
This optional structure hold additional optional structures that convey additional information about a link target

Args:
Expand All @@ -35,7 +35,7 @@ class LnkExtraData:
# KNOWN_FOLDER_PROPS / PROPERTY_STORE_PROPS /
# SHIM_PROPS / SPECIAL_FOLDER_PROPS /
# TRACKER_PROPS / VISTA_AND_ABOVE_IDLIST_PROPS
# This is kinda the same as LnkStringData only that the defined extra stuctures can wildly vary
# This is kinda the same as LnkStringData only that the defined extra structures can wildly vary
def __init__(self, fh: Optional[BinaryIO] = None):
self.extradata = {}

Expand All @@ -58,7 +58,7 @@ def _parse(self, fh: BinaryIO) -> None:
block_data = memoryview(fh.read(read_size))

if len(block_data) != read_size:
# Some malicous lnk files have a mismatch in the size indicated in the data block and actual bytes red.
# Some malicious lnk files have a mismatch in the size indicated in the data block and actual bytes red.
# This causes cstruct to have an EOFError when trying to parse the actual size. Which is not reflected.
log.warning(
"Mismatch in read size (%i) and actual EXTRA_DATA_BLOCK length (%i) for data block (%s)",
Expand Down Expand Up @@ -105,7 +105,7 @@ def _parse(self, fh: BinaryIO) -> None:
else:
log.warning(f"Unknown extra data block encountered with signature 0x{signature:x}")

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

def _parse_guid(self, guid: bytes, endianness: str = "<") -> UUID:
Expand Down Expand Up @@ -183,8 +183,9 @@ def __repr__(self) -> str:

class LnkInfo:
"""This class represents a LNK file's LINK_INFO structure. The optional LINK_INFO structure specifies information
necesarry to resolve a link target if it is not found in its original location. This includes information about the
volume that the target was stored on, the mapped drive letter, and a UNC path if existed when the link was created.
unnecessary to resolve a link target if it is not found in its original location. This includes information about
Zawadidone marked this conversation as resolved.
Show resolved Hide resolved
the volume that the target was stored on, the mapped drive letter, and a UNC path if existed when the link was
created.

Args:
fh: A file-like objet to a LINK_INFO structure
Expand All @@ -203,7 +204,7 @@ def __init__(self, fh: Optional[BinaryIO] = None):
self.linkinfo_header = c_lnk.LINK_INFO_HEADER(fh.read(LINK_INFO_HEADER_SIZE))
self.flags = self.linkinfo_header.link_info_flags

# values higher than 0x24 indicate the presense of optional fields in the link info structure
# values higher than 0x24 indicate the presence of optional fields in the link info structure
# if so the LocalBasePathOffsetUnicode and CommonPathSuffixOffsetUnicode fields are present
if self.linkinfo_header.link_info_header_size >= 0x00000024:
log.error(
Expand Down Expand Up @@ -290,7 +291,7 @@ def _parse(self, buff: BinaryIO) -> None:
)

def flag(self, name: str) -> int:
"""Retuns whether supplied flag is set.
"""Returns whether supplied flag is set.

Args:
name: Name of the flag
Expand Down Expand Up @@ -352,7 +353,7 @@ def __repr__(self) -> str:
class Lnk:
"""This class represents a LNK file's SHELL_LINK_HEADER, and the remainder of the parsed LNK file structures.
This SHELL_LINK_HEADER structure contains identification information, timestamps, and flags that specify the
pressence of optional structures.
presence of optional structures.

Parses a .lnk file (aka Microsoft Shell Item) according to the MS-SHLLINK specification
reference: https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-SHLLINK/%5bMS-SHLLINK%5d.pdf
Expand Down Expand Up @@ -403,7 +404,7 @@ def __init__(
self.extradata = LnkExtraData(self.fh)

def flag(self, name: str) -> int:
"""Retuns whether supplied flag is set.
"""Returns whether supplied flag is set.

Args:
name: Name of the flag
Expand All @@ -414,8 +415,19 @@ def flag(self, name: str) -> int:
return self.flags & c_lnk.LINK_FLAGS[name]

def _parse_header(self, fh: Optional[BinaryIO]) -> Optional[c_lnk.SHELL_LINK_HEADER]:
"""Returns LINK header.

Parse the header in a buffer that does not start at offset 0

Args:
fh: File object

Returns:
LINK header if size is 0x4C, else none
"""
offset = fh.tell()
header_size = unpack("I", fh.read(4))[0]
fh.seek(0)
fh.seek(offset)

if header_size == LINK_HEADER_SIZE:
link_header = c_lnk.SHELL_LINK_HEADER(fh.read(LINK_HEADER_SIZE))
Expand Down
5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ def absolute_path(filename):
return os.path.join(os.path.dirname(__file__), filename)


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


@pytest.fixture
def xp_remote_lnk_file():
return Path(absolute_path("data/remote.file.xp.lnk"))
Expand Down
Binary file added tests/data/modified_remote.file.xp.lnk
Binary file not shown.
11 changes: 11 additions & 0 deletions tests/test_lnk.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
from dissect.shellitem.lnk import Lnk, c_lnk


def test_xp_custom_destination_remote_lnk_file(xp_modified_remote_lnk_file):
# 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)

lnk_file = Lnk(fh)

assert lnk_file.link_header.header_size == 0x4C
assert str(lnk_file.clsid) == "00021401-0000-0000-c000-000000000046"


def test_xp_remote_lnk_file(xp_remote_lnk_file):
fh = xp_remote_lnk_file.open("rb")
lnk_file = Lnk(fh)
Expand Down