[ << ALL_FEED ]

Did you know that malicious people don't like LNK file parsers?

More in Windows

Did you know that malicious actors don’t like LNK file parsers? 👿

In March we wrote about XDSpy attacks on Russian organizations. The LNK files used in this attack were modified in such a way that many parsers “crashed” when processing them. For example, this applies to LECmd, LnkParse3, and ExifTool, which is used on VirusTotal (screenshot 1).

Let’s figure out where the problem lies and how to fix it 🔧

In general terms, the structure of an LNK file looks as follows:

1. SHELL_LINK_HEADER with the LinkFlags value we’re interested in. It defines the presence of certain structures in the LNK file and looks like this:

typedef struct
{
  uint32 HasLinkTargetIDList : 1; // there is a LINKTARGET_IDLIST
  uint32 HasLinkInfo : 1; // there is a LINKINFO
  uint32 HasName : 1; // there is STRING_DATA
  uint32 HasRelativePath : 1; // there is STRING_DATA
  uint32 HasWorkingDir : 1; // there is STRING_DATA
  uint32 HasArguments : 1; // there is STRING_DATA
  uint32 HasIconLocation : 1; // there is STRING_DATA
  uint32 IsUnicode : 1; // strings in UTF-16
    // ... skipped
} LinkFlags;
Code language: plaintext (plaintext)


2. [Optional] LINKTARGET_IDLIST.
3. [Optional] LINKINFO.
4. [Optional] STRING_DATA.

STRING_DATA is an array of StringData structures, each of which has the following format:

• CountCharacters (the number of characters in the string, so for Unicode the number of bytes will be CountCharacters × 2).
• String (the string, which according to the documentation MUST NOT be NULL-terminated).

The STRING_DATA structure contains Description (Name), RelativePath, WorkingDir, Arguments, and IconLocation in the order specified in LinkFlags.

🫱 The problem for parsers that rely on it lies precisely in CountCharacters. To “bring them down,” the kind gentlemen set a large value in CountCharacters, for example 0xFFFF. They make String up to 260 characters long, and place the next StringData exactly 260 characters later (in UTF-16 that’s 520 bytes).

As it turns out, Windows “under the hood” limits the length of the Description, RelativePath, and WorkingDir strings to 260 characters. As a result, we have the following:

• Parsers “crash” due to incorrect values (they calculate the offset of 0xFFFF and land on meaningless bytes).

• Windows limits itself to 260 characters, correctly landing on the next StringData, and launches the LNK file without any problems.

• The 260-character limit is definitely implied for Description, which was not obvious, RelativePath, and WorkingDir.

• For Arguments there is no such limit, or the threshold is much higher, which already makes sense. At the same time, in the LNK file properties, when you right-click, only the first 260 characters of the command will be displayed.

• Windows considers the last byte to be null, even if it isn’t, although from the documentation one might have assumed that the last character is also counted.

🕵️‍♀️ In the LNK file variant from XDSpy, the attackers “broke” the WorkingDir string in this way and set its value to C:\Windows\System32\<spaces> with a length of 260 characters (screenshot 2). Here you can see what such a file looks like on VT (screenshot 3).

---- WorkingDir ----
0x0      CountCharacters (0XFFFF)
0x2      String (up to 0x208) // C:\Windows\System32_______
---- Arguments ----
0x20A   CountCharacters // The Windows parser goes here
0x20C   String // Arguments
...
0xFFFF  RandomData // Regular parsers go here
Code language: plaintext (plaintext)


To fix this misunderstanding, you need to check the size of CountCharacters for the relevant strings and limit the offset to the next StringData to 260 characters, taking into account the isUnicode flag.


#tip #win
@ptescalator

More from global_author

More from global_author

More in Windows