lnk

package module
v0.0.0-...-2c71b1a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 22, 2022 License: Apache-2.0 Imports: 16 Imported by: 0

README

lnk - lnk Parser for Go

lnk is a package for parsing Windows Shell Link (.lnk) files.

It's based on version 5.0 of the [MS-SHLLINK] document:

If the lnk file does not adhere to this specification (either corrupted or from an earlier version), it might not be parsed.

Each file has at least one header (SHELL_LINK_HEADER) and one or more optional sections.

SHELL_LINK = SHELL_LINK_HEADER [LINKTARGET_IDLIST] [LINKINFO]
              [STRING_DATA] *EXTRA_DATA

The existence of these sections are defined by the LinkFlags uint32 in the header (mapped to HEADER.LinkFlags). To see all flags, look at linkFlags in header.go.

Note about size fields: "Unless otherwise specified, the value contained by size fields includes the size of size field itself."

Currently lnk parses every section except EXTRA_DATA. Different data blocks are identified and stored but it does not parse any of them other than identifying the type (via their signature) and storing the content. Data blocks are defined in section 2.5 of the specification.

Setup

Package has only one dependency: https://github.com/olekukonko/tablewriter. It's used to create tables in section stringers.

Usage

Pass a filename to lnk.File or an io.Reader with its contents to lnk.Read. Both return LnkFile:

type LnkFile struct {
	Header     ShellLinkHeaderSection  // File header.
	IDList     LinkTargetIDListSection // LinkTargetIDList.
	LinkInfo   LinkInfoSection         // LinkInfo.
	StringData StringDataSection       // StringData.
	DataBlocks ExtraDataSection        // ExtraData blocks.
}

Each section is a struct that is populated. See their fields in their respective source files.

package main

import (
	"fmt"

	"github.com/parsiya/golnk"
)

func main() {

	Lnk, err := lnk.File("test.lnk")
	if err != nil {
		panic(err)
	}

	// Print header.
	fmt.Println(Lnk.Header)

	// Path to the target file is usually in LinkInfo.LocalBasePath.
	fmt.Println("BasePath", Lnk.LinkInfo.LocalBasePath)

	// fmt.Println(Lnk.LinkInfo)

	// fmt.Println(Lnk.StringData)

	// fmt.Println(Lnk.DataBlocks)
}

header printed

Each section has a Stringer that prints the fields in a table.

link info printed

Extra Data Blocks are not parsed but can be dumped or accessed manually.

extra data block dump

Parse the Windows start menu and extract the base path for all lnk files.

See test/parseStartMenu.go:

package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/parsiya/golnk"
)

// Sample program to parse all lnk files in the "All Users" start menu at
// C:\ProgramData\Microsoft\Windows\Start Menu\Programs.

func main() {
	startMenu := "C:/ProgramData/Microsoft/Windows/Start Menu/Programs"
	basePaths := []string{}
	err := filepath.Walk(startMenu, func(path string, info os.FileInfo, walkErr error) error {
		// Only look for lnk files.
		if filepath.Ext(info.Name()) == ".lnk" {
			f, lnkErr := lnk.File(path)
			// Print errors and move on to the next file.
			if lnkErr != nil {
				fmt.Println(lnkErr)
				return nil
			}
			var targetPath = ""
			if f.LinkInfo.LocalBasePath != "" {
				targetPath = f.LinkInfo.LocalBasePath
			}
			if f.LinkInfo.LocalBasePathUnicode != "" {
				targetPath = f.LinkInfo.LocalBasePathUnicode
			}
			if targetPath != "" {
				fmt.Println("Found", targetPath)
				basePaths = append(basePaths, targetPath)
			}
		}
		return nil
	})
	if err != nil {
		panic(err)
	}

	// Print everything.
	fmt.Println("------------------------")
	for _, p := range basePaths {
		fmt.Println(p)
	}
}

TODO

  1. Use dep?
  2. Identify ExtraDataBlocks.
  3. Clean up code.
  4. Write more unit tests.
  5. Test it on more lnk files.
  6. Add a Data field to each section and store raw bytes there. Then add a Dump method to each section and use hex.Dump to dump the raw bytes.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func HotKey

func HotKey(hotkey uint16) string
HotKeyFlags contains the hotkey.
Although it's 4 bytes, only the first 2 bytes are used.

