使用FlowDroid生成Android应用程序的函数调用图

提到Android应用程序静态分析,就不能不提Flowdroid。该工具是目前使用很广泛的Android应用程序数据流分析工具。它基于强大的Java分析工具Soot开发,提供了许多有用的功能。具体的介绍和使用帮助可以访问开发团队的网站 点这里。

然而有时候我们并不需要使用Flowdroid的全部功能,例如有时候我们只想要一个APK里面的函数调用图。当然我们可以使用androguard(最近好像又更新了)来生成函数调用图,或者用APKTool来反汇编APK,然后再自己编程搜索反汇编后的smali文件来生成函数调用图。但是我认为使用Flowdroid生成的调用图是比较有说服力的。下面直接放代码。

整个工程的目录结构,其中CGGenerator.java用来生成调用图,CGExporter.java里面封装了一些把调用图可视化的函数。除此之外还需要导入4个jar包:gexf4j.jar, AXMLPrinter2.jar, stax2-api-3.1.1.jar, woodstox-core-asl-4.0.6.jar。

使用FlowDroid生成Android应用程序的函数调用图

当然本工程还要导入对Flowdroid原本工程(可以从他们的官方网站上找到下载)的依赖。

使用FlowDroid生成Android应用程序的函数调用图

CGGenerator.java

package flowdroidcg;

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;

