From 52fe74fbea8c68b642157e8ca2e29de067227335 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Thu, 20 May 2021 14:16:05 -0700 Subject: [PATCH] =?UTF-8?q?[AltServer]=20Adds=20ALTDebugConnection=20to=20?= =?UTF-8?q?=E2=80=9Cdebug=E2=80=9D=20sideloaded=20apps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allows AltServer to programmatically enable JIT execution in sideloaded apps. --- .../Connections/ALTDebugConnection+Private.h | 28 ++ AltServer/Connections/ALTDebugConnection.h | 24 ++ AltServer/Connections/ALTDebugConnection.mm | 249 ++++++++++++++++++ AltServer/Devices/ALTDeviceManager.h | 2 + AltServer/Devices/ALTDeviceManager.mm | 15 ++ AltStore.xcodeproj/project.pbxproj | 8 + Shared/Categories/NSError+ALTServerError.h | 2 + Shared/Categories/NSError+ALTServerError.m | 13 + 8 files changed, 341 insertions(+) create mode 100644 AltServer/Connections/ALTDebugConnection+Private.h create mode 100644 AltServer/Connections/ALTDebugConnection.h create mode 100644 AltServer/Connections/ALTDebugConnection.mm diff --git a/AltServer/Connections/ALTDebugConnection+Private.h b/AltServer/Connections/ALTDebugConnection+Private.h new file mode 100644 index 00000000..74d9b442 --- /dev/null +++ b/AltServer/Connections/ALTDebugConnection+Private.h @@ -0,0 +1,28 @@ +// +// ALTDebugConnection+Private.h +// AltServer +// +// Created by Riley Testut on 2/19/21. +// Copyright © 2021 Riley Testut. All rights reserved. +// + +#import "ALTDebugConnection.h" + +#include +#include + +NS_ASSUME_NONNULL_BEGIN + +@interface ALTDebugConnection () + +@property (nonatomic, readonly) dispatch_queue_t connectionQueue; + +@property (nonatomic, nullable) debugserver_client_t client; + +- (instancetype)initWithDevice:(ALTDevice *)device; + +- (void)connectWithCompletionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AltServer/Connections/ALTDebugConnection.h b/AltServer/Connections/ALTDebugConnection.h new file mode 100644 index 00000000..cc24c652 --- /dev/null +++ b/AltServer/Connections/ALTDebugConnection.h @@ -0,0 +1,24 @@ +// +// ALTDebugConnection.h +// AltServer +// +// Created by Riley Testut on 2/19/21. +// Copyright © 2021 Riley Testut. All rights reserved. +// + +#import "AltSign.h" + +NS_ASSUME_NONNULL_BEGIN + +NS_SWIFT_NAME(DebugConnection) +@interface ALTDebugConnection : NSObject + +@property (nonatomic, copy, readonly) ALTDevice *device; + +- (void)enableUnsignedCodeExecutionForProcessWithName:(NSString *)processName completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; + +- (void)disconnect; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AltServer/Connections/ALTDebugConnection.mm b/AltServer/Connections/ALTDebugConnection.mm new file mode 100644 index 00000000..46d3ffcf --- /dev/null +++ b/AltServer/Connections/ALTDebugConnection.mm @@ -0,0 +1,249 @@ +// +// ALTDebugConnection.m +// AltServer +// +// Created by Riley Testut on 2/19/21. +// Copyright © 2021 Riley Testut. All rights reserved. +// + +#import "ALTDebugConnection+Private.h" + +#import "NSError+ALTServerError.h" +#import "NSError+libimobiledevice.h" + +char *bin2hex(const unsigned char *bin, size_t length) +{ + if (bin == NULL || length == 0) + { + return NULL; + } + + char *hex = (char *)malloc(length * 2 + 1); + for (size_t i = 0; i < length; i++) + { + hex[i * 2] = "0123456789ABCDEF"[bin[i] >> 4]; + hex[i * 2 + 1] = "0123456789ABCDEF"[bin[i] & 0x0F]; + } + hex[length * 2] = '\0'; + + return hex; +} + +@implementation ALTDebugConnection + +- (instancetype)initWithDevice:(ALTDevice *)device +{ + self = [super init]; + if (self) + { + _device = device; + _connectionQueue = dispatch_queue_create_with_target("io.altstore.AltServer.DebugConnection", + DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL, + dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)); + } + + return self; +} + +- (void)dealloc +{ + [self disconnect]; +} + +- (void)disconnect +{ + if (_client == nil) + { + return; + } + + debugserver_client_free(_client); + _client = nil; +} + +- (void)connectWithCompletionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler +{ + __block idevice_t device = NULL; + + void (^finish)(BOOL, NSError *) = ^(BOOL success, NSError *error) { + if (device) + { + idevice_free(device); + } + + completionHandler(success, error); + }; + + dispatch_async(self.connectionQueue, ^{ + /* Find Device */ + if (idevice_new_with_options(&device, self.device.identifier.UTF8String, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)) != IDEVICE_E_SUCCESS) + { + return finish(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); + } + + /* Connect to debugserver */ + debugserver_client_t client = NULL; + debugserver_error_t error = debugserver_client_start_service(device, &client, "AltServer"); + if (error != DEBUGSERVER_E_SUCCESS) + { + return finish(NO, [NSError errorWithDebugServerError:error device:self.device]); + } + + self.client = client; + + finish(YES, nil); + }); +} + +- (void)enableUnsignedCodeExecutionForProcessWithName:(NSString *)processName completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler +{ + dispatch_async(self.connectionQueue, ^{ + NSString *localizedFailure = [NSString stringWithFormat:NSLocalizedString(@"JIT could not be enabled for %@.", comment: @""), processName]; + + NSString *encodedName = @(bin2hex((const unsigned char *)processName.UTF8String, (size_t)strlen(processName.UTF8String))); + NSString *attachCommand = [NSString stringWithFormat:@"vAttachName;%@", encodedName]; + + NSError *error = nil; + if (![self sendCommand:attachCommand arguments:nil error:&error]) + { + NSMutableDictionary *userInfo = [error.userInfo mutableCopy]; + userInfo[ALTAppNameErrorKey] = processName; + userInfo[ALTDeviceNameErrorKey] = self.device.name; + userInfo[NSLocalizedFailureErrorKey] = localizedFailure; + + NSError *returnError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; + return completionHandler(NO, returnError); + } + + NSString *detachCommand = @"D"; + if (![self sendCommand:detachCommand arguments:nil error:&error]) + { + NSMutableDictionary *userInfo = [error.userInfo mutableCopy]; + userInfo[ALTAppNameErrorKey] = processName; + userInfo[ALTDeviceNameErrorKey] = self.device.name; + userInfo[NSLocalizedFailureErrorKey] = localizedFailure; + + NSError *returnError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; + return completionHandler(NO, returnError); + } + + completionHandler(YES, nil); + }); +} + +#pragma mark - Private - + +- (BOOL)sendCommand:(NSString *)command arguments:(nullable NSArray *)arguments error:(NSError **)error +{ + int argc = (int)arguments.count; + char **argv = new char*[argc + 1]; + + for (int i = 0; i < argc; i++) + { + NSString *argument = arguments[i]; + argv[i] = (char *)argument.UTF8String; + } + + argv[argc] = NULL; + + debugserver_command_t debugCommand = NULL; + debugserver_command_new(command.UTF8String, argc, argv, &debugCommand); + + delete[] argv; + + char *response = NULL; + size_t responseSize = 0; + debugserver_error_t debugServerError = debugserver_client_send_command(self.client, debugCommand, &response, &responseSize); + debugserver_command_free(debugCommand); + + if (debugServerError != DEBUGSERVER_E_SUCCESS) + { + if (error) + { + *error = [NSError errorWithDebugServerError:debugServerError device:self.device]; + } + + return NO; + } + + if (![self processResponse:@(response) error:error]) + { + return NO; + } + + return YES; +} + +- (BOOL)processResponse:(NSString *)rawResponse error:(NSError **)error +{ + if (rawResponse.length == 0 || [rawResponse isEqualToString:@"OK"]) + { + return YES; + } + + char type = [rawResponse characterAtIndex:0]; + NSString *response = [rawResponse substringFromIndex:1]; + + switch (type) + { + case 'O': + { + // stdout/stderr + + char *decodedResponse = NULL; + debugserver_decode_string(response.UTF8String, response.length, &decodedResponse); + + NSLog(@"Response: %@", @(decodedResponse)); + + if (decodedResponse) + { + free(decodedResponse); + } + + return YES; + } + + case 'T': + { + // Thread Information + + NSLog(@"Thread stopped. Details:\n%s", response.UTF8String + 1); + return YES; + } + + case 'E': + { + // Error + + if (error) + { + NSInteger errorCode = [[[response componentsSeparatedByString:@";"] firstObject] integerValue]; + + switch (errorCode) + { + case 96: + *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil]; + break; + + default: + *error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: response}]; + break; + } + } + + return NO; + } + + case 'W': + { + // Warning + + NSLog(@"WARNING: %@", response); + return YES; + } + } + + return YES; +} + +@end diff --git a/AltServer/Devices/ALTDeviceManager.h b/AltServer/Devices/ALTDeviceManager.h index aad9998c..df1dfb9b 100644 --- a/AltServer/Devices/ALTDeviceManager.h +++ b/AltServer/Devices/ALTDeviceManager.h @@ -11,6 +11,7 @@ @class ALTWiredConnection; @class ALTNotificationConnection; +@class ALTDebugConnection; NS_ASSUME_NONNULL_BEGIN @@ -43,6 +44,7 @@ extern NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification /* Connections */ - (void)startWiredConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTWiredConnection *_Nullable connection, NSError *_Nullable error))completionHandler; - (void)startNotificationConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTNotificationConnection *_Nullable connection, NSError *_Nullable error))completionHandler; +- (void)startDebugConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTDebugConnection *_Nullable connection, NSError * _Nullable error))completionHandler; @end diff --git a/AltServer/Devices/ALTDeviceManager.mm b/AltServer/Devices/ALTDeviceManager.mm index f0c74346..630fd760 100644 --- a/AltServer/Devices/ALTDeviceManager.mm +++ b/AltServer/Devices/ALTDeviceManager.mm @@ -10,6 +10,7 @@ #import "ALTWiredConnection+Private.h" #import "ALTNotificationConnection+Private.h" +#import "ALTDebugConnection+Private.h" #import "ALTConstants.h" #import "NSError+ALTServerError.h" @@ -1277,6 +1278,20 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT completionHandler(notificationConnection, nil); } +- (void)startDebugConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTDebugConnection * _Nullable, NSError * _Nullable))completionHandler +{ + ALTDebugConnection *connection = [[ALTDebugConnection alloc] initWithDevice:device]; + [connection connectWithCompletionHandler:^(BOOL success, NSError * _Nullable error) { + if (success) + { + completionHandler(connection, nil); + } + else + { + completionHandler(nil, error); + } + }]; +} #pragma mark - Getters - - (NSArray *)connectedDevices diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 0f224c4a..23838b95 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -212,6 +212,7 @@ BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4C22DD16DE008935CF /* PillButton.swift */; }; BFA8172923C56042001B5953 /* ServerConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA8172823C56042001B5953 /* ServerConnection.swift */; }; BFA8172B23C5633D001B5953 /* FetchAnisetteDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA8172A23C5633D001B5953 /* FetchAnisetteDataOperation.swift */; }; + BFAD678E25E0649500D4C4D1 /* ALTDebugConnection.mm in Sources */ = {isa = PBXBuildFile; fileRef = BFAD678D25E0649500D4C4D1 /* ALTDebugConnection.mm */; }; BFAD67A325E0854500D4C4D1 /* DeveloperDiskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAD67A225E0854500D4C4D1 /* DeveloperDiskManager.swift */; }; BFAECC522501B0A400528F27 /* CodableServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableServerError.swift */; }; BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; }; @@ -679,6 +680,9 @@ BF9ABA4C22DD16DE008935CF /* PillButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButton.swift; sourceTree = ""; }; BFA8172823C56042001B5953 /* ServerConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConnection.swift; sourceTree = ""; }; BFA8172A23C5633D001B5953 /* FetchAnisetteDataOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAnisetteDataOperation.swift; sourceTree = ""; }; + BFAD678C25E0649500D4C4D1 /* ALTDebugConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTDebugConnection.h; sourceTree = ""; }; + BFAD678D25E0649500D4C4D1 /* ALTDebugConnection.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ALTDebugConnection.mm; sourceTree = ""; }; + BFAD679525E064D400D4C4D1 /* ALTDebugConnection+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ALTDebugConnection+Private.h"; sourceTree = ""; }; BFAD67A225E0854500D4C4D1 /* DeveloperDiskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperDiskManager.swift; sourceTree = ""; }; BFB1169C22932DB100BB457C /* apps.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = apps.json; sourceTree = ""; }; BFB364592325985F00CD0EB1 /* FindServerOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindServerOperation.swift; sourceTree = ""; }; @@ -1566,6 +1570,9 @@ BF718BD323C928A300A89F2D /* ALTNotificationConnection.h */, BF718BD623C92B3700A89F2D /* ALTNotificationConnection+Private.h */, BF718BD423C928A300A89F2D /* ALTNotificationConnection.mm */, + BFAD678C25E0649500D4C4D1 /* ALTDebugConnection.h */, + BFAD679525E064D400D4C4D1 /* ALTDebugConnection+Private.h */, + BFAD678D25E0649500D4C4D1 /* ALTDebugConnection.mm */, ); path = Connections; sourceTree = ""; @@ -2288,6 +2295,7 @@ BF718BD523C928A300A89F2D /* ALTNotificationConnection.mm in Sources */, BF1E312B229F474900370A3C /* RequestHandler.swift in Sources */, BF718BD123C91BD300A89F2D /* ALTWiredConnection.mm in Sources */, + BFAD678E25E0649500D4C4D1 /* ALTDebugConnection.mm in Sources */, BFECAC8524FD950B0077C41F /* Connection.swift in Sources */, BF458690229872EA00BD7491 /* AppDelegate.swift in Sources */, BFECAC8424FD950B0077C41F /* ALTConstants.m in Sources */, diff --git a/Shared/Categories/NSError+ALTServerError.h b/Shared/Categories/NSError+ALTServerError.h index 902c680e..615e9382 100644 --- a/Shared/Categories/NSError+ALTServerError.h +++ b/Shared/Categories/NSError+ALTServerError.h @@ -46,6 +46,8 @@ typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError) ALTServerErrorProfileNotFound = 15, ALTServerErrorAppDeletionFailed = 16, + + ALTServerErrorRequestedAppNotRunning = 100, }; typedef NS_ERROR_ENUM(AltServerConnectionErrorDomain, ALTServerConnectionError) diff --git a/Shared/Categories/NSError+ALTServerError.m b/Shared/Categories/NSError+ALTServerError.m index 4d7e0a24..13ed5115 100644 --- a/Shared/Categories/NSError+ALTServerError.m +++ b/Shared/Categories/NSError+ALTServerError.m @@ -124,6 +124,13 @@ NSErrorUserInfoKey const ALTDeviceNameErrorKey = @"deviceName"; case ALTServerErrorAppDeletionFailed: return NSLocalizedString(@"An error occured while removing the app.", @""); + + case ALTServerErrorRequestedAppNotRunning: + { + NSString *appName = self.userInfo[ALTAppNameErrorKey] ?: NSLocalizedString(@"The requested app", @""); + NSString *deviceName = self.userInfo[ALTDeviceNameErrorKey] ?: NSLocalizedString(@"the device", @""); + return [NSString stringWithFormat:NSLocalizedString(@"%@ is not currently running on %@.", ""), appName, deviceName]; + } } } @@ -141,6 +148,12 @@ NSErrorUserInfoKey const ALTDeviceNameErrorKey = @"deviceName"; case ALTServerErrorMaximumFreeAppLimitReached: return NSLocalizedString(@"Make sure “Offload Unused Apps” is disabled in Settings > iTunes & App Stores, then install or delete all offloaded apps.", @""); + case ALTServerErrorRequestedAppNotRunning: + { + NSString *deviceName = self.userInfo[ALTDeviceNameErrorKey] ?: NSLocalizedString(@"your device", @""); + return [NSString stringWithFormat:NSLocalizedString(@"Make sure the app is running in the foreground on %@ then try again.", @""), deviceName]; + } + default: return nil; }