ARTICLE DETAIL

资讯详情

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

ADB 与 root 权限体系:adb push、su 与 SELinux 排错

ADB 与 root 权限体系:adb push、su 与 SELinux 排错 手机的adb调试口一旦打通很多人脑子里冒出来的第一个念头就是——能不能顺手把root也拿了我手上这台测试机就是回收来的老机型屏幕花了但主板还活着打算把它做成长期跑自动化脚本的节点第一步必须把调试链路调通、把权限拿到手。结果第一步就卡住了把su文件用adb push塞进手机重启之后su命令依然提示 not foundwhoami打出来还是shell。折腾了两天才彻底搞明白adb和root属于两套完全独立的权限体系把文件复制进设备跟让设备承认你有超级用户身份中间隔着的不是一步操作而是整个系统的安全模型。这篇就把这条链路上每一个环节的原理、命令、报错和坑点全盘拆开讲一遍适合刚接触安卓调试的新手也适合做了几年应用层、但对系统底层权限一知半解的老手。1. 先把概念掰开adb 与 root 根本不是一回事1.1 adb 的三端架构决定了它能做什么、不能做什么很多人把adb理解成一个连上手机就能随便改东西的工具这个理解偏差是后面所有踩坑的源头。adb全称 Android Debug Bridge字面意思是调试桥它的职责只有一件事在电脑和设备之间搬运指令和字节流它本身不提供任何权限提升能力。它的架构是三个进程串起来的adb client你在终端敲下的每一条adb xxx命令就是客户端在跑它只负责把参数打包。adb server电脑后台常驻的守护进程默认监听 5037 端口负责管理所有已连接的设备、复用连接、把客户端的请求转发下去。设备一多adb devices里能列出好几个序列号靠的就是这个 server 在做路由。adbd跑在安卓设备内部的守护进程它才是真正执行命令的那一端。搞清楚第三端的身份特别关键。adbd在设备里是以某个 Linux 用户身份运行的出厂零售机的系统镜像里ro.secure1、ro.debuggable0adbd会降权到shell用户也就是 uid 2000而工程机、开发板或者部分厂商的内部固件里ro.debuggable1adbd可以直接以 uid 0 运行。这就解释了一个现象——同样一句adb root有人敲完提示 adbd is already running as root有人敲完提示 adbd cannot run as root in production builds。差别不在命令而在设备固件属性。提示adb root只对ro.debuggable1的固件有效它做的是让adbd重启为 root 身份而不是给系统装一个超级用户管理程序。零售机提示失败是完全正常的不要以为是自己操作错了。理解了这一点就明白为什么把 root 复制到手机这个思路从起点就走偏了。adb push只是把文件写进设备的某个目录文件躺在那里不动它不会自动变成系统的一部分更不会改变adbd的运行身份。1.2 root 在安卓里的准确含义uid 0 加上正确的 SELinux 上下文root这个词被用得太泛了。在安卓语境下它指的是一种运行状态——某个进程拿到了 uid 0并且它的 SELinux 安全上下文允许它做特权操作。这两个条件必须同时满足缺一个都不成立。只拿到 uid 0 却没有匹配的 SELinux 域会发生什么你执行一条本该成功的系统调用返回的是Permission denieddmesg或logcat里会刷出一行avc: denied的审计记录。我第一次遇到这个报错时以为是文件权限位没设对chmod 755反复试了好几遍最后才发现是 SELinux 在强制访问控制层把操作拦住了跟传统 Unix 权限位毫无关系。再叠加上第三条限制现代安卓的/system、/vendor分区默认是只读挂载的而且有 dm-verity 和 AVB 做完整性校验。你就算强行mount -o rw,remount /system写进去一个文件下次启动校验不过设备可能直接进不去系统。这三层限制Unix 权限、SELinux 策略、分区完整性校验共同构成了为什么改 system 分区这条路在近几年的机型上基本走不通。所以判断一台设备到底能不能 root要看的不是型号新不新而是三个条件引导程序能不能解锁、厂商有没有留出可刷写的启动镜像通道、以及社区有没有针对这个芯片方案维护可用的方案。这也是为什么同一个品牌下有的机型社区资源丰富有的机型连引导程序都锁死了。1.3 一个高频混淆点这个 root 和 Linux、MySQL 的 root 完全无关搜索资料的时候你会被大量毫不相干的结果淹没。error 1045 (28000): Access denied for user rootlocalhost这是 MySQL 的认证失败ubuntu 切换 root 用户命令、银河麒麟 v10 系统 root 密码重置这是 Linux 发行版的账户管理square root 123干脆是数学开平方。它们和安卓的 root 权限没有任何关系只是共用了同一个英文单词。这个区别对新手尤其容易造成误判。有人在讨论区发帖问改 root 密码之后进不去系统了配的是安卓设备的截图实际上他执行的是 Linux 的passwd root逻辑——安卓根本没有这套账户数据库的交互方式。我把这几个概念列成表方便对照区分语境root 指什么典型操作失败表现Androiduid 0 的特权进程 SELinux 策略放行通过启动镜像方案获得超级用户管理能力Permission denied、avc: deniedLinux 发行版超级用户账户su -、sudo、passwd root认证失败、账户被锁MySQL数据库管理员账户mysql -u root -perror 1045 (28000)前端/数学根节点、平方根目录树、Math.sqrt()与权限无关把这张表记住能省掉至少一半的无效检索时间。2. 环境准备让 adb 先稳稳跑通2.1 三平台 adb 环境搭建与自检在讨论权限之前先把adb本身跑通这一步做扎实了后面排查问题会轻松很多。Windows 下的主流做法是下载官方的 platform-tools 压缩包解压到一个路径里没有中文、没有空格的目录比如D:\platform-tools然后把这个目录加进系统环境变量的 Path。这一步很多人图省事跳过结果每次都要cd到目录里敲./adb一旦换终端又失效。加完 Path 后重开一个终端敲adb version能打印出版本号就说明配置生效了。驱动是另一个坎部分品牌机型需要在设备管理器里确认有没有出现带感叹号的未知设备必要时安装厂商提供的 USB 驱动。macOS 相对省事用包管理器一条命令装好 platform-tools然后把路径写进 shell 配置文件source一下即可。Linux 下除了包管理器安装还要处理 udev 规则——不配置规则的话普通用户身份访问设备会提示权限不足得用 root 或者手动加规则文件。这一步在lsusb能看到设备、但adb devices是空列表的时候最容易怀疑人生。# 通用自检三步 adb version # 确认客户端可用 adb kill-server # 先杀掉可能状态异常的旧服务 adb start-server # 重新拉起观察是否有端口占用报错adb kill-server这个动作看着简单实际能解决相当比例的玄学问题。server 进程有时候会卡在某个设备的连接状态上导致新设备识别不出来或者adb devices显示一堆offline。我现在的习惯是每次插拔设备不确定状态时先杀一遍再起比反复拔线快得多。2.2 设备端的开关与授权弹窗处理设备这边要做三件事顺序不能乱进入设置找到关于手机连续点击版本号若干次直到提示已进入开发者模式。回到设置里的开发者选项打开USB 调试。部分机型还有USB 安装USB 调试安全设置之类的附加开关按需打开。用数据线连接电脑设备上会弹出允许 USB 调试吗的授权对话框勾选始终允许并确认。第三步的弹窗是adb unauthorized报错的直接原因。这个弹窗背后是一对密钥电脑端在用户目录下的.android文件夹里生成adbkey和adbkey.pub设备端保存你确认过的公钥。只有设备里存了对应的公钥adbd才会认你这个客户端。如果你换了电脑、删了.android目录、或者刷机清了数据公钥就没了必须重新弹窗确认。数据线本身也是一个排查点。市面上大量线材只有供电针脚没有数据针脚插上去手机开始充电看起来一切正常adb devices却永远是空的。判断方法很简单换一根确定能传文件的线试。我在这个问题上浪费过整整一个下午最后是随手换了根原装线才通的。2.3 首次连接必须跑的四条体检命令连上之后不要急着操作先花一分钟做个体检把设备状态摸清楚能避免后面一系列命令执行了但没效果的困惑。adb devices -l # 看设备序列号、型号、连接方式 adb shell getprop ro.product.model # 确认机型 adb shell getprop ro.build.version.release # 系统版本 adb shell getprop ro.debuggable # 关键是否为 1 adb shell id # 看 adbd 给你的身份通常是 uid2000(shell) adb shell getenforce # 看 SELinux 当前模式Enforcing 或 Permissiveadb shell id这一条尤其值得养成习惯。它直接告诉你当前会话的身份uid2000(shell) gid2000(shell)是零售机的常态。getenforce返回Enforcing说明强制访问控制在生效很多命令没报错但也没生效的情况根子就在这里。注意ro.debuggable这类属性是只读的运行时改不了。看到它是 0就别再折腾adb root了把精力放到别处。3. adb 复制文件到手机push 的边界在哪里3.1 哪些目录能写、哪些目录写了也没用adb push这个命令本身没有权限门槛只要adbd能连上它就能往设备里写文件。真正的限制在于写到哪个目录以及写进去的文件能不能被执行。几个常见目标目录的性质差别很大目录当前用户可写重启后保留能否直接执行典型用途/sdcard即/storage/emulated/0可以保留不能传图片、文档、安装包/data/local/tmp可以保留通常可以放临时二进制、脚本/data/data/包名不可以保留不能应用私有数据需对应 uid/system/bin、/vendor/bin不可以保留可以系统可执行文件目录只读挂载/data/adb及其子目录依赖权限保留视情况常用于放模块与守护脚本新手最容易犯的错误是直接把文件push到/system/bin下面然后心想这下应该能当系统命令用了吧。实际执行时会收到Read-only file system或者Permission denied。要写这个目录需要先重挂载为读写而重挂载这个动作本身又需要 uid 0 以及相应的 SELinux 放行——兜了一圈回到原点。/data/local/tmp才是我推荐的主战场。这个目录对shell用户可写允许执行二进制文件重启后内容还在而且不需要动任何系统分区风险低得多。绝大多数我要在设备上跑个自己的工具的需求在这里都能满足。3.2 push 实操单文件、目录、权限与校验把文件推进设备的完整命令序列如下我按实际会用的顺序写# 1. 推送单个二进制到 tmp 目录 adb push ./mytool /data/local/tmp/mytool # 2. 追加可执行权限关键一步很多人漏掉 adb shell chmod 755 /data/local/tmp/mytool # 3. 校验文件完整性与权限 adb shell ls -l /data/local/tmp/mytool adb shell md5sum /data/local/tmp/mytool # 4. 执行 adb shell /data/local/tmp/mytool --help # 推送整个目录注意 -a 参数保留时间戳和模式 adb push -a ./assets /data/local/tmp/assets # 反向拉取用于取日志和证据 adb pull /data/local/tmp/output.log ./output.log adb pull /sdcard/DCIM ./backup_photos第三、四步是我强烈建议固化的习惯。md5sum两只一比能立刻判断传输有没有中断如果电脑端和手机端算出来的哈希值不一样重传就行不用怀疑程序本身有问题。chmod 755这一步也常被跳过结果执行时报Permission denied看起来像是 SELinux 拦的其实是文件根本没有执行位。关于架构还有一个容易翻车的点push进去的二进制必须和设备的 CPU 架构匹配。现在绝大多数设备是arm64-v8a但也有armeabi-v7a的老设备以及部分模拟器是x86_64。用adb shell getprop ro.product.cpu.abi查一下再选文件比执行时报Exec format error再回头找原因高效得多。3.3 复制进去却执行不了四层原因逐级排查文件明明存在就是跑不起来这个现象背后有四种可能按概率从高到低排**第一种是漏了执行位。**表现是Permission denied但ls -l看权限是-rw-r--r--。chmod 755解决。**第二种是挂载点带了 noexec。**某些目录在挂载参数里标注了noexec即使有执行位也不允许运行。用mount | grep 目录看挂载选项能确认。这种情况换个目录就能绕过去。**第三种是 SELinux 拦截。**文件权限全对、挂载参数也没问题执行依然失败logcat里刷出avc: denied记录。这就说明强制访问控制不允许shell域去执行这个上下文下的文件换目录或者调整上下文才可能解决。**第四种是文件本身有问题。**架构不匹配、动态链接库缺失、文件传输过程中被截断都会在不同阶段报错。前两种报的是Exec format error后一种可能直接段错误。这四层排查顺序本质上是从最表层的权限位往里剥一层一层排除。养成这个顺序比漫无目的地重刷固件有效得多。提示判断一个目录能不能执行文件最直接的办法是adb shell cd /data/local/tmp ./某文件不要在自己的电脑目录里想当然。4. 从 adb 到 root真实路径的原理拆解4.1 三条路径横向对比哪条走得通、哪条是死路把网上的说法归纳一下实现 root 权限的思路大体分三类可行性和风险差别巨大。**第一类是引导程序解锁加启动镜像刷写。**这是目前主流且相对可控的做法。核心逻辑是引导程序在解锁状态下允许向启动分区写入自定义镜像而启动镜像里包含了内核和初始化阶段的关键配置在这个阶段挂载一个具备特权能力的组件就能在系统启动过程中获得 uid 0 的运行环境再通过一个管理类应用来授权或拒绝其他应用的提权请求。整个过程不修改系统分区因此不会破坏完整性校验系统更新后重新处理一遍即可。**第二类是临时提权。**某些调试版本固件或者特定开发环境下adb shell会话本身就能拿到 uid 0用adb root重启adbd即可。这类情况在零售机上几乎不存在看到相关教程先确认对方设备是不是工程样机。**第三类是依赖系统漏洞。**通过内核或系统服务中的实现缺陷来突破权限边界。这条路不推荐一是漏洞有版本窗口系统一更新就失效二是来源不明的提权程序本身就可能是恶意载体三是整个过程不可控出现问题时很难回退。对比下来判断标准很清晰设备引导程序是否能解锁、社区是否有对应芯片平台的可维护方案。两个条件都满足第一条路就走得通。路径前提条件数据影响可回退性风险等级解锁 启动镜像方案引导程序可解锁、有对应固件包解锁会清空数据可刷回原厂镜像中等需备份调试固件临时提权固件ro.debuggable1无无低但零售机不可用依赖系统漏洞系统版本落在窗口内不确定差高不建议4.2 通用流程框架每一步在做什么具体的命令因厂商而异但流程骨架是相通的理解每一步的意图比死记命令重要。**第一步备份。**这不是客套话。引导程序解锁这个动作会导致设备恢复出厂状态内部存储的照片、聊天记录、应用数据全部清空。我在第一次操作时侥幸觉得应该不会那么狠结果第二天花了一整天恢复资料。用adb pull把/sdcard下的关键目录拉出来或者用设备自带的云同步与本地备份功能两条一起上。**第二步确认固件版本与工具链。**搞清楚设备当前的系统版本号、芯片平台、地区版本。这些信息决定了你该下载哪个版本的完整固件包以及配套的刷写工具。查这些信息用前面提过的getprop就够注意别只看机型名同一机型不同地区、不同存储版本可能对应不同固件。**第三步解锁引导程序。**在开发者选项里打开OEM 解锁开关然后让设备进入引导程序模式执行厂商提供的解锁指令。不同厂商的解锁方式差异极大有的需要等待若干天有的需要通过官方渠道申请有的干脆不对外开放。这一步必须以官方说明为准不要相信来路不明的第三方指令。**第四步处理启动镜像。**从官方固件包里提取启动镜像文件用可靠的方案对其进行处理生成一个包含特权组件的镜像。处理工具的版本必须与实际系统版本匹配这是整条链路里最容易出错的一环。**第五步刷写并首次启动。**让设备进入引导程序模式把处理后的镜像写入启动分区然后正常开机。开机后安装配套的管理类应用它会引导完成剩余的初始化配置。**第六步验证。**这一步后面单独说。整个流程里第一步和第二步花的钱和时间会在后面每一步回报给你。跳过备份的人最后往往要付出十倍代价。4.3 拿到权限之后验证方式与兼容性影响流程走完怎么确认真的成功了我的验证清单是这样的adb shell id # 期望看到 uid0(root) adb shell su -c id # 通过提权通道执行期望同样是 uid0 adb shell getenforce # 看当前模式 adb shell ls -l /system/bin/su # 看提权程序是否就位如果adb shell id还是shell但su -c id能返回 uid 0说明提权通道已经建立只是adbd本身没有切到 root这是完全正常的。接下来要面对一个更现实的问题兼容性。拿到权限之后一大部分涉及支付、银行、企业办公、部分游戏的 App 会启动自身的环境检测。它们的检测思路大致包括检查是否存在提权相关的可执行文件、检查系统属性是否被改动、检查挂载状态是否异常、检查进程环境中的特定目录。检测到之后轻则功能受限重则直接拒绝启动。这是权限带来的原生代价不是配置错误。理性的做法是动手前先想清楚自己到底需要哪些能力如果只是想抓日志、批量安装应用、模拟点击那么下面要讲的免提权方案可能就够了。注意不要为了看起来干净而随意修改系统属性或进程环境去规避检测这类操作容易误伤系统本身导致启动异常。判断清楚需求边界比研究规避技巧有用得多。4.4 不拿权限也能干很多事替代方案清单这几年我发现把需求拆细之后真正必须依赖 uid 0 的场景其实不多。下面这些能力都可以在shell身份下完成需求免提权方案备注抓应用日志adb logcat按包名过滤完全够用授予运行时权限adb shell pm grant 包名 权限需应用声明该权限精细权限控制adb shell appops set 包名 项 allow/deny覆盖范围更广批量安装卸载adb install、adb uninstall可配合脚本模拟点击滑动adb shell input tap/swipe/keyevent自动化测试常用屏幕截图与录屏adb exec-out screencap -p x.png无需 root修改部分系统设置adb shell settings put命名空间有限制应用内特权 API 调用借助调试通道启动本地服务代理走的是调试权限不是提权最后一条值得多说一句思路是利用调试通道在设备上以shell身份启动一个常驻服务应用通过它与系统交互从而调用一些平时拿不到的系统能力。这类的价值在于——它借的是调试权限而不是提权所以不会触发大部分环境检测也不会破坏系统完整性。对于自动化、辅助类需求这条路通常比直接拿 uid 0 更划算。5. 高频报错排查实录与避坑清单5.1 连接类报错速查表连接问题占了新手求助帖的一多半这张表是我这几年整理出来的高频对照报错信息根本原因处理思路adb devices空列表线材无数据针脚、驱动缺失、服务卡死换线、装驱动、kill-server重启unauthorized设备未确认调试公钥重新插拔触发弹窗勾选始终允许device offline连接状态异常、系统未完全启动重启 adbd、重插线、重启设备no devices/emulators foundserver 未运行或端口被占重启 server检查端口占用多设备时命令报错未指定目标设备加-s 序列号参数无线连接频繁断开网络不稳定、休眠策略保持屏幕常亮、改用有线关于多设备场景我习惯的写法是先在变量里存序列号后面所有命令统一引用这样切设备只需改一处SERIAL$(adb devices | awk NR2{print $1}) adb -s $SERIAL shell getprop ro.product.model adb -s $SERIAL logcat -c adb -s $SERIAL logcat full.log5.2 权限与文件系统类报错速查表这一类报错最迷惑人因为命令能执行、输出是拒绝看起来像是权限不够四个字概括了一切实际原因分层很细报错信息所在层级处理方向Permission denied执行时Unix 权限位chmod 755补执行位Read-only file system分区挂载状态换到/data/local/tmp操作avc: deniedSELinux 强制访问控制无法在 shell 身份下绕过换方案Operation not permitted能力集或命名空间限制确认当前身份检查是否有对应能力No such file or directory路径或解释器缺失检查绝对路径、检查动态链接库Exec format errorCPU 架构不匹配用getprop ro.product.cpu.abi确认error: closed连接在传输中断开重连检查数据线稳定性把这张表和上一节的理论对上就会发现所有报错都能落到Unix 权限 / SELinux / 挂载参数 / 架构 / 传输稳定性这五个桶里的某一个。定位到桶解决路径基本就清晰了。5.3 三条只有踩过才知道的经验**第一条每次操作前先记录基线。**在动手之前把getprop的关键属性、mount的输出、getenforce的状态全部导出一份存起来。出问题时两边一对比立刻能看出是哪个属性变了。我现在的习惯是建一个baseline目录每台设备一份成本几分钟收益是排查时间从几小时压缩到几分钟。**第二条不要在状态不确定的时候连续执行写操作。**很多人一着急就反复刷、反复推文件结果设备状态被改得乱七八糟最后连原厂固件都刷不回去。正确顺序是先读、再确认、再写写之前想清楚回退方案。**第三条日志要实时看不要事后猜。**开两个终端一个执行操作另一个跑日志过滤adb logcat -c adb logcat | grep -Ei avc|denied|su|permission-c先清空缓冲避免翻到几天前的旧记录。grep过滤关键词把噪音挡掉。操作和日志并排看问题几乎无处可藏。这个习惯不分新手老手我用到现在没停过。6. 常用 adb 命令速查按场景分类6.1 设备状态与诊断类adb devices -l # 设备列表与详情 adb shell getprop # 导出全部系统属性 adb shell getprop ro.build.version.release adb shell getprop ro.product.cpu.abi adb shell dumpsys battery # 电池状态 adb shell dumpsys meminfo 包名 # 内存占用 adb shell dumpsys window | grep mCurrentFocus # 当前前台窗口 adb shell top -n 1 # 进程资源占用快照 adb shell df -h # 分区剩余空间dumpsys window | grep mCurrentFocus这一条在写自动化脚本时特别实用它能告诉你当前屏幕上实际获得焦点的窗口是谁。判断应用有没有成功启动、判断弹窗有没有挡住目标界面靠这一条就够了比截屏再肉眼比对高效得多。6.2 应用管理类adb install -r app.apk # 覆盖安装 adb install -t app.apk # 允许测试包 adb uninstall 包名 # 卸载 adb uninstall -k 包名 # 卸载但保留数据 adb shell pm list packages -3 # 列出第三方应用 adb shell pm list packages -s # 列出系统应用 adb shell pm path 包名 # 查安装路径 adb shell pm clear 包名 # 清空应用数据 adb shell am force-stop 包名 # 强制停止 adb shell am start -n 包名/Activity # 启动指定界面pm clear这个命令我在测试环境清理时用得极多它等价于在设置里点清除全部数据但只需要一行配合脚本可以批量重置几十个应用的状态。6.3 日志、截图与文件传输类# 日志 adb logcat -c # 清空缓冲 adb logcat -v threadtime log.txt # 全量抓取 adb logcat --pid$(adb shell pidof -s 包名) # 按进程过滤 adb bugreport ./bugreport.zip # 完整诊断报告 # 截图与录屏 adb exec-out screencap -p shot.png # 注意用 exec-out 避免换行损坏 adb shell screenrecord --time-limit 30 /sdcard/demo.mp4 adb pull /sdcard/demo.mp4 ./demo.mp4 # 文件传输 adb push local.txt /data/local/tmp/ adb pull /data/local/tmp/local.txt ./ adb shell cat /data/local/tmp/local.txt # 小文件直接打印exec-out和shell的区别值得强调adb shell screencap -p shot.png在部分平台会因为换行符转换把二进制数据破坏掉截出来的图打不开。exec-out不做这种转换是二进制输出的正确姿势。这个小坑我当年排查了很久一直在怀疑截图命令本身有问题其实是重定向这一层出了岔子。用--pid过滤日志也是同理logcat默认输出的量非常恐怖直接抓全量再搜文件很快就到几百兆。先拿到进程号再过滤日志体积能缩小两个数量级检索效率完全不是一个量级。我个人在实际操作中的体会是这一整套东西里最值钱的不是某个具体命令而是那条排查链路的顺序感先确认连接再确认身份再确认目录和挂载最后才去看权限和策略。绝大部分adb 用不了root 拿不到的问题都能在这条链路上某个环节找到明确答案而不是靠反复重刷固件去碰运气。另外补一句动手前把备份做掉把基线记下来这两个动作花的时间永远是最划算的投资。
返回列表