Skip to Main Content
All Trimarc services are now delivered through TrustedSec! Learn more
March 12, 2026

LnkMeMaybe - A Review of CVE-2026-25185

Written by Christopher Paschen
Vulnerability Assessment

A Windows shortcut (.lnk) seems very simple on the surface. It is a file that points somewhere and tells the system to open or execute a resource. A shortcut is relatively easy to overlook and can be spoofed to look like something it’s not. It wasn’t until I was tasked to expand our capabilities for interacting with a .lnk that I realized just how many options were exposed by the structure of a .lnk. I set out to build a cross-platform library that could manipulate every structure of a Windows shortcut and walked away with the first CVE to my name (CVE-2026-25185). Through this post we’ll walk through what all goes into a Windows shortcut, the tooling I developed, the resulting CVE, and potential future areas of research.

What Makes a .lnk?

The structure of a Windows shortcut is outlined can be found here.

There are existing libraries that read and format a .lnk file, but as is often the case, the best way to learn about something is to dig into it and build against it. For that reason, I set out to build my own cross-platform C# library that would enable reading, modifying, and creating Windows shortcuts. We’ll walk through those fields below, and I’ll point out interesting findings as we go.

ShellLinkHeader

This header contains the signature of the .lnk, some auxiliary information about the .lnk file, and flags that dictate how the .lnk is structured and interpreted.

Name

Notes

HeaderSize

Static value of 0x4C

LinkCLSID

Static GUID of 00021401-0000-0000- C000-000000000046

LinkFlags

Various flags dictating other fields present in a .lnk (1)

FileAttributes

Various attributes of the target file

CreationTime

Creation time of target file

AccessTime

Access time of target file

WriteTime

Write time of target file

FileSize

Lower 32bits of target file size

Icon Index

Index to pull icon from referenced resource (2)

ShowCommand

Flag indicating expected window state of launched application

HotKey

Indicates key and modifies to trigger this .lnk if on desktop (3)

Reserved1

Unused, and I didn’t reverse engineer to find out otherwise

Reserved2

Unused, and I didn’t reverse engineer to find out otherwise

Reserved3

Unused, and I didn’t reverse engineer to find out otherwise

(1) Outside of specifying what other fields in the .lnk are present, there are a few specific flags of interest, namely:

a. RunAsUser -> lnk will prompt for elevation when run.
b. RunWithShimLayer -> will attempt to apply the shim defined in ShimDataBlock.
c. PreferEnvironmentPath -> Setting can lead to unexpected behavior when a TargetIDList is present.

(2) This index is only used when stringdata.iconLocation is populated.

(3) The Properties window within Windows won’t let you set a hotkey without a modifier key. Despite that restriction in the properties UI, a hotkey without a modifier key will function as expected if the .lnk is created in a way that bypassed that UI restriction.

LinkTargetIDList

For all intents and purposes, this field may as well be undocumented. We have a size and a variable list of 'shell data source-defined data that specifies an item.' There are components of the Windows shell that will interpret this list of items and understand this .lnk to point to a specific resource based on what’s contained in this list. For the library being released at the end of this document, only local file resources are supported.

When this part of the .lnk is present and correctly populated, it will be what the .lnk resolves to, regardless of what other fields are populated in the .lnk. If this field is not present or points at a nonexistent item, then other attributes of the .lnk take over and attempt to resolve the current location of the item that used to be pointed to by this .lnk.

LinkInfo

If a .lnk is not present at the referenced location, the data populated in this structure is one of the ways that the shell will attempt to resolve the new .lnk location. If a new location is found, the LinkTargetIDList is updated with the new location.

Name

Notes

LinkInfoSize

Size of this structure

LinkInfoHeaderSize

0x1C or 0x24 (optional Unicode fields present)

LinkInfoFlags

Specifies which optional internal structures are present

VolumeIDOffset

Offset from start of struct to VolumeID

LocalBasePathOffset

Offset from start of struct to LocalBasePath

CommonNetworkRelativeLinkOffset

Offset to CommonNetworkRelativeLink from start of struct

CommonPathSuffixOffset

Offset to CommonPathSuffix from start of struct

LocalBasePathOffsetUnicode

Offset to Unicode version of the LocalBasePath

CommonPathSuffixUnicode

Offset to Unicode version of CommonPathSuffix

VolumeID

Variable Length Structure

LocalBasePath

String

CommonNetworkRelativeLink

Variable Length Structure

CommonPathSuffix

String

LocalBasePathUnicode

String

