为FlowDroid加上addJavascriptInterface分析(上)

接上篇,上篇末尾提到FlowDroid本身是扫不到webview的js远程调用的,因为原理上讲带有JavascriptInterface注解的代码是不可达的,本文讲一下为如何从零开始,通过修改源码的方式,对FlowDroid进行修改和拓展。

一、为什么这么做

首先,FlowDroid本身是没有设计这种情况的,可能是出于难度,可能是出于疏忽。

然后,Jaadas总结了很多工业上的漏洞模式,对于javascriptInterface的扫描是有的,但扫到时候打印出报告就没有然后了,对于js远程调用后的危害性没有进行分析。

所以将js远程调用的方法内部分析一下,还是有必要的,可以减少人工成本。

最开始是想使用 FlowDroid提供的API,主要是 soot.jimple.infoflow.android.SetupApplication 的一些API,看了一圈没有太满意的,于是准备修改 FlowDroid源码。

二、简介

前一篇文章粗略介绍了一下 soot 和 FlowDroid ,这里详细讲一下逻辑。(可能有些小问题)

FlowDroid的功劳在于对 dummyMain的创建,在log和代码里可以看出,先扫一下 Manifest,确定各个入口和权限,对每个入口进行简单的分析,找到里面的 FragmentReceiver、其他回调,之后将它们也加入待分析的Set里。这也是为什么动态广播里注册动态广播时候无法被分析到的原因,这个操作只被执行一次,所以是有漏扫现象出现的。

之后调用 createMainMethod ,对每种组件(在 soot.jimple.infoflow.entryPointCreators.AndroidEntryPointUtils.ComponentType 里有定义),分别根据各自的生命周期,构建不同的调用顺序(在 soot.jimple.infoflow.AndroidEntryPointCreator.entryPointCreator ),然后调用 createDummyMainInternal 在 dummyMain 里将这些代码块串起来。

之后接着执行 public InfoflowResults runInfoflow(ISourceSinkDefinitionProvider sourcesAndSinks) 的 代码,对source/sink/taintWrapper进行处理,遍历 dummyMain 里的每个 Unit ,打上不同的标记,为将来的Solver做准备,分析的代码位于 infoflow.runAnalysis(sourceSinkManager, dummyMainMethod) ,代码非常多,后面边写边讲。

三、思路

【后面发现这个思路是不可行的,用另外的方法实现了—-2018.4.1】

根据对FlowDroid的了解,写一些demo进行测试,其实FlowDroid对动态注册广播是 registerReceiver 有处理的,这于 addJavascriptInterface 非常像,都是动态生成的、原本不可达的代码。

所以第一步是对比一下二者实现有什么不同,二者最大的不同是 Receiver的回调函数永远是固定的 onReceive,而js的回调各种各样,只要加了注解的都可以被调用到。

第二步,创建 Set来存放这些 Class JsInterface,将来作为entryPoint,打上唯一的标记和各种组件区分开。

第三步,模仿对组件的操作,遍历每个 JsInterface里的方法,将其插入到 dummyMain

第四步,对每个 JsInterface的方法的参数作为污点分析的Source。

第五步,继续后面的分析工作,别崩了。

四、代码修改

1、 soot.jimple.infoflow.android.callbacks.DefaultCallbackAnalyzer -> analyzeReachableMethod() (github上拼写有误)

顾名思义,分析所有可达代码的callback, JsInterface可以被认为是一种特殊的回调,所以这里在这里添加代码是没有任何问题的。

参数只需要一个, SootMethod,使用 SootMethod.getDeclaringClass()判断所在是否为 android.webkit.WebViewSootMethod.getName()判断名字是否为 addJavasciptInterface

一个问题是,这个类本身是没有任何标记的,将来提取会出现问题,所以需要手动给他加一个 jsInterface特有标记,用 implmentsInterface最方便,然后加入到Set里面,我们在 protected final Set<SootClass>dynamicManifestComponents后面加一条 jsInterfaceComponents即可。

下一个问题,自己定义的这个 SOOT_JS_INTERFACE_CLASS,只需要在全局存有一份即可,这里我们 Scene.v().addClass/getSootClass使用提供的全局存放 SootClass

这一步执行完了,就可以对 jsInterfaceComponents正确赋值,在对广播进行插入的时候,顺便将其插入,最后加入到 Set<SootClass> entryPoints中。

此时,可以使用 SetupApplication.printEntrypoints()来打log,可以看到我们已经添加进去的那个类。

再理一下逻辑,在 SetupApplication 里的总入口是 runInfoflow ,先解析Manifest文件拿到最初的入口,加入到 entryPoints 里,之后复制一份到 entrypointWorklist ,挨个调用 calculateCallbacks 。里面使用 DefaultCallbackAnalyzer.collectCallbackMethods ,再调用 analyzeReachableMethod ,得到各种callback,之后塞到 entryPoints 里。针对这些 entryPoints ,循环访问里面的onClick(主要是 collectXmlBasedCallbackMethods 产生的callback),直到没有额外的onClick为止。

2、 soot.jimple.infoflow.entryPointCreator.AndroidEntryPointCreator

这个类是生成 jimple代码的主要类,也是比较复杂、容易写错的地方(我至今也不确定这个地方是否写对了2018.3.23.)

FlowDroid生成的是一个巨大的 dummyMain,我们要做的就是将代码添加进去,编译通过,别崩了就行,让 CFG里将各个方法包含进去。好在原本有一些方法可以复用,简单调试后就跑通了。不放心的话可以将 body打印出来,看一下代码是否符合逻辑。

这里还留了一个坑,没法解决的,就是 SootMethod没有对于注解的标识,我们无法判断是否有 addJavascriptInterface,暂且只能全部丢进去,而且大部分情况也是有注解的。

另外一个无法解决的坑,就是这些js远程调用的顺序问题,毕竟人家js里想怎么调就怎么调,不跟公共组件一样是有固定顺序的,万一有些漏洞必须要按照一定顺序触发,这个是没法扫出来的。但是!我觉得没人会写这种愚蠢的代码出来,所以顺序就随缘吧,佛系扫描器,扫到扫不到全靠缘分。

3、 soot.jimple.infoflow.Infoflow -> scanMethodForSourcesSinks()

(这里临时patch了一下,没来得及写通用方法,而且是有问题的)

对于每个方法片段进行遍历,如果命中了source/sink的规则,就将该条语句标记为source/sink,这里是按照语句进行操作的,最终影响到的是某个变量是否为Source。对于有返回值的方法和构造方法使用起来运行正常,但我们这里的Source是传入的参数,例如下面的两句话,$r1是不会被作为Source的,$r5是可以作为Source的。

而且,经过测试,就算我们手动将$r1那句话标记为Source,也是无法被后续的Solver解出来的,所以这个地方还是有坑的,解决方法是有的,再包装一层自定义的Method,对产生的新变量进行标记。

4、等修改完了,传个github,给原po发一下pull request。

五、总结

嗯,现在遗留下来的问题是标记Source时候的通用方法,以及另外开一个wrapper来实现标记Source并且修改方法调用时的逻辑。(感觉还是很坑的,最初的思路走不下去,一直在改,改改。。。

敬请期待,下篇~


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

发表评论

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

*

code