From d710dd22df53ec55117a7b16078eafc5fb2fbeca Mon Sep 17 00:00:00 2001 From: Aarush Date: Fri, 13 Feb 2026 17:30:23 -0500 Subject: [PATCH 1/2] Add syscall hook to fix Universal Control on blacklisted Macs Implements kernel-level sysctlbyname hook to spoof model identifier for Universal Control processes (UniversalControl, sharingd, rapportd). Fixes: https://github.com/acidanthera/FeatureUnlock/issues/XX Tested on: MacBookPro12,1 running macOS Sequoia --- FeatureUnlock/kern_start.cpp | 11 +++ FeatureUnlock/kern_uc_model.cpp | 161 ++++++++++++++++++++++++++++++++ FeatureUnlock/kern_uc_model.hpp | 50 ++++++++++ 3 files changed, 222 insertions(+) create mode 100644 FeatureUnlock/kern_uc_model.cpp create mode 100644 FeatureUnlock/kern_uc_model.hpp diff --git a/FeatureUnlock/kern_start.cpp b/FeatureUnlock/kern_start.cpp index 32d0008..3388842 100644 --- a/FeatureUnlock/kern_start.cpp +++ b/FeatureUnlock/kern_start.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "kern_uc_model.hpp" #include #include #include "kern_dyld_patch.hpp" @@ -18,6 +19,8 @@ // Original function pointers static mach_vm_address_t orig_cs_validate {}; +static UCModelSpoof ucModelSpoof; + // Boot-arg configurations bool allow_sidecar_ipad; @@ -724,6 +727,13 @@ static void pluginStart() { detectMachineProperties(); detectSupportedPatchSets(); detectNumberOfPatches(); + + // Initialize Universal Control model spoofing for blacklisted models + if (UCModelSpoof::isBlacklistedModel()) { + DBGLOG(MODULE_SHORT, "Initializing UC model spoofing for blacklisted Mac"); + ucModelSpoof.init(); + } + lilu.onPatcherLoadForce([](void *user, KernelPatcher &patcher) { KernelPatcher::RouteRequest csRoute = getKernelVersion() >= KernelVersion::BigSur ? @@ -731,6 +741,7 @@ static void pluginStart() { KernelPatcher::RouteRequest("_cs_validate_range", patched_cs_validate_range, orig_cs_validate); if (!patcher.routeMultipleLong(KernelPatcher::KernelID, &csRoute, 1)) SYSLOG(MODULE_SHORT, "failed to route cs validation pages"); + ucModelSpoof.processKernelPatches(patcher); }); } diff --git a/FeatureUnlock/kern_uc_model.cpp b/FeatureUnlock/kern_uc_model.cpp new file mode 100644 index 0000000..e91383e --- /dev/null +++ b/FeatureUnlock/kern_uc_model.cpp @@ -0,0 +1,161 @@ +// +// kern_uc_model.cpp +// FeatureUnlock.kext +// +// Universal Control Model Spoofing +// Intercepts sysctlbyname for UC processes to spoof blacklisted models +// + +#include "kern_uc_model.hpp" +#include +#include + +// Static member initialization +int (*UCModelSpoof::orgSysctlbyname)(const char *, void *, size_t *, void *, size_t) = nullptr; + +// Shared blacklist of models that don't support Universal Control +static const char *UC_BLACKLISTED_MODELS[] = { + "MacBookPro12,1", + "MacBookPro11,4", + "MacBookPro11,5", + "MacBookAir7,1", + "MacBookAir7,2", + "iMac16,1", + "iMac16,2", + "Macmini7,1", + "MacPro6,1" +}; + +static const size_t UC_BLACKLISTED_COUNT = sizeof(UC_BLACKLISTED_MODELS) / sizeof(UC_BLACKLISTED_MODELS[0]); + +void UCModelSpoof::init() { + DBGLOG("featureunlock", "UC: Initializing Universal Control model spoofing"); +} + +bool UCModelSpoof::isBlacklistedModel() { + char model[64] = {}; + size_t len = sizeof(model); + + if (sysctlbyname("hw.model", model, &len, nullptr, 0) != 0) { + return false; + } + + for (size_t i = 0; i < UC_BLACKLISTED_COUNT; i++) { + if (strcmp(model, UC_BLACKLISTED_MODELS[i]) == 0) { + DBGLOG("featureunlock", "UC: Detected blacklisted model: %s", model); + return true; + } + } + + return false; +} + +void UCModelSpoof::processKernelPatches(KernelPatcher &patcher) { + if (!isBlacklistedModel()) { + DBGLOG("featureunlock", "UC: Model not blacklisted, skipping patch"); + return; + } + + if (!hookSysctlByName(patcher)) { + SYSLOG("featureunlock", "UC: Failed to hook sysctlbyname"); + } +} + +bool UCModelSpoof::hookSysctlByName(KernelPatcher &patcher) { + DBGLOG("featureunlock", "UC: Attempting to hook sysctlbyname"); + + // Find sysctlbyname in the kernel + mach_vm_address_t kern_sysctlbyname = patcher.solveSymbol( + KernelPatcher::KernelID, + "_sysctlbyname" + ); + + if (!kern_sysctlbyname) { + SYSLOG("featureunlock", "UC: Failed to find sysctlbyname symbol"); + return false; + } + + // Route the function to our hook + if (patcher.routeFunction( + kern_sysctlbyname, + reinterpret_cast(hookedSysctlbyname), + reinterpret_cast(&orgSysctlbyname) + )) { + DBGLOG("featureunlock", "UC: Successfully hooked sysctlbyname"); + return true; + } + + SYSLOG("featureunlock", "UC: Failed to route sysctlbyname"); + return false; +} + +int UCModelSpoof::hookedSysctlbyname(const char *name, void *oldp, size_t *oldlenp, + void *newp, size_t newlen) { + // Call original function first + int result = orgSysctlbyname(name, oldp, oldlenp, newp, newlen); + + // Only process successful hw.model queries + if (result != 0 || name == nullptr || oldp == nullptr || oldlenp == nullptr || + strcmp(name, "hw.model") != 0) { + return result; + } + + // Get calling process name + proc_t proc = current_proc(); + if (proc == nullptr) { + return result; + } + + char procname[MAXCOMLEN + 1] = {}; + proc_name(proc_pid(proc), procname, sizeof(procname)); + + // Check if caller is a Universal Control-related process + const char *uc_processes[] = { + "UniversalControl", + "sharingd", + "rapportd", + "ControlCenter" + }; + + bool isUCProcess = false; + for (size_t i = 0; i < sizeof(uc_processes) / sizeof(uc_processes[0]); i++) { + if (strstr(procname, uc_processes[i]) != nullptr) { + isUCProcess = true; + break; + } + } + + if (!isUCProcess) { + return result; + } + + // Check if the returned model is blacklisted + char *model = static_cast(oldp); + + bool isBlacklisted = false; + for (size_t i = 0; i < UC_BLACKLISTED_COUNT; i++) { + if (strcmp(model, UC_BLACKLISTED_MODELS[i]) == 0) { + isBlacklisted = true; + break; + } + } + + if (!isBlacklisted) { + return result; + } + + // Spoof to MacBookPro16,4 (2019 16" MBP - more future-proof) + const char *spoofed_model = "MacBookPro16,4"; + size_t spoofed_len = strlen(spoofed_model) + 1; + if (*oldlenp < spoofed_len) { + DBGLOG("featureunlock", "UC: Buffer too small (%zu < %zu)", *oldlenp, spoofed_len); + return result; + } + strlcpy(model, spoofed_model, *oldlenp); + *oldlenp = spoofed_len; + + DBGLOG("featureunlock", "UC: Spoofed model to %s for process %s", + spoofed_model, procname); + + return result; +} diff --git a/FeatureUnlock/kern_uc_model.hpp b/FeatureUnlock/kern_uc_model.hpp new file mode 100644 index 0000000..848b83f --- /dev/null +++ b/FeatureUnlock/kern_uc_model.hpp @@ -0,0 +1,50 @@ +// +// kern_uc_model.hpp +// FeatureUnlock.kext +// +// Universal Control Model Spoofing +// Fixes Universal Control on blacklisted Macs (MacBookPro12,1, etc.) +// + +#ifndef kern_uc_model_hpp +#define kern_uc_model_hpp + +#include + +class UCModelSpoof { +public: + /** + * Initialize Universal Control model spoofing + */ + void init(); + + /** + * Check if current Mac model is blacklisted for UC + */ + static bool isBlacklistedModel(); + + /** + * Process kernel patches for Universal Control + */ + void processKernelPatches(KernelPatcher &patcher); + +private: + /** + * Hook sysctlbyname to spoof model for UC processes + */ + bool hookSysctlByName(KernelPatcher &patcher); + + /** + * Original sysctlbyname function pointer + */ + static int (*orgSysctlbyname)(const char *name, void *oldp, size_t *oldlenp, + void *newp, size_t newlen); + + /** + * Hooked sysctlbyname implementation + */ + static int hookedSysctlbyname(const char *name, void *oldp, size_t *oldlenp, + void *newp, size_t newlen); +}; + +#endif /* kern_uc_model_hpp */ From c5a38a9776aa7b0675cc406b73a13e00360e1e74 Mon Sep 17 00:00:00 2001 From: Cooldode Date: Fri, 13 Feb 2026 18:49:16 -0500 Subject: [PATCH 2/2] Address review feedback for UC syscall hook --- FeatureUnlock/kern_start.cpp | 4 +++- FeatureUnlock/kern_uc_model.cpp | 17 +++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/FeatureUnlock/kern_start.cpp b/FeatureUnlock/kern_start.cpp index 3388842..47cfc94 100644 --- a/FeatureUnlock/kern_start.cpp +++ b/FeatureUnlock/kern_start.cpp @@ -741,7 +741,9 @@ static void pluginStart() { KernelPatcher::RouteRequest("_cs_validate_range", patched_cs_validate_range, orig_cs_validate); if (!patcher.routeMultipleLong(KernelPatcher::KernelID, &csRoute, 1)) SYSLOG(MODULE_SHORT, "failed to route cs validation pages"); - ucModelSpoof.processKernelPatches(patcher); + if (UCModelSpoof::isBlacklistedModel()) { + ucModelSpoof.processKernelPatches(patcher); + } }); } diff --git a/FeatureUnlock/kern_uc_model.cpp b/FeatureUnlock/kern_uc_model.cpp index e91383e..ca68562 100644 --- a/FeatureUnlock/kern_uc_model.cpp +++ b/FeatureUnlock/kern_uc_model.cpp @@ -26,10 +26,9 @@ static const char *UC_BLACKLISTED_MODELS[] = { "MacPro6,1" }; -static const size_t UC_BLACKLISTED_COUNT = sizeof(UC_BLACKLISTED_MODELS) / sizeof(UC_BLACKLISTED_MODELS[0]); - void UCModelSpoof::init() { DBGLOG("featureunlock", "UC: Initializing Universal Control model spoofing"); + // TODO: Add UCModelSpoof-specific initialization here if needed in future. } bool UCModelSpoof::isBlacklistedModel() { @@ -40,7 +39,7 @@ bool UCModelSpoof::isBlacklistedModel() { return false; } - for (size_t i = 0; i < UC_BLACKLISTED_COUNT; i++) { + for (size_t i = 0; i < arrsize(UC_BLACKLISTED_MODELS); i++) { if (strcmp(model, UC_BLACKLISTED_MODELS[i]) == 0) { DBGLOG("featureunlock", "UC: Detected blacklisted model: %s", model); return true; @@ -118,8 +117,8 @@ int UCModelSpoof::hookedSysctlbyname(const char *name, void *oldp, size_t *oldle }; bool isUCProcess = false; - for (size_t i = 0; i < sizeof(uc_processes) / sizeof(uc_processes[0]); i++) { - if (strstr(procname, uc_processes[i]) != nullptr) { + for (size_t i = 0; i < arrsize(uc_processes); i++) { + if (strcmp(procname, uc_processes[i]) == 0) { isUCProcess = true; break; } @@ -132,8 +131,14 @@ int UCModelSpoof::hookedSysctlbyname(const char *name, void *oldp, size_t *oldle // Check if the returned model is blacklisted char *model = static_cast(oldp); + // Ensure null termination + if (*oldlenp == 0 || model[*oldlenp - 1] != '\0') { + DBGLOG("featureunlock", "UC: hw.model buffer not null-terminated, skipping spoof"); + return result; + } + bool isBlacklisted = false; - for (size_t i = 0; i < UC_BLACKLISTED_COUNT; i++) { + for (size_t i = 0; i < arrsize(UC_BLACKLISTED_MODELS); i++) { if (strcmp(model, UC_BLACKLISTED_MODELS[i]) == 0) { isBlacklisted = true; break;