// // Tweak.x // 主入口文件 - 包含所有 Hooks // #import #import #import #import #import #import #import #import #import #import #include #import #import #import #import #import "XSLog.h" #import "XHookSettingsManager.h" // 设置日志 void SetupXSLog() { XSLogSetupLogWithFilePath(@"/var/mobile/Documents/App.log", 50 * 1024 * 1024); XSLogRedirectNSLog(); } #pragma mark - ============== HOOKS GROUP ============== %group XHooks #pragma mark - ATTrackingManager Hook %hook ATTrackingManager + (ATTrackingManagerAuthorizationStatus)trackingAuthorizationStatus { ATTrackingManagerAuthorizationStatus originalStatus = %orig; NSUInteger status = [XHookSettings trackingStatus]; return (ATTrackingManagerAuthorizationStatus)status; } + (void)requestTrackingAuthorizationWithCompletionHandler:(void (^)(ATTrackingManagerAuthorizationStatus status))completion { ATTrackingManagerAuthorizationStatus attStatus = (ATTrackingManagerAuthorizationStatus)[XHookSettings trackingStatus]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (completion) { completion(attStatus); } }); } %end #pragma mark - ASIdentifierManager Hook %hook ASIdentifierManager - (NSUUID *)advertisingIdentifier { NSUUID *idfa = [XHookSettings idfa]; return idfa ?: %orig; } - (BOOL)isAdvertisingTrackingEnabled { NSUInteger status = [XHookSettings trackingStatus]; return (status >= 3); } %end #pragma mark - UIDevice Hook %hook UIDevice - (NSUUID *)identifierForVendor { NSUUID *idfv = [XHookSettings idfv]; return idfv ?: %orig; } - (NSString *)name { NSString *name = [XHookSettings deviceName]; return name ?: %orig; } - (NSString *)model { return %orig; } - (NSString *)localizedModel { return %orig; } - (NSString *)systemName { return %orig; } - (NSString *)systemVersion { NSString *version = [XHookSettings osVersion]; return version ?: %orig; } - (UIDeviceBatteryState)batteryState { UIDeviceBatteryState state = [XHookSettings batteryState]; return (state != UIDeviceBatteryStateUnknown) ? state : %orig; } - (float)batteryLevel { float level = [XHookSettings batteryLevel]; return (level > -1.0f) ? level : %orig; } - (UIDeviceOrientation)orientation { return UIDeviceOrientationPortrait; } %end #pragma mark - NSProcessInfo Hook %hook NSProcessInfo - (NSTimeInterval)systemUptime { NSTimeInterval res = %orig; double offset = [XHookSettings systemUptimeOffset]; if (offset > -1.0) { res += offset; } return res; } - (unsigned long long)physicalMemory { unsigned long long memory = [XHookSettings physicalMemory]; return (memory > 1000) ? memory : %orig; } - (NSUInteger)processorCount { NSUInteger cores = [XHookSettings cpuCore]; return (cores > 0) ? cores : %orig; } - (NSOperatingSystemVersion)operatingSystemVersion { NSOperatingSystemVersion version = %orig; NSString *versionString = [XHookSettings osVersion]; if (!versionString || ![versionString length]) { return version; } NSArray *components = [versionString componentsSeparatedByString:@"."]; if (components.count == 0 || components.count > 3) { return version; } for (NSString *component in components) { if (![[NSScanner scannerWithString:component] scanInteger:NULL]) { return version; } } version.majorVersion = [components[0] integerValue]; version.minorVersion = components.count > 1 ? [components[1] integerValue] : 0; version.patchVersion = components.count > 2 ? [components[2] integerValue] : 0; return version; } %end #pragma mark - UIWindow Hook %hook UIWindow - (UIEdgeInsets)safeAreaInsets { UIEdgeInsets originalInsets = %orig; NSDictionary *dic = [XHookSettings safeAreaInsets]; if (dic) { CGFloat top = [dic[@"top"] doubleValue]; CGFloat bottom = [dic[@"bottom"] doubleValue]; originalInsets = UIEdgeInsetsMake(top, 0, bottom, 0); } return originalInsets; } %end #pragma mark - UIScreen Hook %hook UIScreen - (CGFloat)brightness { CGFloat brightness = [XHookSettings screenBrightness]; return (brightness > -1.0) ? brightness : %orig; } - (CGRect)bounds { NSDictionary *dic = [XHookSettings screenBounds]; if (dic) { double w = [dic[@"x"] doubleValue]; double h = [dic[@"y"] doubleValue]; return CGRectMake(0, 0, w, h); } return %orig; } - (CGFloat)scale { CGFloat scale = [XHookSettings screenScale]; return (scale > -1.0) ? scale : %orig; } - (CGRect)nativeBounds { NSDictionary *dic = [XHookSettings screenBounds]; if (dic) { double w = [dic[@"nx"] doubleValue]; double h = [dic[@"ny"] doubleValue]; return CGRectMake(0, 0, w, h); } return %orig; } - (BOOL)isCaptured { return NO; } - (UIScreen *)mirroredScreen { return nil; } %end #pragma mark - UIScreenMode Hook %hook UIScreenMode - (CGSize)size { NSDictionary *dic = [XHookSettings screenBounds]; if (dic) { double w = [dic[@"x"] doubleValue]; double h = [dic[@"y"] doubleValue]; return CGSizeMake(w, h); } return %orig; } %end #pragma mark - UIStatusBarManager Hook %hook UIStatusBarManager - (CGRect)statusBarFrame { CGRect originalFrame = %orig; NSDictionary *dic = [XHookSettings safeAreaInsets]; if (dic && dic[@"height"]) { double barHeight = [dic[@"height"] doubleValue]; originalFrame.size.height = barHeight; } return originalFrame; } %end #pragma mark - AVAudioSession Hook %hook AVAudioSession - (float)outputVolume { float volume = [XHookSettings outputVolume]; return (volume > -1.0f) ? volume : %orig; } %end #pragma mark - NSFileManager Hook %hook NSFileManager - (NSDictionary *)attributesOfFileSystemForPath:(NSString *)path error:(NSError **)error { NSDictionary *res = %orig; if ([path isEqualToString:NSHomeDirectory()]) { NSMutableDictionary *modifiedAttributes = [res mutableCopy]; unsigned long long diskSize = [XHookSettings diskSize]; if (diskSize > 0) { modifiedAttributes[NSFileSystemSize] = @(diskSize); } unsigned long long diskFreeSize = [XHookSettings diskFreeSize]; if (diskFreeSize > 0) { modifiedAttributes[NSFileSystemFreeSize] = @(diskFreeSize); } return [modifiedAttributes copy]; } return res; } - (BOOL)fileExistsAtPath:(NSString *)event { NSArray *array = @[@"/Application/Cydia.app", @"/Application/Sileo.app", @"/usr/lib/TweakInject", @"/Library/TweakInject", @"/Library/MobileSubstrate/MobileSubstrate.dylib", @"/bin/bash", @"/usr/sbin/sshd", @"/etc/apt", @"/usr/bin/ssh", @"/private/var/lib/apt", @"/private/var/lib/cydia", @"/private/var/tmp/cydia.log", @"/Applications/WinterBoard.app", @"/var/lib/cydia", @"/private/etc/dpkg/origins/debian", @"/bin.sh", @"/private/etc/apt", @"/etc/ssh/sshd_config", @"/private/etc/ssh/sshd_config", @"/Applications/SBSetttings.app", @"/private/var/mobileLibrary/SBSettingsThemes/", @"/private/var/stash", @"/usr/libexec/sftp-server", @"/usr/libexec/cydia/", @"/usr/sbin/frida-server", @"/usr/bin/cycript", @"/usr/local/bin/cycript", @"/usr/lib/libcycript.dylib", @"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist", @"/System/Library/LaunchDaemons/com.ikey.bbot.plist", @"/Applications/FakeCarrier.app", @"/Library/MobileSubstrate/DynamicLibraries/Veency.plist", @"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist", @"/usr/libexec/ssh-keysign", @"/usr/libexec/sftp-server", @"/Applications/blackra1n.app", @"/Applications/IntelliScreen.app", @"/Applications/Snoop-itConfig.app", @"/var/lib/dpkg/info", @"/User/Applications"]; NSInteger index = [array indexOfObjectPassingTest:^BOOL(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { BOOL ret = [event hasPrefix:obj]; if (ret) { *stop = YES; } return ret; }]; if (index != NSNotFound) { return NO; } return %orig; } %end #pragma mark - NSLocale Hook %hook NSLocale + (NSArray *)preferredLanguages { return @[@"en-US"]; } + (NSLocale *)currentLocale { NSString *loc = [XHookSettings locale]; if (loc) { return [[NSLocale alloc] initWithLocaleIdentifier:loc]; } return %orig; } + (NSLocale *)systemLocale { NSString *loc = [XHookSettings locale]; if (loc) { return [[NSLocale alloc] initWithLocaleIdentifier:loc]; } return %orig; } %end #pragma mark - NSTimeZone Hook %hook NSTimeZone + (NSTimeZone *)localTimeZone { NSString *tz = [XHookSettings timeZone]; if (tz) { return [NSTimeZone timeZoneWithName:tz]; } return %orig; } + (NSTimeZone *)systemTimeZone { NSString *tz = [XHookSettings timeZone]; if (tz) { return [NSTimeZone timeZoneWithName:tz]; } return %orig; } + (NSTimeZone *)defaultTimeZone { NSString *tz = [XHookSettings timeZone]; if (tz) { return [NSTimeZone timeZoneWithName:tz]; } return %orig; } %end #pragma mark - NSMutableURLRequest Hook %hook NSMutableURLRequest - (instancetype)initWithURL:(NSURL *)URL { self = %orig(URL); if (self) { NSString *userAgent = [XHookSettings userAgent]; if (userAgent) { [self setValue:userAgent forHTTPHeaderField:@"User-Agent"]; } } return self; } - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field { NSString *finalValue = value; if ([field isEqualToString:@"User-Agent"]) { NSString *userAgent = [XHookSettings userAgent]; if (userAgent) { finalValue = userAgent; } } %orig(finalValue, field); } %end #pragma mark - NSURLSession Hook %hook NSURLSession + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration { NSString *userAgent = [XHookSettings userAgent]; if (userAgent) { configuration.HTTPAdditionalHeaders = @{@"User-Agent": userAgent}; } return %orig; } - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler { NSMutableURLRequest *mutableRequest = [request mutableCopy]; NSString *userAgent = [XHookSettings userAgent]; if (userAgent) { [mutableRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"]; } return %orig(mutableRequest, completionHandler); } %end #pragma mark - WKWebView Hook %hook WKWebView - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *error))completionHandler { NSString *userAgent = [XHookSettings userAgent]; if (userAgent) { [self setCustomUserAgent:userAgent]; } %orig(javaScriptString, completionHandler); } - (void)setCustomUserAgent:(NSString *)userAgent { NSString *customUA = [XHookSettings userAgent]; %orig(customUA ?: userAgent); } - (NSString *)customUserAgent { NSString *userAgent = [XHookSettings userAgent]; return userAgent ?: %orig; } %end #pragma mark - CTTelephonyNetworkInfo Hook %hook CTTelephonyNetworkInfo - (NSDictionary *)serviceCurrentRadioAccessTechnology { NSDictionary *originalTechnologies = %orig; NSDictionary *simInfoDic = [XHookSettings simInfo]; if (!simInfoDic) { return originalTechnologies; } NSMutableDictionary *modifiedProviders = [[NSMutableDictionary alloc] init]; for (NSString *service in simInfoDic.allKeys) { NSDictionary *dic = simInfoDic[service]; NSString *wwan = dic[@"wwan"]; if ([wwan isEqual:@"5G"]) { modifiedProviders[service] = @"CTRadioAccessTechnologyNR"; } else { modifiedProviders[service] = @"CTRadioAccessTechnologyLTE"; } } return [modifiedProviders count] > 0 ? [modifiedProviders copy] : originalTechnologies; } - (NSDictionary *)serviceSubscriberCellularProviders { NSDictionary *originalProviders = %orig; NSDictionary *simInfoDic = [XHookSettings simInfo]; if (!simInfoDic) { return originalProviders; } NSMutableDictionary *modifiedProviders = [[NSMutableDictionary alloc] init]; for (NSString *service in simInfoDic.allKeys) { NSDictionary *dic = simInfoDic[service]; if (!dic) continue; if (dic[@"id"] && ![dic[@"id"] isEqual:[NSNull null]]) { [[NSUserDefaults standardUserDefaults] setValue:dic[@"id"] forKey:@"XHook-carrier-id"]; } CTCarrier *carrier = [[CTCarrier alloc] init]; [carrier setValue:dic[@"carrierName"] forKey:@"carrierName"]; [carrier setValue:dic[@"mobileCountryCode"] forKey:@"mobileCountryCode"]; [carrier setValue:dic[@"mobileNetworkCode"] forKey:@"mobileNetworkCode"]; [carrier setValue:dic[@"isoCountryCode"] forKey:@"isoCountryCode"]; modifiedProviders[service] = carrier; } return [modifiedProviders count] > 0 ? [modifiedProviders copy] : originalProviders; } %end #pragma mark - CMMotionManager Hook (陀螺仪) static double randomRadiansPerSecond() { double randomValue = (double)arc4random() / UINT32_MAX; return (randomValue * 20.0) - 10.0; } @interface CustomCMGyroData : CMGyroData @property (nonatomic, assign) CMRotationRate customRotationRate; @end @implementation CustomCMGyroData - (CMRotationRate)rotationRate { return self.customRotationRate; } @end %hook CMMotionManager - (void)startGyroUpdatesToQueue:(NSOperationQueue *)queue withHandler:(CMGyroHandler)handler { if (!queue || !handler) return %orig; CMGyroHandler newHandler = ^(CMGyroData *gyroData, NSError *error) { CMRotationRate randomRate; randomRate.x = randomRadiansPerSecond(); randomRate.y = randomRadiansPerSecond(); randomRate.z = randomRadiansPerSecond(); CustomCMGyroData *customGyroData = (CustomCMGyroData *)[[objc_getClass("CMGyroData") alloc] init]; customGyroData.customRotationRate = randomRate; if (handler) { handler((CMGyroData *)customGyroData, error); } }; %orig(queue, newHandler); } %end #pragma mark - C Function Hooks %hookf(int, uname, struct utsname *systemInfo) { int nRet = %orig; NSString *productStr = [XHookSettings productStr]; if (productStr) { char str_machine_name[100]; [productStr getCString:str_machine_name maxLength:100 encoding:NSUTF8StringEncoding]; strcpy(systemInfo->machine, str_machine_name); } return nRet; } %hookf(int, sysctl, const int *name, u_int namelen, void *oldp, size_t *oldlenp, const void *newp, size_t newlen) { if (namelen < 2) { return %orig; } if (name[0] == CTL_KERN && name[1] == KERN_BOOTTIME) { int nRet = %orig; if (nRet == 0 && oldp && oldlenp && *oldlenp >= sizeof(struct timeval)) { struct timeval *a = (struct timeval *)oldp; NSInteger kernBootTimeOffset = [XHookSettings kernBootTimeOffset]; if (kernBootTimeOffset != 0) { a->tv_sec += kernBootTimeOffset; } } return nRet; } else if (name[0] == CTL_HW && name[1] == HW_MACHINE) { NSString *modelName = [XHookSettings productStr]; const char *spoofedModel = [modelName UTF8String]; if (!spoofedModel) { return %orig; } size_t modelLen = strlen(spoofedModel) + 1; if (!oldp && oldlenp) { *oldlenp = modelLen; return 0; } if (oldp && oldlenp) { if (*oldlenp < modelLen) { return ENOMEM; } strlcpy((char *)oldp, spoofedModel, *oldlenp); *oldlenp = modelLen; return 0; } return EINVAL; } return %orig; } %hookf(int, sysctlbyname, const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { if (strcmp(name, "hw.machine") == 0) { int ret = %orig; NSString *machine = [XHookSettings productStr]; if (oldp && machine) { const char *hwMachineCh = [machine UTF8String]; if (hwMachineCh) { size_t len = strlen(hwMachineCh); if (*oldlenp > len) { strlcpy((char *)oldp, hwMachineCh, *oldlenp); *oldlenp = len; } } } return ret; } else if (strcmp(name, "hw.model") == 0) { int ret = %orig; NSString *modelStr = [XHookSettings hwModel]; if (oldp && modelStr) { const char *modelStrCh = [modelStr UTF8String]; if (modelStrCh) { size_t len = strlen(modelStrCh); if (*oldlenp > len) { strlcpy((char *)oldp, modelStrCh, *oldlenp); *oldlenp = len; } } } return ret; } return %orig; } %hookf(int, clock_gettime, clockid_t __clock_id, struct timespec *__tp) { int nret = %orig; if(__clock_id == CLOCK_MONOTONIC_RAW) { // Reserved } return nret; } #pragma mark - Network Interface Helpers static struct ifaddrs* createInterface(const char* name, const char* ipv4, const char* ipv6) { if (!name || !ipv4 || !ipv6) return NULL; @try { struct ifaddrs *new_ifaddr = (struct ifaddrs *)calloc(1, sizeof(struct ifaddrs)); if (!new_ifaddr) return NULL; new_ifaddr->ifa_name = strdup(name); if (!new_ifaddr->ifa_name) { free(new_ifaddr); return NULL; } new_ifaddr->ifa_flags = IFF_UP | IFF_RUNNING; struct sockaddr_in *addr_in = (struct sockaddr_in *)calloc(1, sizeof(struct sockaddr_in)); if (!addr_in) { free(new_ifaddr->ifa_name); free(new_ifaddr); return NULL; } addr_in->sin_family = AF_INET; if (inet_pton(AF_INET, ipv4, &addr_in->sin_addr) != 1) { free(addr_in); free(new_ifaddr->ifa_name); free(new_ifaddr); return NULL; } new_ifaddr->ifa_addr = (struct sockaddr *)addr_in; struct ifaddrs *next_ifaddr = (struct ifaddrs *)calloc(1, sizeof(struct ifaddrs)); if (!next_ifaddr) { free(addr_in); free(new_ifaddr->ifa_name); free(new_ifaddr); return NULL; } next_ifaddr->ifa_name = strdup(name); if (!next_ifaddr->ifa_name) { free(next_ifaddr); free(addr_in); free(new_ifaddr->ifa_name); free(new_ifaddr); return NULL; } next_ifaddr->ifa_flags = IFF_UP | IFF_RUNNING; struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)calloc(1, sizeof(struct sockaddr_in6)); if (!addr_in6) { free(next_ifaddr->ifa_name); free(next_ifaddr); free(addr_in); free(new_ifaddr->ifa_name); free(new_ifaddr); return NULL; } addr_in6->sin6_family = AF_INET6; if (inet_pton(AF_INET6, ipv6, &addr_in6->sin6_addr) != 1) { free(addr_in6); free(next_ifaddr->ifa_name); free(next_ifaddr); free(addr_in); free(new_ifaddr->ifa_name); free(new_ifaddr); return NULL; } next_ifaddr->ifa_addr = (struct sockaddr *)addr_in6; new_ifaddr->ifa_next = next_ifaddr; return new_ifaddr; } @catch (NSException *exception) { return NULL; } } static void modifyInterfaceIP(struct ifaddrs *interface, const char *ipv4, const char *ipv6) { if (!interface || !ipv4 || !ipv6) return; @try { if (interface->ifa_addr) { if (((struct sockaddr_in*)interface->ifa_addr)->sin_family == AF_INET) { struct sockaddr_in *addr = (struct sockaddr_in*)interface->ifa_addr; inet_pton(AF_INET, ipv4, &addr->sin_addr); } else if (((struct sockaddr_in*)interface->ifa_addr)->sin_family == AF_INET6) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6*)interface->ifa_addr; inet_pton(AF_INET6, ipv6, &addr6->sin6_addr); } } } @catch (NSException *exception) { } } %hookf(int, getifaddrs, struct ifaddrs **interfaces) { if (!interfaces) return EINVAL; int nRet = %orig; if (nRet != 0) return nRet; @try { NSDictionary *networkConfig = [XHookSettings networkInterfacesInfo]; if (!networkConfig) { return nRet; } struct ifaddrs *interface; bool isHookEn0IpV4 = false; for (interface = *interfaces; interface; interface = interface->ifa_next) { if (!interface->ifa_name) continue; NSString *name = @(interface->ifa_name); if ([name isEqualToString:@"en0"]) { NSDictionary *en0Config = networkConfig[@"en0"]; if (en0Config) { const char *ipv4 = [en0Config[@"ipv4"] UTF8String]; const char *ipv6 = [en0Config[@"ipv6"] UTF8String]; if (ipv4 && ipv6) { modifyInterfaceIP(interface, ipv4, ipv6); isHookEn0IpV4 = true; } } } } if (!isHookEn0IpV4) { NSDictionary *en0Config = networkConfig[@"en0"]; if (en0Config) { const char *ipv4 = [en0Config[@"ipv4"] UTF8String]; const char *ipv6 = [en0Config[@"ipv6"] UTF8String]; struct ifaddrs *new_en0 = createInterface("en0", ipv4, ipv6); if (new_en0) { new_en0->ifa_next->ifa_next = *interfaces; *interfaces = new_en0; } } } NSDictionary *pdpConfig = networkConfig[@"pdp_ip0"]; if (pdpConfig) { const char *ipv4 = [pdpConfig[@"ipv4"] UTF8String]; const char *ipv6 = [pdpConfig[@"ipv6"] UTF8String]; struct ifaddrs *new_pdp = createInterface("pdp_ip0", ipv4, ipv6); if (new_pdp) { new_pdp->ifa_next->ifa_next = *interfaces; *interfaces = new_pdp; } } } @catch (NSException *exception) { } return nRet; } %hookf(Boolean, SCNetworkReachabilityGetFlags, SCNetworkReachabilityRef target, SCNetworkReachabilityFlags *flags) { Boolean result = %orig(target, flags); if (result && flags != NULL) { *flags |= kSCNetworkReachabilityFlagsReachable; *flags |= kSCNetworkReachabilityFlagsIsWWAN; } return result; } %hookf(BOOL, UIAccessibilityIsClosedCaptioningEnabled) { return YES; } %hookf(BOOL, UIAccessibilityIsMonoAudioEnabled) { return YES; } %end // end of %group XHooks #pragma mark - ============== CONSTRUCTOR & DESTRUCTOR ============== %ctor { NSLog(@"XHook: Starting..."); @try { @autoreleasepool { NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; NSString *jsonPath = @"/var/mobile/Documents/changeapp.json"; BOOL loaded = [XHookSettings loadSettingsFromPath:jsonPath]; if (!loaded) { NSLog(@"XHook: Failed to load settings from %@", jsonPath); return; } if ([XHookSettings shouldHookApp:bundleIdentifier]) { NSUUID *idfa = [XHookSettings idfa]; SetupXSLog(); NSLog(@"XHook: Hooks started for %@; IDFA: %@", bundleIdentifier, idfa); [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"kXHookSSFaceKey"]; %init(XHooks); } else { NSLog(@"XHook: App %@ not in hook list, skipping", bundleIdentifier); } } } @catch (NSException *exception) { NSLog(@"XHook: Error in ctor: %@", exception); } } %dtor { NSLog(@"XHook: Cleaning up..."); [XHookSettings cleanup]; }