支付宝客户端安全生产客户端风险挖掘

背景

客户端有着错综复杂的体系架构,多种技术栈组合及千人千面的场景,如何保障客户端高可用成了我们最大的命题。除了线上告警的应急快反之外,我们迫切需要提前挖掘风险的能力,因此构建一套体系化的挖掘能力,持续不断的挖掘端上漏洞是非常必要的,才能真正从源头上解决问题。

挖掘思路

实现端上全方位立体的扫雷工作,首先要先了解客户端的整体架构、开放接口、数据模块和依赖关系,从而能更全面的挖掘漏洞。

如上图是支付宝客户端mPaaS框架图,基于此框架的客户端App,都是由一个个积木搭建组成,这些积木我们称之为Bundle。由下往上library提供客户的基础服务,例如Rpc、Config、Push等基础功能。Application Framework 提供应用的上下文、AOP切面能力、安全等功能。Common Services通用的服务模块接口,提供对外的能力的封装。Native Application是业务实现层,每个业务基于基础服务、通用能力,最终构建对应的业务能力。

通过对客户端框架的整体结构认识及历史故障分析,我们将扫雷方向瞄准在基础服务框架并梳理了5大类风险点,如下图是客户端风险扫雷整体挖掘思路:

基础框架:Rpc、Config 、Sync、Jsapi 、mTop、Lottie动画、缓存等公共SDK;

系统服务:Broadcast、Scheme、ContextProvider、Http(s)、Cookie、SQlite等;

业务不可用:按钮点击无响应、花屏检测、入口丢失、Loading异常、弹窗异常;

安全风险:Jsapi越权检测、流量海关拦截、隐私明文检测、厂商隐私照明弹;

故障泛化:多媒体视频流变异、限流、超时、模板变异、多线程;

能力介绍

通过客户端架构框架了解及风险点梳理,我们知道客户端风险集中在基础框架的业务变更上,其中有开关JSON配置异常{} -> [] 引起客户端闪退、ffmpeg直播拉流解码异常、卡片模板变更引发历史版本闪退,打包构建导致图标丢失界面异常,因此有必要建设一套全自动化风险挖掘体系,通过优质的种子变异能力,快速生成大量异常数据,持续不断挖掘客户端风险,从而提升客户端稳定性。

如上图是扫雷的三个核心能力,分别是风险挖掘(种子变异能力)、闪退溯源及攻防演练,通过云真机能力串联构建整个风险挖掘体系,实现全自动化挖掘,源源不断挖掘端上风险,提供攻防演练素材。以下是核心能力介绍:

1.风险挖掘两步走

种子变异能力

异常检测能力

2.闪退溯源

3.攻防演练

整体架构

本方案架构主要分为四部分,底层依赖、基础能力、风险类型和自动化实现。通过对底层能力依赖,我们构建了种子变异、异常检测及闪退溯源的基础能力。在此基建上我们实现五类风险类型的挖掘,抽象化挖掘通道,实现挖掘能力可拓展性,当前共实现28种风险类型的覆盖,打通从端到平台的整体链路。

底层依赖模块:

在做扫雷时很大程度上需要依赖端上的Hook能力,如:拦截Rpc、Http(s) response响应数据,替换注入脏数据挖掘端上稳定性风险。Android上主要依赖DexAOP切面能力,其原理主要通过函数invoke指令替换的方式,在构建时对主dex中的函数调用执行代码插桩切面。IOS端通过Method Swizzing,Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。

Android拿PackageManager#getPackageInfo(String, int)切点来说,切之前、之后的代码变化如下:

基建能力

想要做好风险挖掘少不了打磨一套完备的种子变异能力,不同文件类型、数据结构有不同的变异策略。其中有针对多媒体Ffmpeg视频流变异AFL Fuzz,以覆盖率为导向,通过Bitflp变异策略挖掘文件风险。除去文件变异,端上大部分都是结构化数据如:Json、Java对象、OC、结构体等,因此我们基于递归思想构建了结构化数据变异的引擎,解决了结构化数据Fuzz。

