Soot/Heros/FlowDroid/JAADAS 等安卓应用静态代码分析工具科普

最近在看一些静态代码分析相关的技术,主要用于自动化漏洞挖掘,看了大量文章和代码,从最开始的晕头转向到现在的略知一二,学到了不少东西,但了解不深,本文只能从科普的角度讲一下这些工具的用途和使用方法。

一、Soot

Soot 是一个历史很久的项目了,主要用途是将java字节码和dex字节码翻译成中间语言,可以有四种输出结果的表示方式,这里主要使用Jimple 这种表示方式,其实跟java非常像,用较少的信息来表示类、变量之间的关系,它只要15种opcode就可以描述。

Soot 是整个静态分析框架的基础,可以对apk文件进行反汇编,中间如何实现暂且不需要关注,代码里最终得到的是中间表示,通过API可以访问每个Jimple 的成员、调用关系等,根据这个关系,也可以生成肉眼可见描述CFG(control flow graph) 的图,看起来比较舒服。

二、Heros

Heros ,官方描述是"IFDS/IDE solver for Soot"。

IFDS, Interprocedural Finite Distributive Subset ,过程间有限分配子集(我瞎翻译的); IDE, Interprocedural Distributive Environment ,过程间分配环境(我瞎翻译的); 顾名思义,Soot 提供的只是所有类之间相互关联,Heros 则做到了从指定的变量到另一个指定的变量的寻路过程,也是简单的污点分析的基础,现已经被整合到Soot 的release包里。

三、FlowDroid

1、污点分析

科普一下污点分析的基本思路。一般来说污点分析建立在CFG上面,source 表示污点产生的位置,sink 表示目标触发的位置,taintWrapper 表示污点的传递规则,entry 表示程序的入口。举一个例子,下面的代码

1
2
3
4
5
6
void onCreate(Bundle b){
String secret = getSecret();
String mid = secret + "123";
String final = mid + "456";
Log.d("Tag", final);
}

这里可以认为 onCreate 是entry ,getSecret 是source ,Log.d 是sink ,String.append 是taintWrapper 。

污点从secret 到mid 再到final ,最后触发到信息泄漏的位置,一个分析就完成了。

当然,复杂的污点分析涉及到传递中断、不可达的特殊代码等,和前向判断、后向判断,这只是个简单的例子。

2、FlowDroid的改进点

安卓上的VM和PC上的JVM有很大的区别。一个区别是PC上的入口总是public static void main(String args[]) ,不可能是其他的入口;而安卓的入口是各个组件,一般从Manifest 进行解析,以及registerReceiver 的动态注册。另一个是安卓上组件的生命周期是由系统维护的,污点追踪可能因为生命周期回调函数的结束而中断。第三个区别,android里的layout 文件是可以设定回调函数的,例如onClick 事件这种,处理起来很坑的。

FlowDroid 核心部分就是针对android的优化,自己写wrapper来模拟生命周期,构建完整的CFG,从而为后续的分析提供基础。

3、用途用法

编译的话,下载好repo,里面有4个project,在目录下使用mvn -DskipTests install ,在线下载依赖,之后可以在目录下找到jar文件,soot-infoflow-cmd 是命令行里拿来调用的,其他几个repo提供API,全部编译一次大概要一分钟,还是比较久的。

比较关心的文件是SourceAndSinks.txt 和EasyTaintWrapperSource.txt ,使用过程中需要根据自身的需求去增删文件的内容,前者用来定义source 和sink ,后者用来定义污点传递规则。作者写的已经比较完备了,但不是非常全,这也是二次开发入手最直接的地方,直接修改配置文件,可拓展性比较强。默认的用途基本是一些信息泄漏和敏感API的调用,根据作者的描述,最开始也是为了分析恶意文件收集用户信息的行为,以及Log里打印敏感信息这种操作。

随便找个文件扫一扫,其实用时还是挺长的,可以考虑先删掉那些没用的support 类(强烈推荐我之前写的文章,哈哈哈)。Log里会写扫到几个source 和几个sink ,以及wrapper的hit数量,最后是否发现了可疑的问题,只要配置文件写的合理,就可以得到预期的结果。

四、Jaadas

JAADAS 是flanker学长在4年前写的一款工具,基于FlowDroid 提供的框架,用于扫描常见的漏洞,例如本地公开组件拒绝服务,不安全的SSL配置,addJavascriptInterface的远程代码执行风险等,重新写了一些配置文件和拓展功能。

wiki写的也比较明确,(准确说我前三者看的晕的不行,看完学长的wiki顿时明白了很多之前的误区),是一个优秀的FlowDroid 二次开发作品,对工业界自动化漏洞挖掘做出来巨大的贡献。但由于Scala写的,看的不是很懂。

五、其他

学习过程中,自己思考了一些FlowDroid 的缺陷,将来慢慢想办法去修复。

1、FlowDroid缺点一:是我傻逼了

2、FlowDroid缺点二javascriptInterface的处理

关于这个,一开始的思路是加上source 和sink ,这部分flanker是实现过的,但我想要的是js可以调用的方法里面是怎样的,这个有一定难度,比如下面的代码

1
2
3
4
5
6
7
8
9
10
11
void onCreate(Bundle b){
WebView webView = new WebView(this);
webView.addJavascriptInterface(new JSBridge(), "hello");
}

public class JSBridge{
@JavascriptInterface
void func1(String cmd){
Runtime.getRuntime().exec(cmd);
}
}

这里显然是有一个很危险的远程代码执行,但根据soot 的CFG来看,JSBridge 被创建后,并没有对func1 进行调用,属于不可达代码,而且FlowDroid 并没有针对这种情况做特殊处理。

我很想修一下,但对这些不熟,一时半会也修不好,思路是有的,如果对体系更加了解的话,可能可以马上搞定。。。

3、FlowDroid缺点三 自定义的控件

例如layout文件里的onClick 属性是可以对应到Java那边的某些方法的,类似,如果APP的开发者使用自定义的控件的话,也能实现某些不可达代码其实是可达的,FlowDroid 没用办法做通用型的设置,只能现场去改配置。

4、FlowDroid缺点四 多层嵌套

这个是在思考动态注册receiver问题时想到的一个情景,例如下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_target);

registerReceiver(new Step1Receiver(), new IntentFilter("com.leadroyal.AAA"));
}

private class Step1Receiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
String secret = "SecretAAA";
Log.d("AAA", secret);
registerReceiver(new Step2Receiver(), new IntentFilter("com.leadroyal.BBB"));
}
}
private class Step2Receiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
String secret = "SecretBBB";
Log.d("BBB", secret);
}
}

使用FlowDroid时候,SecretAAA是可以被扫描到的,SecretBBB无法被扫描到,因为Step2Receiver被认为是不可达的,缺少额外的一层扫描。

5、总结

Soot 提供中间件的表示和CFG的构建,Heros 提供可达性的解决,FlowDroid 针对安卓优化了CFG的生成方式,提供了开发的API,JADDAS 借助FlowDroid提供的CFG,加上自己的约束和设置,实现了对常见漏洞的扫描。

静态代码分析还是一个很高深的学问,本文只是科普介绍和分析,以后深入研究再出一点高质量的文章。