Android绕过授权方法实践(二)

*郑重声明:本文内容只为学习研究之用,如有人用于非法用途,产生的后果笔者不负任何责任。

上回说到我们考虑两种方法:

(1)通过反射,调用PackageManagerService中的设置权限的方法,直接传入权限和应用信息,对应用授权;

(2)直接修改uid:修改 /data/system/packages.list, /data/system/packages.xml等存储应用运行信息的文件

反射

参考了在上篇中引用的引用文献【1】的大佬的思路,这里也实现了大佬文章中的方法

该方法是反射得到IAppOpsService对象,调用其setMode方法,设置权限。IAppOpsService是Android自带的权限管理工具,通过setMode()方法设置权限。

IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);

IAppOpsService service = IAppOpsService.Stub.asInterface(b);

service.setMode(…)

直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
Class class_serviceManager = Class.forName("android.os.ServiceManager");
Method method_getService = class_serviceManager.getMethod("getService", String.class);
Object result_IBinder = method_getService.invoke(null, "appops");

// IAppOpsService service = IAppOpsService.Stub.asInterface(b);
Class class_IAppOpsService_Stub = Class.forName("com.android.internal.app.IAppOpsService$Stub");
Method method_asInterface = class_IAppOpsService_Stub.getMethod("asInterface", Class.forName("android.os.IBinder"));
Object result_service = method_asInterface.invoke(null, result_IBinder);

// service.setMode()
Class class_IAppOpsService = Class.forName("com.android.internal.app.IAppOpsService");
Method method_setMode = class_IAppOpsService.getMethod("setMode", int.class, int.class, String.class, int.class);
method_setMode.invoke(result_service, op, uid, packagename, mode);

在外层套上main函数,然后打包成一个jar,放入设备。应用运行时,通过adb shell命令执行该jar文件。

(1)main():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void main(String[] args) {
if (args.length == 0) {
System.out.println(false);
return;
}
String packagename = null;
int uid = 0;
int mode = -1;
int op = -1;
for (String arg : args) {
if (arg.startsWith("-") && arg.contains("=")) {
String type = arg.substring(arg.indexOf("-") + 1, arg.indexOf("=")).trim();
String value = arg.substring(arg.indexOf("=") + 1).trim();
switch (type) {
case "packagename":
packagename = value;
break;
case "mode":
mode = Integer.parseInt(value);
break;
case "op":
op = Integer.parseInt(value);
break;
case "uid":
uid = Integer.parseInt(value);
break;
}
}
}
if (packagename == null || packagename.isEmpty() || op == -1 || mode == -1) {
System.out.println(false);
return;
}

方法末尾加入反射代码即可。具体打包和执行过程,参考大佬文章不再赘述。

大佬的文章中采用了直接修改framework源码的方法,并且成功了,但是这种方法需要一定的ROM源码知识,我们选择了反射的方法调用IAppOpsService.setMode,最终执行并没有成功。考虑权限设置是否有两套机制,又反射调了IPackageManager.grantRuntimePermission(),即应用弹框请求权限时最终调用的方法。

权限控制源码分析一文中我们可以看到,在ActivityThread.getPackageManager()中:

1
2
IBinder b = ServiceManager.getService("package");
sPackageManager = IPackageManager.Stub.asInterface(b);

这里的sPackageManager就是IPackageManager类型的对象,PackageManagerService实现了IpackageManager

然后,调用grantRuntimePermission()方法,传入包名,权限,uid即可。

代码参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
try {
// IBinder b = ServiceManager.getService("package");
Class class_serviceManager = Class.forName("android.os.ServiceManager");
Method method_getService = class_serviceManager.getMethod("getService", String.class);
Object result_IBinder = method_getService.invoke(null, "package");

// sPackageManager = IPackageManager.Stub.asInterface(b);
Class class_IPackageManager_Stub = Class.forName("com.android.internal.app.IPackageManager$Stub");
Method method_asInterface = class_IPackageManager_Stub.getMethod("asInterface", Class.forName("android.os.IBinder"));
Object result_service = method_asInterface.invoke(null, result_IBinder);

Class class_IpackageManager = Class.forName("com.android.internal.app.IPackageManager");
Method method_grantRuntimePermission = class_IpackageManager.getMethod("grantRuntimePermission", String.class, String.class, int.class);
method_grantRuntimePermission.invoke(result_service, packagename, permission, uid);

} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}

编译执行没有报错,但是最终对权限并无改变。

修改uid

先说结论:方法不可行。因为不管是在 /data/system/packages.list, /data/system/packages.xml文件中修改,还是通过hook修改传入的uid,都只是在上层打转,并没有改变Setings对象中存放的权限情况。

还是说说具体实践过程吧,纯粹做个记录防止回头又掉进这个坑。

(1)hook

主要hook了几个获取uid的方法:UserHandle类的getUid、getIdentifier、getUserId,Process类的myUid。代码示例:

1
2
3
4
5
6
7
8
XposedHelpers.findAndHookMethod(UserHandle.class, "getUid", int.class, int.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
param.setResult(1000);
XposedBridge.log("UserHandle->getUid:1000");
}
});

hook虽然成功了,修改了应用的uid, 但是对授权并没有什么用。

(2)修改存放应用权限的文件信息

