Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions FeatureUnlock/kern_start.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <Headers/plugin_start.hpp>
#include <Headers/kern_api.hpp>
#include <Headers/kern_user.hpp>
#include "kern_uc_model.hpp"
#include <Headers/kern_devinfo.hpp>
#include <sys/sysctl.h>
#include "kern_dyld_patch.hpp"
Expand All @@ -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;
Expand Down Expand Up @@ -724,13 +727,23 @@ 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 ?
KernelPatcher::RouteRequest("_cs_validate_page", patched_cs_validate_page, orig_cs_validate) :
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");
if (UCModelSpoof::isBlacklistedModel()) {
ucModelSpoof.processKernelPatches(patcher);
}
});
}

Expand Down
166 changes: 166 additions & 0 deletions FeatureUnlock/kern_uc_model.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//
// 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 <sys/sysctl.h>
#include <sys/proc.h>

// 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"
};

void UCModelSpoof::init() {
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The init() function is currently empty and only logs a debug message. If initialization is not needed, consider removing this function entirely and calling processKernelPatches directly from pluginStart. Alternatively, if future initialization logic is planned, add a TODO comment explaining what will be added. Empty functions can be confusing for maintainers.

Suggested change
void UCModelSpoof::init() {
void UCModelSpoof::init() {
// TODO: Add UCModelSpoof-specific initialization logic here if needed,
// such as configuration checks or precomputation before patching.

Copilot uses AI. Check for mistakes.
DBGLOG("featureunlock", "UC: Initializing Universal Control model spoofing");
// TODO: Add UCModelSpoof-specific initialization here if needed in future.
}

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 < arrsize(UC_BLACKLISTED_MODELS); 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<mach_vm_address_t>(hookedSysctlbyname),
reinterpret_cast<mach_vm_address_t *>(&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 < arrsize(uc_processes); i++) {
if (strcmp(procname, uc_processes[i]) == 0) {
isUCProcess = true;
break;
}
}

if (!isUCProcess) {
return result;
}

// Check if the returned model is blacklisted
char *model = static_cast<char *>(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 < arrsize(UC_BLACKLISTED_MODELS); i++) {
if (strcmp(model, UC_BLACKLISTED_MODELS[i]) == 0) {
Comment on lines +132 to +142
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing null termination check before using strcmp on the model buffer. While oldlenp is checked, there's no guarantee that the buffer pointed to by oldp is null-terminated. The original sysctlbyname result should be null-terminated, but explicitly verifying this would prevent potential buffer overruns, especially since this is a security-sensitive kernel hook.

Copilot uses AI. Check for mistakes.
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;
}
50 changes: 50 additions & 0 deletions FeatureUnlock/kern_uc_model.hpp
Original file line number Diff line number Diff line change
@@ -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 <Headers/kern_api.hpp>

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 */