使用FlowDroid生成Android应用程序的函数调用图
2015-09-30 13:12
691 查看
提到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原本工程(可以从他们的官方网站上找到下载)的依赖。
CGGenerator.java
CGExporter
生成的可视化的函数调用图
最后生成的调用图被保存成了gexf格式,这个格式在网络分析里面用的比较多。可以用软件Gephi打开生成的调用图。
生成的函数调用图,密密麻麻的全是节点。
还可以缩放,查看节点label,修改节点颜色等。
后记
FlowDroid比较强大,我自己也没搞太明白…………Soot里面有可以把调用图导出成dot格式的函数,但是节点比较多的时候我的电脑打不开那个dot文件了。可能是本人电脑比较渣吧。其实大部分时候获得了函数调用图就可以在上面进行分析了,没必要把它导出来。导出来只是为了人看着方便。
然而有时候我们并不需要使用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原本工程(可以从他们的官方网站上找到下载)的依赖。
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); } } } } }
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); } }
生成的可视化的函数调用图
最后生成的调用图被保存成了gexf格式,这个格式在网络分析里面用的比较多。可以用软件Gephi打开生成的调用图。
生成的函数调用图,密密麻麻的全是节点。
还可以缩放,查看节点label,修改节点颜色等。
后记
FlowDroid比较强大,我自己也没搞太明白…………Soot里面有可以把调用图导出成dot格式的函数,但是节点比较多的时候我的电脑打不开那个dot文件了。可能是本人电脑比较渣吧。其实大部分时候获得了函数调用图就可以在上面进行分析了,没必要把它导出来。导出来只是为了人看着方便。
相关文章推荐
- Android-Universal-Image-Loader 图片加载库
- 关于android下的raw文件的问题
- Android自定义控件之组合控件
- Android适配器进阶之up(修改完善)
- Android获取联系人电话实验
- android中部分资源的应用
- Android——apk反编译
- Android.mk
- RTMP 4 Android
- Android——init可执行程序
- Android事件分发机制
- AndroidStudio快捷键设置
- 【Android】 Activity启动模式singleTask的数据传输 onNewIntent方法
- Android启动脚本init.rc(2)
- Android正则表达式
- Android中手机号、车牌号正则表达式大全
- Android studio中右键项目没有subversion(SVN)解决办法
- android Get请求错误的问题 关于编码
- android线程间的通信机制
- 【Android】Broadcast Receiver的基本使用,推一条广播给多个Receiver