1. 程式人生 > >iOS 獲取手機上已經安裝的應用

iOS 獲取手機上已經安裝的應用

前言

公司內部有一給自己的 App 釋出後臺,類似於 FIR 那樣的存在, 有完整的 LDAP 賬號登入。每天的 daily build 和 歷史釋出版都會放到那裡去。然而每次都要登入後臺掃描二維碼下載實在是太麻煩了,我們就打算做一個客戶端。

於是問題來了:如何知道我手機上安裝的禮物說版本號比我當前的要低呢?

方案們

方案一 剪貼簿共享

我們知道同一個證書下的軟體,是可以共享剪貼簿的,所以我們可以把禮物說的版本號丟到剪貼簿裡存下來。

但問題在於:

  1. 禮物說要執行過

  2. 致命傷:必須同一個 賬號的 證書,內部 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 都省了。

於是,照貓畫虎,我們有:

任務完成!