自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

關(guān)于iOS私有API掃描

移動(dòng)開發(fā)
近期研究了關(guān)于私有 API 掃描這個(gè)主題。研讀了業(yè)界現(xiàn)有的相關(guān)文章后發(fā)現(xiàn),很多都是簡(jiǎn)單的摘錄,也不對(duì)存在的謬誤做任何點(diǎn)評(píng)。本人在閱讀了網(wǎng)易游戲開源的 iOS private api checker 項(xiàng)目后,對(duì)如何構(gòu)建私有 API 庫(kù)、該項(xiàng)目又是如何識(shí)別 APP 中的私有 API、該方案存在哪些問(wèn)題,一一做了闡述。

導(dǎo)讀:近期研究了關(guān)于私有 API 掃描這個(gè)主題。研讀了業(yè)界現(xiàn)有的相關(guān)文章后發(fā)現(xiàn),很多都是簡(jiǎn)單的摘錄,也不對(duì)存在的謬誤做任何點(diǎn)評(píng)。本人在閱讀了網(wǎng)易游戲開源的 iOS private api checker 項(xiàng)目后,對(duì)如何構(gòu)建私有 API 庫(kù)、該項(xiàng)目又是如何識(shí)別 APP 中的私有 API、該方案存在哪些問(wèn)題,一一做了闡述。

關(guān)于iOS私有API掃描

審核案例

自定義方法和私有 API 重名

APP 沒(méi)有被拒絕,但是 Apple 提醒下次更新時(shí)修改相關(guān) API 名稱。

然而多年前,廣為使用的 Three20 里包含和私有 API 重名的方法,導(dǎo)致很多使用該框架的 APP 審核不通過(guò)。

使用了非公開方法

Apple 發(fā)現(xiàn) APP 使用了非公開的方法 allowsAnyHTTPSCertificateForHost:,拒絕的同時(shí)還提供開發(fā)者自查的方法。

未執(zhí)行到的私有 API 調(diào)用

Qzone 中曾自定義接口 _define: 但是并沒(méi)有調(diào)用過(guò),結(jié)果也被 Apple 發(fā)現(xiàn)并拒絕上架。UITextView 導(dǎo)出的頭文件中有該方法。

Tim Cook 威脅下架 Uber 應(yīng)用

Uber 使用私有 API獲取設(shè)備的序列號(hào),蘋果 CEO 嚴(yán)厲斥責(zé)該行為并威脅要下架。

調(diào)用方式

直接調(diào)用

  1. [self.view recursiveDescription]; 

因?yàn)樗接?API 沒(méi)有暴露出來(lái),編譯會(huì)報(bào)錯(cuò)??梢蕴砑幽涿?Category 聲明下私有方法。 

  1. @interface UIView()  
  2. -(id)recursiveDescription;  
  3. @end 

字符拼接 

  1. NSArray *parts = @[@"_priva", @"teMethod"];  
  2. NSString *selectorString = [parts componentsJoinedByString:@""];  
  3. [self performSelector:NSSelectorFromString(selectorString) withObject:nil]; 

代碼混淆 

  1. // statusBar  
  2. NSData *data = [NSData dataWithBytes:(unsigned char[]){0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x61, 0x72} length:9];  
  3. NSString *key = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; 

檢測(cè)方法

符號(hào)表

用 nm, otool 等工具導(dǎo)出二進(jìn)制包的函數(shù)符號(hào)表,以檢查私有 API 的調(diào)用。缺點(diǎn)是無(wú)法檢測(cè)字符串拼接方法的私有 API 調(diào)用。

動(dòng)態(tài)掃描

動(dòng)態(tài)掃描需要應(yīng)用運(yùn)行起來(lái),每當(dāng)調(diào)用方法時(shí)就判斷是否是私有 API,但是效率會(huì)很低,而且不能保證代碼完全覆蓋。

靜態(tài)分析

在對(duì)二進(jìn)制文件反匯編結(jié)果的基礎(chǔ)上,進(jìn)行靜態(tài)分析:

  • 找出動(dòng)態(tài)調(diào)用 API 方法如 performSelector: ,以及調(diào)用對(duì)象的類
  • 檢查參數(shù),如果參數(shù)是拼接方法生成,推導(dǎo)求得拼接的結(jié)果

如何推導(dǎo),請(qǐng)閱讀加拿大 Laval University 發(fā)表的題為 Static Analysis of Binary Code to Isolate Malicious Behaviors 的論文。如果拼接字符串由服務(wù)端下發(fā),依舊可以避開檢查。

