分析源码

该技术可以通过root权限,绕过权限检测机制, 在后台实现静默地自动授予任意app的任意权限, 没错: 自动授予[任意]app 的 [任意] 权限, 就是这么可怕!

以悬浮窗权限为例, 下面来说下我的研究的过程和最终解决方案:

大概以前在安卓4.x-6.x时代,android原生的悬浮窗权限是默认允许的,导致悬浮窗锁机应用病毒流行, 后来国内MIUI flyme等系统自己加了个悬浮窗权限, 当时的适配方案是反射判断AppOpsManager.checkOp(24) 返回权限授予情况 //24为悬浮窗权限.

该方法后来逐渐被遗弃, 到了android 6.x后 , 原生提供了一个办法来判断悬浮窗权限

Settings.canDrawOverlays(context)

我们来看一下 canDrawOverlays的源码 , 跟踪方法栈到最后一层:

public static boolean isCallingPackageAllowedToPerformAppOpsProtectedOperation(Context context,int uid, String callingPackage, boolean throwException, int appOpsOpCode, String[]permissions, boolean makeNote) {AppOpsManager appOpsMgr = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);int mode = AppOpsManager.MODE_DEFAULT;if (makeNote) {mode = appOpsMgr.noteOpNoThrow(appOpsOpCode, uid, callingPackage);} else {mode = appOpsMgr.checkOpNoThrow(appOpsOpCode, uid, callingPackage);}switch (mode) {case AppOpsManager.MODE_ALLOWED:return true;case AppOpsManager.MODE_DEFAULT:return true;}//略...}

发现 canDrawOverlays的实现原理也是通过AppOpsManager的内部函数实现!

通过上面的AppOpsManager.checkOp(24)和 Settings.canDrawOverlays(context)实现原理 可知
关键在于这个AppOpsManager类, 我们来看下源码有没有 setOp() 这样的函数. (源码直达)
看来看去 只有setMode 比较像是设置权限的操作, 但始终不确定.

于是… 打开安卓模拟器, 通过请求一个悬浮窗权限弹出到权限设置界面, 然后抓取当前fragment;类名称, 然后下载或在线android 源码中搜索这个界面的源码, 查看其悬浮窗授权开关的实现原理.
步骤分别如下:
找到界面

抓取当前fragment名称 通过软件 开发者助手电脑版 / 开发者助手手机版(手机版需要root, 且不稳定可能无法抓取)

以上找到这个设置界面的fragment后, 开始前往这里搜源码: 在线安卓源码搜索

最终找到了这个类 DrawOverlayDetails.java
看了下代码, 非常易懂:

没错了, 就是通过setMode函数来授予权限的, 而且AppOpsManager.OP_SYSTEM_ALERT_WINDOW 的值 刚好就是24, 悬浮窗权限.,而mode值为 AppOpsManager.MODE_ALLOWED代表允许, 该函数需要输入op值(24) , uid ,包名,和 mode值

那么反射一下调用setMode不就得了? 真这么简单我还写这个文章干啥啊…
方式会报安全异常, 需要系统权限才能调用这个setMode函数.
此时我们回到AppOpsManager的setMode内部实现原理, 发现其其实是通过 service实现远程通讯调用的


然后再搜到IAppOpsService的实现, AppOpsService.java

可以看到,实现中第一行就是检测权限

可以看到 只要calling pid和当前pid一致, 就可以跳过检查, 这样就只能通过xposed 去hook实现了.因为xposed是同一个进程下执行代码 所以不急, 继续看 enforcePermission的实现原理
跟踪找到ContextImpl.java类 里


这里写的是 activitymanager为空的时候, 此时如果是root 用户或system用户 就可以免除授权检查直接授予通过

为什么这里这么判断? 可能是linux下的进程 是没有activity的, 也无法获取上下文Context , 在linux获取activity是null的

重头戏在下面:

在linux下执行dex 输出hello world

原理是利用安卓系统内置的工具: app_process

1.在IntelliJ IDEA里新建一个java工程 并打包jar 执行测试

新建后 新建一个类

public class AppOpsManagerCompat {public static void main(String[] args)  {System.out.println("hello world");}
}

注意: 为了方便测试, main函数内记得打印一行 hello world 之类的
然后打包成jar文件, 操作步骤如下:

