
Analyzing Lnk File And Jump List Artifacts
- 275 installs
- 27.3k repo stars
- Updated August 2, 2026
- mukul975/anthropic-cybersecurity-skills
Analyzing LNK File and Jump List Artifacts is an agent skill that structures Windows LNK and Jump List forensic evidence into tabular case reports.
About
Analyzing LNK File and Jump List Artifacts is an agent skill that guides digital forensic examiners through documenting Windows shortcut and Recent Items evidence in a standardized report. Solo builders rarely need it unless they run security consulting or internal IR on Windows endpoints; for them it matters when proving what files and removable drives a user touched during a suspected compromise. The skill centers on filling structured tables—LNK file summary, Jump List summary by application, removable media references, and a findings narrative—so Claude or similar agents output court-ready structure instead of ad-hoc bullet lists. Use it when you have collected LNK/Jump List exports from an image or live response kit and need consistent case metadata (case number, examiner, evidence source) plus cross-referenced activity. It does not run parsers itself; it enforces the reporting workflow and field completeness that investigators expect in DFIR deliverables.
- Report tables for LNK targets, volume serials, and machine identifiers
- Jump List summary keyed by application AppID and access date ranges
- Removable-media correlation via drive letter, volume serial, and label
- Findings section for narrative conclusions from LNK/Jump List analysis
- Apache 2.0 licensed template aligned with Anthropic cybersecurity skills
Analyzing Lnk File And Jump List Artifacts by the numbers
- 275 all-time installs (skills.sh)
- +21 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #646 of 2,203 Security skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill analyzing-lnk-file-and-jump-list-artifactsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 275 |
|---|---|
| repo stars | ★ 27.3k |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 2, 2026 |
| Repository | mukul975/anthropic-cybersecurity-skills ↗ |
What it does
Structure Windows LNK and Jump List forensic findings into a consistent case report during incident response or endpoint investigations.
Who is it for?
Security analysts and consultants documenting Windows user-activity timelines from LNK and Jump List data in a formal case file.
Skip if: Skip if you're shipping SaaS and only need routine dependency scanning or OWASP checklists without endpoint forensics.
When should I use this skill?
You need to produce or complete a LNK File and Jump List Analysis Report with case metadata and evidence tables.
What you get
You get a completed LNK/Jump List analysis report with populated summary tables and a findings section ready for case notes or peer review.
- LNK File and Jump List Analysis Report with summary tables and findings narrative
By the numbers
- Four primary report sections: LNK summary, Jump List summary, removable media, and findings
Files
Analyzing LNK File and Jump List Artifacts
Overview
Windows LNK (shortcut) files and Jump Lists are critical forensic artifacts that provide evidence of file access, program execution, and user behavior. LNK files are created automatically when a user opens a file through Windows Explorer or the Open/Save dialog, storing metadata about the target file including its original path, timestamps, volume serial number, NetBIOS name, and MAC address of the host system. Jump Lists, introduced in Windows 7, extend this by maintaining per-application lists of recently and frequently accessed files. These artifacts persist even after the target files are deleted, making them invaluable for establishing that a user accessed specific files at specific times.
When to Use
- When investigating security incidents that require analyzing lnk file and jump list artifacts
- When building detection rules or threat hunting queries for this domain
- When SOC analysts need structured procedures for this analysis type
- When validating security monitoring coverage for related attack techniques
Prerequisites
- LECmd (Eric Zimmerman) for LNK file parsing
- JLECmd (Eric Zimmerman) for Jump List parsing
- Python 3.8+ with pylnk3 or LnkParse3 libraries
- Forensic image or triage collection from Windows system
- Timeline Explorer for CSV analysis
LNK File Locations
| Location | Description |
|---|---|
%USERPROFILE%\AppData\Roaming\Microsoft\Windows\Recent\ | Recent files accessed |
%USERPROFILE%\Desktop\ | User-created shortcuts |
%USERPROFILE%\AppData\Roaming\Microsoft\Windows\Start Menu\ | Start Menu shortcuts |
%USERPROFILE%\AppData\Roaming\Microsoft\Office\Recent\ | Office recent documents |
LNK File Structure
Shell Link Header (76 bytes)
| Offset | Size | Field |
|---|---|---|
| 0x00 | 4 | HeaderSize (always 0x0000004C) |
| 0x04 | 16 | LinkCLSID (always 00021401-0000-0000-C000-000000000046) |
| 0x14 | 4 | LinkFlags |
| 0x18 | 4 | FileAttributes |
| 0x1C | 8 | CreationTime (FILETIME) |
| 0x24 | 8 | AccessTime (FILETIME) |
| 0x2C | 8 | WriteTime (FILETIME) |
| 0x34 | 4 | FileSize of target |
| 0x38 | 4 | IconIndex |
| 0x3C | 4 | ShowCommand |
| 0x40 | 2 | HotKey |
Key Forensic Fields in LNK Files
- Target file timestamps: Creation, access, modification times of the referenced file
- Volume information: Serial number, drive type, volume label
- Network share information: UNC path, share name
- Machine identifiers: NetBIOS name, MAC address (from TrackerDataBlock)
- Distributed Link Tracking: Machine ID and object GUID
Analysis with EZ Tools
LECmd - LNK File Parser
# Parse all LNK files in Recent folder
LECmd.exe -d "C:\Evidence\Users\suspect\AppData\Roaming\Microsoft\Windows\Recent" --csv C:\Output --csvf lnk_analysis.csv
# Parse a single LNK file with full details
LECmd.exe -f "C:\Evidence\Users\suspect\Desktop\Confidential.docx.lnk" --json C:\Output
# Parse LNK files with additional detail levels
LECmd.exe -d "C:\Evidence\Users\suspect\AppData\Roaming\Microsoft\Windows\Recent" --csv C:\Output --csvf lnk_all.csv --allJLECmd - Jump List Parser
# Parse Automatic Jump Lists
JLECmd.exe -d "C:\Evidence\Users\suspect\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations" --csv C:\Output --csvf jumplists_auto.csv
# Parse Custom Jump Lists
JLECmd.exe -d "C:\Evidence\Users\suspect\AppData\Roaming\Microsoft\Windows\Recent\CustomDestinations" --csv C:\Output --csvf jumplists_custom.csv
# Parse all jump lists with detailed output
JLECmd.exe -d "C:\Evidence\Users\suspect\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations" --csv C:\Output --csvf jumplists_auto.csv --ldJump List Structure
Automatic Destinations (automaticDestinations-ms)
These are OLE Compound files (Structured Storage) identified by AppID hash in the filename:
| AppID Hash | Application |
|---|---|
| 5f7b5f1e01b83767 | Windows Explorer Pinned/Frequent |
| 1b4dd67f29cb1962 | Windows Explorer Recent |
| 9b9cdc69c1c24e2b | Notepad |
| a7bd71699cd38d1c | Notepad++ |
| 12dc1ea8e34b5a6 | Microsoft Paint |
| 7e4dca80246863e3 | Control Panel |
| 1cf97c38a5881255 | Microsoft Edge |
| f01b4d95cf55d32a | Windows Explorer |
| 9d1f905ce5044aee | Microsoft Excel |
| a4a5324453625195 | Microsoft Word |
| d00655d2aa12ff6d | Microsoft PowerPoint |
| bc03160ee1a59fc1 | Outlook |
Custom Destinations (customDestinations-ms)
Created when users pin items to application jump lists. These files contain sequential LNK entries.
Python Analysis Script
import struct
import os
from datetime import datetime, timedelta
FILETIME_EPOCH = datetime(1601, 1, 1)
def filetime_to_datetime(filetime_bytes: bytes) -> datetime:
"""Convert Windows FILETIME (100-ns intervals since 1601) to datetime."""
ft = struct.unpack("<Q", filetime_bytes)[0]
if ft == 0:
return None
return FILETIME_EPOCH + timedelta(microseconds=ft // 10)
def parse_lnk_header(lnk_path: str) -> dict:
"""Parse the Shell Link header from an LNK file."""
with open(lnk_path, "rb") as f:
header = f.read(76)
header_size = struct.unpack("<I", header[0:4])[0]
if header_size != 0x4C:
return {"error": "Invalid LNK header"}
link_flags = struct.unpack("<I", header[0x14:0x18])[0]
file_attrs = struct.unpack("<I", header[0x18:0x1C])[0]
result = {
"header_size": header_size,
"link_flags": hex(link_flags),
"file_attributes": hex(file_attrs),
"creation_time": filetime_to_datetime(header[0x1C:0x24]),
"access_time": filetime_to_datetime(header[0x24:0x2C]),
"write_time": filetime_to_datetime(header[0x2C:0x34]),
"file_size": struct.unpack("<I", header[0x34:0x38])[0],
"has_target_id_list": bool(link_flags & 0x01),
"has_link_info": bool(link_flags & 0x02),
"has_name": bool(link_flags & 0x04),
"has_relative_path": bool(link_flags & 0x08),
"has_working_dir": bool(link_flags & 0x10),
"has_arguments": bool(link_flags & 0x20),
"has_icon_location": bool(link_flags & 0x40),
}
return resultInvestigation Use Cases
Evidence of File Access
1. Parse LNK files from Recent folder to identify accessed documents 2. Cross-reference with MFT timestamps and USN Journal entries 3. Note that LNK files persist even after target files are deleted
Removable Media Access
1. LNK files referencing drive letters E:, F:, G: indicate removable media usage 2. Volume serial number in LNK identifies the specific device 3. MAC address in TrackerDataBlock identifies the source machine
Network Share Activity
1. LNK files with UNC paths (\\server\share) indicate network file access 2. NetBIOS name identifies the remote server 3. Timestamps establish when access occurred
Differences Between Windows 10 and Windows 11
Recent research (IEEE 2025) shows that Windows 11 produces different LNK and Jump List artifacts:
- Fewer automatic LNK files generated for certain file types
- Modified Jump List behavior for modern applications
- UWP/MSIX applications may not generate traditional Jump Lists
- Windows 11 Quick Access replaces some Recent functionality
References
- Shell Link Binary File Format: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/
- Magnet Forensics LNK Analysis: https://www.magnetforensics.com/blog/forensic-analysis-of-lnk-files/
- Jump Lists Forensics 2025: https://www.cybertriage.com/blog/jump-list-forensics-2025/
- Eric Zimmerman's LECmd/JLECmd: https://ericzimmerman.github.io/
Example Output
$ LECmd.exe -d "C:\Evidence\Users\jsmith\AppData\Roaming\Microsoft\Windows\Recent" --csv /analysis/lnk_output
LECmd v1.11.0 - LNK File Parser
================================
Processing 47 LNK files...
--- LNK File: Q4_Report.xlsx.lnk ---
Source: C:\Evidence\Users\jsmith\Recent\Q4_Report.xlsx.lnk
Target Path: C:\Users\jsmith\Downloads\Q4_Report.xlsm
Target Created: 2024-01-15 14:33:45 UTC
Target Modified: 2024-01-15 14:33:45 UTC
Target Accessed: 2024-01-15 14:35:12 UTC
File Size: 251,904 bytes
Drive Type: Fixed (C:)
Volume Serial: A4E7-3F21
Machine ID: DESKTOP-J5M1TH
MAC Address: 48:2A:E3:5C:9B:01
--- LNK File: update_client.exe.lnk ---
Source: C:\Evidence\Users\jsmith\Recent\update_client.exe.lnk
Target Path: C:\ProgramData\Updates\update_client.exe
Target Created: 2024-01-15 14:34:02 UTC
Target Modified: 2024-01-15 14:34:02 UTC
Target Accessed: 2024-01-15 14:36:30 UTC
File Size: 1,258,496 bytes
Drive Type: Fixed (C:)
Volume Serial: A4E7-3F21
Machine ID: DESKTOP-J5M1TH
Working Dir: C:\ProgramData\Updates
Arguments: --silent --no-update-check
Run Window: Hidden
======================================================================
$ JLECmd.exe -d "C:\Evidence\Users\jsmith\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations" --csv /analysis/jumplist_output
JLECmd v1.5.0 - Jump List Parser
==================================
Processing 23 AutomaticDestinations files...
--- Application: Microsoft Excel (AppID: 12dc1ea8e34b5a6) ---
Entries: 15
Most Recent:
Entry 0: C:\Users\jsmith\Downloads\Q4_Report.xlsm (2024-01-15 14:35:12 UTC)
Entry 1: \\FILESERV01\Finance\Budget_2024.xlsx (2024-01-14 09:22:30 UTC)
Entry 2: C:\Users\jsmith\Documents\Expenses\Dec2023.xlsx (2024-01-10 16:45:00 UTC)
--- Application: Windows Explorer (AppID: f01b4d95cf55d32a) ---
Entries: 28
Most Recent:
Entry 0: C:\ProgramData\Updates\ (2024-01-15 14:36:25 UTC)
Entry 1: E:\Backup\ (2024-01-15 15:30:00 UTC)
Entry 2: \\FILESERV01\HR\Employees\ (2024-01-15 16:12:45 UTC)
--- Application: cmd.exe (AppID: 9b9cdc69c1c24e2b) ---
Entries: 5
Most Recent:
Entry 0: C:\Windows\System32\cmd.exe (2024-01-15 14:36:00 UTC)
Summary:
Total LNK files processed: 47
Total Jump List entries: 156
Suspicious artifacts: 3 (hidden window execution, USB drive access, network shares)
CSV exported to: /analysis/lnk_output/ and /analysis/jumplist_output/LNK File and Jump List Analysis Report
Case Information
| Field | Value |
|---|---|
| Case Number | |
| Examiner | |
| Evidence Source |
LNK File Summary
| LNK File | Target Path | Target Created | Target Modified | Volume Serial | Machine ID |
|---|---|---|---|---|---|
Jump List Summary
| Application | AppID | Files Accessed | Date Range |
|---|---|---|---|
Removable Media References
| Drive Letter | Volume Serial | Volume Label | Files Accessed |
|---|---|---|---|
Findings
_(Summary of user activity established through LNK/Jump List analysis)_
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
API Reference: LNK File and Jump List Forensics
LECmd (Eric Zimmerman) - LNK Parser
Syntax
LECmd.exe -f <file.lnk> # Single file
LECmd.exe -d <directory> --all # All files in directory
LECmd.exe -d <dir> --csv <output_dir> # CSV export
LECmd.exe -d <dir> --json <output_dir> # JSON export
LECmd.exe -f <file.lnk> -q # Quiet mode
LECmd.exe -d <dir> -r # Only removable drivesOutput Fields
| Field | Description |
|---|---|
| SourceFile | Path to the .lnk file |
| TargetCreated | Target file creation timestamp |
| TargetModified | Target file modification timestamp |
| TargetAccessed | Target file access timestamp |
| FileSize | Target file size |
| RelativePath | Relative path to target |
| WorkingDirectory | Working directory for target |
| Arguments | Command-line arguments |
| LocalPath | Full local path to target |
| VolumeSerialNumber | Volume serial of target drive |
| DriveType | Fixed, Removable, Network |
| MachineID | NetBIOS name from tracker block |
| MacAddress | MAC from distributed tracker |
JLECmd (Eric Zimmerman) - Jump List Parser
Syntax
JLECmd.exe -f <jumplist_file> # Single file
JLECmd.exe -d <directory> # All jump lists
JLECmd.exe -d <dir> --csv <output> # CSV export
JLECmd.exe -d <dir> --fd # Full LNK details
JLECmd.exe -d <dir> --dumpTo <dir> # Extract embedded LNK filesJump List Locations
%APPDATA%\Microsoft\Windows\Recent\AutomaticDestinations\
%APPDATA%\Microsoft\Windows\Recent\CustomDestinations\LnkParse3 (Python)
Installation
pip install LnkParse3Usage
import LnkParse3
with open("shortcut.lnk", "rb") as f:
lnk = LnkParse3.lnk_file(f)
info = lnk.get_json()
print(info["data"]["relative_path"])
print(info["header"]["creation_time"])
print(info["link_info"]["local_base_path"])
# Extra data blocks
extra = info.get("extra", {})
tracker = extra.get("DISTRIBUTED_LINK_TRACKER_BLOCK", {})
print(tracker.get("machine_id"))
print(tracker.get("mac_address"))Shell Link Binary Format (MS-SHLLINK)
Header Structure (76 bytes)
| Offset | Size | Field |
|---|---|---|
| 0 | 4 | HeaderSize (0x0000004C) |
| 4 | 16 | LinkCLSID |
| 20 | 4 | LinkFlags |
| 24 | 4 | FileAttributes |
| 28 | 8 | CreationTime (FILETIME) |
| 36 | 8 | AccessTime (FILETIME) |
| 44 | 8 | WriteTime (FILETIME) |
| 52 | 4 | FileSize |
| 56 | 4 | IconIndex |
| 60 | 4 | ShowCommand |
Common App IDs (Jump Lists)
| App ID | Application |
|---|---|
| 1b4dd67f29cb1962 | Windows Explorer |
| 5d696d521de238c3 | Google Chrome |
| ecd21b58c2f65a4f | Firefox |
| 1bc392b8e104a00e | Remote Desktop (mstsc) |
| b8ab77100df80ab2 | Microsoft Word |
| cfb56c56fa0f0478 | PuTTY |
| b74736c2bd8cc8a5 | WinSCP |
Suspicious LNK Indicators
| Pattern | Concern |
|---|---|
| PowerShell in arguments | Script execution via shortcut |
| cmd.exe /c in target | Command execution chain |
| UNC path to IP | Network-based payload delivery |
| Base64 encoded arguments | Obfuscated commands |
| mshta/wscript target | Living-off-the-land execution |
Standards - LNK File and Jump List Forensics
Standards
- MS-SHLLINK: Shell Link Binary File Format (Microsoft Open Specifications)
- NIST SP 800-86: Guide to Integrating Forensic Techniques
- SWGDE Best Practices for Computer Forensics
Tools
- LECmd (Eric Zimmerman): LNK file parser
- JLECmd (Eric Zimmerman): Jump List parser
- LnkParse3 (Python): Cross-platform LNK parser
- Magnet AXIOM: Commercial forensic tool with LNK/Jump List support
Key Artifact Locations
- Recent files: %APPDATA%\Microsoft\Windows\Recent\
- AutomaticDestinations: %APPDATA%\Microsoft\Windows\Recent\AutomaticDestinations\
- CustomDestinations: %APPDATA%\Microsoft\Windows\Recent\CustomDestinations\
- Office Recent: %APPDATA%\Microsoft\Office\Recent\
MITRE ATT&CK Mappings
- T1547.009 - Shortcut Modification
- T1204.002 - User Execution: Malicious File
Workflows - LNK and Jump List Analysis
Workflow 1: User File Access Investigation
Collect LNK files from Recent directory
|
Parse with LECmd to CSV
|
Filter by target path for specific files/locations
|
Extract timestamps, volume serial, NetBIOS name
|
Correlate with MFT and Event Log timestamps
|
Document file access timelineWorkflow 2: Jump List Application Activity
Collect AutomaticDestinations and CustomDestinations
|
Parse with JLECmd to CSV
|
Map AppID hashes to applications
|
Extract embedded LNK entries per application
|
Build per-application file access timeline
|
Identify removable media and network pathsWorkflow 3: Removable Media Usage
Filter LNK files for drive letters (E:, F:, G:)
|
Extract volume serial numbers
|
Match with SYSTEM registry USBSTOR entries
|
Identify specific USB devices accessed
|
Build user-device-file timeline#!/usr/bin/env python3
"""Windows LNK file and Jump List artifact analysis agent.
Parses Windows Shell Link (.lnk) files and Jump List artifacts to extract
file access evidence, program execution history, and user activity timelines.
Uses LnkParse3 for binary parsing and supports LECmd/JLECmd CSV output analysis.
"""
import struct
import os
import sys
import json
import hashlib
import datetime
import re
import glob as glob_mod
try:
import LnkParse3
HAS_LNKPARSE = True
except ImportError:
HAS_LNKPARSE = False
def compute_hash(filepath):
"""Compute SHA-256 hash of file."""
sha256 = hashlib.sha256()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
sha256.update(chunk)
return sha256.hexdigest()
def parse_lnk_with_lnkparse3(filepath):
"""Parse LNK file using LnkParse3 library."""
if not HAS_LNKPARSE:
return {"error": "LnkParse3 not installed. pip install LnkParse3"}
with open(filepath, "rb") as f:
lnk = LnkParse3.lnk_file(f)
info = lnk.get_json()
result = {
"target_path": info.get("data", {}).get("relative_path", ""),
"working_dir": info.get("data", {}).get("working_directory", ""),
"arguments": info.get("data", {}).get("command_line_arguments", ""),
"icon_location": info.get("data", {}).get("icon_location", ""),
"description": info.get("data", {}).get("description", ""),
}
header = info.get("header", {})
result["creation_time"] = header.get("creation_time", "")
result["access_time"] = header.get("access_time", "")
result["write_time"] = header.get("write_time", "")
result["file_size"] = header.get("file_size", 0)
result["file_flags"] = header.get("file_attributes", "")
link_info = info.get("link_info", {})
if link_info:
result["local_base_path"] = link_info.get("local_base_path", "")
result["volume_serial"] = link_info.get("volume_serial_number", "")
result["volume_label"] = link_info.get("volume_label", "")
result["drive_type"] = link_info.get("drive_type", "")
extra = info.get("extra", {})
if extra:
tracker = extra.get("DISTRIBUTED_LINK_TRACKER_BLOCK", {})
if tracker:
result["machine_id"] = tracker.get("machine_id", "")
result["mac_address"] = tracker.get("mac_address", "")
result["droid_volume_id"] = tracker.get("droid_volume_identifier", "")
result["droid_file_id"] = tracker.get("droid_file_identifier", "")
return result
def parse_lnk_header_raw(filepath):
"""Parse LNK file header manually from raw bytes."""
with open(filepath, "rb") as f:
data = f.read()
if len(data) < 76:
return {"error": "File too small for LNK header"}
# Shell Link Header (76 bytes)
header_size = struct.unpack_from("<I", data, 0)[0]
if header_size != 0x4C:
return {"error": f"Invalid header size: {header_size:#x} (expected 0x4C)"}
# CLSID check: 00021401-0000-0000-C000-000000000046
clsid = data[4:20]
expected_clsid = bytes.fromhex("01140200000000c0000000000000046".replace("0", "0"))
link_flags = struct.unpack_from("<I", data, 20)[0]
file_attrs = struct.unpack_from("<I", data, 24)[0]
creation_time = filetime_to_datetime(struct.unpack_from("<Q", data, 28)[0])
access_time = filetime_to_datetime(struct.unpack_from("<Q", data, 36)[0])
write_time = filetime_to_datetime(struct.unpack_from("<Q", data, 44)[0])
file_size = struct.unpack_from("<I", data, 52)[0]
icon_index = struct.unpack_from("<I", data, 56)[0]
show_command = struct.unpack_from("<I", data, 60)[0]
result = {
"header_size": header_size,
"link_flags": f"0x{link_flags:08X}",
"file_attributes": f"0x{file_attrs:08X}",
"creation_time": creation_time,
"access_time": access_time,
"write_time": write_time,
"target_file_size": file_size,
"icon_index": icon_index,
"show_command": {1: "Normal", 3: "Maximized", 7: "Minimized"}.get(show_command, str(show_command)),
"flags_decoded": decode_link_flags(link_flags),
}
return result
def filetime_to_datetime(filetime):
"""Convert Windows FILETIME to ISO string."""
if filetime == 0:
return "N/A"
try:
epoch = datetime.datetime(1601, 1, 1)
delta = datetime.timedelta(microseconds=filetime // 10)
return (epoch + delta).isoformat() + "Z"
except (OverflowError, OSError):
return "Invalid"
def decode_link_flags(flags):
"""Decode Shell Link header flags."""
flag_names = {
0x00000001: "HasLinkTargetIDList",
0x00000002: "HasLinkInfo",
0x00000004: "HasName",
0x00000008: "HasRelativePath",
0x00000010: "HasWorkingDir",
0x00000020: "HasArguments",
0x00000040: "HasIconLocation",
0x00000080: "IsUnicode",
0x00000100: "ForceNoLinkInfo",
0x00000800: "RunInSeparateProcess",
0x00001000: "HasDarwinID",
0x00002000: "RunAsUser",
0x00004000: "HasExpIcon",
0x00020000: "HasExpString",
0x00040000: "RunInSeparateProcess",
0x00080000: "PreferEnvironmentPath",
0x00200000: "DisableLinkPathTracking",
0x00800000: "EnableTargetMetadata",
0x04000000: "AllowLinkToLink",
}
decoded = []
for bit, name in flag_names.items():
if flags & bit:
decoded.append(name)
return decoded
JUMP_LIST_APP_IDS = {
"1b4dd67f29cb1962": "Windows Explorer",
"5d696d521de238c3": "Google Chrome",
"9b9cdc69c1c24e2b": "Notepad",
"f01b4d95cf55d32a": "Windows Explorer",
"a7bd71699cd38d1c": "Notepad++",
"918e0ecb43d17e23": "Notepad (Win10)",
"12dc1ea8e34b5a6": "Microsoft Paint",
"b8ab77100df80ab2": "Microsoft Word 2019",
"a4a5324453625195": "Microsoft Excel 2019",
"bc0c37e84e063727": "Microsoft PowerPoint 2019",
"9839aec31243a928": "Microsoft Outlook 2019",
"fb3b0dbfee58fac8": "Acrobat Reader DC",
"ecd21b58c2f65a4f": "Firefox",
"1bc392b8e104a00e": "Remote Desktop (mstsc)",
"b91050d8b077a4e8": "WinRAR",
"290532160612e071": "Windows Media Player",
"28c8b86deab549a1": "Internet Explorer",
"7e4dca80246863e3": "Control Panel",
"e2a593822e01aed3": "Snipping Tool",
"b74736c2bd8cc8a5": "WinSCP",
"cfb56c56fa0f0478": "PuTTY",
}
def scan_jump_lists(jump_list_dir):
"""Scan Jump List directory for automatic and custom destinations."""
results = []
auto_pattern = os.path.join(jump_list_dir, "*.automaticDestinations-ms")
custom_pattern = os.path.join(jump_list_dir, "*.customDestinations-ms")
for jl_file in sorted(glob_mod.glob(auto_pattern) + glob_mod.glob(custom_pattern)):
basename = os.path.basename(jl_file)
app_id = basename.split(".")[0]
jl_type = "automatic" if "automatic" in basename else "custom"
app_name = JUMP_LIST_APP_IDS.get(app_id, "Unknown Application")
results.append({
"file": basename,
"app_id": app_id,
"app_name": app_name,
"type": jl_type,
"size": os.path.getsize(jl_file),
"modified": datetime.datetime.fromtimestamp(
os.path.getmtime(jl_file)).isoformat(),
})
return results
def detect_suspicious_lnk(parsed_lnk):
"""Detect suspicious characteristics in LNK files."""
findings = []
args = parsed_lnk.get("arguments", "")
target = parsed_lnk.get("target_path", "") + " " + parsed_lnk.get("local_base_path", "")
suspicious_patterns = [
(r"powershell", "PowerShell execution via LNK"),
(r"cmd\.exe\s*/c", "Command prompt execution via LNK"),
(r"mshta", "MSHTA execution (HTA payload)"),
(r"certutil.*-decode", "CertUtil decode (file download)"),
(r"bitsadmin.*transfer", "BitsAdmin file download"),
(r"regsvr32.*scrobj", "Regsvr32 COM scriptlet execution"),
(r"wscript|cscript", "Script host execution"),
(r"\\\\[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\\", "UNC path to IP address"),
(r"http[s]?://", "URL in LNK arguments"),
(r"-enc\s+[A-Za-z0-9+/=]{20,}", "Base64-encoded PowerShell"),
]
combined = f"{target} {args}".lower()
for pattern, description in suspicious_patterns:
if re.search(pattern, combined, re.IGNORECASE):
findings.append({"indicator": description, "pattern": pattern})
if parsed_lnk.get("drive_type") == "DRIVE_REMOTE":
findings.append({"indicator": "Target on network drive", "pattern": "DRIVE_REMOTE"})
return findings
def scan_lnk_directory(directory):
"""Scan directory for LNK files and analyze each."""
results = []
for lnk_file in sorted(glob_mod.glob(os.path.join(directory, "*.lnk"))):
parsed = parse_lnk_with_lnkparse3(lnk_file) if HAS_LNKPARSE else parse_lnk_header_raw(lnk_file)
suspicious = detect_suspicious_lnk(parsed)
results.append({
"file": os.path.basename(lnk_file),
"sha256": compute_hash(lnk_file),
"parsed": parsed,
"suspicious": suspicious,
})
return results
if __name__ == "__main__":
print("=" * 60)
print("Windows LNK & Jump List Forensics Agent")
print("Shell Link parsing, Jump List analysis, suspicious detection")
print("=" * 60)
target = sys.argv[1] if len(sys.argv) > 1 else None
if not target or not os.path.exists(target):
print("\n[DEMO] Usage:")
print(" python agent.py <file.lnk> # Analyze single LNK")
print(" python agent.py <directory> # Scan directory for LNK/JumpList")
print(f"\n LnkParse3 available: {HAS_LNKPARSE}")
sys.exit(0)
if os.path.isfile(target) and target.lower().endswith(".lnk"):
print(f"\n[*] Analyzing: {target}")
print(f"[*] SHA-256: {compute_hash(target)}")
if HAS_LNKPARSE:
parsed = parse_lnk_with_lnkparse3(target)
else:
parsed = parse_lnk_header_raw(target)
print("\n--- LNK Properties ---")
for k, v in parsed.items():
print(f" {k}: {v}")
suspicious = detect_suspicious_lnk(parsed)
if suspicious:
print("\n--- Suspicious Indicators ---")
for s in suspicious:
print(f" [!] {s['indicator']}")
elif os.path.isdir(target):
print(f"\n[*] Scanning directory: {target}")
lnk_results = scan_lnk_directory(target)
print(f"[*] Found {len(lnk_results)} LNK files")
for r in lnk_results[:20]:
print(f" {r['file']}: {r['parsed'].get('target_path', r['parsed'].get('local_base_path', '?'))}")
for s in r.get("suspicious", []):
print(f" [!] {s['indicator']}")
jl_dir = os.path.join(target, "AutomaticDestinations")
if not os.path.isdir(jl_dir):
jl_dir = target
jl_results = scan_jump_lists(jl_dir)
if jl_results:
print(f"\n--- Jump Lists ({len(jl_results)}) ---")
for jl in jl_results:
print(f" {jl['app_name']:30s} [{jl['type']}] {jl['app_id']}")
print(f"\n{json.dumps({'lnk_count': len(lnk_results) if os.path.isdir(target) else 1}, indent=2)}")
#!/usr/bin/env python3
"""
LNK File and Jump List Forensic Analyzer
Parses LNK file headers and extracts forensic metadata including
target paths, timestamps, volume information, and machine identifiers.
"""
import struct
import os
import sys
import json
import csv
from datetime import datetime, timedelta
from pathlib import Path
FILETIME_EPOCH = datetime(1601, 1, 1)
def filetime_to_datetime(ft_bytes: bytes):
"""Convert Windows FILETIME to datetime."""
ft = struct.unpack("<Q", ft_bytes)[0]
if ft == 0:
return None
try:
return FILETIME_EPOCH + timedelta(microseconds=ft // 10)
except (OverflowError, OSError):
return None
def parse_lnk_file(filepath: str) -> dict:
"""Parse a Windows LNK file and extract forensic metadata."""
with open(filepath, "rb") as f:
data = f.read()
if len(data) < 76:
return {"error": "File too small for LNK header"}
header_size = struct.unpack("<I", data[0:4])[0]
if header_size != 0x4C:
return {"error": "Invalid LNK header signature"}
link_flags = struct.unpack("<I", data[0x14:0x18])[0]
file_attrs = struct.unpack("<I", data[0x18:0x1C])[0]
result = {
"file": filepath,
"file_size_lnk": len(data),
"creation_time": str(filetime_to_datetime(data[0x1C:0x24])),
"access_time": str(filetime_to_datetime(data[0x24:0x2C])),
"write_time": str(filetime_to_datetime(data[0x2C:0x34])),
"target_file_size": struct.unpack("<I", data[0x34:0x38])[0],
"flags": {
"has_target_id_list": bool(link_flags & 0x01),
"has_link_info": bool(link_flags & 0x02),
"has_name": bool(link_flags & 0x04),
"has_relative_path": bool(link_flags & 0x08),
"has_working_dir": bool(link_flags & 0x10),
"has_arguments": bool(link_flags & 0x20),
"has_icon_location": bool(link_flags & 0x40),
},
"attributes": {
"readonly": bool(file_attrs & 0x01),
"hidden": bool(file_attrs & 0x02),
"system": bool(file_attrs & 0x04),
"directory": bool(file_attrs & 0x10),
"archive": bool(file_attrs & 0x20),
}
}
return result
def scan_directory(lnk_dir: str, output_dir: str) -> str:
"""Scan a directory for LNK files and generate analysis report."""
os.makedirs(output_dir, exist_ok=True)
results = []
for root, dirs, files in os.walk(lnk_dir):
for fname in files:
if fname.lower().endswith(".lnk"):
filepath = os.path.join(root, fname)
parsed = parse_lnk_file(filepath)
results.append(parsed)
report_path = os.path.join(output_dir, "lnk_analysis_report.json")
with open(report_path, "w") as f:
json.dump({
"analysis_timestamp": datetime.now().isoformat(),
"source_directory": lnk_dir,
"total_lnk_files": len(results),
"files": results
}, f, indent=2, default=str)
print(f"[*] Analyzed {len(results)} LNK files")
print(f"[*] Report: {report_path}")
return report_path
def main():
if len(sys.argv) < 3:
print("Usage: python process.py <lnk_directory> <output_dir>")
sys.exit(1)
scan_directory(sys.argv[1], sys.argv[2])
if __name__ == "__main__":
main()
Related skills
How it compares
Use as a structured DFIR report template instead of asking the agent for free-form incident narrative without evidence tables.
FAQ
Who is analyzing-lnk-file-and-jump-list-artifacts for?
It is for forensic examiners, IR consultants, and security-focused developers who investigate Windows endpoints and must document LNK and Jump List evidence in a repeatable format.
When should I use analyzing-lnk-file-and-jump-list-artifacts?
Use it during Ship-phase security work when you are writing up Windows artifact analysis after collection—for example after imaging a laptop in an insider-threat review or validating removable-media usage in an IR engagement.
Is analyzing-lnk-file-and-jump-list-artifacts safe to install?
It is a reporting template skill without embedded exploit code; review the Security Audits panel on this Prism page and only point agents at evidence you are authorized to handle.