網(wǎng)易方案

構(gòu)建私有 API 庫(kù)

從 Github 下載 iOS-private-api-checker 后,可使用 WEB 的方式上傳一個(gè) IPA 進(jìn)行掃描。我們可以使用 virtualenv 創(chuàng)建一個(gè)虛擬環(huán)境,來(lái)安裝所需的依賴庫(kù),以免影響系統(tǒng)級(jí)的 Python 環(huán)境。 

  1. # 創(chuàng)建虛擬環(huán)境 
  2. virtualenv venv 
  3.  
  4. # 啟用虛擬環(huán)境 
  5. . venv/bin/activate 
  6.  
  7. # 安裝依賴的庫(kù) 
  8. pip install -r requirements.txt 
  9.  
  10. # 啟動(dòng)監(jiān)測(cè)服務(wù) 
  11. python run_web.py 

工程的 app/templates/main/index_page.html 里介紹了檢查的原理:

  1. 通過(guò) class-dump 導(dǎo)出 Frameworks 及 PrivateFrameworks 的頭文件,分別設(shè)置為集合 PU 和 PR
  2. 通過(guò) Xcode 代碼提示的 SQLite 數(shù)據(jù)庫(kù)查詢出所有的 documented API,設(shè)置為集合 DA
  3. 那么 PU - DA 為公有 Framework 中的私有 API,設(shè)置為 A
  4. PR 為私有 Framework 中的 API,都不能使用。則私有 API 集合 PRAPI = A + PR
  5. 使用 class-dump 反編譯 ipa 中的 APP 文件,然后和 PRAPI 集合取交集即可獲得

但是,項(xiàng)目根目錄下的 README.md 寫道:

  • 私有的api = (class-dump Framework下的庫(kù)生成的頭文件中的api - (Framework下的頭文件里的api = 有文檔的api + 沒(méi)有文檔的api)) + PrivateFramework下的api

我***眼看到這個(gè)公式,對(duì)其中每一個(gè)運(yùn)算項(xiàng)的含義不是非??隙?,對(duì)括號(hào)里寫上等于號(hào)也是有疑問(wèn)的。另外,這個(gè)公式里還提到了 Framework 下的頭文件里的 API,而在 index_page.html 中完全沒(méi)有提到。所以,建議先無(wú)視這個(gè)公式,對(duì) index_page.html 里的文字也不要糾結(jié)。

閱讀 build_api_db.py 時(shí),看到方法 rebuild_private_api 中的注釋里寫道: 

  1. set_E private api  
  2. undocument_api = set_B - set_C  
  3. set_E = set_A - set_C - undocument_api = set_A - set_B  
  4. if include_private_framework: set_E = set_E + set_D 

單從集合運(yùn)算的角度看 set_E = set_A - set_C - undocument_api 和 set_A - set_B 能不能劃等號(hào)?講道理,應(yīng)該是 set_E = set_A - (set_B + set_C) 吧。這里的 + 是套用原作者的簡(jiǎn)化寫法,指集合的 ∪ 運(yùn)算。所以,建議無(wú)視這個(gè)注釋。

注釋表述的雖有問(wèn)題,但通讀代碼發(fā)現(xiàn)實(shí)際實(shí)現(xiàn)的邏輯是沒(méi)有問(wèn)題的?,F(xiàn)根據(jù) build_api_db.py 及相關(guān)的代碼所對(duì)應(yīng)的構(gòu)建私有 API 庫(kù)的原理做一簡(jiǎn)要闡述:

  1. set_A,表示從系統(tǒng) Frameworks 目錄下所有的 .framework 文件 dump 出的頭文件解析出的 API 集合。對(duì)應(yīng) ios_private.db 中的 framework_dump_apis 表記錄。
  2. set_B,表示從系統(tǒng) Frameworks 目錄下所有的 .framework 文件中的頭文件解析出的 API 集合。對(duì)應(yīng) ios_private.db 中的 framework_header_apis 表記錄。
  3. set_C,表示從 docSet 中索引文件解析出來(lái)的 API 集合。對(duì)應(yīng) ios_private.db 中的 document_apis 表記錄。
  4. set_D,表示從系統(tǒng) PrivateFrameworks 目錄下所有的 .framework 文件 dump 出的頭文件解析出的 API 集合。對(duì)應(yīng) ios_private.db 中的 private_framework_dump_apis 表記錄。
  5. set_E,表示私有 API,從 set_A 中識(shí)別出的私有 API 對(duì)應(yīng) framework_private_apis 中的記錄,表 private_apis 中的是加上 set_D 的記錄。
  6. 如果 rebuild_sdk_private_api 函數(shù)的第二個(gè)參數(shù)是 False 則 set_D 不會(huì)被加入到 private_apis 表中。

構(gòu)建集合 A

api_utils.py 中已經(jīng)封裝好了使用 class-dump 導(dǎo)出 .framework 的頭文件。所以不需要 DumpFrameworks.pl 這類的外部腳本,而且 DumpFrameworks.pl 生成的頭文件目錄結(jié)構(gòu)和本項(xiàng)目不吻合。也不需要下載 Nicolas Seiot 基于 RuntimeBrowser 導(dǎo)出的頭文件。

我們需要做的是,保證目標(biāo)系統(tǒng) (比如 8.1) 的模擬器在本機(jī)已經(jīng)安裝,并且知道 Frameworks 及 PrivateFrameworks 的路徑。

前者只需要在 Xcode / Preferences / Components / Simulator 中將 iOS 8.1 Simulator 下載下來(lái)即可。后者可以通過(guò)創(chuàng)建一個(gè) Xcode 工程,設(shè)置啟動(dòng)參數(shù) DYLD_PRINT_INITIALIZERS = 1 就可以在控制臺(tái)找到 .framework 的全路徑,比如:

  1. /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 8.1.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks 

需要注意的是,上述路徑 iOS 和 8.1 之間存在一個(gè)空格。這個(gè)空格會(huì)引起執(zhí)行 class-dump 的腳本出問(wèn)題,具體如何修復(fù)后面會(huì)給出建議。

根據(jù)我的實(shí)驗(yàn)結(jié)果,將上述路徑的 8.1 改成 9.3 或者 10.3 即為不同系統(tǒng)下的路徑。iOS 11.4 的路徑是:

  1. /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks 

我們不需要記住這些路徑,需要的是掌握獲取路徑的方法,用 find 命令也是 OK 的。

構(gòu)建集合 B

Frameworks 路徑已經(jīng)在構(gòu)建集合 A 的部分介紹過(guò),api_utils.py 中 framework_header_apis 方法就是用于構(gòu)建 Frameworks 目錄下所有的 .framework 文件中的頭文件解析出的 API 集合??闯龊图?A 的區(qū)別了吧?一個(gè)是直接處理 .framework 中包含的頭文件,一個(gè)是從 .framework 中的 Mach-O 文件導(dǎo)出對(duì)應(yīng)的頭文件。

構(gòu)建集合 A/D 其實(shí)就比構(gòu)建集合 B 多一步,即 dump 的過(guò)程。這也是為何在 dump 時(shí),導(dǎo)出頭文件的目錄和系統(tǒng) framework 文件內(nèi)部結(jié)構(gòu)一致,這樣使得接下來(lái)的構(gòu)建集合過(guò)程的代碼可以通用。

構(gòu)建集合 C

生成 documented API 集合的主要障礙在于,本機(jī)缺乏 docSet。本文寫于 2018 年 9 月初,我的工作機(jī)上只有 Xcode 9,而新版本的 Xcode 已經(jīng)使用新的文檔格式并直接集成在 Xcode 中。其實(shí)蘋果官方提供了一個(gè)包含各版本文檔鏈接等信息的 XML,將該 XML 下載到本地即可從中找到 iOS 8.1 等的文檔下載鏈接。 

  1. # 各版本 iOS docSet 的元信息  
  2. https://developer.apple.com/library/downloads/docset-index.dvtdownloadableindex 
  3.  
  4. # iOS 8.1 docSet  
  5. https://devimages-cdn.apple.com/docsets/20141020/031-0773***.dmg 
  6.  
  7. # iOS 9.3.5 docSet  
  8. https://devimages-cdn.apple.com/docsets/20160321/031-52212-A.dmg 

安裝下載下來(lái)的 dmg 后,在 Mac OS 根目錄下便出現(xiàn) docSet 文件了,你可以隨意挪位置。docSet 內(nèi)部的 Contents/Resources/docSet.dsidx 是我們獲得集合 C 的數(shù)據(jù)源。

