Using Squish to automate macOS applications built with the 'Hardened Runtime'

Last edited on

What is a Hardened Runtime AUT?

macOS 10.14 and Xcode 10 introduce a new feature called 'Hardened Runtime'. It is intended to enhance the security of applications by disabling/disallowing various kinds of loading third-party executable code into the AUT - such as Squish libraries.

It effectively disables various features commonly exploited in security breaches.

The capabilities of the application (Apple calls them 'entitlements') is configured when signing the application. See the Apple page on Hardened Runtime Entitlements on how to do this.

Which entitlements Squish needs

This affects both Squish for Qt and Squish for Mac since they're using the same mechanism for non-intrusive hooking. While disabling library validation is certainly easier, there may be reasons not to do that. With Squish for Qt, using the built-in hook and code-signing all Squish libraries that are being loaded into the AUT with your own certificate works, too.

One entitlement is always needed to make Squish work with the hardened runtime:

The steps required to make Squish's quickaddressbook example work with library validation enabled are explained further down on this page. Instead of signing everything, it is possible to set the following entitlement:

If the AUT uses JavaScriptCore , for example if it uses WebKit / WKWebView for embedding web views, the AUT will crash as soon as JavaScriptCore is initialized (specifically in JSC::SecureARM64EHashPins::initializeAtStartup()) on ARM CPUs, also known as 'Apple Silicon'. To allow testing applications in that particular combination, ensure that you have the following entitlement set:

How to tell if an AUT is hardened?

First, Squish will be unable to attach to your AUT. If you are unable to ask the developer, it is possible to check using command line tools provided by Xcode. First, make sure Xcode is installed with the command line tools, and issue the following command,using the path to your own AUT instead of TextEdit.app.

codesign -d -vv /Applications/TextEdit.app

This command produces an output that looks something like this:

Executable=/Applications/TextEdit.app/Contents/MacOS/TextEdit
Identifier=com.apple.TextEdit
Format=app bundle with Mach-O thin (x86_64)
CodeDirectory v=20100 size=1475 flags=0x0(none) hashes=39+5 location=embedded
Platform identifier=7
Signature size=4485
Authority=Software Signing
Authority=Apple Code Signing Certification Authority
Authority=Apple Root CA
Info.plist entries=33
TeamIdentifier=not set
Sealed Resources version=2 rules=13 files=479
Internal requirements count=1 size=68

In the output look for the line starting with 'CodeDirectory'. The 'flags' entry indicates which flags have been enabled. If the 'runtime' flag (0x10000) is present that means the application uses the hardened runtime feature. If it is not set - like in the example output above - the application does not use the hardened runtime.

To find out which entitlements are already enabled in your AUT, you can execute a command like this:

codesign -d --entitlements - /Applications/TextEdit.app

This produces output in the form of a plist file that looks like this:

<?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>com.apple.application-identifier</key>
	<string>com.apple.TextEdit</string>
	<key>com.apple.developer.ubiquity-container-identifiers</key>
	<array>
		<string>com.apple.TextEdit</string>
	</array>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.files.user-selected.executable</key>
	<true/>
	<key>com.apple.security.files.user-selected.read-write</key>
	<true/>
	<key>com.apple.security.print</key>
	<true/>
</dict>
</plist>

In this output, check if the keys com.apple.security.cs.disable-library-validation and com.apple.security.cs.allow-dyld-environment-variables exist and are set to true. If they're missing or set to false hooking with Squish will fail.

Code-Signing to add entitlements

With Xcode, it is possible to sign an executable manually, with your own developer certificate, to give it new entitlements. The links below explain how:

Testing with hardened runtime and library validation enabled

(Optional) Preparing Squish's quickaddressbook example for hardened runtime testing

This is not strictly necessary, however it will give you a minimal project to compare your own setup to. All commands given here are prepared to be executed inside the build directory where qmake was run. IDENTITY refers to your code-signing identity. This prints a list of available identities: security find-identity -v -p codesigning

  1. Configure the project to use Xcode projects for building so that entitlements can be added easily: qmake -spec macx-xcode ../quickaddressbook
  2. Open the generated Xcode project, add the "hardened runtime" capability and check "Allow DYLD Environment Variables"
  3. Build the app through Xcode (Cmd-B)
  4. Run macdeployqt on the newly built app bundle to add required Qt libraries: macdeployqt Debug/quickaddressbook.app -codesign=IDENTITY -hardened-runtime -timestamp -qmldir=path/to/quickaddressbook/qml
  5. Unfortunately, macdeployqt overwrites our custom entitlements. Re-sign with correct entitlements: codesign -fs IDENTITY --deep --timestamp -o runtime --entitlements quickaddressbook.entitlements Debug/quickaddressbook.app

Signing Squish for Qt libraries

This script signs all necessary libraries of Squish for Qt 7.0.

#!/bin/sh

set -eu

if [[ "$#" -ne 2 ]]; then
    echo "Usage: ${0} signing_identity squish_prefix"
    exit 1
fi

export IDENTITY="${1}"
export SQUISH_PREFIX="${2}"

export ORIGINAL_PWD=$PWD
cd "${SQUISH_PREFIX}"
codesign -fs "${IDENTITY}" --deep --timestamp -o runtime lib/libsquishhook.dylib
codesign -fs "${IDENTITY}" --deep --timestamp -o runtime lib/libtracelib.dylib
codesign -fs "${IDENTITY}" --deep --timestamp -o runtime lib/libsquishqt*
codesign -fs "${IDENTITY}" --deep --timestamp -o runtime lib/extensions/qt/libsquishqt*

# Squish >= 7.0
codesign -fs "${IDENTITY}" --deep --timestamp -o runtime lib/libav*
codesign -fs "${IDENTITY}" --deep --timestamp -o runtime lib/libswscaleSquish.5.dylib
cd ${ORIGINAL_PWD}

It is used like this:

./sign-squish-qt.sh IDENTITY "/path/to/Squish for Qt 7.0.0"