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

Last edited on

What is the Hardened Runtime?

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 information in this article are only applies to automating applications which are configured to use the Hardened Runtime with Squish.

Hardened Runtime Entitlements required by Squish

Squish for Qt and Squish for Mac are both affected by the Hardened Runtime, because both use the same mechanism for non-intrusive hooking.

Entitlement Allow DYLD Environment Variables

Allow DYLD Environment Variables is always required to be able to inject code into the running AUT process (which is what Squish is doing).

Entitlement Disable Library Validation

Disable Library Validation is need to avoid that every library, which Squish attempts to load into the AUT process, needs to be signed. (Required because the Squish libraries loaded into the AUT process are not signed with the digital certificate of the AUT developer.)

While disabling library validation is certainly easier, there may be reasons not to do this. With Squish for Qt it also works to use the built-in hook and code-signing all Squish libraries that are being loaded into the AUT (with the digital certificate of the AUT developer).

(The steps required to make Squish's quickaddressbook example work with library validation enabled are explained further down on this page.)

Entitlement Allow Execution of JIT-compiled Code

Allow Execution of JIT-compiled Code is required on ARM (Apple Silicon) based CPUs if the AUT uses JavaScriptCore . (For example if it uses WebKit / WKWebView for embedding web views.)

Without this entitlement (or if it is disabled), the AUT will likely crash when the web view of the application initializes "JavaScriptCore" (specifically in JSC::SecureARM64EHashPins::initializeAtStartup()).

Checking for Hardened Runtime

It may be an option to simply ask the application developer.

Besides that, the tool codesign (provided separately by Apple) can be used to check. It is part of Xcode, but also of the Xcode Command Line Tools. The easiest way to install it is by executing one of the following commands in Terminal - this should prompt installation of the Xcode Command Line Tools:

( source )

Once installed, execute this command:

codesign -d -v /System/Applications/TextEdit.app 2>&1 | grep "CodeDirectory"

This command produces an output that looks something like this:

CodeDirectory v=20400 size=1867 flags=0x0(none) hashes=48+7 location=embedded

The flags entry indicates which flags have been enabled. If the runtime flag (0x10000) is present, 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.

Checking for existing Entitlements

The Hardened Runtime supports exceptions to individual items, called 'entitlements', which comprise the Hardened Runtime.

And some of these entitlements are required for automation through Squish.

To find out which entitlements are already enabled in for an executable:

codesign -d --entitlements - --xml /System/Applications/TextEdit.app | xmllint --format -

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 these keys exist and are set to true:

KeyCapabilityEntitlement
com.apple.security.cs.disable-library-validationHardened RuntimeDisable Library Validation
com.apple.security.cs.allow-dyld-environment-variablesHardened RuntimeAllow DYLD Environment Variables

If they're missing, or set to false, hooking with Squish will fail.

If the application is sandboxed, additional measures need to be taken into account.

Adding Hardened Runtime Entitlements

In an Xcode Project

Xcode Project
Adding "Hardened Runtime" (via double click) in dialog "Capabilities"
Adding Entitlements required for automation with Squish

After making such changes, the application typically needs to be rebuilt.

(Also see Hardened Runtime .)

For an existing executable using codesign (manually)

It is possible to sign an executable manually via the codesign command line tool. This requires an Apple developer certificate.

(codesign is provided via Xcode and/or the Xcode Command Line Tools. Installation of the "Xcode Command Line Tools" can be triggered by executing one of the following commands in a shell/Terminal ( source ): clang, gcc, git.)

The general syntax is:

codesign --entitlements /path/to/entitlements.plist -s <identity> /path/to/executable

<identity> refers to your code-signing identity. To list of available code-signing identities on the local computer:

security find-identity -v -p codesigning

entitlements.plist must be a text file in 'plist' format, for example (obtained by executing: codesign -d --entitlements - --xml /System/Applications/TextEdit.app | xmllint --format -):

<?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>

Testing with 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"

Information from Apple about using the codesign tool: