Debug any iOS Apps on M1 Mac
We have no permission to attach the lldb to any other iOS Apps on the M1 Mac, when SIP is enabled. But we couldn’t launch the iOS Apps if SIP is disabled. It seems that fairplayd has a check for the system boot policy security mode and refuses to decrypt the iOS App macho when SIP is disabled.
So the question is how to debug.
TL;DR
-
Disable
SIPto get the task ports for system processes - Get the decrypted iOS App from jailbroken device with tools (such as bagbak, flexdecrypt, …)
- Resign (free iOS Developer account)
- Patch
amfidprocess to bypass signature check - Patch
UIKitSystemprocess to display the iOS UI as normal posix_spawnthe iOS App macho with thesuspendattribute- Attach my debugger
About re-sign
It is said that only the decrypted IPA resigned with Enrolled Apple Developer certificate ($99 per year) is permitted to install and run. The key operation is adding the M1 Mac device’s UDID to the developer’s device list.
However, I am a free developer now and I don’t have the necessary certificate, so I have to patch the certificate checking code.
Next, I will talk about how I found the patch point and make it run normally.
Try to launch directly
-
open /path/to/iOSApp.appI got the error: The application cannot be opened because it has an incorrect executable format.

-
Directly execute the macho
/path/to/iOSApp.app/iOSAppI got the crash:

Note the Termination Reason: Binary with wrong platform.
posix_spawn internal
From the previous termination reason, I just wonder why the OS can launch the iOS App macho normally without the wrong platform error, when we open it normally. Maybe it has some special spawn attributes for the iOS platform macho?
To prove my guess, I looked up for the posix_spawn implementation from the latest XNU source code. And the answer is yes, there are some new posix_spawn_attributes in the function. In order to make use of these attributes, I made a differece of the spawn related functions inside the module libsystem_kernel.dylib :

I got 8 new interfaces:
_posix_spawnattr_getarchpref_np
_posix_spawnattr_setarchpref_np
_posix_spawnattr_set_subsystem_root_path_np
_posix_spawnattr_set_platform_np
_posix_spawnattr_disable_ptr_auth_a_keys_np
_posix_spawnattr_set_ptrauth_task_port_np
_posix_spawnattr_setnosmt_np
_posix_spawnattr_set_csm_np
and the data structure changes:

