ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android 11下三方应用获取su权限:从原理到实践的完整指南

Android 11下三方应用获取su权限:从原理到实践的完整指南 Android 11下的三方应用要拿su权限说实话比Android 9/10时期麻烦得多。我最近在一批Android 11设备上做自动化调试工具App需要以root身份执行一些系统命令遇到了不少坑授权弹窗不出来、adbd降权导致shell连不上、SELinux策略拦截、targetSdkVersion30的包可见性限制……一个个排查下来等于把Android 11的权限体系和Magisk的授权流程重新梳理了一遍。这篇文章就把我实际操作中验证过的方案完整写出来围绕“Android11 三方应用获取su权限”这个主题从权限原理、实现路径、具体代码、特殊场景到问题排查都讲透。内容面向做系统定制、自动化测试工具、设备管理类App的开发者也适合想弄明白Android 11 root权限机制的玩家参考。全文基于我手头几台Android 11真机的实测结果不涉及任何灰色用途所有操作请务必在你自己拥有并解锁允许调试的设备上进行。1. Android 11下su权限的底层逻辑变化1.1 系统分区隔离与SELinux强制策略带来的影响先说Android 11相对旧版最明显的三个变化system分区强制只读、SELinux策略进一步加强、包可见性机制默认开启。这三个东西单看都是安全优化叠在一起直接改变三方App获取su权限的玩法。以前在Android 9时代只要系统已经root很多三方App写一句Runtime.getRuntime().exec(su)再配合SuperSU的授权管理基本就能拿到root shell。到了Android 11Google把所有系统分区都挂成只读adbd也不再以root身份运行甚至su二进制本身能不能被普通进程执行都取决于SELinux上下文和Magisk的magiskpolicy规则。换句话说光有su文件还不够你的App得能顺利触发授权请求授权服务还得在enforcing模式下放行。SELinux这块是重灾区。Android 11默认的sepolicy里普通App域untrusted_app访问su的规则被收得很紧Magisk通过magiskpolicy --live注入规则允许su从magisk域转换为带mlstrustedsubject的域从而绕过大量限制。如果你用的是自己编译的su二进制而不是Magisk那么大概率需要在sepolicy里手工加规则否则即使拿到uid0很多系统文件、设备节点照样没权限读。这个我在第3章会具体展开。1.2 su调用的完整链路二进制、授权守护进程、App三端配合搞清楚Android 11下su权限的获取方式要先理解一条完整链路不是App里exec(su)就完事了。实际调用涉及三个角色su二进制位于/system/bin/su或/data/adb/magisk/busybox/suMagisk模式下负责启动root shell、设置uid/gid、切换SELinux上下文。授权守护进程MagiskSU的核心是运行在root域的magiskdApp每次执行su时su二进制会与magiskd通信查询当前调用者是否在白名单/黑名单里然后决定是否弹出授权窗口。App侧调用代码通过ProcessBuilder或Runtime.exec启动su进程从标准输入写入要执行的命令从标准输出读取执行结果。这个链路里最容易出问题的是中间环节su二进制如果找不到magiskd的socket或者SELinux policy不允许通信就会直接返回Permission denied连授权弹窗都不会出现。很多人在Android 11上遇到“App明明拿到root了但执行命令全部失败”八成就是卡在这一层。另外一个容易被忽略的点包可见性。Android 11要求targetSdkVersion30及以上的App声明queries才能看到其他应用。如果三方App需要检测某个授权管理App是否安装或者需要读取Magisk的授权列表必须在AndroidManifest里加对应的queries声明否则queryIntentActivities()返回空列表。这虽然不是su权限本身的阻断点但在做“检测root状态”或“关联打开授权App”这类功能时很关键。2. 三方应用获取su权限的三种主流方案2.1 方案一Magisk授权适合绝大多数设备Magisk是目前Android 11设备上最主流的root方案它的设计核心是systemless不修改system分区通过patch boot镜像来实现root权限注入。对三方App开发者来说Magisk方案的好处是magiskd会自动处理授权流程App只需要调用su是否弹窗、如何记住授权全部交给Magisk Manager处理。SELinux policy由Magisk自动注入su转换为root域的规则已经预设好App侧不用额外处理sepolicy。支持DenyListAndroid 11及以上版本替代旧版Magisk Hide可以针对特定App隐藏root痕迹避免某些应用检测到su二进制。具体在Android 11下使用Magisk做su授权唯一的坑是Magisk版本必须够新。旧版Magisk比如20.x对Android 11的适配不完整会在SELinux注入、ramdisk挂载这些环节出问题。实测推荐使用Magisk 23.0以上版本配合ZygiskAndroid 11上已默认集成稳定性和隐藏能力都更可靠。2.2 方案二预置系统签名App 自定义su适合定制固件如果你做的是行业定制设备或者手里有系统签名可以选择把App预置到/system/app再配合一个自定义su二进制。这种方案不依赖Magisk完全自己掌控授权逻辑。思路很简单系统签名App在Android里属于platform域本身就比普通App有更高权限能拿到platform级权限比如WRITE_SECURE_SETTINGS、READ_LOGS。此时再配合自定义su可以在sepolicy里给该App单独加allow规则让它能直接执行su而不触发任何授权弹窗。这个方案对普通开发者不友好因为你需要自己编译AOSP或基于GKI的kernel/ramdisk。编写并编译一个精简su二进制设置正确的SELinux context通常是u:r:su:s0。在sepolicy里为你的App域添加访问规则并且要处理Android 11的system-as-root挂载方式。处理AVB校验如果设备开启Verified Boot所有分区改动都会被检测。优点是一旦做好App取root的过程对用户完全透明不需要额外安装Magisk Manager体验上更像“出厂自带root能力”的专业设备。2.3 方案三运行在root域的自有守护进程适合重度定制第三种方案适合要做深度系统能力集成的App比如远程控屏、系统级网络代理这类思路是写一个守护进程开机时以root身份常驻后台App通过本地socket与守护进程通信由守护进程代为执行需要root的命令。这个方案和su二进制不冲突su只是授权入口守护进程是更高级的远程调用方式。好处是App本体完全不需要root权限可以正常过Google Play的审核只要不使用隐藏权限。通信协议自己定可以做得更安全比如加白名单校验、签名验证。可以绕开Android 11对App使用shell命令的限制直接通过IPC完成操作。但缺点也很明显守护进程本身的投递和启动需要依赖方案一或方案二先获取root入口另外Android 11的SELinux对守护进程的域限制很严格如果设备处于enforcing模式不是所有操作都能执行必要时得给守护进程单独配置seinjava域并加入注入规则。我来整理一个对比表格方便你做技术选型对比项Magisk授权系统签名自定义su自有 root 守护进程对三方App的代码要求低使用标准su调用中需要系统签名高需要设计IPC协议是否需要刷机/改分区需要刷Magisk patch后的boot需要重新打包system/vendor取决于投递方式SELinux适配难度低Magisk自动处理高需自行编写sepolicy中需配置守护进程域授权可控性受Magisk Manager控制完全自己控制完全自己控制隐蔽性/防检测可用DenyList隐藏较难隐藏system被改较好App无root特征适用场景普通正式设备调试、通用工具行业定制机、内部系统远程管控、复杂系统集成实际做技术选型时我的建议很直接个人开发者或做自动化测试工具闭眼选方案一做行业设备且手里有系统签名用方案二需要在root环境跑长时间后台服务的在方案一的基础上叠加方案三的设计思想。方案三单独作为入口不划算一般作为Magisk权限之上的服务层存在。3. 实操Android 11设备上App调用su的完整实现3.1 环境准备与常用检测代码我这里以一台Android 11真机、已经刷入Magisk 25.2为例。准备阶段要做三件事确认设备能正常获取root在PC上通过adb shell执行su -c id看是否返回uid0。在Magisk Manager里打开“允许超级用户请求”相关选项默认是自动弹出授权询问。确认App已经申请了必要的普通权限比如INTERNET用于网络通信非必须、QUERY_ALL_PACKAGES如果要做包检测。检测设备是否已root这段代码是我一直沿用的注意在子线程运行public boolean isRootAvailable() { boolean hasSu false; try { Process process Runtime.getRuntime().exec(su -c id); BufferedReader reader new BufferedReader( new InputStreamReader(process.getInputStream()) ); String line reader.readLine(); if (line ! null line.contains(uid0)) { hasSu true; } process.waitFor(); } catch (IOException | InterruptedException e) { // 没有su二进制或权限被拒 } return hasSu; }这个方法的逻辑很简单通过su -c id启动一个root shell并执行id命令如果输出包含uid0则说明有root能力。这里有个Android 11的细节要提醒targetSdkVersion30的App在Runtime.exec时工作目录默认是App自己的数据目录而su需要的执行路径通常没问题但如果你的命令里用了相对路径很可能会找不到文件。所以所有需要root执行的命令一律写成绝对路径比如/system/bin/screencap、/system/bin/wm。3.2 封装一个健壮的RootShell执行器直接调Runtime.exec(su).getOutputStream()写命令是最简单的方式但实际使用中容易踩几个坑系统命令执行超时导致App卡死、标准错误流不读取导致阻塞、多进程并发时su授权弹窗叠加。我建议封装一个RootShellExecutor统一处理这些边界。先看核心代码这是我在Android 11上验证过多次的版本public class RootShellExecutor { public static Result execute(String command) { return execute(new String[]{command}); } public static Result execute(String[] commands) { Result result new Result(); Process process null; try { process Runtime.getRuntime().exec(su); DataOutputStream os new DataOutputStream(process.getOutputStream()); BufferedReader stdout new BufferedReader( new InputStreamReader(process.getInputStream()) ); BufferedReader stderr new BufferedReader( new InputStreamReader(process.getErrorStream()) ); // 批量写入命令 for (String cmd : commands) { os.writeBytes(cmd \n); os.flush(); } os.writeBytes(exit\n); os.flush(); // 读取输出注意不能只读stdoutstderr也要消费否则可能阻塞 String line; StringBuilder sb new StringBuilder(); while ((line stdout.readLine()) ! null) { sb.append(line).append(\n); } String err; while ((line stderr.readLine()) ! null) { result.error.append(line).append(\n); } int exitCode process.waitFor(); result.exitCode exitCode; result.output sb.toString(); } catch (IOException | InterruptedException e) { result.error.append(e.getMessage()); result.exitCode -1; } finally { if (process ! null) { process.destroy(); } } return result; } public static class Result { public int exitCode; public String output; public StringBuilder error new StringBuilder(); } }这段代码的关键点有三个一是用DataOutputStream逐条写入执行的命令而不是用su -c cmd1;cmd2拼接后者遇到包含特殊字符的命令很容易解析出错二是必须同时消费stdout和stderr否则当输出量大时管道缓冲区塞满进程会一直被阻塞住。我在Android 11上实测过用cat /system/build.prop这种输出量大的命令不读stderr或stdout进程能卡住几十秒不返回三是设置超时机制。上面代码为了简洁没加超时真实项目里建议包一层FutureTask或executor.submit设置10秒超时后强制process.destroy()因为有些系统命令在SELinux拦截下会无限等待。3.3 授权流程的时序与Magisk授权弹窗机制明白了调用代码再来理解授权流程。当App第一次执行Runtime.getRuntime().exec(su)时Magisk的su二进制的执行流程是su 二进制校验发起者的UID、App包名。su通过socket向magiskd发送授权请求。magiskd根据当前策略允许/拒绝/询问决定下一步。如果策略是“询问”Magisk Manager会弹出一个系统级对话框显示发起授权的App名称和UID。用户点击允许后magiskd通过socket返回授权结果su二进制随之设置uid/gid为0启动root shell。如果App在Magisk Manager的“超级用户”列表里已经设置成“始终允许”则第4、5步自动完成。所以你在App里收到su进程的输入流可写、标准输出可读时并不代表App就是root身份只是su进程已经通过授权并切换到root。整个授权是异步的弹窗期间App的exec(su)会一直等待。如果你的代码把exec(su)放到了UI线程那用户一看到授权弹窗App就直接ANR。授权弹窗本身还有一个Android 11的细节Magisk Manager的弹窗是TYPE_APPLICATION_OVERLAY类型的悬浮窗需要在系统设置里允许“显示在其他应用上层”否则在某些定制ROM上弹窗会被系统直接屏蔽。这个不是Android原生行为但在国产ROM上很常见我遇到过在MIUI上Magisk Manager弹窗总是一闪而过的情况打开悬浮窗权限后就好了。3.4 执行结果的解析与常见陷阱调用RootShellExecutor后得到的Result对象需要正确解析。主要注意这几个点exitCode为0不代表命令一定成功。很多系统命令即使执行失败也返回0比如wm size 1080x1920在没有修改权限时会返回0但实际没生效。我的习惯是额外比对输出内容比如执行后再次读取当前分辨率确认。标准输出编码问题。Android系统命令的输出默认是UTF-8但有些aapt、pm命令会输出GBK或系统语言字符直接readLine()可能拿到乱码。建议读取时指定字符集new InputStreamReader(process.getInputStream(), UTF-8)必要时再尝试GBK兜底。su的环境变量少得可怜。通过Magisk su获取的root shellPATH通常只包含/sbin:/system/bin:/system/xbin:/vendor/bin一些App自带的busybox路径不再PATH里需要手动指定绝对路径。另外LD_LIBRARY_PATHHOME等环境变量往往为空读取某些系统配置文件时可能受影响。这三个坑我在Android 11实机上都踩过尤其是PATH问题。第一次写工具时执行tcpdump直接提示not found排查半天才发现是PATH里没有/data/local/tmp改成/data/local/tmp/tcpdump -i any后一切正常。4. 热点场景实战远程投屏和USB OTG里的su应用4.1 远程投屏类App为什么需要su权限最近“Android11 远程投屏”相关的需求热度很高不少开发者在做设备投屏方案。常规的投屏工具通过MediaProjection拿屏幕内容整个过程需要用户手动确认授权而且Android 11上系统会持续显示录制指示。更麻烦的是很多设备出于DTCP等协议限制会禁止某些HDCP内容的投屏采集。三方App拿到su权限后可以在这些方面绕过或补充系统限制通过screenrecord命令录制屏幕su -c screenrecord --time-limit 10 --bit-rate 4000000 /sdcard/demo.mp4然后用MP4Parser从私有目录读取分析。好处是不触发MediaProjection的授权弹窗和录制指示缺点是只能录制没法采集音频需要另外的方式拿MIC数据。全局模拟操作通过input命令注入触摸和按键事件配合屏幕采集实现远程控制。su -c input swipe 500 1500 500 500、su -c input tap 300 400这是远程协助类App的核心。动态调整显示参数wm size和wm density修改分辨率settings put global policy_control immersive.full*隐藏状态栏。这些是shell权限操作普通App拿不到但su可以。以远程协助为例一个完整的操作序列是su -c screenrecord --time-limit 30 --output-format mp4 /data/local/tmp/screen.mp4 su -c input keyevent KEYCODE_HOME su -c input tap 540 1200 su -c input text hello su -c wm size 1080x2340这里注意screenrecord输出到/sdcard时会受到存储权限管理影响我一般先输出到/data/local/tmp再用cat重定向到App沙盒目录读取。另外别直接用Runtime.exec同时执行多条su -c每一条su -c都意味着一次授权判断虽然Magisk会记住授权但进程启动开销不小。更高效的方式是开一个持久的su会话一次性写入多行命令。读者可以基于前面RootShellExecutor改造去掉exit退出逻辑保持会话不关闭在需要时持续写入命令。这里我必须要强调一点远程投屏如果涉及采集他人设备的屏幕或注入操作必须确保已获得设备所有者明确授权并遵守当地法律法规。这里只讨论技术方案商业使用请及时评估合规风险。4.2 USB OTG外设访问不加su通常卡在哪“Android11 usb otg”也是最近高频出现的需求很多人调试USB摄像头、串口设备、U盘时发现明明插上去了App却读不到设备节点。这里背后的原因很直接Android的USB权限模型分为两层一层是UsbManager框架另一层是Linux内核设备节点。UsbManager方式适合标准外设U盘、键鼠App只要动态申请USB_PERMISSION就能拿到UsbDevice并读写。但很多不是标准HID或Mass Storage类的外设比如串口CDC ACM、USB转CAN、自定义HID传感器直接用UsbManager拿到的FileDescriptor很难满足复杂通信需求大家习惯用libusb直接去操作设备节点/dev/bus/usb/001/002。问题来了普通App根本没有/dev/bus/usb/*节点的读权限一打开就报Permission denied。此时su就派上用场了常见做法是通过su临时调整设备节点权限# 先通过UsbManager.getDeviceList()拿到设备的 bus 和 device 号 # 然后使用su修改节点权限 su -c chmod 666 /dev/bus/usb/001/002但这里有个安全风险把设备节点改成666意味着同一台设备上的其他App也能直接操作这个外设多App同时打开时容易冲突。更稳妥的做法是su启动辅助进程由辅助进程持有root身份来完成libusb的open/read/write操作App通过本地socket把需要发送的数据传给辅助进程读取到的数据再回传。这样既满足Android 11的SELinux限制又避免了把设备节点权限彻底打开。另外提醒一点Android 11的UsbManager在ParcelFileDescriptor返回后框架会持有对设备的引用此时su再去chmod或open同一个设备节点不是每次都能成功。如果遇到Permission denied先确认App是否已经通过UsbManager取得设备的访问权再尝试su操作。一个连USB设备都拿不到的App给再高的root权限也白搭。4.3 特殊场景下的安全边界与取舍这里必须把安全和合规问题说清楚。su权限的本质是授予一个进程绕过Android权限模型的能力远程投屏和USB OTG这两个场景尤其要谨慎。为什么我把这一点单列出来因为我见过太多工具类开发者在Android 11上拿到su之后过度使用权限比如远程投屏工具附带“远程截图保存到本地”、“后台静默拍照”等功能这些在未告知用户的情况下属于越权行为。利用su修改settings secure里的enabled_input_methods切换输入法或者隐藏系统应用这类操作容易导致设备状态异常。USB OTG工具把设备节点chmod后不做业务层面的白名单判断导致所有App都能读写外设数据。技术能力越强越应该克制。su权限应当服务于明确的功能需求而不是为了绕过限制去钻空子。我的一个原则是凡是用户能在系统设置里完成的操作尽量不用su凡是会改变设备安全状态的命令必须单独弹窗告知用户。在Android 11这种安全模型高度完善的系统上root权限的滥用不只是合规问题还会影响设备稳定性甚至让系统无法开机。5. 常见问题排查与避坑清单5.1 授权弹窗不出现进程一直卡住这是Android 11上最常遇到的问题。App执行su后没有任何反应授权弹窗也没有出现代码一直阻塞在process.waitFor()。排查思路如下检查Magisk版本是否兼容Android 11低于23.0的建议升级到最新稳定版。在Magisk Manager的“超级用户”列表看有没有你的App记录。如果没有说明magiskd根本没收到请求检查su二进制路径和SELinux policy。在PC端执行adb shell su -c id如果PC端也要卡住就是Magisk自身的授权服务异常尝试重启设备或重新刷入Magisk。如果只有App端卡住而PC端正常大概率是App的UID没被magiskd正确识别可能原因包括双开/分身多用户模式导致UID异常、App被系统冻结等。还有一种隐蔽情况App把su调用放到了UI线程而Magisk的授权弹窗本身也是系统级对话框两个窗口在同一个线程调度上死锁。我Debug过几个案例表面上像是su没响应实际上就是ANR。解决办法很简单所有su相关操作全部移到子线程。5.2 su执行命令全部返回Permission deniedApp已经通过su授权执行id能看到uid0但执行很多命令时报Permission denied。这种情况在Android 11的enforcing模式下非常典型。原因基本可以锁定在SELinux域转换不完整。Magisk的su二进制在授权后会把当前shell切换到magisk域这个域配置了mlstrustedsubject和大量allow规则所以大多数操作能过。但个别命令比如访问某些vendor分区文件、操作特定设备节点仍可能因为没有对应allow规则而被SELinux拦截。处理办法优先用setenforce 0改为permissive模式做诊断。如果改为permissive后命令成功即可确认是SELinux策略问题。对确实需要的操作给magisk域或自定义守护进程域补充规则。Magisk提供了/data/adb/service.d里的脚本配合magiskpolicy命令在开机时动态注入规则。如果命令本身需要访问App数据目录或Android私有目录可能是App进程的SELinux上下文与root shell不一致导致的绕行方案是先用runcon指定上下文或者通过su -c cat /proc/self/attr/current确认当前上下文。5.3 Magisk DenyList与CTS检测问题Android 11上Magisk的DenyList新版替代Magisk Hide是用来隐藏root的重要手段。但很多开发者把DenyList和业务代码错误绑定导致App自己检测不到su进而功能异常。实际使用的建议是DenyList只对你要隐藏root的应用启用不要在调试阶段全局开启。DenyList开启后被隐藏的App会看到一个伪造的环境诸如which su找不到、/system/bin/su文件不存在这些都正常但你的App代码如果依赖这些结果来判断root状态就会走向错误分支。另外如果你的App需要过Google的SafetyNet或Play Integrity检测简单的DenyList在新版本上已经不够了需要配合Zygisk和对应的模块。但这已经超出本文“三方应用获取su权限”的范围而且这些内容经常变化我就不具体展开了。核心原则仍然适用该藏的藏该用的用检测逻辑和业务逻辑要分开。5.4 设备重启后su权限失效的修复顺序Android 11设备重启后偶发“App拿不到root”的情况多数不是Magisk掉了而是su二进制所在的分区或overlay没有正确挂载。我经历过的场景里最常见原因是修改过/data/adb目录的权限或者Magisk模块里某个脚本崩溃导致开机流程中断。修复优先级建议先确认Magisk Manager能否正常打开“安装”状态是否显示正常。打开Magisk Manager里的“Magisk”菜单检查当前版本、Ramdisk状态、以及“安全模式”标志。如果Magisk显而已损坏从PC端重新adb pushMagisk的patch后的boot镜像或直接打开Magisk App进行“直接安装Recommended”修复。若设备能进系统但root不稳定优先卸载最近安装的Magisk模块模块冲突在Android 11上会导致zygote注入失败继而su参数全部异常。调试时养成一个好习惯任何涉及root的操作之前先在PC端adb shell里验证root状态。PC端能拿到root而App拿不到那是App侧的问题PC端也拿不到root就该去修系统环境了。这个排查顺序能帮你省下大量时间。最后分享一个我自己的判断标准做了这么多年Android开发和系统定制关于su权限我有一个很个人的判断标准能用普通权限做的事绝不用su用了su之后能做完的事不要扩大成能控制整台设备。Android 11把三方应用获取su权限的门槛抬高了这其实是好事它逼着开发者更想清楚自己到底需要哪一条root权限、为什么要这条权限而不是拿到root后大包大揽。如果你正在做Android 11相关的三方工具我的建议是先把Magisk方案跑通再根据业务需要决定是否上系统签名或自建守护进程代码层面RootShellExecutor这种封装是底线授权弹窗卡死、SELinux denial、PATH不完整这几个坑提前规避掉。文中的所有代码片段和操作步骤我都在手边几台设备上实测验证过放心复用。后续如果你想深入做Android 11下的输入事件注入或应用进程注入可以在su权限的基础上再单独聊。
返回列表