所以我想在这里放一个我用这个线程和其他一些知识制作的实用程序类。但在我继续之前,一些术语的解释,如果我没听错的话,从那个课上复制过来,用在上面。
在 KitKat 4.4 以下,/system/app 中的所有应用程序都被授予特权权限。甚至计算器应用程序也有它们。这可能是安全漏洞。因此它们被区分为普通和特权系统应用程序,普通应用程序在 KitKat 4.4 以上没有特权权限。因此,这些实用程序考虑到了这一点。他们还考虑到以下名称:
- 平台签名应用程序:任何使用平台/系统密钥签名的应用程序(因此它们具有系统签名权限),无论它是否安装在系统分区上。
- 系统应用程序:安装在系统分区上的任何应用程序。
- 更新的系统应用程序:任何已更新的系统应用程序(意味着现在它也安装在 /data/app 上)。
- 特权系统应用:在 KitKat 4.4 以下,安装在 /system/app 上的任何应用;从 KitKat 4.4 开始,只有安装在 /system/priv-app 上的应用程序(我的意思是只有 /system)。这些应用程序具有特权权限。
- 普通系统应用程序:仅从 KitKat 4.4 开始,那些没有特权权限的应用程序,即使它们仍然是系统应用程序。在 KitKat 4.4 以下,它们不存在。
系统分区说明:在奥利奥8.1之前,只有一个:/system。从 Pie (9) 开始,还有 /vendor 和 /product。
因此,考虑到这一点,这里有两个功能:
/**
* <p>Checks if an app is installed on the system partitions and was updated.</p>
*
* @param applicationInfo an instance of {@link ApplicationInfo} for the package to be checked
*
* @return true if it is, false otherwise
*/
private static boolean isUpdatedSystemApp(@NonNull final ApplicationInfo applicationInfo) {
return (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
}
/**
* <p>Checks if an app is installed in the system partitions (ordinary app or privileged app, doesn't matter).</p>
*
* @param applicationInfo an instance of {@link ApplicationInfo} for the package to be checked
*
* @return true if it is, false otherwise
*/
private static boolean isSystemApp(@NonNull final ApplicationInfo applicationInfo) {
// Below Android Pie (9), all system apps were in /system. As of Pie, they can ALSO be in /vendor and /product.
boolean ret_value = (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// FLAG_SYSTEM checks if it's on the system image, which means /system. So to check for /vendor and
// /product, here are 2 special flags.
ret_value = ret_value || (applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0;
ret_value = ret_value || (applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0;
}
return ret_value;
}
要检查应用程序是特权系统应用程序还是普通系统应用程序,和/或使用平台/系统密钥签名,我将在下面留下 3 个功能。我相信这是题外话,但我会提出以防万一像我这样的人需要它。
/**
* <p>Checks if an app is an ordinary system app (installed on the system partitions, but no privileged or signature
* permissions granted to it).</p>
* <p>Note: will return false for any app on KitKat 4.4 and below.</p>
*
* @param applicationInfo an instance of {@link ApplicationInfo} for the package to be checked
*
* @return true if it is, false otherwise
*/
private static boolean isOrdinarySystemApp(@NonNull final ApplicationInfo applicationInfo) {
// It's an ordinary system app if it doesn't have any special permission privileges (it's not a Privileged app
// nor is it signed with the system key).
boolean ret_value = isSystemApp(applicationInfo) && !hasPrivilegedPermissions(applicationInfo);
final boolean signed_system_key = hasSystemSignaturePermissions(applicationInfo);
ret_value = ret_value && signed_system_key;
return ret_value;
}
/**
* <p>Checks if an app has signature permissions - checks if it's signed with the platform/system certificate by
* comparing it to the "android" package.</p>
* <br>
* <p>ATTENTION: if the chosen app was signed multiple times and the system is running below Android Pie, this check
* may return false wrongly, since it checks if ALL the signatures from the "android" package and the chosen
* application match. If at least one doesn't match in both, this will return false. So use with caution in case of
* multiple signers. With only one signer, it's all right.</p>
*
* @param applicationInfo an instance of {@link ApplicationInfo} for the package to be checked
* @return true if it is, false otherwise
*/
private static boolean hasSystemSignaturePermissions(@NonNull final ApplicationInfo applicationInfo) {
// If on Pie or above, check with a private flag (appeared on Pie only).
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return (applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY) != 0;
}
// Else, check by comparing signatures of a platform-signed app and the chosen app.
return UtilsGeneral.getContext().getPackageManager().checkSignatures(applicationInfo.packageName, "android")
== PackageManager.SIGNATURE_MATCH;
}
/**
* <p>"Value for {@link ApplicationInfo#flags}: set to {@code true} if the application
* is permitted to hold privileged permissions.</p>
*
* {@hide}"
* <p>NOTE: Only on API 19 through API 22.</p>
*/
private static final int FLAG_PRIVILEGED = 1 << 30;
/**
* <p>Checks if an app is a Privileged App.</p>
* <p>Note: will return true for any system app below KitKat 4.4.</p>
*
* @param applicationInfo an instance of {@link ApplicationInfo} for the package to be checked
*
* @return true if it is, false otherwise
*/
private static boolean hasPrivilegedPermissions(@NonNull final ApplicationInfo applicationInfo) {
// Check if it's an app installed in the system partitions. If it is, check with methods that apply only to
// apps installed on the system partitions.
if (isSystemApp(applicationInfo)) {
// If it's below KitKat 4.4 and it's a system app, it's a privileged one automatically.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return true;
}
// If on Marshmallow or above, check with a private flag.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
return true;
}
}
// If between KitKat 4.4 and Lollipop 5.1, use a deleted flag.
if ((applicationInfo.flags & FLAG_PRIVILEGED) != 0) {
return true;
}
}
// In case none returned true above, the app may still be signed with the platform/system's key, which will
// grant it exactly all permissions there are (which includes privileged permissions - ALL permissions).
return hasSystemSignaturePermissions(applicationInfo);
}
如果你愿意,你可以把最后一个加入上面的那个,但我真的不推荐它。只要系统应用程序未更新,它就可以工作。
/**
* <p>Gets a list of folders a system app might be installed in, depending on the device's Android version.</p>
* <p>Note that an updated system app will report as being installed in /data/app. For these locations to be
* checked, the app must not have been updated. If it has, it's not possible to tell using the directory, I think.</p>
*
* @param privileged_app true if it's to return a list for privileged apps, false if it's for ordinary system apps,
* null if it's to return a list for both types
*
* @return a list of folders its APK might be in
*/
@NonNull
private static String[] getAppPossibleFolders(@Nullable final Boolean privileged_app) {
final Collection<String> ret_folders = new ArrayList<>(5);
final String PRIV_APP_FOLDER = "/system/priv-app";
final String ORD_APP_SYSTEM_FOLDER = "/system/app";
final String ORD_APP_VENDOR_FOLDER = "/vendor/app";
final String ORD_APP_PRODUCT_FOLDER = "/product/app";
if (privileged_app == null) {
ret_folders.add(PRIV_APP_FOLDER);
ret_folders.add(ORD_APP_SYSTEM_FOLDER);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ret_folders.add(ORD_APP_VENDOR_FOLDER);
ret_folders.add(ORD_APP_PRODUCT_FOLDER);
}
} else if (privileged_app) {
ret_folders.add(PRIV_APP_FOLDER);
} else {
ret_folders.add(ORD_APP_SYSTEM_FOLDER);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ret_folders.add(ORD_APP_VENDOR_FOLDER);
ret_folders.add(ORD_APP_PRODUCT_FOLDER);
}
}
// Leave it in 0 size allocation. Or null values will appear, and I don't want to need to be careful about it.
return ret_folders.toArray(new String[0]);
/*
Use with:
// If it's an updated system app, its APK will be said to be in /data/app, and the one on the system partitions
// will become unused. But if it's not updated, it's all fine and the APK path can be used to check if it's
// a privileged app or not.
if (!isUpdatedSystemApp(applicationInfo)) {
for (final String folder : getAppPossibleFolders(false)) {
if (applicationInfo.sourceDir.startsWith(folder)) {
return true;
}
}
}
*/
}