First byte is LowByte.
If between 0x30 and 0x5A inclusive, it's ASCII-hex of the key.
If between 0x70 and 0x87 it's F(num-0x70+1) (e.g. 0x70 == F1 and 0x87 == F24).
0x90 == NUM LOCK and 0x91 SCROLL LOCK.

Second byte is HighByte.
0x01: SHIFT
0X02: CTRL
0X03: ALT

HotKey returns the string representation of the hotkey uint32.

func ReadBytes

func ReadBytes(b []byte, offset, num int) (out []byte, n int)

ReadBytes reads n bytes from the slice starting from offset and returns a []byte and the number of bytes read. If offset is out of bounds it returns an empty []byte and 0 bytes read. TODO: Write tests for this.

func StructToJSON

func StructToJSON(v interface{}, indent bool) string

StructToJSON converts a struct into the equivalent JSON string. String is indented if indent is true. Remember only exported fields can be seen by the json package.

Types

type CommonNetworkRelativeLink struct {
	Size uint32

	// Only the first two bits are used. commonNetworkRelativeLinkFlags
	CommonNetworkRelativeLinkFlags uint32

	// String version of the flag.
	CommonNetworkRelativeLinkFlagsStr []string

	// Offset of NetName field from start of structure.
	// If value > 0x14, then NetNameOffsetUnicode must not exist.
	NetNameOffset uint32

	// Offset of DeviceName field from start of structure.
	DeviceNameOffset uint32

	// Type of NetworkProvider. See networkProviderType for table.
	// If ValidNetType is not set, ignore this.
	// NetworkProviderType uint32
	NetworkProviderType string // A uint32 in file, maps to networkProviderType.

	// Optional offset of NetNameUnicode. Must not exist if NetNameOffset > 0x14.
	NetNameOffsetUnicode uint32

	// Optional value of DeviceNameUnicode. Must not exist if NetNameOffset > 0x14.
	DeviceNameOffsetUnicode uint32

	// Server share path (e.g. \\server\share). Null-terminated string.
	NetName string

	// Device name like drive letter. Null-terminated string.
	DeviceName string

	// Unicode string. Must not exist if NetNameOffset > 0x14.
	NetNameUnicode string

	// Unicode string. Must not exist if NetNameOffset > 0x14.
	DeviceNameUnicode string
}

CommonNetworkRelativeLink (section 2.3.2) Information about the network location where a link target is stored,

func CommonNetwork

func CommonNetwork(r io.Reader, maxSize uint64) (c CommonNetworkRelativeLink, err error)

CommonNetwork reads the section data and populates a CommonNetworkRelativeLink. Section 2.3.2 in docs.

func (CommonNetworkRelativeLink) String

func (c CommonNetworkRelativeLink) String() string

String prints CommonNetworkRelativeLink in a table.

type ExtraDataBlock

type ExtraDataBlock struct {
	Size      uint32
	Signature uint32
	Type      string
	Data      []byte
}

ExtraDataBlock represents one of the optional data blocks at the end of the lnk file. Each data block starts with a uint32 size and a uint32 signature. Detection is as follows: 1. Read the uint32 size. If size < 0x04, it's the terminal block. 2. Read the datablock (size-4) more bytes from the io.Reader. 3. Read the uint32 signature. It will designate the datablock. 4. Parse the data based on the signature.

func (ExtraDataBlock) Dump

func (db ExtraDataBlock) Dump() string

Dump returns the hex.Dump of ExtraDataBlock.

type ExtraDataSection

type ExtraDataSection struct {
	Blocks []ExtraDataBlock
	// Terminal block at the end of the ExtraData section.
	// Value must be smaller than 0x04.
	TerminalBlock uint32
}

ExtraDataSection represents section 2.5 of the specification.

func DataBlock

func DataBlock(r io.Reader) (extra ExtraDataSection, err error)

DataBlock reads and populates an ExtraData.

func (ExtraDataSection) String

func (e ExtraDataSection) String() string

String prints the ExtraData blocks' Type, Size, and a hexdump of their content.

type FlagMap

type FlagMap map[string]bool

type IDList

type IDList struct {
	// ItemIDList contains the IDLists.
	ItemIDList []ItemID
	// TerminalID is 00 00.
	TerminalID uint16
}

IDList represents a persisted item ID list.

type ItemID

