文档章节

Xposed AppSettings权限管理原理分析

我爱睡觉
 我爱睡觉
发布于 2017/06/24 17:36
字数 2199
阅读 114
收藏 0

转: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里的某项,权限就被禁用了,同时权限的字体由白变紫。

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

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里的注释说:

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被用到的地方,发现:

  1. XposedMod类中initZygote方法里出现PackagePermissions.initHooks();
  2. 刚才贴出的大段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的问题也解决了。


本文转载自:http://blog.csdn.net/zhangmiaoping23/article/details/54891490

我爱睡觉
粉丝 3
博文 2120
码字总数 0
作品 0
南昌
私信 提问
史上最全面 Android逆向培训之__Xposed使用

刚招来个Android,干了半个月辞职了,他走之后,成堆的bug被测了出来,都是这个新人代码都没看懂就开始改的一塌糊涂,还给提交了。 实在是让人头疼,清理了一个月多月才把他半个月写的bug清理...

Android逆向大神
07/18
0
0
Xposed恶意插件可自动安装激活

Post by Gandalf 概述 AVL移动安全团队在之前的《Xposed恶意插件》一文中分析了一个Xposed恶意插件,但是由于Xposed模块需要手动勾选重启后才能生效,导致应用场景有限。之后网友MindMac提供...

骑牛找牛
2015/01/14
1K
0
那些年Android黑科技②:欺骗的艺术

“我的能量无穷无尽,只有强大暗能量才能统治Android界。 受屎吧!!! =≡Σ((( つ•̀ω•́)つ ” -- 来自暗世界android工程师 前言: 这是黑科技系列的第二篇,是Android知识正营中较有深...

猴亮屏
2017/10/24
136
0
Android逆向之旅---破解某支付软件防Xposed的hook功能检测机制过程分析

一、情景介绍 最近想写几个某支付软件的插件,大家现在都知道现在插件大部分都是基于Xposed的hook功能,包括之前写了很多的某社交软件的插件,所以不多说就直接用Jadx打开支付软件之后然后找...

jiangwei0910410003
2018/05/15
0
0
【转】微信机器人技术总结

http://xun.im/2017/04/26/weixin-robots-tecnicical-review/ 微信机器人技术总结 本文简单从原因,技术原理,稳定性三个角度总结一下微信机器人技术。如有遗漏,欢迎补充。 定义 本文的微信...

章冬阳
2017/04/26
11
0

没有更多内容

加载失败,请刷新页面

加载更多

JS--function

一、声明提前(hoist) 在js程序开始执行前,引擎会查找所有var声明的变量和function声明的函数,集中到当前作用域顶部集中创建,赋值留在原地 二、三种创建函数的方式 1、声明方式创建函数-...

wytao1995
今天
4
0
微服务之间调用控制器注解类型的差异

今天在一个业务服务通过Feign调用文件服务上传文件时遇到了几个问题: 1. 提示http请求头过大的问题; 此时需要修改bootstrap.yml,加入 server: max-http-header-size: 10000000 用以放大...

不再熬夜
今天
7
0
用 4G 工作是什么体验

七月开始,因为工作原因,在公司附近租了个住处,方便工作。离公司近了,感觉就是不一样,之前每天 5:30 就要起床赶地铁,现在可以睡到自然醒,一看才 7 点,悠闲的起床洗漱,踱步到公司,都...

zzxworld
今天
6
0
sonar报错volatile

问题发生 原先代码如下: //认证授权码private static volatile String AUTHORIZATION_CODE = "init"; git push 之后,sonar认为这是个bug检测报告截图如下: 分析排查 解释说明: Markin...

开源小菜鸟2333
今天
5
0
《Java实践指南》--读后

闲读《Java实践指南》... 1.lvy 某些项目中能够看到ivy.xml。早期使用ant的项目中,常常用ivy.xml来下载项目依赖。 2.ant 作为java程序员,应该都知道ant,虽然可能用过的人不多。为什么ant...

RippleChan
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部