浅入浅出FlowDroid(二):handler、wrapper与abstraction

前言

距离这个系列第一篇隔得时间有点久,主要还是比较懒以及不懒的时候又比较忙(被迫不懒),争取后面尽可能完善这个系列,因为很多FlowDroid的细节已经忘差不多了。同时随着接触更多的论文,还是建议大家如果对污点分析的准确性要求较高甚至说以此为创新/贡献的话不要考虑直接使用FlowDroid来作为程序的一部分(估摸着工业界做Android静态分析的也看不上flowdroid,写起来不懂给谁看233)。

FlowDroid针对Android的各类处理还是很值得刚接触静态分析时进行借鉴参考,但终归都是些基于函数签名的非启发式规则,灵活性很差,并且作为一款2014年诞生的工具即便在不断维护终究还是缺乏一些对新特性的支持。

后续的内容(如果有的话)大概就是对FlowDroid整个流程的解析、对IFDS算法的介绍与代码解析、对Callback等特殊机制处理的解析以及一些简单的soot用法,至于完成时间就不大清楚了

简介

本篇将介绍TaintHandler、TaintWrapper的使用以及Abstraction的构成。这三部分没有什么直接地联系,因为它们既与其他部分没什么关系又不会展开太多的篇幅,索性就丢到了一起。

TaintPropagationHandler

如果不是单纯地将FlowDroid用于污点分析,就需要使用这个interface来实现一些自己的逻辑。

说明

当初查看的一些博客、文档时都没有提到这个接口,还是扒拉源码的时候发现在各种heros.FlowFunction的实现里,总会在computeTargets方法里调用一个notifyOutFlowHandler方法,而且总会实现一个computeTargetsInternal方法并在其中调用taintPropagationHandler.notifyFlowIn方法。

上面提到一些的类与方法,这里仅做一个简单说明——各种FlowFunction的实例会在soot.jimple.infoflow.solver.fastSolver.IFDSSolver.propagate方法里根据当前所分析的语句类型被获取并调用computeTargets方法,更简单地来说每一次被taint的数据的“传播”都会根据传播方式调用对应的computeTargets方法的实现,而无论何种类型的该方法都有在处理单次“传播”的开头与结束调用taintPropagationHandler的notifyFlowIn和notifyFlowOut两种方法。很显然,如果我们想获取数据流的传播,就需要根据需求自行实现这一接口的逻辑并进行设置。

细心的话会发现我一开始提的computeTargets末尾调用的是notifyOutFlowHandler而不是taintPropagationHandler.notifyFlowOut,FlowDroid在这里做了一个简单的封装,仅当传播分析结果的outgoing符合一定的条件才会调用taintPropagationHandler的方法,这里需要注意outgoing为null或者isEmpty返回true时taintPropagationHandler都不会被调用,如果需要设计一些针对传播终点的规则要注意这一点。

使用

首先当然要import接口soot.jimple.infoflow.handlers.TaintPropagationHandler并声明一个implements它的类,类中需要实现下面这两个抽象方法

public void notifyFlowIn(Unit stmt, Abstraction taint, InfoflowManager manager, FlowFunctionType type)
public Set notifyFlowOut(Unit stmt, Abstraction d1, Abstraction incoming, Set outgoing, InfoflowManager manager, FlowFunctionType type)

第一个参数stmt具体是对应哪个语句我忘了,但一定要注意这个变量并不对应当前分析的语句,想要获取这次“传播”中当前分析的语句需要在notifyFlowIn中调用taint.getCurrentStmt,而获取“传播”的下一句需要遍历notifyFlowOut的outgoing集合中每一个元素并调用getCurrentStmt。

manager可以用于获取一些全局的变量,比如我想看一看现在分析的语句是在哪个类哪个方法里,就可以通过manager.getICFG方法获取CallFunctionGraph并调用getMethodOf获取语句所属的SootMethod。需要注意Abstraction.getCurrentStmt与IInfoflowCFG.getMethodOf两个方法都有可能返回null,能力有限尚不清楚原因。

最后要注意,nofityFlowOut的实现里要返回outgoing或者根据你的需求过滤后的Abstraction集合,否则这一条“路径”的污点分析就停止了。

TaintWrapper

这个类主要用于设计一些数据流分析的“捷径”,可以让FlowDroid在分析到特定语句和方法时,根据wrapper中的规则直接taint语句中某一变量或是kill这一分析,而不是基于最基础的语法的rule拆解语句并进入方法内,目的当然是提高分析效率并处理一些特殊情况。FlowDroid自己实现了一个EasyTaintWrapper在soot.jimple.infoflow.taintWrappers包中,实现自己的wrapper主要就是要参考这个类是如何进行规则的设置。说实话当时我都没有研究透这个东西,何况现在忘得差不多了,所以这里只做一些简单的介绍。

可以选择实现ITaintPropagationWrapper或者继承AbstractionTaintWrapper中的一种方式来实现自己的wrapper,相比较而言继承AbstractionTaintWrapper的方式当然需要的工作更少,主要需要实现以下几个方法。

