Xposed AppSettings权限管理原理分析
2017-02-06 12:00
225 查看
转:http://vbill.github.io/2015/02/13/xposed-appsettings/
本文分析一个权限管理类Xposed模块的源代码,主要分析权限管理功能实现的原理。完全按照本人看代码的顺序写成。写此文主要不是为了分析代码,而是总结这种分析代码的思路。所以,懒得看过程可以直接跳到代码分析。
下载好源代码,还要在手机上把这个程序安装好,这样能直观感受它的功能。
我们最关键的任务是找到权限控制的核心代码并弄明白它的功能。但是这么多的文件无从下手。我的想法是结合程序的实际操作,然后翻出来相应的代码。
先看AndroidManifest.xml,因为我们要找程序启动界面。除了xposed的meta data,有个定义launcher activity的代码:
就是说类
在手机上打开程序,会看到启动界面主要是一个ListView,每项里面放着app的名称和包名。点击其中某项会跳到新的Activity里。那么目标明确了,找这个跳转代码。
我用startAcitivity为关键词找到了:
可见,ApplicationSettings这个类包含了新的Activity的代码。
新界面只有一个switch,打开switch后所有的选项都出来了。我手机的左下角出现了一个叫”权限管理“的按钮。点开以后是一个对话框,里面的ListView罗列了所有的应用权限让我们修改。单击List里的某项,权限就被禁用了,同时权限的字体由白变紫。
所以我先找这个按钮的代码:
看来
所以说处理单击ListView项并改变程序权限的代码应该在别的地方。
只能是在Adapter的代码里了:
在这里
那么
所以
我们之前找到的
另外
同时在
方法的返回值就是settings。如果再看看整个方法的代码,可知这个应用的所有被修改内容全放到这个
在
整个工程只有一个PackagePermissions类是BroadcastReceiver类。打开后发现这个类有大量的Xposed的hook函数。那么问题来了:
广播接受器什么时候开始工作的?
哪些是hook权限的操作?
hook是如何在开机时就开始了(否则没办法监控权限)
在BroadcastReceiver里叫initHooks()的静态方法里找到:
BroadcastReceiver里的注释说:
也就是说监听器hook到了Android系统的包管理器类
XposedMod类中
刚才贴出的大段BroadcastReceiver里出现
这就意味着每当启动一个进程,都会执行
原来的权限授权以前先:从之前appsSettings存储的sharedPreferences里取得对应应用的权限列表,放到
each循环,对比两个ArrayList,生成第三张表
之后,安卓系统会按照被我们“调包”的权限清单执行程序。
被hook过的方法执行以后:从param对象取出我们刚刚保存的原始的权限列表,然后再次用
于是第二个问题,哪些是hook操作,怎么hook的问题就解决了。
最后,我们打开工程目录下
所以,为何启动时就能hook的问题也解决了。
本文分析一个权限管理类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的问题也解决了。
相关文章推荐
- Xposed AppSettings权限管理原理分析
- shiro原理的分析,系统权限管理以及 运行流程分析
- 权限管理设计、分析、实现参考资料
- PGA自动管理原理深入分析及性能调整
- Android 核心分析(12) -----Android GEWS窗口管理之基本架构原理
- 权限的管理(二进制原理)
- [网际参考]权限管理设计、分析、实现
- EOSS V1.0企业运营支撑系统(基于RBAC原理的权限管理)
- 经典的用户权限管理,数据结构分析设计
- 通用权限管理控件-控件原理
- 经典的用户权限管理,数据结构分析设计
- Oracle - PGA自动管理原理深入分析及性能调整(6)
- Oracle - PGA自动管理原理深入分析及性能调整(2)
- Oracle - PGA自动管理原理深入分析及性能调整(3)
- 权限管理设计、分析、实现参考资料
- 权限管理设计、分析、实现参考资料
- Oracle - PGA自动管理原理深入分析及性能调整(5)
- 权限管理设计、分析、实现参考资料
- 基于web信息管理系统的权限设计分析和总结(数据结构)