Debug and reverse xpcproxy
How the system uses these posix_spawn_attributes to spawn an iOS App macho?
Through monitoring, I found the iOS App launching process is as follow:
-
launchd (pid=1)process spawned a subprocessxpcproxy, with aapplication-identifieras its command line parameter -
Then the
xpcproxyprocess calledposix_spawnwith attributePOSIX_SPAWN_SETEXEC -
Finally the
xpcproxyprocess turned into the iOS App process
Next, I need to debug and reverse the xpcproxy :
- Set breakpoints on the function
posix_spawn_file_actions_initandposix_spawnattr_init - When the breakpoints hit, set watchpoints for the parameter
posix_spawn_file_actions_tandposix_spawnattr_tto see how they are initialized. - When the watchpoints hit, reverse the target function according to the call stack trace.
The following code is the result of reversing:
pid_t child_pid;
posix_spawn_file_actions_t action;
posix_spawnattr_t attr;
/* Create an action object, debug and reverse from xpcproxy*/
posix_spawn_file_actions_init(&action);
posix_spawn_file_actions_addopen(&action, 0, "/dev/null", 0x20000, 0x1B6);
posix_spawn_file_actions_addopen(&action, 1, "/dev/null", 0x20002, 0x1B6);
posix_spawn_file_actions_addopen(&action, 2, "/dev/null", 0x20002, 0x1B6);
/* Create an attributes object*/
posix_spawnattr_init(&attr);
posix_spawnattr_setflags(&attr, 0x4202); // the original is 0x42c2, with POSIX_SPAWN_SETEXEC|POSIX_SPAWN_START_SUSPENDED
posix_spawnattr_setcpumonitor(&attr, 0xfe, 0);
posix_spawnattr_setjetsam_ext(&attr, 0xc, 0x3, 0x4000, 0x4000);
posix_spawnattr_disable_ptr_auth_a_keys_np(&attr);
responsibility_spawnattrs_setdisclaim(&attr, 1);
//posix_spawnattr_set_subsystem_root_path_np(&attr, "/System/iOSSupport/");
posix_spawnattr_set_platform_np(&attr, 2); // #define PLATFORM_IOS 2
char *envir[] = {
//"CFFIXED_USER_HOME=~/Library/Containers/UUID/Data",
"COMMAND_MODE=unix2003",
//"HOME=~/Library/Containers/UUID/Data",
"LOGNAME=mickey",
"MallocSpaceEfficient=1",
"PATH=/usr/bin:/bin:/usr/sbin:/sbin",
"SHELL=/bin/bash",
//"SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.RANDOM/Listeners",
//"TMPDIR=/Users/mickey/Library/Containers/UUID/Data/tmp",
"USER=mickey",
"XPC_FLAGS=1",
//"XPC_SERVICE_NAME=application.com.xxxapp.iOS",
//"_DYLD_CLOSURE_HOME=/Users/mickey/Library/Containers/UUID/Data",
//"__CFBundleIdentifier=com.xxxapp.iOS",
"__CF_USER_TEXT_ENCODING=0x1F5:0x0:0x0", 0
};
posix_spawn(&child_pid, argv[0], &action, &attr, &argv[0], envir);
posix_spawnattr_destroy(&attr);
posix_spawn_file_actions_destroy(&action);
printf("PID of child: %d\n", child_pid);
Patch to bypass signature check
I compiled and ran the reversed code to launch the decrypted iOS App, I got the error:
posix_spawn: Bad executable (or shared library)
The signature was broken due to decryption, then I resigned it with command:
# XXX is the app name and main executable name
codesign -d --entitlements :- /path/to/XXX.app 1>XXX.ent.plist
codesign -f -s "Your free developer certificate" --timestamp=none /path/to/XXX.app/Frameworks/*
codesign -f -s "Your free developer certificate" --entitlements XXX.ent.plist --timestamp=none /path/to/XXX.app
Launch again, no wrong platform error this time, but I got the crash log like this:

This is because the sandbox failed to initialize. My first solution was simply to patch the function _libsecinit_appsandbox to return directly, then the iOS App can run without sandbox restriction. But I hope the iOS App could run inside the sandbox. So I tried to find out why the function _libsecinit_appsandbox fails to initialize. The function talks to the secinitd process by xpc pipe com.apple.secinitd. In the secinitd process, the API call xpc_copy_entitlement_for_token returned NULL. It failed in the syscall csops_audittoken(pid, CS_OPS_ENTITLEMENTS_BLOB, ...) with errno EINVAL. The syscall failed because the target process’s p_csflags (struct proc offset 0x2d0) is 0, not flagged with (CS_VALID | CS_DEBUGGED). The CS_VALID flag was not set due to failure of load_code_signature. load_code_signature called the function vnode_check_signature of AppleMobileFileIntegrity.kext, and then vnode_check_signature called function verify_code_directory of the amfid process by mach_msg_rpc_from_kernel_proper to check the signature. Then I found another hint from the console log, which seems closer to the root cause:

Then I inspected and debugged the amfid process :

The key point is the function MISValidateSignatureAndCopyInfo , implemented in the libmis.dylib , returned an error. Through debugging, my attention turned to a callback of the function:


I just patched the callback function at line 12, to let it jump to line 40 directly. The patch is work and the sandbox could be initialized as usual.
Another solution
Re-signing with free developer’s certificate seems valid for only 7 days. Therefore, I also tried to re-sign it with ad-hoc , which has no time limitation. But it failed in the function vnode_check_signature of the AppleMobileFileIntegrity.kext, error string:
“unsuitable CT policy 0 for this platform/device, rejecting signature.”
Error occurred before the rpc call to amfid process.
So I have to load a kext to patch it for this solution:

Patch to show iOS UI
Now, I can launch the iOS App macho directly. But It has no Window (UI).
And I found a error from the console log:

Then I inspected and debugged the UIKitSystem:

Finally I got the key function:

Just patch it to return true(1)
Debug it
Through the effort before, I can run any decrypted iOS Apps on M1 Mac (SIP disabled). Even I am just a free developer :)
Next, I can debug the iOS App with lldb as usual.
But I prefer the IDA Pro for debugging and strongly recommend it for you.
-
sudo /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/Resources/debugserver 127.0.0.1:23946 -
Select
Remote iOS Debuggerfrom IDA Pro -
Debugger -> Debuggser Options -> Set specific options -> Uncheck
Launch debugserver automaticallyBecause you just launched it manually

-
Debugger -> Process options
Set the hostname to
127.0.0.1, and the port default is23946 - Launch or Attach
- Enjoy your debugging