Overview
Windows shortcut files (.lnk) are binary files that contain references to target files, folders, or applications along with rich metadata. LNK parsers are forensic tools that extract this metadata, which includes the target file path, file size, MAC timestamps (Modified, Accessed, Created) of the target at the time the shortcut was created, volume serial numbers, network share paths, machine identifiers, and the MAC address of the system that created the shortcut. LNK files are automatically created by Windows when users open files and are stored in the Recent Items folder, making them valuable artifacts for proving file access.
In digital forensics, LNK files provide evidence of file interaction even after the original files have been deleted. They reveal what files a user accessed, when they accessed them, the original file locations (including removable media and network shares), and sometimes the identity of the computer that created the file. LNK files persist in %APPDATA%\Microsoft\Windows\Recent\, %APPDATA%\Microsoft\Office\Recent\, the Desktop, and other locations. Multiple tools exist for parsing LNK files, including Eric Zimmerman’s LECmd, LnkParse3 (Python), and Exiftool, each offering different output formats and capabilities.
Installation
LECmd (Eric Zimmerman)
# Download LECmd
Invoke-WebRequest -Uri "https://f001.backblazeb2.com/file/EricZimmermanTools/net6/LECmd.zip" -OutFile LECmd.zip
Expand-Archive LECmd.zip -DestinationPath C:\Tools\LECmd
# Verify
C:\Tools\LECmd\LECmd.exe --help
LnkParse3 (Python)
# Install via pip
pip install LnkParse3
# Verify
lnk_parse --help
# Or use as Python library
python3 -c "import LnkParse3; print('LnkParse3 loaded')"
# Linux
sudo apt install libimage-exiftool-perl
# macOS
brew install exiftool
# Windows (Chocolatey)
choco install exiftool
Core Commands
LECmd
| Command | Description |
|---|
LECmd.exe -f <file> | Parse a single LNK file |
LECmd.exe -d <directory> | Parse all LNK files in directory |
LECmd.exe --csv <outdir> | Output results as CSV |
LECmd.exe --json <outdir> | Output results as JSON |
LECmd.exe --all | Show all available information |
LECmd.exe -q | Quiet mode (less verbose) |
# Parse single LNK file
LECmd.exe -f "C:\Users\analyst\Recent\document.lnk"
# Parse entire Recent directory
LECmd.exe -d "C:\Users\analyst\AppData\Roaming\Microsoft\Windows\Recent" --csv C:\Analysis\
# Parse all LNK files on a drive
LECmd.exe -d "C:\" --csv C:\Analysis\ -q
# Parse with JSON output
LECmd.exe -d "C:\Users\analyst\Recent\" --json C:\Analysis\
# Parse from mounted forensic image
LECmd.exe -d "E:\Users\suspect\AppData\Roaming\Microsoft\Windows\Recent" --csv C:\Evidence\
LnkParse3 (Command Line)
# Parse single file
lnk_parse document.lnk
# Parse with full detail
lnk_parse -a document.lnk
# JSON output
lnk_parse -j document.lnk
# Parse all LNK files in directory
for f in /evidence/Recent/*.lnk; do
echo "=== $f ==="
lnk_parse "$f"
done
LnkParse3 (Python API)
import LnkParse3
# Parse a LNK file
with open("document.lnk", "rb") as f:
lnk = LnkParse3.lnk_file(f)
# Get parsed data as dictionary
data = lnk.get_json()
# Access specific fields
print(f"Target: {lnk.lnk_command}")
print(f"Arguments: {lnk.arguments}")
print(f"Working Dir: {lnk.working_directory}")
# Access header information
header = lnk.header
print(f"Creation Time: {header.creation_time()}")
print(f"Access Time: {header.access_time()}")
print(f"Write Time: {header.write_time()}")
print(f"File Size: {header.file_size()}")
# Access link info
if lnk.link_info:
print(f"Local Path: {lnk.link_info.local_base_path()}")
print(f"Volume Serial: {lnk.link_info.volume_serial_number()}")
# Parse LNK file with Exiftool
exiftool document.lnk
# JSON output
exiftool -j document.lnk
# Specific fields
exiftool -TargetFileDOSName -LocalBasePath -VolumeSerialNumber document.lnk
# Batch processing
exiftool -j /evidence/Recent/*.lnk > all_lnk_metadata.json
# Tab-separated output
exiftool -T -FileName -LocalBasePath -FileCreateDate -FileModifyDate /evidence/Recent/*.lnk
| Field | Description |
|---|
| Creation Time | When the TARGET file was created (at time of LNK creation) |
| Access Time | When the TARGET file was last accessed |
| Write Time | When the TARGET file was last modified |
| File Size | Size of the target file |
| File Attributes | Archive, Hidden, System, etc. |
| Icon Index | Icon associated with the shortcut |
| Show Window | Normal, Minimized, Maximized |
Link Info
| Field | Description |
|---|
| Local Base Path | Full path to target on local volume |
| Volume Label | Volume label of the drive |
| Volume Serial Number | Serial number of the volume |
| Drive Type | Fixed, Removable, Network, CD-ROM |
| Network Share Path | UNC path if target is on network |
| Net Name | Network share name |
| Field | Description |
|---|
| Machine ID (NetBIOS) | Computer name where LNK was created |
| MAC Address | Network adapter MAC of creating system |
| Droid Volume ID | NTFS Object ID volume identifier |
| Droid File ID | NTFS Object ID file identifier |
| Birth Droid Volume ID | Original volume where file was created |
| Birth Droid File ID | Original file ID when first created |
Common LNK Locations
# User-specific LNK files
%APPDATA%\Microsoft\Windows\Recent\ # Recent files
%APPDATA%\Microsoft\Windows\Recent\AutomaticDestinations\ # Jump lists
%APPDATA%\Microsoft\Windows\Recent\CustomDestinations\ # Custom jump lists
%APPDATA%\Microsoft\Office\Recent\ # Office recent files
%USERPROFILE%\Desktop\ # Desktop shortcuts
%APPDATA%\Microsoft\Windows\Start Menu\Programs\ # Start menu
# System-wide
C:\ProgramData\Microsoft\Windows\Start Menu\ # All users start menu
Analysis Techniques
Building File Access Timeline
# Parse all user Recent folders
$users = Get-ChildItem "C:\Users" -Directory
foreach ($user in $users) {
$recentPath = Join-Path $user.FullName "AppData\Roaming\Microsoft\Windows\Recent"
if (Test-Path $recentPath) {
LECmd.exe -d $recentPath --csv "C:\Analysis\LNK\" -q
}
}
# Analyze CSV output
Import-Csv "C:\Analysis\LNK\*_LECmd_Output.csv" |
Sort-Object SourceCreated -Descending |
Select-Object SourceFile, TargetCreated, TargetModified, TargetAccessed,
LocalPath, VolumeSerialNumber, MachineID |
Format-Table
# Find LNK files pointing to removable media
Import-Csv "C:\Analysis\LNK\*_LECmd_Output.csv" |
Where-Object { $_.DriveType -eq "Removable" } |
Select-Object SourceFile, LocalPath, VolumeLabel, VolumeSerialNumber,
TargetCreated, TargetModified |
Format-Table
Network Share Access Evidence
# Find LNK files pointing to network shares
Import-Csv "C:\Analysis\LNK\*_LECmd_Output.csv" |
Where-Object { $_.NetworkShareName -ne "" } |
Select-Object SourceFile, NetworkShareName, LocalPath,
TargetModified, MachineID |
Format-Table
Batch Python Analysis
#!/usr/bin/env python3
"""Analyze all LNK files in a directory."""
import os
import json
import LnkParse3
from datetime import datetime
def analyze_lnk_directory(lnk_dir):
results = []
for filename in os.listdir(lnk_dir):
if not filename.lower().endswith('.lnk'):
continue
filepath = os.path.join(lnk_dir, filename)
try:
with open(filepath, 'rb') as f:
lnk = LnkParse3.lnk_file(f)
results.append({
'lnk_file': filename,
'target': lnk.lnk_command or '',
'arguments': lnk.arguments or '',
'working_dir': lnk.working_directory or '',
'creation_time': str(lnk.header.creation_time()),
'access_time': str(lnk.header.access_time()),
'write_time': str(lnk.header.write_time()),
'file_size': lnk.header.file_size(),
})
except Exception as e:
results.append({'lnk_file': filename, 'error': str(e)})
return results
results = analyze_lnk_directory('/evidence/Recent/')
print(json.dumps(results, indent=2))
Troubleshooting
| Issue | Solution |
|---|
| Cannot parse LNK file | Verify file is valid LNK (starts with 4C 00 00 00 magic bytes) |
| Missing timestamp data | Some LNK files have zeroed timestamps; this is normal for some auto-generated shortcuts |
| Encoding issues | LNK files may contain Unicode strings; ensure tool supports Unicode output |
| Corrupted LNK file | Try multiple parsers; some handle corruption better than others |
| Missing network info | Network share info only present if target was on a network path |
| MAC address not found | Extra data blocks are optional; not all LNK files contain tracker data |
| Permission denied | Run as Administrator or copy LNK files to writable location first |
| LECmd CSV empty | Verify the -d path contains actual .lnk files (not just subdirectories) |