Looking Into Evil macOS Installers

Browsing the internet, I occasionally discover a nice piece of software I’d like to install on my Mac. Most of the time, this software is completely legitimate, however, every one in a while, I find a bit of malware just waiting to be unleashed upon an unsuspecting victim. In this post, I’ll be demonstrating the use of Objective-See’s What’s Your Sign utility to view the signatures on various files.

Background

macOS applications are essentially executable folders, with the .app extension. That folder contains various assets, like images used throughout the program, icons, a file describing the contents of the application, the actual binaries, etc. In contrast, Windows applications are just .exe files, where any assets are stored in its program files directory. All macOS applications, and any nested binaries, are signed with the developers signing certificate, which is signed with Apple’s Root CA to create a chain of trust.

Gatekeeper on macOS helps protect users from downloading and installing malicious software by checking for a Developer ID certificate from apps distributed outside the Mac App Store.

https://developer.apple.com/developer-id/

By default, if an application cannot be verified as following the chain of trust (i.e. the application isn’t signed, or signed with a self-signed certificate), macOS will not allow execution, and the user must explicitly tell the operating system to run the application, much like visiting a website using a self-signed certificate.

An example of trying to open an application that cannot be validated by macOS

macOS applications are typically distributed using Apple Disk Image files, also known as

.dmg files. These files, along with the files within, are typically signed. Once mounted, a Finder window is presented to the user with the files contained within. Typically, there will be the applications .app file, a shortcut to the users Applications folder, and maybe instructions telling the user to simple copy the application to the folder.

Another method developers can use to distribute their applications is by means of .pkg files. These files make use of an application built into macOS named Installer. As per the Installer Wikipedia page,

Package file icon

Installer launches when a package or metapackage file is opened. The installation process itself can vary substantially, as Installer allows developers to customize the information the user is presented with. For example, it can be made to display a custom welcome message, software license and readme. Installer also handles authentication, checks that packages are valid before installing them, and allows developers to run custom scripts at several points during the installation process.

https://en.wikipedia.org/wiki/Installer_(macOS)

Down The Rabbit Hole

So, I opened up the .dmg file containing the application, and what’s this? It’s a Package file. Well, these aren’t typically distributed in disk images, being compressed archives themselves, but not exactly anything suspicious here. Using What’s Your Sign, we can check the signature of this “Install” package, and we find the following.

Alright, so its has a valid signature from a developer named Joshua Franklin, and we can see the fingerprint of Joshua’s certificate. For the rest of the article, I will be referring to the evil-doer as Joshua. Looking further into the information What’s Your Sign provides, we can view the application’s entitlements. What’s an entitlement?

An entitlement is a single right granted to a particular app, tool, or other executable that gives it additional permissions beyond what it would ordinarily have.

https://developer.apple.com/documentation/bundleresources/entitlements

The most notable of these are allow-unsigned-executable-memory and disable-executable-page-protection. Normally, macOS will complain if an application starts running unsigned code for security reasons. These two flags tell macOS not to worry, it wants to run the unsigned code.

Another thing to note is the extension. This is a .app file, not a .pkg file like the name and icon would lead you to believe. So while you think the macOS Installer is going to launch, you actually get Joshua’s code instead. However, since this is a .app file, we can actually view the contents of it by right-clicking and selecting Show Contents.

Here we see the contents of Install.app. There’s some sort of binary inside the MacOS folder, an icon and a rather large text file in the Resources folder, a .plist file containing information about the package, and a PkgInfo file. Our first stop is Info.plist so we can see what the application does when it’s clicked.

The most notable bits of information we can extract are CFBundleExecutable, and CFBundleIdentifier. From those values, we can determine that the package name is com.franklin.jinst and it runs the executable ArOGtREZrLyoDZ. We can also see where the icon is assigned with CFBundleIconFile as the fake .pkg icon, and where CFBundleVersion defines the application version as 92783. Given how little code was actually written, I estimate that rather than this being a build number, Joshua just chose a random number for this.

Now, on to the executable. If we use What’s Your Sign again, we get the following.

Here we see that Joshua signed that strange executable, but we also see something else. That executable isn’t actually an executable like Finder told us. It’s a Bash script! This is great, we can open it and see exactly what’s being done.

Contents of the Bash script

