Skip to content

MASTG-DEMO-0106: Extracting Sensitive Data from Cipher.doFinal via Frida Hooking

Download MASTG-DEMO-0106 APK Open MASTG-DEMO-0106 Folder Build MASTG-DEMO-0106 APK

Sample

This sample encrypts and decrypts a sensitive API key using AES/GCM via the Android KeyStore. The app does not implement any runtime hook detection mechanisms. On the contrary, Detecting Frida hooks and terminating the application on response demonstrates a runtime hook detection mechanism.

Note

This is a series of correlated tests.

MastgTest.kt
 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
package org.owasp.mastestapp

import android.content.Context
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec

class MastgTest(private val context: Context) {

    private val sensitiveApiKey = "sk-OWASP-MAS-SuperSecretKey-1234567890"
    private val keyAlias = "mastgCipherKey"

    private fun getOrCreateSecretKey(): SecretKey {
        val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
        return if (keyStore.containsAlias(keyAlias)) {
            (keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry).secretKey
        } else {
            KeyGenerator.getInstance(
                KeyProperties.KEY_ALGORITHM_AES,
                "AndroidKeyStore"
            ).apply {
                init(
                    KeyGenParameterSpec.Builder(
                        keyAlias,
                        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
                    )
                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                        .build()
                )
            }.generateKey()
        }
    }

    fun mastgTest(): String {
        return try {
            val key = getOrCreateSecretKey()

            // Encrypt the sensitive API key
            val encryptCipher = Cipher.getInstance("AES/GCM/NoPadding")
            encryptCipher.init(Cipher.ENCRYPT_MODE, key)
            val iv = encryptCipher.iv
            val encryptedBytes = encryptCipher.doFinal(sensitiveApiKey.toByteArray(Charsets.UTF_8))
            val encryptedData = Base64.encodeToString(iv + encryptedBytes, Base64.DEFAULT)

            // Decrypt to verify
            val decodedData = Base64.decode(encryptedData, Base64.DEFAULT)
            val ivFromData = decodedData.copyOfRange(0, 12)
            val ciphertext = decodedData.copyOfRange(12, decodedData.size)

            val decryptCipher = Cipher.getInstance("AES/GCM/NoPadding")
            decryptCipher.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(128, ivFromData))
            val decryptedBytes = decryptCipher.doFinal(ciphertext)
            val decryptedString = String(decryptedBytes, Charsets.UTF_8)

            "Encryption and decryption successful.\n" +
                "Encrypted: $encryptedData\n" +
                "Decrypted: $decryptedString"
        } catch (e: Exception) {
            "Error: ${e.message}"
        }
    }
}

Steps

  1. Install the app on a device ( Installing Apps)
  2. Make sure you have Frooky installed on your machine and the frida-server running on the device
  3. Run run.sh to spawn the app with Frida
  4. Click the Start button
  5. Stop the script by pressing Ctrl+C and/or q to quit the Frida CLI
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "category": "CRYPTO",
  "hooks": [
    {
      "class": "javax.crypto.Cipher",
      "methods": [
        "doFinal"
      ]
    }
  ]
}
1
2
#!/bin/bash
frooky -U -f org.owasp.mastestapp --platform android hooks.json

Observation

The output contains all instances of Cipher.doFinal() method calls found at runtime. A backtrace is also provided to help identify the location in the code. The first doFinal call reveals the sensitive API key in plaintext as the input parameter during encryption and the second call reveals it as the return value during decryption.

output.json
  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
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
{
    "type": "summary",
    "hooks": [
        {
            "class": "javax.crypto.Cipher",
            "method": "doFinal",
            "overloads": [
                {
                    "args": [
                        "java.nio.ByteBuffer",
                        "java.nio.ByteBuffer"
                    ]
                },
                {
                    "args": [
                        "[B",
                        "int"
                    ]
                },
                {
                    "args": [
                        "[B",
                        "int",
                        "int",
                        "[B"
                    ]
                },
                {
                    "args": [
                        "[B",
                        "int",
                        "int",
                        "[B",
                        "int"
                    ]
                },
                {
                    "args": []
                },
                {
                    "args": [
                        "[B"
                    ]
                },
                {
                    "args": [
                        "[B",
                        "int",
                        "int"
                    ]
                }
            ]
        }
    ],
    "totalHooks": 7,
    "errors": [],
    "totalErrors": 0
}
{
    "id": "292363a7-8955-4051-9855-fed077c7226f",
    "type": "hook",
    "category": "CRYPTO",
    "time": "2026-02-09T14:00:41.068Z",
    "class": "javax.crypto.Cipher",
    "method": "doFinal",
    "instanceId": 55738552,
    "stackTrace": [
        "javax.crypto.Cipher.doFinal(Native Method)",
        "org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:48)",
        "org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$12$lambda$11(MainActivity.kt:101)",
        "org.owasp.mastestapp.MainActivityKt.$r8$lambda$Pm6AsbKBmypP53K-UABM21E_Xxk(Unknown Source:0)",
        "org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda3.run(D8$$SyntheticClass:0)",
        "java.lang.Thread.run(Thread.java:1119)"
    ],
    "inputParameters": [
        {
            "declaredType": "[B",
            "value": "sk-OWASP-MAS-SuperSecretKey-1234567890"
        }
    ],
    "returnValue": [
        {
            "declaredType": "[B",
            "value": "0x8bd458cea5ccefd16f2ea6b7604d9e9aa65ca5cf8918b60ce36eb3d1890bb58a447134c8c3d5724984e7cd3eb2e794262ad22273e741..."
        }
    ]
}
{
    "id": "f5b1e085-e2c0-4778-93e2-d3f30bc03438",
    "type": "hook",
    "category": "CRYPTO",
    "time": "2026-02-09T14:00:41.074Z",
    "class": "javax.crypto.Cipher",
    "method": "doFinal",
    "instanceId": 61410961,
    "stackTrace": [
        "javax.crypto.Cipher.doFinal(Native Method)",
        "org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:58)",
        "org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$12$lambda$11(MainActivity.kt:101)",
        "org.owasp.mastestapp.MainActivityKt.$r8$lambda$Pm6AsbKBmypP53K-UABM21E_Xxk(Unknown Source:0)",
        "org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda3.run(D8$$SyntheticClass:0)",
        "java.lang.Thread.run(Thread.java:1119)"
    ],
    "inputParameters": [
        {
            "declaredType": "[B",
            "value": "0x8bd458cea5ccefd16f2ea6b7604d9e9aa65ca5cf8918b60ce36eb3d1890bb58a447134c8c3d5724984e7cd3eb2e794262ad22273e741..."
        }
    ],
    "returnValue": [
        {
            "declaredType": "[B",
            "value": "sk-OWASP-MAS-SuperSecretKey-1234567890"
        }
    ]
}

Evaluation

The test fails because the hook executes successfully and the sensitive API key sk-OWASP-MAS-SuperSecretKey-1234567890 is extracted in plaintext from the Cipher.doFinal() calls. The app lacks runtime integrity verification, allowing instrumentation tools to intercept cryptographic operations without any defensive response.