首先
10月初,终于是官宣了Harmony Next系统。月底就已经有获取模拟器 Root 的方案了
HarmonyOS Next 模拟器 root | wuxianlin
HarmonyOSNext模拟器Root(无视模拟器镜像完整性验证)HarmonyOSNext模拟器Root,无视模拟 - 掘金
我在DB1版本的模拟器中就通过类似的方法拿到了 root 权限。但是短短2个月之后的 DB3 就无法使用了。于是对于新的root获取方式的研究直到最近收到了上文的启发,才继续研究了一下,终于是又重获了 Root.
本文就以技术随笔的形式,记录一下一步步获取到Root的思考过程
Topic1 : 获取到Root权限就一定是漏洞?
在 Android 时代的华为手机中,如果能获取到 root 权限就有机会在 PSIRT 上得到不菲的漏洞奖励。这主要是得益于 SecureBoot 保障了设备整个启动过程中是未被篡改的,再加上带有 Bootloader 锁的设备只允许刷入厂商认证的系统镜像;
那么对于一个模拟器设备呢?
Android 的大部分模拟器都是通过 qemu 来完成的。HarmonyOS 也是一样的,在开源的OpenHarmony 就提供了qemu的运行编译选项 (虽然按照教程文档编译可能会被坑到怀疑人生). 因此模拟器很难基于设备可信根来保障加载分区的完整性。此时,获取到Root权限理论上可以像 Android 刷入Magisk 一样简单;
这就是为什么要选择模拟器来拿 Root 用于安全研究;
Topic2 : Root 需要修改那些文件?
启动HarmonyOS模拟器之后,通过 ps 命令查看对应的进程,可以看到两个重要的文件路径:
/Users/xxx/.Huawei/Emulator/deployed : 指向模拟器实例目录,保存实例相关文件,比如cache, user_data 等分区内容;
/Users/xxx/Library/Huawei/Sdk : 指向模拟器系统镜像,保存Kernel image, system, vendor等分区镜像;
一般需要直接系统配置文件都会保存在 system 分区,但是看下这个目录保存的内容会发现image_signature 目录保存了各个分区的签名:
1 | tree system-image/HarmonyOS-NEXT |
Emulator 在启动过程中会检查关键分区的完整性:
1 | cat Emulator.log | grep "verify succeed" |
因此贸然的修改 system.img 并不是一个明智的选择。可以选择修改qemu生成的system.img.qcow2 文件来绕过对 system.img 完整性的检查
1 | file system.img.qcow2 |
qcow2 文件链接了 system.img 的路径,为了在修改方便首先将 qcow2 转换为 ext2 文件系统, 然后就可以使用mount挂在到linux系统上:
1 | qemu-img convert -O raw system.img.qcow2 system.img.raw |
下面就开始调整系统中各项配置文件了
hdcd.cfg
这个文件位于 /system/etc/init/hdcd.cfg, 其功能等效于 Android 系统中的 rc 文件,init 进程会依次加载各个 cfg 文件,完成系统服务的加载:
1 | "services" : [{ |
那么实际上需要修改 uid/gid/secon 即可:
“uid” : “root”
“gid” : [“root”, ….]
“secon” : “u:r:su:s0” (当然,参考了其他的blog发现这里无法调整成 su)
system_common.cil
该文件为 selinux policy 文件,保存路径为 /system/etc/selinux/system_common.cil
把 (type sh)
变更为 (typepermissive sh)
, 让sh变成一个不受selinux限制的特权用户;
同时,这里也出现了第一个迷惑行为:
1 | ls -al system/etc/selinux |
明明已经有 .sha256 文件来提供完整性校验能力,但是init在启动过程中却没有检查,导致了我直接修改 system_common.cil 文件内容仍然可以启动成功;
ohos.para & hdc.para
这两个文件相当于Android prop的各种属性
1 | // system/etc/param/ohos.para |
完成全部修改后,安全umount,然后重新把分区转化为qcow2格式并替换掉 system.img.qcow2
1 | sudo umount /home/xxx/hm/system.img.raw |
启动HarmonyOS模拟器,发现 hdcd 服务挂掉了,Fxxk …
Bug & Fix the Bug & RCA
首先是查看 kernel log,发现 init 正常的拉起了 hdcd,但是,hdcd 貌似异常退出了
1 | [ 9.999149][ T1] [pid=1][Init][INFO][init_common_service.c:664]ServiceStart started info hdcd(pid 827 uid 0) |
由于DB1和DB3版本的模拟器并未出现过 hdcd 异常退出的问题,只要从它们的system.img中复制一个 hdcd 过来就不会崩溃了;当我开心的重新启动模拟器并看到shell提示符已经是”#”时,最令我崩溃的BUG出现了:
1 | id |
当 hdcd 以shell uid 启动的时候,whoami等一系列系统命令都可以运行,但是只要切换到root 用户运行系统命令就会提示 Operation not permitted
;
这个莫名其妙的bug完全超越了我对 Linux 系统的理解,起初怀疑是在/bin/sh或者是kernel中埋入了一些小trick,但是IDA一顿逆向也没有发现任何的异常,于是我放弃了,想想能不能用其他的途径在 uid = shell的情况下把root能力补充上
目前能够做到的包括:
gid = root
: 已经具备了大部分目录的访问权限,但是,无法读写app的私有文件;typepermissive sh
: 这赋予了hdc shell不被selinux规则拦截的权限;自定义 capability : 在hdcd.cfg文件中,可以任意定义 capability;
uid = shell
: 与root权限差距最大的部分;
最有操作空间的部分就是任意添加 capability 了,其中有几个对安全研究非常又帮助的
CAP_SYS_PTRACE : 调试进程权限;
CAP_FOWNER : 忽略文件所有权;
CAP_SETUID : 设置进程 uid;
CAP_SETGID : 设置进程 gid;
CAP_SETUID/CAP_SETGID 这两个 capability 非常的吸引人,在之前获取Android root的blog中,在缺少了 seccomp-bpf 规则下,基于这两个 capability 就可以 fork 一个uid=root的进程;
给hdcd赋予了一系列capability之后,基于类似的原理来试试将 hdcd shell 的子进程uid设置为root
1 |
|
使用 DevEco 提供的 clang++ 编译之后上传到 /data/local/tmp 目录下运行:
1 | id |
神奇的一幕发生了,fork出来的root进程居然不会提示 Operation not permitted;原本只是想分析下抛出错误的原因,没想到误打误撞拿到了root …
虽然通过上面一步步踩坑终于在最后拿到了完整的Root Shell,但还是不知道产生这一系列BUG的原因,直到看到其他 blog 直接用hdcd.root.cfg覆盖了原本的配置, 才发现了问题的根因:
1 | "services" : [{ |
Capabilities 是将 root 权限分解成多个独立的能力赋予给非root用户。原本root用户就具备了所有的Capability,然而 "caps" : ["CAP_NET_RAW"]
导致 init 进程又给已经是root用户的hdcd重新赋予了Capability,导致 hdcd 的一系列崩溃;
实际上,移除掉 caps 的部分再重新打包 qcow2 即可