CommonPathSuffixUnicode

String

While I did some research on the fields and structures within this table, I could not produce any meaningful results. Largely, this table does seem to be for resolving the .lnk when LinkTargetIDList points to an invalid item. It’s possible one could cause some sort of confusion attack using this section, but I will leave that for future research.

StringData

This is such a deceptive name for a table that can control the entire .lnk resolution flow.

Let’s go through each field, as all of them are important.

NAME_STRING: This controls the text that will be shown to the user when hovering on this .lnk.

RELATIVE_PATH: This field can control the resolution of the .lnk if the LinkTargetIDList is invalid. Three specific scenarios have been investigated.

  1. Missing TargetIDList, populated RELATIVE_PATH: .lnk does not function
  2. Valid TargetIDList, populated RELATIVE_PATH: .lnk executes TargetIDList
  3. Invalid TargetIDList, populated RELATIVE_PATH: .lnk executes RELATIVE_PATH and TargetIDList is rewritten

WORKING_DIR: This is the working directory the .lnk target will run from after the .lnk resolves and starts the target. If the .lnk needs to be rewritten for any reason (invalid TargetIDList for instance), this field may be overwritten.

COMMAND_LINE_ARGUMENTS: Up to 65,535 characters are available to give to the program as command line arguments. What I find interesting is you can include newlines, and something like powershell.exe -Command handles said newlines just fine. Makes it easy to run a whole formatted script.

ICON_LOCATION: This points to a resource that contains the icon you want to use for this shortcut. Normally this will be a .exe or .dll file.

ExtraData

This section consists of zero or more extra data blocks followed by a terminal block. It was designed to be extended in the future if needed, with each block denoting a size and having a special signature to identify what the block is.

We’ll go through each block below; I’ll highlight anything specifically special about any given block.

CONSOLE_PROPS: This contains various properties that could be applied to the console if you’re running an application under conhost.exe. In this new brave world of Windows terminal, this section appears to be ignored.

CONSOLE_FE_PROPS: This controls the code page for console applications. I didn’t experiment with this section.

DARWIN_PROPS: This serves as legacy support for install-on-demand. See this blog for more information. My favorite line of that article is by the famous Raymond Chen.
“What can you do with that descriptor? I have no idea, but you did ask for it.”

Seems like an area for future research.

ENVIRONMENT_PROPS: This sure would sound like environment variables that might get applied after the executable is loaded. That, however, is not the case; this is yet another string that can point to the file that is to be executed/opened.

ICON_ENVIRONMENT_PROPS: This alternative field points to a file path that could house the icon. I’ve not seen it be effectively used for that purpose, as setting it does not result in an icon being displayed.

KNOWN_FOLDER_PROPS: This block specifies the offset in bytes of a known folder GUID within the TargetIDList. I did not experiment with this block.

PROPERTY_STORE_PROPS: This can be used to store extra data properties which applications might want to reference within the .lnk. I’ve experimented with using this section for data smuggling, with varying degrees of success.

SHIM_PROPS: This section can be used to apply a compatibility shim to the started application. There are some interesting implications here, but I was not able to find a meaningful abuse.

SPECIAL_FOLDER_PROPS: While the same general purpose as KNOWN_FOLDER_PROPS, this is for special folders instead of known folders.

TRACKER_PROPS: In this block are some GUIDs and the NETBIOS name of the machine that created this .lnk when created through standard Windows utilities.

VISTA_AND_ABOVE_IDLIST_PROPS: This is a slightly different structure compared to TargetIDList. The only time I’ve observed it being used is in shortcuts that target a network-based resource, such as a file on an SMB share.

Learning .lnk Through C# Projects

To better understand and experiment with .lnk files, I created a few projects in C# (available at https://github.com/trustedsec/LnkMeMaybe).

  1. Lnk -> A library that implemented all Windows shortcut structures
  2. LnkUi -> A research-oriented UI to review and quickly modify Windows shortcuts
  3. LnkMeMaybe -> A CLI tool to weaponize any specific Windows shortcuts

Specifically, .NET 8 was targeted and the code was written such that it should be able to run on Windows, Mac, and Linux. And, through manually creating those tools, I gained an understanding of the various components of a .lnk file. There are, in my opinion, a couple interesting investigations paths related to .lnk files.

  1. Code paths that fire when an .lnk is previewed/viewed
  2. Code paths that fire on activation of an .lnk file

The first is far more interesting than the second, as those paths could lead to broad network effects simply by placing an .lnk on a corporate file share.

