Android学习第十一篇:获取apk的函数调用图

1,为什么要获取apk的函数调用图?

在进行android应用分析时,我们对apk的整体运作流程是未知的,通过函数调用图能很好的反映应用的执行流程。

2,如何获取?

2,1,新建项目experiment

Android学习第十一篇:获取apk的函数调用图

2,2,在该网址下载相应的jar包,并导入到项目当中

https://github.com/secure-software-engineering/soot-infoflow-android/wiki

Android学习第十一篇:获取apk的函数调用图

2,3,在eclipse中导入

https://github.com/secure-software-engineering/soot-infoflow-android/wiki

中的几个项目

Android学习第十一篇:获取apk的函数调用图

导入后会出错,根据博客http://blog.csdn.net/liu1075538266/article/details/62361295的方法,既删除所有heros的Build Path的MavenDependence Path即可解决问题。

2,4,编写代码

package com.lengbo.experiment;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import soot.MethodOrMethodContext;
import soot.PackManager;
import soot.Scene;
import soot.SootMethod;
import soot.jimple.infoflow.android.SetupApplication;
import soot.jimple.toolkits.callgraph.CallGraph;
import soot.jimple.toolkits.callgraph.Targets;
import soot.options.Options;
import soot.util.dot.DotGraph;
import soot.util.dot.DotGraphEdge;

public class CGGenerator {
    // 设置android的jar包目录
    public final static String androidPath = "/Volumes/Lengbo/AndroidSDK/platforms";
    // 设置要分析的APK文件
    public final static String apkPath = "/Volumes/Lengbo/MalwareDetection/MonkeyRunner/app-release.apk";
    // dot保存目录
    public final static String dotpath = "/Volumes/Lengbo/MalwareDetection/MonkeyRunner/";
    // 设置sourceAndSink目录
    public final static String sourceAndSink = "/Volumes/Lengbo/MalwareDetection/ToolsAndFiles/apps/sourcesAndSinks.txt";

    private static Map<String, Boolean> visited = new HashMap<String, Boolean>();
    private static CGExporter cge = new CGExporter();

    private static DotGraph dotGraph = new DotGraph("dot");

    public static void main(String[] args) {
        SetupApplication app = new SetupApplication(androidPath, apkPath);
        try {
            // 计算APK的入口点
            app.calculateSourcesSinksEntrypoints(sourceAndSink);
        } catch (Exception e) {
            e.printStackTrace();
        }
        soot.G.reset();

        Options.v().set_src_prec(Options.src_prec_apk);
        Options.v().set_process_dir(Collections.singletonList(apkPath));
        Options.v().set_force_android_jar(androidPath + "/android-21/android.jar");
        Options.v().set_whole_program(true);
        Options.v().set_allow_phantom_refs(true);
        Options.v().set_output_format(Options.output_format_none);
        Options.v().setPhaseOption("cg.spark verbose:true", "on");
        Scene.v().loadNecessaryClasses();

        SootMethod entryPoint = app.getEntryPointCreator().createDummyMain();
        Options.v().set_main_class(entryPoint.getSignature());
        Scene.v().setEntryPoints(Collections.singletonList(entryPoint));
        PackManager.v().runPacks();
        // 获取函数调用图
        CallGraph cg = Scene.v().getCallGraph();
        System.out.println("*********************************");
        System.out.println(entryPoint);
        System.out.println("*********************************");
        // System.out.println(cg.toString());
        System.out.println("*********************************");
        toDot(cg, entryPoint);
        File file = new File(dotpath, "test.dot");
        OutputStream out;
        try {
            out = new FileOutputStream(file);
            dotGraph.render(out, 0);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private static void toDot(CallGraph cg, SootMethod m) {

        Iterator<MethodOrMethodContext> inTargets = new Targets(cg.edgesInto(m));
        visited.put(m.getSignature(), true);
        if (inTargets != null) {
            while (inTargets.hasNext()) {
                SootMethod in = (SootMethod) inTargets.next();
                if (in == null) {
                    System.out.println("in is null");
                }
                System.out.println("----------------In Edge-----------");
                System.out.println(in + "  -->  " + m);
                DotGraphEdge dotGraphEdge = dotGraph.drawEdge(in + "", m + "");
                dotGraphEdge.setLabel("inEdge");
                dotGraph.drawNode(in + "");
                dotGraph.drawNode(m + "");
                System.out.println("************************************");
                if (!visited.containsKey(in.getSignature())) {
                    toDot(cg, in);
                }
            }
        }

        Iterator<MethodOrMethodContext> outTargets = new Targets(cg.edgesOutOf(m));
        if (outTargets != null) {
            while (outTargets.hasNext()) {
                SootMethod out = (SootMethod) outTargets.next();
                if (out == null) {
                    System.out.println("out is null");
                }
                System.out.println("----------------Out Edge-----------");
                System.out.println(m + "  -->  " + out);
                DotGraphEdge dotGraphEdge = dotGraph.drawEdge(m + "", out + "");
                dotGraphEdge.setLabel("outEdge");
                dotGraph.drawNode(m + "");
                dotGraph.drawNode(out + "");
                System.out.println("************************************");
                if (!visited.containsKey(out.getSignature())) {
                    toDot(cg, out);
                }
            }
        }
    }
}

2.5,运行即可得到函数调用图

Android学习第十一篇:获取apk的函数调用图

3,问题与解答

3,1,注意(2,3)下载依赖工程时,需用删除heros的依赖关系,则所报的错误都会解决

3,2,每个CallGraph对象其实是一个边的容器,代表了所有相关的边的集合。通过.edgesInto()可以获得到某条边的所有边,.edgesOutOf()可以获得某条边能到的所有边。

3.3,当你运行该程序到较低版本的android apk时,遇到的sourcetype错误,是你没有加入相应的sdk版本,目前我遇到的有android platforms最低是android-3