public boolean isExclusiveInternal(Stmt stmt, AccessPath taintedPath)
public Set getTaintsForMethodInternal(Stmt stmt, AccessPath taintedPath)
public Set getAliasesForMethod(Stmt stmt, Abstraction d1, Abstraction taintedPath)
public boolean supportsCallee(SootMethod method)
public boolean supportsCallee(Stmt callSite)

isExclusiveInternal方法返回true时表示传入的语句只产生由该wrapper生成的传播结果,反之则既会根据wrapper的规则产生传播也会根据FlowDroid原有的规则产生传播;getAliasesForMethod这个方法我也不大懂,注释中大意是返回语句中的方法的别名,这个别名应该也是数据流分析专有的名词,不了解的话可以跟EasyTaintWrapper一样直接返回null;两个supportCallee方法作用是一样的,如果返回true则表示这一wrapper可以从传入的method或callsite中生成新的传播,注释中还说到“might be removed if not needed elsewhere",暂不清楚只是不在被wrapper分析还是会对整个数据流分析产生影响,不了解的话可以去照抄EasyTaintWrapper的实现或者更求稳地总是返回true。

最核心的就是getTaintsForMethodInternal方法,它会根据其中实现的规则返回产生的新的taint。根据所设计的规则可能调用的方法千差万别,故这里不做一些展开说明,最后生成的AccessPath需要调用manager.getAccessPathFactory().createAccessPath(val, taintedPath.getTaintSubFields())获取,val表示分析这一语句后需要taint的变量。

Abstraction

这个类用于表示数据流传播图中的节点,类文件在soot.jimple.infoflow.data包中。去查看类文件的话会发现其中有很多看着很有用的成员变量,但在我实际使用中发现很多变量其实一直是null,FlowDroid其实没有在构建数据流传播图的同时更新里面的一些值,当然也可能是我个人的原因,或者新版本的FlowDoird现在已经更新了。总之这里主要就提一下accessPath和currentStmt两个类成员,类方法基本都是一些get/set或者用于根据参数生成新的Abstraction,这里不再叙述。

accessPath

这个成员存储的是被taint的变量本身的信息,其中AccessPath.value存储taint的变量的base,AccessPath.fields则是存储可能有的域成员。如果value存储了变量a,fields数组发现值为{b, c},则实际上被taint的变量是a.b.c。另外如果被taint的对象类型为数组,则AccessPath.arrayTaintType会存储一个枚举值来表示到底是数组成员被taint了还是数组长度来自被taint的变量(不要指望能动态追踪taint的数组哪个index或者集合中究竟哪个元素被taint了,FlowDroid只会简单的把数组与集合对象一起taint)。

currentStmt

这个成员存储了被taint的变量所在语句的信息,本身是soot中的stmt类型。soot里提供了非常多的stmt的子类,比如跳转语句、方法调用语句、条件判断语句等等,可以通过instanceof进行判断后,装换成特定子类获取一些进一步的信息。

其他

因为可能莫得后文了,先提一下如果一些特殊情形中需要做数据流的分析却又难以圈定可能的sink方法,可以在soot.jimple.infoflow.infoflow.runAnalysis方法中修改sinkCount的值或者直接修改相关if block中的逻辑,使得FlowDroid可以继续运行。否则,如果sink方法未被发现,FlowDroid不会继续运行。

另外在FlowDroid 2.6.1里,如果设置callback类型方法作为source点会触发bug,原因是FlowDroid在初始化中针对source生成MethodSourceSinkDefinition时未声明parameters参数使得被设置了默认值null,但在后续将callback方法作为source时需要taint方法的参数所以会报错。需要修改soot.jimple.infoflow.android.data.parsers.PermissionMethodParser.parse()方法如下:

private void parse() {
    // ...
    
    for (AndroidMethod am : methods.values()) {
        SourceSinkDefinition singleMethod = null;

        // new added
        if (am.getReturnType().contains("void") && am.getMethodName().startsWith("on")) {
            List types = am.getParameters();
            @SuppressWarnings("unchecked")
            Set[] params = (Set[] )new Set[am.getParameters().size()];
            for (int i = 0; i < types.size(); i++) {
                List param = new ArrayList();
                param.add(types.get(i));
                AccessPathTuple apt = AccessPathTuple.fromPathElements(null, param, SourceSinkType.Source);
                params[i] = Collections.singleton(apt);
            }
                
            singleMethod = new MethodSourceSinkDefinition(am, null, params, null, CallType.Callback);
        }
            
        // origin part
        if (singleMethod == null) singleMethod = new MethodSourceSinkDefinition(am);
        if (am.getSourceSinkType().isSource())
            sourceList.add(singleMethod);
        if (am.getSourceSinkType().isSink())
            sinkList.add(singleMethod);
        if (am.getSourceSinkType() == SourceSinkType.Neither)
            neitherList.add(singleMethod);
    }
}

我这里只是省略了for循环前面的代码,需要做的只是在对应位置添加“// new added"注释下面一部分的代码,"// origin part"便是原来的代码无需修改,其他代码也不要删除。

你可能感兴趣的:(android,静态分析)