异常检测能力:线上问题除了稳定性问题外还有大部分非闪退问题,造成端上业务不可用。因此我们针对不可用问题梳理了对应的特征,实现了端上按钮点击无响应、入口丢失等检测能力,并接入了端智能花屏检测引擎,基于AI图像训练挖掘端上渲染异常。不可用问题千变万化,针对不可用问题检测挖掘,我们只是解决了部分问题,未来还需持续的探索。

闪退溯源能力:当我们扫雷挖掘到客户端闪退,通过该能力我们能快速追溯到闪退根因,如:configKey:h5_js_webview value设置[] ->{} 发生crash,通过CG函数调用图与闪退堆栈关键信息匹配快速溯源定位到闪退开关h5_js_webview,协助开发快速修复问题,也能提供攻防演练大量真实演练案例,解决了常态化演练弹药不足问题。

详细方案

以下是端上风险挖掘整体流程以Config为例:

端上挖掘流程主要分为以下几部分:

HOOK

挖掘的前提需要了解各个风险点的数据流特点,并通过反射代理、HOOK等方式拦截注入脏数据,完成异常注入的第一步。

Android使用DexAOP对各数据通道进行拦截,原理是通过函数invoke指令替换的方式,对主dex中的函数调用执行切面

iOS端Hook通过Method Swizzing,Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换

异常捕获

针对所有类型Config、Rpc、Jsapi注册切面后,我们需要初始化异常捕获,感知客户端闪退。

Android Crash捕获:Thread定义的接口 UncaughtExceptionHandler用于处理未捕获的异常,当发生闪退时会调用UncaughtExceptionHandler.uncaughtException获取对应异常信息,可以通过Thread.setDefaultUncaughtExceptionHandler设置线程默认的异常处理器。native的崩溃发生在机器指令运行的层面,当客户端做了内核不可接受的事情,如除数为0让cpu无法识别指令、段错误等等,内核就会向app对应的线程发送信号(signal),默认处理方式是杀掉整个进程,可通过注册信号处理函数来捕获native crash(SIGSEGV, SIGBUS等)。

#include  
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

iOS Crash捕获主要分为三种:

捕获到闪退后,根据堆栈进行后续的匹配操作,具体逻辑在SLExceptionMonitor类的callWhenCrash方法中。

种子变异

模糊测试关键其中一块是种子变异能力,优秀的数据流变异策略能生产更加符合场景的测试数据,大大提高挖掘的深度,覆盖更多的业务分支。本方案主要介绍下基于AFL多媒体文件变异与结构化数据变异两种。

多媒体AFL Fuzz

随着客户端对直播短视频业务扩展及多媒体中台统一建设,如何高效治理解决多媒体业务带来的稳定性风险势在必行。针对多媒体文件变异,首先我们来了解下端上多媒体架构。

如上图,多媒体统一架构至下而上分为MediaFlow、UI层、互动层、对外接口及业务层。其中MediaFlow是多媒体核心基建,包含H264/AAC等音视频编解码能力、美颜视频算法、网络推流rtmp等,并实现多媒体原子能力接口统一输出。

因此针对多媒体模糊测试,我们瞄准MediaFlow的音视频编解码、音视频算法两部分,将端上MediaFlow代码裁剪实现linux平台移植,并构建多媒体文件输入Input入口,编译插桩执行AFL模糊测试。完成多媒体模糊测试主要分为两部分,MediaFlow裁剪移植与AFL模糊测试两部分。

1.MediaFlow 部分核心组件的裁剪分离化,重新组装了数据流转模式

当下线上运行的 MediaFlow 的运行逻辑如下图所示

数据封装成为 MFPacket后通过 Pipe在原子能力组件间进行流转,每一个原子能力组件又会被封装到一个 Runloop 中进行使用,这种运行模式并不适合进行直接的模糊测试,因为整个数据流转过程会产生一定的 Delay,拖慢整体的模糊测试流程的运行速度

