Apple Silicon and Virtual Machines: Custom Serials, DEP Enrolment and the Secure Enclave
With my last post, I briefly mentioned at the end that my next challenge was to figure out whether the usage of custom serial numbers or Automated Device Enrolment (ADE) through the Device Enrolment Program (DEP) was possible on Apple Silicon VMs running macOS. Well today we’ll go over the challenges of getting DEP working, and how iCloud and Custom Kernel Collections all face the same issue.
- Reversing the Virtualization stack
- Modding VirtualApple to gain more private functions
- Granting Private Entitlements
- Booting our Virtual Machine with custom serial numbers
- Secure Enclave: The Missing Piece
- iCloud, OS Betas and Kernel Collections
- Conclusion
Reversing the Virtualization stack
To start, we’ll be delving into Apple’s Virtualization stack once more: /System/Library/Frameworks/Virtualization.framework
.
What we’ll be looking for is references to serial numbers in the framework, and whether there are any exposed methods to supplement our own values.
After a bit of searching, class dumps and a very useful page from the vz wiki, we can see some interesting properties and methods exposed in Virtualization.framework:
// Property
@property (T@"_VZMacSerialNumber",R) _serialNumber
// Class Method
[VZMacMachineIdentifier _machineIdentifierWithSerialNumber:]
_VZMacSerialNumber |
_machineIdentifierWithSerialNumber |
---|---|
Notes:
- Virtualization.framework currently only supports serial numbers of 10 characters. This means serial numbers from pre-2021, such as Intel machines and first-wave M1s, will be unsupported. Models released after the 2021 M1 iMac should be using this new format (ex. 14”/16” MacBook Pros)
- These private APIs were added in macOS Ventura, thus requiring Ventura or newer as the host. However, Monterey guest VMs can still be used with them.
Modding VirtualApple to gain more private functions
Now that we have these fun new APIs to try, we need an app we can modify. For this, we’ll be modding Saagar Jha’s VirtualApple project. With some help from DhinakG, we’re all set to test our VM!
However, on Virtual Machine creation, we get an unfortunate error when trying to start a Virtual Machine with a custom serial number:
Granting Private Entitlements
After some brief debugging, I found that com.apple.Virtualization.VirtualMachine.xpc
is dying with the following line:
FATAL: Unable to create a virtual machine with restricted devices.
Throwing the XPC service in a decompiler, we find our issue:
if (os_variant_has_internal_content("com.apple.virtualization") != 0x0) {
sub_10024efa8("Restricted devices require the com.apple.private.virtualization entitlement.");
}
To resolve this, we’ll need to sign VirtualApple with a private entitlement. Unfortunately for us, that means we need to disable AMFI.
As we did in the last blog post, boot into recoveryOS and run the following in Terminal:
bputil --disable-boot-args-restriction
nvram 40A0DDD2-77F8-4392-B4A3-1E7304206516:boot-args='amfi=0x80'
amfi=0x80
is a boot argument that disables AMFI’s security restrictions, including allowing us to sign arbitrary entitlements on our applications.
- This boot-arg is also known as
amfi_get_out_of_my_way=0x1
Once back in our main OS, we’ll need to add the following entitlement: com.apple.private.virtualization
Simply add it to VirtualApple’s entitlements.plist, and resign:
- You can also simply re-run the project with the updated entitlement if you don’t have an exported app.
codesign -f -s - --entitlements entitlements.plist VirtualApple.app
Booting our Virtual Machine with custom serial numbers
Finally, we can boot our virtual machine with our custom serial! After a brief install using a Store Demo serial number we have access to, we see some success! Our machine successfully appears as an Demo Unit with the expected “Demo Registration: Please, Enter the demo mode activation code” message:
Now for the real challenge, testing a serial number enrolled in DEP.
Now for this test I’ll grab a 10 character serial number off a test machine and see if we can enroll in that machine’s MDM.
While install and initial setup went smoothly, we do hit a serious error:
An error occurred while obtaining automatic configuration settings
The cloud configuration server is unavailable
No matter the OS or the serial number, we keep hitting this activation error…
Secure Enclave: The Missing Piece
After delving quite deep into the MDM enrolment flow, I found our core issue seems to be a missing UCRT.pem
.
mobileactivationd: [com.apple.mobileactivationd:daemon] UCRT DEP enrollment state requested by mdmclient
mdmclient: [com.apple.ManagedClient:MDMDaemon] [0:MDMDaemon:<0x540>] UCRTsmb5: DEP registered according to UCRT: UCRTDEPStateUnavailable
mdmclient: [com.apple.ManagedClient:MDMDaemon] [0:MDMDaemon:<0x540>] CloudConfiguration: isDeviceRegisteredWithDEP: APNS: -1 UCRT: -1 ProvDEP: 0
A UCRT is a User Identity Certificate that’s sent from Apple’s servers after our machine generates an attestation from the SEP (Secure Enclave Processor) using CryptoTokenKit.
/usr/libexec/teslad
requests the UCRT from Apple through-[MobileActivationMacOSDaemon issueUCRT:withCompletionBlock:]_block_invoke
, the request will result inServer error: 400 (bad request)
and thus the rest of the DEP chain will fail.
So why does our Virtual Machine fail to create a UCRT? Well unfortunately it seems to be caused by missing hardware, specifically the lack of a Secure Enclave in our virtual machine. During attestation, the Owner Identity Certificate (OIC) is requested from the Secure Enclave, however our Virtual Machine doesn’t support this and errors:
mobileactivationd: (libbootpolicy.dylib) [com.apple.BootPolicy:Library] BootPolicy: bootpolicy_get_oic: entry
mobileactivationd: (libbootpolicy.dylib) [com.apple.BootPolicy:Library] BootPolicy: SEP command 38 returned 3
mobileactivationd: (libbootpolicy.dylib) [com.apple.BootPolicy:Library] BootPolicy: assert: bpe == 0 (/AppleInternal/Library/BuildRoots/8ca92091-1d5a-11ee-a938-46d450270006/Library/Caches/com.apple.xbs/Sources/BootPolicy/dylib/dylib.c:1942)
mobileactivationd: (libbootpolicy.dylib) [com.apple.BootPolicy:Library] BootPolicy: bootpolicy_get_oic: exit: SEP storage (3)
Thus a proper attestation cannot be performed, and so the UCRT request fails.
So how does the rest of the OS function when there’s no SEP? Well Apple developed AppleVPBootPolicy.kext
, AppleVPCredentialManager.kext
and AppleVPKeyStore.kext
to handle the missing SEP and trick most of the OS into functioning correctly. Though as we can see, it’s not perfect and fails to handle our Attestation request.
If we reverse /usr/lib/libbootpolicy.dylib
and examine bootpolicy_get_oic
, we’ll see an invocation to the SEP:
int _bootpolicy_get_oic(int arg0, int arg1) {
...
result = __sep_command(0x26, ..., ..., ..., 0x1024);
}
From this, __sep_command
invokes __sep_send
, which implements an IOConnectCall for communication with com.apple.security.BootPolicy
in IOService
- Normally
com.apple.security.BootPolicy
would beBootPolicy.kext
on bare metal, however in VMsAppleVPBootPolicy.kext
will be taking this role.
Inside of AppleVPBootPolicy.kext
, the array _command_functions[]
’s entries corresponds to different functions.
SEP command 38 = _command_get_oic()
However the contents of this function doesn’t provide anything of use, instead always returning error code 3:
signed __int64 _command_get_oic()
{
__asm { HINT #0x22 }
return 3LL;
}
HINT #0x22
translates to0010 001
, which represents Profiling Synchronization Barrier (PSB
).- Thanks Flagers for the tip
It seems that OIC handling was never implemented, most likely #ifdef
‘d out of public versions.
iCloud, OS Betas and Kernel Collections
Another thing you may have noticed is that Apple Silicon Virtual Machines cannot sign into iCloud. Well the reason for this is also attestation related, since AuthKit.framework cannot setup a secure chain of trust. And guess what macOS 13.4 added? A new requirement for developer accounts to access macOS Betas.
And for those needing to boot development kernels or test kernel extensions are also out of luck, as it seems bputil
/kmutil
cannot communicate with the SEP to configure boot for custom Kernel Collections including Auxiliary KCs meant for kexts in /Library/Extensions
.
- Steven Michaud has a thread in UTM’s repo on their research into custom Kernel Collections:
Conclusion
While unfortunately this research journey didn’t result in any real successes for testing DEP workflows, it was still really interesting seeing how the enrolment setup functions as well as work with the private APIs in Virtualization.framework. Though it is quite frustrating seeing many development tools being unavailable in Virtual Machines, especially proper OS betas and custom kernel collections.
- Some partial work-arounds available however:
Perhaps with macOS 15, we’ll finally get VirtualMac3,1
and proper SEP virtualization. Though this is just wishful thinking ;p