提到Android应用程序静态分析,就不能不提Flowdroid。该工具是目前使用很广泛的Android应用程序数据流分析工具。它基于强大的Java分析工具Soot开发,提供了许多有用的功能。具体的介绍和使用帮助可以访问开发团队的网站 点这里。
然而有时候我们并不需要使用Flowdroid的全部功能,例如有时候我们只想要一个APK里面的函数调用图。当然我们可以使用androguard(最近好像又更新了)来生成函数调用图,或者用APKTool来反汇编APK,然后再自己编程搜索反汇编后的smali文件来生成函数调用图。但是我认为使用Flowdroid生成的调用图是比较有说服力的。下面直接放代码。
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 {
public final static String jarPath = "/home/liu/Android/Sdk/platforms";
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{
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){
String identifier = m.getSignature();
visited.put(m.getSignature(), true);
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
- 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
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);
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
- 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打开生成的调用图。
生成的函数调用图,密密麻麻的全是节点。
还可以缩放,查看节点label,修改节点颜色等。
- 后记
FlowDroid比较强大,我自己也没搞太明白…………Soot里面有可以把调用图导出成dot格式的函数,但是节点比较多的时候我的电脑打不开那个dot文件了。可能是本人电脑比较渣吧。其实大部分时候获得了函数调用图就可以在上面进行分析了,没必要把它导出来。导出来只是为了人看着方便。
原文地址: http://blog.csdn.net/liu3237/article/details/48827523