Exploring Windows Artifacts : LNK Files

I decided to start a new blog post series that will focus on windows forensic artifacts. In these posts, I will try to go through all windows artifacts (at least until I get bored 😁), understand its structure, and write a rust parser for them. You may ask, hey handsome guy, why reinvent the wheel? and I will say for two responses:

  • I am curious how windows artifacts work.
  • I like rust 🦀 , and I want to write my parsers to learn rust more and to have parsers in rust.

And with that out of the way, let’s start with the first artifact I looked into, which is LNK files.

What are LNK Files?

Windows uses LNK files to create a shortcut or link to a file or folder. LNK files could be created manually or automatically generated by windows. Windows generates many LNK files in different places to track the latest files opened by the user, The most known location for DFIR people is C:\Users\%username%\AppData\Roaming\Microsoft\Windows\Recent. This location contains many LNK files for the most recent files and folders opened by the user.

What information can we extract from LNK files ?

Here is the information we can extract from LNK files:

Name Description
Target File Flags The flags of the target the LNK file is pointing to (ARCHIVE, DIRECTORY, etc)
Target File MAC times The MAC times (last modification, last access and creation time) for the target file
Target File Size The size in byte for the target file (0 if it is a folder)
Target Path MAC times The MAC times for all directories to the target. For example “C:\windows\temp\test.txt” will have three MAC times for each “windows” and “temp” directories and “test.txt” file.
MFT entry MFT entry for target file and for all directories to the target. For example “C:\windows\temp\test.txt” will have MFT entry for “windows”, “temp” and “test.txt”
Volume Info Volume Type (Fixed, Removable, etc) and Volume serial number (execute vol C: to get volume serial number for the C volume)
Target Full Path Full path to the target
UNC Path full UNC path if the target is on a share
Hostname Hostname of the machine the LNK file was created on
File Droid Target file droid (Object ID) and file birth droid

LNK File Structure

LNK file structure is documented very well, so I am not going to explain all of the data in each struct, However I will explain the data you can extract from each struct. If you want to learn everything about these structs, I will leave a couple of references at the end of the blog post. The following are the most essential structs that builds the LNK file:

  • ShellLinkHeader
  • LinkTargetIDList (Optional)
  • LinkInfo
  • VolumeID (Optional)
  • CommonNetworkRelativeLink (Optional)
  • StringData (Optional) (array)
  • ExtraData (Optional)

SHELLLINKHEADER

The ShellLinkHeader structure contains identification information, timestamps, and flags that specify the presence of optional structures. The following table explains some fields of this struct :

Field Name Type Details
Link CLSID GUID A GUID that identifies the LNK file. This should always be {00021401-0000-0000-C000-000000000046}
LinkFlags Struct This struct contains flags that specify the presens of an optional struct, what type of data is present on this LNK file and more
FileAttributes Struct Contains the file attributes for the target file (the file referenced by the LNK file)
Creation TIme FILETIME Creation time for the target file
Access Time FILETIME Access time for the target file
Write Time FILETIME Last modification time for the target file
FileSize u32 File size in bytes

LINKTARGETIDLIST

The LinkTargetIDList structure specifies the target of the link. The presence of this optional structure is specified by the HasLinkTargetIDList bit in LinkFlags field in ShellLinkHeader struct. The following table explains some fields of this struct:

Field Name Type Details
IDListSize u16 The size of the IDList struct
IDList [ShellItem] Array of shell items that contains information about the target file
TerminalID u16 This value is always \x00\x00

The following are some of the shell items found on LNK files:

  • RootShellItem : Contains CLSID which specifies the root of the target file. You can check all of the available CLSIDs by checking the following key on the registry HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\. This registry key contains a list of CLSIDs, So if you want to check the CLSID {20D04FE0-3AEA-1069-A2D8-08002B30309D} we can execute the following command:
