diff --git a/dylib_dobby_hook/EndelHack.m b/dylib_dobby_hook/EndelHack.m new file mode 100644 index 0000000..2a24dbe --- /dev/null +++ b/dylib_dobby_hook/EndelHack.m @@ -0,0 +1,125 @@ +#import +#import + +@interface EndelHack : NSObject +@property (class, nonatomic, readonly) EndelHack *shared; +- (void)applyHack; +@end + +@implementation EndelHack + +static EndelHack *_shared = nil; +static IMP originalDataTask = NULL; + ++ (EndelHack *)shared { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _shared = [EndelHack new]; + }); + return _shared; +} + +#pragma mark - NSURLSession Hook + +static id hookedDataTaskWithRequest(id self, SEL _cmd, NSURLRequest *request, id completionHandler) { + if (!request || !completionHandler) { + if (originalDataTask) { + return ((id (*)(id, SEL, id, id))originalDataTask)(self, _cmd, request, completionHandler); + } + return nil; + } + + NSString *url = [[request URL] absoluteString]; + + NSString *targetEndpoint = @"api-production.endel.io/v4/call"; + if ([url containsString:targetEndpoint]) { + typedef void (^CompletionBlock)(NSData *, NSURLResponse *, NSError *); + CompletionBlock wrappedHandler = ^(NSData *data, NSURLResponse *response, NSError *error) { + + if (!data || error) { + ((CompletionBlock)completionHandler)(data, response, error); + return; + } + + NSError *jsonError = nil; + id jsonObject = [NSJSONSerialization JSONObjectWithData:data + options:NSJSONReadingMutableContainers + error:&jsonError]; + + if (jsonError || ![jsonObject isKindOfClass:[NSDictionary class]]) { + ((CompletionBlock)completionHandler)(data, response, error); + return; + } + + NSMutableDictionary *modified = (NSMutableDictionary *)jsonObject; + + NSString *subscriptionKey = @"subscription"; + if (modified[subscriptionKey] && [modified[subscriptionKey] isKindOfClass:[NSDictionary class]]) { + NSMutableDictionary *sub = [modified[subscriptionKey] mutableCopy]; + + [sub setObject:@"DEFAULT" forKey:@"promo_type"]; + [sub setObject:@"YEAR" forKey:@"period"]; + [sub setObject:@"" forKey:@"promo_tag"]; + [sub setObject:@YES forKey:@"store_trial"]; + [sub setObject:@(0) forKey:@"time_left"]; + [sub setObject:@"12_Months_Freemium_Trial" forKey:@"price_id"]; + [sub setObject:@(9999999999) forKey:@"valid_until"]; + [sub setObject:@"ACTIVE" forKey:@"type"]; + [sub setObject:@(0) forKey:@"price"]; + [sub setObject:@"CALENDAR_BASED" forKey:@"trial_type"]; + [sub setObject:@"NOSTORE" forKey:@"prev_store"]; + [sub setObject:@YES forKey:@"cancel_at_period_end"]; + [sub setObject:@NO forKey:@"multiple_subscriptions"]; + [sub setObject:@"SGD" forKey:@"currency"]; + [sub setObject:@"APP_STORE" forKey:@"store"]; + [sub setObject:@NO forKey:@"trial_canceled"]; + + [modified setObject:sub forKey:subscriptionKey]; + } + + NSData *modifiedData = [NSJSONSerialization dataWithJSONObject:modified + options:0 + error:nil]; + + if (modifiedData) { + ((CompletionBlock)completionHandler)(modifiedData, response, nil); + } else { + ((CompletionBlock)completionHandler)(data, response, error); + } + }; + + if (originalDataTask) { + return ((id (*)(id, SEL, id, id))originalDataTask)(self, _cmd, request, [wrappedHandler copy]); + } + return nil; + } + + if (originalDataTask) { + return ((id (*)(id, SEL, id, id))originalDataTask)(self, _cmd, request, completionHandler); + } + return nil; +} + +#pragma mark - Hook Installation + +- (void)applyHack { + NSString *sessionClassName = @"NSURLSession"; + Class sessionClass = objc_getClass([sessionClassName UTF8String]); + if (sessionClass) { + NSString *selectorName = @"dataTaskWithRequest:completionHandler:"; + Method targetMethod = class_getInstanceMethod(sessionClass, NSSelectorFromString(selectorName)); + if (targetMethod) { + originalDataTask = method_getImplementation(targetMethod); + method_setImplementation(targetMethod, (IMP)hookedDataTaskWithRequest); + } + } +} + +@end + +__attribute__((constructor)) +static void initialize(void) { + @autoreleasepool { + [[EndelHack shared] applyHack]; + } +} diff --git a/dylib_dobby_hook/ios/apps/HACK-HackerNews.m b/dylib_dobby_hook/ios/apps/HACK-HackerNews.m new file mode 100644 index 0000000..6caab93 --- /dev/null +++ b/dylib_dobby_hook/ios/apps/HACK-HackerNews.m @@ -0,0 +1,216 @@ +#import +#import +#import +#import +#import + +#pragma mark - Helper Macros + +#define HOOK_METHOD(cls, sel, replacement, original) \ + do { \ + Method m = class_getInstanceMethod(cls, sel); \ + if (m) { \ + *(original) = (void *)method_getImplementation(m); \ + method_setImplementation(m, (IMP)(replacement)); \ + } \ + } while(0) + +#pragma mark - String Builders + +static NSString *buildBundleID(void) { + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + if (!bundleID) { + NSMutableString *fallback = [NSMutableString string]; + [fallback appendString:@"com.pranapps"]; + [fallback appendString:@"."]; + [fallback appendString:@"ByteHackerNews"]; + return fallback; + } + return bundleID; +} + +static NSString *buildKeychainService(void) { + NSMutableString *service = [NSMutableString string]; + [service appendString:buildBundleID()]; + [service appendString:@"."]; + [service appendString:@"productid_transaction"]; + return service; +} + +static NSString *buildFakeTransaction(void) { + NSMutableString *tx = [NSMutableString string]; + [tx appendString:@"hack_upgrade_pro"]; + [tx appendString:@"_"]; + [tx appendString:@"1000000001"]; + return tx; +} + +#pragma mark - Keychain Injection + +static NSData *encodeTransactionString(NSString *txString) { + NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:NO]; + NSString *archiveKey = @"productid_transaction"; + [archiver encodeObject:txString forKey:archiveKey]; + [archiver finishEncoding]; + return [archiver encodedData]; +} + +static void injectKeychainTransaction(void) { + @autoreleasepool { + NSString *serviceKey = buildKeychainService(); + NSString *txValue = buildFakeTransaction(); + NSData *archivedData = encodeTransactionString(txValue); + + NSMutableDictionary *deleteQuery = [NSMutableDictionary dictionary]; + [deleteQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; + [deleteQuery setObject:serviceKey forKey:(__bridge id)kSecAttrService]; + SecItemDelete((__bridge CFDictionaryRef)deleteQuery); + + NSMutableDictionary *addQuery = [NSMutableDictionary dictionary]; + [addQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; + [addQuery setObject:serviceKey forKey:(__bridge id)kSecAttrService]; + [addQuery setObject:archivedData forKey:(__bridge id)kSecValueData]; + [addQuery setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(__bridge id)kSecAttrAccessible]; + + OSStatus addStatus = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL); + + if (addStatus == errSecDuplicateItem) { + NSMutableDictionary *updateQuery = [NSMutableDictionary dictionary]; + [updateQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; + [updateQuery setObject:serviceKey forKey:(__bridge id)kSecAttrService]; + + NSMutableDictionary *updateAttrs = [NSMutableDictionary dictionary]; + [updateAttrs setObject:archivedData forKey:(__bridge id)kSecValueData]; + + SecItemUpdate((__bridge CFDictionaryRef)updateQuery, (__bridge CFDictionaryRef)updateAttrs); + } + } +} + +#pragma mark - NSUserDefaults Hooks (Backup Layer) + +static id (*orig_objectForKey)(id, SEL, NSString *) = NULL; +static id hk_objectForKey(id self, SEL _cmd, NSString *key) { + NSString *kPremium = @"premium"; + NSString *kPro = @"pro"; + NSString *kPurchased = @"purchased"; + NSString *kTransaction = @"transaction"; + + if ([key rangeOfString:kPremium].location != NSNotFound || + [key rangeOfString:kPro].location != NSNotFound || + [key rangeOfString:kPurchased].location != NSNotFound || + [key rangeOfString:kTransaction].location != NSNotFound) { + return buildFakeTransaction(); + } + + return orig_objectForKey ? orig_objectForKey(self, _cmd, key) : nil; +} + +static BOOL (*orig_boolForKey)(id, SEL, NSString *) = NULL; +static BOOL hk_boolForKey(id self, SEL _cmd, NSString *key) { + NSString *kPremium = @"premium"; + NSString *kPro = @"pro"; + NSString *kPurchased = @"purchased"; + + if ([key rangeOfString:kPremium].location != NSNotFound || + [key rangeOfString:kPro].location != NSNotFound || + [key rangeOfString:kPurchased].location != NSNotFound) { + return YES; + } + + return orig_boolForKey ? orig_boolForKey(self, _cmd, key) : NO; +} + +#pragma mark - AppDelegate Hook (Re-injection) + +static BOOL (*orig_didFinishLaunching)(id, SEL, id, id) = NULL; +static BOOL hk_didFinishLaunching(id self, SEL _cmd, id app, id opts) { + injectKeychainTransaction(); + return orig_didFinishLaunching ? orig_didFinishLaunching(self, _cmd, app, opts) : YES; +} + +#pragma mark - Hook Installation + +static void installHooks(void) { + @autoreleasepool { + Class udClass = [NSUserDefaults class]; + + NSString *selObjForKey = @"objectForKey:"; + SEL objSel = NSSelectorFromString(selObjForKey); + HOOK_METHOD(udClass, objSel, hk_objectForKey, &orig_objectForKey); + + NSString *selBoolForKey = @"boolForKey:"; + SEL boolSel = NSSelectorFromString(selBoolForKey); + HOOK_METHOD(udClass, boolSel, hk_boolForKey, &orig_boolForKey); + + int numClasses = objc_getClassList(NULL, 0); + if (numClasses <= 0) return; + + Class *classList = (Class *)malloc(sizeof(Class) * numClasses); + if (!classList) return; + + objc_getClassList(classList, numClasses); + + Class appDelegateClass = NULL; + NSString *delegateName = @"AppDelegate"; + + for (int i = 0; i < numClasses; i++) { + const char *className = class_getName(classList[i]); + if (!className) continue; + + NSString *clsName = [NSString stringWithUTF8String:className]; + if ([clsName isEqualToString:delegateName]) { + appDelegateClass = classList[i]; + break; + } + + if (class_conformsToProtocol(classList[i], @protocol(UIApplicationDelegate))) { + appDelegateClass = classList[i]; + break; + } + } + + free(classList); + + if (appDelegateClass) { + NSString *selDidFinish = @"application:didFinishLaunchingWithOptions:"; + SEL didFinishSel = NSSelectorFromString(selDidFinish); + HOOK_METHOD(appDelegateClass, didFinishSel, hk_didFinishLaunching, &orig_didFinishLaunching); + } + } +} + +#pragma mark - Loader Class + +@interface HACKPremium : NSObject ++ (instancetype)shared; +- (void)activate; +@end + +@implementation HACKPremium + +static HACKPremium *_shared = nil; + ++ (instancetype)shared { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _shared = [[HACKPremium alloc] init]; + }); + return _shared; +} + +- (void)activate { + injectKeychainTransaction(); + installHooks(); +} + +@end + +#pragma mark - Constructor + +__attribute__((constructor)) +static void initHACKPremium(void) { + @autoreleasepool { + [[HACKPremium shared] activate]; + } +}