本人習(xí)慣使用 SQLPro for SQLite 工具查看 sqlite 數(shù)據(jù)庫(kù)文件,將 docSet.dsidx 重命名為 docSet.sqlite 即可雙擊打開。其中 ZTOKENTYPE 表中的 func,instm,clm,intfm,intfcm 五種類型是我們要關(guān)注的:

  • func 表示全局 C 函數(shù)
  • instm 表示實(shí)例方法 instance method
  • clm 表示類方法 class method
  • intfm 表示協(xié)議方法,- 開頭
  • intfcm 表示協(xié)議方法,+ 開頭

憑感覺(jué)猜測(cè) intf 是 interface 的縮寫,interface 即 OOP 的接口而不是 Obj-C 定義類的那個(gè) interface

至于***版 iOS 的 documented API 怎么獲得,本人沒(méi)有研究。既然 Dash 的作者能生成 Apple API Reference 那理論上講應(yīng)該是可以生成 dsidx 文件的。記錄有些許價(jià)值的 Dash Release Notes 作為日后研究的線索:

  • "Xcode 8 doesn’t come with docsets anymore and that means Dash won’t automatically support the iOS 10, macOS 10.12, watchOS 3 and tvOS 10 docs. I’m working on a version of Dash that supports the new docs and will release an update as soon as possible." -- Jun 14th, 2016
  • "Apple API Reference Support. Apple has new API docs. You can use them in Dash by installing the Apple API Reference docset." -- Jul 2nd, 2016
  • "The Apple API Reference docset now reads the docs from within Xcode 8. This reduces disk space usage while also allowing me to modify & improve the docs at display-time. Thanks a lot to the Xcode team at Apple for helping me understand the new documentation format!" -- Oct 25th, 2016

構(gòu)建集合 D

同構(gòu)建集合 A,路徑的 Frameworks 改成 PrivateFrameworks 即可:

  1. /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 8.1.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/ 

構(gòu)建集合 E

以 set_A 為處理對(duì)象:

  1. 所有以 _ 開頭的方法,全部加到 set_E 中;
  2. 其他 API,如果不在 set_B 也不在 set_C 中,則加到 set_E 中
  3. 在不在 set_B / set_C 中的比較基準(zhǔn)是 api_name,class_name,sdk 三個(gè)值

步驟 3 是基于 db 查詢來(lái)實(shí)現(xiàn)的

代碼缺陷

1、build_api_db.py 中 rebuild_sdk_private_api(sdk_version, False),需改成 True

2、build_api_db.py 中 if include_private_framework 之后應(yīng)該是把 private_framework_apis 插入到數(shù)據(jù)表中,而不是 framework_dump_private_apis

3、api/api_utils.py 中 all_headers_path += iterate_dir(framework, "", os.path.join(framework_folder, header_path)) 應(yīng)該改成 all_headers_path += iterate_dir(framework, "", header_path)

4、db/dsidx_dbs.py 中 sql = balabala 需要確認(rèn) dsidx 文件中五種 TOKEN 類型對(duì)應(yīng)的 ID

比如我從 Apple 下載下來(lái)的 8.1 docSet 對(duì)應(yīng)相同 ZTOKENTYPE 的 ID 不是 (3,9,12,13,16) 而是 (11,13,1,8,19)。

如果你是從百度網(wǎng)盤等地方直接下載別人的 ios_private.db,請(qǐng)打開這個(gè) db 檢查下 document_apis 表中的數(shù)據(jù)真的都是 API 么。

另,原作者寫 (3,9,12,13,16) 是因?yàn)楫?dāng)時(shí) iOS 7.0 docSet 里確實(shí)是這幾個(gè) ID,這一點(diǎn)我通過(guò)往前翻 commit 記錄得到了確認(rèn)。所以靈活一點(diǎn)的寫法是根據(jù) ZTYPENAME 篩選數(shù)據(jù)。

5、dump/class_dump_utils.py 中 ret = subprocess.call(cmd.split()) 健壯性不夠

我在 Xcode 9 安裝 iOS 8.1 模擬器后看到 Frameworks 路徑是帶空格的,經(jīng)過(guò) split 就會(huì)導(dǎo)致路徑被拆分成兩段。改成 ret = subprocess.call([class_dump_path, '-H', frame_path, '-o', out_path]) 應(yīng)該就可以規(guī)避該問(wèn)題。

掃描私有 API

主要邏輯

