Xposed AppSettings权限管理原理分析

    xiaoxiao2021-03-26  33

    转:http://vbill.github.io/2015/02/13/xposed-appsettings/

    本文分析一个权限管理类Xposed模块的源代码,主要分析权限管理功能实现的原理。完全按照本人看代码的顺序写成。写此文主要不是为了分析代码,而是总结这种分析代码的思路。所以,懒得看过程可以直接跳到代码分析。

    准备工作


    下载好源代码,还要在手机上把这个程序安装好,这样能直观感受它的功能。

    大致思路


    我们最关键的任务是找到权限控制的核心代码并弄明白它的功能。但是这么多的文件无从下手。我的想法是结合程序的实际操作,然后翻出来相应的代码。

    直奔主题

    先看AndroidManifest.xml,因为我们要找程序启动界面。除了xposed的meta data,有个定义launcher activity的代码:

    <activity android:name=".XposedModActivity" android:label="@string/app_name" android:configChanges="orientation|screenSize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>

    就是说类de.robv.android.xposed.mods.appsettings.XposedModActivity包含启动界面的代码。

    在手机上打开程序,会看到启动界面主要是一个ListView,每项里面放着app的名称和包名。点击其中某项会跳到新的Activity里。那么目标明确了,找这个跳转代码。

    我用startAcitivity为关键词找到了:

    list.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // Open settings activity when clicking on an application String pkgName = ((TextView) view.findViewById(R.id.app_package)).getText().toString(); Intent i = new Intent(getApplicationContext(), ApplicationSettings.class); i.putExtra("package", pkgName); startActivityForResult(i, position); } });

    可见,ApplicationSettings这个类包含了新的Activity的代码。

    新界面只有一个switch,打开switch后所有的选项都出来了。我手机的左下角出现了一个叫”权限管理“的按钮。点开以后是一个对话框,里面的ListView罗列了所有的应用权限让我们修改。单击List里的某项,权限就被禁用了,同时权限的字体由白变紫。

    所以我先找这个按钮的代码:

    btnPermissions.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // set up permissions editor try { final PermissionSettings permsDlg = new PermissionSettings(ApplicationSettings.this, pkgName, allowRevoking, disabledPermissions); permsDlg.setOnOkListener(new PermissionSettings.OnDismissListener() { @Override public void onDismiss(PermissionSettings obj) { allowRevoking = permsDlg.getRevokeActive(); disabledPermissions.clear(); disabledPermissions.addAll(permsDlg.getDisabledPermissions()); } }); permsDlg.display(); } catch (NameNotFoundException e) { } } });

    看来PermissionSettings就是权限管理界面的类。在这个类中唯一被我发现的和ListView有关的代码:

    // Load the list of permissions for the package and present them loadPermissionsList(pkgName); final PermissionsListAdapter appListAdapter = new PermissionsListAdapter(owner, permsList, disabledPerms, true); appListAdapter.setCanEdit(revokeActive); ((ListView) dialog.findViewById(R.id.lstPermissions)).setAdapter(appListAdapter);

    所以说处理单击ListView项并改变程序权限的代码应该在别的地方。

    只能是在Adapter的代码里了:

    if (allowEdits) { row.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (!canEdit) { return; } TextView tv = (TextView) v.findViewById(R.id.perm_name); if ((tv.getPaintFlags() & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { disabledPerms.remove(tv.getTag()); tv.setPaintFlags(tv.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG)); tv.setTextColor(Color.WHITE); } else { disabledPerms.add((String) tv.getTag()); tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); tv.setTextColor(Color.MAGENTA); } } }); }

    在这里disabledPerms是一个Set<String>类型的对象。顾名思义,这里面方的可能是被禁用的权限。上面代码并没有直接处理权限的部分。结合Diaglog界面有“确定”按钮,推测最后程序先将权限添加到Set中,然后统一禁止权限。

    那么disabledPerms怎么传递出去的呢?搜索整个Adapter的代码,看到构造函数里有一句:this.disabledPerms = disabledPerms;。再回头看该才找到的PermissionSettings类里和List唯一有关的代码里有:

    final PermissionsListAdapter appListAdapter = new PermissionsListAdapter(owner, permsList, disabledPerms, true);

    所以disabledPerms就是我们要找的。下一步可以找PermissionSettings里的这个disabledPerms里的结果怎么返回回去的。搜索这个类里的代码,找到了get方法:

    /** * Get the list of permissions in the disabled state */ public Set<String> getDisabledPermissions() { return new HashSet<String>(disabledPerms); }

    我们之前找到的ApplicationSettings里面的btnPermissions.setOnClickListener里有这么一行:disabledPermissions.addAll(permsDlg.getDisabledPermissions());。

    另外disabledPermissions只有声明没有定义。在onCreate()方法里它才被赋值:

    // Setting for permissions revoking allowRevoking = prefs.getBoolean(pkgName + Common.PREF_REVOKEPERMS, false); disabledPermissions = prefs.getStringSet(pkgName + Common.PREF_REVOKELIST, new HashSet<String>());

    同时在private Map<String, Object> getSettings()这个方法结尾处有这么两行:

    if (disabledPermissions.size() > 0) settings.put(pkgName + Common.PREF_REVOKELIST, new HashSet<String>(disabledPermissions));

    方法的返回值就是settings。如果再看看整个方法的代码,可知这个应用的所有被修改内容全放到这个settings里了。

    在onOptionsItemSelected里出现了getSettings()的调用,同时还有很多sharedPreference的操作。在手机上,我们修改应用权限,然后单击右上角的保存按钮,弹出提示对话框,询问是否结束进程以便下次启动时采用新设置。根据这点找到代码:

    prefsEditor.commit(); // Update saved settings to detect modifications later initialSettings = newSettings; // Check if in addition to saving the settings, the app should also be killed AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.settings_apply_title); builder.setMessage(R.string.settings_apply_detail); builder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Send the broadcast requesting to kill the app Intent applyIntent = new Intent(Common.MY_PACKAGE_NAME + ".UPDATE_PERMISSIONS"); applyIntent.putExtra("action", Common.ACTION_PERMISSIONS); applyIntent.putExtra("Package", pkgName); applyIntent.putExtra("Kill", true); sendBroadcast(applyIntent, Common.MY_PACKAGE_NAME + ".BROADCAST_PERMISSION"); dialog.dismiss(); } });

    整个工程只有一个PackagePermissions类是BroadcastReceiver类。打开后发现这个类有大量的Xposed的hook函数。那么问题来了:

    广播接受器什么时候开始工作的? 哪些是hook权限的操作? hook是如何在开机时就开始了(否则没办法监控权限)

    在BroadcastReceiver里叫initHooks()的静态方法里找到:

    final Class<?> clsPMS = findClass("com.android.server.pm.PackageManagerService", XposedMod.class.getClassLoader()); // Listen for broadcasts from the Settings part of the mod, so it's applied immediately findAndHookMethod(clsPMS, "systemReady", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Context mContext = (Context) getObjectField(param.thisObject, "mContext"); mContext.registerReceiver(new PackagePermissions(param.thisObject), new IntentFilter(Common.MY_PACKAGE_NAME + ".UPDATE_PERMISSIONS"), Common.MY_PACKAGE_NAME + ".BROADCAST_PERMISSION", null); } }); // if the user has disabled certain permissions for an app, do as if the hadn't requested them findAndHookMethod(clsPMS, "grantPermissionsLPw", "android.content.pm.PackageParser$Package", boolean.class, new XC_MethodHook() { @SuppressWarnings("unchecked") @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String pkgName = (String) getObjectField(param.args[0], "packageName"); if (!XposedMod.isActive(pkgName) || !XposedMod.prefs.getBoolean(pkgName + Common.PREF_REVOKEPERMS, false)) return; Set<String> disabledPermissions = XposedMod.prefs.getStringSet(pkgName + Common.PREF_REVOKELIST, null); if (disabledPermissions == null || disabledPermissions.isEmpty()) return; ArrayList<String> origRequestedPermissions = (ArrayList<String>) getObjectField(param.args[0], "requestedPermissions"); param.setObjectExtra("orig_requested_permissions", origRequestedPermissions); ArrayList<String> newRequestedPermissions = new ArrayList<String>(origRequestedPermissions.size()); for (String perm: origRequestedPermissions) { if (!disabledPermissions.contains(perm)) newRequestedPermissions.add(perm); else // you requested those internet permissions? I didn't read that, sorry Log.w(Common.TAG, "Not granting permission " + perm + " to package " + pkgName + " because you think it should not have it"); } setObjectField(param.args[0], "requestedPermissions", newRequestedPermissions); } @SuppressWarnings("unchecked") @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { // restore requested permissions if they were modified ArrayList<String> origRequestedPermissions = (ArrayList<String>) param.getObjectExtra("orig_requested_permissions"); if (origRequestedPermissions != null) setObjectField(param.args[0], "requestedPermissions", origRequestedPermissions); }

    分析代码

    BroadcastReceiver里的注释说:

    /* Hook to the PackageManager service in order to * - Listen for broadcasts to apply new settings and restart the app * - Intercept the permission granting function to remove disabled permissions */

    也就是说监听器hook到了Android系统的包管理器类com.android.server.pm.PackageManagerService,并且hook了里面的方法。所以上面提到的哪些是hook权限的操作基本上解决了,代码之后详细分析。那么广播什么时候开始接收的?在IntelliJ里搜索BroadcastReceiver被用到的地方,发现:

    XposedMod类中initZygote方法里出现PackagePermissions.initHooks(); 刚才贴出的大段BroadcastReceiver里出现mContext.registerReceiver里面有它的构造函数。

    initZygote是Xposed框架IXposedHookZygoteInit接口中要自己实现的方法。接口的源代码为:

    /** * Hook the initialization of Zygote (the central part of the "Android OS") */ public interface IXposedHookZygoteInit extends IXposedMod { /** * Called very early during startup of Zygote * @throws Throwable everything is caught, but will prevent further initialization of the module */ public void initZygote(StartupParam startupParam) throws Throwable; public static class StartupParam { public String modulePath; } }

    这就意味着每当启动一个进程,都会执行initZygote里监听器的initHooks()方法来给包管理器挂钩。权限监听的钩子应该挂到com.android.server.pm.PackageManagerService这个类的名为的grantPermissionsLPw方法上。程序用了Xposed框架提供的findAndHookMethod方法。通过这个方法接收的参数,我们得知被hook的grantPermissionsLPw方法接收两个参数,分别是Package类型(android.content.pm.PackageParser的内部类),和boolean。initHooks()方法还顺带注册监听器来接受来自appSettings这个app自己发出的广播。再进一步查看工程代码和安卓源代码,就知道,appSettings的权限拦截原理是这样的:

    原来的权限授权以前先:从之前appsSettings存储的sharedPreferences里取得对应应用的权限列表,放到Set<String> disabledPermissions里。并用ArrayList<String> origRequestedPermissions存放应用索要的权限,并利用Xposed自带的方法存储一份这个权限列表到param对象里。然后通过for each循环,对比两个ArrayList,生成第三张表newRequestedPermissions,并用setObjectField方法替换掉了原来Package对象的requestedPermissions对象。

    之后,安卓系统会按照被我们“调包”的权限清单执行程序。

    被hook过的方法执行以后:从param对象取出我们刚刚保存的原始的权限列表,然后再次用setObjectField把这个原始列表复原回去。

    于是第二个问题,哪些是hook操作,怎么hook的问题就解决了。

    最后,我们打开工程目录下assets/xposed_init文件,看到de.robv.android.xposed.mods.appsettings.XposedMod。所以,Xposed框架开始执行的就是这个类里的代码。它实现了IXposedHookZygoteInit与 IXposedHookLoadPackage接口。所以能:1.在zygote启动时执行,从而管理权限。2.在应用app的包加载前执行hook操作,替换应用资源(这是appSettings另一个功能,但本文不分析)。

    所以,为何启动时就能hook的问题也解决了。

    转载请注明原文地址: https://ju.6miu.com/read-664276.html

    最新回复(0)