Tracing .lnk Preview Code Paths

To investigate the code that fires when a .lnk is viewed, we need to obtain a trace of that activity. Luckily, with tools such as procmon (https://learn.microsoft.com/en-us/sysinternals/downloads/procmon), getting this trace is easy.

First, I’ll configure procmon on a system and configure a filter to look for viewme.lnk.

Figure 1 - Configuring Procmon Filter

Second, I’ll open a file share where I intend to write a .lnk from a second system.

Third, from an unrelated system, I’ll drop a .lnk named viewme.lnk into the share opened on the first system.

Figure 2 - Dropping viewme.lnk

Finally, I’ll review the stack trace of the system opening the .lnk file, which should inform where disassembly should begin.

Figure 3 - Stack Trace of .lnk Opening

The first CreateFile call was locating and loading the shell handler. The second CreateFile call is the handler initializing. The disassembly for the offset shown above lands us within CShellLink::_LoadFromStream.

Figure 4 - Disassembly of windows.storage.dll

Looking at cross-references to _LoadFromStream, this appears to get called through most code paths that would sound like they are initializing this object.

Figure 5 - Cross-References to _LoadFromStream

Any code within this path that could have some potential effect is going to be highly valuable to us. This code was hit simply by viewing a folder with the associated .lnk in it. End-user interaction with the .lnk itself is not required.

From there, I spent a fair bit of time reversing this class. Skipping to the part of interest, we find ourselves at this block within _LoadFromStream:

Figure 6 – Check for Darwin Block and Call to _UpdateIconFromExpIconSz

In this case, the code is attempting to see if the .lnk has an ExtraData block that contains the signature 0xa0000006. That signature corresponds with DARWIN_PROPS. If the .lnk has a Darwin data block, the object completely ignores it and calls _UpdateIconFromExpIconSz. It’s possible I’m missing something though, because I really have no idea why it checks for a Darwin data block prior to that update call.

Figure 7 - UpdateIconFromExpIconSz Calling PathFileExistsW

Inside the next block the object has some checks, which I’ve found to not matter beyond having a valid .lnk file built. After that, this object checks for a data block with signature 0xa0000007. This corresponds to ICON_ENVIRONMENT_PROPS. The code indexes into this data block 268 bytes, which points right at the TargetUnicode field. If this field is populated, any environment variables are expanded in it and the result is passed to PathFileExistsW. This is our vulnerability trigger. 

In short, if you have a .lnk with a populated Darwin ExtraData block, and a populated icon environment data block, the system will attempt to open the path pointed to by the icon environment data block. This causes the system to authenticate out to the target, allowing for relay and various credential attacks.

Gaining Computer Credentials

Given this path is executed when this object loads the .lnk, what else might use this specific object when working with .lnk files? So far, I’ve found two other systems that leverage this object when working with shortcuts: Microsoft’s indexing service and Windows Defender.

Figure 8 - SearchProtocolHost.exe Stack Trace Opening .lnk

A full demo video of credentials being captured can be found at the bottom of the blog.

Tool Release

This blog largely covered the structure of an .lnk and one specific vulnerability that resulted from this research. Along with this blog post, we’re releasing the .lnk library, the UI to examine and modify .lnk files, and a CLI tool for generating bespoke .lnk files at https://github.com/trustedsec/LnkMeMaybe. The .lnk library itself isn’t unique or special, but I believe the UI and CLI tool are meaningful contributions that may be valuable for others performing research on .lnk files. The CLI tool is being released with subcommands that can generate a .lnk capable of triggering CVE-2026-25185 and a few examples that generate fairly standard .lnk files. This should give any future developers or internal shops a starting point if they wish to adopt this tooling. If you enjoy the CLI syntax and coding flow, it’s adopted from another TrustedSec framework, Titanis (https://github.com/trustedsec/Titanis).

Closing

Windows .lnk files continue to be a rich research area, largely due to their complexity and age. There are other issues I’ve found using this tooling that would not count as security vulnerabilities under Microsoft’s current guidelines. With that said, I hope this enables researchers to continue to expose flaws in the vast codebase making up .lnk files and hopefully reduces the attack surface they provide over time.

Reporting timeline:

Date

Outcome

December 10, 2025

Initial report

December 10, 2025

Assigned MSRC case number

January 20, 2026

Issue confirmed, development of fix starts

February 09, 2026

Impact classified as Spoofing, Severity Important; bounty awarded

February 10, 2026

Enters pre-release for March 10, 2026 security update

March 10, 2026

Patch released

Demo Video