feature image scripting macs with malice

Scripting Macs With Malice | How Shlayer and Other Malware Installers Infect macOS

The Myth of the Safe Mac is something we’ve written about before, but sometimes it takes a startling statistic to get people’s attention. Research suggesting that 10% of all Kaspersky-protected Macs had been hit by Shlayer malware in 2019 certainly caught the attention of last week’s news cycle, and hopefully it’s the sort of stat that can serve as a wake-up call to those who still ask in 2020 “Do Macs get viruses?” (by which, of course, they mean ‘do Macs get infected with malware?’).

The resounding answer to that is “Yes, of course they do!”, and they’re getting infected at increasing rates because malware authors have adapted and evolved their techniques. In this post, we’ll explore in more detail the infection method adopted by Shlayer and other malware families recently. This method has proven to be an effective means of beating built-in macOS security controls and, indeed, a number of end user protection tools, too.

image of scripting macs with malice

Why Threat Actors Are Turning to Scripts

Until recently, most threat actors approached macOS infection in pretty much the same way. From adware to OSX.Dok and APT Lazarus group targeted attacks, the first stage infection is typically a standard Apple application bundle containing a malicious Mach-O binary file. The Mach-O is to macOS what PE is to Windows or ELF to Linux: the standard system executable format at the heart of GUI applications.

image of shroomcourt adware

The problem with such compiled binaries from a threat actor’s point of view is that they have a lot of code surface for detection. Legacy-style AV will scan binaries on execution, while macOS itself will check such binaries for Notarization and codesigning on first launch. The life of a binary file downloaded from the internet is one of a series of hurdles to leap over. And on top of that, building and compiling new binaries to beat hash and Yara rule checks is also more work for attackers than they would like.

Using a scripting language offers attackers a number of advantages. They’re easier to iterate on, they’re harder to scan, and although they can technically be codesigned, they don’t have to be, and that codesigning is in any case a brittle and easily removed extended attribute. But scripts have a couple of disadvantages, too. First, they still have to lose the Gatekeeper “quarantine bit” (read here if you’re not familiar with how Gatekeeper works); and second, asking victims to download and execute a sketchy-looking script is not a convincing look for malware that’s trying to pass itself off as a legitimate application.

Shlayer and related adware installers like Bundlore have got around both of these problems with surprising ease. Attackers have realized that due to increasing security controls on legitimate applications, many macOS users have become familiarized with the process and are undaunted by the prospect of simply overriding their own security preferences. Instructions like this allow even non-admin users to override Gatekeeper with two-clicks and let the malware launch.

image right click to bypass macOS security

The second problem was never really a problem to begin with, but it took malware authors a while to either catch on to, or bother to implement, the fact that the file format inside an application bundle can be anything you like – it doesn’t have to be a Mach-O. A python or shell script will do just as well. Moreover, with clever uses of aliases and filenames, attackers don’t even need to use an application bundle at all. While a scary-looking shell script might put some potential victims on alert, a nice icon like this will look harmless enough to many others.

image of player dmg

Inside Shlayer.a malware

The image above shows how the Shlayer.a variant presents itself to users when they mount the disk image that contains it. The disk image is typically downloaded by users who have been tricked through social engineering to believing they need to update Adobe Flash player.

However, switch views in the Finder and then toggle hidden files with the key chord Command-Shift-Period, and we can see there’s a bunch of other files hidden away on the disk image. Note the enc file, which is called by the Player.command shell script.

image of hidden malicious files in dmg

This version is relatively simple, but still clever. The hidden enc file contains base64-encoded data that the Player.command script decodes into the following second stage shell script:

image of early shlayer variant

This downloads and launches the next stage in the form of a malicious app in a subfolder of the /tmp folder. The code

$ mktemp -d /tmp/XXXXXXXXX

offers the malware an easy way to generate a random folder name. The number of Xs determine the length, and the mktemp command helpfully generates a random string of that length.

image of mktemp

Shlayer.a has been around for 18 months or so, and aside from the download URL, it hasn’t changed much in that time. Despite that, at the time of writing the most recent sighting of Shlayer.a on VirusTotal was 2 hours ago; malware authors don’t persist with unsuccessful strategies, so Shlayer.a is clearly still very much a going concern.

image of shlayer a on virustotal

Inside Shlayer.d malware

A more recent version increases the complexity and the size of the shell script and uses a very different technique. If you run the file command on both samples, you’ll notice that they are both Bash scripts but the newer sample contains binary data:

image of shlayer_d file type