/data/system目录中的packages.list和packages.xml两个文件分别存放了应用的uid、安装路径信息和授予的权限信息。

找到package标签中name=包名的项,在<perm>字段增加权限:

<item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />

<item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />

保存文件。

点击运行应用,发现并未生效。重启设备,发现新增的字段没有了。手动在设备的”设置“中修改权限,发现此文件并未改变,还是只有系统默认授予的权限。

另外还有一个文件/data/system/users/0/runtime-permissions.xml,保存了实时的权限信息,在“设置”中,修改授权情况会直接将权限信息写入此文件。

<pkg name="com.xxx"> <item name="android.permission.READ_SMS" granted="true" flags="0" /> <item name="android.permission.READ_CALENDAR" granted="true" flags="0" /> <item name="android.permission.READ_CALL_LOG" granted="true" flags="0" /> <item name="android.permission.ACCESS_FINE_LOCATION" granted="true" flags="0" /> </pkg>

尝试手动在文件中添加以上内容,重启设备后又恢复之前的状态了,也没有生效。

读写runtime-permissions.xml源码分析

分析一下在哪里读了这个文件。

在Settings类中,可以看到

1
private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml";

搜索RUNTIME_PERMISSIONS_FILE_NAME,找到:

1
2
3
4
5
6
1372    private File getUserRuntimePermissionsFile(int userId) {
1373 // TODO: Implement a cleaner solution when adding tests.
1374 // This instead of Environment.getUserSystemDirectory(userId) to support testing.
1375 File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
1376 return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME);
1377 }

返回该文件的File对象。搜索getUserRuntimePermissionsFile方法,找到了readStateForUserSyncLPr()读取该文件。调用此方法的位置只有一处,在readLPw方法中。同样的readLPw方法也读了packages.xml文件。而readLPw有且只有在PackageManagerService的构造方法中调用了,说明开机时确实是读了runtime-permissions.xml文件。

然后分析下在哪里写了文件。

runtime-permissions.xml文件的编辑在Settings类中,PackageManagerService构造方法中调用了updatePermissionsLPw()方法,里面调用了Settings对象的grantPermissionsLPw方法,此方法最终调用writPermissionsSync,也就是说授予权限时会写入该文件:(具体过程在授权源码分析一文中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
4984        private void writePermissionsSync(int userId) {
4985 AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId));
......
FileOutputStream out = null;
5021 try {
5022 out = destination.startWrite();
5023
5024 XmlSerializer serializer = Xml.newSerializer();
......
if (mRestoredUserGrants.get(userId) != null) {
5064 ArrayMap<String, ArraySet<RestoredPermissionGrant>> restoredGrants =
5065 mRestoredUserGrants.get(userId);
5066 if (restoredGrants != null) {
5067 final int pkgCount = restoredGrants.size();
5068 for (int i = 0; i < pkgCount; i++) {
5069 final ArraySet<RestoredPermissionGrant> pkgGrants =
5070 restoredGrants.valueAt(i);
5071 if (pkgGrants != null && pkgGrants.size() > 0) {
5072 final String pkgName = restoredGrants.keyAt(i);
5073 serializer.startTag(null, TAG_RESTORED_RUNTIME_PERMISSIONS);
5074 serializer.attribute(null, ATTR_PACKAGE_NAME, pkgName);
......
5103 serializer.endDocument();
5104 destination.finishWrite(out);
......
}

这样就将包的权限信息写入了runtime-permissions.xml文件。

失败原因:

关键在runtime-permissions.xml文件,在设备中的”设置“修改权限时,文件同步更新了,修改文件的话却没办法更新权限,涉及到某些存储权限信息的字段,还未找到。

至于通过hook修改uid,也只能在checkPermission时起到绕过作用,具体调用接口还是没有权限。还尝试了hook权限申请弹框的代码,也并没有什么用(涉及到framework层,难道说尝试修改ROM才是最终秘诀?)。

另外,测试使用的是小米,跟原生系统的权限控制不太一样。

小米应用权限设置页Activity:

ACTIVITY com.miui.securitycenter/com.miui.permcenter.permissions.PermissionsEditorActivity b347418 pid=6791

原生系统应用权限设置页:

ACTIVITY com.google.android.permissioncontroller/com.android.packageinstaller.permission.ui.ManagePermissionsActivity 23a5170 pid=4142

ACTIVITY com.google.android.permissioncontroller/com.android.packageinstaller.permission.ui.AppPermissionActivity 9e17de6 pid=4142


还尝试了把APP改成系统应用,以为就可以随便获取权限了。但是Android 7之后就不行了,系统应用要使用涉及隐私的接口,也需要弹框向用户申请权限。

在实践这个方法的时候,使用小米手机碰到了挂载文件系统问题,刷入了一些第三方包还把手机刷成砖,废了半天劲才挽救回来。。。。

总结一下:

不管是反射还是hook都要在framework层下手,要么研究怎么修改ROM,要么研究使用Magisk怎么在设备开机初始化之前修改配置。

经过十几天的抓耳挠腮,上蹿下跳,目前还处于“山重水复疑无路”阶段。不过,鲁迅说过:“排除掉错误的路线,剩下的就是对的路线。”(鲁迅:我没有,别瞎说。)多多尝试,就算没结果,也会有收获。