您的位置:首页 > 移动开发 > IOS开发

iOS如何获取手机上的已安装的所有应用

2017-05-26 00:00 567 查看

前言

公司内部有一给自己的 App 发布后台,类似于 FIR 那样的存在, 有完整的 LDAP 账号登录。每天的 daily build 和 历史发布版都会放到那里去。然而每次都要登录后台扫描二维码下载实在是太麻烦了,我们就打算做一个客户端。

于是问题来了:如何知道我手机上安装的礼物说版本号比我当前的要低呢?

方案们

方案一 剪贴板共享

我们知道同一个证书下的软件,是可以共享剪贴板的,所以我们可以把礼物说的版本号丢到剪贴板里存下来。

但问题在于:

礼物说要运行过

致命伤:必须同一个账号的证书,内部 APP 肯定会去用企业证书,而礼物说可能是线上证书,可能是企业证书。

所以这个方案被否掉了。

方案二 URLScheme

把礼物说的 URLScheme 写成 GiftTalk233 这样的方案,然后在项目中 CanOpenURL 确认安装。

当问题在于:

iOS 9 之后,CanOpenURL 的白名单有上限,而且要修改编译脚本过于麻烦,这个方案也被否掉了。

于是,常规方案都不可行,那么要祭出大杀器了,私有 API

私有 API

毕竟是内部使用的 APP,所以用用私有 API 也没有什么问题。经过查阅越狱开发的文档,我们发现了这么一个私有库:MobileInstallation.framework

使用起来还是蛮方便的,直接:

CFDictionaryRef dict = MobileInstallationLookup(NULL);
NSLog(@"%@", (NSDictionary*) dict);

然而,这个库正常情况下是不会被 Link 进来的,于是我们该怎么做呢?

链接私有库

经过前几篇文章,我们知道动态链接库都是写在 Mach-O 的头部。然而它最终是经过 unix 的一个系统函数 dlopen 来加载的,这货是可以突破沙盒环境的,不信你可以给 dlopen 下个断点看看。所以我们可以手动调用 dlopen 加载想要的私有库。

void* libHandle = dlopen("/System/Library/PrivateFrameworks/MobileInstallation.framework/MobileInstallation",RTLD_NOW);

if (libHandle) {
NSLog(@"加载成功!");
}

但是对于 C 语言函数来说,他在编译时就被换成了对应的函数指针,所以我们想调用上述方法并没有那么容易。而如果是 OC 的方法的话,我们直接通过 runtime 就可以拿到对应的结果了。

所以现在我们需要拿个函数指针做个映射:



这样就可以了!

然鹅,我拿这个这个东西去给同事得瑟的时候,被打脸了。

我拿了一台公司的 iPhone 4 iOS 7 做的开发,这个工作一切正常。但 iOS 8 以上,func 取不到。吓得我赶紧去 Github 上看头文件。
https://github.com/MP0w/iOS-Headers/blob/master/iOS8.1/PrivateFrameworks/MobileInstallation/MIInstallerClient.h
惊了个呆,iOS 8 居然重写了这个 framework,但仔细观察了一下,貌似有一个方法是我们想要的:

- (void)fetchInstalledAppsWithOptions:(id)arg1 completion:(CDUnknownBlockType)arg2

好吧,我之前树的 Flag 应验了,那就 iOS 8 以上换一个写法吧。



真机运行得到:

required to have an entitlement named “com.apple.private.mobileinstall.allowedSPI” with an array containing “CopyInstalledAppsForLaunchServices” to call the MobileInstallation SPI

Wut?



entitlement.plist

若你对 iOS 签名机制了机的话,entitlement 绝对不陌生。不过我相信大多数开发者都很少和他打交道,这里我来简单说一下。

我们都知道签名需要证书的 profile 文件,而 entitlement 是一个授权机制,他里面约定了很多 iOS 的更高级的权限。有点像 Android 写在 manifest 里面的 permission。

比如:

访问 HealthKit 需要添加
com.apple.developer.healthkit


使用 Network Extension 查找
WI-FI
需要添加
com.apple.developer.networking.HotspotHelper


但是,这个是需要苹果授权的,且与 profile 对应的。于是我们想在 entitlement 里面添加
com.apple.private.mobileinstall.allowedSPI
是没问题,但是是无法通过签名,会提示找不到对应的 profile。

于是我们想调用私有的 entitlement 的话,越狱设备可以随便搞,但不越狱的话开发者层面是没有什么可以绕过去的方法 (但做安全的蒸米大大说其实是有解的,若有朋友知道欢迎留言)。

仿佛走入了死胡同

柳暗花明又一村

我们都知道同步推是有企业证书版本的,且能查看我们本地的全部 APP。于是,我们对着他们的代码找一下呗~

经过一番查找,我们定位到了一个叫做 ApplicationManager 的类,这个 loadInstalledApplications 的方法很抢眼



然后在逻辑结构中可以看到:



他很鸡贼的判断了一下系统版本是不是大于 8.0,如果不是,就用左边的 MobileInstallation.framework 来做,否则调用了一个方法,叫做
suoyouyianzhuangdeyingyong
。我能吐槽这名字么…

通过下面代码,我们可以发现一个类叫做
LSApplicationWorkspace




在 Github 的私有头文件里面搜了一下,惊了个呆:

https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/MobileCoreServices.framework/LSApplicationWorkspace.h


他居然是 MobileCoreServices 的私有函数!我们连 dlopen 都省了。

于是,照猫画虎,我们有:



任务完成!

()

Class c = NSClassFromString(@"LSApplicationWorkspace");

id s = [(id)c performSelector:NSSelectorFromString(@"defaultWorkspace")];

NSArray *arr = [s performSelector:NSSelectorFromString(@"allInstalledApplications")];

for (id item in arr) {

NSLog(@"%@",[item performSelector:NSSelectorFromString(@"applicationIdentifier")]);

NSLog(@"%@",[item performSelector:NSSelectorFromString(@"bundleVersion")]);

NSLog(@"%@",[item performSelector:NSSelectorFromString(@"shortVersionString")]);

}

()

后记

说了那么一大堆,结果就这么几行代码解决了。是不是觉得有点不爽,那就给你爆一个猛料:

这东个西以可过通 JSPatch 或 字符拼串接 来现实生环产境用使。

原文:https://mp.weixin.qq.com/s?__biz=MzIwMTYzMzcwOQ==&mid=2650948457&idx=1&sn=9b1f3e0c405b7621a6b3b7c8419a2695&scene=1&srcid=0724IIVS39VqVG4NfViPhU7s&key=77421cf58af4a653d340bb0deaee291e763e1249dcd3486b70f792ddc3190758d2e6bf3bc85be93b98e673fe7c2730ea&ascene=0&uin=MzM5MTU4Mzk1
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: