Android builds system-level applications with silent authorization and silent installation

background

Developing applications based on a custom tablet system can obtain the support of the Android source code layer.
Android8.1 system.

Target

  • By default, all required permissions are obtained, and no pop-up window is asked for authorization.
  • A silent upgrade installs a new version.

accomplish

Obtain system application permissions

Through sharedUserIdthis configuration, the current application process is configured as a system application process, and all permissions can be obtained.

principle

Through Shared User id, multiple APKs with the same UserId can be configured to run in the same process, so by default, they can access any data from each other, and at the same time, they can access databases and files in the data directory of other APKs, just like accessing this program data is the same.

In order to prevent other applications from obtaining the sharedUserId and abusing it, the Android system restricts that the same userId must use the same signature to be installed on the same device. Therefore, if you want to set it as a system shared process, it must be consistent with the system application signature.

The logic for the system to sign the application:

build/target/product/securityThere are four sets of default signatures in the directory of the system source code Android.mkfor use in compiling the APK, which LOCAL_CERTIFICATEare implemented by specifying fields, which are:

  • testkey: Normal APK, used by default.
  • platform: The APK completes some core functions of the system. After the access test to the folders existing in the system, the UID of the process where the APK compiled in this way is system.
  • shared: The APK needs to share data with the home/contacts process.
  • media: The APK is a part of the media/download system

It is used by ordinary applications testkey. If it is a system application, it can be specified as platform.

The principle and introduction refer to this article : Android's permission mechanism - "sandbox" mechanism sharedUserId and signature

accomplish

Refer to this article to implement : Android apk system signature

1. Prerequisite:
The application signature cannot be a custom signature, but must be a system signature. The signature file needs to be provided by the system provider, including several files such as platform.pk8, platform.x509.pem, signapk.jarand so on. If this step cannot be provided, it sharedUserIdis hopeless through configuration.

2. ConfiguresharedUserId the node increase
in :AndroidManifest.xmlmanifest

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.moore.systemapk"
    android:sharedUserId="android.uid.system">
</manifest>

3. Signature
Method 1:
In the system source code layer, configure the application as Android.mkthe system application type among the four default types in the corresponding principleLOCAL_CERTIFICATEplatform

Method 2:
Build the unsigned apk package, and sign the application through the command method.
java -jar signapk.jar platform.x509.pem platform.pk8 unsigned.apk signed.apk
Replace your apk name and path unsigned.apkwith signed.apkthe output path.

Method 3:
According to the signature file provided by the system, generate a jks or keystone file, which can be directly packaged and signed in the AS without additional signature.

  1. generate shared.priv.pemfile

openssl pkcs8 -in platform.pk8 -inform DER -outform PEM -out shared.priv.pem -nocrypt

  1. generate shared.pk12file

openssl pkcs12 -export -in platform.x509.pem -inkey shared.priv.pem -out shared.pk12 -name moore

  1. generate jksor keystonefile

keytool -importkeystore -deststorepass android -destkeypass android -destkeystore moore.jks -srckeystore shared.pk12 -srcstoretype PKCS12 -srcstorepass android -alias moore

After generation, it can be configured in gradle, or choose the path where the signature is located when packaging

4. Problems
In the actual operation process, if you use the command method to sign, there is a high probability no conscrypt_openjdk_jni in java.library.path:of errors. Get the one provided by the manufacturer libconscrypt_openjdk_jni.so, re-sign, and report another error: FATAL ERROR in native method: RegisterNatives failed for 'org/conscrypt/NativeCrypto', try to change the computer or system (windows, mac, linux) There is no solution. It is normal for the computer on the manufacturer's side to execute the command, so I gave up and executed it by generating a jks signature.

5. Installation
Since the signature has been changed, if the app is installed on the device where the app was originally installed, it must be uninstalled in advance. The same package name has a different signature, and the installation is obviously unsuccessful.

silent installation

The application realizes the automatic update function, and there are several installation methods:

  • Call the system installer and pass in the apk path (authorization is required to allow the installation of applications)
  • Execute the shell command to silently install apk (requires root authority)
  • Use PackageInstallercode to install, simulate the system installation process (need to be a system application)
Normal upgrade installation

First of all, the installation page will appear in this way, which does not meet our needs, but some problems were found during the installation process, so let’s list them for further reference.

1. Method
After downloading the upgrade package apk, directly use Intent to invoke the system installation intent. It should be noted that customization is required for versions above 8.0 FileProvider, otherwise the path cannot be obtained and an exception will be thrown FileUriExposedException.

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri fileUri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    //自定义的FileProvider,需要在清单文件中声明
    String authority = XUpdate.getContext().getPackageName() + ".updateFileProvider";
    fileUri = FileProvider.getUriForFile(XUpdate.getContext(), authority, file);
} else {
    fileUri = Uri.fromFile(file);
}
intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
context.startActivity(intent)

2. Problem
Here comes the problem, according to the normal application process, this method is no problem, everything is normal, but our application has been set ** android:sharedUserId="android.uid.system"**, it is a bit troublesome.

When starting the installation, an error is reported: There was a problem parsing the package

I have confirmed that there is no problem with the downloaded installation package. The installation can be initiated through file management and other applications, and it can be installed normally after removing the systemUid. The problem is the sharedUserId.

Reason:
By tracking the source program of the system installation process, the following code was found:

final int callingAppId = UserHandle.getAppId(callingUid);
if ((callingAppId == SYSTEM_UID) || (callingAppId == ROOT_UID)) {
    if ("com.android.settings.files".equals(grantUri.uri.getAuthority())) {
        // Exempted authority for cropping user photos in Settings app
    } else {
        Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission"
                + " grant to " + grantUri + "; use startActivityAsCaller() instead");
        return -1;
    }
}

ok, to solve the case, because we are an application set to SystemUid, but our provider is not com.android.settings.files, so it is directly blocked return -1, and com.android.settings.filesthis provider is defined in the Settings system application, that is to say, only the SystemUid application of Settings can Invokes the normal installation process.

solution:

  • We can't change our own to the one android:authoritiesavailable above com.android.settings.files, because this is the only value in the entire system, and it cannot be installed on the device if it is changed to the same value. pawn.
  • Remove systemUid, it can be installed normally, if you can remove it, you won't bother to study it. pawn.
  • Modify the source code! In the above judgment, add an or condition, add our own authorities, but if you want to modify the source code, you can use it conditionally

For detailed analysis of this problem, please refer to the article : The application call of Android android.uid.system fails to install apk

shell command installation

As the name suggests, it is installed directly by entering commands to execute. We all know that the Android kernel is linux, so the principle is to use the root account to directly command the installation.
You need to determine whether you have root privileges first:

public static boolean isDeviceRooted() {
    String su = "su";
    String[] locations = {"/system/bin/", "/system/xbin/", "/sbin/", "/system/sd/xbin/",
            "/system/bin/failsafe/", "/data/local/xbin/", "/data/local/bin/", "/data/local/"};
    for (String location : locations) {
        if (new File(location + su).exists()) {
            return true;
        }
    }
    return false;
}

If you have permission, the splicing command filePathis the apk path

String command = "pm install -i " + packageName + " --user 0 " + filePath;

Then execute the command through the method of ShellUtils .execCommand

If you have Root permission, this method is feasible and there will be no interface, but the devices we release to the outside world will not open root permission, so this method can only be used during development and testing, and it cannot be operated online.

Simulate System Installer Flow

Before the introduction, you can refer to these two articles to understand the system installation application process and code logic
Android package management mechanism (1) PackageInstaller initialization
Android package management mechanism (2) PackageInstaller install APK

What we have to do is to analyze and call up the system installation page, what operations the logic layer does after clicking install, and then implement this process directly in our application.

general flow

  1. Define a listener callback to receive notifications of installation start, progress, and end
  2. Get the object from PackageManagerGet PackageInstaller, get the apk file File object
  3. Create an installation session Sessionand get the session id
  4. Read the apk File and write the file as a streamsession
  5. Create one PendingIntentto invoke the PMS installation logic
  6. Commit this session and enter the PMS installation

Introduction to PMS installation:

  1. After receiving the session commit, it will be assembled into an PackageInstallObserverAdapterobject, Handlerwhich will be processed by PMS by sending the message
  2. PackageManagerServiceThe install logic PMSHandleris IMediaContainerServicerelated to several classes
  3. PMSHandlerProcess the logic of binding messages, new installation messages, etc. Only when the binding is successful can the next installation operation be performed
  4. Bind the service through the binder, parse and copy the apk (time-consuming operation, open a new service process in the Android system)
  5. InstallParamsAnd InstallArgsspecific operations such as pre-installation inspection, installation location confirmation, copy and move, etc.
  6. After the installation is successful, a message is distributed through the PMS to notify the user of the application, update the Settings, and prepare data for the newly installed application. If the installation fails, the apk will be deleted.
  7. disconnect binder

Reference article: : Android package management mechanism (3) PMS handles APK installation

key code

int count;
int sessionId;
byte[] buffer = new byte[65536];
InputStream inputStream;
OutputStream outputStream;
//拿到apkFile,拿到packageInstaller对象
File apkFile = new File(apkFilePath);
long apkFileLength = apkFile.length();
PackageManager pm = context.getPackageManager();
PackageInstaller packageInstaller = pm.getPackageInstaller();
packageInstaller.registerSessionCallback(忽略回调实现);
//创建会话
PackageInstaller.Session session = null;
PackageInstaller.SessionParams sessionParams;
try {
    sessionParams = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
    sessionId = packageInstaller.createSession(sessionParams);
    session = packageInstaller.openSession(sessionId);
    inputStream = new FileInputStream(apkFile);
    outputStream = session.openWrite(apkName, 0, apkFileLength);
    //写入
    while ((count = inputStream.read(buffer)) != -1) {
        outputStream.write(buffer, 0, count);
        float progress = ((float) count / (float) apkFileLength);
        session.setStagingProgress(progress);
    }
    session.fsync(outputStream);
    inputStream.close();
    outputStream.flush();
    outputStream.close();
    //创建PendingIntent
    Intent intent = new Intent();
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
    //绑定并提交
    session.commit(pendingIntent.getIntentSender());
} catch (Exception e) {
    e.printStackTrace();
} finally {
    if (session != null) {
        session.abandon();
    }
}

At this point, the silent installation can be realized, but there is still a problem. Normally, our system upgrade needs to restart the application after the upgrade is completed. However, because our application is killed during the installation process, Therefore, the callback set when creating the session cannot receive the callback for the completion of the installation! !

The installation is complete and restart

Idea:
At the beginning of the installation, create an AlarmManager task, and reopen the application after 10s (depending on the size of the apk).

Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
PendingIntent restartIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0及以上
    mgr.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 10000, restartIntent);
}

In this way, a silent installation can be achieved, and the application will be restarted after the installation is complete.

Guess you like

Origin blog.csdn.net/lizebin_bin/article/details/125293599