Skip to content

MASTG-DEMO-0105: Activity-Level Overlay Protection Using setHideOverlayWindows

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

Sample

The sample shows an app that uses setHideOverlayWindows(true) at the activity level (available from Android 12, API level 31) and declares the required HIDE_OVERLAY_WINDOWS permission in the manifest. Unlike the view-level protections in Missing Overlay Protection on a Sensitive View, this approach prevents any overlay window from appearing over the entire activity.

 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.app.Activity
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.view.Gravity
import android.view.ViewGroup
import android.widget.Button
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.Toast

// SUMMARY: This sample demonstrates activity-level overlay protection using setHideOverlayWindows(true),
// which prevents any overlay window from appearing over the activity. It requires the HIDE_OVERLAY_WINDOWS
// permission and targets Android 12 (API level 31) and above.

class MastgTest(private val context: Context) {

    val shouldRunInMainThread = true

    fun mastgTest(): String {
        val layout = LinearLayout(context).apply {
            orientation = LinearLayout.VERTICAL
            gravity = Gravity.CENTER
            setBackgroundColor(Color.TRANSPARENT)
        }

        val halfWidth = (context.resources.displayMetrics.widthPixels * 0.5).toInt()

        val buttonParams = LinearLayout.LayoutParams(
            halfWidth,
            LinearLayout.LayoutParams.WRAP_CONTENT
        ).apply {
            gravity = Gravity.CENTER_HORIZONTAL
            topMargin = 24
        }

        val vulnerableButton = Button(context).apply {
            text = "Confirm Payment"
            setOnClickListener {
                Toast.makeText(context, "Button clicked", Toast.LENGTH_SHORT).show()
            }
        }
        layout.addView(vulnerableButton, buttonParams)

        (context as? Activity)?.let { activity ->
            // PASS: [MASTG-TEST-0x01] The activity uses setHideOverlayWindows(true) to prevent overlay
            // windows from appearing over this activity, protecting all sensitive UI elements at once.
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                activity.window.setHideOverlayWindows(true)
            }

            val root = activity.findViewById<ViewGroup>(android.R.id.content)
            if (layout.parent == null) {
                root.addView(
                    layout,
                    FrameLayout.LayoutParams(
                        FrameLayout.LayoutParams.MATCH_PARENT,
                        FrameLayout.LayoutParams.MATCH_PARENT
                    )
                )
            }
        }

        return "Created button with activity level overlay protection"
    }
}
 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
package org.owasp.mastestapp.pass;

import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Toast;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;

/* compiled from: MastgTest.kt */
@Metadata(d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u000b\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0000\b\u0007\u0018\u00002\u00020\u0001B\u000f\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0004\b\u0004\u0010\u0005J\u0006\u0010\n\u001a\u00020\u000bR\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000R\u0014\u0010\u0006\u001a\u00020\u0007X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\b\u0010\t¨\u0006\f"}, d2 = {"Lorg/owasp/mastestapp/pass/MastgTest;", "", "context", "Landroid/content/Context;", "<init>", "(Landroid/content/Context;)V", "shouldRunInMainThread", "", "getShouldRunInMainThread", "()Z", "mastgTest", "", "app_debug"}, k = 1, mv = {2, 0, 0}, xi = 48)
/* loaded from: classes3.dex */
public final class MastgTest {
    public static final int $stable = 8;
    private final Context context;
    private final boolean shouldRunInMainThread;

    public MastgTest(Context context) {
        Intrinsics.checkNotNullParameter(context, "context");
        this.context = context;
        this.shouldRunInMainThread = true;
    }

    public final boolean getShouldRunInMainThread() {
        return this.shouldRunInMainThread;
    }

    public final String mastgTest() {
        LinearLayout layout = new LinearLayout(this.context);
        layout.setOrientation(1);
        layout.setGravity(17);
        layout.setBackgroundColor(0);
        int halfWidth = (int) (this.context.getResources().getDisplayMetrics().widthPixels * 0.5d);
        LinearLayout.LayoutParams $this$mastgTest_u24lambda_u241 = new LinearLayout.LayoutParams(halfWidth, -2);
        $this$mastgTest_u24lambda_u241.gravity = 1;
        $this$mastgTest_u24lambda_u241.topMargin = 24;
        final Button $this$mastgTest_u24lambda_u243 = new Button(this.context);
        $this$mastgTest_u24lambda_u243.setText("Confirm Payment");
        $this$mastgTest_u24lambda_u243.setOnClickListener(new View.OnClickListener() { // from class: org.owasp.mastestapp.pass.MastgTest$$ExternalSyntheticLambda0
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {
                MastgTest.mastgTest$lambda$3$lambda$2($this$mastgTest_u24lambda_u243, view);
            }
        });
        layout.addView($this$mastgTest_u24lambda_u243, $this$mastgTest_u24lambda_u241);
        Context context = this.context;
        Activity activity = context instanceof Activity ? (Activity) context : null;
        if (activity != null) {
            if (Build.VERSION.SDK_INT >= 31) {
                activity.getWindow().setHideOverlayWindows(true);
            }
            ViewGroup root = (ViewGroup) activity.findViewById(android.R.id.content);
            if (layout.getParent() == null) {
                root.addView(layout, new FrameLayout.LayoutParams(-1, -1));
                return "Created button with activity level overlay protection";
            }
            return "Created button with activity level overlay protection";
        }
        return "Created button with activity level overlay protection";
    }

    /* JADX INFO: Access modifiers changed from: private */
    public static final void mastgTest$lambda$3$lambda$2(Button this_apply, View it) {
        Intrinsics.checkNotNullParameter(this_apply, "$this_apply");
        Toast.makeText(this_apply.getContext(), "Button clicked", 0).show();
    }
}
 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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MASTestApp"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:windowSoftInputMode="adjustResize"
            android:theme="@style/Theme.MASTestApp">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
 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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="1"
    android:versionName="1.0"
    android:compileSdkVersion="35"
    android:compileSdkVersionCodename="15"
    package="org.owasp.mastestapp.pass"
    platformBuildVersionCode="35"
    platformBuildVersionName="15">
    <uses-sdk
        android:minSdkVersion="29"
        android:targetSdkVersion="35"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"/>
    <permission
        android:name="org.owasp.mastestapp.pass.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
        android:protectionLevel="signature"/>
    <uses-permission android:name="org.owasp.mastestapp.pass.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"/>
    <application
        android:theme="@style/Theme.MASTestApp"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:debuggable="true"
        android:allowBackup="true"
        android:supportsRtl="true"
        android:extractNativeLibs="false"
        android:fullBackupContent="@xml/backup_rules"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:appComponentFactory="androidx.core.app.CoreComponentFactory"
        android:dataExtractionRules="@xml/data_extraction_rules">
        <activity
            android:theme="@style/Theme.MASTestApp"
            android:name="org.owasp.mastestapp.pass.MainActivity"
            android:exported="true"
            android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity
            android:name="androidx.compose.ui.tooling.PreviewActivity"
            android:exported="true"/>
        <activity
            android:theme="@android:style/Theme.Material.Light.NoActionBar"
            android:name="androidx.activity.ComponentActivity"
            android:exported="true"/>
        <provider
            android:name="androidx.startup.InitializationProvider"
            android:exported="false"
            android:authorities="org.owasp.mastestapp.pass.androidx-startup">
            <meta-data
                android:name="androidx.emoji2.text.EmojiCompatInitializer"
                android:value="androidx.startup"/>
            <meta-data
                android:name="androidx.lifecycle.ProcessLifecycleInitializer"
                android:value="androidx.startup"/>
            <meta-data
                android:name="androidx.profileinstaller.ProfileInstallerInitializer"
                android:value="androidx.startup"/>
        </provider>
        <receiver
            android:name="androidx.profileinstaller.ProfileInstallReceiver"
            android:permission="android.permission.DUMP"
            android:enabled="true"
            android:exported="true"
            android:directBootAware="false">
            <intent-filter>
                <action android:name="androidx.profileinstaller.action.INSTALL_PROFILE"/>
            </intent-filter>
            <intent-filter>
                <action android:name="androidx.profileinstaller.action.SKIP_FILE"/>
            </intent-filter>
            <intent-filter>
                <action android:name="androidx.profileinstaller.action.SAVE_PROFILE"/>
            </intent-filter>
            <intent-filter>
                <action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION"/>
            </intent-filter>
        </receiver>
    </application>
</manifest>

Steps

Let's run our semgrep rule against the decompiled code and the reversed manifest to detect the overlay protection mechanisms.

../../../../rules/mastg-android-overlay-protection.yml
 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
rules:
  - id: mastg-android-overlay-protection-setfiltertoucheswhenobscured
    severity: INFO
    languages:
      - java
    metadata:
      summary: Detects usage of setFilterTouchesWhenObscured method for overlay attack protection
    message: "[MASVS-PLATFORM-3] setFilterTouchesWhenObscured is used to protect against overlay attacks"
    pattern: $VIEW.setFilterTouchesWhenObscured(...)

  - id: mastg-android-overlay-protection-onfiltertoucheventforsecurity
    severity: INFO
    languages:
      - java
    metadata:
      summary: Detects overriding of onFilterTouchEventForSecurity for custom overlay protection
    message: "[MASVS-PLATFORM-3] onFilterTouchEventForSecurity is overridden for custom overlay protection"
    pattern: |
      public boolean onFilterTouchEventForSecurity(...) { ... }

  - id: mastg-android-overlay-protection-flag-window-is-obscured
    severity: INFO
    languages:
      - java
    metadata:
      summary: Detects checks for FLAG_WINDOW_IS_OBSCURED flag to detect overlay attacks
    message: "[MASVS-PLATFORM-3] FLAG_WINDOW_IS_OBSCURED is checked to detect overlay attacks"
    pattern-either:
      - pattern: $EVENT.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED
      - pattern: ($EVENT.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0

  - id: mastg-android-overlay-protection-flag-window-is-partially-obscured
    severity: INFO
    languages:
      - java
    metadata:
      summary: Detects checks for FLAG_WINDOW_IS_PARTIALLY_OBSCURED flag to detect overlay attacks
    message: "[MASVS-PLATFORM-3] FLAG_WINDOW_IS_PARTIALLY_OBSCURED is checked to detect overlay attacks"
    pattern-either:
      - pattern: $EVENT.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED
      - pattern: ($EVENT.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0

  - id: mastg-android-overlay-protection-xml-attribute
    severity: INFO
    languages:
      - xml
    metadata:
      summary: Detects filterTouchesWhenObscured attribute in layout XML files
    message: "[MASVS-PLATFORM-3] android:filterTouchesWhenObscured attribute is used to protect against overlay attacks"
    pattern: android:filterTouchesWhenObscured="true"

  - id: mastg-android-overlay-protection-sethideoverlaywindows
    severity: INFO
    languages:
      - java
    metadata:
      summary: Detects usage of setHideOverlayWindows to prevent overlay windows from appearing over the activity
    message: "[MASVS-PLATFORM-3] setHideOverlayWindows is used to prevent overlay windows from appearing over this window"
    pattern: $WINDOW.setHideOverlayWindows(...)

  - id: mastg-android-overlay-protection-hide-overlay-windows-permission
    severity: INFO
    languages:
      - xml
    metadata:
      summary: Detects HIDE_OVERLAY_WINDOWS permission in the manifest, required for setHideOverlayWindows
    message: "[MASVS-PLATFORM-3] HIDE_OVERLAY_WINDOWS permission is declared, required to use setHideOverlayWindows"
    pattern: android:name="android.permission.HIDE_OVERLAY_WINDOWS"
run.sh
1
2
3
4
#!/bin/bash

# Run semgrep to detect setHideOverlayWindows in the decompiled code and HIDE_OVERLAY_WINDOWS permission in the manifest
NO_COLOR=true semgrep -c ../../../../rules/mastg-android-overlay-protection.yml ./MastgTest_reversed.java ./AndroidManifest_reversed.xml --text > output.txt

Observation

The rule detected the setHideOverlayWindows call in the decompiled code and the HIDE_OVERLAY_WINDOWS permission in the manifest:

output.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
┌─────────────────┐
 2 Code Findings 
└─────────────────┘

    AndroidManifest_reversed.xml
      rules.mastg-android-overlay-protection-hide-overlay-windows-permission
          [MASVS-PLATFORM-3] HIDE_OVERLAY_WINDOWS permission is declared, required to use
          setHideOverlayWindows                                                          

           14 <uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"/>

    MastgTest_reversed.java
      rules.mastg-android-overlay-protection-sethideoverlaywindows
          [MASVS-PLATFORM-3] setHideOverlayWindows is used to prevent overlay windows from appearing over this
          window                                                                                              

           55 activity.getWindow().setHideOverlayWindows(true);

Evaluation

The test passes because the app implements activity-level overlay protection:

  • The setHideOverlayWindows(true) call (detected in the code output) instructs the system to hide any TYPE_APPLICATION_OVERLAY windows when this activity is in the foreground, effectively preventing overlay attacks on all UI elements in the activity.
  • The HIDE_OVERLAY_WINDOWS permission (detected in the manifest output) is required to use setHideOverlayWindows on Android 12 (API level 31) and above.

This is a stronger protection than the view-level approach shown in Missing Overlay Protection on a Sensitive View, which requires adding protection to every sensitive view individually.