OSX-KVM/reversing-notes.md

584 lines
16 KiB
Markdown

#### Problem
On a macOS virtual machine,
```
$ sudo /usr/bin/AssetCacheManagerUtil activate
AssetCacheManagerUtil[] Failed to activate content caching: Error Domain=ACSMErrorDomain Code=5 "virtual machine"...
```
It seems that the `Content Caching` functionality is not available when macOS
is running in a virtual machine. How can we enable this feature on our macOS
VM?
#### April 2020 Update
I was able to patch the Catalina 10.15.4 kernel to disable the VMM detection.
Original function:
![Original function](screenshots/macOS-kernel-patching-1.png)
Patched function:
![Patched function](screenshots/macOS-kernel-patching-2.png)
```
static int
cpu_features SYSCTL_HANDLER_ARGS
{
__unused struct sysctl_oid *unused_oidp = oidp;
__unused void *unused_arg1 = arg1;
__unused int unused_arg2 = arg2;
char buf[512];
buf[0] = '\0';
// cpuid_get_feature_names(cpuid_features(), buf, sizeof(buf));
cpuid_get_feature_names(cpuid_features(), buf, sizeof(buf)); // NOP this <-- NOTE!
return SYSCTL_OUT(req, buf, strlen(buf) + 1);
}
...
"bsd/dev/i386/sysctl.c" 980 lines --13%--
```
See `bsd/dev/i386/sysctl.c 138: cpuid_get_feature_names(cpuid_features` too.
Useful commands:
```
sudo mount -uw /
sudo mv /System/Library/Kernels/kernel /System/Library/Kernels/kernel.bak
sudo kextcache -i /
```
Update: Use `resources/kernel_autopatcher.py` to patch your kernels! :-)
#### March 2020 Update
Update: This approach causes the macOS VM to consume multiple CPU(s) 100% on
the host!
See `osfmk/i386/tsc.c 142: if (cpuid_vmm_present()) {` for details.
Instead of trying to hack things from within the VM, we can turn off VMM
detection from the outside.
See [boot-macOS-Catalina.sh](./boot-macOS-Catalina.sh) to see how it is done.
Essentially, we add `hypervisor=off,vmx=on,kvm=off` flags to the QEMU's CPU
configuration.
Once this is done,
```
$ sysctl -a | grep VMM
<nothing>
$ sudo /usr/bin/AssetCacheManagerUtil activate
2020-03-14 19:05:21.416 AssetCacheManagerUtil[1313:53576] Content caching activated.
2020-03-14 19:05:21.417 AssetCacheManagerUtil[1313:53576] Restart devices to take advantage of content caching immediately
$ sudo /usr/bin/AssetCacheManagerUtil status
2020-03-14 19:10:31.154 AssetCacheManagerUtil[1362:54464] Content caching status: {
Activated = 1;
Active = 1;
CacheDetails = {
};
CacheFree = 119663451136;
CacheLimit = 0;
CacheStatus = OK;
CacheUsed = 0;
Parents = (
);
Peers = (
);
PersonalCacheFree = 119663451136;
PersonalCacheLimit = 0;
PersonalCacheUsed = 0;
Port = 49363;
PrivateAddresses = (
"192.168.100.137"
);
PublicAddress = "11.XX.YY.ZZ";
RegistrationStatus = 1;
RestrictedMedia = 0;
ServerGUID = "XXX";
StartupStatus = OK;
TotalBytesAreSince = "2020-03-15 02:05:06 +0000";
TotalBytesDropped = 0;
TotalBytesImported = 0;
TotalBytesReturnedToChildren = 0;
TotalBytesReturnedToClients = 0;
TotalBytesReturnedToPeers = 0;
TotalBytesStoredFromOrigin = 0;
TotalBytesStoredFromParents = 0;
TotalBytesStoredFromPeers = 0;
}
```
w00t!
I found this technique from [this article](https://superuser.com/questions/1387935/hiding-virtual-machine-status-from-guest-operating-system). Thanks!
This was tested on macOS Mojave 10.14.6 and on macOS Catalina 10.15.3.
#### CPU flags
```
$ sysctl -a | grep VMM
machdep.cpu.features: FPU ... VMM PCID XSAVE OSXSAVE AVX1.0
```
Turning off `kvm=on` flag doesn't help in hiding the `VMM` flag.
https://github.com/hjuutilainen/adminscripts/blob/master/check-if-virtual-machine.py
uses the same trick to detect if macOS is running in a VM.
#### VM detection code in macOS
This code was found in the `AssetCache` binary.
```objective-c
char __cdecl -[ECConfig runningInVM](ECConfig *self, SEL a2)
{
void *v2; // rax
void *v3; // r15
__int64 v4; // r13
size_t v5; // r12
__int64 v6; // r13
int *v7; // rax
char *v8; // rax
bool v9; // bl
__int64 v10; // r12
__int64 v11; // r12
int *v12; // rax
char *v13; // rax
__int64 v14; // rbx
size_t v15; // rcx
void *v16; // rax
void *v17; // r14
char result; // al
__int64 *v19; // [rsp+0h] [rbp-40h]
size_t v20; // [rsp+8h] [rbp-38h]
__int64 v21; // [rsp+10h] [rbp-30h]
v20 = 0LL;
*__error() = 0;
if ( sysctlbyname("machdep.cpu.features", 0LL, &v20, 0LL, 0LL) || v20 - 1 > 0xF423F )
{
v10 = qword_100394620;
if ( (unsigned __int8)os_log_type_enabled(qword_100394620, 16LL) )
{
v11 = objc_retain(v10);
v12 = __error();
v13 = strerror(*v12);
*((_DWORD *)&v19 - 8) = 134218242;
*(__int64 **)((char *)&v19 - 28) = 0LL;
*((_WORD *)&v19 - 10) = 2080;
*(__int64 **)((char *)&v19 - 18) = (__int64 *)v13;
_os_log_error_impl(&_mh_execute_header, v11, 16LL, aSysctlMachdepC, &v19 - 4, 22LL);
objc_release(v11);
v9 = 0;
goto LABEL_21;
}
LABEL_12:
v9 = 0;
goto LABEL_21;
}
v2 = malloc(v20);
v3 = v2;
if ( !v2 )
{
v14 = qword_100394620;
if ( (unsigned __int8)os_log_type_enabled(qword_100394620, 16LL) )
{
v15 = v20;
*((_DWORD *)&v19 - 4) = 134217984;
*(__int64 **)((char *)&v19 - 12) = (__int64 *)v15;
_os_log_error_impl(&_mh_execute_header, v14, 16LL, aOutOfMemoryLd, &v19 - 2, 12LL);
v9 = 0;
goto LABEL_21;
}
goto LABEL_12;
}
if ( sysctlbyname("machdep.cpu.features", v2, &v20, 0LL, 0LL) )
{
v4 = qword_100394620;
if ( (unsigned __int8)os_log_type_enabled(qword_100394620, 16LL) )
{
v19 = (__int64 *)&v19;
v5 = v20;
v6 = objc_retain(v4);
v7 = __error();
v8 = strerror(*v7);
*((_DWORD *)&v19 - 8) = 134218242;
*(__int64 **)((char *)&v19 - 28) = (__int64 *)v5;
*((_WORD *)&v19 - 10) = 2080;
*(__int64 **)((char *)&v19 - 18) = (__int64 *)v8;
_os_log_error_impl(&_mh_execute_header, v6, 16LL, aSysctlMachdepC, &v19 - 4, 22LL);
objc_release(v6);
}
v9 = 0;
}
else
{
v16 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithUTF8String:", v3);
v17 = (void *)objc_retainAutoreleasedReturnValue(v16);
v9 = (unsigned __int8)objc_msgSend(v17, "isEqualToString:", CFSTR("VMM"))
|| (unsigned __int8)objc_msgSend(v17, "hasPrefix:", CFSTR("VMM "))
|| (unsigned __int8)objc_msgSend(v17, "hasSuffix:", CFSTR(" VMM"))
|| (unsigned __int8)objc_msgSend(v17, "containsString:", CFSTR(" VMM "));
objc_release(v17);
}
free(v3);
LABEL_21:
result = __stack_chk_guard;
if ( __stack_chk_guard == v21 )
result = v9;
return result;
}
```
The following code was found in `AssetCacheManagerService` binary,
```objective-c
char __cdecl -[ACMSManager _canActivateWithReason:](ACMSManager *self, SEL a2, id *a3)
{
__int64 v3; // rax
id *v4; // r14
OS_os_log *v5; // rax
__int64 v6; // rbx
char v7; // bl
__int64 v8; // r15
struct objc_object *v9; // rax
void *v10; // r12
void *v11; // rax
OS_os_log *v12; // rax
__int64 v13; // rbx
char result; // al
__int64 v15; // [rsp+0h] [rbp-30h]
v15 = v3;
v4 = a3;
if ( (unsigned __int8)-[ACMSManager runningInVM](self, "runningInVM", v3) )
{
v5 = -[ACMSManager logHandle](self, "logHandle");
v6 = objc_retainAutoreleasedReturnValue(v5);
if ( (unsigned __int8)os_log_type_enabled(v6, 0LL) )
{
*((_WORD *)&v15 - 8) = 0;
_os_log_impl(&_mh_execute_header, v6, 0LL, aRunninginvm_2, &v15 - 2, 2LL);
}
objc_release(v6);
if ( v4 )
{
objc_retainAutorelease(CFSTR("virtual machine"));
*v4 = (id)CFSTR("virtual machine");
}
v7 = 0;
}
else
{
v8 = _kACSMSettingsDenyActivationKey;
v9 = -[ACMSManager _managedPrefSettingForKey:](self, "_managedPrefSettingForKey:", _kACSMSettingsDenyActivationKey);
v10 = (void *)objc_retainAutoreleasedReturnValue(v9);
v11 = objc_msgSend(&OBJC_CLASS___NSNumber, "class");
if ( !(unsigned __int8)objc_msgSend(v10, "isKindOfClass:", v11) )
{
objc_release(v10);
v10 = 0LL;
}
if ( (unsigned __int8)objc_msgSend(v10, "boolValue") == 1 )
{
v12 = -[ACMSManager logHandle](self, "logHandle");
v13 = objc_retainAutoreleasedReturnValue(v12);
if ( (unsigned __int8)os_log_type_enabled(v13, 0LL) )
{
*((_DWORD *)&v15 - 4) = 138412290;
*(__int64 *)((char *)&v15 - 12) = v8;
_os_log_impl(&_mh_execute_header, v13, 0LL, asc_10000E438, &v15 - 2, 12LL);
}
objc_release(v13);
if ( v4 )
{
objc_retainAutorelease(CFSTR("disabled by your system administrator"));
*v4 = (id)CFSTR("disabled by your system administrator");
}
v7 = 0;
}
else
{
v7 = 1;
if ( v4 )
*v4 = 0LL;
}
objc_release(v10);
}
result = __stack_chk_guard;
if ( __stack_chk_guard == v15 )
result = v7;
return result;
}
```
```objective-c
ACMSManager *__cdecl -[ACMSManager init](ACMSManager *self, SEL a2)
{
...
if ( sysctlbyname("machdep.cpu.features", 0LL, &v59, 0LL, 0LL) || v59 - 1 > 0xF423F )
{
v33 = objc_msgSend(v26, "logHandle");
v34 = objc_retainAutoreleasedReturnValue(v33);
if ( (unsigned __int8)os_log_type_enabled(v34, 0LL) )
{
v35 = *__error();
*((_DWORD *)&v45 - 8) = 134218240;
*(__int64 *)((char *)&v45 - 28) = 0LL;
*((_WORD *)&v45 - 10) = 1024;
*(_DWORD *)((char *)&v45 - 18) = v35;
_os_log_impl(&_mh_execute_header, v34, 0LL, aSysctlMachdepC, &v45 - 4, 18LL);
}
v36 = v34;
}
else
{
v27 = malloc(v59);
v28 = v27;
if ( v27 )
{
if ( sysctlbyname("machdep.cpu.features", v27, &v59, 0LL, 0LL) )
{
v29 = objc_msgSend(v26, "logHandle");
v30 = objc_retainAutoreleasedReturnValue(v29);
if ( (unsigned __int8)os_log_type_enabled(v30, 0LL) )
{
v58 = &v45;
v31 = v59;
v32 = *__error();
*((_DWORD *)&v45 - 8) = 134218240;
*(__int64 *)((char *)&v45 - 28) = v31;
*((_WORD *)&v45 - 10) = 1024;
*(_DWORD *)((char *)&v45 - 18) = v32;
_os_log_impl(&_mh_execute_header, v30, 0LL, aSysctlMachdepC, &v45 - 4, 18LL);
}
objc_release(v30);
v2 = v60;
}
else
{
v40 = ((__int64 (__fastcall *)(void *, const char *, void *))objc_msgSend)(
&OBJC_CLASS___NSString,
"stringWithUTF8String:",
v28);
v41 = objc_retainAutoreleasedReturnValue(v40);
v42 = (void *)v41;
v43 = ((__int64 (__fastcall *)(__int64, const char *, const __CFString *))objc_msgSend)(
v41,
"isEqualToString:",
CFSTR("VMM"));
v2 = v60;
if ( v43
|| (unsigned __int8)objc_msgSend(v42, "hasPrefix:", CFSTR("VMM "))
|| (unsigned __int8)objc_msgSend(v42, "hasSuffix:", CFSTR(" VMM"))
|| (unsigned __int8)objc_msgSend(v42, "containsString:", CFSTR(" VMM ")) )
{
objc_msgSend(v26, "setRunningInVM:", 1LL);
}
objc_release(v42);
}
free(v28);
goto LABEL_23;
}
v37 = objc_msgSend(v26, "logHandle");
v38 = objc_retainAutoreleasedReturnValue(v37);
if ( (unsigned __int8)os_log_type_enabled(v38, 0LL) )
{
v39 = v59;
*((_DWORD *)&v45 - 4) = 134217984;
*(__int64 *)((char *)&v45 - 12) = v39;
_os_log_impl(&_mh_execute_header, v38, 0LL, aOutOfMemoryLd, &v45 - 2, 12LL);
}
v36 = v38;
}
```
The `AssetCacheManagerService` binary seems to be our target.
I ran the following queries to spot these binaries,
```
$ find / -name "*AssetCache*" -exec grep -i "virtual machine" {} \; 2>/dev/null
$ find / -exec grep -Hn "ACSMErrorDomain" {} \; 2>/dev/null
```
Running `http://newosxbook.com/tools/XPoCe2.html` indicates that
`/usr/bin/AssetCacheManagerUtil` talks with `AssetCacheManagerService`.
After attaching `lldb` to `AssetCacheManagerService`,
```
$ nm AssetCacheManagerService | grep VM
000000010000bb97 t -[ACMSManager runningInVM]
000000010000bbaa t -[ACMSManager setRunningInVM:]
0000000100012928 s _OBJC_IVAR_$_ACMSManager._runningInVM
(lldb) break set --name '-[ACMSManager setRunningInVM:]'
Breakpoint 1: where = AssetCacheManagerService`-[ACMSManager setRunningInVM:], address = 0x0000000105bf5baa
(lldb) break set --name '-[ACMSManager _canActivateWithReason:]'
Breakpoint 2: where = AssetCacheManagerService`-[ACMSManager _canActivateWithReason:], address = 0x0000000105bf33f4
(lldb) break set --name '-[ACMSManager runningInVM]'
Breakpoint 3: where = AssetCacheManagerService`-[ACMSManager runningInVM], address = 0x0000000105bf5b97
Process 940 stopped
* thread #3, queue = 'com.apple.AssetCacheManagerService.ACMSManager.workQueue', stop reason = breakpoint 2.1
frame #0: 0x0000000105bf33f4 AssetCacheManagerService`-[ACMSManager _canActivateWithReason:]
AssetCacheManagerService`-[ACMSManager _canActivateWithReason:]:
-> 0x105bf33f4 <+0>: pushq %rbp
0x105bf33f5 <+1>: movq %rsp, %rbp
0x105bf33f8 <+4>: pushq %r15
0x105bf33fa <+6>: pushq %r14
Target 0: (AssetCacheManagerService) stopped.
```
#### Fix ideas
* Patch `AssetCacheManagerService` binary?
* Kernel patching - change the way `sysctlbyname` behaves?
* Manipulate function execution using Frida?
#### Patch #1
![Patching runningInVM method](screenshots/ida-patch.png?raw=true "Patching runningInVM()")
After this binary patch is applied, `activation` seems to be working ;)
```
$ sudo /usr/bin/AssetCacheManagerUtil activate
... Failed to activate content caching: Error Domain=ACSMErrorDomain Code=3 "already activated"...
```
However, `sudo /usr/bin/AssetCacheManagerUtil status` fails to work just yet.
```
$ sudo /usr/bin/AssetCacheManagerUtil status
2018-11-10 19:29:24.051 AssetCacheManagerUtil[419:3473] Content caching status: {
Activated = 0;
Active = 0;
CacheDetails = {
};
CacheFree = 2000000000;
CacheLimit = 2000000000;
CacheStatus = OK;
CacheUsed = 0;
Parents = (
);
Peers = (
);
PersonalCacheFree = 2000000000;
PersonalCacheLimit = 2000000000;
PersonalCacheUsed = 0;
Port = 0;
RegistrationError = "NOT_ACTIVATED";
RegistrationResponseCode = 403;
RegistrationStatus = "-1";
RestrictedMedia = 0;
ServerGUID = "XXX";
StartupStatus = FAILED;
TotalBytesAreSince = "2018-11-11 03:27:25 +0000";
TotalBytesDropped = 0;
TotalBytesImported = 0;
TotalBytesReturnedToChildren = 0;
TotalBytesReturnedToClients = 0;
TotalBytesReturnedToPeers = 0;
TotalBytesStoredFromOrigin = 0;
TotalBytesStoredFromParents = 0;
TotalBytesStoredFromPeers = 0;
}
```
It seems more patching of the involved binaries (`/usr/libexec/AssetCache/AssetCache`) is required?
Note: The `AssetCacheManagerService.dif` included in this repository was
derived on a macOS 10.14.1 system.
#### Patch #2
Change the four `VMM` strings to `XXX` in the `/usr/libexec/AssetCache/AssetCache` binary.
```
$ pwd
/usr/libexec/AssetCache
$ sudo codesign --remove-signature AssetCache
$ sudo /usr/bin/AssetCacheManagerUtil status
2018-11-10 23:40:07.459 AssetCacheManagerUtil[973:21653] Content caching status: {
Activated = 1;
Active = 0;
CacheDetails = {
};
CacheFree = 2000000000;
CacheLimit = 2000000000;
CacheStatus = OK;
CacheUsed = 0;
Parents = (
);
Peers = (
);
PersonalCacheFree = 2000000000;
PersonalCacheLimit = 2000000000;
PersonalCacheUsed = 0;
Port = 49181;
RegistrationStarted = "2018-11-11 07:38:48 +0000";
RegistrationStatus = 0;
RestrictedMedia = 0;
ServerGUID = "XXX";
StartupStatus = PENDING;
TotalBytesAreSince = "2018-11-11 07:38:48 +0000";
TotalBytesDropped = 0;
TotalBytesImported = 0;
TotalBytesReturnedToChildren = 0;
TotalBytesReturnedToClients = 0;
TotalBytesReturnedToPeers = 0;
TotalBytesStoredFromOrigin = 0;
TotalBytesStoredFromParents = 0;
TotalBytesStoredFromPeers = 0;
}
```
A bit of progress I think ;)
Note: However, it seems that more reversing and patching work is required.
#### Questions
* I haven't been able to see this `sysctlbyname("machdep.cpu.features"...` call
being hit in `lldb`.
Maybe this call is executed once at program startup?
* Can we use DTrace on macOS to trace execution of this call in a system-wide
fashion?
### References
* https://geosn0w.github.io/Debugging-macOS-Kernel-For-Fun/
* https://www.hex-rays.com/wp-content/uploads/2019/12/xnu_debugger_primer.pdf