In terms of post exploitation for macOS, python has been the primary language used up to this point. I can totally see several reasons why: lots of public support and libraries available, ease of use, python 2 being on all macOS endpoints natively, etc. In fact, python is my personal favorite scripting language and I also wrote my own post exploitation tool for macOS in python:
MacShell post exploitation tool for macOS. Author: Cedric Owens - cedowens/MacShell
I wrote MacShell for a few specific reasons: I wanted to get a more in-depth understanding of macOS post exploitation challenges and considerations, I wanted to write something simple and easy for blue teams to follow in terms of building a deeper understanding of macOS post exploitation, and I wanted to do granular detection testing. The more I analyzed (from a blue team perspective) the artifacts from MacShell (and other python-based post exploitation tools), the more I started to see commonalities associated with python-based exploitation toolkits. For example, it is common to see the “os”, “commands” (python2)/ “subprocesses” (python3), and “popen” libraries used to execute shell commands to perform certain post-exploitation tasks. While this makes execution of shell commands super easy, this also leaves artifacts that blue teamers can easily key on to detect post exploitation activity (ex: a single python process spawning several /bin/sh shells, specific command line arguments/strings, python making interesting network connections, etc.). So then I started to research ways to access macOS internals and then challenged myself to find ways to do this without using python. Just as on the Windows side of the house, using .net to make API calls and to access Windows internals has been the trend…I believe this will soon also apply to macOS.
So I picked the Swift programming language to focus on. Why Swift? Well Objective C is incredibly powerful and has been around for a while, but has a steep learning curve (and plus C is not my personal favorite language). Swift is relatively new and allows a lot of the same API calls and macOS internals access as Objective C. Plus Swift is a bit easier to learn (in my opinion), so I chose Swift.
So I embarked on a personal challenge to rewrite the post-exploitation tasks performed in my MacShell tool in Swift, and below are some examples of my Swift code for macOS post exploitation. I kept the same model of a client and server script where the server is the C2 server and the client runs on the host on which you want to perform post exploitation tasks. The code snippets below are examples from my Swift client code.
Here is the link to the Swift client (mach-o binary) code on my github page:
You can't perform that action at this time. You signed in with another tab or window. You signed out in another tab or…
I wrote MacShellSwift for similar purposes as MacShell (help blue teams begin testing detection gaps when MacOS API calls are used for post exploitation). I did not really write this for live red team operations use, though red teamers can gain insight into using MacOS internals for post exploitation.
My goal with this post is to aid both blue teams and red teams by showing examples of post exploitation using macOS internals. I hope to not only help red teamers with ideas but also to help blue teamers posture for these types of post exploitation tasks. One thing blue teamers can do now is to run these code samples against your current endpoint detection and response platform to see what is/is not seen or detected and then start to build a plan to address the gaps. To test the code samples, you can create a Swift playground and run the code and then see what can be seen by the EDR platform/logs.
All of the code samples below are from Swift v5.0.1 and XCode 10.2.1. Before we jump into the post exploitation examples using macOS internals, let’s first talk about getting set up with XCode.
Brief Intro to XCode (for making mach-o files and .app packages)
The XCode IDE for MacOS can be downloaded from the Mac Store. Based on what version of XCode you download, a corresponding version of Swift will also be downloaded. Once installed, you can start a new project with: File -> New.
At that point you will be presented with a window similar to below:
For the purposes of this post, I have leveraged the “Command Line Tool” and “Cocoa App” applications. I have leveraged the “Command Line Tool” application to build mach-o files and the “Cocoa App” application to build .app packages.
Simple Steps to Make A New “Command Line Tool” Project (to build a mach-o binary):
- Create a new directory at your desired location
- cd into that directory
- run swift package init — type executable
- run swift package generate-xcodeproj
- Open your xcodeproj file in XCode and edit the main.swift code as needed
- Build and run your code within XCode by clicking the run button or from the terminal by navigating to the top level directory for your project and typing swift run (to build and execute the code) or swift build (to just build the binary)
Simple Steps to Make A New “Cocoa Application” Project (to build a .app package):
- Open XCode
- File -> New -> Cocoa Application
- You will be brought to a screen similar to below:
- Name your project and ensure that “Use Storyboards” is selected
- You will then be brought to a screen like this in XCode:
Here is some brief info on the screen items above. On the left panel:
- Main.storyboard: Where you can build the graphical content of your window that the user will interact with; items such as text boxes, buttons, labels, images, etc. are here. Clicking a button on the top menu bar of XCode that looks like this is how you select elements to add to the window:
- Assets.xcassets: I have used this to add images for the app window or icon images for the application package
- ViewController.swift: Where you can add code behind the window elements (ex: what happens when a button is pushed or when text is typed in a text box)
- AppDelegate.swift: Code around application completion and termination can be added here
Next you can design your window with the labels, text boxes, buttons, etc as desired. Once complete, you can click the Run button in XCode to see what the window looks like when run. Once you are satisfied with the layout, you can then proceed to start adding code behind the window elements. To do so, first hold the “option” button and click the ViewController.swift item on the left menu bar. That will then display ViewController.swift in a new column to the right of main.storyboard as below:
Next, you can then start to add code behind whatever window items you want. For each item that you want to add code for, you can hold down the “control” button and click and drag that item from the main.storyboard over to the ViewController.swift window on the right:
Once this is done, you will have the option to type in a name for that item as a variable in ViewController.swift:
The window item will then show up in ViewController.swift as an IBOutlet variable. In the example above, the following variable will be added to ViewController.swift: @IBOutlet weak var textBox1: NSTextField! (note: if this were a sensitive field you would use NSSecureTextField instead). This lets XCode know that the textBox1 variable in the ViewController.swift file is linked to the text box item in the window form that you dragged over to ViewController.swift. Your text box items will be Outlets and your push button will be an Action.
An example of code to get data entered and send it to a web server is below:
For this example I imported Cocoa (for MacOS internal API calls) and the IBM-Swift Socket library to make the HTTP POST. In this example, I used cocoapods to install the Socket library:
- Install cocoapods: “sudo gem install cocoapods”
- cd to your project directory and create a file named “Podfile”; the contents of this file should be:
- Save and then run “pod install”, which will successfully add the Alamofire package into your project for use
Migrating from Command Line Utilities to MacOS API Calls
Next, I will walk through examples of post exploitation tasks that leverage macOS internal API calls instead of relying on command line utilities. We will be leveraging the Cocoa Framework, which is the macOS API that has most of the classes we need for post exploitation. The Cocoa API is simply accessible by adding “import Cocoa” at the top of your swift code.
So below are several API calls that are alternatives for command line utilities for macOS post exploitation.
Taking A Screenshot
Sample Swift code for taking a screenshot using macOS internals:
The example above gets a screenshot and stores it in the variable “jpegData”. Then the data in this variable is sent over a socket connection to a listening server. (note, in the example above imported the Socket library from https://github.com/IBM-Swift/BlueSocket).
The example above serves as a better post exploitation option than running the “screencapture” command from the terminal, as this would leave bread crumbs that blue team can easily trace and alert on. However, if you were interested in running the screencapture shell command directly from Swift, an example of that is below:
Fake Authentication Prompt
There are different ways you can go about presenting a fake authentication prompt on macOS.
My preferred approach is to manually build alert properties and contents via the Cocoa API. Here is an example of code to do so:
This example does not leverage any command line utilities. In my testing the code above works very well when building .app package files (labeled as “Cocoa Applications” in XCode). However, I have not been able to get consistent results when using this code from mach-o binaries. In those instances, when text is typed into the command box, the text is redirected to a different window. This may be a bug and I have not yet found a good workaround.
Another example of a way to launch a fake pop up without launching /usr/bin/osascript is to use the NSAppleScript class. I have been able to get this approach working when using Cocoa .app packages, but not when building mach-o binaries. Here is an example of this code:
In the example above the result variable contains the actions performed by the user on the fake prompt (button clicked and text entered).
Lastly, another example is directly invoking the /usr/bin/osascript binary from Swift. Downside with this approach is that it is easily detectable (ex: blue team can hunt on osascript with -e and the word popup in command line artifacts and find it). Here is an example of this code:
In all examples above the fake popup prompt looks like this:
Seems that the only consistently working method I have observed in my testing when launching fake pop-ups from mach-o binaries is the last option (directly invoking /usr/bin/osascript from Swift — least desired option). However, when building Cocoa .app packages the less detectable approaches (first two approaches above) have consistently worked and provide a good way to perform this post exploitation task while staying off the command line.
Listing Internal IP Addresses
To obtain all internal addresses on a macOS host using Cocoa API calls, you can use the code below, which is on stackoverflow:
I have confirmed that the code above from stackoverflow works as expected. This function returns a string array type, which is the addresses string array populated with any internal IP addresses found.
Navigating The File System
In order to list your current directory, change directory paths, and cat files on a macOS host without using common command line utilities (ex: “cd”, “ls”, “pwd”, “cat”), Swift provides access to macOS internals that can be of use.
An example of getting your current directory and then changing to a new directory in Swift using macOS internal calls:
Then in order to list directory contents, you can use the following Swift code:
Lastly, if you wanted to view the contents of a file (i.e., cat a file) without using the “cat” command, you can use the Swift code below:
When I rewrote MacShell from python into Swift, I used variables (entered by the operator on the C2 server) and passed those as variables to code to dynamically navigate the file system and cat contents as requested by the operator.
Accessing Clipboard Contents
Below is an example of how to access pasteboard contents via Swift:
Since the code above does not leave any easily searchable command line artifacts, it serves as an option that is harder to detect than the easier method of invoking osascript -e ‘return (the clipboard)’ from Swift (which leaves command line artifacts that can be easily found).
The process of downloading files using Swift is pretty similar to the example above for viewing the contents of a file, except you add a few more lines of code to send the data somewhere. An example is below:
In this example, I imported a socket library for Swift (from: https://github.com/IBM-Swift/BlueSocket.git), created a socket connection, and wrote the data from the file to the socket. There are a few additional steps associated with importing Swift libraries, which I will skip over for now.
In my actual Swift post exploitation client, I used a file marker so that the server would know where the end of the file is when it saves it and writes to disk.
Get Info on Running Processes
The only way I know of currently to gather information on running processes in Swift is by using the NSWorkspace class. An example is below:
Since this code is looking at running applications, this will not capture all running daemons/processes on the macOS host (though it will still pull back a lot of good information). An alternate option is to spawn “ps” or “lsof” directly from Swift, but that would leave behind easily detectable command line artifacts.
Gathering Basic System Information
This is an example of pulling basic information about a macOS system using Swift. Running osascript -e ‘return (system info)' from Swift returns more verbose information, but again will leave behind traceable command line artifacts that can be easily found.
I am currently not aware of any endpoint detection and response (EDR) products for MacOS that provide visibility into Cocoa API calls. My recommendation for detection is as follows:
First build detections for common command-line utilities and post exploitation frameworks based on scripting languages. Examples include:
- Any MS Office product spawning /bin/sh
- Any scripting language spawning /bin/sh (may take some tuning initially if admins/developers are doing this)
- Any scripting language spawning osascript
- osascript with “-e” and “popup” (possible fake credential prompt to a user)
- osascript with “-e” and “clipboard” (possible clipboard dump)
- abnormal counts of network connections from a single python or ruby process
In terms of detecting macOS Cocoa API calls for post exploitation, since I am not aware of a good way to do this, I recommend the following:
- network visibility (hosts communicating externally, frequency counts of destination hosts, beaconing patterns, length of connections, etc.)
- visibility into new binaries/apps (ex: apps dropping binaries to the system, apps making HTTP POST requests, apps making prolonged network connections, new mach-o files being dropped to disk, etc.)
- pull down my tool SwiftShell (link at beginning of this post) and run the client mach-o on a macOS host with EDR and note visibility/detection gaps
I also recommend staying up on the JAMF acquisition of Digita Security, as this should lead to very solid enterprise macOS detection and prevention techniques and solutions that blue teams can leverage.
There are other examples which I did not include, such as using Swift to install persistence. My goal was to demonstrate how just like C# can be used by red teamers on the Windows side of the house to access Windows internals and APIs, Swift can provide similar functionality for macOS via the Cocoa API. Just as blue teamers on the Windows side of the house have had to become more familiar with Windows internals, I think it is a matter of time before we start seeing a similar transition on the macOS side of the house. So hopefully this post will not only help red teamers with some ideas on using macOS internals but will also help blue teamers begin to posture for detecting these types of attacks by improving visibility and detections.