IOS+OSX逆向去除应用Bundleid校验

桃仙
6 min readDec 12, 2022

--

在 iOS 设备上安装多个同一个 App 的方式只有一种,修改 Info.plist 中的 CFBundleIdentifier

在整个 iOS 生态中,Bundle Identifier 是作为一个 App 的唯一标识符存在。

对于拥有相同的 Bundle Identifier 的 App,无论 Binary 和资源文件都多大的差异,iOS 都会将它们视为同一个 App。

对于拥有不同的 Bundle Identifier 的 App,也无论 Binary 与和资源文件是否一致,iOS 会将它们视为不同 App。

Info.plist 文件,是整个 App 的信息、配置、权限的信息整合文件,其在 App 中起到至关重要的作用。

为了防止 Info.plist 被恶意篡改,iOS 提供一种数字签名技术。通过该技术,计算出 Info.plist 文件的 Hash 值,加密后存入到签名文件中。在安装时与安装后,可通过该签名文件存的 Hash 值进行文件签名校验。也因此,App 签名后无法修改 Info.plist 文件;而即使是已安装 App 的 Info.plist 文件,修改后也会导致 App 闪退。

所以可以得出结论,对于已安装的 App 的 Info.plist 文件的 CFBundleIdentifier 值不会被修改

通过上述结论,由于 Info.plist 文件的不可修改性质,我们可以在 App 运行时来读取 Info.plist 文件中的值来判断该值是否与出产时候相同,从而判断当前进程是否是一个多开 App。

Foundation.framework 提供了几种获取 App 的 Bundle Identifier 方法,基本如下:

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

使用上面几种的任意一种,就可以获取到当前 App 的 Bundle Identifier 值,之后通过 -[NSString isEqualToString:] 方法来判断是否分身。

1.重签名

苹果钥匙串访问获取证书

对QQ进行解包

主要修改Info.plst文件,修改BundleIdentifier为任意值,并且修改qq.app为uu.app 在Info.plst修改对应值

codesign -f -s "iPhone Developer: Ding Ge (PZDFM6CK9C)" uu.app --重签名

登录后提示AppID验证失败,QQ做了Bundleid的校验 如果校验不对会登录失败

2.Hook

如何反检测多开

在已经知道如何检测多开的时候,就可以知道如何防止 App 检测多开了。

简单的来说,就是干掉上面的几个方法,强制返回一个原来的值即可。

检测不到多开

具体思路是判断返回值是不是真实的 Bundle Identifier,如果是则返回原来的 Bundle Identifier。这样做的目的防止影响到别的对象以及别的 key 对应的值。

3.可能用到的工具

  1. Theos
  2. optool/insert_dylib
  3. unsign (optional)
%config(generator=internal)
#include <dlfcn.h>
// You don't need to #include <substrate.h>, it will be done automatically, as will
// the generation of a class list and an automatic constructor.
#import <Foundation/Foundation.h>
// 以 - (NSString *)bundleIdentifier 为例
%hook NSBundle
- (NSString *)bundleIdentifier{
NSString *str = @"com.tencent.qq";
NSArray *address = [NSThread callStackReturnAddresses];
NSDictionary *dic = [[NSBundle mainBundle]infoDictionary];
[dic setValue:@"com.tencent.qq" forKey:@"CFBundleIdentifier"];
Dl_info info = {0};
if(dladdr((void *)[address[2] longLongValue], &info) == 0) return %orig;
NSString *path = [NSString stringWithUTF8String:info.dli_fname];
if ([path hasPrefix:NSBundle.mainBundle.bundlePath]) {
NSLog(@"!!!!!!!!!!!!!");
return str;
} else {
// 二进制是系统或者越狱插件
NSLog(@"!!!!!!系统!!!!!!");
return %orig;
}
}
%end

在 App 运行时,除微信主二进制文件外,随着被加载到内存中的二进制还有:微信内置 Framework,微信所用到的系统 Framework,插件自身 dylib。而我们并不能保证系统 Framework 是否会调用、何时会调用 NSBundle 相关方法。如果系统 Framework 调用了相关方法,得到了假的 Bundle ID,则有可能出现无法预计的问题,甚至是出现了也找不到问题的bug。所以我们必须保证如果是系统调用的方法,要返回真实的 Bundle ID。同时,如果插件自身想要获取 Bundle ID,也应该要返回一个真实的 Bundle ID。于是提出需求:如果是微信调用的方法,返回假值,否则返回真值。我们可以通过 dyld 的 dladdr() 函数配合当前调用栈地址来判断调用者来自哪个二进制文件

将以上代码保存为一个 Tweak.xm 文件(名字后缀名随意),放在与uu.app 同级目录下,便于后续操作。

--

--