为FlowDroid加上addJavascriptInterface分析(中)
接上篇,上篇主要遇到问题是Source无法对Parameter进行标记,本文两部分,第一部分讲如何加上标记并且完成整个CFG(隔的时间太长了,不大记得这个操作了),第二部分讲通过加入Callback的方式让JSInterface的代码变得可达,并且成功实现预期。
一、为什么直接标记JIdentifyStmt不被认为是Source呢
先看代码,AndroidSourceSinkManager.java
1 | protected SourceSinkDefinition getSource(Stmt sCallSite, IInfoflowCFG cfg) { |
输入是sCallSite ,就是整个dummyMain 的每条语句,上来就先判断了是否containsInvokeExpr() ,而JIdentifyStmt 是一个声明语句,当然不包含方法调用,所以第一句就被毙了。如果要使用这个分支的话,可以考虑将需要被taint的参数进行一次自己定义的clone的操作,对clone出来的参数标记为source。例如 func(String input) 时候,写为input = input.toString() ,此时将toString 作为产生source的method,就可以“曲线救国”,将参数进行标记。
这里涉及到三种Stmt,JIdentityStmt ——用于对参数赋值,JInvokeStmt ——用于调用但不赋值,JAssignStmt ——用于调用并且赋值。
二、曲线救场
思路就是这个思路,主要修改地方仍然是AndroidEntryPointCreator ,原本我们只是插入js远程调用的语句,测试时候参数本来也全都是String,所以这里额外插入一句,将每个参数都执行一遍toString ,对于自定义的复杂对象,可以使用SootClass.addMethod ,加入一个自定义的方法,只需要返回其本身即可,因为内部我们也会不进行分析,所以这样做还算完备。
构造出来的方法大概长这样
1 | $r0 = @this: JBridge |
此时,我们讲toString标记为Source,其返回值$r1 就可以被标记为Source。经过测试,确实能够在log里看到多出来的这个Source。但是,不会被寻路到Sink,什么概念呢,就是Source和Sink看起来是连着的,但是无法被forwardProblem 进行solve。这里思考了比较久,用了其他的几种方法,例如对比我们手动生成的$r1.toString 句子和soot反汇编出来$r1.toString的句子有什么区别;例如进行替换或者微小修改后,看是否能够正常运行;例如使用复制的方式构造stmt替换掉原来的stmt句子。
大量的证据表明,我们的stmt构造方式是没有问题的,没有被寻路寻到,很可能是我们添加stmt的前,callGraph 早就生成了而且没有被更新过,导致创造的语句虽然是Source,却是孤立的节点,没有上下文。
三、将JsInterface视为Callback处理
之后尝试了一些刷新cfg 的方法,后面也没有结果,思路也差不多用完了,可能需要换一条路了。正一筹莫展的时候,yufei说他搞定了,使用的是手动加入callback的思路。
FlowDroid有一个运行参数叫做,"-cs" ,表示CallbackSourceMode ,默认是SourceListOnly ,基本没啥用,改为AllParameters 时会主动标记回调方法的所有参数。举个例子,下面的代码,在默认时是没有检测结果的,设置为AllParameters就会有检测结果。
1 |
|
(为什么我要这样写呢,因为我发现了FlowDroid的一个bug,para2 那句其实是不会扫描出结果的,文末我会讲一下bug的产生和修复) 反思一下我的失败操作和yufei的成功操作,还是最开始思路的问题,我选了一条很长的调用链,修改的东西太多,本以为是个小坑,但后来发现是大坑,到最后全都填完了,也不知道为什么没有成功。另外,没有注意到CallbackSourceMode 的配置问题,所以也没有注意到getSource 后面的对于callback 判断的代码,导致绕了远路。还有就是对FlowDroid的整体架构没有那么了解,错误预估了难度,以后还是得先看再写。
四、代码实现
ok下面讲一下实现方式,主要涉及2个文件,AbstractCallbackAnalyzer 和DefaultCalbackAnalyzer 。
针对callback的分析,总入口在SetupApplication 里,跳到DefaultCallbackAnalyzer.collectCallbackMethods() ,针对每个class进行分析。
1 | for (SootClass sc : entryPointClasses) { |
同样是analyzeReachableMethod ,对每个方法进行分析,只需要关注其中的analyzeMethodForCallbackRegistrations 。
1 | while (reachableMethods.hasNext()) { |
原本的逻辑是将方法的每个参数确认一遍,是否为Set<String>androidCallbacks
里的成员,这个类是读取AndroidCallback.txt 里的每一行,例如View.OnClickListener 就是其中的一个。
以func(ClickListener obj) 为例,发现obj属于OnClickListener ,对其稍加判断,加入到Set<SootClass>callbackClasses
中,将来做一些额外的处理。这种回调的特点是无法确定是哪个方法设置了这个listener,所以存储的是obj的SootClass。
对于JsInterface的类,有个特点,它的位置特别固定,一定是addJavascriptInterface 的第一个参数的类,所以考虑这里加一层白名单,尽可能优雅地进行改动。代码不难写,先判断方法名,如果匹配了,就直接加到callbackClasses 里。
这时候,信息集中到了callbackClasses 里,寻找引用,发现在AbstractCallbackAnalyzer.analyzeClassInterfaceCallback ,会对MultiMap<SootClass, CallbackDefinition> callbackMethods
进行添加。
以MyOnClickListener 为例,找到其父类的interface,发现是onClick 这个方法,之后用MyOnClickListener 的onClick 方法建立CallbackDefinition ,加入到callbackMethods 中。
类比到JsInterface,并不是使用父类的interface作为入口的,而是使用JavascriptInterface 这个annotation ,所以需要使用一些代码来判断并且添加,如下的代码。
这时候,全部信息都集中到了callbackMethods 里,跟踪其引用,发现在AndroidSourceSinkManager.checkCallbackParameterSource 里,返回MethodSourceSinkDefinition 。这个方法会根据CallbackSourceMode 进行不同的行为,这里直接选择ALL的话,就可以自动将各个remote-js的参数标记为source了。
但我们想在无论哪种模式下,都对这种本来就是source的东西标记下,就加一层判断咯。
1 | // If JavascriptInterface, all parameters are sources |
恩,加上这三处patch,我们的功能就完全实现啦!是不是很简单呢~撒花~✿✿ヽ(゚▽゚)ノ✿✿
五、文末彩蛋——FlowDroid的一个bug
还记得这个testcase吗
1 |
|
测一下,para2 其实是打印不出来的,而且Intent intent 也没有被标记为source,这个显然不合理嘛。跟了很久,发现是AccessPathBasedSourceSinkManager.createSourceInfo 里的一个check过不去。(千万别看AndroidSourceSinkManager 的方法,那个被Override掉了)
1 | case Callback: |
这里判断了methodDef.getParameters.length 和 paramRef.getIndex ,在Callback 这个case里,前者永远是1,而且来自于createParameterSource 方法里的AccessPathTuple.getBlankSourceTuple() ,本身含义是空。而后者表示正在处理的参数是第几个。举个例子,onReceive(Context ctx, Intent intent) ,第一次经过这里时候,ctx 满足条件,打上了source标记;第二次经过时intent 作为第一个参数,1==1 ,过不去,就没有被标记。
对啊,这什么逻辑,显然不合理啊,根据猜测,这里的代码应该是另一处复制来的,本意为了检查当前处理的param 是否越界,但getParameters 的初始化有问题,导致这里是错的。
getParameters 返回的是可达路径,callback 一般是不可达的,所以这里是空路径,直接去掉这个check即可。而且整个project也只有这一处用到了Callback 类型的返回值,所以这样修复是没有任何问题的。
修复后,就可以标记全部的参数、而不是第一个参数啦。
六、总结
还有一点想法需要写,还是这种source的标记问题,onReceive 的第一个参数Context 其实是不需要被标记的,精准的callback标记并没有配置文件和选项。可以考虑对某些class加黑名单,或者checkCallbackParameterSource 时指定参数位置。
下篇时,讲一下如何实现精准的标记Source。