type ItemID struct {
	// Size of ItemID INCLUDING the size.
	Size uint16
	// Data length is size-2 bytes.
	Data []byte
}

ItemID is an element from IDList. From [MS-SHLLINK]: "The data stored in a given ItemID is defined by the source that corresponds to the location in the target namespace of the preceding ItemIDs. This data uniquely identifies the items in that part of the namespace."

type LinkInfoSection

type LinkInfoSection struct {

	// Size of the LinkInfo structure. Includes these four bytes.
	Size uint32

	// Size of LinkInfo header section.
	// If == 0x1c => Offsets to optional fields are not specified.
	// If >= 0x24 => Offsets to optional fields are specified.
	// Header section include LinkInfoSize and some of the following fields.
	LinkInfoHeaderSize uint32

	// Flags that specify whether the VolumeID, LocalBasePath, LocalBasePathUnicode,
	// and CommonNetworkRelativeLink fields are present in this structure.
	// See linkInfoFlags
	LinkInfoFlags uint32

	// LinkInfoFlagsStr contains the flags in string format.
	LinkInfoFlagsStr []string

	// Offset of VolumeID if VolumeIDAndLocalBasePath is set.
	VolumeIDOffset uint32

	// Offset of LocalBasePath if VolumeIDAndLocalBasePath is set.
	LocalBasePathOffset uint32

	// Offset of CommonNetworkRelativeLink if CommonNetworkRelativeLinkAndPathSuffix is set.
	CommonNetworkRelativeLinkOffset uint32

	// Offset of CommonPathSuffix.
	CommonPathSuffixOffset uint32

	// Offset of optional LocalBasePathUnicode and present if VolumeIDAndLocalBasePath is set
	// and LinkInfoHeaderSize >= 0x24.
	LocalBasePathOffsetUnicode uint32 // Optional

	// Offset of CommonPathSuffixUnicode and present if LinkInfoHeaderSize >= 0x24.
	CommonPathSuffixOffsetUnicode uint32 // Optional

	// VolumeID present if VolumeIDAndLocalBasePath is set.
	VolID VolID

	// Null-terminated string present if VolumeIDAndLocalBasePath is set.
	// Combine with CommonPathSuffix to get the full path to target.
	LocalBasePath string // Optional

	// Optional CommonNetworkRelativeLink, contains information about network
	// location of the target.
	NetworkRelativeLink CommonNetworkRelativeLink

	// Null-terminated string. Combine with LocalBasePath to get full path to target.
	CommonPathSuffix string // Optional

	// Null-terminated Unicode string to base path.
	// Present only VolumeIDAndLocalBasePath is set and LinkInfoHeaderSize >= 0x24.
	LocalBasePathUnicode string // Optional

	// Null-terminated Unicode string to common path.
	// Present only VolumeIDAndLocalBasePath is set and LinkInfoHeaderSize >= 0x24.
	CommonPathSuffixUnicode string // Optional

	// Section's raw bytes.
	Raw []byte
}

LinkInfoSection represents the LinkInfo structure. Section 2.3 of [MS-SHLLINK]. It appears right after LinkTargetIDList if it's in the linkFlags.

func LinkInfo

func LinkInfo(r io.Reader, maxSize uint64) (info LinkInfoSection, err error)

LinkInfo reads the io.Reader and returns a populated LinkInfoSection.

func (LinkInfoSection) Dump

func (li LinkInfoSection) Dump() string

Dump returns the hex.Dump of section data.

func (LinkInfoSection) String

func (li LinkInfoSection) String() string

String prints LinkInfoSection in a table.

type LinkTargetIDListSection

type LinkTargetIDListSection struct {
	// First two bytes is IDListSize.
	IDListSize uint16
	// // Section's raw bytes.
	List IDList
}

LinkTargetIDListSection contains information about the target of the link. Section 2.2 in= [MS-SHLLINK]

func LinkTarget

func LinkTarget(r io.Reader) (li LinkTargetIDListSection, err error)

LinkTarget returns a populated LinkTarget based on bytes passed. []byte should point to the start of the section. Normally this will be offset 0x4c of the lnk file.

type LnkFile

type LnkFile struct {
	Header     ShellLinkHeaderSection  // File header.
	IDList     LinkTargetIDListSection // LinkTargetIDList.
	LinkInfo   LinkInfoSection         // LinkInfo.
	StringData StringDataSection       // StringData.
	DataBlocks ExtraDataSection        // ExtraData blocks.
}