reg query HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{20D04FE0-3AEA-1069-A2D8-08002B30309D} /ve

  • VolumeShellItem : Contains information about the volume which the target file reside in.
  • FileEntryShellIteam : Contains information about a file or directory, such as:
    • File/Directory name
    • File size in bytes
    • File/Directory MAC times
    • File/Directory MFT entry and sequence number

LINKINFO

The LinkInfo structure specifies information necessary to resolve a link target if it is not found in its original location. This includes information about the volume that the target was stored on, the mapped drive letter, and a Universal Naming Convention (UNC) form of the path if one existed when the link was created.

VolumeID

The VolumeID structure specifies information about the volume that a link target was on when the link was created. The following table contains the most important fields on this struct:

Field Name Type Details
DriveType u32 (enum) Specify the type of the drive (fixed, removable, etc)
DriveSerialNumber u32 Specifies the drive serial number of the volume the link target is stored on
VolumeLabel String The volume label of the volume the link target is stored on

The CommonNetworkRelativeLink structure specifies information about the network location where a link target is stored, including the mapped drive letter and the UNC path prefix. The following table contains the most important fields on this struct:

Field Name Type Details
NetName String Specify a share path
DeviceName String Specify the file/folder name

The full path to the target file/folder is {Netname}\{DeviceName}

STRINGDATA

StringData refers to a set of structures that convey user interface and path identification information. The presence of these optional structures is controlled by LinkFlags in the ShellLinkHeader.

Field Name Details Available if
NAME_STRING An optional structure that specifies a description of the shortcut that is displayed to end users to identify the purpose of the shell link HasName flag is set
RELATIVE_PATH An optional structure that specifies the location of the link target relative to the file that contains the shell link HasRelativePath flag is set
WORKING_DIR An optional structure that specifies the file system path of the working directory to be used when activating the link target HasWorkingDir flag is set
COMMAND_LINE_ARGUMENTS An optional structure that stores the command-line arguments that are specified when activating the link target HasArguments flag is set
ICON_LOCATION An optional structure that specifies the location of the icon to be used when displaying a shell link item in an icon view HasIconLocation flag is set

EXTRADATA

ExtraData refers to a set of structures that convey additional information about a link target. These optional structures can be present in an extra data section that is appended to the basic Shell Link Binary File Format.

What did I learned from this ?

During the parsing, I learned a lot about the artifact and about parsing binary data. Here are some of my findings:

  • LNK file contain FIleReference (MFT entry/sequence number)
  • MS-DOS date and time timezone is not always UTC
  • MS-DOS date and time resolution is 2 seconds
  • LNK files get generated only if the target was opened from an interactive session. For example, if you execute notepad.exe test.txt a LNK file to test.txt will not be generated
  • A lot of rust stuff!
  • LNK struct is used in the JumpList artifact (next parser 🤔)
  • ShellItems are annoying to parse😆

Some parsers only show the most relevant data and remove the “irrelevant” data, such as the FileReference. This is fine most of the time, but it is nice to have all the data and decide if you need it.

The parser

Now we get to the fun part! In the past couple of weeks, I worked on a rust parser to extract all of the data I can and output it in multiple formats. During the LNK parser development, I developed a rust library called winparsingtools which, as the name suggests, is a collection of structs and utilities to parse common windows binary formats. I will keep adding implementation for different structs as I explore new windows artifacts. You can check the winparsingtools library on my github or on crates.io.

Now let’s get to the LNKParser. You can use the LNKParser library or the binary. Here is the help message for the binary LNKParser:

lnk_parser 0.1.0
AbdulRhman Alfaifi - @A__ALFAIFI
Windows LNK Files Parser

USAGE:
    lnk_parser.exe [FLAGS] [OPTIONS]

FLAGS:
    -h, --help          Prints help information
        --no-headers    Don't print headers when using CSV as the output format
        --normalize     Normalize the result to the most important fields
    -V, --version       Prints version information

