IOS multi open detection, anti multi open detection, anti anti multi open detection

  • content
  • comment
  • relevant

Generally, the way to get the bid

 NSBundle.mainBundle.bundleIdentifier;
[NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleIdentifier"];
NSBundle.mainBundle.infoDictionary[@"CFBundleIdentifier"];
[NSDictioanry dictionaryWithContentsOfFile:@"Info.plist"][@"CFBundleIdentifier"]

Theoretically hook the above methods to achieve the goal

 #include <dlfcn.h>

BOOL MUIsCallFromQQ() {
   NSArray *address = [NSThread callStackReturnAddresses];
   Dl_info info = {0};
   if(dladdr((void *)[address[2] longLongValue], &info) == 0) return NO;
   NSString *path = [NSString stringWithUTF8String:info.dli_fname];
   if ([path hasPrefix:NSBundle.mainBundle.bundlePath]) {
//Binary from the ipa package
       if ([path.lastPathComponent isEqualToString:@"MyPlugin.dylib"]) {
//Binary is the plug-in itself
           return NO;
      } else {
//Binary is QQ
           return YES;
      }
  } else {
//Binary is the system or jailbreak plug-in
       return NO;
  }
}

//Take - (NSString *) bundleIdentifier as an example
%hook NSBundle

//Take - (NSString *) bundleIdentifier as an example
%hook NSBundle
   
- (NSString *)bundleIdentifier{

   if (MUIsCallFromWeChat()) {
   NSString *str =  @"com.tencent.mqq";
   return str;
  }
   return %orig;

}

perhaps

 %hook QQAddressBookAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.

%orig;
NSDictionary *dic = [[NSBundle mainBundle]infoDictionary];
[dic setValue:@"com.tencent.mqq" forKey:@"CFBundleIdentifier"];

return YES;
}
%end

The following was copied from https://iosre.com/t/ios/11611/22

How to detect multiple opening

Through the above conclusion, due to Info.plist The unmodifiable nature of the file. We can read the value in the Info.plist file when the app is running to determine whether the value is the same as when it was produced, so as to determine whether the current process is a multi open app.

Foundation.framework Several Bundle Identifier methods are provided to obtain App, basically as follows:

 NSBundle.mainBundle.bundleIdentifier;
[NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleIdentifier"];
NSBundle.mainBundle.infoDictionary[@"CFBundleIdentifier"];
[NSDictioanry dictionaryWithContentsOfFile:@"Info.plist"][@"CFBundleIdentifier"]

Using any of the above methods, you can obtain the Bundle Identifier value of the current App, and then use the -[NSString isEqualToString:] Method to determine whether to be separated

How to de detect multiple opening

When you already know how to detect excessive opening, you can know how to prevent the app from detecting excessive opening.

To put it simply, you can kill the above methods and force them to return an original value.

1. No multiple opening detected

The specific idea is to determine whether the return value is the real Bundle Identifier. If it is, the original Bundle Identifier will be returned. The purpose of this is to prevent affecting other objects and the values corresponding to other keys.

because NSDictionary There are many ways to obtain value through key, and these methods also need to be eliminated:

 info[@"CFBundleIdentifier"]; // - [NSDictionary objectForKeyedSubscript:]
[info objectForKey:@"CFBundleIdentifier"];
[info valueForKey:@"CFBundleIdentifier"];

Here is a relatively lightweight hook tool for non jailbreak platform written by me—— MUHook 366 The macro set written in imitation of Captain Hook is more convenient and fast than Captain Hook, and CydiaSubstrate.dylib is not required. (The function is still improving)

Taking WeChat as an example, the code is as follows:

 /*
*Assume that the Bundle Identifier value of the current avatar is com. unique. xin
*/

#import <Foundation/Foundation.h>

#define CFBundleIdentifier @"CFBundleIdentifier"
#define ORIG_VALUE @"com.tencent.xin"
#define REAL_VALUE @"com.unique.xin"

/*
*The - [NSBundle bundleIdentifier] method is eliminated here
*/
MUHInstanceImplementation(NSBundle, bundleIdentifier, NSString *) {
NSString *orig = MUHOrig(NSBundle, bundleIdentifier);
if ([orig isEqualToString:REAL_VALUE]) {
orig = ORIG_VALUE;
}
return orig;
}

/*
*The - [NSBundle objectForInfoDictionaryKey:] method is eliminated here
*
*MUHInstanceImplementation annotation: define the implementation body of a hook
*The first parameter is the class to hook
*The second parameter is the method name, which will be used in the following MUHMain and MUHORig. It can be inconsistent with the actual method name, but it must be unique
*The third parameter is the return value type
*The fourth and subsequent parameters are the actual parameter table of the Hook method.
*
*If you want to hook a Class Method, such as+[UIImage imageNamed]
*Please use MUHClassImplementation, the specific usage is the same as above
*/
MUHInstanceImplementation(NSBundle, objForInfoKey, id, NSString *key) {
/*
*MUHORig comment: call the original method
*The first parameter is the class of the original method
*The second parameter is the method name
*The third and subsequent parameters are the actual parameters passed in
*/
NSString *orig = MUHOrig(NSBundle, objForInfoKey, key);
if ([key isKindOfClass:[NSString class]]) {
if ([key isEqualToString:CFBundleIdentifier]) {
           if ([orig isEqualToString:REAL_VALUE]) {
          orig = ORIG_VALUE;
          }
      }
  }
return orig;
}

MUHInstanceImplementation(NSBundle, infoDictionary, NSDictionary *) {
NSMutableDictionary *info = [MUHOrig(NSBundle, infoDictionary) mutableCopy];
if (self == NSBundle.mainBundle) {
info[CFBundleIdentifier] = REAL_VALUE;
}
return [info copy];
}

MUHInstanceImplementation(NSDictionary, objectForKey, id, NSString *key) {
id orig = MUHOrig(NSDictionary, objectForKey, key);
if ([key isKindOfClass:[NSString class]]) {
if ([key isEqualToString:CFBundleIdentifier]) {
if ([orig isEqualToString:REAL_VALUE]) {
id = ORIG_VALUE;
}
}
}
return orig
}

MUHInstanceImplementation(NSDictionary, valueForKey, id, NSString *key) {
id orig = MUHOrig(NSDictionary, valueForKey, key);
if ([key isKindOfClass:[NSString class]]) {
if ([key isEqualToString:CFBundleIdentifier]) {
if ([orig isEqualToString:REAL_VALUE]) {
id = ORIG_VALUE;
}
}
}
return orig
}

MUHInstanceImplementation(NSDictionary, objectForKeyedSubscript, id, NSString *key) {
id orig = MUHOrig(NSDictionary, objectForKeyedSubscript, key);
if ([key isKindOfClass:[NSString class]]) {
if ([key isEqualToString:CFBundleIdentifier]) {
if ([orig isEqualToString:REAL_VALUE]) {
id = ORIG_VALUE;
}
}
}
return orig
}


/*
*MUHMain comment: define a function with the constructor attribute.
*When Dyld loads this binary file into memory, it will automatically call this function to complete the hook work before running
*/
void MUHMain() {
/*
*MUHHookInstanceMessage Note: Make an instance hook implementation defined above effective
*The first parameter is the class of the above implementation body
*The second parameter is the method name obtained by the above implementation body
*The third parameter is the corresponding SEL
*
*If you want a Class Hook to take effect, you can use
* MUHHookClassMessage()
*Same as above
*/
   MUHHookInstanceMessage(NSBundle, bundleIdentifier, bundleIdentifier);
MUHHookInstanceMessage(NSBundle, infoDictionary, infoDictionary);
   MUHHookInstanceMessage(NSBundle, objectForInfoDictionaryKey, objectForInfoDictionaryKey:);
MUHHookInstanceMessage(NSDictionary, objectForKey, objectForKey:);
MUHHookInstanceMessage(NSDictionary, valueForKey, valueForKey:);
MUHHookInstanceMessage(NSDictionary, objectForKeyedSubscript, objectForKeyedSubscript:);
}

At the same time, WeChat can also use IDFA and DeviceName to determine whether the same device logs in to different WeChat, so the following two methods should also be eliminated:

 #import <UIKit/UIKit.h>
#import <AdSupport/AdSupport.h>

MUHInstanceImplementation(ASIdentifierManager, advertisingIdentifier, NSUUID *) {
NSString *idfa = [userDefaults stringForKey:@"com.unique.idfa"];
if(! idfa)
{
idfa = [NSUUID UUID]. UUIDString;
[userDefaults setObject:idfa forKey:@"com.unique.idfa"];
[userDefaults synchronize];
}
NSUUID *udid = [[NSUUID alloc] initWithUUIDString:idfa];
return udid;
}

/*
*Forcibly return a popular name that is not easily suspected
*/
MUHInstanceImplementation(UIDevice, name, NSString *) {
return @"iPhone";
}

void MUHMain() {
MUHHookInstanceMessage(ASIdentifierManager, advertisingIdentifier, advertisingIdentifier);
MUHHookInstanceMessage(UIDevice, name, name);
}

2. Only make WeChat unable to detect multiple opening

As I write here, the calls to NSBundle are basically eliminated. But there is a potential problem.

When the app is running, in addition to the WeChat main binary file, the binaries loaded into the memory include the WeChat built-in framework, the system framework used by WeChat, and the plug-in itself dylib.

However, we cannot guarantee whether or when the system framework will call NSBundle related methods. If the system framework calls relevant methods and gets a fake Bundle ID, unexpected problems may occur, or even bugs that cannot find the problem may occur. Therefore, we must ensure that if the method is called by the system, the real Bundle ID will be returned.

At the same time, if the plug-in itself wants to obtain the Bundle ID, it should also return a real Bundle ID.

So we put forward the demand: If it is a method called by WeChat, it returns a false value, otherwise it returns a true value.

We can use Dyld's dladdr() The function is judged according to the current call stack address caller From which binary file. The specific codes are as follows:

 #include <dlfcn.h>

BOOL MUIsCallFromWeChat() {
NSArray *address = [NSThread callStackReturnAddresses];
Dl_info info = {0};
if(dladdr((void *)[address[2] longLongValue], &info) == 0) return NO;
NSString *path = [NSString stringWithUTF8String:info.dli_fname];
if ([path hasPrefix:NSBundle.mainBundle.bundlePath]) {
//Binary from the ipa package
if ([path.lastPathComponent isEqualToString:@"MyPlugin.dylib"]) {
//Binary is the plug-in itself
  return NO;
  } else {
//Binary is WeChat
  return YES;
  }
} else {
//Binary is the system or jailbreak plug-in
  return NO;
}
}

//Take - [NSBundle bundleIdentifier] as an example, and extend the rest by yourself
MUHInstanceImplementation(NSBundle, bundleIdentifier, NSString *) {
NSString *orig = MUHOrig(NSBundle, bundleIdentifier);
if (MUIsCallFromWeChat() == NO) {
return orig;
}
if ([orig isEqualToString:REAL_VALUE]) {
orig = ORIG_VALUE;
}
return orig;
}

How to cancel anti detection multiple opening

1. Use CoreFoundation detection (not necessarily reliable)

And Foundation Correspondingly, CoreFoundation There is also a set of CFBundleRef C language API of the operation. Most manufacturers generally use NSBundle To get the Bundle ID. So you can use this set of C language APIs to obtain the CFBundleIdentifier and then determine whether to open more.

However, since this set of APIs is open to Apple after all, plug-ins can still be used fishhook 33 To complete the hook of the C function. So I think this method It's not totally reliable

2. Use Appex and Watch for detection (not necessarily reliable)

Due to iOS regulations, the prefix of the Bundle ID of all PlugIn and Watch Apps must be the same as the Bundle ID of mainBundle, so if the Bundle ID of mainBundle is changed for more open apps, the Bundle ID of PlugIns and Watch Apps should also be modified.

Therefore, you can determine whether the mainBundle has been modified by obtaining the Bundle ID of PlugIn and Watch App.

But ordinary WeChat plug-ins delete the PlugIns and Watch folders, so this method is not very reliable.

3. Use FILE+encryption+confusion detection (I think it's reliable)

Whether to open more or not is finally judged Info.plist In file CFBundleIdentifier Whether the value is modified. The above methods to obtain this value are all known through the API provided by Apple. In fact, you can implement a set of acquisition methods by yourself, bypass the familiar API, and then perform some string encryption and code obfuscation to achieve a more secure multi open detection method.

 #Define EncryptStr (str) str//Encryption algorithm
#Define DecryptStr (str) str//Decryption algorithm

static BOOL ZheBuShiYiGeDuoKaiJianCe()
   int result = 1;
NSString * filePath=[NSBundle. mainBundle. bundlePath stringByAppendingPathComponent: DecryptStr (@ "Write dead encrypted Info. plist here");
   NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfFile:filePath];
   filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"temp.txt"];
  [dictionary writeToFile:filePath atomically:YES];
//I don't know why the Info.plist in the Demo cannot be read directly by UTF8. It must be read and written by the dictionary.
//There may be a vulnerability here. You can directly hook - [NSDictionary dictionary WithContentsOfFile:]
//But I don't know why I can't read it directly. If God knows, he can guide me.
   
   unsigned long long size = [NSFileManager.defaultManager attributesOfItemAtPath:filePath error:nil].fileSize;
   FILE *file = fopen(filePath.UTF8String, "r");
   if (file != NULL) {
       char *content = malloc(size + 1); memset(content, 0, size + 1);
       fread(content, size, sizeof(char), file);
Char * beginIndex=strstr (content, DecryptStr ("write CFBundleIdentifier of dead encryption here"));
BeginIndex=strstr (beginIndex, DecryptStr ("write the encrypted<string>here"))+8;
       if (beginIndex != NULL) {
Char * endIndex=strstr (beginIndex, DecryptStr ("write here for dead encryption</string>"));
           if (endIndex != NULL) {
               unsigned long bidsize = endIndex - beginIndex;
               char *endbid = malloc(bidsize + 1); memset(content, 0, bidsize + 1);
               strlcpy(endbid, beginIndex, bidsize + 1);
Result=strcmp (EncryptStr (endbid), "Write the dead encrypted BundleID here");
//The encrypted BundleID is directly compared here, because strcmp may also be hooked.
               free(endbid);
          }
      }
       free(content);
       beginIndex = content = NULL;
       fclose(file);
  }
   printf("%d\n", result);
   return result == 0;
}

comment

zero Comments

Post reply

Your email address will not be disclosed. Required items have been used * tagging