風險點
1、class-dump有些文件會報錯,需要查看下
2、mach-o文件中的依賴除了系統,是不是還需要dump第三方其他的庫進行掃描
@xpath
3、私有api在公開的Framework及私有的PrivateFramework都有。
4、9.2.5的iOS系統對應的Xcode 8是有docset的,后面的Xcode都有新的文件格式了,下面有介紹,需要自己分析,但是數據結構有點亂,可能我我還沒悟出來。。。。。。
前言
最近SDK的開發,經常會給到安全組掃描安全漏洞,會有一項報告是私有API警告,就想著自己實現一個工具來提前掃描。看了網上很多文章,基本上都是簡單的介紹,大多數資料都是網易游戲開源的一個iOS
private_api_ckecker
項目,項目現在已經不維護了,而且是用
Python2
和
Flask
寫的,而且Bug好多,但是思路是可以研究一下的。
下面就用
Python3
和
Django
重寫該項目,把Bug都給修復了,而且會記錄一下該掃描思路的不足以及如何構建私有API庫
檢測方法
符號表
用
nm
,
otool
等工具導出二進制包的函數符號表,以檢查私有 API 的調用。缺點是無法檢測字符串拼接方法的私有 API 調用。
動態分析
動態掃描需要應用運行起來,每當調用方法時就判斷是否是私有 API,但是效率會很低,而且不能保證代碼完全覆蓋。
靜態分析
在對二進制文件反匯編結果的基礎上,進行靜態分析:
找出動態調用 API 方法如
performSelector:
,以及調用對象的類
檢查參數,如果參數是拼接方法生成,推導求得拼接的結果
如何推導,請閱讀加拿大 Laval University 發表的題為 Static Analysis of Binary Code to Isolate Malicious Behaviors 的論文。如果拼接字符串由服務端下發,依舊可以避開檢查。
檢測思路
1、通過
class-dump
導出
Frameworks
以及
PrivateFrameworks
中可執行文件的頭文件,通過腳本提取方法分別為
SET_A
集合和
SET_E
集合
2、通過
Framework
中的Header文件夾下暴露的頭文件進行提取,通過腳本提取方法設置為
SET_B
集合
3、找到Xcode內置的
com.apple.adc.documentation.iOS.docset
數據庫(iOS 9.3之后修改了內置數據結構,后面介紹再介紹),多表查詢出對應的API,設置
SET_C
集合
4、那么
SET_F =(SET_A - SET_B - SET_C)
就是公有Framework下對應的私有API,設置為集合
SET_F
5、原本B集合中的API就是私有庫里面的,因此都不能被使用,則最終的私有API集合為
SET_D = SET_F + SET_E
6、使用
class-dump
反編譯ipa包中的app文件,然后和
SET_D
做交集即可獲取到。
以下是構建所用到的表名
集合A —
framework_dump_apis
framework可執行文件dump后的api集合
集合B —
framework_header_apis
framework暴露的頭文件api集合
集合C —
document_apis
內置文檔docset數據集合
集合D —
all_private_apis
最終私有apis集合
集合E —
private_framework_dump_apis
私有framework可執行文件dump后的集合
集合F —
framework_private_apis
集合A - 集合B - 集合 C剩下的apis
集合G —
white_list_apis
白名單
當項目啟動的時候會根據數據庫不存在就會創建這7張表,其中
db_names
是對應的配置文件中的數組
def
create_relate_tables
(
)
:
sql
=
(
"create table %s("
"id integer primary key AUTOINCREMENT not null, "
"api_name varchar, "
"class_name varchar, "
"type varchar, "
"header_file varchar, "
"source_sdk varchar, "
"source_framework varchar )"
)
for
db_name
in
db_names
.
keys
(
)
:
SqliteHandler
(
)
.
execute_sql
(
sql
%
(
db_names
[
db_name
]
)
,
(
)
)
構建集合A(framework_dump_apis)
首先我們要知道如何拿到系統
Framework
的對應路徑,在Xcode中配置啟動參數
DYLD_PRINT_INITIALIZERS = 1
,啟動之后就能在控制臺拿到對應的全路徑。
Framework
和
PrivateFramework
都是在該路徑下
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
Library
/
CoreSimulator
/
Profiles
/
Runtimes
/
iOS
.
simruntime
/
Contents
/
Resources
/
RuntimeRoot
/
System
/
Library
/
Frameworks
網上的Demo都是很早之前的Demo,路徑已經變化了,所以最新的路徑獲取方式按上面的方式拿即可。
api_utils.py
中我們會針對集合A調用如下
# SET_A dump framework所有的API,Mach-o文件導出對應頭文件,給framework路徑作為參數
def
frame_work_dump_apis
(
version
,
framework_folder
)
:
"""
class-dump framework下庫生成的所有頭文件api
"""
# dump 目標文件的framework到指定目錄 /tmp/public_headers/xxx.framework/Headers/xxx.h 返回值 /tmp/public-headers/ 打成.h
framework_header_path
=
__class_dump_frameworks
(
framework_folder
,
'public_headers/'
)
# 獲取.h文件集合
all_headers
=
__get_headers_from_path
(
framework_header_path
)
# 解析文件內容,獲得api
framework_apis
=
__get_apis_from_headers
(
version
,
all_headers
)
return
framework_apis
第一步是
class-dump
出頭文件組織結構和Xcode內置的Framework中的Headers結構一致,然后導入到工程下的
/tmp/public_headers/xxxxx.framework/Headers/xxxxx.h
第二步把所有目錄下的頭文件集合成數組
[(frameworkname, prefix, 具體路徑),()]
第三步提取頭文件中的方法,類以及類型等屬性
[{'class':'','methods':'','type':''},{},{}]
,這里Python的正則提取就不介紹了,太多了,可以看工程源碼,都是獨立可以使用的模塊
第四步把上述信息組裝成對應的
key
和
value
,對應
framework_dump_apis
表中的字段
第五步多插入庫 右側數據結構
[{'class':'','methods':'','type':''},{},{}]
# (:api_name,:api_name,:api_name,:api_name,:api_name,:api_name)
# 多插
def
insert_apis
(
table_name
,
datas
)
:
"""
Mysql
https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-executemany.html
如果是 [(),(),()] 則用%s
如果是[{},{},{}] 就用 :name取值
"""
sql
=
"insert into "
+
table_name
+
" (api_name,class_name,type,header_file,source_sdk,source_framework) values (:api_name,:class_name,:type,:header_file,:source_sdk,:source_framework)"
return
SqliteHandler
(
)
.
insert_many
(
sql
,
datas
)
此時,公有庫Dump出來的所有API表就建立好了,可以查看
framework_dump_apis
表,里面根據關鍵字能搜索到你平時用的API,一共有
139610
個
注意:
當我們在Framework目錄下進行dump的時候有些結果是嵌套在里面的,比如Framework內部還有Framework,比如
AVFoundation.Frameworks
,提取的時候千萬不能忘掉,而且每個版本有可能不同,需要注意
那么最后提取出來是
142724
個
構建集合B(framework_header_apis)
Framework的Header中頭文件的路徑獲取方式已經介紹過了
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
由于我們構建集合A的時候Dump出來的頭文件結構規則和Framework結構一直,所以和構建集合A不同的是就是不需要dump,直接把頭文件導出,然后挨個分析導出對應的數據結構即可,然后統一多插入庫即可,代碼和第一步用到的一樣。
注意:
這里的結構和Framework可執行文件那里一樣會出現嵌套結構,雖然我們自己dump到tmp目錄下是不會有,但是公用代碼的話,這里也需要處理一下上面嵌套的結構,可以在上面給的路徑下看到對應的
AVFoundation.Framework
也一樣嵌套
可以看下簡單的日志路徑:
頭文件讀入,正在處理正則
-
-
-
>
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
/
AVFoundation
.
framework
/
Headers
/
AVAudioUnitReverb
.
h
頭文件讀入,正在處理正則
-
-
-
>
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
/
AVFoundation
.
framework
/
Headers
/
AVPlayerMediaSelectionCriteria
.
h
頭文件讀入,正在處理正則
-
-
-
>
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
/
AVFoundation
.
framework
/
Headers
/
AVDepthData
.
h
頭文件讀入,正在處理正則
-
-
-
>
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
/
AVFoundation
.
framework
/
Headers
/
AVCaptureFileOutput
.
h
頭文件讀入,正在處理正則
-
-
-
>
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
/
AVFoundation
.
framework
/
Headers
/
AVAudioUnitTimeEffect
.
h
頭文件讀入,正在處理正則
-
-
-
>
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
/
AVFoundation
.
framework
/
Headers
/
AVUtilities
.
h
頭文件讀入,正在處理正則
-
-
-
>
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
/
AVFoundation
.
framework
/
Frameworks
/
AVFAudio
.
framework
/
Headers
/
AVAudioUnitSampler
.
h
頭文件讀入,正在處理正則
-
-
-
>
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
/
AVFoundation
.
framework
/
Frameworks
/
AVFAudio
.
framework
/
Headers
/
AVAudioEngine
.
h
頭文件讀入,正在處理正則
-
-
-
>
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
/
AVFoundation
.
framework
/
Frameworks
/
AVFAudio
.
framework
/
Headers
/
AVAudioUnitGenerator
.
h
頭文件讀入,正在處理正則
-
-
-
>
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
/
AVFoundation
.
framework
/
Frameworks
/
AVFAudio
.
framework
/
Headers
/
AVAudioTime
.
h
頭文件讀入,正在處理正則
-
-
-
>
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
/
AVFoundation
.
framework
/
Frameworks
/
AVFAudio
.
framework
/
Headers
/
AVAudioUnitMIDIInstrument
.
h
頭文件讀入,正在處理正則
-
-
-
>
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
SDKs
/
iPhoneOS
.
sdk
/
System
/
Library
/
Frameworks
/
AVFoundation
.
framework
/
Frameworks
/
AVFAudio
.
framework
/
Headers
/
AVAudioUnitEffect
.
h
A集合和B集合主要的提取邏輯都是在
api_utils.py
文件中,已經加上注釋。
那么最后提取出
21551
個
構建集合C(document_apis)
Xcode 9以下,Apple的文檔是以docSet的格式存在的。這是官方提供的XML信息,里面包含了所有版本的文檔信息。
# 各版本 iOS docSet 的元信息
https
:
//
developer
.
apple
.
com
/
library
/
downloads
/
docset
-
index
.
dvtdownloadableindex
# iOS 8.1 docSet
https
:
//
devimages
-
cdn
.
apple
.
com
/
docsets
/
20141020
/
031
-
07735
-
A
.
dmg
# iOS 9.3.5 docSet
https
:
//
devimages
-
cdn
.
apple
.
com
/
docsets
/
20160321
/
031
-
52212
-
A
.
dmg
iOS 9.3.5是最后一個能獲取到的docset文件了。下載后的
com.apple.adc.documentation.iOS.docset
文件,顯示包內容打開
docSet 內部的 Contents/Resources/docSet.dsidx
就是我們要獲取到的集合C,把這個文件拖進
Navicat
,看下表結構
打開我們的
ZTOKEN
表,表字段
ZTYPENAME
就是我們要關注獲取到的方法類型,主要是以下幾個
- func(pk=1) 全局C函數
- instm(pk=4) instance method 對象方法
- clm (pk=2) class method 類方法
- intfm (pk=6) interface method (- 協議)
- intfcm (pk=22)interface class method (+ 協議)
def
get_dsidx_apis
(
db_path
)
:
sql
=
"SELECT T.Z_PK,"
\
" T.ZTOKENNAME,"
\
" T.ZTOKENTYPE,"
\
" T.ZCONTAINER, "
\
"F.ZDECLAREDIN FROM ZTOKEN as T"
\
" INNER JOIN ZTOKENMETAINFORMATION as F ON T.Z_PK=F.ZTOKEN"
\
" WHERE ZTOKENTYPE IN (1,2,4,6,22)"
return
SqliteHandler
(
db_path
=
db_path
)
.
execute_select
(
sql
,
(
)
)
db_path是我們下載的docset文件的路徑,首先通過查詢
ZTOKEN
和
ZTOKENMETAINFORMATION
進行連表查詢,然后再根據對應的字段查對應的表把我們建的數據庫表字段對應好,然后組裝成能進行多插的數據結構,插入對應的表即可,一共
32150
條
api_name
ZTOKEN
表
--
-
ZTOKENNAME
字段
class_name
ZTOKEN
表
--
-
ZCONTAINER
表
--
>
ZCONTAINERNAME
字段
type
ZTOKEN
表
--
-
ZTOKENTYPE
字段
header_file
ZTOKEN
表
--
-
ZTOKENMETAINFORMATION
表
--
-
ZDECLAREDIN
字段
--
-
ZHEADER
表
--
>
ZHEADERPATH
字段
source_sdk
12.1
source_framework
ZTOKEN
表
--
-
ZTOKENMETAINFORMATION
表
--
-
ZDECLAREDIN
字段
--
-
ZHEADER
表
--
>
FRAMEWORKNAME
字段
但是在iOS 9.3.5之后,Xcode不在內置docset數據庫,而是換了一種數據結構,反正看起來雖然有點邏輯,但是很難提取完整。
雖然說iOS 9之后咱們能用到的API基本沒太大的變化,也能用上面的方式進行提取,但是如果要精益求精,就必須按新的API數據結構來提取了,具體如下
Xcode 9之后的API 內置在一個Framework里面,主要是兩個文件:map.db和cache.db
/
Applications
/
Xcode
.
app
/
Contents
/
SharedFrameworks
/
DNTDocumentationSupport
.
framework
/
Versions
/
A
/
Resources
/
external
1、以UIButton為例,在map.db里面查詢對應的uuid
select
uuid
from
map
where
source_language
=
1
and
reference_path
=
'uikit/uibutton'
2、然后到cache.db的refs表中查詢到對應的data_id
select
data_id
from
refs
where
uuid
=
'hcOyO61dSB'
3、上面根據uuid拿到的data_id是2187,然后在同級目錄下找到fs文件夾,找到對應的資源文件
上面拿到的文件是經過蘋果最新的無損壓縮算法LZFSE進行壓縮的,Github上已經有人實現了LZFSE算法實現,下載后編譯得到
lzfse
,然后放進
/usr/local/bin
lzfse
-
decode
-
i
/
Applications
/
Xcode
.
app
/
Contents
/
SharedFrameworks
/
DNTDocumentationSupport
.
framework
/
Versions
/
A
/
Resources
/
external
/
fs
/
2187
-
o
/
Users
/
mikejing191
/
Desktop
/
2187.
json
解壓后的文件是一個字符串,也不是Json字符串,感覺他是由許多個Json字符串組合而成,你可以通過以下簡單的算法拿到一段段的Json,但是有時候解析出來也不是正確的Json格式,就非常惡心了
def
get_decode_json
(
filepath
)
:
with
open
(
filepath
,
'rb'
)
as
f
:
text
=
f
.
read
(
)
filter_text
=
text
.
decode
(
'utf-8'
,
'ignore'
)
# print(filter_text)
return
filter_text
return
[
]
# 2439 是UIView
if
__name__
==
'__main__'
:
result
=
get_decode_json
(
'/Users/mikejing191/Desktop/2035.decode.json'
)
num
=
0
result_array
=
result
.
split
(
'}{'
)
result
=
''
for
str
in
result_array
:
print
(
''
)
if
num
==
0
:
js
=
str
+
'}'
elif
num
==
len
(
result_array
)
-
1
:
js
=
'{'
+
str
else
:
js
=
'{'
+
str
+
'}'
num
+=
1
print
(
js
)
但問題是,即使這樣拆開了拿,也不一定拿到的每個字符串就是Json字符串了,而且他的key都是基本上一個字母,不知道具體的含義,很難精準的提取出需要的API,如果有朋友能提取到數據庫,可以把提取到的數據庫分享下,非常感謝
構建集合E(private_framework_dump_apis)
由于集合A已經把結構調整好了,代碼都一樣,因此只要把集合A里面的路徑改成如下即可
/
Applications
/
Xcode
.
app
/
Contents
/
Developer
/
Platforms
/
iPhoneOS
.
platform
/
Developer
/
Library
/
CoreSimulator
/
Profiles
/
Runtimes
/
iOS
.
simruntime
/
Contents
/
Resources
/
RuntimeRoot
/
System
/
Library
/
PrivateFrameworks
一共
547128
條
構建集合F (framework_private_apis)
提取集合A中所有api數據集合
SET_A
,并且創建一個空的私有API集合
PR_API
1、遍歷
SET_A
,判斷api是否以
_
開頭,如果是加入到
PR_API
集合中
2、其他API既不在集合B(頭文件API集合),也不在集合C(docset API集合),那么也加入到
PR_API
集合中
3、不在集合B/集合C的判斷條件是 Sql語句中的Where語句,條件是
api_name
,
class_name
,
source_sdk
。
def
api_is_exist_in_table
(
table_name
,
api_obj
)
:
sql
=
"SELECT * FROM %s WHERE api_name = ? and class_name = ? and source_sdk = ?;"
%
table_name
parameters
=
(
api_obj
[
'api_name'
]
,
api_obj
[
'class_name'
]
,
api_obj
[
'source_sdk'
]
)
return
SqliteHandler
(
)
.
execute_select_one
(
sql
,
params
=
parameters
)
# 所有公有framework下的
API
計算如下
:
# 屬于公有
11707
# 屬于私有
102266
# 屬于私有下劃線
28751
# 去重前
-
--
公有庫內的私有
API
length:
131017
這里從公有庫中提取去來的API需要根據
class_name
和
api_name
進行去重,Python庫
itertools
提供了groupby方法,專門給數組根據key進行分組,以下是groupby的例子:
案例如下,根據date排序,分組之后取出該組下第一個即可,那么上面是根據類名和方法名分組,去重取出第一個即可
rows
=
[
{
'address'
:
'5412 N CLARK'
,
'date'
:
'07/01/2012'
}
,
{
'address'
:
'5148 N CLARK'
,
'date'
:
'07/04/2012'
}
,
{
'address'
:
'5800 E 58TH'
,
'date'
:
'07/02/2012'
}
,
{
'address'
:
'2122 N CLARK'
,
'date'
:
'07/03/2012'
}
,
{
'address'
:
'5645 N RAVENSWOOD'
,
'date'
:
'07/02/2012'
}
,
{
'address'
:
'1060 W ADDISON'
,
'date'
:
'07/02/2012'
}
,
{
'address'
:
'4801 N BROADWAY'
,
'date'
:
'07/01/2012'
}
,
{
'address'
:
'1039 W GRANVILLE'
,
'date'
:
'07/04/2012'
}
,
]
def
group_by_date
(
obj
)
:
return
obj
[
'date'
]
x
=
sorted
(
rows
,
key
=
group_by_date
)
# 打印如下
[
{
'address'
:
'5412 N CLARK'
,
'date'
:
'07/01/2012'
}
,
{
'address'
:
'4801 N BROADWAY'
,
'date'
:
'07/01/2012'
}
,
{
'address'
:
'5800 E 58TH'
,
'date'
:
'07/02/2012'
}
,
{
'address'
:
'5645 N RAVENSWOOD'
,
'date'
:
'07/02/2012'
}
,
{
'address'
:
'1060 W ADDISON'
,
'date'
:
'07/02/2012'
}
,
{
'address'
:
'2122 N CLARK'
,
'date'
:
'07/03/2012'
}
,
{
'address'
:
'5148 N CLARK'
,
'date'
:
'07/04/2012'
}
,
{
'address'
:
'1039 W GRANVILLE'
,
'date'
:
'07/04/2012'
}
]
y
=
groupby
(
x
,
group_by_date
)
for
g
,
l
in
y
:
print
(
g
)
print
(
list
(
l
)
# 打印如下
07
/
01
/
2012
[
{
'address'
:
'5412 N CLARK'
,
'date'
:
'07/01/2012'
}
{
'address'
:
'4801 N BROADWAY'
,
'date'
:
'07/01/2012'
}
]
07
/
02
/
2012
[
{
'address'
:
'5800 E 58TH'
,
'date'
:
'07/02/2012'
}
,
{
'address'
:
'5645 N RAVENSWOOD'
,
'date'
:
'07/02/2012'
}
,
{
'address'
:
'1060 W ADDISON'
,
'date'
:
'07/02/2012'
}
]
07
/
03
/
2012
[
{
'address'
:
'2122 N CLARK'
,
'date'
:
'07/03/2012'
}
]
07
/
04
/
2012
[
{
'address'
:
'5148 N CLARK'
,
'date'
:
'07/04/2012'
}
,
{
'address'
:
'1039 W GRANVILLE'
,
'date'
:
'07/04/2012'
}
]
以下根據API的class_name和api_name進行去重
# api去重 根據api_name和class_name
def
deduplication_api_list
(
apis
)
:
"""
相同類名和相同方法名去重
:param apis:
:return:
"""
def
group_by_api
(
api
)
:
return
api
[
'api_name'
]
+
'/'
+
api
[
'class_name'
]
new_apis
=
[
]
# 先排序
apis
=
sorted
(
apis
,
key
=
group_by_api
)
# 再根據類名和方法名成組
for
group
,
itr
in
groupby
(
apis
,
key
=
group_by_api
)
:
l
=
list
(
itr
)
if
l
and
len
(
l
)
>
0
:
new_apis
.
append
(
l
[
0
]
)
return
new_apis
去重后公有庫內的私有API還剩
128854
條
構建最終集合D (all_private_apis)
最終的私有API集合只要把集合F和集合E合并入庫即可,一共
675982
條
構建完Log
******************
SET_A
142724
********************
********************
SET_B
21551
*******************
********************
SET_C
32150
********************
********************
SET_E
547128
********************
********************
所有公有framework下的API計算如下:
屬于公有11707
屬于私有102266
屬于私有下劃線28751
********************
去重前---公有庫內的私有API length:131017
start group by
..
..
去重后----公有庫內的私有API length:128854
公有庫下的私有API插入最終集合---all_private_apis---128854
私有庫API集合取出插入最終集合---all_private_apis---547128
SET_D
675982
********************
SET_F
公有庫下的私有API插入獨立集合F集合---framework_private_apis---128854
********************
構建完的數據庫
mkj_private_apis.db
放在了鏈接: https://pan.baidu.com/s/15x3kExmwL5RrPJQIYB6LXw 提取碼: sdbe ,不想自己構建的可以下載下來放進根目錄
掃描私有API
1.解壓ipa,提取Mach-O
1、用
zipfile
解壓項目到tmp目錄下
2、首先得安裝
macholib
庫,通過
python -mmacholib find xxxxxx
掃描路徑下的可執行文件,默認掃描到的是數組,提取出第一個就是項目可執行文件。
3、
strings
去獲取可執行文件下可打印字符。
strings
主要是獲取非文本文件中包含的文本內容,用
\n
去分割成集合用
set類型
去表示
集合1
4、
otool -L
提取項目中用到的依賴庫PublicFramework和PrivateFramework
5、
class_dump
從Mach-O文件中導出頭文件信息,解析出類名,協議和變量名一樣用
set類型
集合2
6、
集合1 - 集合2
,由于是
set
類型,可以通過減法進行過濾得到
集合3
。
7、
class-dump
的分析結果通過正則匹配到方法
Method集合4
8、步驟4拿到的PublicFramework作為sql語句的條件查詢
SET_D(all_private_apis表)
,如果有白名單的話,再把白名單的私有API過濾,得到最終該掃描項目用到的框架里面的私有API
集合5
# 從SET_D 私有API庫里面查找api_name 而且framework不屬于參數,而且不在白名單里面
def
get_private_api_list
(
framework
=
None
)
:
framework_str
=
_get_sql_in_strings
(
framework
)
# in frameworks
private_db_name
=
db_names
[
"SET_D"
]
white_list_containers
=
_get_white_lists_results
(
)
# 有frame過濾條件s
if
framework_str
:
sql
=
"select * from %s group by api_name, class_name having source_framework in "
%
(
private_db_name
)
+
framework_str
+
" and api_name not in "
+
white_list_containers
+
";"
params
=
(
)
else
:
sql
=
"select * from %s group by api_name, class_name having api_name not in "
%
(
private_db_name
)
+
white_list_containers
+
";"
params
=
(
)
private_apis
=
SqliteHandler
(
)
.
execute_select
(
sql
,
params
)
print
(
sql
)
return
private_apis
9、
集合3 和 集合5
取交集,判斷集合5中的
api_name
是否在集合3里面,把在的重新生成一個
集合6
,這里集合3可以理解為剩余字符串的API關鍵字,如果和集合5私有API集合有交集,那么就暫且認為是有可能出現的私有API,統一為集合6
10、遍歷
集合6
,和集合4產生交集,由于4和6都是有
api_name
,
class_name
等詳細信息的集合,因此最終根據這兩個值為Key產生的交集,才算的上真正的私有API調用,存在就是私有API,不存在就不是私有API。
掃描結果舉例
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
App可見Strings
:
14634
App協議,變量屬性
:
3954
剩下的字符串
--
-
>
String
-
App協議,變量屬性
:
11640
App方法名app_methods
:
4088
App用的Public對應的
private
apis length
:
15125
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
strings剩余可見字符串關鍵字和Publick對應的私有
API
集合交集后的私有
API
--
-
>
347
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
最終
API
掃描結果
method_in_app
:
0
method_not_in_app
:
347
private
framework
:
0
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
## 使用方式和流程 上面介紹了如何構建私有API庫,還有很多不足,搜集可能不全面。還介紹了用入庫的私有API如何進行ipa掃描,下面介紹下如何用Python3 + Django環境去使用。
1.構建私有API數據庫
如果用現成的跑完的數據庫,可以從這里下載,放在云盤了(鏈接: https://pan.baidu.com/s/15x3kExmwL5RrPJQIYB6LXw 提取碼: sdbe),這里面的表名對應的用途上面有介紹。
如果你會自己編譯入庫,就在
config.py
文件中找到
sdks_configs
,配置對應的路徑地址,
framework_path
和
private_framework_path
對應的是Framework可執行文件的路徑,
framework_header_path
頭文件路徑,前者需要自己dump,后者可以直接用,具體怎么找路徑可以在上面
構建集合A
找到,
docset_path
路徑可以自己下載setdoc文件,上面
構建集合C
也是下載下來找到db文件的路徑,setdoc文件也是放在云盤了(鏈接: https://pan.baidu.com/s/15x3kExmwL5RrPJQIYB6LXw 提取碼: sdbe),可以自己下下來,試著跑腳本入庫。
2.虛擬環境配置
virtualenv方式
1.進入項目文件夾,用
virtualenv
創建虛擬環境,沒有該工具用
pip install virtualenv
/
pip3 install virtualenv
安裝
2.
virtualenv venv
3.
virtualenv -p /usr/local/bin/python3 venv
# 創建3的環境
4.
pip install -r requirements.txt
# 虛擬環境導入依賴
5.
. venv/bin/activate
# 啟動虛擬環境
Pycharm方式
1.下載項目下來,用Pycharm打開,然后點擊Pycharm — Preference — Project — Project Interpreter配置虛擬環境
2.點擊右邊的齒輪,選擇add,Virtualenv Environment — New Environment 默認確定即可
3.打開Pycharm下面的Terminal,進入虛擬環境,安裝依賴包
4.安裝
pip install -r requirements.txt
5.然后
build_apis_db.py
文件可以單獨跑,就會在項目主目錄下生成一個
tmp
文件夾生成對應
framework
dump之后的頭文件
6.最后自動會正則這些頭文件,然后寫入
mkj_private_apis.db
對應的表中進行后續匹配
3.直接腳本運行
把下載好的db文件或者自己編譯好的db文件如上面所示出現在根目錄,然后打開
check_private_apis.py
文件,修改main函數里面的
chech_multi(path)
的參數,對應的path就是ipa文件所在目錄,腳本會批量掃描目錄下所有ipa并輸出Excel,可以在項目tmp目錄中找到生成的Excel文件
當然,這里的私有API都是舉例測試用的,這里的各種信息是掃描ipa包里面的plist文件和mobileprovision文件出來的檢查ipa信息工具,下面的私有API就是根據我們拋出來的數據庫比對出來的,正常情況下是無信息的,需要再完善下,如果掃到了也需要人工干預確認下。
4.Django本地環境運行
Django不熟悉的可以看看另一個文章虛擬環境啟動Django的Hello World
上面已經安裝配置好了Django的運行環境,安裝好了所有依賴,依然cd到項目根目錄,然后執行啟動虛擬環境
.
venv/bin/activate
python private_apis_app/manage.py runserver
啟動信息如下
Watching
for
file
changes with StatReloader
Performing system checks
..
.
System check identified no issues
(
0 silenced
)
.
You have 17 unapplied migration
(
s
)
. Your project may not work properly
until
you apply the migrations
for
app
(
s
)
: admin, auth, contenttypes, sessions.
Run
'python manage.py migrate'
to apply them.
July 10, 2019 - 11:14:01
Django version 2.2.3, using settings
'private_apis_app.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
這里Django會有紅色的警告,告訴你數據庫沒遷移,不過我們先用不到,可以無視他,然后打開
http://127.0.0.1:8000/check/
,直接把ipa包拖入頁面區域,然后等跑數據,最終也會出現在頁面上
項目地址:
Python3私有API掃描工具
ipa信息掃描工具
參考文章:
iOS 私有API掃描總結
iOS 私有 API 調用檢測機制探討
iOS 私有API獲取
Docsets問題
應用安全審計
How do I check where my app is using IDFA
私有API-iOS10 openURL方法跳轉到設置界面失效的解決方法
私有API平安好房的大佬總結
Django和Flask入門
Django備忘錄
靜態掃描Git庫
RuntimeBrowser庫,所有API集合
xlswriter
python -m mod
utf-8 can’t decode byte…的解決方法
mysql excutemany
otool 用途
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