OPTIONS:
    -p, --path <PATH>...                   Path(s) to LNK Metadata Files to be Parsed - accepts glob (Defaults to
                                           'RecetItems' for all users)
    -o, --output <output>                  The file path to write the output to [default: stdout]
        --output-format <output-format>    Output format. [default: csv]  [possible values: csv, jsonl, json]

And here is a sample output in JSON format:

{
    "target_full_path": "C:\\Users\\u0041\\Desktop\\test\\test.txt",
    "lnk_file_metadata": {
        "full_path": "C:\\Users\\u0041\\Documents\\Projects\\LNKParser-rs\\samples\\WIN10\\1607_14393\\windows_generated.lnk",
        "mtime": "2021-02-08T12:52:20Z",
        "atime": "2021-02-13T19:14:07Z",
        "ctime": "2021-02-08T12:52:13Z"
    },
    "shell_link_header": {
        "file_attr": [
            "ARCHIVE"
        ],
        "mtime": "2021-02-08T12:41:58Z",
        "atime": "2021-02-08T12:41:03Z",
        "ctime": "2021-02-08T12:41:03Z",
        "file_size": 4
    },
    "link_target_id_list": {
        "id_list": [
            {
                "shell_item_data": {
                    "FileEntry": {
                        "is_file": false,
                        "file_size": 0,
                        "last_modified": "2021-02-08T12:46:24Z",
                        "file_attr_flags": [
                            "DIRECTORY"
                        ],
                        "name": "test",
                        "extention_block": {
                            "ctime": "2021-02-08T12:46:24Z",
                            "atime": "2021-02-08T12:46:24Z",
                            "file_ref": {
                                "mft_entry": 91461,
                                "sequence_number": 3
                            },
                            "primary_name": "test"
                        }
                    }
                }
            },
            {
                "shell_item_data": {
                    "FileEntry": {
                        "is_file": true,
                        "file_size": 4,
                        "last_modified": "2021-02-08T12:42:00Z",
                        "file_attr_flags": [
                            "ARCHIVE"
                        ],
                        "name": "test.txt",
                        "extention_block": {
                            "ctime": "2021-02-08T12:41:04Z",
                            "atime": "2021-02-08T12:41:04Z",
                            "file_ref": {
                                "mft_entry": 90070,
                                "sequence_number": 3
                            },
                            "primary_name": "test.txt"
                        }
                    }
                }
            }
        ]
    },
    "link_info": {
        "volume_id": {
            "drive_type": "DRIVE_FIXED",
            "serial_number": "E02E-8A93"
        },
        "local_base_path": "C:\\Users\\u0041\\Desktop\\test\\test.txt"
    },
    "relative_path": "..\\..\\..\\..\\..\\Desktop\\test\\test.txt",
    "working_dir": "C:\\Users\\u0041\\Desktop\\test",
    "extra_data": {
        "extra_data_blocks": [
            {
                "Tracker": {
                    "machine_id": "win10",
                    "file_droid": "BD4FAD74-6A0A-11EB-8ECF-5076AFA95947",
                    "file_droid_birth": "BD4FAD74-6A0A-11EB-8ECF-5076AFA95947",
                    "volume_droid": "00D2581C-4749-44BD-9381-9BDFADF8A9DE",
                    "volume_droid_birth": "00D2581C-4749-44BD-9381-9BDFADF8A9DE"
                }
            }
        ]
    }
}

You can also use the lib as follows:

use std::fs::File;
use lnk_parser::LNKParser;
fn main(){
    // Open the LNK file
    let file = File::open("samples\\WIN10\\1607_14393\\windows_generated.lnk")
    					.unwrap();
    // Pass the `File` instance to `from_reader` function.
    // `std::fs::File` implements `Read` & `Seek` traits.
    let lnk_file = LNKParser::from_reader(file);
    println!("{:?}", lnk_file);
}

You can find my LNK parser in GitHub or crates.io

Reference