
第一次编译出自己的AOSP镜像、刷进手机、在终端敲下id看到uid0的时候我盯着屏幕愣了好几秒。那不是什么黑科技只是把所有源码层面的权限开关都按正确的方式打开了一遍。后来不少朋友问我“Android修改源码实现root”到底怎么搞我才发现很多人对root的理解还停留在“下载一个工具点一下”的层面。这篇文章就把我从编译AOSP到集成su、调整SELinux策略、处理分区校验这一整套流程讲清楚既有源码层面的原理也有可以直接照做的改动步骤适合想做定制ROM、系统开发或者单纯想弄懂root底层机制的开发者。1. 先搞清楚一件事源码做出的root和网上那些一键工具不是一回事很多人理解的root是手机解锁bootloader之后刷入SuperSU或者Magisk包然后一个授权管理App弹窗询问“是否授予root权限”。这个流程确实能拿到root但它属于“对已发布系统镜像的二次修改”。而“修改源码实现root”走的是另一条路你在编译Android系统的时候就把su、授权管理器、SELinux策略、init启动配置全部写进系统镜像里。刷机之后系统天生就带root能力不需要再去修改分区、不需要关掉完整性校验来适配补丁因为这一切在编译阶段就已经是系统的一部分了。在展开技术细节之前我觉得有必要先把root的本质说透。Android底层是Linux内核Linux系统里权限的根源就是uid。普通用户是uid 1000之后shell是uid 2000system服务是uid 1000而uid 0就是root也就是Linux里的“超级用户”。Android给每个普通App分配一个独立的uid把它们关在各自的沙箱里让App之间无法互相读写数据这是Android安全模型的根基。但Android在Linux的uid隔离之上又多做了几层防护应用沙箱每个应用进程有自己的uid和独立的data目录访问其他应用的数据会被拒绝。权限模型即使你有能力访问某个资源也必须在AndroidManifest里声明权限系统在安装或运行时检查。SELinuxSEAndroid这是最容易被忽略的一层。传统的Linux权限叫DAC自主访问控制root在DAC层面几乎是全能的但SELinux属于MAC强制访问控制即使进程是root如果SELinux策略里没有给这个进程放行某项操作它照样被拒绝。所以“修改源码实现root”这件事并不是简单地把su二进制编进镜像就完事了。你得同时处理DAC层面的uid切换、SELinux层面的策略放行还要绕过dm-verity这类分区完整性校验。好在源码方案天然有个优势改动发生在编译阶段镜像内容和你写入的内容是一致的校验签名、完整性和策略文件都对得上不存在“事后改系统导致校验失败”的问题。这套方案适合谁如果你是做定制ROM、企业设备管理、嵌入式Android开发或者单纯想深入理解Android系统启动与权限机制的非常值得花时间走一遍。如果只是想让自己手上的手机能跑需要root权限的App那我建议直接去看Magisk这类现成方案没必要从源码开始折腾——源码方案的开发周期是论周算的而且每换一个设备型号适配成本都不低。2. 在Android源码树里root权限藏在哪几个关键位置源码树那么大root权限不是说某个文件里写一句“允许root”就生效的。它分散在构建系统、init进程、adbd、SELinux策略、包管理服务等多个模块里。2.1 构建类型定基调eng、userdebug、user 三者的差别每次编译Android之前都要lunchlunch出来的构建类型基本决定了这套系统的root基调。构建类型ro.securero.debuggableadb root常见用途eng01默认可用开发调试权限最松userdebug11可手动开启功能测试、调试user10不可用正式发布版在eng版本里adbd默认以root身份运行adb shell进去直接就是root。但这不是我们要的“普通App也能通过su提权”的root它只是调试通道的root。而且eng版本有很多调试特性和性能损耗不适合做成正式系统。真正要做的是在userdebug或者user版本里把su和授权管理器集成进去让每个App都能通过su二进制向授权管理器发起提权请求。2.2 adbd 与 ro.secure为什么 adb root 不是完整 root先说说adb root。adbd进程在启动时会读取ro.secure这个系统属性如果为1adbd会把自己降权到shell用户如果为0adbd直接以root身份跑。userdebug版本里你可以用adb root命令让adbd重新以root身份启动但这只是让adb这个调试通道拿到root普通App依然是受限的。这里牵出一个关键属性ro.secure和ro.debuggable通常定义在ramdisk里的default.prop不同Android版本路径有差异Android 9之后有些分支把它挪到了system/etc/prop.default。源码方案里可以直接改这个文件但这只是开关之一真正让普通App提权得看su这条链路。2.3 su 与授权管理器真正给普通 App 放权的链路当你在源码里集成一个“root方案”时核心是两件事su二进制和授权管理App。su二进制的作用是让任何进程都可以执行它并且在执行后切换到root身份。但这里有个安全设计问题如果任何人都能无条件变成root那系统就彻底裸奔了。所以在成熟的源码root方案里su不是直接提权而是先发起一个请求到授权管理服务比如经典的Superuser、Koush的Superuser等由这个服务弹窗或按预设规则决定“允许还是拒绝”。这个架构可以概括成一句话su负责提权能力授权管理器负责提权决策。授权管理器内部维护一个数据库记录哪些App被允许、哪些被拒绝。如果App未被记录就弹窗询问用户。2.4 被忽略的前置条件SELinux 与分区完整性校验就算你编译了su也编译了Superuser授权管理App很多人在第一次刷机后仍然会发现su命令执行失败。罪魁祸首往往是SELinux。Android从4.3开始引入SELinux从5.0开始全面强制enforcing。默认策略下普通App的域比如untrusted_app根本没有执行su的权限甚至su自己的域也可能缺少必要的allow规则。所以你必须单独为su写一个.te策略文件并且在file_contexts里给su二进制打上正确的文件标签让SELinux知道“这个文件是su的可执行文件允许它进入su域”。另外还有dm-verity。这是Linux内核提供的一种块设备完整性校验机制Android用它来保证system分区等镜像内容没有被篡改。如果启动时发现system分区的某个块的hash对不上系统会直接拒绝启动或者进入recovery。源码方案因为是在编译期就把su写进了system.img所以天然不会触发dm-verity的校验失败。这也正是“修改源码实现root”和“刷机后改分区”在底层思路上最大的不同。3. 手把手让AOSP编译产物自带可授权的root能力这一节直接给可操作的方案。我用的是AOSP 开源Superuser这个经典组合我自己在Android 7~9的几个项目里都用这个路子跑通过Android 10以上的分支会有一些细节差异后面单独说。3.1 确定方案集成开源 Superuser 而不是自己写一个如果你问我“能不能自己写一个su和授权管理器”答案是能但完全没必要。AOSP历史上就有一个开源的Superuser实现Koush的packages/apps/Superuser很多第三方ROM都是基于它做的定制它包含su二进制源码在Superuser的jni目录里授权管理App的完整源码与su配套的SELinux策略示例与su配套的init配置示例我自己编译时用的是这个仓库的一个稳定分支把它放到AOSP源码树的packages/apps/Superuser目录下然后在device.mk里把su和Superuser加进PRODUCT_PACKAGES。3.2 编译进 suPRODUCT_PACKAGES 和 Makefile 改动每个设备的device.mk不同但通用的做法是加这两行PRODUCT_PACKAGES \ su \ Superuser如果是老版本的Superuser有些实现还需要一套独立的su模块源码。你们可以看一下Superuser工程目录里jni/su/Android.mk或Android.bp的内容只要它定义了su这个模块名上面这行就能把它编进系统镜像。改完之后编译验证一下看out/target/product/xxx/system/xbin/su或system/bin/su是否存在。如果没找到大概率是PRODUCT_PACKAGES没有生效检查一下你的device.mk有没有被TARGET_DEVICE对应的product配置正确include进去。3.3 让 su 能切换身份init.rc 的 service 配置与 6755 权限su要能正常工作权限位必须是6755。这一串数字拆开看6 setuid setgid也就是42执行su时进程能切换到文件ownerroot的身份755 owner可读写执行group和其他用户可读可执行一个普通App运行su时因为su有setuid位进程身份会从普通uid变成root然后su再根据授权管理器的裁决决定是否帮你执行后续命令。光有权限位还不够很多方案还会在init脚本里注册一个su服务告诉init进程su应该以什么身份和SELinux域启动。参考配置如下service su /system/xbin/su class main user root group root shell seclabel u:r:su:s0 oneshot这段配置的意思很明确su进程以root身份运行进入su这个SELinux域。oneshot表示服务启动一次就退出适合这种“命令行工具”类服务。3.4 给 SELinux 一个通行证su.te、file_contexts 与 sepolicy 编译SELinux策略是源码root方案里最容易翻车的地方但也是最体现功底的部分。首先在device/[厂商]/[设备]/sepolicy目录下新建一个su.te文件大致内容如下示意具体允许规则要按你的项目情况调整type su, domain; type su_exec, exec_type, file_type; # 允许执行su二进制并进入su域 allow su su_exec:file entrypoint; allow untrusted_app su_exec:file { read execute execute_no_trans }; allow su system_file:file { read open getattr execute }; allow su shell:fd use; allow su self:capability { setuid setgid };写这个文件的时候要注意Android的SELinux策略有大量neverallow约束很多“看起来合理”的自定义规则会被编译时直接拒绝。如果你看到neverallow报错不要硬来得顺着报错提示去调整域设计。比如早期一些方案直接allow su domain:process { ptrace }这在带neverallow的新分支上基本过不了编译。然后在file_contexts文件里给su二进制打标签/system/xbin/su u:object_r:su_exec:s0这样当init进程执行/system/xbin/su时SELinux能识别出它是su的可执行文件允许它进入su域。最后确保你的BoardConfig.mk里已经启用了sepolicy编译并且把su.te和file_contexts里的配置包含进去。AOSP的build系统会自动处理大部分事情关键是你别把文件放错位置。3.5 构建镜像并刷机验证整个链路是否跑通改完这些之后执行完整的镜像编译source build/envsetup.sh lunch your_device-userdebug make -j8编译完成后检查关键文件ls -l out/target/product/your_device/system/xbin/su看到-rwsr-xr-x root root就说明权限对了。再用系统自带的工具检查sepolicy是否包含su相关规则adb shell getenforce刷机后第一次运行su会触发授权管理App的弹窗。我建议先在电脑上验证一遍adb shell $ su正常情况下会看到Superuser弹窗选择“允许”之后终端里就变成#号此时执行id能看到uid0(root)整条链路就算通了。4. 内核和 ramdisk 层面还能做哪些调整很多人在“编译出带su的镜像”之后就完事了但如果你做的是给特定设备定制系统还会遇到adb root不可用、SELinux被强制限制、分区校验导致刷机失败等一堆问题。这些问题的根源很多不在system分区而在ramdisk和内核cmdline。4.1 ramdisk 里的属性如何影响 adb rootdefault.prop或新版本的prop.default里定义了ro.secure、ro.debuggable、ro.adb.secure这三个关键属性。ro.secure1时adbd降权为shellro.debuggable1时允许adb root命令ro.adb.secure1时要求USB调试必须授权如果你的系统是user版本并且希望保留adb root能力可以尝试在ramdisk里把ro.secure改成0。但这里要注意很多构建脚本在编译user版本时会强制覆盖这几个属性你改了也会被还原。要真正做到“user版本也能adb root”可能得去改system/core/adb/daemon/main.cpp里关于secure的判断逻辑或者在build系统里查一下属性赋值的覆盖点。4.2 内核 cmdlineSELinux与dm-verity的最终开关内核cmdline可以在bootloader启动时传给内核Android系统里最常见的两个参数是androidboot.selinuxpermissive启动时把SELinux设为permissive模式所有权限拒绝会变成日志记录而不是真正拒绝。androidboot.veritymodeenforcing/androidboot.veritymodeeio控制dm-verity的校验模式。如果你是做定制系统开发阶段为了快速定位问题可以先用androidboot.selinuxpermissive跑起来等一切稳定了再改回enforcing。但要注意这样做的代价是SELinux的保护全部失效只适合在调试阶段用正式交付时务必要回归enforcing验证su的SELinux规则是否完整。4.3 编译内核时保留的配置项有些root方案需要内核支持特定的机制比如CONFIG_SECURITY_SELINUX、CONFIG_ANDROID_PARANOID_NETWORK等。如果你拿到的内核源码里这些选项被裁剪掉了即使system侧集成了su很多提权操作依然会失败。我自己遇到过的一个案例是某设备的kernel把CONFIG_ANDROID_PARANOID_NETWORK打开了导致root后的App想操作网络相关接口时被拒绝看起来像是root失效其实是内核网络权限限制。所以要确认你的内核config里有需要保留的选项一般用设备原厂内核配置就行别随便裁剪。5. 现成root工具与源码方案的差异别被Magisk带偏了聊到root绕不开Magisk。我见过很多人问“能不能把Magisk塞进源码编译里”也有不少人觉得“都有Magisk了为什么还要在源码里改”。这两条路确实都能拿root但思路完全不同。维度源码集成rootMagisk类现成方案adb rooteng/userdebug实现时机编译期写入启动期hook、overlay注入运行时切换adbd身份系统镜像完整性天然通过需要patch boot镜像天然通过普通App授权支持有授权管理器支持有Magisk授权模型不支持定制ROM集成度最彻底属于后处理不算完整root维护成本高每次升级都要重新适配低跟着工具版本走无Magisk的方案很聪明它通过patch boot镜像在系统启动早期挂载一个overlay层不直接改动system分区里的文件从而绕过了dm-verity对system分区的校验。但它的本质仍然是对官方已发布镜像的“启动期篡改”和源码方案那种“系统天生支持root”的设计哲学不一样。在定制ROM领域源码集成root仍然是主流做法。因为定制ROM很多功能本来就依赖系统源码改动比如平台签名、预置应用、系统级功能扩展等root只是整个定制工作的一部分。你用Magisk去给一个定制包做后处理反而可能破坏定制ROM本身的完整性。那如果非要在源码编译流程里引入Magisk模型呢可以做一个“编译后处理脚本”编译完完整镜像后对boot.img执行Magisk的patch操作再重新打包。但这就等于你放弃了源码方案的干净性还得确保Magisk版本与你的Android版本兼容。除非有特殊需求否则我不推荐这么干。6. 实测验证和最容易踩的坑最后聊聊验证方法和我在实际编译中遇到的坑这些都是常规文档不会写的。6.1 验证 root 是否生效一套完整的命令序列刷机完成后按顺序执行下面这些命令可以快速判断root链路哪一环节出了问题adb shell getenforce输出Enforcing或Permissive。如果你的SELinux策略没写好这里看到Enforcing时su大概率跑不起来。adb shell id正常是uid2000(shell)。adb shell su首次会弹授权窗口允许后再执行adb shell # id # getenforce如果id显示uid0(root) gid0(root)说明DAC层面的提权成功如果getenforce仍显示Enforcing且su操作正常说明SELinux策略也通了。一个比较容易误解的点是你看到#号不等于所有root操作都能做。某些操作依然会被SELinux拦截只是su这个动作本身是被允许的。所以测试root不能只敲id建议用su -c ls /data/user/0这类真实访问权限资源的命令来验证。6.2 SELinux 拒绝的排查链路avc: denied 到底在说什么su执行失败时最常见的输出是su: unable to exec: Permission denied。这背后十有八九是SELinux拒绝但DAC权限不对也会报类似错误。排查路径可以按这个顺序走先确认文件权限adb shell ls -l /system/xbin/su不是-rwsr-xr-x就回去改init配置或打包脚本。再确认SELinux标签adb shell ls -Z /system/xbin/su应该看到u:object_r:su_exec:s0。然后看denied日志adb shell dmesg | grep avc | tail -20日志里会出现类似这样的内容avc: denied { execute } for pid1234 commsu namesu devdm-0 inoxxx scontextu:r:untrusted_app:s0:c... tcontextu:object_r:su_exec:s0 tclassfile这里denied { execute }说明普通App连执行su文件的权限都没有需要在su.te里补充allow untrusted_app su_exec:file { read execute }等规则。开发阶段如果判断不了策略问题可以先临时关SELinux验证一下是不是SELinux导致的adb shell setenforce 0如果关了SELinux之后su能正常工作几乎可以确定是策略缺失。记得测完恢复setenforce 1并回头把缺失的allow规则补齐。6.3 编译期和刷机期的常见问题AOSP编译本身的坑非常多这里只说我遇到的、和root方案关联比较大的几个。第一千万不要在NTFS或者FAT32格式的磁盘上编译AOSP。symlink和权限位会出各种奇怪问题su的setuid位可能在打包时就被弄丢了。用ext4或apfs这类支持Unix权限的文件系统。第二编出来的镜像刷进去发现没有su先不要怀疑代码先确认你的PRODUCT_PACKAGES是否真的在那个lunch target的product配置里。我犯过的错误是把PRODUCT_PACKAGES写在了某个永远不会被include的mk文件里。第三Android 10以上如果遇到system镜像分区空间不够su集成后镜像塞不下需要检查BoardConfig.mk里的BOARD_SYSTEMIMAGE_PARTITION_SIZE适当调大。第四Android 10以上默认开启AVBAndroid Verified Boot2.0如果你刷的不是和bootloader签名的同一套key启动时可能卡在验证阶段。定制ROM通常的做法是编译时关闭AVB或者替换成自己的key。6.4 不同 Android 版本的改动差异这个部分很重要因为网上很多教程是很多年前写的直接抄在Android 14上会头破血流。我简单梳理一下关键差异Android 7.0及以前su通常位于system/extras/suSELinux虽然存在但不少OEM默认permissiveroot方案相对容易。Android 8.0引入Treblesystem分区和vendor分区分离sepolicy被编译进boot image的dtb里。改动SELinux策略后必须重新编译boot镜像。Android 9.0强制SELinux enforcing并且adb root在user构建上更难绕过。ramdisk里的default.prop改名为prop.default。Android 10及以上动态分区登场system不再是独立的物理分区刷机时通常要刷super.img。AVB的强制校验也更严格。所以说“修改源码实现root”这件事没有一份能跨所有版本照抄的配置清单你必须针对自己编译的具体分支去调整。换个版本su在源码树里的位置可能变了、SELinux策略的语法可能变了、分区布局可能变了但核心认知是一样的root su二进制 授权决策 SELinux放行 内核允许 分区校验放行缺任何一个环节都跑不通。从我个人经验来说在源码层面把root链路完整走通一次你对Android系统的理解会上一个台阶。因为你会被迫去搞明白构建类型、init进程、adbd、SELinux、分区校验这些平时藏在应用层底下的东西。如果你只是想要一个“能弹窗授权的root”直接刷现成工具更省事但如果你想真正掌控这套系统的权限边界源码这条路值得走一遍。