Starting on line 2, we see that a variable appDir is being set to a bunch of inline foo. In bash, "$0" returns the name of the script being run (ArOGtREZrLyoDZ), pwd gets the full path to the current directory, and dirname returns the path to the parent directory. It first runs cd on the directory the script is running in, runs pwd to get the scripts path (/Volumes/Install/Install.app/Contents/MacOS/), then those two dirname commands return the parent directory of the parent directory. So when we evaluate that line, appDir="/Volumes/Install/Install.app/".

On line 3, basename is used to just get the name of the directory, so appName="Install.app" then, line 4 just sets tmpBundleIdentifier to be a random base64 string based on the hash of the date.

Line 5 uses mktemp to create a temporary directory in the /tmp/ folder, with a 12 character random name, then sets tmpDir to be the path of that new directory and line 6 roughly translates to tmpApp="/tmp/XXX/Install.app/".

Line 7 sets a variable named binFile to be the name of the script, and line 8 defines a variable called archive to be the reversed text of binFile. So binFile=ArOGtREZrLyoDZ and archive=ZDoyLrZERtGOrA.

Line 10 uses openssl to decrypt commandArgs using archive as the key. commandArgs is defined on line 9 as a base64 encoded string, while archive is just the name of the script backwards, as defined on line 8. Then, on line 11, the script starts a new bash instance, telling it to run the commands listed in commandArgs

Since we know the encryption key, we can just run openssl ourselves to decrypt commandArgs and after cleaning it up a bit, we get this:

  1. cp -R "$appDir" "$tmpApp"
  2. rm -rf "$tmpApp/Contents/_CodeSignature"
  3. xattr -c "$tmpApp"
  4. rm -rf "$tmpApp/Contents/Resources/$binFile"
  5. defaults write "$tmpApp/Contents/Info.plist" "CFBundleIdentifier" "$tmpBundleIdentifier"
  6. openssl enc -aes-256-cbc -d -A -base64 -k "$archive" -in "$appDir/Contents/Resources/$archive" -out "$tmpApp/Contents/MacOS/$binFile"
  7. chmod 777 "$tmpApp/Contents/MacOS/$binFile"
  8. open -a "$tmpApp/Contents/MacOS/$binFile" && rm -rf $tmpDir

First, it copies everything in /Volumes/Install/Install.app/ to /tmp/XXX/Install.app/ thus moving itself to the hard drive, deletes the _CodeSignature folder, then uses the xattr command to clear its extended attributes.

What’s so special about its extended attributes? Well, the extended attributes play a huge role in the functionality of GateKeeper. When you download a file on the internet on macOS, the browser will save information about where that file was downloaded from. The filesystem has a special place for this information, or attributes, to be stored. When an application is opened, GateKeeper checks to see if a specific attribute, com.apple.quarantine, is set. If it is, a popup will launch asking the user if they’re sure about launching the application since it came from the internet. If the user agrees to open the application, the extended attribute will be removed so the user is not asked again. However, if the application is unsigned, GateKeeper will refuse to open the application. If the application is opened from the context menu, GateKeeper will recognize that the user does, in fact, want to open this application, and will ask the user if they’re sure they want to run an application from an unsigned user. Just like before, if the user agrees, the extended attribute is removed. Without the extended attribute, GateKeeper will trust the application.

So, not only is this application downloaded from the internet, the script removed the _CodeSignature folder, essentially making this application unsigned. This means that if the script tried to run the application, GateKeeper would refuse to comply.

If we run xattr -l Install.app we can see all of the extended attributes assigned to the application.

5d2d24c0 is the hex representation of the timestamp at which it was downloaded, Firefox is the application that downloaded it, then that UUID at the end is a reference to the download location.

Now, if we were to remove the extended attributes like the script does, GateKeeper no longer intervenes, and it can open the application without any warning to the user.

Continuing on with the script file, we then see it deletes the old binFile from its tmpApp folder on the hard drive and then changes the CFBundleIdentifier in Info.plist to the random one it generated earlier. It then decrypts that very large text file we found at Resources/ZDoyLrZERtGOrA using ZDoyLrZERtGOrA as the key, saves that to where the old bin file was in the tmpApp directory, and makes it executable.

It then opens that new binary, then covers its tracks by deleting the tmpApp folder.

VirusTotal results for the encrypted binary

https://www.virustotal.com/gui/file/765acd4956f790fbfe092350aba5dd821a99c2d75f894f21c63317552615a5ef/detection