Thursday, March 21, 2019

A Minimal LNK

Yeah, so I've written about LNK files before, but I wanted to take it a step further and explore just how much of the specification is required for a functioning LNK file.

Step 1
I used VBS to create a "bare-bones" LNK to run calc.exe.  I like to have something visual when testing this sort of thing.

The resulting LNK file is 890 bytes in size, and here's what the metadata for the file looks like:

guid               {00021401-0000-0000-c000-000000000046}
mtime              Wed Apr 11 23:34:36 2018 Z
atime              Wed Apr 11 23:34:36 2018 Z
ctime              Wed Apr 11 23:34:36 2018 Z
basepath           C:\Windows\System32\calc.exe
shitemidlist       My Computer/C:\/Windows/System32/calc.exe
**Shell Items Details (times in UTC)**
  C:2018-04-11 21:04:34  M:2018-10-11 21:39:08  A:2018-10-11 21:39:08 Windows (9)
  C:2018-04-11 21:04:34  M:2018-12-20 22:46:22  A:2018-12-20 22:46:22 System32 (9)
  C:2018-04-11 23:34:38  M:2018-04-11 23:34:38  A:2018-04-11 23:34:38 calc.exe (9)
vol_sn              22D3-06AE
vol_type           Fixed Disk
hotkey              0x14
showcmd          0x4


GUID/ID pairs:
{46588ae2-4cbc-4338-bbfc-139326986dce}/4       SID: S-1-5-21-3855314428-4085452759-4066589348-1000

GUID  : {1ac14e77-02e7-4e5d-b744-2eb1ae5198b7}

Machine ID                   : enzo
New Droid ID Time        : Tue Sep 18 10:39:24 2018 UTC
New Droid ID Seq Num  : 7175
New Droid    Node ID     : 5c:26:0a:24:29:6f
Birth Droid ID Time       : Tue Sep 18 10:39:24 2018 UTC
Birth Droid ID Seq Num : 7175
Birth Droid Node ID       : 5c:26:0a:24:29:6f

Okay, that is a LOT of stuff that's created in an LNK file, based on the following .vbs script:

set w = CreateObject("")
set l = w.CreateShortcut("\foo2.lnk")
l.TargetPath = "c:\windows\system32\calc.exe"

Step 2
Write code that creates a bare-bones LNK file header.  By "bare-bones", I mean one with the time stamps and any extraneous metadata zero'd out.

Step 3
Write code that goes to the LNK file created in step 1, and strips out just the linktargetIDlist, or "shell item ID list".  Zero out all of the time stamps in the shell items, and just for giggles, change the version value within the shell items.  Append this linktargetIDlist to the header created in step 2.

The resulting LNK file appears below:

The LNK file is 389 bytes in size, and functions perfectly well, no matter where I put it within the file system.  I double-click it, it launches the Calculator, as expected. 

However, this is what the metadata now looks like:

guid               {00021401-0000-0000-c000-000000000046}
shitemidlist       My Computer/C:\/Windows/System32 /calc.exe
**Shell Items Details (times in UTC)**
  C:0                   M:0                   A:0                  Windows (10)
  C:0                   M:0                   A:0                  System32  (10)
  C:0                   M:0                   A:0                  calc.exe  (10)
hotkey             0x0
showcmd         0x1


The result of this process is a functioning LNK file with minimal metadata.  No disk or volume info, no SID, no MAC address, none of the things we'd look for when analyzing a weaponized LNK file.

I put some of what was used in the creation of these LNK files on GitHub.


Bryan B said...

This is pretty cool stuff. Thanks for the share!

Have you seen this in the wild? What drew you to experiment with this in particular?


H. Carvey said...


What I was seeing in the wild was 'weaponized' LNK file with a good bit of metadata, but none that was really being used, with the notable exception of what the FireEye folks had done.

> What drew you to experiment with this in particular?

I had a thought..."What would the LCpl do?" Of all the responses to that question that I came up with, this one was the most interesting!

B!n@ry said...

This is scary and at the same time cool :D

Would love to do this on a wide scale and see how could such activity be traced back to the actor?!

BTW, did you zero out the times manually, or also scripted that?

As usual, nice work Harlan and thanks for sharing.

H. Carvey said...


I zero'd out the time stamps manually, in the shell items. I'm working in a script to do it for me.


@mattnotmax said...

This is great (and I wondering if it could be extended to other artifacts?).

I imagine if used in the wild the absence of any metadata would be the indicator itself?

I could see an application where a threat group needed access to a system where the difference between 890 bytes and 389 bytes might make a difference:
- air gapped network requiring some obscure entry point
- appending the lnk file to within another file but needing to keep the same byte size as the original file.

Just thoughts on your experiment. Thanks Harlan

H. Carvey said...


Other artifacts, such as?

> ...if used in the wild the absence of any metadata would be the indicator itself?

Yes, it would. However, I tend to believe that the extra effort (albeit not a great deal) isn't put into "scrubbing" weaponized LNK files because it's not needed.

I'm not at all clear as to how a smaller LNK file would be applied to an air-gapped network...