Launching Apfell Programmatically
This blog post serves as a quick reference on how to launch Cody Thomas’s Apfell on a macOS device via an app package. Link to Cody’s Apfell framework:
There are different ways you can execute an Apfell launcher on a macOS device (ex: can generate a python script from Apfell and run it on the target host, can run an osascript one-liner on the host, etc.). However, I want to focus on how to execute an Apfell launcher on macOS without needing to rely on python (might be easily detectable depending on the environment and blue team) and without using the /usr/bin/osascript binary (which can also be keyed on for easy detection). So, this is a quick walkthrough on how to launch Apfell using Swift code from a .app package on a macOS device running Mojave.
Create An App Package in Xcode
- Open Xcode
- File → Create New Xcode Project
- Click “MacOS” at the top and then click Cocoa App:
4. Set the name and check the “Use Storyboards” option:
5. On the Main.storyboard, design your layout as desired (this is the window that the user will interact with)
6. When done designing your main.storyboard window, option click (hold down the “option” button while clicking) “ViewController.swift” on the left panel. Example below
7. While holding the “control” button, click and drag the button from your main.storyboard window over to the ViewController.swift window on the right and in the pop-up box set a variable name for your button. This will then set up your text box as an @IBAction variable in your ViewController.swift code. Example below:
8. Next, add the code here to execute an Apfell launcher (note: you should already have your Apfell C2 server up and running before this step):
note: I imported the Cocoa API (“import Cocoa”) and the OSAKit framework (“import OSAKit”) in the code above. Both are standard in Swift.
Quick step through of the code above:
- creates an asynchronous thread
- creates an OSAScript class instance (instead of the /usr/bin/osascript binary), feeds the command to it, and executes it
- sleeps for 1 sec to give it time to execute
- hides the app from the dock
- hides the app window (but app is still running Apfell in the background)
also of note: on Mojave Application Transport Security (ATS) checks outbound connections from .app packages to ensure that the connection to the destination website meets Apple’s security requirements. I have had to use valid trusted SSL certificates on the destination server for ATS to allow the .app to connect out. Outbound connections to a server with self signed certificate were stopped by ATS. I even tried modifying the Info.plist file to add a domain exception but still no success. So I recommend using a valid signed cert (Let’s Encrypt works fine).
Additionally, in order to successfully pass ATS checks when connecting to an SSL server add the following entries to your Info.plist file in your app package:
9. Ensure the app has the proper sandbox permissions needed (outgoing connections, file access, etc.). This can be done in Xcode by clicking your app name in the upper left hand corner and then clicking “Capabilities” → “App Sandbox”.
10. Ensure “Hardened Runtime” is set to On. (click your app name in the upper left hand corner and then click “Capabilities” → “Hardened Runtime”)
11. Sign your app with your developer cert. I recommend using a developer account that is not your personal apple developer account. First you’ll need to set up a developer account at developer.apple.com (costs $99/yr). Once set up, you can link your apple ID to Xcode (Xcode → Preferences → Accounts → Add Apple ID). Then after linking to your Apple ID and managing your certs (that you have in your developer account at developer.apple.com), you can have Xcode automatically manage signing for your project using your developer certs. This can be set by clicking your app name in the upper left hand corner of Xcode and then click “General” → “Signing” → “Automatically manage signing”. Enter the team name that corresponds to the developer ID you set up.
12. Starting with macOS 10.14.5, all developers creating a Developer ID cert for the first time will be required to notarize their apps (https://developer.apple.com/news/?id=04102019a). To submit the app for notarizing, follow the steps below in Xcode:
- Product → Archive → Distribute App →select “Developer ID” →Next →Upload
The app package will then be submitted to the Apple notarization service. In my experience it comes back pretty quickly (in about 5–10 mins). You will receive an email once/if the notarization is successful and then you can export the signed and notarized .app package from that same archive window (just click the app name in the archive window, and you will see a green check mark and “Ready to distribute”. In the right panel you can click the “Export Notarized App” and download it where you desire).
Up to this point, I have not had any issues with getting my apps with Apfell launchers notarized (i.e., they pass the notarization checks from Apple)
13. Once signed and notarized, you can then distribute. You can do so by creating a .dmg and adding your app into the .dmg (easiest) or by creating a .pkg installer for the app. But keep in mind if you go the .pkg route, you will also need to repeat the signing process for the .pkg file.
Update:
My test app with a the JXA code to launch Apfell was successfully notarized by Apple when submitted. I believe this is because it is simply running JXA, with the Apfell Objective C post exploitation code living on the server rather than in the app code and and since the notarization check is looking solely at the app code and not server code, the app just looks like an app running JXA. I have also shared this blog post with Apple and notified them.
Detection Opportunities
- This code as written, will cause the .app package to make the outbound request to the Apfell server. In doing so, the app will use a unique user agent string that should stand out: “<.app_name>/<.app_version> CFNetwork/<version> Darwin/<version>”
- This code as written, will cause the .app package to make a direct connection to the Apfell server in the background. Periodic HTTP/HTTPS beaconing from a .app package over a period of time can also be a good indicator.
- If your org monitors new apps downloaded, installed, or executed on macOS endpoints, then you could further narrow down your search looking for apps with the hidden attribute (in Swift this attribute is the .isHidden == true). An example of that code is below: