Skip to main content

flowdroid使用

·905 words·5 mins
ベーコン
Author
ベーコン
ljahum-beacon

辛粘块洛! #

  • 环境 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打开

调整下参数得到如下

分析出来结果如下

Untitled

很丑,过滤一下myapplication 的节点

Untitled

全选右键复制到新图

调整一下大小和颜色得到以下

Untitled

很奇怪没有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_

Untitled

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_

Untitled

Reference: インターネット