Skip to content

Commit ffb9dd7

Browse files
authored
Merge pull request #28 from gdesmar/terminal_block
Adding support for Unknown Extra block and appended data after Terminal block
2 parents 3db3f9e + f541c81 commit ffb9dd7

File tree

11 files changed

+169
-29
lines changed

11 files changed

+169
-29
lines changed

LnkParse3/exceptions.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
class LnkParserError(Exception):
2-
...
1+
class LnkParserError(Exception): ...

LnkParse3/extra/terminal.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import hashlib
2+
from LnkParse3.extra.lnk_extra_base import LnkExtraBase
3+
4+
"""
5+
TerminalBlock (4 bytes): A 32-bit, unsigned integer that indicates the end of the extra data section.
6+
This value MUST be less than 0x00000004.
7+
8+
No data should be expected or found after the terminal block, but in the rare case where it
9+
does, this class will fulfill the undocumented feature of keeping track of it.
10+
This can be the case with malicious shortcut files trying to hide their payload.
11+
12+
------------------------------------------------------------------
13+
| 0-7b | 8-15b | 16-23b | 24-31b |
14+
------------------------------------------------------------------
15+
| <u_int32> BlockSignature == 0x00000000 - 0x00000003 |
16+
------------------------------------------------------------------
17+
| appended data |
18+
------------------------------------------------------------------
19+
"""
20+
21+
22+
class Terminal(LnkExtraBase):
23+
def name(self):
24+
return "TERMINAL_BLOCK"
25+
26+
def appended_data(self):
27+
start = 4
28+
return self._raw[start:]
29+
30+
# Overwrite the usual size with the real appended data length
31+
def size(self):
32+
return 4 + len(self.appended_data())
33+
34+
def as_dict(self):
35+
tmp = super().as_dict()
36+
tmp["appended_data_sha256"] = hashlib.sha256(self.appended_data()).hexdigest()
37+
return tmp

LnkParse3/extra/unknown.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import hashlib
2+
from LnkParse3.extra.lnk_extra_base import LnkExtraBase
3+
4+
"""
5+
This class does not represent a specific extra block defined in the [MS-SHLLINK] documentation.
6+
It aims to cover cases where malicious shortcut files tries to hide their payload in an
7+
undocumented block that still uses the right format and a valid length.
8+
"""
9+
10+
11+
class Unknown(LnkExtraBase):
12+
def name(self):
13+
return "UNKNOWN_BLOCK"
14+
15+
def extra_data(self):
16+
start = 4
17+
return self._raw[start:]
18+
19+
def as_dict(self):
20+
tmp = super().as_dict()
21+
tmp["extra_data_sha256"] = hashlib.sha256(self.extra_data()).hexdigest()
22+
return tmp

LnkParse3/extra_data.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import warnings
2+
from struct import unpack
23
from struct import error as StructError
34

45
from LnkParse3.extra_factory import ExtraFactory
6+
from LnkParse3.extra.unknown import Unknown
7+
from LnkParse3.extra.terminal import Terminal
58

69
"""
710
EXTRA_DATA:
8-
Zero or more ExtraData structures (section 2.5).
11+
A structure consisting of zero or more property data blocks followed by a terminal block (section 2.5).
912
"""
1013

1114

@@ -36,11 +39,20 @@ def _iter(self):
3639
if cls:
3740
yield cls(indata=data, cp=self.cp)
3841

42+
# If there is data following the Terminal Block, we should take note of it and tell the user.
43+
if len(rest) > 4 and unpack("<I", rest[:4])[0] < 0x00000004:
44+
yield Terminal(indata=rest, cp=self.cp)
45+
3946
def as_dict(self):
4047
res = {}
4148
for extra in self:
4249
try:
43-
res[extra.name()] = extra.as_dict()
50+
if isinstance(extra, Unknown):
51+
if extra.name() not in res:
52+
res[extra.name()] = []
53+
res[extra.name()].append(extra.as_dict())
54+
else:
55+
res[extra.name()] = extra.as_dict()
4456
except (StructError, ValueError) as e:
4557
msg = "Error while parsing `%s` (%s)" % (extra.name(), e)
4658
warnings.warn(msg)

LnkParse3/extra_factory.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from LnkParse3.extra.metadata import Metadata
1414
from LnkParse3.extra.known_folder import KnownFolder
1515
from LnkParse3.extra.shell_item import ShellItem
16+
from LnkParse3.extra.unknown import Unknown
1617

1718
"""
1819
------------------------------------------------------------------
@@ -57,7 +58,7 @@ def extra_class(self):
5758
# Allow for no accompanying data for a reported size, observed in malicious files
5859
try:
5960
sig = str(hex(self._rsig()))[2:] # huh?
60-
return self.EXTRA_SIGS.get(sig)
61+
return self.EXTRA_SIGS.get(sig, Unknown)
6162
except struct.error as e:
6263
warnings.warn(f"Error while parsing extra's signature {e}")
6364
return None

LnkParse3/lnk_file.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -256,22 +256,34 @@ def nice_id(identifier):
256256
cprint("EXTRA BLOCKS:", 1)
257257
for extra_key, extra_value in self.extras.as_dict().items():
258258
cprint(f"{extra_key}", 2)
259-
for key, value in extra_value.items():
260-
if extra_key == "METADATA_PROPERTIES_BLOCK" and isinstance(value, list):
261-
cprint(f"{nice_id(key)}:", 3)
262-
for storage in value:
263-
cprint("Storage:", 4)
264-
for storage_key, storage_value in storage.items():
265-
if isinstance(storage_value, list):
266-
cprint(f"{nice_id(storage_key)}:", 5)
267-
for item in storage_value:
268-
cprint("Property:", 6)
269-
for item_key, item_value in item.items():
270-
cprint(f"{nice_id(item_key)}: {item_value}", 7)
271-
else:
272-
cprint(f"{nice_id(storage_key)}: {storage_value}", 5)
273-
else:
274-
cprint(f"{nice_id(key)}: {value}", 3)
259+
if extra_key == "UNKNOWN_BLOCK":
260+
cprint("Block:", 3)
261+
for list_value in extra_value:
262+
for key, value in list_value.items():
263+
cprint(f"{nice_id(key)}: {value}", 4)
264+
else:
265+
for key, value in extra_value.items():
266+
if extra_key == "METADATA_PROPERTIES_BLOCK" and isinstance(
267+
value, list
268+
):
269+
cprint(f"{nice_id(key)}:", 3)
270+
for storage in value:
271+
cprint("Storage:", 4)
272+
for storage_key, storage_value in storage.items():
273+
if isinstance(storage_value, list):
274+
cprint(f"{nice_id(storage_key)}:", 5)
275+
for item in storage_value:
276+
cprint("Property:", 6)
277+
for item_key, item_value in item.items():
278+
cprint(
279+
f"{nice_id(item_key)}: {item_value}", 7
280+
)
281+
else:
282+
cprint(
283+
f"{nice_id(storage_key)}: {storage_value}", 5
284+
)
285+
else:
286+
cprint(f"{nice_id(key)}: {value}", 3)
275287

276288
def format_linkFlags(self):
277289
return " | ".join(self.header.link_flags())

LnkParse3/text_processor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
the terminating null character are undefined and can have any value. The
66
undefined bytes MUST NOT be used.
77
"""
8+
89
import warnings
910

1011

tests/json/shortcut_target_only.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
{"shortcut_target": ".\\a.txt"}
1+
{
2+
"shortcut_target": ".\\a.txt"
3+
}

tests/json/unknown_target.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
{
22
"data": {},
3-
"extra": {},
3+
"extra": {
4+
"TERMINAL_BLOCK": {
5+
"appended_data_sha256": "d64c62e65398d37cd27e11fd729fa102016a05ba67f5020e17dfbd3b857dd96e",
6+
"size": 67653
7+
}
8+
},
49
"header": {
510
"accessed_time": "2012-08-06T11:51:14+00:00",
611
"creation_time": "2012-08-06T11:51:14+00:00",

tests/json/unknown_target.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Windows Shortcut Information:
2+
Header Size: 76
3+
Link CLSID: 00021401-0000-0000-C000-000000000046
4+
Link Flags: HasTargetIDList | IsUnicode - (129)
5+
File Flags: - (0)
6+
7+
Creation Timestamp: 2012-08-06 11:51:14.390625+00:00
8+
Modified Timestamp: 2012-08-06 11:51:14.390625+00:00
9+
Accessed Timestamp: 2012-08-06 11:51:14.390625+00:00
10+
11+
File Size: 0 (r: 68608)
12+
Icon Index: 0
13+
Window Style: SW_SHOWNORMAL
14+
HotKey: UNSET - UNSET {0x0000}
15+
Reserved0: 0
16+
Reserved1: 0
17+
Reserved2: 0
18+
19+
TARGETS:
20+
Size: 877
21+
Index: 78
22+
ITEMS:
23+
Volume Item
24+
Flags: 0xe
25+
Data: None
26+
Unknown
27+
28+
DATA
29+
30+
EXTRA BLOCKS:
31+
TERMINAL_BLOCK
32+
Size: 67653
33+
Appended data sha256: d64c62e65398d37cd27e11fd729fa102016a05ba67f5020e17dfbd3b857dd96e

0 commit comments

Comments
 (0)