为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,确定各个入口和权限,对每个入口进行简单的分析,找到里面的Fragment、Receiver、其他回调,之后将它们也加入待分析的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.WebView,SootMethod.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()

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

1
2
3
4
$r0 = @this: JBridge
$r1 = @parameter0: java.lang.String

$r5 = virtualinvoke $r4.<android.telephony.TelephonyManager: java.lang.String getDeviceID()>()

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

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

五、总结

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