LnkFile represents one lnk file.

func File

func File(filename string) (f LnkFile, err error)

File parses an lnk File.

func Read

func Read(r io.Reader, maxSize uint64) (f LnkFile, err error)

Read parses an io.Reader pointing to the contents of an lnk file.

type ShellLinkHeaderSection

type ShellLinkHeaderSection struct {
	Magic          uint32    // Header size: should be 0x4c.
	LinkCLSID      [16]byte  // A class identifier, should be 00021401-0000-0000-C000-000000000046.
	LinkFlags      FlagMap   // Information about the file and optional sections in the file.
	FileAttributes FlagMap   // File attributes about link target, originally a uint32.
	CreationTime   time.Time // Creation time of link target in UTC. 16 bytes in file.
	AccessTime     time.Time // Access time of link target. Could be zero. 16 bytes in file.
	WriteTime      time.Time // Write time  of link target. Could be zero. 16 bytes in file.
	TargetFileSize uint32    // Filesize of link target. If larger than capacity, it will have the LSB 32-bits of size.
	IconIndex      int32     // 32-bit signed integer, the index of an icon within a given icon location.
	ShowCommand    string    // Result of the uint32 integer: The expected windows state of the target after execution.
	HotKey         string    // HotKeyFlags structure to launch the target. Original is uint16.
	Reserved1      uint16    // Zero.
	Reserved2      uint32    // Zero.
	Reserved3      uint32    // Zero.
	Raw            []byte    // Section's raw bytes.
}

ShellLinkHeaderSection represents the lnk header.

func Header(r io.Reader, maxSize uint64) (head ShellLinkHeaderSection, err error)

Header parses the first 0x4c bytes of the io.Reader and returns a ShellLinkHeader.

func (ShellLinkHeaderSection) Dump

func (h ShellLinkHeaderSection) Dump() string

Dump returns the hex.Dump of section data.

func (ShellLinkHeaderSection) String

func (h ShellLinkHeaderSection) String() string

String prints the ShellLinkHeader in a table.

type StringDataSection

type StringDataSection struct {

	// NameString specifies a description of the shortcut that is displayed to
	// end users to identify the purpose of the shell link.
	// Present with HasName flag.
	NameString string

	// RelativePath specifies the location of the link target relative to the
	// file that contains the shell link.
	// Present with HasRelativePath flag.
	RelativePath string

	// WorkingDir specifies the file system path of the working directory to
	// be used when activating the link target.
	// Present with HasWorkingDir flag.
	WorkingDir string

	// CommandLineArguments stores the command-line arguments of the link target.
	// Present with HasArguments flag.
	CommandLineArguments string

	// IconLocation specifies the location of the icon to be used.
	// Present with HasIconLocation flag.
	IconLocation string
}

StringDataSection represents section 2.4 of the lnk. Fields are mostly optional and present if certain flags are set.

func StringData

func StringData(r io.Reader, linkFlags FlagMap) (st StringDataSection, err error)

StringData parses the StringData portion of the lnk. flags is the ShellLinkHeader.LinkFlags.

func (StringDataSection) String

func (st StringDataSection) String() string

String prints StringDataSection in a table.

type VolID

type VolID struct {
	// Size of VolumeID including this.
	Size uint32

	// Type of drive that target was stored on.
	DriveType string // Originally a uint32 that is the index to the driveType []string.

	// Serial number of the volume. TODO: Store as hex string?
	DriveSerialNumber string // Originally a uint32, converted to hex string.

	// Offset to the a null-terminated string that contains the volume label of
	// the drive that the link target is stored on.
	// If == 0x14, it must be ignored and VolumeLabelOffsetUnicode must be used.
	VolumeLabelOffset uint32

	// Offset to Unicode version of VolumeLabel.
	// Must not be present if VolumeLabelOffset is not 0x14.
	VolumeLabelOffsetUnicode uint32

	// VolumeLabel in either ASCII-HEX or Unicode.
	VolumeLabel string
}

VolID is VolumeID (section 2.3.1. of [SHLLINK]). Information about the volume that the link target was on when the link was created.

func VolumeID

func VolumeID(r io.Reader, maxSize uint64) (v VolID, err error)

VolumeID reads the VolID struct.

func (VolID) String

func (v VolID) String() string

String prints VolumeID in a table.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL