为FlowDroid加上addJavascriptInterface分析(中)

接上篇,上篇主要遇到问题是Source无法对Parameter进行标记,本文两部分,第一部分讲如何加上标记并且完成整个CFG(隔的时间太长了,不大记得这个操作了),第二部分讲通过加入Callback的方式让JSInterface的代码变得可达,并且成功实现预期。

一、为什么直接标记JIdentifyStmt不被认为是Source呢

先看代码, AndroidSourceSinkManager.java

输入是 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 ,加入一个自定义的方法,只需要返回其本身即可,因为内部我们也会不进行分析,所以这样做还算完备。

构造出来的方法大概长这样

此时,我们讲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就会有检测结果。

(为什么我要这样写呢,因为我发现了FlowDroid的一个bug, para2 那句其实是不会扫描出结果的,文末我会讲一下bug的产生和修复)

反思一下我的失败操作和yufei的成功操作,还是最开始思路的问题,我选了一条很长的调用链,修改的东西太多,本以为是个小坑,但后来发现是大坑,到最后全都填完了,也不知道为什么没有成功。另外,没有注意到 CallbackSourceMode 的配置问题,所以也没有注意到 getSource 后面的对于 callback 判断的代码,导致绕了远路。还有就是对FlowDroid的整体架构没有那么了解,错误预估了难度,以后还是得先看再写。

四、代码实现

ok下面讲一下实现方式,主要涉及2个文件, AbstractCallbackAnalyzer 和 DefaultCalbackAnalyzer 。

针对callback的分析,总入口在 SetupApplication 里,跳到 DefaultCallbackAnalyzer.collectCallbackMethods() ,针对每个class进行分析。

同样是 analyzeReachableMethod ,对每个方法进行分析,只需要关注其中的 analyzeMethodForCallbackRegistrations 。

原本的逻辑是将方法的每个参数确认一遍,是否为 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的东西标记下,就加一层判断咯。

恩,加上这三处patch,我们的功能就完全实现啦!是不是很简单呢~撒花~✿✿ヽ(゚▽゚)ノ✿✿

五、文末彩蛋——FlowDroid的一个bug

还记得这个testcase吗

测一下, para2 其实是打印不出来的,而且 Intent intent 也没有被标记为source,这个显然不合理嘛。跟了很久,发现是 AccessPathBasedSourceSinkManager.createSourceInfo 里的一个check过不去。(千万别看 AndroidSourceSinkManager 的方法,那个被Override掉了)

 

这里判断了 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。


=============================================================
随着访客的增多,LeadroyaL在本站流量的开支越来越多了,曾经1元能用1个月,现在1元只能用3天。如果觉得本文帮到了你,希望能够为服务器的流量稍微打赏一点,谢谢!

《为FlowDroid加上addJavascriptInterface分析(中)》有4个想法

  1. 您好,我最近刚开始研究flowdroid这个工具,想请教一下,flowdroid能否实现对动态注册的或是反射调用时的回调函数的检测呢?这个检测主要指可以用他提供的callgraph相关接口输出对应的函数调用。我这边自己尝试了一下,在反射中使用回调的话,最后的callgraph里没有对应的调用关系,直接new的话就可以输出这个调用关系。

    1. 反射处理起来很困难,我认为flowdroid是不具有处理反射的能力的;动态注册的是可以的,但需要修改代码,例如jsInterface可以视为动态注册,broadcast 可以视为动态注册。

      1. 那如果想定位反射中的调用关系,该用什么工具或是方法呢?还是说目前还没有相关的工具和方法可以实现

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code