Securing User-Level Functions in Android Applications
Defending Android user interactions against screen capture, biometric bypass, keylogging, and tapjacking
Overview
Android client-side security operates in an inherently adversarial environment. An application may run on a rooted device, inside an emulator, or under the control of runtime instrumentation and hooking tools. Attackers can attempt to intercept, modify, or automate sensitive user interactions—including screen rendering, biometric authentication, text entry, and touch events.
Native Android security APIs are essential, but local controls alone are not sufficient when an attacker can manipulate the application process. A defense-in-depth approach combines Android platform protections with Runtime Application Self-Protection (RASP) and remote attestation.
Approov Mobile App and API Protection strengthens this architecture by helping ensure that only authentic, uncompromised application instances can obtain the short-lived credentials required to access backend APIs.
This article covers four critical user-level attack vectors:
- Screen capture and recording
- Biometric authentication bypass
- Keylogging and input interception
- Tapjacking and overlay attacks
1. Prevent Screen Capture and RecordingThreat
Malicious applications, rogue accessibility services, screen-scraping malware, and compromised device environments may capture sensitive application content. Common targets include:
- Account balances and personally identifiable information
- Payment-card details
- One-time passwords and recovery codes
- Authentication screens and session data
Native Android protection
Use FLAG_SECURE on activities or windows that render sensitive data. This helps prevent screenshots, screen recordings, and display of protected content on non-secure external displays.
Kotin
import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
class SecureActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Prevent screenshots, screen recordings, and non-secure display mirroring
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE
)
setContentView(R.layout.activity_secure)
}
}
Apply FLAG_SECURE selectively to screens that handle credentials, payment approval, sensitive personal data, or high-risk transactions. Applying it globally can interfere with legitimate user workflows, such as support-assisted troubleshooting or screen sharing.
A sufficiently capable attacker may use runtime hooking frameworks such as Frida or Xposed to alter application behavior, including attempts to remove or bypass window-security settings.
Approov complements native controls by:
- Detecting runtime instrumentation and application tampering.
- Assessing the integrity of the running application environment.
- Withholding or revoking access to short-lived API credentials when the app does not meet attestation requirements.
- Preventing a manipulated client from accessing protected backend services, even if a local UI control is bypassed.
2. Protect Biometric Authentication IntegrityThreat
Attackers may hook biometric authentication flows and force the success callback without a genuine fingerprint or face authentication event. If the application treats biometric authentication as a simple Boolean result, a manipulated callback can bypass local authorization logic.
Native Android protection
Do not rely on biometric success alone. Bind BiometricPrompt to a hardware-backed AndroidKeyStore key through a CryptoObject.
The cryptographic operation remains unavailable until the device completes the required user authentication. This makes the biometric result meaningful because it unlocks a protected key operation rather than merely triggering an application callback.
kotlin
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.biometric.BiometricPrompt
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
fun createBiometricCryptoObject(): BiometricPrompt.CryptoObject {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
val builder = KeyGenParameterSpec.Builder(
"BiometricKeyAlias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.setInvalidatedByBiometricEnrollment(true)
keyGenerator.init(builder.build())
keyGenerator.generateKey()
val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
val key = keyStore.getKey("BiometricKeyAlias", null) as SecretKey cipher.init(Cipher.ENCRYPT_MODE, key)
return BiometricPrompt.CryptoObject(cipher)
}
Use the resulting object when starting biometric authentication:
biometricPrompt.authenticate(promptInfo, cryptoObject)After successful authentication, use the unlocked cryptographic operation to decrypt a locally protected value, authorize a transaction step, or unlock a short-lived session artifact.
Approov defense amplificationApproov can extend this control beyond the device by ensuring that sensitive backend access depends on both user authorization and app integrity.
Recommended approach:
- Use biometric authentication to unlock a locally encrypted secret or session artifact.
- Use Approov attestation to verify the legitimacy of the running app and device environment.
- Request short-lived API credentials only after both conditions are met.
- Enforce the attestation result at the API gateway or backend service.
This limits the value of a locally bypassed biometric callback: even if an attacker manipulates the UI flow, a compromised application instance may still fail to obtain valid API access credentials.
Reference3. Reduce Keylogging and Input Interception RiskThreat
Sensitive input fields can be exposed through several mechanisms:
- Third-party keyboards and input method editors (IMEs)
- Malicious accessibility services
- Screen readers or services with excessive permissions
- Background malware monitoring UI content
- Runtime hooks that inspect Android views or text buffers
No client-side control can guarantee that plaintext entered on a compromised device remains confidential. However, applications can reduce exposure and minimize the time that sensitive data is present in accessible UI components.
Native Android protection
For confidential fields, disable Autofill and full-screen extract mode where appropriate.
xml
<EditText
android:id="@+id/secureInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:imeOptions="flagNoExtractUi"
android:importantForAutofill="no" />
Consider these additional practices:
Use textPassword, numberPassword, or equivalent input types for secrets such as passwords and PINs.
- Disable Autofill for values that should not be retained or suggested by other apps.
- Avoid persisting sensitive input in logs, analytics events, crash reports, or saved application state.
- Clear sensitive fields after successful use or when the app enters the background.
- Assess active accessibility services and apply a risk-based policy for sensitive workflows.
- Use a purpose-built in-app keypad for high-risk use cases, such as transaction PIN confirmation, while recognizing that a custom keypad does not make a compromised device trustworthy.
Approov defense amplification
Approov provides a server-enforced layer of protection when the local environment appears suspicious or compromised.
Potential uses include:
- Identifying application tampering, instrumentation, emulation, and other high-risk runtime states.
- Applying updated protection policies without requiring an immediate app-store release.
- Denying access to sensitive APIs when an application instance cannot satisfy integrity checks.
- Combining runtime integrity results with backend fraud controls, device reputation, transaction risk signals, and step-up authentication.
For example, a banking app can permit low-risk account viewing on a normal device but require additional verification—or deny a funds-transfer API request—when integrity checks identify a potentially manipulated environment.
Reference- OWASP Mobile Application Security Testing Guide (MASTG) input-security guidance
4. Prevent Tapjacking and Overlay AttacksThreat
Tapjacking occurs when another application displays a transparent or deceptive overlay above a legitimate application. The user may believe they are interacting with visible content while touch events trigger actions in the underlying app.
Overlay attacks can target high-impact actions such as:
- Confirming a payment or bank transfer
- Approving a new device
- Granting permissions
- Changing account recovery information
- Authorizing API or session activity
Native Android protection
Enable filterTouchesWhenObscured on high-risk interactive elements.
xml
<Button
android:id="@+id/authorizeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Authorize"
android:filterTouchesWhenObscured="true" />
For custom views or more granular handling, inspect motion-event flags before accepting a touch event.
override fun onFilterTouchEventForSecurity(event: MotionEvent): Boolean { val flags = event.flags val isObscured = (flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0 val isPartiallyObscured = (flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0 if (isObscured || isPartiallyObscured) { // Reject the touch event or display a security warning return false } return super.onFilterTouchEventForSecurity(event) }Android includes system-level safeguards against some untrusted overlays on modern API levels. Explicit touch filtering remains important for compatibility, defense in depth, and protection of high-value actions.
Approov defense amplification
Local touch filtering protects a specific interface element. Approov extends protection to the backend boundary.
If an overlay attack occurs in a risky device state, the application can be prevented from obtaining the attestation result or short-lived token required for sensitive API requests. This reduces the likelihood that a deceptive local interaction can be converted into a successful backend transaction.
Recommended backend policy:
- Require valid Approov attestation for high-risk API endpoints.
- Treat failed or missing attestation as a transaction-risk signal.
- Require step-up authentication or deny execution for payment, credential, device-enrollment, and recovery workflows.
- Log attestation failures alongside transaction telemetry for fraud investigation.
Architectural Protection Matrix
|
Threat vector |
Native Android Control |
Approov RASP & Cloud Role |
Reference |
|---|---|---|---|
|
Screen capture and recording |
|
Helps identify runtime manipulation and prevents compromised app instances from obtaining protected API credentials |
|
|
Biometric bypass |
|
Adds remote integrity validation before backend access is granted |
|
|
Keylogging and input interception |
Password input types, |
Enables server-side enforcement when the app environment is suspicious or fails integrity verification |
OWASP MASTG |
|
Tapjacking and overlays |
|
Helps block access to sensitive APIs when an overlay attack or compromised environment is detected |
Implementation Checklist
Use this checklist when securing Android workflows that involve credentials, payments, financial transactions, personal data, or account recovery.
- Apply
FLAG_SECUREto activities that display sensitive data. - Bind
BiometricPromptto an AndroidKeyStore-backedCryptoObject. - Require
.setUserAuthenticationRequired(true)for biometric-protected keys. - Consider invalidating keys after biometric enrollment changes where appropriate.
- Enable
filterTouchesWhenObscured="true"for all high-risk buttons and actionable UI controls. - Inspect
FLAG_WINDOW_IS_OBSCUREDandFLAG_WINDOW_IS_PARTIALLY_OBSCUREDin custom security-sensitive views. - Apply
flagNoExtractUiand disable Autofill on confidential input fields. - Avoid logging, storing, or transmitting sensitive input beyond the minimum required scope.
- Establish a policy for handling accessibility services during high-risk workflows.
- Integrate the Approov SDK into the Android build and release pipeline.
- Require valid Approov attestation before granting access to sensitive APIs.
- Use short-lived API tokens and enforce attestation at the backend or API gateway.
- Define a fallback response for failed integrity checks, such as step-up authentication, transaction delay, or request denial.
Recommended Security Model
The most effective architecture treats the Android client as an untrusted execution environment and moves final authorization decisions to the backend.
Native Android controls should protect the user interface and reduce exposure at the point of interaction. Approov should validate that the requesting app instance is authentic and operating in an acceptable runtime environment before sensitive backend services issue credentials or execute high-risk actions.
Together, these controls create layered protection:
- Android APIs reduce UI-level exposure and manipulation.
- Hardware-backed cryptography binds sensitive actions to genuine user authentication.
- Approov validates application integrity and helps identify hostile runtime conditions.
- Backend enforcement ensures that bypassing a local client-side control does not automatically grant access to protected services.