public class CGGenerator {

//设置android的jar包目录

public final static String jarPath = "/home/liu/Android/Sdk/platforms";

//设置要分析的APK文件

public final static String apk = "/home/liu/app-release.apk";

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

private static CGExporter cge = new CGExporter();

public static void main(String[] args){

SetupApplication app = new SetupApplication(jarPath, apk);

try{

//计算APK的入口点,这一步导入的文件是Flowdroid进行污点分析的时候需要的,这里直接新建一个空文件即可

app.calculateSourcesSinksEntrypoints("/home/liu/temp/sourcesAndSinks.txt");

}catch(Exception e){

e.printStackTrace();

}

soot.G.reset();

Options.v().set_src_prec(Options.src_prec_apk);

Options.v().set_process_dir(Collections.singletonList(apk));

Options.v().set_force_android_jar(jarPath + "/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();

//可视化函数调用图

visit(cg,entryPoint);

//导出函数调用图

cge.exportMIG("flowdroidCFG.gexf", "/home/liu/temp");

}

//可视化函数调用图的函数

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

//在soot中,函数的signature就是由该函数的类名,函数名,参数类型,以及返回值类型组成的字符串

String identifier = m.getSignature();

//记录是否已经处理过该点

visited.put(m.getSignature(), true);

//以函数的signature为label在图中添加该节点

cge.createNode(m.getSignature());

//获取调用该函数的函数

Iterator<MethodOrMethodContext> ptargets = new Targets(cg.edgesInto(m));

if(ptargets != null){

while(ptargets.hasNext())

{

SootMethod p = (SootMethod) ptargets.next();

if(p == null){

System.out.println("p is null");

}

if(!visited.containsKey(p.getSignature())){

visit(cg,p);

}

}

}

//获取该函数调用的函数

Iterator<MethodOrMethodContext> ctargets = new Targets(cg.edgesOutOf(m));

if(ctargets != null){

while(ctargets.hasNext())

{

SootMethod c = (SootMethod) ctargets.next();

if(c == null){

System.out.println("c is null");

}

//将被调用的函数加入图中

cge.createNode(c.getSignature());

//添加一条指向该被调函数的边

cge.linkNodeByID(identifier, c.getSignature());

if(!visited.containsKey(c.getSignature())){

//递归

visit(cg,c);

}

}

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

CGExporter

package flowdroidcg;

import it.uniroma1.dis.wsngroup.gexf4j.core.EdgeType;

import it.uniroma1.dis.wsngroup.gexf4j.core.Gexf;

import it.uniroma1.dis.wsngroup.gexf4j.core.Graph;

import it.uniroma1.dis.wsngroup.gexf4j.core.Mode;

import it.uniroma1.dis.wsngroup.gexf4j.core.Node;

import it.uniroma1.dis.wsngroup.gexf4j.core.data.Attribute;

import it.uniroma1.dis.wsngroup.gexf4j.core.data.AttributeClass;

import it.uniroma1.dis.wsngroup.gexf4j.core.data.AttributeList;

import it.uniroma1.dis.wsngroup.gexf4j.core.data.AttributeType;

import it.uniroma1.dis.wsngroup.gexf4j.core.impl.GexfImpl;

import it.uniroma1.dis.wsngroup.gexf4j.core.impl.StaxGraphWriter;

import it.uniroma1.dis.wsngroup.gexf4j.core.impl.data.AttributeListImpl;

import java.io.File;

import java.io.FileWriter;

import java.io.IOException;

import java.io.Writer;

import java.util.List;

public class CGExporter {

private Gexf gexf;

private Graph graph;

private Attribute codeArray;

private AttributeList attrList;

public CGExporter() {

this.gexf = new GexfImpl();

this.graph = this.gexf.getGraph();

this.gexf.getMetadata().setCreator("liu3237").setDescription("App method invoke graph");

this.gexf.setVisualization(true);

this.graph.setDefaultEdgeType(EdgeType.DIRECTED).setMode(Mode.STATIC);

this.attrList = new AttributeListImpl(AttributeClass.NODE);

this.graph.getAttributeLists().add(attrList);

//可以给每个节点设置一些属性,这里设置的属性名是 codeArray,实际上后面没用到

this.codeArray = this.attrList.createAttribute("0", AttributeType.STRING,"codeArray");

}

public void exportMIG(String graphName, String storeDir) {

String outPath = storeDir + "/" + graphName + ".gexf";

StaxGraphWriter graphWriter = new StaxGraphWriter();

File f = new File(outPath);

Writer out;

try {

out = new FileWriter(f, false);

graphWriter.writeToStream(this.gexf, out, "UTF-8");

} catch (IOException e) {

e.printStackTrace();

}

}

public Node getNodeByID(String Id) {

List<Node> nodes = this.graph.getNodes();

Node nodeFinded = null;

for (Node node : nodes) {

String nodeID = node.getId();

if (nodeID.equals(Id)) {

nodeFinded = node;

break;

}

}

return nodeFinded;

}

public void linkNodeByID(String sourceID, String targetID) {

Node sourceNode = this.getNodeByID(sourceID);

Node targetNode = this.getNodeByID(targetID);

if (sourceNode.equals(targetNode)) {

return;

}

if (!sourceNode.hasEdgeTo(targetID)) {

String edgeID = sourceID + "-->" + targetID;

sourceNode.connectTo(edgeID, "", EdgeType.DIRECTED, targetNode);

}

}

public void createNode(String m) {

String id = m;

String codes = "";

if (getNodeByID(id) != null) {

return;

}

Node node = this.graph.createNode(id);

node.setLabel(id).getAttributeValues().addValue(this.codeArray, codes);

node.setSize(20);

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

生成的可视化的函数调用图

最后生成的调用图被保存成了gexf格式,这个格式在网络分析里面用的比较多。可以用软件Gephi打开生成的调用图。

使用FlowDroid生成Android应用程序的函数调用图

生成的函数调用图,密密麻麻的全是节点。

还可以缩放,查看节点label,修改节点颜色等。

使用FlowDroid生成Android应用程序的函数调用图

后记

FlowDroid比较强大,我自己也没搞太明白…………Soot里面有可以把调用图导出成dot格式的函数,但是节点比较多的时候我的电脑打不开那个dot文件了。可能是本人电脑比较渣吧。其实大部分时候获得了函数调用图就可以在上面进行分析了,没必要把它导出来。导出来只是为了人看着方便。

相关推荐