Skip to content

MASTG-DEMO-0107: Detecting Frida hooks and terminating the application on response

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

Sample

This sample encrypts and decrypts a sensitive API key using AES/GCM via the Android KeyStore. Unlike the unprotected variant in Extracting Sensitive Data from Cipher.doFinal via Frida Hooking, this version includes a runtime hook detection mechanism that reads /proc/self/maps to check for the presence of Frida-related libraries (e.g., frida-agent, frida-gadget). If detected, the app terminates the process immediately via Process.killProcess() before any cryptographic operations are performed.

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

import android.content.Context
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import java.io.BufferedReader
import java.io.FileReader
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"

    init {
        if (detectHooking()) {
            android.os.Process.killProcess(android.os.Process.myPid())
        }
    }

    private fun detectHooking(): Boolean {
        try {
            BufferedReader(FileReader("/proc/self/maps")).use { reader ->
                var line: String?
                while (reader.readLine().also { line = it } != null) {
                    val l = line!!.lowercase()
                    if (l.contains("frida") || l.contains("gadget")) {
                        return true
                    }
                }
            }
        } catch (_: Exception) {
            // Unable to read maps
        }
        return false
    }

    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 {
        // Check for hooking before performing cryptographic operations
        if (detectHooking()) {
            android.os.Process.killProcess(android.os.Process.myPid())
            return ""
        }

        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. Observe that the app terminates before the hooks can capture any data
 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 no instances of Cipher method calls found at runtime. The app terminated before any hooks could capture data.

output.json
1

Evaluation

The test passes because the hooking attempt fails due to the app's defensive response. The app detects the Frida agent by scanning /proc/self/maps for entries containing "frida" or "gadget" and terminates the process via Process.killProcess(). The process terminates before Cipher.doFinal() hooks execute, so no sensitive data is extracted.

Note

Even if the test case passes, it might still be possible to bypass the app's defensive response. For example, an attacker could hook the detectHooking() method itself or lower level APIs such as the file reading APIs to hide Frida from the process memory map. Bypassing Frida Detection in /proc/self/maps to Extract Sensitive Data demonstrates such a bypass. Detection of Reverse Engineering Tools and Runtime Integrity Verification describe such challenges.