Apple silicon Macs can run iPhone and iPad apps natively using the same Mac Catalyst technology that powers apps like Twitter or Darkroom on macOS. Although all iOS apps are by default directly available on the Mac App Store, developers can opt-out and make their apps unavailable. Unfortunately, some of the most popular apps like Instagram, Deliveroo or Netflix have been removed from the Mac App Store… but that does not mean they cannot run on macOS, they are simply hidden in the Mac App Store. If you have purchased or downloaded any app from the iOS App Store, you have the option to make a backup of it using an IPA file, and those files can be used on Apple silicon Macs to install any iOS app.
Currently there are multiple approaches to download those IPA files for any iOS app:
Once you have the IPA file, you can simply double click it on any Apple silicon Mac and voilà!
Apple Silicon Macs running iOS apps natively is truly game-changing.
— Pedro José Pereira Vieito (@pvieito) November 16, 2020
macOS will become the most complete app platform ever: from complex workflows with Xcode, Final Cut Pro or MATLAB to ordering with Deliveroo, browsing IMDb or watching Netflix… there will be an app for that! pic.twitter.com/CiCTOROaNR
While the Home app allows you to read and control your HomeKit-based devices on macOS, iPadOS and iOS, sometimes you want more control. You may want to bridge some sensor to other protocol, expose you home temperature on a public API, or simply export the historical data to a CSV file. Either way, currently the HomeKit
framework is private API on macOS, and it is not available on other platforms like Linux or Windows.
Fortunately there are some cross-platform implementations of the HomeKit Accessory Protocol that support controller-mode functionality:
In particular HomeKit Python supports reading and writing HomeKit characteristics on paired devices as well as generating additional pairings. The main problem is that typically HomeKit devices only support one main pairing controller, thus, once it is paired with the Apple Home app it can only be controlled with the pairing keys managed by the homed
system daemon which are gated by the HomeKit
framework.
Fortunately, the HomeKit pairing keys are stored on the iCloud Keychain. Unfortunately (but reasonably), the system tries hard to hide these pairing keys. In particular, they do not appear on the Keychain app nor can be read with the Security
framework SecItem*
family of APIs without some private entitlements granting access to the keychain access group com.apple.hap.pairing
.
First of all, we have to subvert the AMFI security model to be able to sign arbitrary executables with private entitlements. To do it, we have to disable System Integrity Protection and AMFI. Rebooting on Recovery OS we can disable the protections using the Terminal app:
$ csrutil disable
$ nvram boot-args=amfi_get_out_of_my_way=0x1
$ reboot
After reboot, we can use KeychainTool to dump the HomeKit keychain items.
$ git clone https://github.com/pvieito/KeychainKit.git
$ cd KeychainKit
$ cat KeychainTool/KeychainTool.entitlements
# The all-powerful * `keychain-access-groups` entitlement which grants its bearer permission to read all keychain items:
<?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>keychain-access-groups</key>
<array>
<string>*</string>
</array>
</dict>
</plist>
Now we will compile and run KeychainTool
to dump all the keychain entries in the com.apple.hap.pairing
access group.
Bear in mind that KeychainTool
uses CodeSignKit to self sign its executable with private entitlements before relaunching itself. If a codesign
error is shown set the CODESIGNKIT_DEFAULT_IDENTITY
environment variable to the name of your Apple Developer certificate as presented on the Keychain app.
$ swift run KeychainTool -g com.apple.hap.pairing
[*] HomeKit Pairing Identity (com.apple.hap.pairing)
[ ] Account: 7C73D188-BF12-4B8C-B7A5-5842D71C24EA
[ ] Key: “21159cfa6032438be197d668b3562e262441965789f95634d6460d4cce5cc706+d2ed8558b369b4ee1fbf4f9eb8d687ee2799aba5608efc2712d8175697bd8ad8”
[*] Paired HomeKit Accessory: CC:0D:07:E4:F7:54 (com.apple.hap.pairing)
[ ] Account: CC:0D:07:E4:F7:54
[ ] Key: “3a4473f1efe5e378fdd329826936f34b674fcb97c8aa5bd9818abde46963f864”
[*] Paired HomeKit Accessory: 58:CA:96:CE:66:5F (com.apple.hap.pairing)
[ ] Account: 58:CA:96:CE:66:5F
[ ] Key: “44d34407d583aee3b12b774a6eb15ee96c527fa83af1db66ac90f60494bbbc29”
Voilà! Here we have all we need:
7C73D188-BF12-4B8C-B7A5-5842D71C24EA
and the LTP and LTS keys required by the HomeKit protocol separated by a +
sign on the entry key payload: 21159cfa6032438be197d668b3562e262441965789f95634d6460d4cce5cc706
, d2ed8558b369b4ee1fbf4f9eb8d687ee2799aba5608efc2712d8175697bd8ad8
58:CA:96:CE:66:E9
has the LTP key 44d34407d583aee3b12b774a6eb15ee96c527fa83af1db66ac90f60494bbbc29
.Now we can use the pairing keys to set up HomeKit Python on any device and platform:
$ python3 -m pip install "homekit[IP]" --user
$ python3 -m homekit.discover
# `homekit_python` will list all HomeKit devices found on the network:
Name: Eve Extend XXXX._hap._tcp.local.
Url: http_impl://192.168.0.123:8080
Configuration number (c#): 5
Feature Flags (ff): Supports HAP Pairing (Flag: 1)
Device ID (id): 58:CA:96:CE:66:5F # Same Device ID as in the keychain entry.
Model Name (md): Eve Extend XXXXXXXX
Protocol Version (pv): 1.1
State Number (s#): 1
Status Flags (sf): Accessory has been paired. (Flag: 0)
Category Identifier (ci): Bridge (Id: 2)
$ mkdir ~/.homekit_python
$ python3 -m homekit.init_controller_storage -f ~/.homekit_python/pairing.json
$ nano ~/.homekit_python/pairing.json
# Write the pairing credentials of each the device:
{
"EveExtend": {
"AccessoryPairingID": "58:CA:96:CE:66:E9",
"AccessoryLTPK": "44d34407d583aee3b12b774a6eb15ee96c527fa83af1db66ac90f60494bbbc29",
"iOSPairingId": "7C73D188-BF12-4B8C-B7A5-5842D71C24EA",
"iOSDeviceLTSK": "d2ed8558b369b4ee1fbf4f9eb8d687ee2799aba5608efc2712d8175697bd8ad8",
"iOSDeviceLTPK": "21159cfa6032438be197d668b3562e262441965789f95634d6460d4cce5cc706",
"AccessoryIP": "192.168.0.123",
"AccessoryPort": 8080,
"Connection": "IP"
}
}
$ python3 -m homekit.identify -f ~/.homekit_python/pairing.json -a EveExtend
# The HomeKit device should identify itself (for example blinking an LED).
$ python3 -m homekit.get_accessories -f ~/.homekit_python/pairing.json -a EveExtend
# Shows all the accesories exposed by the device.
$ python3 -m homekit.get_characteristic -f ~/.homekit_python/pairing.json -a EveExtend -c 3.38
# Shows the value of a given characteristic, for example the room relative humidity:
{
"3.38": {
"value": 54.4525146484375
}
}
Finally, remember to re-enable System Integrity Protection and reboot your Mac:
$ csrutil clear
Entitlements are an important part of Apple Security architecture. They allow Apple to limit an OS feature to be only available to Apple-approved processes. Nowadays, even with System Integrity Protection disabled, the AMFI Kernel Extension and amfid
process dueto will always kill any process at execution with restricted Entitlements not signed by Apple or with a properly Apple-approved embedded provisioning profile.
Unrestricted Entitlements are available to all signed binaries, even ad-hoc (some examples of this are the Sandbox entitlements com.apple.security.*
or the application identifier one com.apple.application-identifier
) but they do no give any special capability to the process, on the contrary, they limit its reach.
To allow any Entitlements, even the more interesting Restricted ones, for a Developer ID signed binary we have to modificate the amfid
process (to allow adhoc signatures too we would have to patch the AMFI Kernel Extension or its underlying dependencies which I didn’t try).
To patch a system daemon, we have to disable macOS System Integrity Protection. After some reverse engineering, it seems one of the main decisions in the amfid
flow is in the address offset 0x347D
.
amfid
.Knowing that we can change the following two instructions from:
test %r14, %r14
je loc_100003531
To this:
mov %r14, %r15
jno loc_100003531
This way the flow will always jump to loc_100003531
and %r14
will become null (%r15
is always null in this point), so every Developer ID signed process will be validated even without a provisioning profile allowing its Entitlements.
To achieve this modification we can go the hard way by modifing the binary in situ (it is located at /usr/libexec/amfid
) or the soft way: patching amfid
memory at runtime. I preferred the second option so I could restart the unpatched amfid
code by simply killing it.
To do it I ported to Python 3 a wrapper for Mach VM APIs called pymach and added a new function to get the ASRL Slice Offset of the process: PyMach for Python 3. With that I wrote this script for macOS 10.12.2. To use it simply run:
$ sudo ./amfid_patch.py
And answer yes
when asked if you want to patch the process. Voilà! Now any Developer ID signed binaries will be executed even with restricted Entitlements.
You can set any Entitlement you want, like com.apple.developer.icloud-container-identifiers
or com.apple.private.appleaccount.app-hidden-from-icloud-settings
with an arbitrary iCloud container. For a complete list of private Entitlements used by Apple you can go to Jonathan Levin’s Entitlements Database.
In macOS a given app can or cannot be sandboxed and since macOS Sierra non App Store apps can access iCloud APIs so could be a non-sandboxed app using this locations to sync data.
So if you want to access a sandboxed iCloud location you should go to the app container and use it as the home path.
$SANDBOXED_CONTAINER = ~/Library/Container/com.example.app/Data
Each app can have one or more document containers inside named with its Bundle ID:
~/Library/Mobile Documents/
The information and details of each container are stored here:
~/Library/Application Support/CloudDocs/session/containers
The info is stored in a plist file with the name of the container for example:
iCloud.com.pvieito.Example.plist
And the icons are stored in a folder with the name of the container:
iCloud.com.pvieito.Example/40x40_iOS.png
iCloud.com.pvieito.Example/80x80_iOS.png
iCloud.com.pvieito.Example/120x120_iOS.png
This synced data is stored as a property list file in:
~/Library/SyncedPreferences/
$(SANDBOXED_CONTAINER)/Library/SyncedPreferences/
CloudKit works online but macOS stores a cache of its contents in a folder appropriately called CloudKit:
~/Caches/CloudKit
$(SANDBOXED_CONTAINER)/CloudKit