From 8b7c6d0d5f7c356fa23fed0e88178fdf842746f7 Mon Sep 17 00:00:00 2001 From: artemis Date: Sat, 20 Jul 2024 18:13:53 +0800 Subject: [PATCH] Support ForkLift --- README.md | 42 +- dylib_dobby_hook.xcodeproj/project.pbxproj | 14 +- dylib_dobby_hook/apps/AnyGoHack.m | 192 ----- dylib_dobby_hook/apps/ForkLiftHack.m | 79 ++ dylib_dobby_hook/apps/SurgeHack.m | 799 --------------------- dylib_dobby_hook/apps/iMazingHack.m | 396 ---------- script/auto_hack.sh | 2 +- script/surge_hack.sh | 68 -- tools/mac_patch_helper | Bin 43056 -> 61216 bytes tools/patch.json | 10 + 10 files changed, 117 insertions(+), 1485 deletions(-) delete mode 100644 dylib_dobby_hook/apps/AnyGoHack.m create mode 100644 dylib_dobby_hook/apps/ForkLiftHack.m delete mode 100644 dylib_dobby_hook/apps/SurgeHack.m delete mode 100644 dylib_dobby_hook/apps/iMazingHack.m delete mode 100644 script/surge_hack.sh diff --git a/README.md b/README.md index fcf70f8..2a5ad38 100644 --- a/README.md +++ b/README.md @@ -26,27 +26,35 @@ 2. Xcode|Clion 集成开发调试环境 3. 特征码搜索 -| App | version | x86 | arm | Download | remark | Author | -|-----------------|---------|-----|-----|---------------------------------------------|--------------------------------------------------------------------------------------------------------------|---------------------| -| TablePlus | 6.* | ✔ | ✔ | https://tableplus.com/ | inject_bin="/Applications/TablePlus.app/Contents/Frameworks/Sparkle.framework/Versions/B/Sparkle" | | -| DevUtils | 1.* | ✔ | ✔ | https://devutils.com/ | | | -| AirBuddy | 2.* | ✔ | ✔ | https://v2.airbuddy.app/download | inject_bin="/Applications/AirBuddy.app/Contents/Frameworks/LetsMove.framework/Versions/A/LetsMove" | | -| Navicat Premium | 17.* | ✔ | ✔ | App Store | inject_bin="/Applications/Navicat Premium.app/Contents/Frameworks/EE.framework/Versions/A/EE" | QiuChenlyOpenSource | -| Paste | 4.1.3 | ✘ | ✔ | App Store | | LeeeMooo | -| Transmit | 5.* | ✔ | ✔ | https://panic.com/transmit/#download | | | -| AnyGo | 7.* | ✔ | ✔ | https://itoolab.com/gps-location-changer/ | | | -| Downie | 4.* | ✔ | ✔ | https://software.charliemonroe.net/downie/ | inject_bin="/Applications/Permute 3.app/Contents/Frameworks/Licensing.framework/Versions/A/Licensing" | | -| Permute | 3.* | ✔ | ✔ | https://software.charliemonroe.net/permute/ | inject_bin="/Applications/Downie 4.app/Contents/Frameworks/Licensing.framework/Versions/A/Licensing" | | -| ProxyMan | 5.2 | ✔ | ✔ | https://proxyman.io/ | inject_bin="/Applications/Proxyman.app/Contents/Frameworks/HexFiend.framework/Versions/A/HexFiend" | | -| Movist Pro | 2.* | ✔ | ✔ | https://movistprime.com/ | inject_bin="/Applications/Movist Pro.app/Contents/Frameworks/MediaKeyTap.framework/Versions/A/MediaKeyTap" | | -| Surge | 5.7.* | ✔ | ✔ | https://nssurge.com/ | inject_bin="/Applications/Surge.app/Contents/Frameworks/MMMarkdown.framework/Versions/A/MMMarkdown" | | -| Infuse | 7.7.* | ✔ | ✔ | App Store | inject_bin="/Applications/Infuse.app/Contents/Frameworks/Differentiator.framework/Versions/A/Differentiator" | | -| MacUpdater | 3. | ✔ | ✔ | https://www.corecode.io/macupdater/#download | inject_bin="/Applications/MacUpdater.app/Contents/Frameworks/Sparkle.framework/Versions/B/Sparkle" | | +
+ 点击这里展开/收起 + +| App | version | x86 | arm | Download | remark | Author | +|-----------------|---------|-----|-----|----------------------------------------------|----------------------------------------------------------------------------------------------------------------------|---------------------| +| TablePlus | 6.* | ✔ | ✔ | https://tableplus.com/ | inject_bin="/Applications/TablePlus.app/Contents/Frameworks/Sparkle.framework/Versions/B/Sparkle" | | +| DevUtils | 1.* | ✔ | ✔ | https://devutils.com/ | | | +| AirBuddy | 2.* | ✔ | ✔ | https://v2.airbuddy.app/download | inject_bin="/Applications/AirBuddy.app/Contents/Frameworks/LetsMove.framework/Versions/A/LetsMove" | | +| Navicat Premium | 17.* | ✔ | ✔ | App Store | inject_bin="/Applications/Navicat Premium.app/Contents/Frameworks/EE.framework/Versions/A/EE" | QiuChenlyOpenSource | +| Paste | 4.1.3 | ✘ | ✔ | App Store | | LeeeMooo | +| Transmit | 5.* | ✔ | ✔ | https://panic.com/transmit/#download | | | +| AnyGo | 7.* | ✔ | ✔ | https://itoolab.com/gps-location-changer/ | DMCA | | +| Downie | 4.* | ✔ | ✔ | https://software.charliemonroe.net/downie/ | inject_bin="/Applications/Permute 3.app/Contents/Frameworks/Licensing.framework/Versions/A/Licensing" | | +| Permute | 3.* | ✔ | ✔ | https://software.charliemonroe.net/permute/ | inject_bin="/Applications/Downie 4.app/Contents/Frameworks/Licensing.framework/Versions/A/Licensing" | | +| ProxyMan | 5.2 | ✔ | ✔ | https://proxyman.io/ | inject_bin="/Applications/Proxyman.app/Contents/Frameworks/HexFiend.framework/Versions/A/HexFiend" | | +| Movist Pro | 2.* | ✔ | ✔ | https://movistprime.com/ | inject_bin="/Applications/Movist Pro.app/Contents/Frameworks/MediaKeyTap.framework/Versions/A/MediaKeyTap" | | +| Surge | 5.7.* | ✔ | ✔ | https://nssurge.com/ | DMCA | | +| Infuse | 7.7.* | ✔ | ✔ | App Store | inject_bin="/Applications/Infuse.app/Contents/Frameworks/Differentiator.framework/Versions/A/Differentiator" | | +| MacUpdater | 3. | ✔ | ✔ | https://www.corecode.io/macupdater/#download | inject_bin="/Applications/MacUpdater.app/Contents/Frameworks/Sparkle.framework/Versions/B/Sparkle" | | +| CleanShotX | 4. | ✔ | ✘ | https://updates.getcleanshot.com/v3/ | DMCA | | +| ForkLift | 4. | ✔ | ✔ | https://binarynights.com/ | inject_bin="/Applications/ForkLift.app/Contents/Frameworks/UniversalDetector.framework/Versions/A/UniversalDetector" | | + +
+ ## Usage -[download latest release](https://github.com/marlkiller/dylib_dobby_hook_private/releases/download/latest/dylib_dobby_hook.tar.gz) +[download latest release](https://github.com/marlkiller/dylib_dobby_hook/releases/download/latest/dylib_dobby_hook.tar.gz) ```shell tar -xzvf dylib_dobby_hook.tar.gz cd script diff --git a/dylib_dobby_hook.xcodeproj/project.pbxproj b/dylib_dobby_hook.xcodeproj/project.pbxproj index b41cd82..87888ac 100644 --- a/dylib_dobby_hook.xcodeproj/project.pbxproj +++ b/dylib_dobby_hook.xcodeproj/project.pbxproj @@ -27,11 +27,9 @@ B55407272B653DCB005C08E6 /* NavicatPremiumHack.m in Sources */ = {isa = PBXBuildFile; fileRef = B55407262B653DCB005C08E6 /* NavicatPremiumHack.m */; }; B554D7BC2B63F2A300B7EFEA /* DevUtilsHack.m in Sources */ = {isa = PBXBuildFile; fileRef = B554D7BB2B63F2A300B7EFEA /* DevUtilsHack.m */; }; B580D3CA2BE3579300979568 /* LightRoomHack.m in Sources */ = {isa = PBXBuildFile; fileRef = B580D3C72BE3579300979568 /* LightRoomHack.m */; }; - B580D3CB2BE3579300979568 /* AnyGoHack.m in Sources */ = {isa = PBXBuildFile; fileRef = B580D3C82BE3579300979568 /* AnyGoHack.m */; }; - B580D3CC2BE3579300979568 /* SurgeHack.m in Sources */ = {isa = PBXBuildFile; fileRef = B580D3C92BE3579300979568 /* SurgeHack.m */; }; B58160172BE88569001DDB9B /* encryp_utils.m in Sources */ = {isa = PBXBuildFile; fileRef = B58160112BE88569001DDB9B /* encryp_utils.m */; }; B581601B2BE88569001DDB9B /* encryp_utils.h in Headers */ = {isa = PBXBuildFile; fileRef = B58160162BE88569001DDB9B /* encryp_utils.h */; }; - B5CFE84D2BE9F03200CF8D9E /* iMazingHack.m in Sources */ = {isa = PBXBuildFile; fileRef = B5CFE84C2BE9F03200CF8D9E /* iMazingHack.m */; }; + B5D0C0BB2C4B553500881398 /* ForkLiftHack.m in Sources */ = {isa = PBXBuildFile; fileRef = B5D0C0BA2C4B553500881398 /* ForkLiftHack.m */; }; B5D2ED3F2BC0252D0030CBCA /* DevHack.m in Sources */ = {isa = PBXBuildFile; fileRef = B5D2ED3E2BC0252D0030CBCA /* DevHack.m */; }; B5F06B1D2BEF591E0079E68D /* InfuseHack.m in Sources */ = {isa = PBXBuildFile; fileRef = B5F06B1C2BEF591E0079E68D /* InfuseHack.m */; }; B5FE3B862BA2A571001AE437 /* TransmitHack.m in Sources */ = {isa = PBXBuildFile; fileRef = B5FE3B842BA2A571001AE437 /* TransmitHack.m */; }; @@ -79,11 +77,9 @@ B56968C62BEA4E2A0022FAC6 /* libdylib_dobby_hook.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libdylib_dobby_hook.dylib; sourceTree = ""; }; B56968C72BEA4E4D0022FAC6 /* cmake_debugger.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = cmake_debugger.sh; sourceTree = ""; }; B580D3C72BE3579300979568 /* LightRoomHack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LightRoomHack.m; sourceTree = ""; }; - B580D3C82BE3579300979568 /* AnyGoHack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnyGoHack.m; sourceTree = ""; }; - B580D3C92BE3579300979568 /* SurgeHack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SurgeHack.m; sourceTree = ""; }; B58160112BE88569001DDB9B /* encryp_utils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = encryp_utils.m; sourceTree = ""; }; B58160162BE88569001DDB9B /* encryp_utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = encryp_utils.h; sourceTree = ""; }; - B5CFE84C2BE9F03200CF8D9E /* iMazingHack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iMazingHack.m; sourceTree = ""; }; + B5D0C0BA2C4B553500881398 /* ForkLiftHack.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ForkLiftHack.m; sourceTree = ""; }; B5D2ED3E2BC0252D0030CBCA /* DevHack.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DevHack.m; sourceTree = ""; }; B5F06B1C2BEF591E0079E68D /* InfuseHack.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InfuseHack.m; sourceTree = ""; }; B5FE3B842BA2A571001AE437 /* TransmitHack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TransmitHack.m; sourceTree = ""; }; @@ -184,12 +180,9 @@ isa = PBXGroup; children = ( B5F06B1C2BEF591E0079E68D /* InfuseHack.m */, - B5CFE84C2BE9F03200CF8D9E /* iMazingHack.m */, 38D1AC312B54D1CC00E6CB9E /* AirBuddyHack.m */, 38D1AC2B2B54D03B00E6CB9E /* HackProtocol.h */, - B580D3C82BE3579300979568 /* AnyGoHack.m */, B580D3C72BE3579300979568 /* LightRoomHack.m */, - B580D3C92BE3579300979568 /* SurgeHack.m */, B5FE3B842BA2A571001AE437 /* TransmitHack.m */, 382A25842B6918DF0083F28C /* PasteHack.m */, 38B83E302B5505B300919735 /* TablePlusHack.m */, @@ -327,15 +320,12 @@ 382A25862B6918DF0083F28C /* PasteHack.m in Sources */, B554D7BC2B63F2A300B7EFEA /* DevUtilsHack.m in Sources */, B5F06B1D2BEF591E0079E68D /* InfuseHack.m in Sources */, - B580D3CC2BE3579300979568 /* SurgeHack.m in Sources */, B55407272B653DCB005C08E6 /* NavicatPremiumHack.m in Sources */, 3816BBB92B54FF090051CF39 /* MemoryUtils.m in Sources */, 38B83E312B5505B300919735 /* TablePlusHack.m in Sources */, B580D3CA2BE3579300979568 /* LightRoomHack.m in Sources */, - B580D3CB2BE3579300979568 /* AnyGoHack.m in Sources */, B5FECEAE2BC4FC29008916D6 /* common_ret.m in Sources */, 38D1AC2A2B54CA4D00E6CB9E /* Constant.m in Sources */, - B5CFE84D2BE9F03200CF8D9E /* iMazingHack.m in Sources */, B502BD5D2BCFB35200DAD97F /* MovistProHack.m in Sources */, B5FE3B862BA2A571001AE437 /* TransmitHack.m in Sources */, ); diff --git a/dylib_dobby_hook/apps/AnyGoHack.m b/dylib_dobby_hook/apps/AnyGoHack.m deleted file mode 100644 index 7271edf..0000000 --- a/dylib_dobby_hook/apps/AnyGoHack.m +++ /dev/null @@ -1,192 +0,0 @@ -// -// AnyGoHack.m -// dylib_dobby_hook -// -// Created by 马治武 on 2024/3/17. -// - -#import -#import "Constant.h" -#import "dobby.h" -#import "MemoryUtils.h" -#import -#include -#import "HackProtocol.h" - -@interface AnyGoHack : NSObject - -@end - - -@implementation AnyGoHack - -- (NSString *)getAppName { - return @"com.itoolab.AnyGo"; -} - -- (NSString *)getSupportAppVersion { - // 7.0.0 - return @"7."; -} - -+ (int)hk_isInChina { - NSLog(@">>>>>> Swizzled hk_isInChina method called"); - NSLog(@">>>>>> self.className : %@", self.className); - return 0; -} -- (BOOL)hk_isRegistered { - NSLog(@">>>>>> Swizzled hk_isRegistered method called"); - return YES; -} - - -- (BOOL)hk_isOverDevicesLimit { - NSLog(@">>>>>> Swizzled hk_isOverDevicesLimit method called"); - return NO; -} - - -- (id)hk_device { - NSLog(@">>>>>> Swizzled hk_device method called"); - NSString *className = @"IOSDevice"; - Class class = objc_getClass([className UTF8String]); - return [[class alloc] init]; -} - -- (void)hk_checkRegisterValid:(uint64_t)arg1 { - NSLog(@">>>>>> Swizzled hk_checkRegisterValid method called"); - NSLog(@">>>>>> self.className : %@", self.className); - return ; -} - -- (NSString *) hk_emailText { - NSLog(@">>>>>> Swizzled hk_emailText method called"); - return [Constant G_EMAIL_ADDRESS] ; -} - -- (NSString *) hk_codeText { - NSLog(@">>>>>> Swizzled hk_codeText method called"); - NSUUID *uuid = [NSUUID UUID]; - NSString *uuidString = [uuid UUIDString]; - return uuidString; -} - -// void __cdecl -[RegisterWindow updateUIIsRegister:](RegisterWindow *self, SEL a2, char a3) -//- (int)hk_updateUIIsRegister:(int)arg1 withA3:(int)a3 { -// NSLog(@">>>>>> Swizzled hk_updateUIIsRegister method called"); -// NSLog(@">>>>>> self.className : %@", self.className); -// return [self hk_updateUIIsRegister:arg1 withA3:a3]; -//} - -- (BOOL)hack { -#if defined(__arm64__) || defined(__aarch64__) -#elif defined(__x86_64__) -#endif - - // if ([GlobalFunction isForTest] == 0x0 && [GlobalFunction isInChina] != 0x0) { - [MemoryUtils hookClassMethod: - objc_getClass("GlobalFunction") - originalSelector:NSSelectorFromString(@"isInChina") - swizzledClass:[self class] - swizzledSelector:@selector(hk_isInChina) - ]; -// rax = [RegisterManager shareManager]; -// r14 = [rax isRegistered]; -// [rax release]; -// rax = [r13 objectForKey:@"pathType"]; -// rax = [rax retain]; -// var_40 = [rax intValue]; -// [rax release]; -// var_48 = r13; -// if (r14 != 0x0) { - [MemoryUtils hookInstanceMethod: - objc_getClass("RegisterManager") - originalSelector:NSSelectorFromString(@"isRegistered") - swizzledClass:[self class] - swizzledSelector:@selector(hk_isRegistered) - ]; - - -// r12 = [[RegisterManager shareManager] retain]; -// var_29 = [r12 isOverDevicesLimit:rax]; -// if (var_29 == 0x0) goto loc_10003a8f1; - - [MemoryUtils hookInstanceMethod: - objc_getClass("RegisterManager") - originalSelector:NSSelectorFromString(@"isOverDevicesLimit:") - swizzledClass:[self class] - swizzledSelector:@selector(hk_isOverDevicesLimit) - ]; - - -// -[RegisterWindow updateUIIsRegister:]: -//0000000100059bb9 push rbp -// [MemoryUtils hookInstanceMethod: -// objc_getClass("RegisterWindow") -// originalSelector:NSSelectorFromString(@"updateUIIsRegister:") -// swizzledClass:[self class] -// swizzledSelector:@selector(hk_updateUIIsRegister:withA3:) -// ]; - [MemoryUtils hookInstanceMethod: - objc_getClass("RegisterManager") - originalSelector:NSSelectorFromString(@"email") - swizzledClass:[self class] - swizzledSelector:@selector(hk_emailText) - ]; - [MemoryUtils hookInstanceMethod: - objc_getClass("RegisterManager") - originalSelector:NSSelectorFromString(@"regCode") - swizzledClass:[self class] - swizzledSelector:@selector(hk_codeText) - ]; - -// [MemoryUtils hookInstanceMethod: -// objc_getClass("RegisterWindow") -// originalSelector:NSSelectorFromString(@"checkRegisterValid:result:") -// swizzledClass:[self class] -// swizzledSelector:@selector(hk_checkRegisterValid:) -// ]; -// [MemoryUtils hookInstanceMethod: -// objc_getClass("MainWindowCtr") -// originalSelector:NSSelectorFromString(@"checkRegisterValid:result:") -// swizzledClass:[self class] -// swizzledSelector:@selector(hk_checkRegisterValid:) -// ]; - - -// objc 反射用法 TEST -// // 调用 RegisterManager 类的 shareManager 方法 -// Class registerManagerClass = NSClassFromString(@"RegisterManager"); -// SEL shareManagerSelector = NSSelectorFromString(@"shareManager"); -// id registerManager = [registerManagerClass performSelector:shareManagerSelector]; -// NSLog(@">>>>>> RegisterManager object: %@", registerManager); -// -// // 获取对象的属性与值 -// unsigned int count; -// objc_property_t *properties = class_copyPropertyList(registerManagerClass, &count); -// -// for (unsigned int i = 0; i < count; i++) { -// objc_property_t property = properties[i]; -// NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)]; -// if ([propertyName isEqualToString:@"isRegister"]) { -// // 找到对应的属性,设置其值 -// [registerManager setValue:@(1) forKey:propertyName]; -// // break; -// } -// id propertyValue = [registerManager valueForKey:propertyName]; -// NSLog(@">>>>>> Property: %@, Value: %@", propertyName, propertyValue); -// } -// -// // 获取对象的方法与返回值类型 -// Method *methods = class_copyMethodList(registerManagerClass, &count); -// for (unsigned int i = 0; i < count; i++) { -// Method method = methods[i]; -// const char *methodName = sel_getName(method_getName(method)); -// // method_getReturnType(method, &dst, sizeof(char));// 获取方法返回类型 -// const char *returnType = method_getTypeEncoding(method);// 获取方法参数类型和返回类型 -// NSLog(@">>>>>> Method Name: %s, Return Type: %s", methodName, returnType); -// } - - return YES; -} -@end diff --git a/dylib_dobby_hook/apps/ForkLiftHack.m b/dylib_dobby_hook/apps/ForkLiftHack.m new file mode 100644 index 0000000..81991b2 --- /dev/null +++ b/dylib_dobby_hook/apps/ForkLiftHack.m @@ -0,0 +1,79 @@ +// +// CleanShotXHack.m +// dylib_dobby_hook +// +// Created by voidm on 2024/7/19. +// + +#import +#import "Constant.h" +#import "MemoryUtils.h" +#import +#import "HackProtocol.h" +#include + + +@interface ForkLiftHack : NSObject + + + +@end + + +@implementation ForkLiftHack + + + +- (NSString *)getAppName { + return @"com.binarynights.ForkLift"; +} + +- (NSString *)getSupportAppVersion { + return @"4."; +} + + + +- (BOOL)hack { + + +// +// ; struct ForkLift.RegistrationData { +// ; let name: Swift.String +// ; let quantity: Swift.Int +// ; let license_type: Swift.Int +// ; let validityDate: Foundation.Date +// ; let signature: Swift.String +// ; let licenseKey: Swift.String? +// ; } +//_$s8ForkLift16RegistrationDataVMn: // nominal type descriptor for ForkLift.RegistrationData +//00000001008327c0 struct __swift_StructDescriptor { ; "RegistrationData", DATA XREF=_$s8ForkLift16RegistrationDataVMa+7 +// struct __swift_ContextDescriptor { // context +// 0x10051, // flags +// _$s8ForkLiftMXM-0x1008327c4, // parent context +// aRegistrationda-0x1008327c8, // name of the type +// _$s8ForkLift16RegistrationDataVMa-0x1008327cc, // type accessor function pointer +// _$s8ForkLift16RegistrationDataVMF-0x1008327d0 // fields +// }, +// 0x6, // number of fields +// 0x2 +//} + // 自定义日期字符串 + NSDictionary *registrationDataDict = @{ + @"name": [Constant G_EMAIL_ADDRESS], + @"quantity": @520, + @"license_type": @1, + @"validityDate": @1753025400, // @"2025-07-20 15:30:00", + @"signature": @"SignatureExample", + @"licenseKey": @"ABC123XYZ" + }; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:registrationDataDict options:0 error:nil]; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults setObject:jsonData forKey:@"registrationData"]; + [defaults synchronize]; + + + return YES; +} + +@end diff --git a/dylib_dobby_hook/apps/SurgeHack.m b/dylib_dobby_hook/apps/SurgeHack.m deleted file mode 100644 index 50f50ac..0000000 --- a/dylib_dobby_hook/apps/SurgeHack.m +++ /dev/null @@ -1,799 +0,0 @@ -// -// SurgeHack.m -// dylib_dobby_hook -// -// Created by 马治武 on 2024/4/4. -// - -#import -#import "Constant.h" -#import "dobby.h" -#import "MemoryUtils.h" -#import -#include -#import "HackProtocol.h" -#include -#import -#import "common_ret.h" -#include -#import -#import "encryp_utils.h" - -@interface NSString (MyExtension) -- (BOOL)sEqualToStringEx:(NSString *)parameter; -@end - -@implementation NSString (MyExtension) - -- (BOOL)sEqualToStringEx:(NSString *)parameter { - return false; -} -@end - -@interface SurgeHack : NSObject - - - -@end - - -@implementation SurgeHack - -static IMP urlWithStringSeletorIMP; -static IMP objectForKeyedSubscriptImp; -static IMP objectForKeyedSubscriptImp2; -static IMP KD_JSONObjectIMP; -static IMP initWithBytesNoCopyMethodIMP; -static IMP dataTaskWithRequestIMP; -static IMP dataTaskWithRequestIMP2; -static IMP componentsJoinedByStringIMP; -static IMP shouldPerformMITMForHostIMP; - -static NSString *publicKey = @"-----BEGIN PUBLIC KEY-----\n" -"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvnU72zbRQFSB7IZ4ob2u\n" -"4YgjsI6507rjwhR5DzxZBPtxuAuQOJnCM6XqrFy0hUazNDUybmVb+abbQlbmHs9C\n" -"MGsYLpYKqJLNFCGy+2CRJxCLrTE35pJ36zIVvdj+1qH2KfyfiEjBc6+F6W3E0TwW\n" -"BMd0ezj1pWYZoCytabmhgvhumWXI0ReOIGLuMrEOAf8zKZBWRRVSW3cLSwq0eQ7u\n" -"ubi5UD5rvkmcDuL+RUQySi4W8vOpteq3ceZmtZVpUAXvUjnXzg/EX94VVfPCWhd1\n" -"Ii4P+EBkaV7SuqFgZiczmkcXin5JrkATnIEf5pi71XWadeVZgFSOrkseCQE+Twta\n" -"9QIDAQAB\n" -"-----END PUBLIC KEY-----\n"; - - -static NSDictionary *policySign; -// 这里常量不能用 NSNumber, 需要 基本类型 -static const NSInteger enterpriseLicense = 0; - - -- (NSString *)getAppName { - // >>>>>> 5.7.1 (2758) - return @"com.nssurge.surge-mac"; -} - -- (NSString *)getSupportAppVersion { - return @"5.7."; -} - - - -- (BOOL)hack { - - // 程序使用ptrace来进行动态调试保护,使得执行lldb的时候出现Process xxxx exited with status = 45 (0x0000002d)错误。 - // 使用 DobbyHook 替换 ptrace函数。 - DobbyHook((void *)ptrace, (void *)my_ptrace, (void *)&orig_ptrace); - - NSString *searchFilePath = [[Constant getCurrentAppPath] stringByAppendingString:@"/Contents/MacOS/Surge"]; - uintptr_t fileOffset =[MemoryUtils getCurrentArchFileOffset: searchFilePath]; - - // deviceId = [EncryptionUtils generateSurgeDeviceId]; - // NSLog(@">>>>>> deviceId: %@",deviceId); - - // NSDictionary *jsonLicense = @{ - // @"deviceID": @"16b8f4bdfd55d29f737427b4ec0c14d7", - // @"type":@"licensed", // trial:licensed:revoked - // @"product": @"SURGEMAC5", - // @"expiresOnDate": @1746350567, - // @"p": @"QU1oHx8IbxU1PvQQ3340JA==" - // }; - // 伪造 License , p[1]=1 -// policySign = @{ -// @"policy": @"ewogICJwIiA6ICJtdmlCeW5KRnI3dVZQcGxONVZoTEd3PT0iLAogICJkZXZpY2VJRCIgOiAiMTZiOGY0YmRmZDU1ZDI5ZjczNzQyN2I0ZWMwYzE0ZDciLAogICJwcm9kdWN0IiA6ICJTVVJHRU1BQzUiLAogICJ0eXBlIiA6ICJsaWNlbnNlZCIsCiAgImV4cGlyZXNPbkRhdGUiIDogMTc0NjM1MDU2Nwp9", -// @"sign": @"a6r7vh1fShggsQgBfghOc6FhXdXsgSrHMhwlwO8HJYMDxch7+H8Q0mAnKdG3yY4J002iTTpgtPcUMctlVWOXLNaHnDEY0lWGnU/oHW5BPBiJ6eqPe/Mf+pqwtG4HTRqGRNUiomru2q7zu4VuYdJFklFffnK7gSLxwxAvV71K8JCkvxb0I+eNuJUSzsRhOJC4HMBmeoq++SBViUANbZhI6u+w5XXVcV3VC0VAfsMrIYItHY8oWE9FjWkB+BnJa6+yXgxfBK89wnsW0gCszAs9Pa1e06QEQ1vHKWmbp8XKP5OwzCLp73vSKyjB4cl9o57qwt9Y+DD1Qsdi1MT0NpWqwA==", -// @"enterprise":@1 -// }; - - // 伪造 License , p[1]=3 - policySign = @{ - @"policy": @"ewogICJwIiA6ICJRVTFvSHg4SWJ4VTFQdlFRMzM0MEpBPT0iLAogICJkZXZpY2VJRCIgOiAiMTZiOGY0YmRmZDU1ZDI5ZjczNzQyN2I0ZWMwYzE0ZDciLAogICJwcm9kdWN0IiA6ICJTVVJHRU1BQzUiLAogICJ0eXBlIiA6ICJsaWNlbnNlZCIsCiAgImV4cGlyZXNPbkRhdGUiIDogMTc0NjM1MDU2Nwp9", - @"sign": @"K7QcxRYpPeIYOUtSHqLr2jfzWKYKlphJen0xUwJLVqwM4Xr/DcDvEgQVyIgV8poF9RnhCg7eruw6wbXzMv9GSfSC/zKm4DUsjPsqtvr4X/+thfUyzVrAJgGUmIGSOR7dFrOvdhbx4h+hsiWR9oYVq+vQfp7xdkF1RD+72QVbhR8PJyBCxja34JklxvDKGDJmsc1FhbkTeXzEBNkSNhD58HRWajIxlAs6sckjbGV/DPrrV8cC3Rok3iytiMcp/KRqtaJYtZk/RMrvxnByszutZ8U9z5ve1mHMdO1BKEnUREtriVZ/kU0I07itV9tpDoJReL9pQiQk93L/WdO9mmtERA==", - @"enterprise":@(enterpriseLicense) - }; - // NSLog(@">>>>>> policySign: %@",policySign); - - // 写入 license attr 信息 - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:policySign options:NSJSONWritingPrettyPrinted error:nil]; - Class KDStorageHelperClass = NSClassFromString(@"KDStorageHelper"); - SEL directoryPathSelector = NSSelectorFromString(@"applicationSupportDirectoryPathWithName:"); - NSString *directoryPath = [KDStorageHelperClass performSelector:directoryPathSelector withObject:@"com.nssurge.surge-mac"]; - const char *directoryUTF8String = [directoryPath UTF8String]; - const void *jsonBytes = [jsonData bytes]; - size_t jsonLength = [jsonData length]; - setxattr(directoryUTF8String, "com.nssurge.surge-mac.nsa.3", jsonBytes, jsonLength, 0, 0); - - - // 伪造 public key - // - NSData initWithBytesNoCopy:length:freeWhenDone: - Class NSDataClass = NSClassFromString(@"NSData"); - SEL initWithBytesNoCopySeletor = NSSelectorFromString(@"initWithBytesNoCopy:length:freeWhenDone:"); - Method initWithBytesNoCopyMethod = class_getInstanceMethod(NSDataClass, initWithBytesNoCopySeletor); - initWithBytesNoCopyMethodIMP = method_getImplementation(initWithBytesNoCopyMethod); - [MemoryUtils hookInstanceMethod: - NSDataClass - originalSelector:initWithBytesNoCopySeletor - swizzledClass:[self class] - swizzledSelector:NSSelectorFromString(@"hk_initWithBytesNoCopy:length:freeWhenDone:") - ]; - - // hook NSURL['+ URLWithString:'] - Class NSURLControllerClass = NSClassFromString(@"NSURL"); - SEL urlWithStringSeletor = NSSelectorFromString(@"URLWithString:"); - Method urlWithStringSeletorMethod = class_getClassMethod(NSURL.class, urlWithStringSeletor); - urlWithStringSeletorIMP = method_getImplementation(urlWithStringSeletorMethod); - [MemoryUtils hookClassMethod: - NSURLControllerClass - originalSelector:urlWithStringSeletor - swizzledClass:[self class] - swizzledSelector:NSSelectorFromString(@"hk_URLWithString:") - ]; - - - // trigger : expiresOnDate 解析 policy,获取 license/device 信息 - // -[NSDictionary objectForKeyedSubscript:]: - // Class NSDictionaryClass = NSClassFromString(@"__NSDictionaryI"); - // SEL objectForKeyedSubscriptSeleter = NSSelectorFromString(@"objectForKeyedSubscript:"); - // Method objectForKeyedSubscriptMethod = class_getInstanceMethod(NSDictionaryClass, objectForKeyedSubscriptSeleter); - // objectForKeyedSubscriptImp = method_getImplementation(objectForKeyedSubscriptMethod); - // [MemoryUtils hookInstanceMethod: - // NSDictionaryClass - // originalSelector:objectForKeyedSubscriptSeleter - // swizzledClass:[self class] - // swizzledSelector:NSSelectorFromString(@"hook_objectForKeyedSubscript:") - // ]; - - //获取授权凭证 0 未授权, 1 试用 xxx,2 授权, arm : 0000000100173b70 - // -[WindowController trialButtonPressed:] sub_1001c643c() == 0x1:0x0 - // 00000001001c643c push rbp ; CODE XREF=sub_10008aad8+635, sub_1000beb1b+34, -[SGMAppDelegate applicationDidFinishLaunching:]+3108, sub_1001b16ab+64, sub_1001b1820+14, sub_1001b1820+24, sub_1001b1820+33, sub_1001b2827+7, sub_1001b2827+26, sub_1001b2827+64, sub_1001b441b+83 - // 00000001001c643d mov rbp, rsp - // 00000001001c6440 mov eax, dword [dword_100860ec0] ; dword_100860ec0 - // 00000001001c6446 pop rbp - // 00000001001c6447 ret - - -//## helper 修复 -//# 通过 dtrace 监控到 surge helper[18720] kill 了 surge [18706] 进程 -//0 243 kill:return 18720 com.nssurge.surge-mac.helper 9 18706 0 -//# 查看 helper 日志 发现 helper 检测了 Framework 下文件签名 -//14156 signing bytes in 3 blob(s) from /Applications/Surge.app/Contents/Frameworks/Sparkle.framework/Versions/Current/Sparkle(x86_64) -//2024-05-12 15:50:49.107835+0800 0x309f28 Debug 0x0 19297 0 com.nssurge.surge-mac.helper: (Security) [com.apple.securityd:cfloadfile] failed to fetch /Applications/Surge.app/Contents/Frameworks/Sparkle.framework/Versions/Current/./_CodeSignature/CodeRequirements-1 error=-10 -//2024-05-12 15:50:49.107869+0800 0x309f28 Debug 0x0 19297 0 com.nssurge.surge-mac.helper: (Security) [com.apple.securityd:staticCode] SecStaticCode network default: YES -//2024-05-12 15:50:49.107900+0800 0x309f28 Debug 0x0 19297 0 com.nssurge.surge-mac.helper: (Security) [com.apple.securityd:notarization] Extracting ticket from bundle: /Applications/Surge.app/Contents/Frameworks/Sparkle.framework/Versions/Current/. - - // arm: SecCodeCnginfo : 000000010027c7b8 - // 3. 网络不可用(如果校验不通过, 会影响下面的 dns 解析) --> 移除文件签名验证 -> -[SGMScriptManagementViewController viewDidAppear]: intrinsic_movaps(xmm0, *(int128_t *)0x100603550) - // 00000001005a78b0 db "SecCodeCngInformateWithPckValidimework/Bmework/S", 0 ; DATA XREF=sub_10027f4e8+168 - - // 00000001006034c0 db "SecCodeCheckValiSecCodeCopySigniSecStaticCodeCreSecStaticCodeChe/Contents/Frameworks/MMMarkdown.framework/MMMarkorks/Bugsnag.fraorks/Sparkle.fra", 0 ; DATA XREF=sub_10030b9a5+33 - // 000000010030b9a5 push rbp ; Objective C Block defined at 0x100747c88, Begin of try block, DATA XREF=0x100747c98 - // 000000010030b9a6 mov rbp, rsp - // 000000010030b9a9 push r15 - // 000000010030b9ab push r14 - // 000000010030b9ad push r13 - // 000000010030b9af push r12 - // 000000010030b9b1 push rbx - // 000000010030b9b2 sub rsp, 0xb8 - // 000000010030b9b9 mov edi, 0x14 ; argument "size" for method imp___stubs__malloc - // 000000010030b9be call imp___stubs__malloc ; malloc - // 000000010030b9c3 mov rbx, rax - // 000000010030b9c6 movaps xmm0, xmmword [aSeccodecheckva] ; "SecCodeCheckValiSecCodeCopySigniSecStaticCodeCreSecStaticCodeChe/Contents/Frameworks/MMMarkdown.framework/MMMarkorks/Bugsnag.fraorks/Sparkle.fra" - // 000000010030b9cd movups xmmword [rax], xmm0 - // 000000010030b9d0 mov dword [rax+0x10], 0x79746964 - // 000000010030b9d7 mov rdi, qword [objc_cls_ref_NSString] ; argument "class" for method imp___stubs__objc_alloc, objc_cls_ref_NSString - // 000000010030b9de call imp___stubs__objc_alloc ; objc_alloc - // 000000010030b9e3 mov rsi, qword [0x10082f6d8] ; @selector(initWithBytes:length:encoding:) - -#if defined(__arm64__) || defined(__aarch64__) - NSString *sub_0x10030b9a5Code = @"FF 43 04 D1 E9 23 0A 6D FC 6F 0B A9 FA 67 0C A9 F8 5F 0D A9 F6 57 0E A9 F4 4F 0F A9 FD 7B 10 A9 FD 03 04 91 80 02 80 52 .. .. 09 94 F3 03 00 AA .. .. 00 .."; -#elif defined(__x86_64__) - NSString *sub_0x10030b9a5Code = @"55 48 89 E5 41 57 41 56 41 55 41 54 53 48 81 EC .. .. .. .. BF .. .. .. .. E8 .. .. .. .. 48 89 C3 0F 28 05 .. .. .. .. 0F 11 00"; -#endif - NSArray *sub_0x10030b9a5Offsets =[MemoryUtils searchMachineCodeOffsets: - searchFilePath - machineCode:sub_0x10030b9a5Code - count:(int)1 - ]; - intptr_t _sub_0x10030b9a5 = [MemoryUtils getPtrFromGlobalOffset:0 targetFunctionOffset:(uintptr_t)[sub_0x10030b9a5Offsets[0] unsignedIntegerValue] reduceOffset:(uintptr_t)fileOffset]; - - // dobby.dylib 动态库 , arm 这里不能写 nil ? EXC_BAD_ACCESS (code=1, address=0x0) - // 所以全局替换成了静态库 .a - DobbyHook((void *)_sub_0x10030b9a5, ret, NULL); - - - // hook DNS 解析 -// Class SGDNSRecordIPv4Class = NSClassFromString(@"SGDNSRecordIPv4"); -// SEL addressWithPortSeletor = NSSelectorFromString(@"addressWithPort:"); -// Method addressWithMethod = class_getInstanceMethod(SGDNSRecordIPv4Class, addressWithPortSeletor); -// addressWithMethodIMP = method_getImplementation(addressWithMethod); -// [MemoryUtils hookInstanceMethod: -// SGDNSRecordIPv4Class -// originalSelector:addressWithPortSeletor -// swizzledClass:[self class] -// swizzledSelector:NSSelectorFromString(@"hook_addressWithPort:arg3:arg4:arg5:") -// ]; - - - // 过滤 mac/v3/ac,mac/v3/resource/jsvm,mac/v3/deactivate/,mac/v3/init/,mac/v3/free-trial,mac/v3/resource/module,mac/v3/resource/jsvm,mac/v3/device?deviceID=%@ - // mac/v3/ac - Class SGRequestHelperClass = NSClassFromString(@"SGRequestHelper"); - SEL dataTaskWithRequestSeletor = NSSelectorFromString(@"dataTaskWithRequest:completionHandler:"); - Method dataTaskWithRequestMethod = class_getInstanceMethod(SGRequestHelperClass, dataTaskWithRequestSeletor); - dataTaskWithRequestIMP = method_getImplementation(dataTaskWithRequestMethod); - [MemoryUtils hookInstanceMethod: - SGRequestHelperClass - originalSelector:dataTaskWithRequestSeletor - swizzledClass:[self class] - swizzledSelector:NSSelectorFromString(@"hook_dataTaskWithRequest:completionHandler:") - ]; - - // -[SGHTTPClient dataTaskWithURLRequest:configuration:completionHandler:] - // mac/v3/xx - Class SGHTTPClientClass = NSClassFromString(@"SGHTTPClient"); - SEL dataTaskWithRequestSeletor2 = NSSelectorFromString(@"dataTaskWithURLRequest:configuration:completionHandler:"); - Method dataTaskWithRequestMethod2 = class_getInstanceMethod(SGHTTPClientClass, dataTaskWithRequestSeletor2); - dataTaskWithRequestIMP2 = method_getImplementation(dataTaskWithRequestMethod2); - [MemoryUtils hookInstanceMethod: - SGHTTPClientClass - originalSelector:dataTaskWithRequestSeletor2 - swizzledClass:[self class] - swizzledSelector:NSSelectorFromString(@"hook_dataTaskWithURLRequest:configuration:completionHandler:") - ]; - - - // 下面俩个 设置菜单会报错,大概是因为没权限调用 Ponte 与 Remote 功能 - [MemoryUtils replaceInstanceMethod: - NSClassFromString(@"SGPonteManager") - originalSelector:NSSelectorFromString(@"updatePonteDeviceListWithCompletionHandler:") - swizzledClass:[self class] - swizzledSelector:@selector(ret) - ]; - [MemoryUtils replaceInstanceMethod: - NSClassFromString(@"SGRemoteNotificationCenter") - originalSelector:NSSelectorFromString(@"setup") - swizzledClass:[self class] - swizzledSelector:@selector(ret) - ]; - - - // 这个判断决定模块(不包括官方内置模块)走本地存储 还是 icloud 存储,得绕过 icloud 存储,不然模块存不了 - // -[NSUbiquitousKeyValueStore defaultStore] - [MemoryUtils replaceClassMethod: - NSClassFromString(@"NSUbiquitousKeyValueStore") - originalSelector:NSSelectorFromString(@"defaultStore") - swizzledClass:[self class] - swizzledSelector:@selector(ret0) - ]; - - // 过滤 https://76.223.12.1/mac/v3/ac 请求 - // arm: /mac/deviceNasystemVedeviceModevice -> 00000001000e1fe8 - // Message from debugger: Terminated due to signal 9 - // queue = 'com.apple.root.default-qos', stop reason = EXC_GUARD (code=4611686022722355231, subcode=0x8fd4dbfade2dead) - // triger : -[SGMOverviewViewController viewDidLoad]: >> dispatch_source_set_event_handler(r15, &var_68); - // 把这个 nop 掉就没必要修复 objectForKeyedSubscript(k) 参数了; mac/v3/ac - // 00000001003044df push rbp ; DATA XREF=-[SGMOverviewViewController viewDidLoad]+1256 - //#if defined(__arm64__) || defined(__aarch64__) - // NSString *sub_0x1003044dfCode = @"FF C3 05 D1 FC 6F 11 A9 FA 67 12 A9 F8 5F 13 A9 F6 57 14 A9 F4 4F 15 A9 FD 7B 16 A9 FD 83 05 91 F6 03 00 AA .. .. 00 .."; - //#elif defined(__x86_64__) - // NSString *sub_0x1003044dfCode = @"55 48 89 E5 41 57 41 56 41 55 41 54 53 48 81 EC .. .. .. .. 48 89 BD .. .. .. .. 48 8B 05 .. .. .. .. 48 8B 00 48 89 45 .. 48 8B 1D .. .. .. .. 4C 8B 35 .. .. .. .. BF .. .. .. .. E8 .. .. .. .. 49 89 C7 0F 28 05 .. .. .. .. 0F 11 00 48 B8 .. .. .. .. .. .. .. .."; - //#endif - // NSArray *sub_0x1003044dfOffsets =[MemoryUtils searchMachineCodeOffsets: - // searchFilePath - // machineCode:sub_0x1003044dfCode - // count:(int)3 - // ]; - // - // for (NSNumber *sub_0x1003044dIt in sub_0x1003044dfOffsets) { - // intptr_t _sub_0x1003044df = [MemoryUtils getPtrFromGlobalOffset:0 targetFunctionOffset:(uintptr_t)[sub_0x1003044dIt unsignedIntegerValue] reduceOffset:(uintptr_t)fileOffset]; - // DobbyHook((void *)_sub_0x1003044df, ret1, nil); - // } - - - // 通过 hook join 函数来伪造 device id - // NSMutableArray componentsJoinedByString - Class NSMutableArrayClass = NSClassFromString(@"NSMutableArray"); - SEL componentsJoinedByStringSeletor = NSSelectorFromString(@"componentsJoinedByString:"); - Method componentsJoinedByStringMethod = class_getInstanceMethod(NSMutableArrayClass, componentsJoinedByStringSeletor); - componentsJoinedByStringIMP = method_getImplementation(componentsJoinedByStringMethod); - [MemoryUtils hookInstanceMethod: - NSMutableArrayClass - originalSelector:componentsJoinedByStringSeletor - swizzledClass:[self class] - swizzledSelector:NSSelectorFromString(@"hook_componentsJoinedByString:") - ]; - - - - // 获取真实的设备 ID - // deviceId rcx 呢 --> sub_1000b7dbd(0x2, r15, @"Device ID: %@", rcx, 0x0, r9, var_140); arm : 0000000100174278 - // void *fun_device = (void *)_sub_0x1001c6ca6; - // deviceId = ((NSString* (*)())fun_device)(); - // NSLog(@">>>>>> device id is %@",deviceId); - - // hook CCCrypt ; 来获取 p 参数的 key 与 iv - DobbyHook((void *) CCCrypt,(void *) hk_CCCrypt, (void **) &original_CCCrypt); - - - // 'SGSocket-346', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) - // p 参数第一位如果是 3, 会走这个地方, 导致程序过段时间奔溃或网络不可用 - // 据说正版的第一位是 3, 但是改成 1 好像也没啥问题, 也不会崩溃,要么 hook 下面的, 要么用 1; -// 000000010000e0fd push rbp ; Objective C Block defined at 0x10073fd58, DATA XREF=0x10073fd68 -// 000000010000e0fe mov rbp, rsp -// 000000010000e101 push r15 -// 000000010000e103 push r14 -// 000000010000e105 push r13 -// 000000010000e107 push r12 -// 000000010000e109 push rbx -// 000000010000e10a sub rsp, 0x628 -// 000000010000e111 mov rax, qword [___stack_chk_guard_10073e278] ; ___stack_chk_guard_10073e278 -// rax = [SGDNSPacket queryPacketWithDomain:@"captive.apple.com" identifier:0x0 queryType:0x100]; - -#if defined(__arm64__) || defined(__aarch64__) - NSString *sub_0x10000e091Code = @"FA 67 BB A9 F8 5F 01 A9 F6 57 02 A9 F4 4F 03 A9 FD 7B 04 A9 FD 03 01 91 FF 83 18 D1 .. .. 00 .."; -#elif defined(__x86_64__) - NSString *sub_0x10000e091Code = @"55 48 89 E5 41 57 41 56 41 55 41 54 53 48 81 EC 28 06 00 00 48 8B 05 .. .. .."; -#endif - NSArray *sub_0x10000e091Offsets =[MemoryUtils searchMachineCodeOffsets: - searchFilePath - machineCode:sub_0x10000e091Code - count:(int)3 - ]; - - for (NSNumber *sub_0x10000e091It in sub_0x10000e091Offsets) { - intptr_t _sub_0x10000e091 = [MemoryUtils getPtrFromGlobalOffset:0 targetFunctionOffset:(uintptr_t)[sub_0x10000e091It unsignedIntegerValue] reduceOffset:(uintptr_t)fileOffset]; - DobbyHook((void *)_sub_0x10000e091, ret, NULL); - } - - - - // TODO: SSL 域名过滤该如何优雅 HOOK ?? -// -[SGHTTPEngine shouldPerformMITMForHost:sourceAddress:isSNI:]: -// -// 0000000100049de7 mov r13, qword [0x100837058] ; @selector(isEqualToString:) // __NSCFString -// if ([rbx isEqualToString:@"api.enterprise.nssurge.com:443"] != 0x0 || -// [rbx isEqualToString:@"www.surge-activation.com:443"] != 0x0 || -// [rbx isEqualToString:@"www.surge-activation.com"] != 0x0 || -// [rbx isEqualToString:@"api.enterprise.nssurge.com"] != 0x0) - // 它可以在任意地方进行hook 并获取/修改 寄存器的上下文 -// DobbyInstrument((void *) 0x00000001000499d9, shouldPerformMITMForHost_handler); - - -// -[SGJournalRecordContainer _shouldHideRequest:takeoverMode:engineIdentifier:URL:]: -// r15 hasSuffix:@".surge-activation.com"] != 0x0 || [r15 isEqual:@"enterprise.nssurge.com"] != 0x0 -// -// 000000010005cdb9 mov r14, qword [0x1008360b8] ; @selector(hasSuffix:), CODE XREF=-[SGJournalRecordContainer _shouldHideRequest:takeoverMode:engineIdentifier:URL:]+642 -// 000000010005cdc0 lea rdx, qword [cfstring__surge_activation_com] ; @".surge-activation.com" -// -// 000000010005cdd7 mov rsi, qword [0x100837008] ; @selector(isEqual:) -// 000000010005cdde lea rdx, qword [cfstring_enterprise_nssurge_com] ; @"enterprise.nssurge.com" - - - - - // license 窗口 - // -[SGMLicenseViewController reload]: - // https://www.surge-activation.com/mac/v3/device?deviceID=ce2500e944650ad7f6e2f580268e5454 - [MemoryUtils hookInstanceMethod: - NSClassFromString(@"SGMEnterprise") - originalSelector:NSSelectorFromString(@"settings") - swizzledClass:[self class] - swizzledSelector:NSSelectorFromString(@"hk_settings") - ]; - - - // 发送消息提醒 - Method raiseEventMethod = class_getClassMethod(NSClassFromString(@"SGEEventCenter"), NSSelectorFromString(@"raiseEvent:content:type:")); - IMP raiseEventMethodIMP = method_getImplementation(raiseEventMethod); - ((void *(*)(id, SEL,id,id,int))raiseEventMethodIMP)( - NSClassFromString(@"SGEEventCenter"), - NSSelectorFromString(@"raiseEvent:content:type:"), - @"WARNNING", - @"Ref: https://github.com/marlkiller/dylib_dobby_hook",1 - ); - - return YES; -} - - -void shouldPerformMITMForHost_handler(void *address, DobbyRegisterContext *ctx) { - SEL tmp = @selector(sEqualToStringEx:); - -#if defined(__arm64__) || defined(__aarch64__) -#elif defined(__x86_64__) - intptr_t r12 = (intptr_t)ctx->general.regs.r12; -// 去 nm 的各种指针, 这样写最好 - ctx->general.regs.r12 = (intptr_t)tmp; -// *(intptr_t*)&ctx->general.regs.r12 = (intptr_t)tmp; - -#endif - -} -// https://github.com/PhD-5/iOSDefectTweak/blob/master/hooks/CCCryptHook.m -// https://github.com/PhD-5/CCCryptHook/blob/master/hooks/CCCrypt.xm -static size_t getIVLength(CCAlgorithm alg) { - switch(alg) { - case kCCAlgorithmAES128: - return kCCBlockSizeAES128; - case kCCAlgorithmDES: - return kCCBlockSizeDES; - case kCCAlgorithm3DES: - return kCCBlockSize3DES; - case kCCAlgorithmCAST: - return kCCBlockSizeCAST; - case kCCAlgorithmRC2: - return kCCBlockSizeRC2; - default: - return 0; - } -} -static CCCryptorStatus (*original_CCCrypt)( - CCOperation op, /* kCCEncrypt, etc. */ - CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */ - CCOptions options, /* kCCOptionPKCS7Padding, etc. */ - const void *key, - size_t keyLength, - const void *iv, /* optional initialization vector */ - const void *dataIn, /* optional per op and alg */ - size_t dataInLength, - void *dataOut, /* data RETURNED here */ - size_t dataOutAvailable, - size_t *dataOutMoved); - -CCCryptorStatus hk_CCCrypt( - CCOperation op, /* kCCEncrypt, etc. */ - CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */ - CCOptions options, /* kCCOptionPKCS7Padding, etc. */ - const void *key, - size_t keyLength, - const void *iv, /* optional initialization vector */ - const void *dataIn, /* optional per op and alg */ - size_t dataInLength, - void *dataOut, /* data RETURNED here */ - size_t dataOutAvailable, - size_t *dataOutMoved) -{ -// 0x1001c7ce7 <+955>: leaq -0x50(%rbp), %rdi -// 0x1001c7ceb <+959>: movq %r14, %rsi // deviceId.getBytes() -// 0x1001c7cee <+962>: movq %rax, %rdx // 32 -// 0x1001c7cf1 <+965>: callq 0x10045fca0 ; // rbp-50 为 key, rbp-40 为 iv ; 这里调用的 sha256 来根据 deviceId 生成key/iv -// ... -// 00000001001c7d63 lea rcx, qword [rbp+-0x50] ; argument "key" for method imp___stubs__CCCrypt -// 00000001001c7d67 mov r8d, 0x20 ; argument "keyLength" for method imp___stubs__CCCrypt -// 00000001001c7d6d mov edi, 0x1 ; argument "op" for method imp___stubs__CCCrypt -// 00000001001c7d72 xor esi, esi ; argument "alg" for method imp___stubs__CCCrypt -// 00000001001c7d74 mov edx, 0x1 ; argument "options" for method imp___stubs__CCCrypt -// 00000001001c7d79 lea r9, qword [rbp+-0x40] ; argument "iv" for method imp___stubs__CCCrypt -// 00000001001c7d7d lea r10, -// 00000001001c7d84 push r10 ; argument "dataOutMoved" for method imp___stubs__CCCrypt -// 00000001001c7d86 push r13 ; argument "dataOutAvailable" for method imp___stubs__CCCrypt -// 00000001001c7d88 push qword [qword_100867b70] ; argument "dataOut" for method imp___stubs__CCCrypt, -// 00000001001c7d8e push rax ; argument "dataInLength" for method imp___stubs__CCCrypt -// 00000001001c7d8f push r14 ; argument "dataIn" for method imp___stubs__CCCrypt -// 00000001001c7d91 call imp___stubs__CCCrypt ; CCCrypt - - // TODO: 没搞明白 p 参数解密后是什么, 以及干嘛的.. -// https://drafts.misty.moe/surge%E7%A0%B4%E8%A7%A3-89fa88c1794d43f6888e43bd30f91bfe#d777e8afaa1041eaa844910d5b519ecf -// AES_CBC_256( -// input: "\x03\x04\x02NSExtension", -// key: SHA256(deviceID), -// iv: SHA256(deviceID)[16:32], -// options: PKCS7Padding) -// 00000000 03 04 02 4e 53 45 78 74 65 6e 73 69 6f 6e 00 |...NSExtension.| -// -// [0] = 3, 为固定header -// [1] = 4, 控制VMess加密 -// [2] = 2, 控制TCP、SS的端口字节数 -// [3:] = "NSExtension", 控制TE plugin加载 - - // 似乎 解密后的 dataOut 第一字节不能是 3,不能是 0 - if (keyLength==32) { - // 0x1001c6921 <+1125>: callq 0x1005df574 ; symbol stub for: CCCrypt - // 0x1001c6926 <+1130>: addq $0x30, %rsp - NSData *keyData = [NSData dataWithBytes:key length:keyLength]; - NSString *keyBase64 = [keyData base64EncodedStringWithOptions:0]; - NSString *keyStr = [[NSString alloc] initWithData:keyData encoding:NSUTF8StringEncoding]; - NSLog(@">>>>>> keyBase64 :%@",keyBase64); - NSLog(@">>>>>> keyStr :%@",keyStr); - - const uint8_t *keyBytes = (const uint8_t *)keyData.bytes; - NSString *keyHex = @""; - for (int i = 0; i < keyLength; i++) { - keyHex = [keyHex stringByAppendingFormat:@"%02x ", keyBytes[i]]; - } - // b7 6a bf 75 99 07 0d 87 1e 59 68 f3 84 4a 79 c0 6a f3 7d 82 af 81 a2 84 39 7e 0a cc f2 83 cb 24 - NSLog(@">>>>>> keyHex: %@", keyHex); - - if (iv!=nil) { - NSData *ivData = [NSData dataWithBytes:iv length:(unsigned int)getIVLength(alg)]; - NSString *ivBase64 = [ivData base64EncodedStringWithOptions:0]; - NSString *ivStr = [[NSString alloc] initWithData:ivData encoding:NSUTF8StringEncoding]; - NSLog(@">>>>>> ivBase64 :%@",ivBase64); - NSLog(@">>>>>> ivStr :%@",ivStr); - - const uint8_t *ivBytes = (const uint8_t *)ivData.bytes; - NSString *ivHex = @""; - for (int i = 0; i < getIVLength(alg); i++) { - ivHex = [ivHex stringByAppendingFormat:@"%02x ", ivBytes[i]]; - } - // 6a f3 7d 82 af 81 a2 84 39 7e 0a cc f2 83 cb 24 - NSLog(@">>>>>> ivHex: %@", ivHex); - } - - NSData *dataInData = [NSData dataWithBytes:dataIn length:dataInLength]; - NSString *dataInBase64 = [dataInData base64EncodedStringWithOptions:0]; - NSString *dataInStr = [[NSString alloc] initWithData:dataInData encoding:NSUTF8StringEncoding]; - NSLog(@">>>>>> dataInBase64 :%@",dataInBase64); - NSLog(@">>>>>> dataInStr :%@",dataInStr); - - } - CCCryptorStatus result = original_CCCrypt(op,alg,options,key,keyLength,iv,dataIn,dataInLength,dataOut,dataOutAvailable,dataOutMoved); - - if (keyLength==32) { - NSData *dataOutData = [NSData dataWithBytes:dataOut length:*dataOutMoved]; - NSString *dataOutBase64 = [dataOutData base64EncodedStringWithOptions:0]; - NSString *dataOutStr = [[NSString alloc] initWithData:dataOutData encoding:NSUTF8StringEncoding]; - NSLog(@">>>>>> dataOutBase64 :%@",dataOutBase64); - NSLog(@">>>>>> dataOutStr :%@",dataOutStr); - - // 读取 hex - unsigned char *dataOutBytes = (unsigned char *)dataOutData.bytes; - NSString *dataOutHex = @""; - for (NSUInteger i = 0; i < *dataOutMoved; i++) { - NSString *byteString = [NSString stringWithFormat:@"%02x ", dataOutBytes[i]]; - dataOutHex = [dataOutHex stringByAppendingString:byteString]; - } - // 03 04 02 4e 53 45 78 74 65 6e 73 69 6f 6e - NSLog(@">>>>>> Memory content at address %p: %@", dataOut, dataOutHex); - // kCCSuccess = 0, - NSLog(@">>>>>> result: %d",result); - - } - return result; -} - - - - -typedef void (^CompletionHandler1)(NSError *error, NSURLResponse *response, NSData *data); -typedef void (^CompletionHandler2)(NSData *data, NSURLResponse *response, NSError *error); - -// Ref: https://github.com/NyaMisty/Surge4Advanced/blob/master/Tweak.x -// __auto_type wrapper = ^(NSError *error, NSDictionary *data) { -// __auto_type resp = [[NSHTTPURLResponse alloc] initWithURL:url statusCode:200 HTTPVersion:@"1.1" headerFields:@{}]; -// NSData *body = [NSJSONSerialization dataWithJSONObject:data options:0 error: &error]; -// completionHandler(body, resp, error); -// }; - -//// fake req -// void (^handler)(NSError *error, NSDictionary *data) = ^(NSError *error, NSDictionary *data){ -// NSDictionary *licInfo = @{ -// @"deviceID": @"deviceID", -// @"expirationDate": @4070880000, // 2099-01-01 00:00:00 -// @"fusDate": @4070880000, -// @"type": @"licensed", -// @"issueDate": [NSNumber numberWithInt:(long)[[NSDate date] timeIntervalSince1970]], -// @"p": @"p", -// }; -// NSData *licInfoData = [NSJSONSerialization dataWithJSONObject:licInfo options:0 error: &error]; -// NSString *licInfoStr = [[NSString alloc] initWithData:licInfoData encoding:NSUTF8StringEncoding]; -// NSString *licInfoBase64 = [licInfoData base64EncodedStringWithOptions:0]; -// wrapper(nil, @{ -// @"license": @{ -// @"policy": licInfoBase64, -// @"sign": @"" -// } -// }); -// }; -// dispatch_async(dispatch_get_main_queue(), ^{ -// handler(nil, nil); -// }); - -// if ([reqUrl hasSuffix:@"ac"]) { // disable refresh req -// [req setURL:[NSURL URLWithString:@"http://127.0.0.1:65536"]]; -// void (^handler)(NSError *error, NSDictionary *data) = ^(NSError *error, NSDictionary *data){ -// wrapper(nil, @{}); -// }; -// dispatch_async(dispatch_get_main_queue(), ^{ -// handler(nil, nil); -// }); -// } - -// completionHandler:(void (^)(NSError *error, NSURLResponse *response, NSData *data))completionHandler -//- (void) hook_dataTaskWithURLRequest:(NSMutableURLRequest*)request configuration:arg2 -// completionHandler:(void (^)(NSError *error, NSURLResponse *response, NSData *data))completionHandler{ -- (void) hook_dataTaskWithURLRequest:(NSMutableURLRequest*)request configuration:arg2 completionHandler:(CompletionHandler1)completionHandler{ - - // Printing description of arg3: - // <__NSGlobalBlock__: 0x100742fa0> - // signature: "v32@?0@"NSError"8@"NSHTTPURLResponse"16@"NSData"24" - // invoke : 0x100108c4a - - NSURL *url = [request URL]; - NSString *urlString = [url absoluteString]; - if ([urlString containsString:@"mac/v3/"] && completionHandler) { - // 在 Objective-C 中,completionHandler 是一种常见的异步编程模式,它通常用于在一个操作完成后执行一些额外的代码或处理结果。 - // po arg2 >> signature: "v32@?0@"NSData"8@"NSURLResponse"16@"NSError"24" - __auto_type wrapper = ^(NSError *error, NSDictionary *data) { - __auto_type resp = [[NSHTTPURLResponse alloc] initWithURL:url statusCode:200 HTTPVersion:@"1.1" headerFields:@{}]; - NSData *body = [NSJSONSerialization dataWithJSONObject:data options:0 error: &error]; - completionHandler(error, resp, body); - }; - - NSDictionary *respBody; - NSString *reqBody; - if ([urlString containsString:@"mac/v3/ac"]) { - respBody =@{ - @"k":@0 - }; - }else{ - respBody =@{ - @"code":@0 - }; - } - reqBody = [[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding]; - NSLog(@">>>>>> [hook_dataTaskWithURLRequest] Intercept url: %@, request body: %@, response body: %@",url, reqBody,respBody); - wrapper(nil,respBody); - return; - } - NSLog(@">>>>>> [hook_dataTaskWithURLRequest] Allow to pass url: %@",url); - ((void(*)(id, SEL,id,id,id))dataTaskWithRequestIMP2)(self, _cmd,request,arg2,completionHandler); -} - -- (void) hook_dataTaskWithRequest:(NSMutableURLRequest*)request completionHandler:(CompletionHandler2)completionHandler{ - - // Printing description of arg2: - // <__NSStackBlock__: 0x7ff7bfefd828> - // signature: "v32@?0@"NSData"8@"NSURLResponse"16@"NSError"24" - - NSURL *url = [request URL]; - NSString *urlString = [url absoluteString]; - if ([urlString containsString:@"mac/v3/"] && completionHandler) { - // 在 Objective-C 中,completionHandler 是一种常见的异步编程模式,它通常用于在一个操作完成后执行一些额外的代码或处理结果。 - __auto_type wrapper = ^(NSError *error, NSDictionary *data) { - __auto_type resp = [[NSHTTPURLResponse alloc] initWithURL:url statusCode:200 HTTPVersion:@"1.1" headerFields:@{}]; - NSData *body = [NSJSONSerialization dataWithJSONObject:data options:0 error: &error]; - completionHandler(body, resp,error); - }; - NSDictionary *respBody; - NSString *reqBody; - - if ([urlString containsString:@"mac/v3/init/"]) { - // 如果不是 enterprise, 每次软件启动都会校验这个接口 - respBody = policySign; - } else if ([urlString containsString:@"mac/v3/device"]) { - respBody =@{ - @"account":[Constant G_EMAIL_ADDRESS], - @"license":@"🇨🇳 Voidm.Com" - }; - } else if ([urlString containsString:@"mac/v3/ac"]) { - respBody =@{ - @"k":@0 - }; - }else if ([urlString containsString:@"mac/v3/resource/jsvm"]) { - NSString *jsvm = [NSString stringWithContentsOfFile: - [[Constant getCurrentAppPath] stringByAppendingString:@"/Contents/MacOS/jsvm.js"] - encoding:NSUTF8StringEncoding error:nil - ]; - if (!jsvm) { - NSLog(@">>>>>> /Contents/MacOS/jsvm.js 文件不存在 ???"); - [MemoryUtils exAlart:@"FBI warning" message:@"/Contents/MacOS/jsvm.js 文件不存在 ???"]; - respBody =@{ - @"code": @0 - }; - } else { - respBody =@{ - @"code": @0, - @"data": jsvm, - @"v": @"2024071200000000" - }; - } - - }else if ([urlString containsString:@"mac/v3/resource/module"]) { - NSData *nsData = [NSData dataWithContentsOfFile:[[Constant getCurrentAppPath] stringByAppendingString:@"/Contents/MacOS/modules.json"] - options:NSDataReadingMappedIfSafe error:nil]; - if (!nsData) { - NSLog(@">>>>>> /Contents/MacOS/modules.json 文件不存在 ???"); - [MemoryUtils exAlart:@"FBI warning" message:@"/Contents/MacOS/modules.json 文件不存在 ???"]; - respBody =@{ - @"code": @0 - }; - } else { - id sections = [NSJSONSerialization JSONObjectWithData: - nsData options:NSJSONReadingMutableContainers error:nil - ]; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@{ - @"v": @"2024071200000000", - @"sections": sections, - @"u": @"8fce02ae-b82a-40ce-bda5-9cfbcb50d65f" - } options:0 error:nil]; - respBody =@{ - @"code":@0, - @"data": [jsonData base64EncodedStringWithOptions:0] - }; - } - - } else { - // TODO: 暂时未知, 有没有必要 hook 各个请求的 response data - respBody =@{ - @"code":@0 - }; - } - reqBody = [[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding]; - NSLog(@">>>>>> [hook_dataTaskWithRequest] Intercept url: %@, request body: %@, response body: %@",url, reqBody,respBody); - wrapper(nil,respBody); - return; - } - // pass: https://api.enterprise.nssurge.com/client/refresh - - NSLog(@">>>>>> [hook_dataTaskWithRequest] Allow to pass url: %@",url); - ((void(*)(id, SEL,id,id))dataTaskWithRequestIMP)(self, _cmd,request,completionHandler); -} - -- (NSString *) hook_componentsJoinedByString:arg1{ - if ([arg1 isEqualToString:@"/"] && [[self valueForKeyPath:@"@count"] isEqualToNumber:@6]) { - NSLog(@">>>>>> device info : %@",self); - // md5("1/2/3/4/5/6") = 16b8f4bdfd55d29f737427b4ec0c14d7 - return @"1/2/3/4/5/6"; - } - return ((NSString*(*)(id, SEL, NSString*))componentsJoinedByStringIMP)(self, _cmd, arg1); -} - - - - -// 这里可以伪造 public key,自签名 -- (NSData *) hk_initWithBytesNoCopy:(void *)bytes length:(NSUInteger)length freeWhenDone:(int)freeWhenDone { - // 0x1001c6638 <+380>: callq 0x1001c6b0b ; ___lldb_unnamed_symbol7189 - //-> 0x1001c663d <+385>: testb %al, %al - if (length == 0x1c3) { - NSLog(@">>>>>> hk_initWithBytesNoCopy input length: %lu", (unsigned long)length); - - // NSString *inputString = [[NSString alloc] initWithBytes:bytes length:length encoding:NSUTF8StringEncoding]; - // 替换公钥 - const char *replacementBytes = [publicKey UTF8String]; - NSUInteger replacementLength = [publicKey lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - memcpy(bytes, replacementBytes, replacementLength); - length = replacementLength; - } - NSData *ret = ((NSData*(*)(id, SEL,void *,NSUInteger,int))initWithBytesNoCopyMethodIMP)(self, _cmd,bytes,length,freeWhenDone); - return ret; -} - -- (id)hk_settings{ - NSLog(@">>>>>> hk_settings"); - - // -[SGMLicenseViewController reload]: - // call sub_1001b1647 ; sub_1001b1647 - id ret = class_createInstance(objc_getClass("SGMEnterpriseSettings"), 0); - [ret performSelector:NSSelectorFromString(@"setUserID:") withObject:[Constant G_EMAIL_ADDRESS]]; - [ret performSelector:NSSelectorFromString(@"setCompanyName:") withObject:@"Home"]; - [ret performSelector:NSSelectorFromString(@"setCompanyID:") withObject:@"CN"]; - return ret; -} -+ (id)hk_URLWithString:arg1{ - id ret = ((id(*)(id, SEL,id))urlWithStringSeletorIMP)(self, _cmd,arg1); - // >>>>>> hk_URLWithString https://www.surge-activation.com/mac/v3/init/ - if ([arg1 containsString:@"v3"]) { - NSLog(@">>>>>> hk_URLWithString [v3] %@",arg1); - } - return ret; -} - -@end diff --git a/dylib_dobby_hook/apps/iMazingHack.m b/dylib_dobby_hook/apps/iMazingHack.m deleted file mode 100644 index 53ade64..0000000 --- a/dylib_dobby_hook/apps/iMazingHack.m +++ /dev/null @@ -1,396 +0,0 @@ -// -// SurgeHack.m -// dylib_dobby_hook -// -// Created by 马治武 on 2024/4/4. -// -/* -clang++ release.c -o librelease3.dylib -dynamiclib -framework Security -framework CoreFoundation -L. -ltinycrypto -ldobby -arch x86_64 -arch arm64 -https://downloads.imazing.com/mac/iMazing/3.0.2.21053/iMazing_3.0.2.21053.dmg - id666666666666odr - imzb8636f74ac99006crd -*/ - -#import -#import "Constant.h" -#import "dobby.h" -#include -#import "MemoryUtils.h" -#import -#include -#import "HackProtocol.h" -#include -#import -#import "common_ret.h" -#include -#import -#import "encryp_utils.h" -#import -#import -#include -#include -#import -#import -#include -#include -#include -#include -#include -#import -#include - - -@interface iMazingHack : NSObject - - - -@end - - -@implementation iMazingHack - -static intptr_t MainAddr; -static OSStatus (*orig_SecRequirementCreateWithString)(CFStringRef text, SecCSFlags flags, SecRequirementRef _Nullable *requirement); -void * (* orig_SynchronousRequest)(void * selfp, const char * domain, int port, bool ssl, void * req); - -char lic_format[] = -"{" - "\"data\":{" - "\"account\":{" - "\"email\":\"marlkiller@voidm.com\"," - "\"can_purchase\":true," - "\"opt_in\":{" - "\"news_offers\":false," - "\"device_info_upload\":false," - "\"analytics\":false" - "}" - "}," - "\"license\":{" - "\"license_type\":\"business_devices_subscription\"," - "\"status\":\"active\"," - "\"seats\":{" - "\"total\":1000," - "\"available\":999" - "}" - "\"subscription_info\":{" - "\"status\":\"active\"," - "\"amount\":\"12,000\"," - "\"currency\":\"USD\"," - "\"next_bill_time\":1916064000" - "}," - "}," - "\"client\":{" - "\"imazing_client_identifier\":\"%s\"," - "\"uuid\":\"%s\"" - "}," - "\"license_auth_token\":\"%s\"," - "\"tasks_available\":false," - "\"license_token_version\":1" - "}," - "\"iat\":%d," - "\"iss\":\"account.imazing.com\"," - "\"aud\":[" - "\"%s\"" - "]," - "\"exp\":%d" -"}"; - -char manage_page[] = -"HTTP/1.1 200 Ok\r\nContent-Type: application/json\r\n\r\n" -"{" - "\"title\":\"Manage Your iMazing Subscription\"," - "\"message\":\"Your iMazing Subscription is currently managed by Antibiotics.\"" -"}"; - -char license_logout[] = "HTTP/1.1 200 Ok\r\nContent-Type: application/json\r\n\r\n{}"; - -- (NSString *)getAppName { - // >>>>>> AppName is [com.DigiDNA.iMazing3Mac],Version is [3.0.0], myAppCFBundleVersion is [20985]. - return @"com.DigiDNA.iMazing3Mac"; -} - -- (NSString *)getSupportAppVersion { - return @"3."; -} - -static int (*origin_isatty)(int code); -static void (*origin_exit)(int code); -static int (*origin_ioctl)(int code,unsigned long code2,...); - -int my_isatty(int code) { - NSLog(@">>>>>> [AntiAntiDebug] - new_isatty"); - return 0; -} - -void my_exit(int code) { - NSLog(@">>>>>> [AntiAntiDebug] - new_exit"); -} -int my_ioctl(int code,unsigned long code2,...) { - NSLog(@">>>>>> [AntiAntiDebug] - new_ioctl"); - return 1; -} - - -static void* (*orig_dlsym)(void* handle, const char* symbol); -static void* my_dlsym(void* handle, const char* symbol){ - NSLog(@">>>>>> my_dlsym symbol :%s",symbol); - if(strcmp(symbol, "ptrace") == 0){ - NSLog(@">>>>>> [AntiAntiDebug] - dlsym get ptrace symbol"); - return (void*)my_ptrace; - } - return orig_dlsym(handle, symbol); -} - - -static void* (*orig_syscall)(int code, va_list args); -static void* my_syscall(int code, va_list args){ - int request; - va_list newArgs; - va_copy(newArgs, args); - if(code == 26){ - request = (long)args; - if(request == 31){ - NSLog(@">>>>>> [AntiAntiDebug] - syscall call ptrace, and request is PT_DENY_ATTACH"); - return nil; - } - } - return (void*)orig_syscall(code, newArgs); -} -static void* (*orig_dlopen)(const char* filename, int myflags); -static void* my_dlopen(const char* filename, int myflags) { - NSLog(@">>>>>> the dlopen name :%s",filename); - return orig_dlopen(filename, myflags); -} - -- (BOOL)hack { - - NSLog(@">>>>>> iMazingHack init"); -// Error: Exception: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) - -// 标准输出文件描述符是否指向终端 -// DobbyHook((void*)isatty,(void*)&my_isatty,(void**)&origin_isatty); -// DobbyHook((void*)exit,(void*)&my_exit,(void**)&origin_exit); - // 标准输出文件描述符的窗口大小,如果为0则可能是在被调试 -// DobbyHook((void*)ioctl,(void*)&my_ioctl,(void**)&origin_ioctl); - - // 使用 dlsym 函数获取动态链接库中的函数地址 -// DobbyHook((void *)dlsym, (void *)my_dlsym, (void *)&orig_dlsym); - // 系统提供了一个系统调用函数 syscall,上面讲到的 ptrace 也是通过系统调用去实现的 -// DobbyHook((void *)syscall, (void *)my_syscall, (void *)&orig_syscall); - // 使用 dlopen 函数打开动态链接库 -// DobbyHook((void *)dlopen, (void *)my_dlopen, (void *)&orig_dlopen); - - DobbyHook((void *)ptrace, (void *)my_ptrace, (void *)&orig_ptrace); - DobbyHook((void *)sysctl, (void *)my_sysctl, (void *)&orig_sysctl); - DobbyHook((void *)task_swap_exception_ports, (void *)my_task_swap_exception_ports, (void *)&orig_task_swap_exception_ports); - DobbyHook((void *)task_get_exception_ports, (void *)my_task_get_exception_ports, (void *)&orig_task_get_exception_ports); - - -// DobbyHook((void *)SecCodeCopySelf, ret0, NULL); -// DobbyHook((void *)SecStaticCodeCreateWithPath, ret0, NULL); - DobbyHook((void *)SecCodeCheckValidity, ret0, NULL); - DobbyHook((void *)SecStaticCodeCheckValidity, ret0, NULL); - DobbyHook((void *)SecRequirementCreateWithString, hk_SecRequirementCreateWithString, (void **)&orig_SecRequirementCreateWithString); - - - MainAddr = _dyld_get_image_vmaddr_slide(0); - intptr_t SynchronousRequestAddr, VerifyJwtPkAddr; -#ifdef __aarch64__ - SynchronousRequestAddr = 0x10077d59c; // "SynchronousRequest" -> quote -> quote -> quote - VerifyJwtPkAddr = 0x100780098; // "VerifyJwtPk" -> quote -> quote -#elif __x86_64__ - SynchronousRequestAddr = 0x10084e9d0; - VerifyJwtPkAddr = 0x1008514f0; -#endif - - DobbyHook((void *)(MainAddr + SynchronousRequestAddr), (void *)hk_SynchronousRequest, (void **)&orig_SynchronousRequest); - -#ifdef __aarch64__ -char ret_1[] = {0x20, 0x00, 0x80, 0xD2, 0xC0, 0x03, 0x5F, 0xD6}; -char ret_0[] = {0x00, 0x00, 0x80, 0xD2, 0xC0, 0x03, 0x5F, 0xD6}; -#elif __x86_64__ -char ret_1[] = {0x48, 0x31, 0xC0, 0x48, 0xFF, 0xC0, 0xC3}; -char ret_0[] = {0x48, 0x31, 0xC0, 0xC3}; -#endif - - _write_memory((mach_vm_address_t)(MainAddr + VerifyJwtPkAddr), sizeof(ret_1), (vm_offset_t)ret_1); - - pthread_t ptid; - pthread_create(&ptid, NULL, &server, NULL); - - - return YES; -} - -void * server(void * arg) { - const uint16_t port_number = 9001; - int server_fd = socket(AF_INET, SOCK_STREAM, 0); - - struct sockaddr_in *server_sockaddr = init_sockaddr_in(port_number); - struct sockaddr_in *client_sockaddr = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in)); - socklen_t server_socklen = sizeof(*server_sockaddr); - socklen_t client_socklen = sizeof(*client_sockaddr); - - - if (bind(server_fd, (const struct sockaddr *) server_sockaddr, server_socklen) < 0) { - printf("Error! Bind has failed\n"); - exit(0); - } - if (listen(server_fd, 2) < 0) { - printf("Error! Can't listen\n"); - exit(0); - } - - - const size_t buffer_len = 1024; - char *buffer = (char *)malloc(buffer_len * sizeof(char)); - char *response = NULL; - - while (1) { - int client_fd = accept(server_fd, (struct sockaddr *) &client_sockaddr, &client_socklen); - - printf("Connection with `%d` has been established and delegated to the process %d.\nWaiting for a query...\n", client_fd, getpid()); - - read(client_fd, buffer, buffer_len); - printf("Received `%s`. Processing... ", buffer); - - response = process_operation(buffer); - bzero(buffer, buffer_len * sizeof(char)); - - send(client_fd, response, strlen(response), 0); - printf("Responded with `%s`. Waiting for a new query...\n", response); - free(response); - - printf("Closing session with `%d`. Bye!\n", client_fd); - close(client_fd); - } - free(buffer); - return NULL; -} - -struct sockaddr_in* init_sockaddr_in(uint16_t port_number) { - struct sockaddr_in *socket_address = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in)); - memset(socket_address, 0, sizeof(*socket_address)); - socket_address -> sin_family = AF_INET; - socket_address -> sin_addr.s_addr = htonl(INADDR_ANY); - socket_address -> sin_port = htons(port_number); - return socket_address; -} - - -char* process_operation(char *input) { - char *path = (char *)malloc(100); - char *output = (char *)malloc(100); - for (int i = 0; ; i++) { - if (*(input+i) == '\r') { - *(path+i) = '\0'; - break; - } - *(path+i) = *(input+i); - } - printf("path: '%s'\n", path); - if (strcmp(path, "GET / HTTP/1.1") == 0) { - printf("Received empty path......\n"); - strcpy(output, "HTTP/1.1 200 Ok\r\n"); - } else if (strcmp(path, "POST /v1/auth/license_auth_token_logout HTTP/1.1") == 0) { - printf("Received token_logout......\n"); - output = (char *)realloc(output, 512); - strcpy(output, license_logout); - } else if (strcmp(path, "POST /v1/licenses/send_manage_license_email HTTP/1.1") == 0) { - printf("Received manage_license......\n"); - output = (char *)realloc(output, 512); - strcpy(output, manage_page); - } else if (strcmp(path, "POST /v1/auth/license_code_login HTTP/1.1") == 0 || strcmp(path, "POST /v1/auth/license_auth_token_login HTTP/1.1") == 0){ - char *client_uuid = (char *)malloc(50); // len = 36 - char *client_identifier = (char *)malloc(50); // len = 19 - int in_len = strlen(input); - for (int i = 0; i < in_len; i++) { - unsigned cur_hash = 0, b = 267; - while (input[i] != '\n' && input[i] != ':') { - cur_hash += input[i]; - cur_hash *= b; - i++; - } - // uuid: 4137240225 - // imazing_client_identifier: 804052081 - if (cur_hash == 4137240225) { - strncpy(client_uuid, input+i+2, 36); - } else if (cur_hash == 804052081) { - strncpy(client_identifier, input+i+2, 19); - } - while (input[i] != '\0' && input[i] != '\n') i++; - } - - char *audience = (char *)malloc(100); - strcpy(audience, client_uuid); - audience[36] = '-'; - strcat(audience, client_identifier); - - unsigned char *aud_hash = (unsigned char *)malloc(50); - CC_SHA256(audience, strlen(audience), aud_hash); - free(audience); - - char *aud = (char *)malloc(100), hex_ch[] = "0123456789abcdef"; - for (int i = 0; i < 32; i++) { - aud[2*i] = hex_ch[aud_hash[i] >> 4]; - aud[2*i+1] = hex_ch[aud_hash[i] & 15]; - } - aud[64] = '\0'; - - char auth_token[] = "imzlat1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff"; - unsigned long cur_time = time(NULL); - - char *data = (char *)malloc(2048); - snprintf(data, 2048, lic_format, client_identifier, client_uuid, auth_token, cur_time, aud, cur_time+259200); - free(client_uuid), free(client_identifier), free(aud); - -// char *payload = (char *)malloc(2048); - - - NSData *inputData = [NSData dataWithBytes:data length:strlen(data)]; - NSString *base64String = [inputData base64EncodedStringWithOptions:0]; - const char *payload = [base64String UTF8String]; -// char *payload = malloc(strlen(base64Chars) + 1); -// base64_encode(strlen(data), (unsigned char *)data, payload); - - free(data); - - output = (char *)realloc(output, 3072); - snprintf(output, 3072, "HTTP/1.1 200 Ok\r\nContent-Type: application/json\r\n\r\n{\"license_token\":\"eyJhbGciOiJFZERTQSJ9.%s.abcd\"}", payload); - free(payload); - } - - free(path); - - return output; // need to free! -} - -int _write_memory(mach_vm_address_t address, mach_vm_address_t count, vm_offset_t bytes) { - int kr = 0; - kr |= mach_vm_protect(mach_task_self(), address, count, FALSE, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY); - kr |= mach_vm_write(mach_task_self(), address, bytes, count); - kr |= mach_vm_protect(mach_task_self(), address, count, FALSE, VM_PROT_READ | VM_PROT_EXECUTE); - return (kr); -} -void * hk_SynchronousRequest(void * selfp, const char * domain, int port, bool ssl, void * req) { -#ifdef debug - printf(">>>>>>>> outgoing: %s \n", domain); -#endif - if (strcmp(domain, "api.imazing.com") == 0) { -#ifdef debug - printf("======== blocking outgoing to api.imazing.com ....\n"); -#endif - return orig_SynchronousRequest(selfp, "127.0.0.1", 9001, 0, req); - } - return orig_SynchronousRequest(selfp, domain, port, ssl, req); -} - -OSStatus hk_SecRequirementCreateWithString(CFStringRef text, SecCSFlags flags, SecRequirementRef _Nullable *requirement) { - OSStatus status; - CFStringRef requirementString = CFSTR("certificate leaf = H\"B231060196247A36C3094AC947BA60E226B848BF\""); - status = orig_SecRequirementCreateWithString(requirementString, flags, requirement); - CFRelease(requirementString); - return status; -} - -@end diff --git a/script/auto_hack.sh b/script/auto_hack.sh index 4a0b2b8..c395736 100644 --- a/script/auto_hack.sh +++ b/script/auto_hack.sh @@ -54,7 +54,7 @@ hack_app "Movist Pro" "/Applications/Movist Pro.app/Contents/Frameworks/MediaKey hack_app "AirBuddy" "/Applications/AirBuddy.app/Contents/Frameworks/LetsMove.framework/Versions/A/LetsMove" hack_app "Infuse" "/Applications/Infuse.app/Contents/Frameworks/Differentiator.framework/Versions/A/Differentiator" hack_app "MacUpdater" "/Applications/MacUpdater.app/Contents/Frameworks/Sparkle.framework/Versions/B/Sparkle" +hack_app "ForkLift" "/Applications/ForkLift.app/Contents/Frameworks/UniversalDetector.framework/Versions/A/UniversalDetector" -hack_app "Surge" "/Applications/Surge.app/Contents/Frameworks/MMMarkdown.framework/Versions/A/MMMarkdown" "surge_hack.sh" diff --git a/script/surge_hack.sh b/script/surge_hack.sh deleted file mode 100644 index f2f9bbc..0000000 --- a/script/surge_hack.sh +++ /dev/null @@ -1,68 +0,0 @@ -current_path=$PWD -mac_patch_helper="$PWD/../tools/mac_patch_helper" -mac_patch_helper_config="$PWD/../tools/patch.json" -SMJobBlessUtil="$PWD/../tools/SMJobBlessUtil-python3.py" - - -sudo chmod a+x $mac_patch_helper - - - -# 修改 helper -$mac_patch_helper Surge $mac_patch_helper_config - - -surge_path="/Applications/Surge.app" -surge_helper_path="/Applications/Surge.app/Contents/Library/LaunchServices/com.nssurge.surge-mac.helper" - - -echo $SUDO_USER -sudo chmod a+rwx "$surge_helper_path" - -# 先使用lipo将help strip成单一架构的binary,否则codesign重签名后验证依然不通过(因为只签了当前架构的)? -# lipo -thin x86_64 "$surge_helper_path" -output "$surge_helper_path" -# lipo -thin x86_64 "$surge_path/Contents/MacOS/Surge" -output "$surge_path/Contents/MacOS/Surge" -# lipo -info "$surge_path" -# lipo -info "$surge_helper_path" - - -# /tmp/uninstall-surge-helper.sh -sudo /bin/launchctl unload /Library/LaunchDaemons/com.nssurge.surge-mac.helper.plist -sudo /usr/bin/killall -u root -9 com.nssurge.surge-mac.helper -sudo /bin/rm /Library/LaunchDaemons/com.nssurge.surge-mac.helper.plist -sudo /bin/rm /Library/PrivilegedHelperTools/com.nssurge.surge-mac.helper -sudo /bin/rm "~/Library/Preferences/com.nssurge.surge-mac.plist" -sudo /bin/rm "~/Library/Application Support/com.nssurge.surge-mac" - - -xattr -c '/Applications/Surge.app' -# 修改主程序的 Info.plist 的 SMPrivilegedExecutables 域并重签名 -sudo /usr/libexec/PlistBuddy -c 'Print SMPrivilegedExecutables' /Applications/Surge.app/Contents/Info.plist -sudo /usr/libexec/PlistBuddy -c "Set :SMPrivilegedExecutables:com.nssurge.surge-mac.helper \"identifier \\\"com.nssurge.surge-mac.helper\\\"\"" "/Applications/Surge.app/Contents/Info.plist" -sudo /usr/libexec/PlistBuddy -c 'Print SMPrivilegedExecutables' /Applications/Surge.app/Contents/Info.plist - -# Exception Type: EXC_CRASH (SIGKILL (Code Signature Invalid)) - -echo "codesign before" -sudo codesign -d -r- "$surge_path" -sudo codesign -d -r- "$surge_helper_path" - -sudo codesign -f -s - --all-architectures --deep "$surge_path" -sudo codesign -f -s - --all-architectures --deep "$surge_helper_path" - -echo "codesign after" -sudo codesign -d -r- "$surge_path" -sudo codesign -d -r- "$surge_helper_path" - -# python3 "$SMJobBlessUtil" check "$surge_path" - -# echo "codesign.." -# sudo codesign -f -s - --all-architectures --deep "$surge_path" -# sudo codesign -f -s - --all-architectures --deep "$surge_helper_path" -# python3 "$SMJobBlessUtil" check "$surge_path" -# /Users/voidm/Downloads/Surge.app/Contents/Library/LaunchServices/com.nssurge.surge-mac.helper: tool designated requirement malformed - - -# module and jsvm -cp -f "$PWD/surge/modules.json" "$surge_path/Contents/MacOS/" -cp -f "$PWD/surge/jsvm.js" "$surge_path/Contents/MacOS/" \ No newline at end of file diff --git a/tools/mac_patch_helper b/tools/mac_patch_helper index f415c1db39f4096165e5dfb0d52c5170b01bf88f..98c0d70f265b3973b15f4aab63532c51834e1283 100755 GIT binary patch literal 61216 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 T2T~qLc_8J1ln4I%Jn;VjnPk#( literal 43056 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