Will macOS and iOS merge?
Janury 24, 2024
Our goal at DFF is to reveal any threats on mobile devices, and that requires us to do a fair share of both static and dynamic analysis. The former is simple enough, as you can throw a binary into a variety of disassemblers and decompilers. The latter, however, is challenging - especially in the case of iOS, which - outside of “Research Devices” - does not have an environment hospitable for debugging.
In today’s post, we would like to share a simple, but quite powerful approach - changing the playing field from the challenging iOS to the much more welcoming macOS.
Let’s port iOS binaries to macOS :)
Let’s take a look at the relevant differences between iOS and macOS Mach-O binaries:
Architecture, Platform and SDK:
iOS executables can be built for ARM-based processors, while macOS executables can be built for both Intel-based and the newer ARM-based processors family (i.e. Apple Silicon in all its variants). This information is stored in the header section of the Mach-O, as shown below:
Machine-specific values are defined for all supported architectures and CPUs in <mach/machine.h> header.
The platform related information is otherwise stored in a load command named LC_BUILD_VERSION: This replaces the older LC_MACOS/IPHONEOS/TVOS/WATCHOS_VERSION_MIN, since Apple keeps making new “OSes” (most recently, VISION OS), though in fact these are all identical, with minor UI differences and maybe an odd daemon here or there.
Reference: darwin-xnu/EXTERNAL_HEADERS/mach-o/loader.h
The LC_BUILD_VERSION load command is part of the Mach-O file format and contains details about the version of the binary, including the platform (operating system), build version, and information related to the SDK (Software Development Kit) used for compilation. At one time, this was informatory only, but is now verified by the OS, which will kill the binary on mismatch.
The strategy here is quite simple:
We are going to map an input Mach-O (taken for instance from an IPSW) to memory, iterate through its Load Commands until we find LC_BUILD_VERSION and tweak it accordingly.
Note that any modification of the header will invalidate the signature. We can discount this, since on macOS unsigned binaries (or linker signed) are allowed - so we can remove the signature, and re-fake-sign in some way.
So we wrote the tool, gave it a try, and ……
the binary still got killed.
Looking at the os_log log (with /usr/bin/log stream) we see:
The message is from the kernel, in exec_mach_imgact. The specific check responsible to prevent the binary from running is in the OSX (not iOS) kernel:
Reference: darwin-xnu/bsd/kern/kern_exec.c
arm64e introduced PAC for both mobile and desktop platforms; on macOS, this feature was, at the time, a preview ABI, therefore it requires SIP to be disabled and ‘-arm64e_preview_abi’ boot-arg set.
We can workaround this check by tweaking Mach-O’s header field cpusubtype field so that ip_origcpusubtype does not read 0, therefore allowing execution.
Back to Tests!
Let’s try our tool against some target that is not shipped as a macOS binary, for instance lsdiagnose. This is one of the command line utilities shipped with stock iOS for the purposes of running under sysdiagnose. iOS has a couple of other such nice utilities (as well as a few interesting daemons which can be subjected to this).
Trying this on the binary from the iOS image…
Aaaaaand…..
It works!
This means we can now execute - and also debug - any binary from any iOS variant with the comfort of our development environment. This won’t get entitled binaries, but if you use a SIP disabled system for your debugging and reversing (in a VM, we hope!) that’s not a problem.
This technique should work as is on all CLIs and most daemons (/usr/libexec), since those rely on dylibs present both in the iOS flavors and macOS. Vision OS isn’t out yet, but it should be workable there just as well. It should also work the other way around - porting from macOS to your favorite iOS variant, though naturally there you’d need it to be jailbroken.
Lastly, we leave it as a thought exercise for the interested reader if this will work on actual Apps (that is, library dependencies on iOS specific frameworks, which can’t be found on macOS).
TL;DR
Craig Federighi vehemently denies macOS and iOS will merge.
Ah, yes. And the source: