flowdroid使用
Table of Contents
辛粘块洛! #
- 环境 win11
- ide:idea2023
- jdk17、jdk8
最近在做隐私相关的工作,手工逆向CTF还行,在商用app里面大浪淘沙太痛苦了
花了一天时间看了看怎么玩这个东西,感觉还行
导入dependencies
官网给的用不了,上网找了一个
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>untitled</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>de.fraunhofer.sit.sse.flowdroid</groupId>
<artifactId>soot-infoflow</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>de.fraunhofer.sit.sse.flowdroid</groupId>
<artifactId>soot-infoflow-summaries</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>de.fraunhofer.sit.sse.flowdroid</groupId>
<artifactId>soot-infoflow-android</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>stax2-api</artifactId>
<version>3.1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.codehaus.woodstox/woodstox-core-asl -->
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>woodstox-core-asl</artifactId>
<version>4.0.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/it.uniroma1.dis.wsngroup.gexf4j/gexf4j -->
<dependency>
<groupId>it.uniroma1.dis.wsngroup.gexf4j</groupId>
<artifactId>gexf4j</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
抄个生成函数调用图的代码 输出cge格式 #
CGExporter.java
package org.example;
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);
}
}
CGGenerator.java
package org.example;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import soot.MethodOrMethodContext;
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 java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class CGGenerator {
//设置android的jar包目录 你自己的AndroidSdk位置,下载安卓studio自动安装
public final static String androidPlatformPath = "D:\\AndroidSdk\\platforms";
//设置要分析的APK文件
public final static String appPath = "C:\\Users\\test_user\\Desktop\\flowdroidJarTest\\FlowDroid-2.9\\DroidBench\\app-release.apk";
public final static String outputPath = "C:\\Users\\test_user\\Desktop\\untitled\\src\\main\\apps\\output";
static Object ob = new Object();
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(androidPlatformPath, appPath);
soot.G.reset();
//
//github上作者记录的AndroidCallbacks
//这里用于检测leaks和生成ceg图无关
String file = "C:\\Users\\test_user\\Desktop\\untitled\\src\\main\\java\\AndroidCallbacks2.txt";
System.out.println(file);
app.setCallbackFile(file);
app.constructCallgraph();
//SootMethod 获取函数调用图
SootMethod entryPoint = app.getDummyMainMethod();
CallGraph cg = Scene.v().getCallGraph();
//可视化函数调用图
visit(cg,entryPoint);
//导出函数调用图
cge.exportMIG("flowdroidCFG", outputPath);
}
//可视化函数调用图的函数
private static void visit(CallGraph cg,SootMethod m){
//在soot中,函数的signature就是由该函数的类名,函数名,参数类型,以及返回值类型组成的字符串
String identifier = m.getSignature();
//记录是否已经处理过该点
visited.put(identifier, true);
//以函数的signature为label在图中添加该节点
cge.createNode(identifier);
//获取调用该函数的函数
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);
}
}
}
}
}
这个代码可以在jdk17、11的环境下直接跑
其他地方先不用管,更改sdk版本、apk位置、AndroidCallbacks直接下载下来就ok了
TEST #
简简单单写个demo
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import java.io.File;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout1);
String secret = getSecret();
String mid = secret + "123";
String tmp = mid + "456";
func(tmp);
}
public String getSecret(){
return "Hello";
}
public void func(String tmp){
Log.d("Tag", tmp);
}
}
跑完了会在output目录生成个flowdroidCFG.gexf
下载个Gephi打开
调整下参数得到如下
分析出来结果如下
很丑,过滤一下myapplication
的节点
全选右键复制到新图
调整一下大小和颜色得到以下
很奇怪没有getSecret
flowdroid jar包使用 #
官网下载
- soot-infoflow-cmd-2.9.0-jar-with-dependencies.jar
- FlowDroid-2.9.zip解压
做三件事
建立以下三个文件夹并放入对应文件
(1)你的app
FlowDroid-2.9\DroidBench\xxx.apk
(2)
FlowDroid-2.9\soot-infoflow-cmd\targe\soot-infoflow-cmd-2.9.0-jar-with-dependencies.jar
(3)把
AndroidSdk(你的安卓sdk目录)\platforms\android-33\android.jar复制到FlowDroid-2.9\android\android.jar
编写自己的SourcesAndSinks.txt #
_SOURCE_代表起点
_SINK_代表终点
如果有路径从_SOURCE_ to _SINK_则表示一次leak
% 代表注释
<java.lang.String: int hashCode()>
这种格式函数的id可以从CGGenerator
生成的图在gephi中用导出功能导出的Excel表格获取
很奇怪输出的Excel和Gephi给出的表格并不完全一样,应该是去掉了回环的路径
Source | Target | Type | Label | Weight |
---|---|---|---|---|
<dummyMainClass: com.example.myapplication.MainActivity dummyMainMethod_com_example_myapplication_MainActivity(android.content.Intent)> | <com.example.myapplication.MainActivity: void |
Directed | 1 | |
<com.example.myapplication.MainActivity: void onCreate(android.os.Bundle)> | <com.example.myapplication.MainActivity: void func(java.lang.String)> | Directed | 1 | |
<dummyMainClass: com.example.myapplication.MainActivity dummyMainMethod_com_example_myapplication_MainActivity(android.content.Intent)> | <com.example.myapplication.MainActivity: void onCreate(android.os.Bundle)> | Directed | 1 |
SourcesAndSinks正文
% <javax.servlet.ServletRequest: java.lang.String getParameter(java.lang.String)> -> _SOURCE_
% <javax.persistence.EntityManager: javax.persistence.TypedQuery createQuery(java.lang.String,java.lang.Class)> -> _SINK_
% <javax.servlet.http.HttpServletResponse: void sendRedirect(java.lang.String)> -> _SINK_
% <java.io.File: boolean delete()> -> _SINK_
% <java.io.File: boolean delete()> -> _SINK_
<com.example.myapplication.MainActivity: String getSecret(void)> -> _SOURCE_
<com.example.myapplication.MainActivity: void onCreate(android.os.Bundle)> -> _SOURCE_
<com.example.myapplication.MainActivity: void func(java.lang.String)> -> _SINK_
<java.lang.String: int hashCode()> -> _SOURCE_
<java.lang.RuntimeException: void <init>(java.lang.String)> -> _SINK_
% <android.content.SharedPreferences$Editor: android.content.SharedPreferences$Editor putBoolean(java.lang.String,boolean)> -> _SINK_
% <android.content.SharedPreferences$Editor: android.content.SharedPreferences$Editor putFloat(java.lang.String,float)> -> _SINK_
% <android.content.SharedPreferences$Editor: android.content.SharedPreferences$Editor putInt(java.lang.String,int)> -> _SINK_
% <android.content.SharedPreferences$Editor: android.content.SharedPreferences$Editor putLong(java.lang.String,long)> -> _SINK_
% <android.content.SharedPreferences$Editor: android.content.SharedPreferences$Editor putString(java.lang.String,java.lang.String)> -> _SINK_
编写启动脚本
github issus上的老哥说jdk1.8才能稳定运行。。。
哥们在powershell上跑的,在cmd或者bash上跑要自己改一下格式
& 'C:\Program Files\Java\jre-1.8\bin\java.exe' -jar soot-infoflow-cmd/target/soot-infoflow-cmd-2.9.0-jar-with-dependencies.jar `
-a ./DroidBench/app-release.apk `
-p android/android.jar `
-s soot-infoflow-android/SourcesAndSinksTest.txt `
# -o sootOutput
运行,然后就可以开一把紧张刺激的英雄联盟慢慢等了
...
[main] INFO soot.jimple.infoflow.memory.MemoryWarningSystem - Shutting down the memory warning system...
[main] INFO soot.jimple.infoflow.android.SetupApplication$InPlaceInfoflow - Memory consumption after path building: 452 MB
[main] INFO soot.jimple.infoflow.android.SetupApplication$InPlaceInfoflow - Path reconstruction took 2 seconds
[main] INFO soot.jimple.infoflow.android.SetupApplication$InPlaceInfoflow - The sink virtualinvoke r0.<com.example.myapplication.MainActivity: void func(java.lang.String)>($r2) in method <com.example.myapplication.MainActivity: void onCreate(android.os.Bundle)> was called with values from the following sources:
[main] INFO soot.jimple.infoflow.android.SetupApplication$InPlaceInfoflow - - $i2 = virtualinvoke $r3.<java.lang.Object: int hashCode()>() in method <androidx.collection.SimpleArrayMap: int hashCode()>
[main] INFO soot.jimple.infoflow.android.SetupApplication$InPlaceInfoflow - - $i1 = virtualinvoke $r2.<java.lang.Object: int hashCode()>() in method <androidx.collection.SimpleArrayMap: java.lang.Object put(java.lang.Object,java.lang.Object)>
[main] INFO soot.jimple.infoflow.android.SetupApplication$InPlaceInfoflow - Data flow solver took 395 seconds. Maximum memory consumption: 11391 MB
[main] INFO soot.jimple.infoflow.android.SetupApplication - Found 1 leaks
(base) PS C:\Users\test_user\Desktop\flowdroidJarTest\FlowDroid-2.9>
生成运行会得到Found xxx leaks
confusing #
但是很奇怪的是,如果source和sink只包含main包里面的方法(),则找不到source和sink,
SourcesAndSinks 包含MainActivity以外的函数
<com.example.myapplication.MainActivity: String getSecret(void)> -> _SOURCE_
<com.example.myapplication.MainActivity: void onCreate(android.os.Bundle)> -> _SOURCE_
<com.example.myapplication.MainActivity: void func(java.lang.String)> -> _SINK_
<java.lang.String: int hashCode()> -> _SOURCE_
<java.lang.RuntimeException: void <init>(java.lang.String)> -> _SINK_
SourcesAndSinks 不包含MainActivity以外的函数
<com.example.myapplication.MainActivity: String getSecret(void)> -> _SOURCE_
<com.example.myapplication.MainActivity: void onCreate(android.os.Bundle)> -> _SOURCE_
<com.example.myapplication.MainActivity: void func(java.lang.String)> -> _SINK_
% <java.lang.String: int hashCode()> -> _SOURCE_
% <java.lang.RuntimeException: void <init>(java.lang.String)> -> _SINK_
Reference: インターネット