Dummy Android System App

I ran the commands below from two locations: (1) the AOSP root folder and (2) the directory where the Cuttlefish Android emulator is installed.

upgautam@amd:/opt/aosp$ source build/envsetup.sh
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined$ HOME=$PWD ./bin/launch_cvd

upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb devices
List of devices attached
0.0.0.0:6520	device

We built a dummy system app and tried to test its elevated access, such as running ls /sys.

<uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

Note: Declaring a system permission in a normal app does not grant that permission. Declaration and grant are different steps.

Android grants many privileged permissions only when the app is installed as a system app. To test this, I modified my dummy app as follows:

<?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.ACCESS_SUPERUSER" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <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.DummySystemApplication"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.DummySystemApplication">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

And, my activtiy as,

package com.example.dummysystemapplication

import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.dummysystemapplication.ui.theme.dummysystemapplicationTheme
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.OutputStreamWriter
import kotlin.concurrent.thread


class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

        disableSELinux()
        executeRootCommand()

        setContent {
            dummysystemapplicationTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Uddha P. Gautam",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }

    private fun disableSELinux() {
        thread {
            try {
                // Start the root shell with /system/xbin/su 0
                val command = "/system/xbin/su 0"
                val process = Runtime.getRuntime().exec(command)

                // Send setenforce 0 command to the root shell
                val outputStream = process.outputStream
                val writer = BufferedWriter(OutputStreamWriter(outputStream))
                writer.write("setenforce 0\n")
                writer.flush()

                // Capture the output of the command
                val output = process.inputStream.bufferedReader().use(BufferedReader::readText)
                process.waitFor()

                runOnUiThread {
                    Toast.makeText(this, "SELinux disabled:\n$output", Toast.LENGTH_SHORT).show()
                }
            } catch (e: Exception) {
                e.printStackTrace()
                runOnUiThread {
                    Toast.makeText(this, "Error disabling SELinux", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }


    // Function to execute root commands
    private fun executeRootCommand() {
        thread {
            try {
                // Start the root shell
                val command = "/system/xbin/su 0"
                val process = Runtime.getRuntime().exec(command)

                // Send the "ls /sys" command to the root shell
                val outputStream = process.outputStream
                val writer = BufferedWriter(OutputStreamWriter(outputStream))
                writer.write("ls /sys\n")
                writer.flush()

                // Capture the output of the command
                val output = process.inputStream.bufferedReader().use(BufferedReader::readText)
                process.waitFor()

                runOnUiThread {
                    Toast.makeText(this, "Root Command Output:\n$output", Toast.LENGTH_LONG).show()
                }
            } catch (e: Exception) {
                e.printStackTrace()
                runOnUiThread {
                    Toast.makeText(this, "Can't run root command", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        text = "Hello $name!",
        modifier = modifier
    )
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    dummysystemapplicationTheme {
        Greeting("Uddhav P. Gautam (Owner of RGR Innovate LLC)")
    }
}

Build the apk and push. But this time, I also installed magisk app and from there patched the boot.img, I pulled that patched boot.img to host and then stopped and started cuttlefish emulator with new patched boot.img.

upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb push ~/Desktop/DummyApp/app-debug.apk /system/priv-app/dummy_app/app-debug.apk
adb: error: failed to copy '/home/upgautam/Desktop/DummyApp/app-debug.apk' to '/system/priv-app/dummy_app/app-debug.apk': remote secure_mkdirs() failed: Read-only file system
/home/upgautam/Desktop/DummyApp/app-debug.apk: 1 file pushed, 0 skipped. 674.3 MB/s (8670593 bytes in 0.012s)

To fix this read-only error,

upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb reboot
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb root
restarting adbd as root
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb shell
vsoc_x86_64:/ # whoami
root
vsoc_x86_64:/ # setenforce 0
vsoc_x86_64:/ # disable-verity
AVB verification is disabled, disabling verity state may have no effect
enabling overlayfs
Reboot the device for new settings to take effect
vsoc_x86_64:/ # reboot
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb root
adb: unable to connect for root: no devices/emulators found
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb root
restarting adbd as root
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb shell
vsoc_x86_64:/ # mount -o rw,remount /system
vsoc_x86_64:/ # mkdir /system/priv-app/dummy_app/
vsoc_x86_64:/ # exit
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb push ~/Desktop/DummyApp/app-debug.apk /system/priv-app/dummy_app/app-debug.apk
/home/upgautam/Desktop/DummyApp/app-debug.apk: 1 file pushed, 0 skipped. 21.7 MB/s (8670593 bytes in 0.380s)
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb reboot
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb root
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb shell
vsoc_x86_64:/ # am start com.example.dummysystemapplication/.MainActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.dummysystemapplication/.MainActivity }

The issue still exists. I could not make it work, and I still could not test the expected privileged behavior even though the app is installed as a system app. I also tried the Cuttlefish emulator with the original boot.img, and saw the same result.

upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb push ~/Desktop/DummyApp/app-debug.apk /system/priv-app/dummy_app/app-debug.apk
/home/upgautam/Desktop/DummyApp/app-debug.apk: 1 file pushed, 0 skipped. 22.6 MB/s (8670593 bytes in 0.366s)
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb root
adbd is already running as root
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb shell
vsoc_x86_64:/ # chmod 644 /system/priv-app/dummy_app/app-debug.apk
vsoc_x86_64:/ # chown root:system /system/priv-app/dummy_app/app-debug.apk
vsoc_x86_64:/ # reboot   //when it restarts the apk will be installed as system app
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb root
restarting adbd as root
upgautam@amd:/opt/cuttlefish/cuttlefish-run/combined/bin$ ./adb shell
vsoc_x86_64:/ # setenforce 0
vsoc_x86_64:/ # am start com.example.dummysystemapplication/.MainActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.dummysystemapplication/.MainActivity }
#Still I get Error.