reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
Apache License 2.0 — see the repository root LICENSE file for the full text.
|
||||
|
||||
Copyright the AnalystAIPack contributors.
|
||||
|
||||
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
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
name: analyzing-malicious-lnk-files
|
||||
description: 'Analyzes weaponized Windows shortcut (.lnk) files: parsing the shell link structure
|
||||
for the target command, arguments, icon, and working directory, and recovering hidden
|
||||
PowerShell/cmd payloads and embedded content used in phishing. Activates for requests to
|
||||
analyze a malicious LNK, parse a shortcut file, or extract a command from a .lnk.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware-analysis
|
||||
- lnk
|
||||
- shortcut
|
||||
- document-malware
|
||||
- static-analysis
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1204.002
|
||||
- T1059.001
|
||||
- T1059.003
|
||||
d3fend:
|
||||
- D3-FCR
|
||||
- D3-DA
|
||||
references:
|
||||
- 'Microsoft [MS-SHLLINK] Shell Link Binary File Format — https://learn.microsoft.com/openspecs/windows_protocols/ms-shllink/'
|
||||
- 'LNK forensic structure references — https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc'
|
||||
---
|
||||
|
||||
# Analyzing Malicious LNK Files
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a `.lnk` delivered via phishing (often in an archive or ISO) and need its real action.
|
||||
- You must recover the target command line, arguments, and any embedded/hidden payload.
|
||||
- You are extracting IOCs (commands, URLs, file paths) from a shortcut.
|
||||
|
||||
**Do not use** Windows Explorer to inspect or double-click the shortcut — that executes its
|
||||
target. Parse the binary structure statically.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An LNK parser (Python `struct`, or `lnkparse`/`LECmd`); the sample handled inertly.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Never double-click or hover-load the shortcut in Explorer; parse raw bytes only.
|
||||
- Defang recovered URLs and store any embedded payload password-protected.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Confirm and parse the header
|
||||
|
||||
Verify the LNK magic (`4C 00 00 00` + the link CLSID) and read `LinkFlags` to know which optional
|
||||
structures are present.
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py parse sample.lnk
|
||||
```
|
||||
|
||||
### Step 2: Recover the target and arguments
|
||||
|
||||
Extract the relative/local path and, crucially, the `COMMAND_LINE_ARGUMENTS` string — malicious
|
||||
LNKs hide `powershell -enc ...` or `cmd /c ...` here.
|
||||
|
||||
### Step 3: Inspect icon and metadata
|
||||
|
||||
Read the icon location (often spoofed to look like a document) and metadata (machine ID,
|
||||
timestamps) useful for attribution/clustering.
|
||||
|
||||
### Step 4: Find appended/embedded payloads
|
||||
|
||||
Check for data appended after the LNK structure (some campaigns embed scripts/binaries past the
|
||||
parsed end) and extract it.
|
||||
|
||||
### Step 5: Decode and extract IOCs
|
||||
|
||||
Decode encoded commands (UTF-16LE base64), defang URLs, and route extracted payloads to the right
|
||||
analysis workflow.
|
||||
|
||||
## Validation
|
||||
|
||||
- The full command line, including arguments, is recovered (not just the target path).
|
||||
- Encoded commands are decoded to cleartext.
|
||||
- Appended/embedded payloads are detected and extracted, and IOCs are defanged.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Reading only the target path and missing the malicious arguments.
|
||||
- Inspecting in Explorer and executing the shortcut.
|
||||
- Ignoring data appended past the LNK structure.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the LNK parser.
|
||||
- MS-SHLLINK and the liblnk format notes (linked in frontmatter).
|
||||
@@ -0,0 +1,42 @@
|
||||
# API Reference: Analyzing Malicious LNK Files
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` is a minimal [MS-SHLLINK] parser: it validates the LNK header and CLSID, reads
|
||||
LinkFlags, skips the IDList/LinkInfo, and extracts the StringData fields (name, relative path,
|
||||
working dir, arguments, icon), plus the count of any appended data. It parses statically and never
|
||||
executes the shortcut.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | struct parsing, file IO |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| LECmd / lnkparse | Full-featured LNK parsing including extra data blocks |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `parse(path)`
|
||||
Returns LinkFlags and the recovered StringData fields, appended-data size, and a suspicious flag.
|
||||
|
||||
### `_read_stringdata(data, off, unicode_)`
|
||||
Reads a CountedString (ANSI or UTF-16LE) per the IsUnicode flag.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py parse sample.lnk
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- Microsoft [MS-SHLLINK] Shell Link Binary File Format —
|
||||
https://learn.microsoft.com/openspecs/windows_protocols/ms-shllink/
|
||||
- liblnk LNK format documentation —
|
||||
https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc
|
||||
- Malicious-argument hiding in COMMAND_LINE_ARGUMENTS, established document-analysis practice
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse a Windows shell link (.lnk) and recover its command and arguments.
|
||||
|
||||
Implements a minimal [MS-SHLLINK] parser using stdlib ``struct``: it validates the header,
|
||||
reads LinkFlags, and walks the optional StringData section to extract the name, relative path,
|
||||
working directory, command-line arguments, and icon location. It also reports any data appended
|
||||
past the structure. It parses bytes statically and never executes the shortcut.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HEADER_SIZE = 0x4C
|
||||
LINK_CLSID = b"\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46"
|
||||
|
||||
# LinkFlags bits relevant to StringData (MS-SHLLINK 2.1.1)
|
||||
F_HAS_LINK_TARGET_IDLIST = 0x00000001
|
||||
F_HAS_NAME = 0x00000004
|
||||
F_HAS_RELATIVE_PATH = 0x00000008
|
||||
F_HAS_WORKING_DIR = 0x00000010
|
||||
F_HAS_ARGUMENTS = 0x00000020
|
||||
F_HAS_ICON_LOCATION = 0x00000040
|
||||
F_IS_UNICODE = 0x00000080
|
||||
|
||||
|
||||
def _read_stringdata(data: bytes, off: int, unicode_: bool) -> tuple[str, int]:
|
||||
count = struct.unpack_from("<H", data, off)[0]
|
||||
off += 2
|
||||
if unicode_:
|
||||
raw = data[off:off + count * 2]
|
||||
off += count * 2
|
||||
return raw.decode("utf-16-le", "replace"), off
|
||||
raw = data[off:off + count]
|
||||
off += count
|
||||
return raw.decode("latin-1", "replace"), off
|
||||
|
||||
|
||||
def parse(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
if len(data) < HEADER_SIZE or struct.unpack_from("<I", data, 0)[0] != HEADER_SIZE:
|
||||
return {"error": "not a LNK (bad HeaderSize)"}
|
||||
if data[4:20] != LINK_CLSID:
|
||||
return {"error": "not a LNK (bad CLSID)"}
|
||||
flags = struct.unpack_from("<I", data, 20)[0]
|
||||
unicode_ = bool(flags & F_IS_UNICODE)
|
||||
off = HEADER_SIZE
|
||||
|
||||
# Skip LinkTargetIDList if present
|
||||
if flags & F_HAS_LINK_TARGET_IDLIST:
|
||||
idlist_size = struct.unpack_from("<H", data, off)[0]
|
||||
off += 2 + idlist_size
|
||||
|
||||
# Skip LinkInfo if present (HasLinkInfo = 0x2)
|
||||
if flags & 0x00000002:
|
||||
link_info_size = struct.unpack_from("<I", data, off)[0]
|
||||
off += link_info_size
|
||||
|
||||
result = {"flags": hex(flags), "unicode": unicode_}
|
||||
for bit, key in [(F_HAS_NAME, "name"), (F_HAS_RELATIVE_PATH, "relative_path"),
|
||||
(F_HAS_WORKING_DIR, "working_dir"), (F_HAS_ARGUMENTS, "arguments"),
|
||||
(F_HAS_ICON_LOCATION, "icon_location")]:
|
||||
if flags & bit and off < len(data):
|
||||
try:
|
||||
value, off = _read_stringdata(data, off, unicode_)
|
||||
result[key] = value
|
||||
except struct.error:
|
||||
break
|
||||
|
||||
result["appended_data_bytes"] = max(0, len(data) - off)
|
||||
args = result.get("arguments", "")
|
||||
result["suspicious"] = any(t in args.lower() for t in
|
||||
("powershell", "-enc", "cmd", "mshta", "rundll32", "frombase64"))
|
||||
return result
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Parse a malicious LNK")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("parse")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(parse(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user