記事ディレクトリ
バックグラウンド
カスタム タブレット システムに基づいてアプリケーションを開発すると、Android ソース コード レイヤーのサポートを取得できます。
Android8.1系。
目標
- デフォルトでは、必要な権限がすべて取得され、ポップアップ ウィンドウで認証が求められることはありません。
- サイレント アップグレードでは、新しいバージョンがインストールされます。
達成
システムアプリケーションの権限を取得する
この構成によりsharedUserId
、現在のアプリケーション プロセスがシステム アプリケーション プロセスとして構成され、すべての権限を取得できるようになります。
原理
共有ユーザー ID を使用すると、同じユーザー ID を持つ複数の APK が同じプロセスで実行されるように構成できるため、デフォルトでは相互に任意のデータにアクセスでき、同時にデータ ディレクトリ内のデータベースとファイルにアクセスできます。他の APK の場合も、このプログラム データにアクセスするのと同じです。
他のアプリケーションがsharedUserIdを取得して悪用するのを防ぐために、Androidシステムでは、同じuserIdを同じデバイスにインストールするには同じ署名を使用する必要があると制限しています。したがって、システム共有プロセスとして設定する場合は、システム アプリケーションの署名と一致している必要があります。
システムがアプリケーションに署名するためのロジックは次のとおりです。
システム ソース コードのbuild/target/product/security
ディレクトリには、Android.mk
APK のコンパイルに使用するデフォルトの署名の 4 つのセットがあり、LOCAL_CERTIFICATE
次のフィールドを指定することで実装されます。
testkey
: 通常の APK、デフォルトで使用されます。platform
: APK は、システムのいくつかのコア機能を完了します。システム内に存在するフォルダーへのアクセステスト後、このようにして APK がコンパイルされたプロセスの UID は system です。shared
: APK はホーム/連絡先プロセスとデータを共有する必要があります。media
: APK はメディア/ダウンロード システムの一部です
通常のアプリケーションで使用されますtestkey
が、システムアプリケーションの場合は と指定できますplatform
。
原理と導入については、この記事を参照してください: Android の許可メカニズム - 「サンドボックス」メカニズムsharedUserId と署名
達成
Android APK システム署名を実装するには、この記事を参照してください。
1. 前提条件:
アプリケーション署名はカスタム署名にすることはできませんが、システム署名である必要があります。署名ファイルはシステムプロバイダーから提供される必要があり、 、 などのいくつかのファイルが含まれていますがplatform.pk8
、platform.x509.pem
このsignapk.jar
手順が提供できない場合は、sharedUserId
設定を行うことは絶望的です。
2.sharedUserId
でAndroidManifest.xml
ノードのmanifest
増加を構成します。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.moore.systemapk"
android:sharedUserId="android.uid.system">
</manifest>
3. 署名
方法 1:
システムのソース コード層で、対応する原則の 4 つのデフォルト タイプのうちのシステム アプリケーション タイプAndroid.mk
としてLOCAL_CERTIFICATE
アプリケーションを構成します。platform
方法 2:
署名されていない apk パッケージをビルドし、コマンド メソッドを使用してアプリケーションに署名します。
java -jar signapk.jar platform.x509.pem platform.pk8 unsigned.apk signed.apk
APK の名前とパスを出力パスunsigned.apk
に置き換えます。signed.apk
方法 3:
システムによって提供される署名ファイルに従って、jks または keystone ファイルを生成します。これらのファイルは、追加の署名なしで AS で直接パッケージ化および署名できます。
shared.priv.pem
ファイルを生成する
openssl pkcs8 -in platform.pk8 -inform DER -outform PEM -out shared.priv.pem -nocrypt
shared.pk12
ファイルを生成する
openssl pkcs12 -export -in platform.x509.pem -inkey shared.priv.pem -out shared.pk12 -name moore
- 生成
jks
またはkeystone
ファイルする
keytool -importkeystore -deststorepass android -destkeypass android -destkeystore moore.jks -srckeystore shared.pk12 -srcstoretype PKCS12 -srcstorepass android -alias moore
生成後、gradle で構成することも、パッケージ化するときに署名が配置されるパスを選択することもできます
4. 問題点
実際の操作プロセスにおいて、コマンド方式で署名するとエラーが発生する可能性が高くなりますので、no conscrypt_openjdk_jni in java.library.path:
メーカー提供のものを入手しlibconscrypt_openjdk_jni.so
、再度署名し、別のエラーを報告してください:FATAL ERROR in native method: RegisterNatives failed for 'org/conscrypt/NativeCrypto'
コンピュータを変更してみるか、 system (windows, mac, linux) 解決策はなく、メーカー側のコンピュータがコマンドを実行するのが普通なので諦めてjks署名を生成して実行しました。
5. インストール
署名が変更されているため、元のアプリがインストールされていた端末にアプリがインストールされている場合は、事前にアンインストールする必要があり、同じパッケージ名でも署名が異なり、当然ながらインストールは失敗します。
サイレントインストール
アプリケーションは自動更新機能を実現しており、いくつかのインストール方法があります。
- システム インストーラーを呼び出し、apk パスを渡します (アプリケーションのインストールを許可するには承認が必要です)。
- シェルコマンドを実行してapkをサイレントインストールします(root権限が必要です)
- コードを使用して
PackageInstaller
インストールし、システム インストール プロセスをシミュレートします (システム アプリケーションである必要があります)
通常のアップグレードインストール
まず、このようにインストールページが表示されますが、これは私たちのニーズを満たしていませんが、インストールプロセス中にいくつかの問題が見つかったので、さらなる参考のためにそれらをリストしてみましょう。
1. 方法
アップグレードパッケージapkをダウンロードした後、インテントを直接使用してシステムインストールインテントを呼び出します。8.0 以降のバージョンではカスタマイズが必要であることに注意してくださいFileProvider
。カスタマイズしないとパスが取得できず、例外がスローされます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. 問題
ここで問題が発生します。通常のアプリケーションプロセスによれば、この方法は問題なく、すべてが正常ですが、アプリケーションは ** ** に設定されており、少しandroid:sharedUserId="android.uid.system"
面倒です。
インストールを開始すると、「パッケージの解析中に問題が発生しました」というエラーが報告されます。
ダウンロードしたインストールパッケージに問題がないことを確認しました。ファイル管理などからインストールが開始でき、systemUidを削除すれば正常にインストールできます。問題はsharedUserIdです。
理由:
システムのインストール プロセスのソース プログラムを追跡すると、次のコードが見つかりました。
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、ケースを解決します。これは、SystemUid に設定されたアプリケーションですが、プロバイダーがそうでないため、com.android.settings.files
直接ブロックされておりreturn -1
、com.android.settings.files
このプロバイダーは設定システム アプリケーション、つまり設定の SystemUid アプリケーションのみで定義されているためです。 can 通常のインストールプロセスを呼び出します。
解決:
- これはシステム全体で唯一の値であり、同じ値に変更するとデバイスにインストールできないため、独自の値を
android:authorities
上記の利用可能な値に変更することはできません。com.android.settings.files
ポーン。 - systemUid を削除すると、正常にインストールできます。削除できれば、わざわざ調べる必要はありません。ポーン。
- ソースコードを修正してください!上記の判定では or 条件を追加したり、独自の を追加しました
authorities
が、ソースコードを修正したい場合は条件付きで使用できます
この問題の詳細な分析については、 「Android android.uid.system のアプリケーション呼び出しで apk のインストールに失敗する」の記事を参照してください。
シェルコマンドのインストール
名前が示すように、コマンドを入力して実行することで直接インストールされます。Android カーネルが Linux であることは誰もが知っているため、原則として root アカウントを使用してインストールを直接指示します。
最初に root 権限があるかどうかを確認する必要があります。
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;
}
許可がある場合、接続コマンドはfilePath
apk パスです
String command = "pm install -i " + packageName + " --user 0 " + filePath;
次に、ShellUtilsのexecCommand
メソッドを通じてコマンドを実行します。
Root権限を持っている場合、この方法は実行可能であり、インターフェースはありませんが、外部に公開するデバイスはroot権限を開かないため、この方法は開発とテスト中にのみ使用でき、オンラインで操作することはできません。
システム インストーラ フローをシミュレートする
導入の前に、これら 2 つの記事を参照して、システム インストール アプリケーションのプロセスとコード ロジックを理解してください。
Android パッケージ管理のメカニズム (1) PackageInstaller の初期化
Android パッケージ管理のメカニズム (2) PackageInstaller の APK のインストール
私たちがしなければならないことは、システム インストール ページを分析して呼び出し、インストールをクリックした後にロジック層がどのような操作を行うかを分析し、このプロセスをアプリケーションに直接実装することです。
大まかな流れ
- インストールの開始、進行状況、および終了の通知を受信するリスナー コールバックを定義します。
PackageManager
Getからオブジェクトを取得しPackageInstaller
、apkファイルを取得する Fileオブジェクト- インストールセッションを作成し
Session
、セッションIDを取得します。 - apk ファイルを読み取り、ファイルをストリームとして書き込みます
session
PendingIntent
PMS インストール ロジックを呼び出すために作成します。- このセッションをコミットし、PMS インストールを開始します
PMS インストールの概要:
- セッションコミットを受信すると、オブジェクトに組み立てられ
PackageInstallObserverAdapter
、Handler
PMS によってメッセージが送信されて処理されます。PackageManagerService
インストール ロジックは複数のクラスに関連してPMSHandler
いますIMediaContainerService
PMSHandler
バインド メッセージ、新しいインストール メッセージなどのロジックを処理します。バインドが成功した場合にのみ、次のインストール操作を実行できます。- バインダーを介してサービスをバインドし、apk を解析してコピーします (時間のかかる操作、Android システムで新しいサービス プロセスを開きます)。
InstallParams
また、InstallArgs
設置前検査、設置場所の確認、コピーや移動などの具体的な作業も行います。- インストールが成功すると、ユーザーにアプリケーションを通知し、設定を更新し、新しくインストールされたアプリケーションのデータを準備するメッセージが PMS を通じて配信されます。インストールに失敗した場合、apk は削除されます。
- バインダーを切断する
キーコード
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();
}
}
この時点でサイレント インストールは実現できますが、まだ問題が残っています。通常、システム アップグレードでは、アップグレード完了後にアプリケーションを再起動する必要があります。ただし、アプリケーションはインストール プロセス中に強制終了されるため、コールバックが実行されます。セッション作成時に設定したものでは、インストール完了のコールバックを受信できません。!
インストールが完了し、再起動します
アイデア:
インストールの開始時に、AlarmManager タスクを作成し、10 秒後にアプリケーションを再度開きます (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);
}
このようにして、サイレント インストールが実現され、インストールの完了後にアプリケーションが再起動されます。