Our newer sample has also ballooned to 1.7MB compared to the slim-Jim Shlayer.a version of 181 bytes. If we open that in Vi, we’ll get 6000 lines or so of binary. From within Vi, we can call the xxd utility to get a clearer picture of what’s going on. The firsts 400 lines or so are a shell script with a base64 encoded string for input.

image of shlayer_d

Decoding that reveals another script embedded within it:

image of shlayer_d embedded script

and more base64 code at Line 17, which defines the variable _t.

image shlayer_d second stage base64

Note how the script itself leverages the xxd utility to decode its own binary data. The final Line 18 at the end of the data reveals the command that will be run:

eval "$(_m "$_t" "$_y")"

Let’s change that eval to echo to print out the next stage.

image of shlayer final stage decryption

As we can see, the script jumps through quite a few hoops before gathering operating system version and machine ID. As with Shlayer.a, it then downloads and executes the payload from a subfolder within /tmp, making use of the mktemp utility. The payload is then deleted and, for good measure, the script kills the Terminal.app as its final act.

Inside Bundlore: Friends of the Shlayer Family

As mentioned at the beginning of this post, other researchers have covered the more recent Shlayer.e variant, which uses Python rather than Bash to achieve much the same thing, so I won’t repeat their analysis here, but the technical details are certainly worth reading up on.

Throughout the above, I’ve followed the naming conventions used by Kaspersky in order to facilitate readers following the story from there or elsewhere, but as you’ll notice if you look at the detections on VirusTotal, there is a wide variety of names for the Shlayer malware, with some engines identifying Shlayer variants as Bundlore variants and vice versa. Given the fluidity and similarity between samples of Shlayer and Bundlore, let’s look at a sample that offers a different twist on what we saw above.

The final sample I want to take a look at uses very similar techniques as the previous Shlayer samples, although it is tagged by Kaspersky as “OSX.Bnodlero.x” and by a number of other vendors as “Bundlore-CJ”. Let’s use Pacifist to inspect and extract the disk image’s contents.

image of bundlore installer

In terms of distribution and packaging, we see the sample is a DMG containing an installer.app. The app’s icon is an image that resembles the familiar .pkg image, and the main executable is once again a shell script. However, the script is somewhat different to the earlier ones, and it’s worth taking a look at from a defender’s point of view.

While the Shlayer samples above all eventually called out to a URL to retrieve the final payload, this sample packs an encrypted payload within the Installer.app bundle itself. The two files of interest in the Installer.app bundle are the script executable and the file with a random name in the Resources folder. While the executable name is a randomly generated string of alphanumeric characters, note that the name of the file in the Resources folder is the same string reversed.

Let’s start by decoding the base64 in the script. Here’s the encoded and decoded version side-by-side.

image of decrypted bundlore script

After decryption, we find that the script writes a Mach-O binary to a subfolder in the /tmp folder, again using mktemp but this time with a 12-character length. The script creates a bundle structure for the executable, giving it a random Bundle Identifier from hashing the date command and running it through base64 before formatting it with tr to delete unwanted characters.

The script then gives the new bundle executable permissions and launches it using the built-in open command. The script then promptly deletes the bundle and its parent directory from the filesystem, leaving nothing to be found as the source of infection.

Conclusion

In this post, we’ve seen how threat actors are easily able to convince users to override their own security settings, and how these same bad actors are using scripts as opposed to the more easily detectable and harder to iterate native binary format as the initial installer. Many legacy AV scanners don’t even recognize scripts as executables, and as the parent process will be the scripting language’s executable (e.g., /bin/bash or /usr/bin/python), these may also bypass security solutions that whitelist such tools. The best way to protect users from these kind of threats is with a behavioral solution that does not care what file type is used or what parent process is invoked, and which of course cannot be overridden by the user on the endpoint.

Sample Hashes

Shlayer.a:
a45a770803ad44eca678e74a5a10c270062c449c8ed6c6ac5a2b3217881272ad
da500f8821dde25cff8bba0e9dd5bf2d8efa8b188718f93287960969eec1b6b7

Shlayer.d: – First Stage
c32199390872536e45f0cc9d5a55e23ed5b0822772555b57def9aeb22cfdcb49

Shlayer.d: – Second Stage
f968eec32cbc625d8c3ef27c5a785be6c3a84df1344569f329da18dc66beb9a2

OSX.Bnodlero.x: – First Stage
962dd0564f179904c7ae59e92c6456a2906527fc2dc26480d25ef87b28bd429a

OSX.Bnodlero.x: – Decrypted Mach-O
6dd68a2b1375d47f99d7219aa5131bbab008bf0ae73784836b8c46b3e1d8f461