iOS私有API检测扫描思路以及工具开发(Python3 + Django)

风险点

1、class-dump有些文件会报错,需要查看下

2、mach-o文件中的依赖除了系统,是不是还需要dump第三方其他的库进行扫描@xpath

3、私有api在公开的Framework及私有的PrivateFramework都有。

4、9.2.5的iOS系统对应的Xcode 8是有docset的,后面的Xcode都有新的文件格式了,下面有介绍,需要自己分析,但是数据结构有点乱,可能我我还没悟出来。。。。。。

5.编译好的db和docset文件晚点上传到云盘

前言

最近SDK的开发,经常会给到安全组扫描安全漏洞,会有一项报告是私有API警告,就想着自己实现一个工具来提前扫描。看了网上很多文章,基本上都是简单的介绍,大多数资料都是网易游戏开源的一个iOS private_api_ckecker项目,项目现在已经不维护了,而且是用Python2Flask写的,而且Bug好多,但是思路是可以研究一下的。

下面就用Python3Django重写该项目,把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,启动之后就能在控制台拿到对应的全路径。FrameworkPrivateFramework都是在该路径下

/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的正则提取就不介绍了,太多了,可以看工程源码,都是独立可以使用的模块

第四步把上述信息组装成对应的keyvalue,对应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文件的路径,首先通过查询ZTOKENZTOKENMETAINFORMATION进行连表查询,然后再根据对应的字段查对应的表把我们建的数据库表字段对应好,然后组装成能进行多插的数据结构,插入对应的表即可,一共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_nameapi_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放在了云盘,不想自己构建的可以下载下来放进根目录

扫描私有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数据库

如果用现成的跑完的数据库,可以从这里下载,放在云盘了(晚上放),这里面的表名对应的用途上面有介绍。

如果你会自己编译入库,就在config.py文件中找到sdks_configs,配置对应的路径地址,framework_pathprivate_framework_path对应的是Framework可执行文件的路径,framework_header_path头文件路径,前者需要自己dump,后者可以直接用,具体怎么找路径可以在上面构建集合A找到,docset_path路径可以自己下载setdoc文件,上面构建集合C也是下载下来找到db文件的路径,setdoc文件也是放在云盘了(晚上放),可以自己下下来,试着跑脚本入库。

最终会在项目根目录多一个数据库文件,用于扫描
在这里插入图片描述

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文件夹生成对应frameworkdump之后的头文件

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 用途

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章