Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions dylib_dobby_hook/EndelHack.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@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];
}
}
216 changes: 216 additions & 0 deletions dylib_dobby_hook/ios/apps/HACK-HackerNews.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#import <dlfcn.h>
#import <Security/Security.h>

#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];
}
}