fix: Resolve frontmost app detection always returning SpringBoard

vphoned runs as root (uid 0) in the system launchd domain, but
SBSCopyFrontmostApplicationDisplayIdentifier() internally looks up
SpringBoard's mach service which is registered in the mobile user
session (uid 501). The bootstrap lookup silently fails, returning nil,
causing the fallback to always report com.apple.springboard.

Fix by temporarily dropping euid to mobile (seteuid(501)) before
calling SBS functions, then restoring root after. Also add the
low-level SBFrontmostApplicationDisplayIdentifier(port, buf) MIG
variant as a secondary method, and a heuristic fallback that walks
running apps via FBSSystemService.

Add com.apple.springboard.debugapplications entitlement to vphoned.
This commit is contained in:
Lakr
2026-03-11 00:35:41 +08:00
parent 7a5a8edd27
commit bb12565070
2 changed files with 61 additions and 0 deletions
+2
View File
@@ -505,6 +505,8 @@
<string>IOSurfaceRootUserClient</string>
<string>RootDomainUserClient</string>
</array>
<key>com.apple.springboard.debugapplications</key>
<true/>
<key>com.apple.springboard.launchapplications</key>
<true/>
<key>com.apple.springboard.launchapplicationswithoptions</key>
+59
View File
@@ -9,6 +9,7 @@
#import "vphoned_apps.h"
#import "vphoned_protocol.h"
#include <dlfcn.h>
#include <mach/mach.h>
#include <objc/message.h>
#include <signal.h>
#include <unistd.h>
@@ -34,6 +35,10 @@
static Class gFBSSystemServiceClass = Nil;
// SBSCopyFrontmostApplicationDisplayIdentifier loaded via dlsym
static NSString *(*pSBSCopyFrontmost)(void) = NULL;
// Low-level MIG variants
static mach_port_t (*pSBSSpringBoardServerPort)(void) = NULL;
static void (*pSBFrontmostApplicationDisplayIdentifier)(mach_port_t port,
char *result) = NULL;
static BOOL gAppsLoaded = NO;
@@ -61,6 +66,9 @@ BOOL vp_apps_load(void) {
if (!pSBSCopyFrontmost) {
NSLog(@"vphoned: SBSCopyFrontmostApplicationDisplayIdentifier not found");
}
pSBSSpringBoardServerPort = dlsym(sbs, "SBSSpringBoardServerPort");
pSBFrontmostApplicationDisplayIdentifier =
dlsym(sbs, "SBFrontmostApplicationDisplayIdentifier");
} else {
NSLog(@"vphoned: dlopen SpringBoardServices failed: %s", dlerror());
}
@@ -231,10 +239,61 @@ NSDictionary *vp_handle_apps_command(NSDictionary *msg) {
pid_t pid = 0;
NSString *name = @"";
// SBS functions talk to SpringBoard via mach IPC. When running as root,
// the bootstrap port resolves in the system domain, which cannot see
// SpringBoard's per-user mach service. Temporarily drop to mobile (501)
// so the port lookup hits the user-session namespace.
uid_t orig_euid = geteuid();
BOOL switched = NO;
if (orig_euid == 0) {
if (seteuid(501) == 0) {
switched = YES;
}
}
// Method 1: High-level SBSCopyFrontmostApplicationDisplayIdentifier
if (pSBSCopyFrontmost) {
frontApp = pSBSCopyFrontmost();
}
// Method 2: Low-level SBFrontmostApplicationDisplayIdentifier(port, buf)
if ((!frontApp || frontApp.length == 0) && pSBSSpringBoardServerPort &&
pSBFrontmostApplicationDisplayIdentifier) {
mach_port_t port = pSBSSpringBoardServerPort();
if (port != MACH_PORT_NULL) {
char buf[256] = {0};
pSBFrontmostApplicationDisplayIdentifier(port, buf);
if (buf[0] != '\0') {
frontApp = [NSString stringWithUTF8String:buf];
}
}
}
if (switched) {
seteuid(orig_euid);
}
// Method 3: Walk running apps, pick the one with highest PID (most
// recently launched) that isn't SpringBoard — rough heuristic fallback
if ((!frontApp || frontApp.length == 0) && gFBSSystemServiceClass) {
LSApplicationWorkspace *ws = [LSApplicationWorkspace defaultWorkspace];
pid_t bestPid = 0;
NSString *bestApp = nil;
for (LSApplicationProxy *proxy in [ws allInstalledApplications]) {
pid_t p = pid_for_app(proxy.bundleIdentifier);
if (p > 0 &&
![proxy.bundleIdentifier isEqualToString:@"com.apple.springboard"])
{
if (p > bestPid) {
bestPid = p;
bestApp = proxy.bundleIdentifier;
}
}
}
if (bestApp)
frontApp = bestApp;
}
if (!frontApp || frontApp.length == 0) {
frontApp = @"com.apple.springboard";
}