目前通过代码逻辑上的调整,我们基于 MediaFlow 的预设组件来对 MediaFlow 中的原子能力进行了二次组装,得到了一个对模糊测试友好的测试实现方案,该测试方案在测试速度上相较于原始的技术方案有较大提升

2.AFL执行模糊测试

什么是AFL?AFL是一款测试工具,可以高效地对二进制程序进行Fuzzing,通过对代码插桩afl-gcc,以覆盖率数据调整样本,提高漏洞挖掘概率,充分挖掘可能存在的风险漏洞,特别适合多媒体文件风险挖掘。如下图是AFL变异原理流程:


1.源码编译时进行afl-gcc代码插桩,以记录代码覆盖率;

2.选择一些输入文件,作为初始测试集加入输入队列;

3.将队列中的文件按一定的策略变异,如Bitflip按位进行翻转,0变1,1变0;

4.如果经过变异,如果覆盖率有变动代表走到新的代码路径,则将其保留添加到队列中,作为优质种子继续变异;

5.整个Fuzz过程不断循环,期间触发了Crash的文件会被记录下来;

AFL Fuzz运行图:

结构化数据变异

传统Fuzz是无结构线性二进制数据适用于通过AFL进行突变,结构化变异通过理解结构体内容含义,语义进行数据突变,适合JSON/java对象/OC/结构体。

结构化数据模糊测试两步走:

1、递归遍历方式进行数据变异:结构化数据无法像二进制流数据方式Fuzz,容易导致还未进入程序内部,就因数据结构异常被丢弃了,同时针对不同数据类型可以采取不同策略进行修改数据。如JSON可以通过递归方式获取每个子节点数据进行变异,针对安卓Intent/Bundle数据可以通过反射方式修改Field。

2、随机值+语义化变异:Fuzz通常是随机性的,不关心数据类型字段含义进行变异,才能挖掘到一些出其不意的风险,这也是模糊测试的特点。但是有时候我们需要理解某些字段的含义,才能变异出更加符合真实场景下异常数据,走到更深的链路里,比如:针对Http请求返回"success":"false" ,如果返回了False可能导致页面打不开,子页面覆盖不到;发现字段类型是UI文案,则注入长字符或空字符可能导致控件重叠丢失;String可能是JSON/XML针对不同类型执行不同变异策略,因此在随机值变异的基础上,结合语义化变异能够组合变异出更多异常值,挖掘到更深链路。

如上图,种子变异分为通用变异和语义化变异。通用变异是基于种子数据特征梳理的变异策略,如针对文件类,则使用Bit变异,0变1/1变0等策略改变文件数据;边界值变异则是针对数字、字符长度、图片大小等空间边界进行枚举变异;语义化变异则是针对业务属性、数据类型采用符合业务场景的变异策略,如:Lottie动画文件则采取Bit位变异,客户端开关则使用边界值变异,Version版本号则采用版本号变异规则等。

闪退溯源

闪退溯源是什么?

客户端运行时存在同一时刻多种网络数据并发请求,无法知道当前出现的Crash由于哪个Config、Rpc配置错误导致,所以想要定位问题根因,将闪退与变更配置绑定是必不可少的,因此将某个闪退与某个变更做绑定叫闪退溯源。

为什么风险挖掘需要闪退溯源这个能力?

有了闪退溯源的能力,可以解决以下几个问题:

闪退溯源原理(该方案以Config为例)

要定位客户端闪退是由于哪个Config引起,首先需要在端上Hook收集Config CallGraph调用栈数据,存储到本地Database中。其次在客户端出现闪退时获取Crash Trace,并过滤掉系统和三方调用,再和数据库中的Config调用栈数据进行相似度匹配,分析定位出闪退根因,以下是溯源简易流程:

闪退溯源方案,主要分为以下几部分

开关切点调用栈收集

Hook开关并在invoke函数中获取当前线程的调用栈,即开关对应的函数调用链路数据。

Android:

IOS:

此时我们拿到的开关调用栈包含了系统和三方sdk的调用数据,因此我们需要通过关键字android.os、java.等剔除系统调用,最后拿到属于开关的调用栈数据。如下ta_extHub_api_*trace:

Android:

iOS:

通过Call Graph生成完整调用图

如上客户端Runtime收集的开关调用栈数据是不完整的,只能收集到开关调用前的链路,开关调用后的数据才是变更溯源的关键信息,因此我们通过静态代码分析Call Graph能力,获取到开关调用后的下游链路堆栈。

上图:上半部是通过运行时hook获取到堆栈调用路径,下半部是需要通过CG求解的路径

有了如上的分析后,我们能够收集到完整的开关调用图,并落到本地数据库。简单介绍下Call Graph。CG是一种流程控制图,表示着目标程序里各个子过程的相互调用关系,上图箭头代表着函数的调用方向,详细CG能力建设有Soot、astparser等静态分析工具,在这里不继续展开。

堆栈相似度匹配

常规的相似度算法如Edit Distance算法是通过比较两个字符串之间,由一个转成另一个所需要的最小编辑操作次数,一般编辑距离越小,两个字符串的相似度越大。如下图算法,按照levenshtein计算方式abc变换到abe只需要1个替换步骤。



a

b

c


0

1

2

3

a

1

0

1

2

b

2

1

0

1

e

3

2

1

1

综上字符串相似度算法,由于闪退堆栈与开关调用栈数据长度大小不统一相差过大,导致我们无法使用类似的相似度计算公式来匹配,针对堆栈匹配需要另辟蹊径。

本方案主要通过对闪退堆栈进行按行拆分,提取关键词模糊匹配方式获取高疑闪退开关,并按照一定权重排序。如下是开关webview_apid配置异常导致客户端闪退的堆栈,结合排查经验,可知道栈顶MyWebVSwitch.MyWebView是导致客户端闪退的根因,搜索数据库开关调用栈,如有包含Class/Method则认为该开关可能是引起闪退的根因,添加到队列中。

大部分情况下,我们拿闪退堆栈栈顶Class/Method去数据库里搜如果有命中开关是比较理想结果,往往数据库里可能找不到数据匹配开关,那么我们需要把闪退Trace从栈顶向栈底按行拆解,可以设置拆解的深度比如5行,并对每一行Class/Method关键字进行拆分多个关键字,按照排列组合方式与数据库里收集的开关堆栈进行模糊匹配,最后输出匹配结果。下图是闪退堆栈按行拆解,组合关键字匹配流程:

当我们根据闪退堆栈拆解出关键字并分析出开关集合后,可根据如下公式计算计算权重分将开关列表进行排序。

排序权重计算公式如上图(N=闪退堆栈大小,dep堆栈层级,ruleIndex为规则下标,factor因子越大权重值阶梯越明显),规则概要如下:

通过以上方法我们可以计算出开关调用堆栈与闪退堆栈的权重分数即相似度,并把开关按相似度进行排序,分数越大,命中率越大,通过该方式我们能在线下复现挖掘的风险。

自动化与数据上报

风险挖掘少不了自动化脚本,本方案使用slm自动化UI框架:monkey+深度UI遍历,最后将挖掘的风险上报到服务端蓝军平台,并建根据闪退数据一键“创建缺陷”+“新增演练案例”。

总结

通过线上问题及历史P级故障梳理沉淀,落地了多种风险类型挖掘,提升了挖掘广度,并持续打磨种子突变能力和闪退溯源等原子能力,提升了挖掘深度和代码覆盖率,在端上稳定性挖掘中有不错的收益,做到贴合业务场景发现真实问题。现在阶段挖掘能力以稳定性闪退为主,非闪退问题千变万化,针对不可用问题检测挖掘,我们只是解决了少部分问题,未来还需持续的探索,做好事前防御,把更多问题拦在线下。

作者:凡月

来源:微信公众号:蚂蚁质量AnTest

出处:https://mp.weixin.qq.com/s/WPjaMmQm9yccFM7eh6E4Hg

展开阅读全文

页面更新:2024-03-13

标签:客户端   风险   堆栈   安全生产   模糊   异常   能力   代码   业务   测试   数据

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top