閱讀 iOS_private.py 梳理出識(shí)別 APP 中私有 API 的主要邏輯如下:

  1. 基于 strings 工具從 Mach-O 文件導(dǎo)出字符串,按空格拆解得到集合1
  2. 使用 otool -L 從 Mach-O 文件獲得用到的 Frameworks 及 PrivateFrameworks 列表
  3. 基于 class-dump 從 Mach-O 文件導(dǎo)出的頭文件信息,解析出類名變量名集合2、方法集合3
  4. 集合4 = 集合1 - 集合2(比較基準(zhǔn)是 api_name)
  5. 表 framework_private_apis 中按 api_name, class_name 分組得到類名方法名組合的集合5
  6. 對(duì)集合5 和集合4 按 api_name 匹配,得到集中6
  7. 集合6 和集合 3 按 api_name, class_name 匹配,得到最終的私有 API 集合

步驟2 的結(jié)果可以作為步驟5 的部分條件。白名單表 whitelist 里的數(shù)據(jù),會(huì)從結(jié)果集中排除,對(duì)應(yīng)到代碼邏輯上也是在步驟5 被過(guò)濾掉。

代碼缺陷

因上述步驟6 中是按 api_name, class_name 的組合做匹配條件的,故原始代碼的 SQL 語(yǔ)句中 group by 不僅要有 api_name 還應(yīng)該加上 class_name 這個(gè)字端。

改進(jìn)建議

直接使用網(wǎng)易方案大概率是發(fā)現(xiàn)不了私有 API 的。檢測(cè)邏輯只考慮了 api_name, class_name 全匹配,局限性太大。

  1. 在私有 API 數(shù)據(jù)庫(kù)的建設(shè)上,TSRC 實(shí)驗(yàn)室的做法是進(jìn)一步增加條件,比如一些純小寫字母的 API,大多是一些 C 函數(shù),再過(guò)濾掉一批
  2. 在掃描算法的設(shè)計(jì)上,如果步驟5 只 group by api_name,步驟6 只匹配 api_name,同時(shí)在源代碼中存在 @selector(XXX) 這樣的字符串,基本可以認(rèn)定該 api_name 為私有 API
  3. 對(duì)于靜態(tài)拼接或者加解密的 API,可以通過(guò)動(dòng)態(tài) hook 的方式進(jìn)行識(shí)別,但也存在一些局限性
  4. 加入 prefs: 及 App-Prefs: 協(xié)議的掃描

驗(yàn)證特定 API

蘋果審核提出使用了不該用的某某 API,那么我們勢(shì)必要支持篩查該 API 用在何處,是我們的 APP 還是第三方 SDK 中。在代碼工程根目錄,執(zhí)行:

  1. find . -type f | grep ".a" | grep -v ".app" | xargs grep advertisingIdentifier 

遺留主題

在研讀網(wǎng)易游戲的開源方案時(shí),對(duì)于 iOS 10+ 如何構(gòu)建 documented API 數(shù)據(jù)集這個(gè)問(wèn)題直接跳了過(guò)去,后續(xù)可進(jìn)一步調(diào)研。

責(zé)任編輯:未麗燕 來(lái)源: 簡(jiǎn)書
相關(guān)推薦

2015-10-21 10:21:13

隱私安全私有API個(gè)人信息安全

2011-05-11 10:02:37

iOS

2018-12-18 11:36:30

私有云存儲(chǔ)云計(jì)算

2013-06-03 16:27:49

iOS開發(fā)移動(dòng)應(yīng)用移動(dòng)開發(fā)

2011-07-07 16:53:56

iOS 條形碼掃描

2011-08-11 14:46:25

2018-05-27 17:44:53

私有庫(kù)索引庫(kù)倉(cāng)庫(kù)

2013-12-17 11:18:53

iOS開發(fā)多媒體API

2015-08-20 09:00:23

ios9api

2011-09-02 19:12:59

IOS應(yīng)用Sqlite數(shù)據(jù)庫(kù)

2014-03-12 10:13:00

iOSSEL對(duì)象

2010-10-20 09:10:29

私有云

2011-08-23 16:48:41

Lua 5.1API 函數(shù)

2021-04-14 10:10:46

首席信息官APICIO

2013-03-27 11:33:32

iOS開發(fā)iOSjson解析方式

2015-07-29 09:22:25

IOS多線程

2011-08-19 11:34:05

iOS架構(gòu)圖

2011-08-18 11:19:13

IOS開發(fā)Core Plot S

2012-04-04 22:36:52

iOS5

2013-12-12 10:46:22

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)