MuntashirAkon / AppManager

A full-featured package manager and viewer for Android
https://muntashirakon.github.io/AppManager/
Other
4.8k stars 271 forks source link

Force revoke a normal permission #725

Open cpuuntery opened 2 years ago

cpuuntery commented 2 years ago

Describe the solution you'd like While you can control plenty of permission using Appops. But there are permissions you cannot control using Appops like [android.permission.INTERNET] but according to this post you can control other permissions using the file [runtime-permissions.xml] the file path in android 6 to 9 is /data/system/users/<User_ID>/runtime-permissions.xml but in android 12 /data/misc_de/0/apexdata/com.android.permission/runtime-permissions.xml

For example, you can change

<shared-user name="com.termux">
    <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
    <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
    <item name="android.permission.INTERNET" granted="true" flags="0" />
</shared-user>
<shared-user name="com.termux">
    <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
    <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
    <item name="android.permission.INTERNET" granted="false" flags="0" />
</shared-user>

And android support parsing XML files. And I know I can edit the file manually, but the XML file has too many lines and the copy and paste after the edit is a hassle and a chore for me. I would appreciate it very much if this app will automate that process from me.

MuntashirAkon commented 2 years ago

Does it actually work? android.permission.INTERNET is not a runtime permission.

cpuuntery commented 2 years ago

@MuntashirAkon Yes, I tried multiple times with multiple apps, and it works 100% every time. And even if the file is called runtime-permissions.xml. The file includes almost all permissions, if not all permissions. even non runtime-permissions and even if you don't believe me, you can try yourself using android emulator

MuntashirAkon commented 2 years ago

Related issue #132

cpuuntery commented 2 years ago

After a bit of research, it turns out while android can parse XML files natively. But It treats XML files as immutable files (Read only) and according to this answer, modifying XML file is really hard so I thought why not regex the XML file.

And yes, I know that some people think regexing in XML file is bad thing but runtime-permissions.xml is a really simple XML. there are not 10 nested children or anything like that and we can exclude any permission we can change using pm revoke

here is a list of the permissions that pm revoke can not change

android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_NETWORK_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.REORDER_TASKS
android.permission.DOWNLOAD_WITHOUT_NOTIFICATION
android.permission.FOREGROUND_SERVICE
android.permission.INTERNET
android.permission.MANAGE_ACCOUNTS
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.USE_CREDENTIALS
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
android.permission.AUTHENTICATE_ACCOUNTS
android.permission.READ_PROFILE

here is the regex that can change the permission

(android.package.name*?android.permission.anypermission" granted=)(".*?")

replace

$1true to enable and $1false to disable permission

demo

so the plan is to parse runtime-permissions.xml to get a list of packages and permission and exclude some permission that we can revoke using pm revoke then regex the file to disable or enable permission

and lastly, i know implementing features is hard and time-consuming for devolopers but i think this feature will set the app apart from every other app that can control permission

MuntashirAkon commented 2 years ago

Rest assured, if this feature is implemented, the file will be written just like how it is written by the framework. Also, regex will no longer work in the future as Android is migrating to a new binary XML schema for faster parsing of the XML files.

cpuuntery commented 1 year ago

@MuntashirAkon To help the developer, this is my full research on the subject. I have a lot to say, so I will try to write three comments or five comment for easier readability. and not write everything in one comment to prevent it from turning in to a very long wall of text.

cpuuntery commented 1 year ago

In my first comment. I only manipulated the permission on android 12 and did not test on older version of android. In the older version of android. You need to change /data/system/packages.xml, and you do not need to turn the true to false you just delete the line that contain the permission.

cpuuntery commented 1 year ago

The first question I asked myself is how reliable changing packages.xml. It turns out, changing it does not have effect until system_server restart(pkill system_server) or android reboot

and the permission gets added to back to packages.xml after the package gets updated

so, after a bit of research, it turns out android cached permissions after parsing AndroidManifest.xml inside the apk. In fact, this command will cause the cache to be written to packages.xml dumpsys package write

cpuuntery commented 1 year ago

After knowing, the permission get cached after parsing AndroidManifest.xml. I started asking myself can I stop android from caching the permission so changes to packages.xml take effect immediately. And according to this commit there are some interesting methods like

       PackageManager.invalidatePackageInfoCache();
       PermissionManager.disablePermissionCache();
       PermissionManager.disablePackageNamePermissionCache();

PackageManager.corkPackageInfoCache();

@Override
        public void disablePackageCaches() {
            // disable all package caches that shouldn't apply within system server
            PackageManager.disableApplicationInfoCache();
            PackageManager.disablePackageInfoCache();
            ApplicationPackageManager.invalidateGetPackagesForUidCache();
            ApplicationPackageManager.disableGetPackagesForUidCache();
            ApplicationPackageManager.invalidateHasSystemFeatureCache();
            PackageManager.corkPackageInfoCache();
        }

but some of these methods only exist in android 11+ and even if we can call these methods we still need to change packages.xml

cpuuntery commented 1 year ago

So after giving up on disabling the permission cache, I asked myself can we manipulate/change the permission cache. But to change something you need to understand how it works first so i found a plenty of blogs in Chinese that explain how the caching works. I used Google Translate to read the blogs. blog1 blog2 blog3 blog4 blog5 blog6 blog7 blog8 blog9 blog10 blog11 blog12 blog13 blog14 the blog in english but is it is very old

cpuuntery commented 1 year ago

After learning how permission cache works. How can we change it? Or can we intercept calls to the permission cache midway and change the returned results. The answer is yes, we can. We just need to hook the PackageManagerService process and manipulate the cached array of permissions.

And as a proof of my theory I found this thread to change the mapping of android permission to the linux user groups without restarting android or killing the system_server and changing etc/permissions/platform.xml

package kz.virtex.android.sdcardfix;

import de.robv.android.xposed.IXposedHookZygoteInit;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;

public class XMain implements IXposedHookZygoteInit
{
    @Override
    public void initZygote(StartupParam startupParam) throws Throwable
    {
        final Class<?> pms = XposedHelpers.findClass("com.android.server.pm.PackageManagerService", null);

        XposedHelpers.findAndHookMethod(pms, "readPermission", "org.xmlpull.v1.XmlPullParser", "java.lang.String", new XC_MethodHook()
        {
            protected void afterHookedMethod(MethodHookParam param) throws Throwable
            {
                String permission = (String) param.args[1];

                if (permission.equals("android.permission.WRITE_EXTERNAL_STORAGE")) {
                    Class<?> process = XposedHelpers.findClass("android.os.Process", null);
                    int gid = (Integer) XposedHelpers.callStaticMethod(process, "getGidForName", "media_rw");
                    Object mSettings = XposedHelpers.getObjectField(param.thisObject, "mSettings");
                    Object mPermissions = XposedHelpers.getObjectField(mSettings, "mPermissions");
                    Object bp = XposedHelpers.callMethod(mPermissions, "get", permission);
                    int[] bp_gids = (int[]) XposedHelpers.getObjectField(bp, "gids");
                    XposedHelpers.setObjectField(bp, "gids", appendInt(bp_gids, gid));
                }
            }
        });
    }

    private static int[] appendInt(int[] cur, int val)
    {
        if (cur == null) {
            return new int[]
            { val };
        }
        final int N = cur.length;
        for (int i = 0; i < N; i++) {
            if (cur[i] == val) {
                return cur;
            }
        }
        int[] ret = new int[N + 1];
        System.arraycopy(cur, 0, ret, 0, N);
        ret[N] = val;
        return ret;
    }
}
cpuuntery commented 1 year ago

in the old days there was exposed, but now there are only two options, Zygisk or LSPosed

lastly how can you test Zygisk or LSPosed

well LSPosed require Magisk to be installed and Zygisk comes by default with Magisk

the question now is how to install Magisk

well if you are using android avd you can use this repository https://github.com/newbit1/rootAVD

you alse can run android inside using Docker https://github.com/remote-android/redroid-doc https://github.com/ayasa520/redroid-script

or android x86 for newer version you can use BlissOS these two repos to install Magisk https://github.com/HuskyDG/initrd-magisk https://github.com/shakalaca/MagiskOnEmulator

cpuuntery commented 1 year ago

@MuntashirAkon And to emphasize again, I am not writing multiple comment to bother the developer or anything like that. I wrote multiple comment for easier readability.

And adding process hooking to the app will help with making existing features like changing the SSAID without the need for reboot.

And it will also help in the future if android changed every xml to the new binary xml format. Because we can let android do the heavy lifting, and we can intercept the data midway to read it or change it.

cpuuntery commented 1 year ago

And just to be safe, this is my last comment on this issue unless I hear anything that says otherwise from the developer

MuntashirAkon commented 1 year ago

I have only skimmed over your comments. So, please correct me if I am wrong but this looks overly complicated. Unlike say SSAID or SAF, the probability of breaking something is quite high here. Permissions are at the heart of Android and its security. Therefore, they have to be handled delicately. I think, an interesting experiment would be to find a way to let the system know that the permissions are in fact run-time permissions, not the usual normal permissions.

cpuuntery commented 1 year ago

@MuntashirAkon

So, please correct me if I am wrong but this looks overly complicated

There are many ways to do the same thing. This is what I was meaning in my comments.

In android, apps can not use permissions even if it is normal (granted at install time) if it is not declared in AndroidManifest.xml My thought is to use this behaviour.

After AndroidManifest.xml gets parsed, the permissions array is passed(we will intercept the array before it get passed to the next component) to the android components responsible for permission management.

by using this way, we will not change anything (logic or behaviour inside permission management) in android permission management/enforcement.

The only thing that is changed is data that is passed to it so if the app have android.permission.RECEIVE_BOOT_COMPLETED in it's AndroidManifest.xml we will make android think that the permission does not exist in the manifest.

Android default behaviour is to deny any permissions that are not in AndroidManifest.xml

cpuuntery commented 1 year ago

Permissions are at the heart of Android and its security. Therefore, they have to be handled delicately

While I didn't mean to suggest doing things this way

even if we change the behaviour or logic of permission management, like the link to the POC.

I fail to understand how that will make android less secure. I mean yes it is more aggressive change than what I was thinking of, but it is still a valid option

And this feature request is about revoking permissions, not granting permissions, so I think it will improve security.

cpuuntery commented 1 year ago

@MuntashirAkon

I think, an interesting experiment would be to find a way to let the system know that the permissions are in fact run-time permissions, not the usual normal permissions

Your suggestion is also very valid. And i 100% agree it is valid, but it is not the only one in my opinion.

I think that my first plan (tricking android into thinking the permission does not exist) is the easiest.

But in the end I just want this feature implemented in your great and wonderful app

MuntashirAkon commented 1 year ago

In android, apps can not use permissions even if it is normal (granted at install time) if it is not declared in AndroidManifest.xml My thought is to use this behaviour.

Yes. But the latter approach is universal, applicable to all apps and within the concepts used by Android, and this sort of features already offered by some operating systems (e.g. Graphene OS).

we will not change anything (logic or behaviour inside permission management) in android permission management/enforcement.

It's not that simple. Take the INTERNET permission as an example. An app declaring this permission expects that the permission would be granted by default. If the permission were revoked or were not present in AndroidManifest.xml, the app would crash immediately. To mitigate the issue, additional measures have to be taken to ensure that the such thing would never happen. This requires a lot of modifications to the frameworks via hooks (e.g. creating a fake app op and controlling the permission via app op instead of the permission itself). However, not all permissions cause such issues. But most interested ones (such as the one I've just discussed) do.

cpuuntery commented 1 year ago

@MuntashirAkon

It's not that simple. Take the INTERNET permission as an example. An app declaring this permission expects that the permission would be granted by default. If the permission were revoked or were not present in AndroidManifest.xml, the app would crash immediately.

i fully agree with you that maybe disabling permissions that the app expect will always available will maybe cause an app to crash. So what you are thinking may apply to a permission like android.permission.VIBRATE and android.permission.RECEIVE_BOOT_COMPLETED

But i 100% sure tricking android to believe that if android.permission.INTERNET does not exist in AndroidManifest.xml does not cause any app to crash

app without android.permission.INTERNET permission is the same as app with android.permission.INTERNET permission, but Wi-Fi is disabled and airplane mode is on

and if your theory is really correct, then I would have seen app crashes. When I manually modified packages.xml or runtime-permissions.xml

So I suggest that we trick android into thinking that android.permission.INTERNET does not exist in AndroidManifest.xml when dealing with the INTERNET permission and use you appopsmethod when dealing with other permissions

MuntashirAkon commented 1 year ago

i fully agree with you that maybe disabling permissions that the app expect will always available will maybe cause an app to crash. So what you are thinking may apply to a permission like android.permission.VIBRATE and android.permission.RECEIVE_BOOT_COMPLETED

You can revoke the related app op for the former, and for the latter, you can simply disable the broadcast receivers that receive it.

app without android.permission.INTERNET permission is the same as app with android.permission.INTERNET permission, but Wi-Fi is disabled and airplane mode is on