配置好后开始构建生成jar包, 步骤如下:

最后jar包路径生成在这个位置下:

为了确定无误, 可以使用jadx/jd-gui 等反编译工具查看jar包的内容, 这里要注意下MANIFEST.MF文件描述的mainclass是否正确. 否则可能在生成配置中没有设置main类

当然也可以通过java -jar AppOpsManagerTest.jar 来确认这个jar包可成功执行

2.使用工具把jar转成dex )

基本命令 dx --dex --output
步骤
找到你的sdk中的 dx.bat 或 dx.jar

D:\Android\sdk\build-tools\27.0.2\dx.bat
执行

D:\Android\sdk\build-tools\27.0.2\dx.bat --dex --output  appops.dex  AppOpsManagerTest.jar

为了方便, 可以把dx命令配置到系统环境变量

3.验证dex(可选)

使用 jadx, jeb2 等反编译工具 对dex进行反编译 查看dex是否是自己刚才写的那样

4.复制dex到 Android手机中.

复制到 /data/local/tmp/下

5.确认dex文件的权限

查看权限情况
ls -l

修改权限全满
chmod 777 /data/local/tmp/appops.dex

6.执行dex
//插入手机 然后利用adb push 把dex复制到手机中 (可选, 可自己手动复制)
adb push D:\Android\IDEAProjects\AppOpsManagerTest\out\artifacts\AppOpsManagerTest_jar\appops.dex//复制appops.dex到  /data/local/tmp/ 下
adb shell "su -c 'cp -rf /sdcard/appops.dex /data/local/tmp/'"//修改权限全满
adb shell "su -c 'chmod 777  /data/local/tmp/appops.dex'"//运行dex
adb shell "su -c 'app_process -Djava.class.path=/data/local/tmp/appops.dex  /data/local/tmp com.mx.appops.AppOpsManagerCompat'" //输出hello world  略, 和 'java -jar ' 执行的结果一样.

linux下无法获取Context的解决办法

问题解决了, 现在可以在linux下执行java 代码(dex) 了, 现在开始尝试
反射调用setMode函数

…然而…
…卧槽…

上下文Context 怎么拿? 于是我找遍了系统没有发现静态的上下文提供反射用
好吧…只好研究下 getSystemService的原理了, 看看能不能免context也能拿到service/manager
最后发现 ContextImpl.java 关键位置:

发现在SystemServiceRegistry.java类中, 创建了AppOpsManager的源码

并且通过 ServiceManager.getServiceOrThrow/getService 和 asInterface取得服务, 这个过程不需要上下文Context的参与

IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
IAppOpsService service = IAppOpsService.Stub.asInterface(b);
service.setMode(...)

而IAppOpsService 又是AppOpsManager的内部实现, 也有setMode 方法!..
所以模仿上面的代码反射执行就可以了!


注意: 图中的uid 改成你的需要申请悬浮窗权限app的uid, 你的app的uid可通过 Binder.getCallingUid()获取, 注意我说的是在你app 代码中获取 , 不是写在这例子中, 这例子中Binder.getCallingUid()会返回0, 因为执行的时候uid是0 可免权限, 这也算能绕过权限的原因

最终实现

我把它写成一个可通过main函数传参的方式

package com.mx.appops;import android.Manifest;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.*;
import com.android.internal.app.IAppOpsService;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;public class AppOpsManagerCompat {//运行示例 adb shell "su -c 'app_process -Djava.class.path=/data/local/tmp/appops.dex  /data/local/tmp com.mx.appops.AppOpsManagerCompat'" -packagename=包名 -op=24  -mode=0public 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;}try {IBinder iBinder = ServiceManager.getService(Context.APP_OPS_SERVICE);IAppOpsService iAppOpsService = IAppOpsService.Stub.asInterface(iBinder);if(uid<=0) {//如果不传uid 那么通过包名获取uid//这里仍然是采用相同的技术:  免上下文Context获取到PackageManagerIPackageManager ipm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));PackageInfo packageInfo = ipm.getPackageInfo(packagename, 0, 0);uid = packageInfo.applicationInfo.uid;}iAppOpsService.setMode(op, uid, packagename, mode);iAppOpsService.noteOperation(op, uid, packagename);System.out.println(true);} catch (Exception e) {System.out.println(false);}}}

注意注释, -packagename=包名 -op=24 -mode=0 为参数 , uid可以不传, 会通过包名获取, 这样简单了吧.

嗯, 现在打包成dex 执行吧

返回成功! 然后打开系统设置看看… 悬浮窗权限开关自动开启了 ! 而且打开app的确可以使用悬浮窗了!

把命令集成到app中

然后你可以把这些命令行操作封装到你的app中去, 举例

public static boolean setMode(Context context, int code, int uid, String packageName, int mode,ShellUtils.OnShellExecStreamListener listener){String dexName = "a12w2314.dex";String tmpDir  = "/data/local/tmp/";File   dexFile = new File(tmpDir, dexName);if (!dexFile.getParentFile().exists()){dexFile.getParentFile().mkdirs();}dexFile = new File(tmpDir, dexName);if (!dexFile.getParentFile().exists()){ShellUtils.exec("mkdir " + tmpDir, null);}if (!dexFile.exists() || dexFile.length() <= 0){try{AssetManager assets = context.getAssets();InputStream  open   = assets.open(dexName);File         file   = new File(context.getFilesDir(), dexName);if (!file.getParentFile().exists()){boolean mkdirs = file.getParentFile().mkdirs();}FileOutputStream outputStream = new FileOutputStream(file);byte[]           buff         = new byte[2048];int              len;while ((len = open.read(buff)) != -1){outputStream.write(buff, 0, len);}outputStream.flush();outputStream.close();open.close();if (file.exists()){ShellUtils.exec("cp -rf " + file.getAbsolutePath() + " " + tmpDir + " && chmod 777 " + tmpDir + dexName, null);}} catch (Exception e){e.printStackTrace();}}if (!dexFile.exists()){dexFile = new File(tmpDir, dexName);}if (dexFile.exists()){String cmd = "app_process -Djava.class.path=" + tmpDir + dexName + "  " + tmpDir + " com.mx.appops.AppOpsManagerCompat -packagename=" + packageName + " -op=" + code + " -uid=" + uid + " -mode=" + mode;ShellUtils.exec(cmd, listener);//这里未必成功, 需要监听dex中syso输出的成功值,dex已经写了 但是在这边不想写监听 以后再说,凑合用吧return true;}return false;}

以上代码:
从app的Asset文件夹中复制dex文件出来到app私有目录下, 利用cp -rf 再复制到/data/local/tmp下, 修噶权限, 然后执行修改权限命令, 并传入参数
ShellUtils.exec(cmd, listener); 是执行shell的工具类, 这里不贴了 网上找吧

填坑

linux执行dex 下无法获取上下文, activity, uid为0 等原因如图

输出结果:

本文只演示了op值为24 的悬浮窗权限静默root开启的方案, 其它权限全部通用 , 我试了后台弹出界面权限也是可以的, 还有那些快捷方式创建权限, 电话 短信修改, xxxxx 都是可以通过我上面的方法改的, 把op值改下即可, op值表如何获取可以看源码, 第三方权限op值如何获取可以看我之前的帖子 Android 权限适配 从此第三方系统新增的权限无法判断状态的问题得到解决! 如MIUI自启动, 后台弹出界面权限等

加群QQ群418263790 一起研究一些骚操作技术吧.

Android安全之使用root权限绕过检测机制,强行自动允许应用的悬浮窗/应用后台弹出界面等权限相关推荐

  1. android 实现悬浮窗相机后台视频隐秘录制

    android 实现悬浮窗相机后台视频隐秘录制 GitHub上参考了别人做悬浮窗的代码,后面自己加的的相机录像功能 主要功能: 1.悬浮窗录制视频,可实现后台或锁屏使用摄像头录制视频. 2.可自定义悬 ...

  2. 后台弹出界面权限 绕过_教您如何发微信「朋友圈」,设置访问权限

    引言 我们经常在微信的朋友圈中看到朋友们分享的动态,我们也可以将我们的美好瞬间记录在朋友圈,与亲朋好友共同分享. 发送朋友圈的步骤 1.打开微信,点击底部的[发现],进入界面后点击[朋友圈],可以看到 ...

  3. android自动拨号 代码,在Android上,是否有一种方法可以强行自动自动拨号?_android_开发99编程知识库...

    我一直在研究一个 Android 应用 概念,在用户启动服务时,应用程序必须自动拨号. 我发现,当应用程序尝试自动拨号时,手机( 还是叫 Intent ) 不会自动拨号,而用户必须手动启动服务. 目前 ...

  4. Android 悬浮窗权限各机型各系统适配大全(总结)

    原文链接:点击打开链接 ======================================================================================== ...

  5. Android 悬浮窗权限各机型各系统适配大全

    这篇博客主要介绍的是 Android 主流各种机型和各种版本的悬浮窗权限适配,但是由于碎片化的问题,所以在适配方面也无法做到完全的主流机型适配,这个需要大家的一起努力,这个博客的名字永远都是一个将来时 ...

  6. Android M及以上版本系统 悬浮窗权限 的解决方案

    Android M及以上版本系统 悬浮窗权限 的解决方案 Android的窗口体系中,WindowManager占有非常重要的地位,平时我们使用悬浮窗会遇到一些权限的问题. 当 Android工程在 ...

  7. android动态申请悬浮框权限,Android动态权限申请工具(包括悬浮窗)

    为了保证APP正常运行,动态权限申请是android比较常用的功能,由于每次都需要做申请.等待返回还有拒绝反馈等操作,比较麻烦,所以集成了一个比较简单的动态权限申请库 集成方法: Step 1. Ad ...

  8. 关于Android应用中的悬浮窗(一)——权限

    现在越来越多的Android APP都有悬浮窗的功能,公司项目中最新的需求也需要加入悬浮窗的功能,这次的功能是指在应用内的悬浮窗(而不是系统级别的悬浮窗).悬浮窗功能的时候,整体分了2个部分: - 悬 ...

  9. android动态申请悬浮框权限,Android创建悬浮窗的完整步骤

    在Android中想要创建悬浮窗分为三步 1.申请权限 2.使用服务启动悬浮窗 3.设置悬浮窗参数并添加进WindowManager 下面话不多说了,来一起看看详细的实现过程 申请权限 首先需要申请悬 ...

  10. Android悬浮窗适配全机型,包含8.0,小米魅族华为悬浮窗权限适配demo看这一篇就够了

    机型多杂,适配无法完全兼容,不如换种实现方式,性能比悬浮窗好,不需要权限,效果更好:https://blog.csdn.net/m0_38058826/article/details/10399339 ...

最新文章

  1. HTML中的一些知识点
  2. 营销型网站吸引用户说难也难,说简单也简单
  3. linux 运行python 看不到异常信息_linux python运行报编码错误
  4. 苹果safari浏览器window.open问题
  5. Java多线程-线程通信
  6. iOS10 打开APP设置界面和WIFI界面
  7. java登陆挤下去代码_application作用域实现用户登录挤掉之前登录用户代码
  8. Python稳基修炼的经典案例15(计算机二级、初学者必会字符格式处理)
  9. c51语言bit函数,keil C51中的本征函数库及使用说明
  10. C++ Primer Plus学习(四)—— string类实践
  11. 泰勒展开与找第一项系数不为1的解题策略
  12. 计算机文化基础十一版百度云,计算机文化基础(高职高专版 第十一版)第一章答案...
  13. 怎么禁用计算机usb驱动,u盘驱动程序被禁用怎么办
  14. 苹果手机打电话没有声音怎么回事_手机打电话听筒没有声音,只有打开免提时才有声音,该怎么办?...
  15. 用一个div绘制背景流动网格特效
  16. 基因组测序为什么没完没了?
  17. 通过浏览器访问服务器
  18. WDM和WDF usb驱动不同点
  19. 【IoT】STM32 分散加载文件 .sct 解析
  20. 基于FPGA的以太网控制器(MAC)设计(中)

热门文章

  1. 伟森盛业:法大大电子合同助力供应链数字创新
  2. 未储存的Pages文件怎么恢复
  3. [微语 20.11.17] 本质
  4. Python小白入门--(域名whois查询为例)
  5. 如何在产品经理工作面试中回答估算问题
  6. html5渐变色毛玻璃,基于CSS3实现的毛玻璃渐变效果
  7. 【Blender报错记录】Bone Heat Weighting: failed to find solution for one or more bones
  8. 《Machine Learning in Action》—— hao朋友,快来玩啊,决策树呦
  9. java单词大全_编程常用英语单词大全
  10. 快读快写模板【附O2优化】