风险点
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 用途