That's not how it works. The network API directly throws a SecurityException when it cannot initiate an Internet connection. Since most apps expect it to be granted beforehand, most apps crash immediately when it attempts to connect to the Internet (see #833).

cpuuntery commented 1 year ago

Yes. But the latter approach is universal, applicable to all apps and within the concepts used by Android, and this sort of features already offered by some operating systems (e.g. Graphene OS).

I think that implementing appops. Will take too much time and is way harder to do than tricking android into thinking that a specific permission does not exist in AndroidManifest.xml.

And just like I said before it my not work with all the normal permissions, but I am sure it will work with android.permission.INTERNET permission.

I suggest that you consider using the [tricking android into thinking a permission does not exist in AndroidManifest.xml] with at least just the internet permission.

Don't Let the Perfect Be the Enemy of the Good

The appops method, may be more elegant, but it is harder to implement. My method, maybe less elegant, but is way easier to implement. That means that if only and only if you are willing and interested. My method will take less of your valuable time than the appops method, so I am suggesting my method because I think of you and your valuable time.

cpuuntery commented 1 year ago

@MuntashirAkon

You know what they say, a picture is better than a thousand word I tried to block internet access using my way of changing packages.xml and I used version 3.0.0 the same version in issue #833 the app still works after I blocked the internet access I guess my way is better than GrapheneOS. The only thing that it is lacking that updating the app negate the block

before the block picthwsand.png

after the block picthansnd2.png

Here are some very interesting logs after I blocked the internet using my way

*APP* UID 10079 ProcessRecord{de8e56c 4000:io.github.muntashirakon.AppManager/u0a79}
    user #0 uid=10079 gids={50079, 20079, 9997}            ###[[NO group 3003 aka internet]]###

Package [io.github.muntashirakon.AppManager] (da60f24):
    userId=10079
    pkg=Package{2299a8d io.github.muntashirakon.AppManager}
    codePath=/data/app/io.github.muntashirakon.AppManager-QB3YGc-8iuVZ1DW02eibPQ==
    versionCode=410 minSdk=21 targetSdk=32
    versionName=3.0.0                                                ###[[same version in issue #833]]###
    splits=[base]
    apkSigningVersion=2
    flags=[ HAS_CODE ALLOW_CLEAR_USER_DATA LARGE_HEAP ]
    privateFlags=[ HAS_DOMAIN_URLS PARTIALLY_DIRECT_BOOT_AWARE PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION ]
    dataDir=/data/user/0/io.github.muntashirakon.AppManager
    supportsScreens=[small, medium, large, xlarge, resizeable, anyDensity]
    timeStamp=2023-06-06 12:50:30
    firstInstallTime=2023-06-06 12:50:32
    lastUpdateTime=2023-06-06 12:50:32
    signatures=PackageSignatures{edbf442 [f3d46410]}
    installPermissionsFixed=true installStatus=1
    pkgFlags=[ HAS_CODE ALLOW_CLEAR_USER_DATA LARGE_HEAP ]
    install permissions:                                        ###[[NO android.permission.INTERNET]]###
      android.permission.GET_APP_OPS_STATS: granted=true
      android.permission.ACCESS_NETWORK_STATE: granted=true
      android.permission.REQUEST_DELETE_PACKAGES: granted=true
      com.android.launcher.permission.INSTALL_SHORTCUT: granted=true
      android.permission.DUMP: granted=true

cat /data/system/packages.list

io.github.muntashirakon.AppManager 10079 0 /data/user/0/io.github.muntashirakon.AppManager default:targetSdkVersion=32 none                                                                                        ##[[NO group 3003 aka internet]]##

Here is how I changed packages.xml

sed -i -E '/.*<package name>.*/,/.*<permission that will be removed>.*/ s/.*<permission that will be removed>.*/\n/' /data/system/packages.xml

sed -i -E '/.*io.github.muntashirakon.AppManager.*/,/.*android.permission.INTERNET.*/ s/.*android.permission.INTERNET.*/\n/' /data/system/packages.xml

reboot android after the reboot, the app will lose internet access

the-acc-is-ef commented 1 year ago

@MuntashirAkon If you have some free time. Can you post an update of your thoughts on this issue. I tried the sed command, and it worked, but a reboot is needed after you run the command

diabl0w commented 11 months ago

following this feature request... adding this and the apk compile/decompile feature would make this app the true ultimate swiss army knife of android app management