Build & Deploy Android System Apps on Rooted Devices
Installing and running custom Android system apps unlocks powerful capabilities — including access to hidden or privileged Android APIs that are normally restricted from user-installed apps.
System apps can integrate more deeply with the OS, modify system behavior, and run with elevated permissions — enabling advanced use cases like process monitoring, bypassing app visibility restrictions, accessing protected directories, and hooking into low-level system services.
On rooted devices, this gives researchers, developers, and reverse engineers far more control over the Android runtime environment - potentially opening the door to make root detection easier to defeat.
Build a System App
-
Use the following
minimal-android-project
repository as starting point in order to build a custom system app locally.git clone https://github.com/czak/minimal-android-project.git
then:
cd minimal-android-project
-
Edit
./build.gradle
to use this working example.Replace
custom.package.name
with the package name you want to app to have. This value needs to be consistent across the project.infoUnless you know exactly what you're doing, nothing else should need to be changed here.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.5.2'
}
}
apply plugin: 'com.android.application'
android {
namespace "custom.package.name"
compileSdkVersion 34 // or 35 if you have Android 15 SDK
defaultConfig {
applicationId "custom.package.name"
minSdkVersion 24 // Android 7.0+, safe minimum
targetSdkVersion 34 // or 35 if using Android 15
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
repositories {
google()
mavenCentral()
} -
Edit file at
./src/main/AndroidManifest.xml
to look like the working example below. Replacepackage
with the custom name of your system app package.noteThis example uses a
.MainActivity
, but this is optional — you can remove the<activity>
block entirely if you don’t need a launcher or UI.Only do this if you know what you are doing.
The line
android:sharedUserId="android.uid.system"
is what identifies the app as a system-level app:<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:sharedUserId="android.uid.system">
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<application
android:label="SystemApp"
android:allowBackup="false">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest> -
Optional: If you choose to use a
MainActivity
, here's a working example. Ensure thepackage
value is consistent with the value you chose.package com.corellium.mysystemapp;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView label = new TextView(this);
label.setText("Hello world!");
setContentView(label);
}
} -
Based on the package named assigned to the Android system app, directories need to be renamed to be consistent with the package naming.
For example, if my system app is named
com.corellium.mysystemapp
then my project directories should look like this:./src/main/java/com/corellium/mysystemapp
-
Install
Gradle
. For MacOS, usebrew
:brew install gradle
-
Kick off the build:
gradle assembleRelease
A successful build places the release unsigned .apk
in ./build/outputs/apk/release/minimal-android-project-release-unsigned.apk
For any build issues, review the output about why the build failed. If stuck, ensure you're using the example code as is and only changing the package name + relevant project directories.
Sign Your System App
-
Obtain the necessary keys for signing. Android test keys have remained consistent across versions for AOSP-based builds.
git clone https://android.googlesource.com/platform/build
Change directories into build:
cd build
This is the key for signing:
./target/product/security/platform.pk8
This is the certificate for signing:
target/product/security/platform.x509.pem
-
Sign the app using these keys from the repository cloned. We'll use
apksigner
to sign system app, the tool can be obtained by installing the Official Android Studio.Ensure you replace the example paths for
key
andcert
to the path to the key and cert on your local system.apksigner sign --key /path/to/build/target/product/security/platform.pk8 --cert /path/to/build/target/product/security/platform.x509.pem --out customsystemapp.apk /path/to/app/unsigned/system/app
Install and Run Your System App
If the current steps are followed as is, the Android system should handle the correct file permissions for you.
-
Run the
adb connect
command to connect to your Android device. -
Restart
adb
as root:adb root
-
Remount root fs as read/write:
adb shell mount -o remount,rw /
-
Create directory on the Android device. The directory name under
/system/priv-app/
must match the APK name without the.apk
extension.Example: If my
.apk
was namedCorelliumSystemApp.apk
, the directory name should reflect this.adb shell mkdir -p /system/priv-app/CorelliumSystemApp
-
Push your system app to the Android device under the custom directory you created in the previous step.
adb push custom_system_app.apk /system/priv-app/custom_system_app_dir/
-
Grant privileged permissions to your system app. This is accomplished by creating an
.xml
file at/system/etc/permissions/
containing the following text. Ensure you replace thepackage
with your custom package name.For best practice, name your
.xml
file using your app’s package name, like:privapp-permissions-com.corellium.mysystemapp.xml
<permissions>
<privapp-permissions package="com.corellium.mysystemapp">
<permission name="android.permission.INSTALL_PACKAGES"/>
</privapp-permissions>
</permissions> -
Initiate soft reboot.
adb reboot
-
Once device boots back up, confirm the app is installed.
adb shell pm path <package_name>
If you included the
.MainActivity
in your system app you can run the app like:am start -n com.corellium.mysystemapp/.MainActivity
If you encounter issues installing/running your system app, try looking through logcat
logs using the custom directory value you created under /system/priv-app/
when doing a grep
:
adb logcat | grep "custom_system_app_dir"